フロントエンドとバックエンドのインターフェース 長谷川啓 2019.03.08
コード生成するために最低限必要なバックエンドの函数 (1) コード生成するために最低限必要なバックエンドの函数 (1) int generator_seed(); フロントエンドをコンパイルしたコンパイラと同じコンパイラによってバックエンドがコンパイルされていることを確認するための函数。また、Cコンパイラのバックエンドが、例えばC++コンパイラのバックエンドとして誤って使われないようにするための函数。 他の動作しているバックエンドと同じ値を返すようにしてください。 int generator_open_file(const char* fn); コード生成の結果を書き込むファイルをオープンするための函数。正常にオープンできた場合は 0 を、そうでなければ 0 以外を返す。
コード生成するために最低限必要なバックエンドの函数 (2) コード生成するために最低限必要なバックエンドの函数 (2) struct interface_t { const scope* m_root; // 記号表 fundef* m_func; // 函数(名前、型等)とパラメータスコープ vector<tac*> m_code; // 函数の 3 番地コード }; void generator_generate(const interface_t* info); 函数のコード生成が必要なタイミングで呼び出される。 あるいは、ファイルの最後で記号表をコードに反映しなければならないときに呼び出される。この場合は interface_t::m_func は 0 で呼び出される。 m_func が 0 でないならば m_root->m_children.back() == m_func->m_param がフロントエンドによって保証される。 m_root->m_children.size() == 1 は C++ コンパイラでは成立しない
コード生成するために最低限必要なバックエンドの函数 (3) コード生成するために最低限必要なバックエンドの函数 (3) int generator_close_file(); generator_open_file でオープンしたファイルをクローズする。正常にクローズできた場合は 0 を、そうでなければ 0 以外を返す。
コード生成するためのオプショナルな函数 (1) void generator_option(int argc, const char** argv, int* error); バックエンドにオプションを受け渡すための函数。argv[0] はジェネレータのパス名が指定され、argv[1] 以降にオプションの文字列がセットされる。 error[0], ..., error[argc-1] に 0 以外の値を書き込むことによってオプションの処理でエラーが発生したことをフロントエンドに伝える。
コード生成するためのオプショナルな函数 (2) void generator_spell(void*); 以前は全く別の目的で使用されていた。VC++6.0時代のソースを参照。 現状では、フロントエンドで 3 番地コードのダンプをするための函数のポインタがこの函数を使用して渡されている。 int generator_sizeof(int id); 引数 id の表す型のサイズをバックエンドに問い合わせる。この呼び出しはコード生成に先立って連続して行われる。id は以下のいずれか: type:: SHORT, type:: INT, type:: LONG, type:: LONGLONG, type:: FLOAT, type:: DOUBLE, type:: LONG_DOUBLE, type:: POINTER この函数が定義されていない場合は、各型のサイズはフロントエンドをコンパイルしたコンパイラと同じサイズになる。
コード生成するためのオプショナルな函数 (3) int generator_sizeof_type() sizeof 演算子の結果の型を type::UINT, type::ULONG, type::ULONGLONG のいずれかを返すことによって決定する。この函数が定義されていない場合は sizeof 演算子の結果の型は unsigned int になる。generator_sizeof と同様にコード生成に先立って1回だけ呼び出される。 int generator_wchar_type() wchar_t の型を type::SHORT, type::USHORT, type::INT, type::UINT, type::LONG, type::ULONG のいずれかを返すことによって決定する。この函数が定義されていない場合は wchar_t の型は unsigned short int になる。generator_sizeof と同様にコード生成に先立って1回だけ呼び出される。 int generator_ptrdiff_type() ptrdiff_t の型を type::INT, type::LONG, type::LONGLONG のいずれかを返すことによって決定する。この函数が定義されていない場合は ptrdiff_t の型は long になる。generator_sizeof と同様にコード生成に先立って1回だけ呼び出される。 bool require_align(); 構造体のレイアウトを決める際にアラインメントが必要でない場合に false を返すようにする。定義しないか、定義して true を返せばアラインメントされてメンバのオフセットが決定される。
コード生成するためのオプショナルな函数 (4) struct long_double_t { void (*bit)(unsigned char* res, const char* str); void (*add)(unsigned char* x, const unsigned char* y); // x := x + y void (*sub)(unsigned char* x, const unsigned char* y); // x := x - y void (*mul)(unsigned char* x, const unsigned char* y); // x := x * y void (*div)(unsigned char* x, const unsigned char* y); // x := x / y void (*minus)(unsigned char* x, const unsigned char* y); // x := -y bool (*zero)(const unsigned char*); double (*to_double)(const unsigned char*); void (*from_double)(unsigned char*, double); bool (*cmp)(goto3ac::op, const unsigned char*, const unsigned char*); }; long_double_t* generator_long_double(); フロントエンドがコンパイル時に行う long double の演算をバックエンドで行うための函数。long_double_t::bit はlong double 型のリテラル(例えば 1.5L など)のビット表現を取得するときに呼び出される。
その他 struct last_interface_t { }; scope* m_root; // 記号表 vector<pair<const fundef*, vector<tac*> >* m_funcs; // 函数と三番地コードの全体 }; void generator_last(last_interface_t* info); ファイルの末尾に到達したときに呼び出される。記号表と函数全体をバックエンドで参照できるようにする。この函数を定義すると、本来解放されるはずのコンパイル時のヒープ領域は解放されない。コード生成のためだけであれば generator_generate を使用するとよい。 C コンパイラでは m_root->m_children.size() == m_funcs->size() であり m_root->children[i] は各 m_funcs[i].first->m_param になっている。
x := y x の型は不完全型ではない。また函数型ではない。 型修飾子なしの x の型がポインタでないならば、型修飾子なしの y の型と compatible である。 型修飾子なしの x の型が型 T1 へのポインタならば、 y は 0 であるか、 型修飾子なしの y の型は型 T2 へのポインタ型で、 T1 の型修飾子なしの型 T’1 と T2 の型修飾子なしの型 T’2 は compatible である。
x := y + z y の型も z の型も格上げされている。 y の型も z の型も算術型ならば、算術変換が行われている。型修飾子なしの x の型は 型修飾子なしの y の型と等価である。 型修飾子なしの y の型がポインタであれば、z の型は整数型であり、型修飾子なしの z の型がポインタであれば y の型は整数型である。前者の場合、x の型と y の型には x := y の条件 3.2 が成立する。
x := y - z 加算の 1, 2と同じ。 型修飾子なしの y の型も z の型もポインタならば、y の型と z の型で x := y の 3.2 の条件が成立している。x の型は ptrdiff_t である。 型修飾子なしの y の型がポインタで z の型が整数型であれば x の型と y の型で x := y の 3.2 の条件が成立している。
x := y * z 加算の 1, 2 と同じ。 y の型も z の型も算術型でなくてはならない。
x := y % z 加算の 1, 2 と同じ。 y の型も z の型も整数型でなくてはならない。
x := y << z, x := y >> z
x := y & z, x := y ^ z, x := y | z 加算の 1, 2 と同じ。
x := -y y の型は格上げされた型である。 y は算術型である。型修飾子なしの x の型は型修飾子なしの y の型と等価となる。
x := ~y y の型は格上げされた型である。 y は整数型である。型修飾子なしの x の型は型修飾子なしの y の型と等価となる。
x := (T)y x の型も y の型もスカラー型である。 型修飾子なしの x の型は T と 等価である。
param y y の型は函数型ではない。 y の型は不完全型も許される。バックエンドは以下のように完全型に変換することで y のサイズを求めることができる。 const type* T = y->m_type; T = T->complete_type(); int size = T->size(); 後続する call との間には param 以外の 3 番地コードが現れることはない。
x := call y, call y 修飾子なしの y の型は函数へのポインタ型か函数型である。 修飾子なしの x の型は函数の戻り値の型と compatible である。 x の型は不完全型ではない。 函数の戻り値の型が void 型以外でも x := call y ではなく call y が生成されることがある。
return y, return return y がある函数の 3 番地コードの一つであるとき、y の型はその函数の戻り値の型と compatible な型で、完全型である。
goto to, to to は 3 番地コードのうちの一つで goto により参照された to は、同じ函数の 3 番地コードの中にあることがフロントエンドによって保証される。
if y op z goto to op は ==, !=, <, >, <=, >= のいずれか 以下のいずれかが成立 y と z は算術変換されている 型修飾子なしの y の型がポインタ型ならば、y の型と z の型で x := y における条件 3.2 が成立する。 op が ==, != の場合、型修飾子なしの y の型がポインタならば z は 0 でもよい。型修飾子なしの z の型がポインタならば y は 0 でもよい。
x := &y y は定数ではない。 x の型と y の型へのポインタとの間に x := y における条件 3.2 が成立する。
*y := z, x := *y z の型、x の型は函数型以外の完全型である。この型を T とすると y の型は型 T へのポインタか、型修飾子付きの T へのポインタである。
x[y] := z (1) x := y[z] (2) オフセットを表す (1) の y と (2) の z は整数型である。
x := alloca y x の型は variable length array である。 x が参照されるのは x := alloca y の後である。 y の型は整数型である。
X := asm string, Y, R X はこの 3 番地コードにより変更される可能性のあるものすべてを表す。
x := va_start y, x := va_arg y, T, va_end y va_start(x,y), x = va_arg(y,T), va_end(y) にそれぞれ対応している。