190 likes | 339 Views
コンパイラ 2012 年 11 月 1 日. 酒居敬一@A468 ( sakai.keiichi@kochi-tech.ac.jp ) http://www.info.kochi-tech.ac.jp/k1sakai/Lecture/COMP/2012/index.html. 演習(予習) 学生番号: ____________ 名前: ______________. C 言語で全域的な変数を定義したとする。 この定義がコンパイラにより次のように翻訳された。 これらを次のように使うとエラーになる場合があった。 どこがエラーになるでしょうか?その理由は?.
E N D
コンパイラ2012年11月1日 酒居敬一@A468(sakai.keiichi@kochi-tech.ac.jp) http://www.info.kochi-tech.ac.jp/k1sakai/Lecture/COMP/2012/index.html
演習(予習)学生番号:____________ 名前:______________ • C言語で全域的な変数を定義したとする。 • この定義がコンパイラにより次のように翻訳された。 • これらを次のように使うとエラーになる場合があった。どこがエラーになるでしょうか?その理由は? int a = 20; int b[3] = {1, 2, 3}; a: .word 20 b: .word 1, 2, 3 int *p = b; a = 1; b = 9; p += a; a = p[0] + b[1];
演習問題のとらえかた • 名前表を作ってみる • 自動的なモノにはasやldでは対応する静的な名前はない。 • asやldでは、全域的で静的なモノの名前はアドレスでしかないが、コンパイラではその名前を区別して扱っている。 • aは変数なので、aを参照したときはaの場所のメモリの値を使う、aに代入するときにはaの場所のメモリに値を入れる。 • bは配列なので、bを参照したときはそのアドレスを使う、一方bへの代入操作はその場所に値を入れる文法にはなってない。
コード生成 • ターゲットマシンモデル。 • いわゆるスタックマシン。実例としてはJava VMやIA-32のFPU。 • レジスタの代わりに、スタックを対象に処理する。 • スタックに、データのpush/pop命令と、演算命令を作用させる。 push系 の命令 演算系 の命令 演算器 変数エリア 0 データ 1 pop系 の命令 … 2 スタック ポインタ データ 3 データ 4 … … オペランドスタック
フォンノイマン型アーキテクチャ • 構成要素 • 演算装置・制御装置・記憶装置・入出力装置・情報経路 • 動作原理 • 記憶装置からプログラムカウンタの指すアドレスから次の命令を読み込む。 • 命令の長さ分プログラムカウンタを増やす。 • 「書かれた順番に処理する」を実装している。 • 制御装置で命令を解読し、その内容に応じて各構成要素を制御する。 • フラグとして、演算装置の内部状態を持つことが多い。 • 分岐はプログラムカウンタの変更(加減算やload)。 • 条件分岐は演算装置の内部状態に基づくプログラムカウンタの変更。 • ステップ1に戻る。
記憶装置 • プロセッサに関係する記憶装置。 • レジスタ • ごく少量。最高速で高並列で動作する。 • スタック • アーキテクチャにより、実装している場合がある。 • ごく少量。最高速で動作する。 • キャッシュ • ソフトウェアからは見えない。主記憶とプロセッサの速度差をごまかすために存在する。 • 少量・超高速・高並列~中量・高速・逐次まで数種類ある。 • 主記憶 • 多量。プロセッサから見ればとてつもなく遅い。 • 高級言語が想定しているスタックは、この記憶階層に作られる。
Java VMバイトコード(データ操作) • iload index • indexの位置のローカル変数の値を取り出しオペランドスタックにプッシュする。 • iload_<n> • index nの位置のローカル変数の値を取り出しオペランドスタックにプッシュする。 • istore index • indexの位置のローカル変数にオペランドスタックからポップして格納する。 • istore_<n> • index nの位置のローカル変数にオペランドスタックからポップして格納する。 • iconst_m1 • -1をオペランドスタックにプッシュする。 • iconst_<i> • 定数iをオペランドスタックにプッシュする。 • bipush<n> • バイト定数nをオペランドスタックにプッシュする。 • sipush<n> • 短整数定数nをオペランドスタックにプッシュする。 • pop • オペランドスタックからポップする。値は捨てる。
Java VMバイトコード(算術演算) • iadd • オペランドスタックからオペランドを2個ポップし、加算結果をプッシュする。 • isub • オペランドスタックからオペランドを2個ポップし、減算結果をプッシュする。 • まず1個ポップし、つぎに「TOS -= ポップした値」処理に相当。 • mul • オペランドスタックからオペランドを2個ポップし、乗算結果をプッシュする。 • idiv • オペランドスタックからオペランドを2個ポップし、除算結果をプッシュする。 • まず1個ポップし、つぎに「TOS /= ポップした値」処理に相当。 • ineg • オペランドスタックからオペランドを1個ポップし、符号反転結果をプッシュする。
Java VMバイトコード(フロー制御) • ifeq <address> • オペランドスタックから値を1個ポップし、0ならばaddress番地に相対ジャンプ。 • ifge <address> • オペランドスタックから値を1個ポップし、正か0ならばaddress番地に相対ジャンプ。 • ifgt <address> • オペランドスタックから値を1個ポップし、正ならばaddress番地に相対ジャンプ。 • ifle <address> • オペランドスタックから値を1個ポップし、負か0ならばaddress番地に相対ジャンプ。 • iflt <address> • オペランドスタックから値を1個ポップし、負ならばaddress番地に相対ジャンプ。 • goto <address> • address番地に相対ジャンプ。 • goto_w <address> • address番地に相対ジャンプ。addressが大きいときに使う。farジャンプ相当。 • invokestatic <method> • オペランドスタックからメソッドのエントリ番号をポップし、引数をプッシュしてジャンプ。 • ireturn • メソッドの呼び出し元に戻る。
式と代入文の命令語生成 • 後順走査により、中間表現の木から逆ポーランド記法の命令語を生成する。 expressionの値が親に渡される • 演算子の優先順の他に、評価順や結合順といったことも考慮して中間表現を生成し、そこから命令語を生成する。 • Cの代入演算子では右から左へ結合する。 • 例えば、x=y=z=1;という文では、最も右の=が最初に処理される。 • =の演算の後は、その右辺の値を式の評価値とする。 = identifier expression …
結合順と評価順 • /と*は同じ優先順位なので、A/B*C の解釈としては(A/B)*C と A/(B*C) の2通りあってもいいはず。 • 加算や乗算だけの組み合わせならどちらの解釈でもいい。 • しかし、減算や除算では解釈により結果が異なってしまう。 • 「左から右」という意味は、同じ優先順位の演算が並ぶ場合、左側の演算子から順に作用させましょう、ということ。 • この場合の解釈は、(A/B)*C になる。 • 実は文法で結合順は決められている。 • AとBとCが関数呼び出しの場合など、演算対象がどの順で評価されるかについての規定は無いことが多い。 • A/B でAの値とBの値のどちらを先に求めるのか?は不定。
演算と副作用 • レジスタマシンでも、ソフトウェアスタックがある。 • push/pop命令が、特殊な形式のオペランドのmov命令の別名定義になっている場合がある。 • 「push r0」は「mov r0,@-r7」と同じオペコード。 • CやJavaには ++ や -- という単項演算子がある。 • それに対応する機械命令が存在するので、効率化のために高級言語でもそのまま演算子として使えるようにした。 • レジスタやメモリの値のincrement/decrementの命令。 • レジスタ間接メモリ参照の際のレジスタ(ポインタ)のincrement/decrement操作。さきのpush/popなど。 • 機械命令では作用する時期が明確だけど、高級言語では、その作用が終わる時期が不明確。 • たとえば、a[i] = i++;で++の作用が完了するのはいつ?
式と代入文の命令語生成 • ノードごとにバイトコードを生成 = z + y / zに関しては、代入対象zの参照、代入 する値の参照、代入操作が後順走査 各処理に対応する。 しかし、代入操作のときにzを直接参照 できるため、=ノードを探索したときに 生成されるコードの中で代入対象が 直接示されている。 1 - x 1
制御構造(条件分岐) • L-expressionとR-expressionの値を比較し、条件が成立すればstatementを実行する。 IF-statement condition L-expression R-expression statement
制御構造(繰り返しループ) • L-expressionとR-expressionを比較し、条件が成立している限りは、<statement>を繰り返し実行する。 WHILE-statement condition L-expression R-expression statement
制御構造(関数呼び出し) • まず、引数を順にスタックに積み上げる。次に、エントリーポイントを求めてcall命令でジャンプする。 • 代入の例のように関数が直接参照できる場合は、call命令生成時にエントリーポイントを示す。 call identifier arg-vector expression 実引数の数だけ expression …
引数などの渡し方 • 何かを渡すにあたっては、領域の確保と、そこへのコピーが行われる。領域はスタックに確保するか、レジスタ。 • 値渡し(Pass by Value) • オブジェクトの値をコピーして渡す。 • 受け取り側で値型として参照できる。 • 参照渡し(Pass by Reference) • オブジェクトへのアドレスを渡す。 • 名前渡し(Pass by Name) • オブジェクトの名前を渡す。
関数などの呼び出し方 • 呼ぶにあたっては、そのエントリーポイントが必要。 • 参照による呼び出し(Call by Reference) • エントリーポイントのアドレスから呼び出す。 • Cではld が名前からアドレスに置き換える。 • 動的束縛や、関数のポインタを介した呼び出し。 • 名前による呼び出し(Call by Name) • 動的リンクでは、名前により該当オブジェクトを呼び出す。 • Javaだと知らず知らずのうちに動的リンクをやっている。 • ClassNotFoundException に垣間見えるかな? • CとかJavaとは違う言語では、こういう呼び出しもある。 • schemeとかPostScriptなど。