まず,ARMで使用できる物理レジスタの種類について簡単に説明する.Raspberry Pi 3 に搭載されている ARM プロセッサには全部で16個のレジスタが用意されており,それぞれ以下の表中の「レジスタ名」欄の名前がついている.

ARMレジスタ一覧

レジスタ名 MIPS 説明
a1 a0 関数呼出しの第1引数.関数の返り値
a2 a1 関数呼出しの第2引数
a3 a2 関数呼出しの第3引数
a4 a3 関数呼出しの第4引数
v1 t0 汎用
v2 t1 汎用
v3 t2 汎用
v4 t3 汎用
v5 t4 汎用
v6 t5 汎用
v7 t6 汎用
fp fp フレームポインタ
ip - 関数内呼出し用.MiniMLコンパイラでは別の用途に使用
sp sp スタックポインタ
lr ra リンクレジスタ
pc pc プログラムカウンタ.MiniMLコンパイラでは直接操作しない

配布コードのarm_spec.ml中では,レジスタを表す型を次のように定義している.

type reg =
    A1   (* 1st parameter & return value *)
  | ...
  | A4   (* 4th parameter *)
  | V1
  | ...
  | V7
  | Fp   (* frame pointer *)
  | Ip   (* intra-procedural call register *)
  | Sp   (* stack pointer *)
  | Lr   (* link register (return address) *)
  (* | Pc (* program counter *) *)

上記のレジスタ一覧中の「MIPS」欄には,参考までに用途の似ているMIPSレジスタの名前を載せてある.MIPSでの名前づけと比べてみると,種類を表す先頭文字が一部異なっていたり,ではなくから番号付けされていたりという些細な違いはあるものの,おおむね類似したレジスタが揃っている.なお,MIPSで相当するもののないipレジスタは,MiniMLコンパイラでは本来とは異なる用途で用いる.

fpsplrレジスタは,名前もほぼ同じことから容易に推測できるように,それぞれ対応するMIPSレジスタと同じ用途に使用される.つまり,fpは関数フレームを指すポインタ,spはスタックトップを指すポインタ,lrはリターンアドレスを格納するためにそれぞれ用いられる.これらのレジスタの具体的な使い方については,呼出し規約でさらに詳しく説明する.

次に,ARMの命令中で指定可能なオペランドのアドレッシングモードについて説明する.ARMは,MIPS同様,RISCアーキテクチャと呼ばれるタイプのプロセッサであり,演算は基本的にレジスタ中の値に対して行うが,一般的には以下のオペランド指定が可能である.

レジスタ
指定されたレジスタに含まれている値をオペランドとする.たとえば:
add v1, v2, v3

は,v2レジスタ中の値にv3レジスタ中の値を足し,その結果をv1レジスタ中に格納する.MIPSのアセンブリコードとは異なり,レジスタ名の前にプレフィックス「$」をつける必要はない.

即値
プレフィックス「#」をつけることで,命令中に整数の即値を直接埋め込むことができる.MIPSでは,addili等,一部の命令だけが即値オペランドを持つことができるが,ARMではより多くの命令が即値をオペランドにできる(詳細は,この後の各命令の説明で述べる).
ラベル
アセンブリコード中のラベルのつけられたメモリアドレスをオペランドとする.関数呼出しの際,関数本体コードの先頭アドレスへのジャンプ命令に用いられる.また,他の言語では大域変数の格納場所の指定などにも用いられる.ラベル名(文字列)の前にプレフィックス「=」をつける必要がある.
レジスタ間接
レジスタ中に含まれる値を(バイトアドレッシングの)メモリアドレスとして扱い,そこからオフセットバイト分だけ離れたメモリ中の場所に含まれている値をオペランドとする.たとえば:
ldr v1, [v2, #8]

は,v2レジスタ中の値にを足して得られるアドレスから1ワードを読み出し,その値をv1レジスタに格納する(ldr命令はMIPSのlw命令に相当).アセンブリコードにおける記法が,MIPSの記法「8($v2)」とは異なることに注意.

これらのアドレッシングモードを(ほぼそのままの)OCamlのデータ型として定義すると以下のようになる(定義中のimmlabelは,それぞれintstringの別名).このデータ型定義もarm_spec.mlにすでに含まれている.

type addr =
    I  of imm         (* 即値(Immediate) *)
  | R  of reg         (* レジスタ(Register) *)
  | L  of label       (* ラベル(Label) *)
  | RI of reg * imm   (* レジスタ間接(Register Indirect) *)

ARMの仕様は,MIPSの仕様と比べかなり大きく複雑である.そこで,この資料ではMiniMLコンパイラの作成に必要となる必要最小限の命令セットだけに絞って説明する.命令の一覧を以下の表に示す.

MiniMLコンパイラで用いるARM命令セット一覧

命令フォーマット MIPS 説明
add , , add, addi を足した結果をに格納
b j ラベルへジャンプ
bl jal 関数の呼出し
blx jalr レジスタの指す関数の呼出し
bx jr レジスタの指す命令へジャンプ
b (b) 比較結果がであればラベルへジャンプ
cmp , の比較
ldr , lw をレジスタにロード
mov , move, li をレジスタへ移動
mul , , mul を掛けた結果をに格納
str , sw レジスタの値をにストア
sub , , sub からを引いた結果をに格納

レジスタ一覧と同様,参考までにMIPSの類似する命令を載せている.以下,MIPSの類似する命令とは振舞いが若干異なる命令についてだけ注意点を挙げる.

分岐命令のジャンプ先指定
ジャンプ先のラベルをオペランドとして指定する際,先に述べたプレフィックス「=」をつける必要はない.一般に,命令一覧中,と書かれている箇所にはラベル名をそのまま記述し,と書かれている箇所にラベル(が指し示すメモリアドレス)を値として指定したい場合には,プレフィックス「=」をつける.
mov命令
MIPSのmove命令に相当し,第2オペランド(ソース)から第1オペランド(デスティネーション)へ値をコピーする.ただし,MIPSではソースとデスティネーションのどちらもレジスタでなければいけないが,ARMではソースにそれ以外のアドレッシングモードも指定できる.そのため,MIPSでは即値をレジスタに格納するための専用の命令(li命令)が別に用意されているが,ARMではこのmov命令を使って表現できる.命令一覧の他の命令中に現れるについても同様であり,MIPSと比べて,より柔軟なオペランド指定が可能である.
cmp命令
2つのオペランド間の比較を行う.比較結果を入れるためのレジスタは自由に指定できず,専用のレジスタに入れられる.また,比較の種類(==!=<<=)を指定する必要はなく,専用レジスタに含まれている値から,全種類の比較結果を後から(たとえば次のb命令によって)参照することができる.

MIPS でも比較結果をレジスタにセットする命令が用意されているが,(1) 比較結果は整数値(1または0)としてオペランドに指定された汎用レジスタに格納される,(2) 1つの命令で全種類の比較を行えるのではなく,slt等,比較の種類に応じた命令が1つずつ用意されている,という点でARMのcmp命令とは異なる.

b命令
最も最近のcmp命令による比較結果に基づき条件分岐を行う.分岐先の命令のアドレス(ラベル)を唯一のオペランドとし,条件は命令名(オペコード)中のにエンコードされている.たとえば,条件がの場合のオペコードは,それぞれbltLess Than),beqEQual to),bneNot Equal to),bgeGreater than or Equal to)となる.

なお,MIPS にもほぼ同じ名前の条件分岐命令があるが,事前に実行済みの比較結果に従って分岐するのではなく,b命令自身が分岐先の他に2つのオペランドを受け取り,それらの比較も実行する点が異なる.

以上を踏まえると,たとえば「if < then else 」のような式は,cmp命令とbltを組み合わせた以下のアセンブリコードで実現できる(変数の値は,v1v2レジスタにそれぞれ含まれているものとする):

  cmp  v1, v2
  blt  L_then
  e2のコード
  b    L_end
L_then:
  e1のコード
L_end:

ARMのアセンブリコード全体は,命令,ラベル,およびディレクティブの列からなる. ラベルはその直後の命令(またはディレクティブ)につけられているものと見なす. 複数のラベルが連続している場合には,それら全てのラベルが一番最後のラベルの 直後にある命令(またはディレクティブ)につけられていることになる.

ARMアセンブリコード中に書くことのできるディレクティブには様々な種類があるが,ここでは,MiniMLコンパイラのターゲットコード中で必要となる最低限のディレクティブだけ説明する.

.text
テキストセグメントの開始を指定する.ARM命令などの実行コードはテキストセグメントに配置する必要がある.
.align
このディレクティブの直後に書かれる要素(命令など)が指定されたアドレス境界に配置されるよう,適宜調整される.たとえば,.align 2と書くと,直後の要素は必ず4 () バイト境界に置かれる.この実験の実行環境である Raspberry Pi 3 には32ビットOSがインストールされているため,すべての命令は4バイト境界に配置されていなければいけない.
.global
名前が他のアセンブリファイルからも参照可能な名前であることを指定する.たとえば,ある関数をC言語コード(をコンパイルした結果のアセンブリファイル)から呼び出せるようにするには,その先頭ラベルをこのディレクティブでグローバルと指定する必要がある.