1-7 コード生成2


前回は構文木から IA32 用のアセンブラ命令を出力するコード生成器を作成した。 今回はさらに配列も操作できるようにする。


配列の実装

Cの配列は連続するメモリ領域にとった変数である。例えば局所変数として、 と宣言すると、スタック・フレーム内に a[0] から a[9] までの変数が連続して割り当てられる。例えばスタックフレームの末尾から 3 番目 "-8(%ebp)" から 12 番目 "-44(%ebp)" までに割り当てられたとする。

すると a[i] の内容をレジスタ %edx に読み出すためのコードは、

となる。shll は論理左シフト命令である。左へ 2bit シフトすることによって、1命令で変数 i の値を 4 倍できる。変数1つ当り 4bytes なので、4 倍することが必要である。

上のコードははじめの2行で、配列の添字 (offset) の値を4倍して、a[0]用に割り当てられたメモリのアドレスから、何bytes離れたところに a[i] 用のメモリが割り当てられているか計算する。3-5 行目は、a[i]用に割り当てられているメモリのアドレスを求めている。例では a[0] はスタック・フレームの末尾から 12 番目なので、 $ebp - 44 の値が a[0] のアドレスである。a[i] はこれに i の 4 倍を足し合わせたものである。

このコードを見ると、C言語で、配列名が a[0] へのポインタ、すなわち &a[0]、 と同一視されるのは合理的であることがわかるだろう。 3行目は、ポインタ変数 a の値を計算しているともとれる。a[i] と *(a + i) はC言語では等価であるが、*(a + i) を単純にコンパイルすると、

となり、a[i] のコンパイル結果とほぼ同じになる。


codegen.c の変更点

今回は Tiny C でも配列を使えるようにする。 構文解析器は既に配列を認識するので、codegen.c だけを書きかえればよい。

配列に関係するプログラムは、次のような構文木に変換されるのであった。

まずは ComputeDeclaration() で局所変数の個数を計算するとき、配列の場合にも正しく計算するようにする。

これによって大域変数 nLocalVars の値が正しく設定されるようになる。 (codegen.c にはこの変更は既に適用ずみ。)

変数宣言は次のようにしてコンパイルする。

宣言された変数が配列の場合は、localVars の値を 1 ではなく、配列の大きさ分増やしていることに注意。 こうすることによって、配列の大きさ分の変数が、スタック・フレーム内に連続して配置されるようになる。

残るは、配列の要素を読み出す式と配列の要素への代入式のコンパイルである。

変数の値を読み出すコードを生成する関数は CompileRead() であるが、読み出す変数として配列変数が指定された場合は、どのようなコードを生成すればよいのだろうか。

変数が配列でない場合には、regname が指定するレジスタに、読み出した変数の値を書きこめばよい。 一方、配列の場合には、varname が指定する配列の先頭要素のアドレスを、regname が指定するレジスタに書きこまなければならない。 C 言語では、配列名 k は、&k[0] (配列 k の先頭要素 k[0] へのポインタ) と同じ意味であることを思い出してほしい。 したがって生成するべきコードは次のようになる。

ここで %<reg> は regname が指定するレジスタである。 配列の先頭へのオフセット値とは、配列がスタックフレームの先頭から何 byte 目から始まるかを表す値である。 オフセット値が n ならば n 番目から始まる。

CompileRead() を上のように修正すれば、配列の要素を読み出す式のコンパイルは容易である。配列の要素の読み出し ("[]" 配列名 index式) からは、次のようなコードを生成すればよい。

「配列名」の計算は、配列名について CompileOperand() を呼べばよい。 オペランドが配列名であれば、CompileOperand() は CompileRead() を呼び出し、%edx に配列の先頭要素のアドレスを書き込むコードが生成される。 結局、他の2項演算子とほぼ同様の手順でコンパイルできる。

配列の要素への代入は、CompileAssign() を拡張して実装する。 配列要素への代入文は

という形をしているので、CompileAssign() で kind の値が LIST であれば配列であることがわかる。

配列要素への代入文のコンパイル方法は、読み出しの場合とほぼ同様で、最後の movl 命令のオペランドの順序が入れ替わる程度である。


課題7

前回の課題で作成した Tiny C コンパイラを拡張して、配列が扱えるようにせよ。

またテスト・プログラム bar.c を実際にコンパイルしてみよ。

実行結果が正しいか確認せよ。




目次へ戻る

Copyright (C) 1999-2000 Shigeru Chiba

Email: chiba@is.tsukuba.ac.jp