コンパイラ 2012年11月1日 酒居敬一@A468(sakai.keiichi@kochi-tech.ac.jp) http://www.info.kochi-tech.ac.jp/k1sakai/Lecture/COMP/2012/index.html.

Slides:



Advertisements
Similar presentations
1 B10 CPU を作る 1 日目 解説 TA 高田正法
Advertisements

2.5 プログラムの構成要素 (1)文字セット ① ASCII ( American Standard Code for Interchange ) JIS コードと同じ ② EBCDIC ( Extended Binary Coded Decimal for Information Code ) 1.
プログラミング演習II 2004年11月 30日(第6回) 理学部数学科・木村巌.
コンパイラ 2012年10月25日
計算機システムⅡ 主記憶装置とALU,レジスタの制御
実行時のメモリ構造(1) Jasminの基礎とフレーム内動作
コンパイラ 第9回 コード生成 ― スタックマシン ―
プログラミング言語論 第6回 型 情報工学科 篠埜 功.
第2回:Javaの変数と型の宣言 プログラミングII 2007年10月2日.
2012年度 計算機システム演習 第4回 白幡 晃一.
計算機システムⅡ 命令セットアーキテクチャ
アルゴリズムとデータ構造 2011年6月13日
第4回放送授業.
  【事例演習6】  数式インタプリタ      解 説     “インタプリタの基本的な仕組み”.
プログラムはなぜ動くのか.
2016年度 プログラミングⅠ ~ 内部構造と動作の仕組み(1) ~.
コンピュータの原理 1E17M053-9 奈良 皐佑 1E17M070-7 師尾 直希        1E17M078-6 渡邊 惇.
岩村雅一 知能情報工学演習I 第8回(後半第2回) 岩村雅一
コンパイラ 2011年10月27日
プログラミング言語入門 手続き型言語としてのJava
勉強会その3    2016/5/1 10 8分35秒 データの表現 演算.
アルゴリズムとデータ構造1 2006年6月16日
コンピュータ系実験Ⅲ 「ワンチップマイコンの応用」 第1週目 アセンブリ言語講座
プログラミング言語入門.
Integer Java Virtual Machine
岩村雅一 知能情報工学演習I 第8回(C言語第2回) 岩村雅一
コンパイラ 2012年11月15日
アルゴリズムとデータ構造1 2005年6月28日
第7回 プログラミングⅡ 第7回
復習 前回の関数のまとめ(1) 関数はmain()関数または他の関数から呼び出されて実行される.
プログラミング言語論 第五回 理工学部 情報システム工学科 新田直也.
アルゴリズムとデータ構造 補足資料5-1 「メモリとポインタ」
オブジェクト指向プログラミングと開発環境
コンパイラ 2011年10月20日
オブジェクト指向言語論 第六回 知能情報学部 新田直也.
プログラミング言語論 第六回 理工学部 情報システム工学科 新田直也.
JAVAバイトコードにおける データ依存解析手法の提案と実装
コンピュータアーキテクチャ 第 2 回.
コンピュータアーキテクチャ 第 4 回.
オブジェクト指向言語論 第三回 知能情報学部 新田直也.
2013年度 プログラミングⅠ ~ 内部構造と動作の仕組み(2) ~.
2017年度 プログラミングⅠ ~ 内部構造と動作の仕組み(1) ~.
ポインタとポインタを用いた関数定義.
コンパイラ 2012年10月29日
アルゴリズムとデータ構造 2012年6月11日
プログラミング論 ポインタ
コンピュータアーキテクチャ 第 2 回.
コンピュータアーキテクチャ 第 5 回.
計算機アーキテクチャ1 (計算機構成論(再)) 第二回 命令の種類と形式
アルゴリズムとデータ構造1 2009年6月15日
コンピュータアーキテクチャ 第 4 回.
第5回 プログラミングⅡ 第5回
オブジェクト指向言語論 第五回 知能情報学部 新田直也.
情報処理Ⅱ 第7回 2004年11月16日(火).
コンピュータアーキテクチャ 第 5 回.
情報処理Ⅱ 2005年10月28日(金).
オブジェクト指向言語論 第二回 知能情報学部 新田直也.
コンパイラ 2012年10月11日
コンパイラ 第12回 実行時環境 ― 変数と関数 ― 38号館4階N-411 内線5459
アルゴリズムとデータ構造 2010年6月17日
2014年度 プログラミングⅠ ~ 内部構造と動作の仕組み(1) ~.
演算子のオーバーロード.
2005年度 データ構造とアルゴリズム 第2回 「C言語の復習:配列」
情報処理Ⅱ 2005年11月25日(金).
プログラミング演習II 2004年11月 2日(第3回) 理学部数学科・木村巌.
情報処理Ⅱ 小テスト 2005年2月1日(火).
情報処理Ⅱ 2006年10月27日(金).
オブジェクト指向言語論 第六回 知能情報学部 新田直也.
プログラミング 3 ポインタ(1).
6.5 最終コード生成 (1)コードの形式 ①絶対2進コード(AB : absolute binary) 命令後のオペランドが絶対番地指定。
Presentation transcript:

コンパイラ 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では対応する静的な名前はない。 aは変数なので、aを参照したときはaの場所のメモリの値を使う、 aに代入するときにはaの場所のメモリに値を入れる。 bは配列なので、bを参照したときはそのアドレスを使う、一方b への代入操作はその場所に値を入れる文法にはなってない。 名前 何 型 語長 種別 a 変数 int 2 全域的・静的 b 配列 int[] 2*3 p int * 局所的・自動的 asで書いたときには同一であるにもかかわらず、aとbは参照する(使う・見る)という操作でさえも区別されている。

コード生成 ターゲットマシンモデル。 いわゆるスタックマシン。実例としてはJava VMやIA-32のFPU。 レジスタの代わりに、スタックを対象に処理する。 スタックに、データのpush/pop命令と、演算命令を作用させる。 演算器 変数エリア 0 1 2 3 4 … データ オペランドスタック スタック ポインタ 演算系 の命令 push系 pop系

フォンノイマン型アーキテクチャ 構成要素 動作原理 演算装置・制御装置・記憶装置・入出力装置・情報経路 記憶装置からプログラムカウンタの指すアドレスから 次の命令を読み込む。 命令の長さ分プログラムカウンタを増やす。 「書かれた順番に処理する」を実装している。 制御装置で命令を解読し、その内容に応じて各構成要素を 制御する。 フラグとして、演算装置の内部状態を持つことが多い。 分岐はプログラムカウンタの変更(加減算やload)。 条件分岐は演算装置の内部状態に基づくプログラムカウンタの変更。 ステップ1に戻る。

記憶装置 プロセッサに関係する記憶装置。 レジスタ スタック キャッシュ 主記憶 ごく少量。最高速で高並列で動作する。 アーキテクチャにより、実装している場合がある。 ごく少量。最高速で動作する。 キャッシュ ソフトウェアからは見えない。主記憶とプロセッサの速度差を ごまかすために存在する。 少量・超高速・高並列~中量・高速・逐次まで数種類ある。 主記憶 多量。プロセッサから見ればとてつもなく遅い。 高級言語が想定しているスタックは、この記憶階層に作られる。 キャッシュは不可視なのでおいとくとして、記憶装置固有のアドレッシングが存在する。 実動する製品は、妥協の産物。いきなりコード生成は難しいので通常は中間言語という理想的なアーキテクチャを仮定してコンパイルをする。

Java VMバイトコード(データ操作) iload 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個ポップし、符号反転結果をプッシュする。 演算対象がTOSから順に固定されている。だから、オペランドとして明記しない。

Java VMバイトコード(フロー制御) ifeq <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 = …

参考:Cの演算子の優先度と結合規則 演算子 結合規則 () [] -> . 左から右 関数参照、配列参照、構造体参照 () [] -> . 左から右 関数参照、配列参照、構造体参照 ! ~ ++ -- + - * & (型名) sizeof 右から左 いずれも単項演算子 * / % 乗算・除算・剰余の二項演算子 + - 加算・減算の二項演算子 << >> 左シフト・右シフトの二項演算子 < <= > >= 大小を比較する二項演算子 == != 同値を判定する二項演算子 & ビットごとANDの二項演算子 ^ ビットごとXORの二項演算子 | ビットごとORの二項演算子 && AND条件の二項演算子 || OR条件の二項演算子 ?: 条件による選択の三項演算子 = += -= *= /= %= &= ^= |= <<= >>= 代入の二項演算子 , 連接の二項演算子

結合順と評価順 /と*は同じ優先順位なので、A/B*C の解釈としては (A/B)*C と A/(B*C) の2通りあってもいいはず。 加算や乗算だけの組み合わせならどちらの解釈でもいい。 しかし、減算や除算では解釈により結果が異なってしまう。 「左から右」という意味は、同じ優先順位の演算が並ぶ場合、 左側の演算子から順に作用させましょう、ということ。 この場合の解釈は、(A/B)*C になる。 実は文法で結合順は決められている。 AとBとCが関数呼び出しの場合など、演算対象がどの順 で評価されるかについての規定は無いことが多い。 A/B でAの値とBの値のどちらを先に求めるのか?は不定。 文法の中で結合順は考慮されている。評価順は文法に書かれておらず、実際にはコード生成のときに決まる。

演算と副作用 レジスタマシンでも、ソフトウェアスタックがある。 CやJavaには ++ や -- という単項演算子がある。 push/pop命令が、特殊な形式のオペランドの mov命令の別名定義になっている場合がある。 「push r0」は「mov r0,@-r7」と同じオペコード。 CやJavaには ++ や -- という単項演算子がある。 それに対応する機械命令が存在するので、効率化のために 高級言語でもそのまま演算子として使えるようにした。 レジスタやメモリの値のincrement/decrementの命令。 レジスタ間接メモリ参照の際のレジスタ(ポインタ)の increment/decrement操作。さきのpush/popなど。 機械命令では作用する時期が明確だけど、高級言語 では、その作用が終わる時期が不明確。 たとえば、a[i] = i++;で++の作用が完了するのはいつ?

式と代入文の命令語生成 ノードごとにバイトコードを生成 ① iload 2 // y ② iconst_1 ③ iload 1 // x ノードごとにバイトコードを生成  ① iload 2 // y ② iconst_1 ③ iload 1 // x ④ iconst_1 ⑤ isub ⑥ idiv ⑦ iadd ⑧ istore 2 // z + = / z y 1 - x zに関しては、代入対象zの参照、代入 する値の参照、代入操作が後順走査 各処理に対応する。 しかし、代入操作のときにzを直接参照 できるため、=ノードを探索したときに 生成されるコードの中で代入対象が 直接示されている。

制御構造(条件分岐) L-expressionとR-expressionの値を比較し、 条件が成立すればstatementを実行する。 IF-statement L-expression R-expression statement condition <condition>の実行オブジェクトコード (L-expressionの計算コード R-expressionの計算コード 比較演算コード) 条件付分岐命令 <condition>が不成立のとき前方の9へジャンプ <statement>の実行コード 9: 実験では@next相当のラベルに、ローカルラベルを使った。そしてオフセットはasかldが計算する(バックパッチ)。

制御構造(繰り返しループ) L-expressionとR-expressionを比較し、条件が成立 している限りは、<statement>を繰り返し実行する。 WHILE-statement L-expression R-expression statement condition 無条件分岐 1f // 条件判定部分へジャンプ 0: // ループボディーの開始位置 <statement>の実行オブジェクト 1: <condition>の実行オブジェクト 条件分岐 0b // 繰り返し条件成立時は後方の0へジャンプ 9: // ループの終了。

… 制御構造(関数呼び出し) まず、引数を順にスタックに積み上げる。 次に、エントリーポイントを求めてcall命令でジャンプする。 expression arg-vector identifier … 実引数の数だけ その場合はldなどのリンケージエディタがエントリーポイントへのオフセットを計算する。

引数などの渡し方 何かを渡すにあたっては、領域の確保と、そこへのコピー が行われる。領域はスタックに確保するか、レジスタ。 何かを渡すにあたっては、領域の確保と、そこへのコピー が行われる。領域はスタックに確保するか、レジスタ。 値渡し(Pass by Value) オブジェクトの値をコピーして渡す。 受け取り側で値型として参照できる。 参照渡し(Pass by Reference) オブジェクトへのアドレスを渡す。 名前渡し(Pass by Name) オブジェクトの名前を渡す。

関数などの呼び出し方 呼ぶにあたっては、そのエントリーポイントが必要。 参照による呼び出し(Call by Reference) エントリーポイントのアドレスから呼び出す。 Cではld が名前からアドレスに置き換える。 動的束縛や、関数のポインタを介した呼び出し。 名前による呼び出し(Call by Name) 動的リンクでは、名前により該当オブジェクトを呼び出す。 Javaだと知らず知らずのうちに動的リンクをやっている。 ClassNotFoundException に垣間見えるかな? CとかJavaとは違う言語では、こういう呼び出しもある。 schemeとかPostScriptなど。