それでは,仮想機械コードをARMアセンブリコードへ変換する具体的な方法について説明する.
まず,仮想機械コードの(Vm.operand
型の)オペランドの変換は,以下の細かな点を除いて講義テキストのと同じである.
-
引数は2つまで受け取る可能性がある.これまでに説明したとおり,第1引数はa1レジスタ,第2引数はa2レジスタに含まれている.
-
番目の局所変数(
Vm.Local
)は,前節の図のレイアウトに従い,フレームポインタfpからの相対アドレス[fp, #-4(
+2)]
によって参照する. -
MIPSの
move
,lw
命令には,ARMのmov
,ldr
命令がそれぞれ対応する.また,MIPSのli
命令もARMのmov
命令に対応する.
これらの違いを踏まえると,を実装するOCamlの関数gen_operand
は次のように定義できる.ただし,(* <= 1*)
行目のparam_to_reg
は,番目の引数に対応するレジスタを返す関数とする.
let mem_access reg offset = RI (reg, offset * 4) (* <= 2 *)
let local_access i = mem_access Fp (-i-2) (* <= 3 *)
let gen_operand rd = function
Vm.Param i ->
let rs = param_to_reg i in (* <= 1 *)
if rd = rs then [] else [Instr (Mov (rd, (R rs)))]
| Vm.Local i -> [Instr (Ldr (rd, local_access i))]
| Vm.Proc lbl -> [Instr (Ldr (rd, L lbl))]
| Vm.IntV i -> [Instr (Mov (rd, I i))]
(* <= 2 *)
行目のmem_access
関数は,レジスタreg
中に含まれているメモリアドレスからoffset
ワード目を指すaddr
を返し,(* <= 3 *)
行目のlocal_access
関数は番目の局所変数を指す(fpレジスタ相対の)addr
を返す.これらの2つの関数は,この後の仮想機械コードの命令や関数宣言からアセンブリコードを生成する際にも利用できる.
オペランドの場合と同様,基本的には講義テキストの,を参考にすれば,仮想機械コードの命令(Vm.instr
)と仮想機械コードの関数宣言(Vm.decl
)に対応するARMアセンブリコードを生成することは難しくない.
move t
,
空いている適当なレジスタを使用して:
opをrに格納str
r,
局所変数idのアドレス
というアセンブリ命令列を生成する.
t
,
,
がadd
またはmul
の場合には,空いている適当なレジスタ,を使用して:
op1をr1に格納 op2をr2に格納 bopr1
,
r1,
r2str
r1,
局所変数idのアドレス
というアセンブリ命令列を生成する.
がlt
の場合には,上の命令列と殆ど同じだが,「
,
,
」の行は代わりに:
cmp
r1,
r2blt
l1mov
r1, #0
b
l2 l1:
mov
r1, #1
l2:
の命令列とし,比較結果に従って定数かを代入するコードを生成する.ここで,,はフレッシュなラベルである.
:
同じ名前のラベルをアセンブリコード中の同じ位置に置くだけで良い.
bif
,
空いている適当なレジスタを使用して:
opをrに格納cmp
r, 0
bne
l
というアセンブリ命令列を生成する.
goto
アセンブリ命令b
を生成する.
call t
,
(
)
呼出し規約に従い,空いている適当なレジスタを使用して:
a1,a2レジスタを所定の位置に退避 op1をa1レジスタに格納 op2をa2レジスタに格納 opfをレジスタrに格納blx
rstr a1,
局所変数idのアドレス a1,a2レジスタを所定の位置から復帰
というアセンブリ命令列を生成する.
ret
opをa1レジスタに格納
b
関数末尾のラベル
というアセンブリ命令列を生成する.関数末尾のラベルについては,次の関数宣言の変換で説明するる.
new t
, [
]
ヒープ中にメモリを確保するには,メモリ確保用関数mymalloc
(実行環境で説明)を呼び出す.この関数は,確保したいワード数を引数として受け取る.そこで,まずはcall
の場合と同様にして関数呼出しコードを生成する.ただし:
- 引数の個数は1つだけである.
- ジャンプ先のアドレスには
mymalloc
という固定のラベルがつけられているため,blx
命令ではなくbl
命令を用いる.
という点が異なる.
次に,そうして呼び出されたmymalloc
の返り値は確保したメモリ領域の先頭アドレスであるため,を先頭アドレスから適切なオフセットだけ離れた場所へ格納する命令列を生成する.最後に,その先頭アドレスを局所変数の場所に格納する.なお,先頭アドレスはmymalloc
の呼出し直後はa1レジスタに含まれている.そのため,退避しておいたa1レジスタの値を復帰するタイミングには注意が必要である.
read t
, #
(
)
空いている適当なレジスタを使用して:
opをrに格納ldr
r,
rからiワード分離れたメモリアドレスstr
r,
局所変数idのアドレス
というアセンブリ命令列を生成する.
関数宣言Vm.ProcDecl (
,
,
)
は,呼出し規約に従い,次のようなアセンブリ命令列に変換する.ただし,は関数名,は局所変数の個数,は関数本体の仮想機械命令列とする.
.align 2
.global
name name:
fpレジスタを所定の位置に退避 lrレジスタを所定の位置に退避sub fp, sp, 4
sub sp, sp,
nlocal instrsから生成されるアセンブリ命令列 name_ret:
add sp, fp, 4
lrレジスタを所定の位置から復帰 fpレジスタを所定の位置から復帰bx lr
「_ret
」は,呼出し規約に基づき関数の実行終了時に必ず実行しなければいけない処理の先頭に付けられているラベルである.
関数の返り値をa1レジスタに格納した状態でこのラベルへジャンプすることで,スタックフレームの巻き戻し等の適切な処理を行った上で呼出し元へリターンすることができる.
最後に,プログラム全体に対応するARMのアセンブリコードは,仮想機械コードにおけるプログラム全体が関数宣言の列であったのと同様に,上記の方法に従って生成されたすべての関数宣言のアセンブリ命令列を一つに繋げたものとなる.ただし,アセンブリコードはテキストセグメントに配置する必要があるため,先頭に.text
指示子を書く必要がある.
- 課題9(必須) ARMコード生成
arm_noreg.ml
のcodegen
関数を完成させることにより,仮想機械コードからARMアセンブリコードを生成しなさい.