中間コード

コンパイラ資料
中間コード
160601a
ここまでの流れ
型検査
トークン
AST
字句解析
構文解析
属性
中間コード
レジスタ
割り付け
意味解析
Intermediate representation (IR)
ASTを抽象機械のアセンブリ言語に翻訳する
block
{
}
:=
decl
int x;
x:=x+3;
Mov r4 Loc(0)
Mov r5 r4
(x : int)
+
x
x
AST
Add r5 3
Mov r4 r5
3
IR
IRの特徴
• CPUを模した抽象機械の命令
– 一般的な命令 (詳細はあとで)
– 一般的なデータアクセス(メモリ、レジスタ、即値)
– 特定のフレームレイアウト(7章)に依存しない
• データ構造のまま(まだ機械的な編集操作が可能)
• 汎用レジスタ無限個
IRの役割
• CPUにとってのAST
– マシン非依存(マシン依存コードへの中継地点)
• レジスタの割り当てのためのデータ構造
• 最適化のためのデータ構造(オペランドも)
– mov r5 r5
同一性検出⇒除去可能
本IR独自の設計方針
• x86アセンブリに近い(マシン独立でない)
– 命令選択(instruction selection)の処理をスキッ
プできる
• kittyの機能に必要なもののみに限定
– アドレス計算(配列,ポインタの脱参照に必要)は
しない
IR (とはいってもほぼx86/アセンブリ(INTEL記法))
命令:
Mov a1 a2
Add a1 a2
Sub a1 a2
Mul a1
Div a1
Not a
Call fl
Label sl
Jmp sl
Cmp a1 a2
Je sl
Jne sl
オペランド: ()内はパラメータ
Imm(n) --- 即値n
Reg(n) --- n番目の汎用レジスタ
Formal(n) --- n番目の仮引数の場所
Arg(n) --- n番目の実引数の場所
Local(n)--- n番目の局所変数の場所
Save(n) --- レジスタ退避場所n番目
Formal, Arg,Localはメモリ(フレーム、後述)上の場所
ラベル
SimpleLabel(n)--- ジャンプ用ラベル
FunLabel(文字列) --- 関数名ラベル
a1, a2,aはオペランド flはFunLabel, slはSimpleLabel
即値へのmove、memoryからmemoryへのmoveは禁止
命令の意味
命令
# 意味
Mov a1 a2
# a2(の内容)をa1にコピー
Add a1 a2
# a1←a1+a2
Sub a1 a2
# a1←a1-a2
Mul a1
# eax ←eax ×a1 (edxを壊す)
Div a1
# eax←eax ÷a1 (edxを壊す)
Not a
#否定(bit反転)
Call funlabel
# ラベル funlabelをもつ関数を呼ぶ
Label simplelabel
# (ラベル)
Jmp simplelabel
#labelに飛ぶ
Cmp a1 a2
# a1とa2を比較(次と組で使う)→a1
Je simplelabel
#直前のCmpがa1=a2ならsimplelabelに飛ぶ
Jne simplelabel
#直前のCmpがa1!=a2ならsimplelabelに飛ぶ
Jg simplelabel
#直前のCmpがa1>a2ならsimplelabelに飛ぶ
{Jge, Jl, Jle} # {>=,<,<=} (符号つき)
命令とオペランドのデータ構造
//instr.h
class Instr{
set<Reg*> use;
set<Reg*> def;
};
class Move_ : Instr{
Access* dst;
Access* src;
};
.....
class Access{/* 当面は空 */};
Label
class Imm : Access{
int value;
};
Move
class Reg : Access{
int sn;
int RA_temp;
};
Move
class Local : Access{
int pos;
};
Add
class SimpleLabel {
int sn;
};
Move
L2:
Mov r4 Loc(0)
Mov r5 r4
Add r5 3
Mov r4 r5
SimpleLabel 2
Reg 4
Reg 5
Local 0
Reg 4
Reg 5
Imm 3
Reg 4
Reg 5
翻訳に用いる変数・関数
• 命令列(コードリスト)記録用グローバル変数
list<Instr*>* code;
• 命令用コンストラクタ兼出力記録関数(icode)
void Move(Access* d,Access* s);
Move_(d,s)を作成してコードリスト末尾に追加
(ほかの命令も同様)
※ 大文字関数名はこの用途に用いることにする。
• Access、ラベルのコンストラクタ関数(小文字名)
Reg* reg(void),
Imm* imm(int n),
SimpleLabel* new_label(void),
FunLabel* fun_label(std::string) etc.
出力アセンブリコード仕様
アセンブリ言語:MASM (32bit)
ターゲットマシン:x86
実行環境,Windows
※ IRではまだコード出力はしない
MASMの構文:レジスタ
汎用レジスタ:
eax, ebx, ecx, edx, esi, edi
スタックポインタ: esp
ベースポインタ: ebp
ptrの指す番地のdsip個上は
DWORD PTR [ptr+disp]で参照
例
スタックに1ワード(32bit)積むとespは
WRD=32bit/1バイト=4だけ減る。
積む前のスタックの最上位要素を参照するには
[esp+4] とする
MASMの構文:ニーモニック
Op Arg1, Arg2
例 mov ebx, eax
意味:レジスタeaxの値をレジスタebxにコピー
例 mov DWORD PTR [ebx+n], eax
意味:レジスタeaxの値をebxの値をアドレスのn番
地上のメモリにコピー
例 mov eax, 5
意味:即値5をレジスタeaxにコピー
gas(AT&T記法)とMASM(microsoft asm, Intel記法)は
移動の向きとオペランドの順が逆なので注意
演算子ニーモニック
算術/2 add, sub 整数 和、差
構文 add t, s
// t ← t + s
構文 sub t, s
// t ← t - s
算術/1 imul, idiv 整数 積、商
構文 imul t
// edx(上位),eax ← t×eax
論理/2 and,or
ビットごとの論理積、和
比較/2 cmp
制御/1 jmp, je, jne
スタックからポップ pop t
スタックにプッシュ push s
アセンブリソースファイルの構成
.686P ; コメント: Pentium Pro以降
.XMM ; SIMD命令
.model flat ; メモリモデル(flat = 32bit/Windows)
EXTERN printf : PROC ; 外部記号宣言
_TEXT SEGHENT
hoge DB '%d', 0aH, 00H
_TEXT ENDS
PUBLIC _fact ;公開記号宣言
_TEXT SEGMENT
_fact PROC
push ebp
push ebp, esp
…..
ret
_fact ENDP
_TEXT ENDS
END
;定数定義(文字列)
Visual Studioでアセンブリを出力する
→
→
→
→
→
Projectのプロパティー
構成プロパティ
C/C++
出力ファイル
アセンブリの出力
(「なし」以外を選ぶ)
実験1
C/C++で次のコードをコンパイルして出力コードを観察せよ。
int main(){
int x=10;
x=x*x+7;
printf(“%d\n”x);
}
MASM Reference
• MASM Reference
http://msdn.microsoft.com/enus/library/afzk3475
• Directive Reference
http://msdn.microsoft.com/enus/library/8t163bt0%28v=vs.100%29.aspx
コマンドラインからMASMを使う
Visual Studio 2010 → Visual Studio Tools
Visual Studio コマンドプロンプトを起動
>ml [options] filelist [/link] linkoptions
例: ml factorial.asm msvcrt.lib
(factorial.asmをアセンブル後、オブジェクトを生成
してCランタイムライブラリmsvcrt.libとリンク)
コマンドラインスイッチのヘルプ:
>ml /? または ml /help
Visual Studio IDEからMASMを使う
プロジェクトのプロパティー → カスタムビルドツール → コマンドライン
[Release構成]
ml /c /Fo"$(Configuration)\$(ProjectName).obj" "$(ProjectName).asm"
[Debug構成]
ml /c /Zi /Fo"$(Configuration)\$(ProjectName).obj" "$(ProjectName).asm"
プロジェクトのプロパティー → カスタムビルドツール →出力ファイル
[すべての構成]
$(OutDir)$(ProjectName).obj
プロジェクトのプロパティー → リンカー → 入力→追加の依存ファイル
[すべての構成]
msvcrt.lib
【参考】 Visual Studioの設定で使用できる変数一覧:
http://msdn.microsoft.com/ja-jp/library/c02as0cs.aspx
実験2
• 実験1で生成したアセンブリファイルをアセン
ブル・リンクして実行結果を確かめよ
x86/32bit/MASMアセンブリ練習
• 10の階乗のプログラムをMASMで書け
– while文
– 再帰
IRへの翻訳の概略
• ASTノードごとに、生成すべき命令列をicode
で記述する。
– icodeはC++コードでIRを記述する、いわば言語
内言語
– icodeの命令の引数(Access*)はC++側で準備
する
– icode命令はアセンブリをその場で書く要領
• 値をもつASTノードは生成命令列が値を置く(
仮想)レジスタを返す。
レジスタは無限個
• 各ノードで計算の中間結果(値)は新しいレジ
スタに格納する。
reg() は呼ばれるたびに新しいシリアルナンバーを
もつレジスタを作って返す。 (new_label()も同様に
新しいラベルを作って返す。)
• シリアルナンバー0~5(=REG_SIZE-1)の仮想
レジスタはあらかじめ実レジスタに割り当てて
おく
– 特定のレジスタを参照するために用いる
(precolored仮想レジスタ)
precolored の作成
翻訳のはじめにまとめて作成しておく
Reg*
Reg*
Reg*
Reg*
Reg*
Reg*
Eax=reg();
Ebx=reg();
Ecx=reg();
Edx=reg();
Esi=reg();
Edi=reg();
//r0
//r1
//r2
//r3
//r4
//r5
中間コード生成
IRVisitor : public Visitor{
ConditionIRVisitor* cv;//visitor切り替え用
void* visit(XXNode* n, void* obj);
// nを根とする木をIRに翻訳(条件式は除く)
// 式の木の場合は値を格納する場所(Access*)を返す。
}
ConditionIRVisitor : public IRVisitor{
IRVisitor* ev; //visitor切り替え用
void* visit(XXNode* n, SimpleLabel* label);
//条件式専用visitor:
//値が偽のときlabelにジャンプするIRに翻訳
}
void* IRVisitor::visit(OpNode* n, void* obj)
{
Access *t1,*t2;
Reg* rand;
t1=static_cast<Access*>(n->children[0]->accept(this,obj));
//第1被演算数を計算するコードを生成し、結果がおかれる場所をt1に代入する
t2=static_cast<Access*>(n->children[1]->accept(this,obj));
//第2被演算数を計算するコードを生成し、結果がおかれる場所をt2に代入する
switch(n->op_kind){
case PLUS_OP:
rand=reg();
//新しいレジスタをつくりrandに代入
Move(rand, t1);
//Move命令 rand ← t1 を生成
Add(rand,t2);
//Add命令 rand ← rand + t2 を生成
break;
.....
}
return rand;
}
void* IRVisitor::visit(IfNode* n, void* obj)
{
SimpleLabel* L1=new_label(); //ラベルL1を作る(まだ置かない)
SimpleLabel* L2=new_label(); //別のラベルL2を作る
n->children[0]->accept(this->cv,L1);//visitor切り替え
// 「条件式の値がfalseならL1にjumpするコード」を生成
n->children[1]->accept(this,obj); //if文のTrue部のコードを生成
Jmp(L2);
//「 ラベルL2にjumpする命令」を生成
Label(L1);
//「ラベルL1」をここ(現時点のコード末尾)に貼る
n->children[2]->accept(this,obj); //if文のFalse部のコードを生成
Label(L2);
return nullptr;
}
//「ラベルL2」をここに貼る
void* IRVisitor::visit(RelNode* n,
void* ob)
{
Acccess *t1,*t2;
t1=static_cast<Access*>
(n->children[0]->accept(this,ob));
t2=static_cast<Access*>
(n->children[1]->accept(this,ob));
Reg* rand=reg();
Move(rand,t1);
Rel(n->rel_kind, rand , t2);
return rand
}
Access* Rel(int kind, Access* left,
Access* right){
SimpleLabel *L1,*L2;
Cmp(left,right);
L1=new_label();
L2=new_label();
switch(kind){
case EQ:
Je(L1);
Move(left, imm(0));
Jmp(L2);
Label(L1);
Move(left, imm(~0));
Label(L2);
break;
...
}
return left;
}
void* ConditionIRVisitor::visit(RelNode*n,
void* Lf)
{
Acccess *t1,*t2;
t1=static_cast<Access*>
(n->children[0]->accept(this,nullptr));
t2=static_cast<Access*>
(n->children[1]->accept(this,nullptr));
Reg* rand=reg();
Move(rand, t1);
cRel(n->rel_kind, rand, t2,
static_cast<SimpleLabel*>(Lf));
return nullptr;
}
void cRel(int kind, Access* left,
Access* right, Access* Lf){
Cmp(left,right);
switch(kind){
case EQ:
Jne(Lf);
break;
...
}
}
課題 IR_visitor
関数呼び出しとリターン文を除くstatement
をIRに翻訳するプログラム
入力例
{
int x,s;
x := 100;
s := 0;
while (x <> 0) {
s := s+x;
x := x-1;
}
}
印字プログラムを(Vsitorパターンで)作
成して確かめよ。
印字例
Mov r6, 100
Mov Loc(1), r6
Mov r7, 0
Mov Loc(0), r7
L_1 :
Mov r8, Loc(1)
Cmp r8, 0
Je L_2
Mov r9, Loc(0)
Add r9, Loc(1)
Mov r10, r9
Mov Loc(0), r10
Mov r11, Loc(1)
Sub r11, 1
Mov r12, r11
Mov Loc(1), r12
Jmp L_1
L_2 :
参考(GNU assembler, gas)
コンパイルから実行ファイルまで
gccの場合
1. cc (gcc): Cを gasに翻訳(コンパイル)
foo.c  foo.s
as (Gnu Assembler, gas) :gas を再配置可能オブジ
ェクトに変換(アセンブル)
foo.s  foo.o
ld : リンカ(または、リンクエディタ)
複数のオブジェクトを結合して、実行可能形式に。
foo.o  foo.exe
調査課題
cc -S filename.c はアセンブリコードのみを生成する. いろいろなC
言語ソースファイルで試し, 出力コードを調べよ.
cc -v filename.c はgccがどのような外部プログラムを呼び出して
いるかを表示する. 試せ.
cc -v filename.c >& hoge.sh でhoge.shに格納して編集し, アセン
ブリコードfilename.sをもとに 実行可能ファイルを作成するシェ
ルスクリプトをつくれ. 引数, スイッチを削除してみてどれが必須
かしらべよ.
シェルスクリプト:
1行めに #!/usr/bin/sh と書いておけば以下の行がshから実行され
る. シェルスクリプトの引数は$1で参照できる. (ファイル名など)
出力アセンブリコード仕様
アセンブリ言語:gas (32bit)
ターゲットマシン:x86
実行環境,OS: Linux, Cygwin※など(POSIX)
※CygwinはPOSIXのみエミュレートするのでシステム
コールはない
gasの構文:レジスタ
汎用レジスタ:
%eax, %ebx, %ecx, %edx, %esi, %edi
スタックポインタ: %esp
ベースポインタ: %ebp
ptrの指す番地のdsip個上はdisp(ptr)で参照
例
スタックに1ワード(32bit)積むと%espは
WRD=32bit/1バイト=4だけ減る。
積む前のスタックの最上位要素を参照するに
は 4(%esp) とする
gasの構文:ニーモニック
Op Arg1, Arg2
例 movl %eax, %ebx
意味:レジスタ%eaxの値をレジスタ%ebxにコピー
例 movl %eax, n(%ebx)
意味:レジスタ%eaxの値を%ebxの値をアドレスの
n番地上のメモリにコピー
例 movl $5, %eax
意味:即値5をレジスタ%eaxにコピー
gas(AT&T記法)とmasm(microsoft asm, Intel記法)は
移動の向きとオペランドの順が逆なので注意
演算子ニーモニック
算術/2 addl, subl 整数 和、差
構文 addl s, t
// t + s → t
構文 subl s, t
// t - s → t
算術/1 imul, idiv 整数 積、商
構文 imul t
// t×%eax →%edx(上位),%eax
論理/2 and,or
ビットごとの論理積、和
比較/2 cmp
制御/1 jmp, je, jne
スタックからポップ popl t
スタックにプッシュ pushl s
x86/32bit/gasアセンブリ練習
Cで次のコードをコンパイル(cc -S)して出力コードを観察せよ。
int main(){
int x=10;
x=x*x+7;
printf(“%d\n”,x);
}
• 10の階乗のプログラムをgasで書け
– while文
– 再帰