Top Banner
コココココ 2011 コ 11 コ 8 コ コココ一@A468 ([email protected] ) http://www.info.kochi-tech.ac.jp/k1sakai/Lecture/COMP/2011/index. html 1
19

コンパイラ 2011 年 11 月 8 日

Jan 08, 2016

Download

Documents

CAI

コンパイラ 2011 年 11 月 8 日. 酒居敬一@A468 ( [email protected] ) http://www.info.kochi-tech.ac.jp/k1sakai/Lecture/COMP/2011/index.html. 演習(予習) 学生番号: ____________ 名前: ______________. C 言語で全域的な変数を定義したとする。 この定義がコンパイラにより次のように翻訳された。 これらを次のように使うとエラーになる場合があった。 どこがエラーになるでしょうか?その理由は?. - PowerPoint PPT Presentation
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: コンパイラ 2011 年 11 月 8 日

コンパイラ2011年 11月 8日

酒居敬一@A468 ([email protected])

http://www.info.kochi-tech.ac.jp/k1sakai/Lecture/COMP/2011/index.html

1

Page 2: コンパイラ 2011 年 11 月 8 日

演習(予習)学生番号: ____________ 名前:______________

1. C 言語で全域的な変数を定義したとする。

2. この定義がコンパイラにより次のように翻訳された。

3. これらを次のように使うとエラーになる場合があった。どこがエラーになるでしょうか?その理由は?

2

int a = 20;int b[3] = {1, 2, 3};

a: .word 20b: .word 1, 2, 3

int *p = b;a = 1;b = 9;p += a;a = p[0] + b[1];

Page 3: コンパイラ 2011 年 11 月 8 日

演習問題のとらえかた

3

名前表を作ってみる

自動的なモノには as や ld では対応する静的な名前はない。 as や ld では、全域的で静的なモノの名前はアドレスでしか

ないが、コンパイラではその名前を区別して扱っている。 a は変数なので、 a を参照したときは a の場所のメモリの値を使う、

a に代入するときには a の場所のメモリに値を入れる。 b は配列なので、bを参照したときはそのアドレスを使う、一方 b

への代入操作はその場所に値を入れる文法にはなってない。

名前 何 型 語長 種別

a 変数 int 2 全域的・静的

b 配列 int[] 2*3 全域的・静的

p 変数 int * 2 局所的・自動的

Page 4: コンパイラ 2011 年 11 月 8 日

コード生成 ターゲットマシンモデル。

いわゆるスタックマシン。実例としては Java VM や IA-32の FPU 。

レジスタの代わりに、スタックを対象に処理する。 スタックに、データの push/pop 命令と、演算命令を作用

させる。

4

演算器

変数エリア 0

… …

データ…

データデータ

オペランドスタック

スタックポインタ

演算系の命令

push 系の命令

pop 系の命令

Page 5: コンパイラ 2011 年 11 月 8 日

フォンノイマン型アーキテクチャ 構成要素

演算装置・制御装置・記憶装置・入出力装置・情報経路 動作原理

1. 記憶装置からプログラムカウンタの指すアドレスから次の命令を読み込む。

2. 命令の長さ分プログラムカウンタを増やす。 「書かれた順番に処理する」を実装している。

3. 制御装置で命令を解読し、その内容に応じて各構成要素を制御する。 フラグとして、演算装置の内部状態を持つことが多い。 分岐はプログラムカウンタの変更(加減算や load )。

条件分岐は演算装置の内部状態に基づくプログラムカウンタの変更。

4. ステップ1に戻る。5

Page 6: コンパイラ 2011 年 11 月 8 日

記憶装置 プロセッサに関係する記憶装置。

レジスタ ごく少量。最高速で高並列で動作する。

スタック アーキテクチャにより、実装している場合がある。 ごく少量。最高速で動作する。

キャッシュ ソフトウェアからは見えない。主記憶とプロセッサの速度差を

ごまかすために存在する。 少量・超高速・高並列~中量・高速・逐次まで数種類ある。

主記憶 多量。プロセッサから見ればとてつもなく遅い。 高級言語が想定しているスタックは、この記憶階層に作られる

。6

Page 7: コンパイラ 2011 年 11 月 8 日

Java VMバイトコード ( データ操作 )

7

•iload indexindex の位置のローカル変数の値を取り出しオペランドスタックにプッシュする。

•iload_<n>index n の位置のローカル変数の値を取り出しオペランドスタックにプッシュする。

•istore indexindex の位置のローカル変数にオペランドスタックからポップして格納する。

•istore_<n>index n の位置のローカル変数にオペランドスタックからポップして格納する。

•iconst_m1-1 をオペランドスタックにプッシュする。

•iconst_<i>定数 i をオペランドスタックにプッシュする。

•bipush<n>バイト定数 n をオペランドスタックにプッシュする。

•sipush<n>短整数定数 n をオペランドスタックにプッシュする。

•popオペランドスタックからポップする。値は捨てる。

Page 8: コンパイラ 2011 年 11 月 8 日

Java VMバイトコード ( 算術演算 )

8

•iaddオペランドスタックからオペランドを2個ポップし、加算結果をプッシュする。

•isubオペランドスタックからオペランドを2個ポップし、減算結果をプッシュする。まず1個ポップし、つぎに「TOS -= ポップした値」処理に相当。

•mulオペランドスタックからオペランドを2個ポップし、乗算結果をプッシュする。

•idivオペランドスタックからオペランドを2個ポップし、除算結果をプッシュする。まず1個ポップし、つぎに「TOS /= ポップした値」処理に相当。

•inegオペランドスタックからオペランドを1個ポップし、符号反転結果をプッシュする。

Page 9: コンパイラ 2011 年 11 月 8 日

Java VMバイトコード ( フロー制御 )

9

•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メソッドの呼び出し元に戻る。

Page 10: コンパイラ 2011 年 11 月 8 日

式と代入文の命令語生成 後順走査により、中間表現の木から逆ポーランド記法の

命令語を生成する。    expression の値が親に渡される

演算子の優先順の他に、評価順や結合順といったことも考慮して中間表現を生成し、そこから命令語を生成する。 Cの代入演算子では右から左へ結合する。

例えば、 x=y=z=1; という文では、最も右の = が最初に処理される。 = の演算の後は、その右辺の値を式の評価値とする。

10

identifier expression

Page 11: コンパイラ 2011 年 11 月 8 日

参考: C の演算子の優先度と結合規則

11

演算子 結合規則

() [] -> . 左から右 関数参照、配列参照、構造体参照

! ~ ++ -- + - * & ( 型名 ) sizeof 右から左 いずれも単項演算子

* / % 左から右 乗算・除算・剰余の二項演算子

+ - 左から右 加算・減算の二項演算子

<< >> 左から右 左シフト・右シフトの二項演算子

< <= > >= 左から右 大小を比較する二項演算子

== != 左から右 同値を判定する二項演算子

& 左から右 ビットごとANDの二項演算子

^ 左から右 ビットごとXORの二項演算子

| 左から右 ビットごとORの二項演算子

&& 左から右 AND条件の二項演算子

|| 左から右 OR条件の二項演算子

?: 右から左 条件による選択の三項演算子

= += -= *= /= %= &= ^= |= <<= >>=

右から左 代入の二項演算子

, 左から右 連接の二項演算子

Page 12: コンパイラ 2011 年 11 月 8 日

結合順と評価順 /と*は同じ優先順位なので、 A/B* C  の解釈としては

(A/B)* C  と  A/(B* C) の2通りあってもいいはず。 加算や乗算だけの組み合わせならどちらの解釈でもいい。 しかし、減算や除算では解釈により結果が異なってしまう。 「左から右」という意味は、同じ優先順位の演算が並ぶ場合、左側の演算子から順に作用させましょう、ということ。 この場合の解釈は、 (A/B)* C  になる。

実は文法で結合順は決められている。

AとBとCが関数呼び出しの場合など、演算対象がどの順で評価されるかについての規定は無いことが多い。 A/B でAの値とBの値のどちらを先に求めるのか?は不定。

12

Page 13: コンパイラ 2011 年 11 月 8 日

演算と副作用 レジスタマシンでも、ソフトウェアスタックがある。

push/pop 命令が、特殊な形式のオペランドのmov 命令の別名定義になっている場合がある。

「 push r0 」は「 mov r0,@-r7 」と同じオペコード。

Cや Java には ++ や -- という単項演算子がある。 それに対応する機械命令が存在するので、効率化のために

高級言語でもそのまま演算子として使えるようにした。 レジスタやメモリの値の increment/decrement の命令。 レジスタ間接メモリ参照の際のレジスタ(ポインタ)の

increment/decrement 操作。さきの push/pop など。 機械命令では作用する時期が明確だけど、高級言語

では、その作用が終わる時期が不明確。 たとえば、 a[i] = i++; で ++ の作用が完了するのはいつ?

13

Page 14: コンパイラ 2011 年 11 月 8 日

ノードごとにバイトコードを生成 

式と代入文の命令語生成

14

① iload 2 // y

② iconst_1

③ iload 1 // x

④ iconst_1

⑤ isub

⑥ idiv

⑦ iadd

⑧ istore 2 // z

+

=

/

z

y

1 -

x 1

z に関しては、代入対象zの参照、代入する値の参照、代入操作が後順走査各処理に対応する。しかし、代入操作のときにzを直接参照できるため、=ノードを探索したときに生成されるコードの中で代入対象が直接示されている。

Page 15: コンパイラ 2011 年 11 月 8 日

制御構造(条件分岐) L-expression と R-expression の値を比較し、

条件が成立すれば statement を実行する。

15

IF-statement

L-expression R-expression statement

condition

<condition> の実行オブジェクトコード

(L-expression の計算コード

R-expression の計算コード

比較演算コード )

条件付分岐命令

<condition> が不成立のとき前方の9へジャンプ

<statement> の実行コード

9:

Page 16: コンパイラ 2011 年 11 月 8 日

制御構造(繰り返しループ) L-expression と R-expression を比較し、条件が

成立している限りは、 <statement> を繰り返し実行する。

16

WHILE-statement

L-expression R-expression statement

condition

無条件分岐  1f // 条件判定部分へジャンプ

0: // ループボディーの開始位置

<statement> の実行オブジェクト

1: <condition> の実行オブジェクト

条件分岐 0b // 繰り返し条件成立時は後方の 0へジャンプ

9: // ループの終了。

Page 17: コンパイラ 2011 年 11 月 8 日

制御構造(関数呼び出し) まず、引数を順にスタックに積み上げる。

次に、エントリーポイントを求めて call 命令でジャンプする。

代入の例のように関数が直接参照できる場合は、call 命令生成時にエントリーポイントを示す。

17

call

expression

expression

arg-vectoridentifier

…実引数の数だけ

Page 18: コンパイラ 2011 年 11 月 8 日

引数などの渡し方 何かを渡すにあたっては、領域の確保と、そこへのコピ

ーが行われる。領域はスタックに確保するか、レジスタ。

値渡し( Pass by Value) オブジェクトの値をコピーして渡す。 受け取り側で値型として参照できる。

参照渡し( Pass by Reference) オブジェクトへのアドレスを渡す。

名前渡し( Pass by Name) オブジェクトの名前を渡す。

18

Page 19: コンパイラ 2011 年 11 月 8 日

関数などの呼び出し方 呼ぶにあたっては、そのエントリーポイントが必要。

参照による呼び出し( Call by Reference) エントリーポイントのアドレスから呼び出す。

Cでは ld が名前からアドレスに置き換える。 動的束縛や、関数のポインタを介した呼び出し。

名前による呼び出し( Call by Name) 動的リンクでは、名前により該当オブジェクトを呼び出す

。 Javaだと知らず知らずのうちに動的リンクをやっている。

ClassNotFoundException に垣間見えるかな? Cとか Java とは違う言語では、こういう呼び出しもある。

scheme とか PostScript など。19