まず,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コンパイラでは本来とは異なる用途で用いる.
fp
,sp
,lr
レジスタは,名前もほぼ同じことから容易に推測できるように,それぞれ対応するMIPSレジスタと同じ用途に使用される.つまり,fp
は関数フレームを指すポインタ,sp
はスタックトップを指すポインタ,lr
はリターンアドレスを格納するためにそれぞれ用いられる.これらのレジスタの具体的な使い方については,呼出し規約でさらに詳しく説明する.
次に,ARMの命令中で指定可能なオペランドのアドレッシングモードについて説明する.ARMは,MIPS同様,RISCアーキテクチャと呼ばれるタイプのプロセッサであり,演算は基本的にレジスタ中の値に対して行うが,一般的には以下のオペランド指定が可能である.
- レジスタ
- 指定されたレジスタに含まれている値をオペランドとする.たとえば:
add v1, v2, v3
は,
v2
レジスタ中の値にv3
レジスタ中の値を足し,その結果をv1
レジスタ中に格納する.MIPSのアセンブリコードとは異なり,レジスタ名の前にプレフィックス「$
」をつける必要はない. - 即値
- プレフィックス「
#
」をつけることで,命令中に整数の即値を直接埋め込むことができる.MIPSでは,addi
,li
等,一部の命令だけが即値オペランドを持つことができるが,ARMではより多くの命令が即値をオペランドにできる(詳細は,この後の各命令の説明で述べる). - ラベル
- アセンブリコード中のラベルのつけられたメモリアドレスをオペランドとする.関数呼出しの際,関数本体コードの先頭アドレスへのジャンプ命令に用いられる.また,他の言語では大域変数の格納場所の指定などにも用いられる.ラベル名(文字列)の前にプレフィックス「
=
」をつける必要がある.Note: (pc相対ではなく)絶対アドレスを意味するそうです. - レジスタ間接
- レジスタ中に含まれる値を(バイトアドレッシングの)メモリアドレスとして扱い,そこからオフセットバイト分だけ離れたメモリ中の場所に含まれている値をオペランドとする.たとえば:
ldr v1, [v2, #8]
は,
v2
レジスタ中の値にを足して得られるアドレスから1ワードを読み出し,その値をv1
レジスタに格納する(ldr
命令はMIPSのlw
命令に相当).アセンブリコードにおける記法が,MIPSの記法「8($v2)
」とは異なることに注意.
これらのアドレッシングモードを(ほぼそのままの)OCamlのデータ型として定義すると以下のようになる(定義中のimm
,label
は,それぞれint
,string
の別名).このデータ型定義も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と比べて,より柔軟なオペランド指定が可能である.Warning: すべての命令ですべてのアドレッシングモードが有効かどうかは,実はきちんと調べていません.たとえば,ldr
命令の代わりに,レジスタ間接のを指定するmov
命令を使ってもメモリから値をロードできそうな気はしますが未確認です.後者の書き方(や,この資料に出てこない他の色々な組み合わせ)を使いたいようでしたら,自分で確認してください.実行環境の方法に従ってRaspberry Pi 3上のアセンブラにかければ,簡単に確認できます. cmp
命令- 2つのオペランド間の比較を行う.比較結果を入れるためのレジスタは自由に指定できず,専用のレジスタに入れられる.また,比較の種類(
==
,!=
,<
,<=
)を指定する必要はなく,専用レジスタに含まれている値から,全種類の比較結果を後から(たとえば次のb
命令によって)参照することができる.MIPS でも比較結果をレジスタにセットする命令が用意されているが,(1) 比較結果は整数値(1または0)としてオペランドに指定された汎用レジスタに格納される,(2) 1つの命令で全種類の比較を行えるのではなく,
slt
等,比較の種類に応じた命令が1つずつ用意されている,という点でARMのcmp
命令とは異なる. b
命令- 最も最近の
cmp
命令による比較結果に基づき条件分岐を行う.分岐先の命令のアドレス(ラベル)を唯一のオペランドとし,条件は命令名(オペコード)中のにエンコードされている.たとえば,条件が,,,の場合のオペコードは,それぞれblt
(Less Than),beq
(EQual to),bne
(Not Equal to),bge
(Greater than or Equal to)となる.Tip: MiniML言語には<
しか比較演算子がないので,おそらく全部知っておく必要はない.なお,MIPS にもほぼ同じ名前の条件分岐命令があるが,事前に実行済みの比較結果に従って分岐するのではなく,
b
命令自身が分岐先の他に2つのオペランドを受け取り,それらの比較も実行する点が異なる.以上を踏まえると,たとえば「
if
<
then
else
」のような式は,cmp
命令とblt
を組み合わせた以下のアセンブリコードで実現できる(変数,の値は,v1
,v2
レジスタにそれぞれ含まれているものとする):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言語コード(をコンパイルした結果のアセンブリファイル)から呼び出せるようにするには,その先頭ラベルをこのディレクティブでグローバルと指定する必要がある.