それでは,仮想機械コードをARMアセンブリコードへ変換する具体的な方法について説明する.

まず,仮想機械コードの(Vm.operand型の)オペランドの変換は,以下の細かな点を除いて講義テキストのと同じである.

  • 引数は2つまで受け取る可能性がある.これまでに説明したとおり,第1引数はa1レジスタ,第2引数はa2レジスタに含まれている.

  • 番目の局所変数(Vm.Local )は,前節の図のレイアウトに従い,フレームポインタfpからの相対アドレス[fp, #-4(+2)]によって参照する.

  • MIPSのmovelw命令には,ARMのmovldr命令がそれぞれ対応する.また,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,

空いている適当なレジスタを使用して:

  oprに格納
  str r, 局所変数idのアドレス

というアセンブリ命令列を生成する.

t, ,

addまたはmulの場合には,空いている適当なレジスタを使用して:

  op1r1に格納
  op2r2に格納
  bop r1, r1, r2
  str r1, 局所変数idのアドレス

というアセンブリ命令列を生成する.

ltの場合には,上の命令列と殆ど同じだが,「 , , 」の行は代わりに:

  cmp r1, r2
  blt l1
  mov r1, #0
  b l2
l1:
  mov r1, #1
l2:

の命令列とし,比較結果に従って定数を代入するコードを生成する.ここで,はフレッシュなラベルである.

:

同じ名前のラベルをアセンブリコード中の同じ位置に置くだけで良い.

bif ,

空いている適当なレジスタを使用して:

  oprに格納
  cmp r, 0
  bne l

というアセンブリ命令列を生成する.

goto

アセンブリ命令b を生成する.

call t, ()

呼出し規約に従い,空いている適当なレジスタを使用して:

  a1,a2レジスタを所定の位置に退避
  op1をa1レジスタに格納
  op2をa2レジスタに格納
  opfをレジスタrに格納
  blx r
  str a1, 局所変数idのアドレス
  a1,a2レジスタを所定の位置から復帰

というアセンブリ命令列を生成する.

ret

  opをa1レジスタに格納
  b 関数末尾のラベル

というアセンブリ命令列を生成する.関数末尾のラベルについては,次の関数宣言の変換で説明するる.

new t, []

ヒープ中にメモリを確保するには,メモリ確保用関数mymalloc実行環境で説明)を呼び出す.この関数は,確保したいワード数を引数として受け取る.そこで,まずはcallの場合と同様にして関数呼出しコードを生成する.ただし:

  1. 引数の個数は1つだけである.
  2. ジャンプ先のアドレスにはmymallocという固定のラベルがつけられているため,blx命令ではなくbl命令を用いる.

という点が異なる.

次に,そうして呼び出されたmymallocの返り値は確保したメモリ領域の先頭アドレスであるため,を先頭アドレスから適切なオフセットだけ離れた場所へ格納する命令列を生成する.最後に,その先頭アドレスを局所変数の場所に格納する.なお,先頭アドレスはmymallocの呼出し直後はa1レジスタに含まれている.そのため,退避しておいたa1レジスタの値を復帰するタイミングには注意が必要である.

read t, #()

空いている適当なレジスタを使用して:

  oprに格納
  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.mlcodegen関数を完成させることにより,仮想機械コードからARMアセンブリコードを生成しなさい.