コンパイラ資料 実行時環境
本日のメニュー: 関数の翻訳 関数によるスタック利用の詳細 関数定義・呼び出しの翻訳(AST→IR) x86 MASM assembler 本日のメニュー: 関数の翻訳 関数によるスタック利用の詳細 x86 MASM assembler スタック領域(フレーム)の管理・利用のコード 関数定義・呼び出しの翻訳(AST→IR) FUN, ARGS, CALL, RTNノード
実行時環境 activation record 関数呼び出しを持つ言語の場合 関数の呼び出しごとに割り当てられるメモリ領域のこと、またその領域に格納される情報 通常はスタックを用いるのでactivation recordのことをスタックフレームまたは単にフレームとよぶ。
関数定義と関数呼び出し int foo(int x) //関数定義、呼び出し元(親) { int y; if(x=0)y:=1; else y=x+ bar(x-1);//関数呼び出し(子) return y; //リターン文 }
今のフレーム(自分) 自分を呼び出した親のフレーム フレームのレイアウト 上: 低アドレス 下: 高アドレス 引数0 ←esp スタックポインタ 上: 低アドレス … 引数m-1 spills 今のフレーム(自分) 局所変数 退避callee-save 退避ebp ←ebp フレームポインタ 戻りアドレス 引数0 … 自分を呼び出した親のフレーム 下: 高アドレス 引数n-1 フレームのレイアウト
呼び出し動作 call push ebp mov ebp, esp sub esp, (SIZE-2)*WRD, mov [ebp – WRD ], ebx mov [ebp- 2*WRD ], esi mov [ebp - 3*WRD ], edi body ←esp mov esp, ebp pop ebp ←ebp ret
呼び出し規約 calling convention cdecl レジスタesi, edi, ebp, ebxは呼び出し前後で値が変化してはいけない。呼び出された側で使うときは退避してから使い、おわったらもどす。(callee-save) そのほかの(汎用)レジスタは呼び出し側で退避する。(caller-save) 引数の積み込み順序は最後の引数が最初
フレームサイズの決定 フレームサイズSIZE未定のまま、次を翻訳時・レジスタ割り当て時にカウントし、IR生成完了後にSIZEを埋める 使用するcallee-save registerの個数 ローカル変数の同時使用個数の最大値 レジスタからあふれた(spill)数の最大数 caller-save退避用含む 呼び出す関数の引数の最大値(関数ごと/一律)
関数定義コードの構成 prologue epilogue 関数名のラベル(第1子から取得) (第2子から引数リスト取得) 呼び出し元ベースポインタの退避 ベースポインタ更新 スタックフレームの作成 callee-saveレジスタ退避 関数本体 エピローグラベル callee-saveレジスタ復帰 スタックフレーム解放 呼び出し元ベースポインタ復帰 リターン prologue epilogue
prologue/epilogue prologue: push ebp mov ebp, esp sub esp, (SIZE-2)*WRD mov [ebp-4], ebx, mov [ebp-8], esi mov [ebp-12], edi epilogue : mov edi, [ebp-12] mov esi, [ebp-8] mov ebx, [ebp-4] mov esp, ebp pop ebp ret 使うものだけ 使ったものだけ
関数本体からの参照(IDノードで) 自分 親 仮引数 局所変数 引数kはベースポインタのk+2個下 [ebp+(k+2)*WRD] 引数0 ←esp 仮引数 引数kはベースポインタのk+2個下 [ebp+(k+2)*WRD] (k = 0,1,…) 局所変数 出現順(i=0,…)に記号表に場所を登録 [ebp-(i+S+1)*WRD] S=使用するcallee-save reg.の数 … 引数m-1 spills 自分 局所変数 退避callee-save 退避ebp ←ebp 戻りアドレス 引数0 … 親 引数n-1
リターン文(RTNノードで) 戻り値(リターン文引数の値)をeaxに置く エピローグラベルにjmp
関数呼び出し(Call) 呼び出し側利用レジスタ退避(caller-save) 実引数をespから下(高位)に向かって順に積む 引数jは[esp+j*WRD] (j=0,…) call XXX 戻り値取り出し, 値置き用レジスタ t にコピー movl t, eax 呼び出し側利用レジスタ復帰
呼び出し時の実引数格納場所 自分 親 引数jは [esp+j*WRD ] に格納する。 引数0 ←esp … 引数m-1 spills 局所変数 退避callee-save 退避ebp ←ebp 戻りアドレス 引数0 … 親 引数n-1
Accessの印字まとめ reg(n) : eax, ebx, ecx, edx, esi, edi (n=0,1,2,3,4,5) reg(n): [ebp-(WRD*(n-REG_SIZE+L+S+1)] (n>= REG_SIZE=6) formal(n) : [ebp+WRD*(n+2)] //退避ebpのn+2個下(n=0,1,...) local (n) : [ebp -(WRD*(n+S+1)) ] //退避ebpのn+S+1個上(n=0,1,...) arg (n) : [esp+WRD*n] //esp以下に積む (n=0,1,...) label(n) : L_n L=関数内の局所変数の数 S=使用したcallee-saveレジスタ数(=退避数) 上下は図の上/下(アドレスの低/高の方向)
課題:Print printf(“%d\n”, 5); print文は外部関数(Cの標準ライブラリ)を呼び出す命令列に翻訳する。 Cでつぎの関数呼び出しに対応するアセンブリコードを調べてAST/Printノードの翻訳処理を追加せよ。 printf(“%d\n”, 5); ヒント:第1引数の定数文字列を表す専用Accessを導入せよ。
課題:IR_visitor(完全版) 6章のIR_visitorに関数定義、リターン文、関数呼び出しのケースを加筆してIRへの翻訳を完成させよ。 (prologue, epilogueは定型なのでそれぞれ1つのInstrとしてよい。)