自作しているCコンパイラではx86-64のアセンブリを出力していたが、 ノートパソコンを近年のMacBookAirにしたところCPUアーキテクチャが違うためコンパイル結果を動かせなくなってしまった。 そこでバックエンドとしてaarch64を追加した。
概要
M1MacはCPUがAppleシリコン・Armアーキテクチャとのことで、x86-64向けにコンパイルしたバイナリを動かすことができない (Rosetta2という謎技術があるが…)。 そこでCコンパイラから出力するアセンブリにaarch64(Arm64)を追加することにした。
WASMのときはフロントエンド(パーサ)の共有だけにとどまったが、aarch64向けには中間表現まで共有して、バックエンドを差し替える形にする。
アーキテクチャ判別
aarch64かどうかでコンパイル対象とするソースを切り替えるために、Makefile内で判定する。 configureとかautoconfなどはまったくわからないので、Makefileでゴリ押しする。
シェルの arch
コマンドでプロセッサのアーキテクチャが取得できるので、Makefileからシェル呼び出しの結果を判定:
ARCH:=$(shell arch) |
してコンパイル対象にするバックエンドのソースファイルを切り替える。
ファイル単位ではなく部分的に判別したい場合には、コンパイラの定義済みマクロ __aarch64__
や __x86_64__
で判定する。
aarch64の命令セット
x86との違い:
- RISCアーキテクチャ(固定長で簡易な命令セット)
- 汎用レジスタ:31個
x0
〜x30
x31
: スタックポインタsp
- 通常命令の場合、ゼロレジスタ
xzr
として使える
- 通常命令の場合、ゼロレジスタ
x30
: リンクレジスタlr
x29
: フレームポインタfp
- レジスタは32bit(
wn
)または64bit(xn
)で、それ未満はメモリ操作時に1バイトや2バイトでアクセスできる - 演算命令でオペランドが3つ与えられる (a = b + c)
- レジスタのプッシュ・ポップという直接の命令はなく、アドレッシングモードのプリ・ポストを使う
- しかしスタックポインタは16バイト単位を維持する必要がある
- コールに対するリターンアドレスはスタックではなくリンクレジスタに保持される
- 剰余命令がない(のでいったん除算して再度乗算して求めるとか)
- 演算命令に直値を与えられない(加減算と
cmp
以外):空きレジスタを使用する必要がある- 加減算に使用できる定数値は0~4095まで
- ビット演算でも直値を使えるが、使える値のビットパターンに制限がある
- 大きな(16bitを超える)値の代入には命令が2~4つ必要(
movk
) - 浮動小数点レジスタ:
d0
〜d31
(s
単精度)
他に色々利用できそうな命令があるが、局所最適化をしてないので単純な命令しか使用してない。
アドレッシングモード
オペランドとしてレジスタ以外に与えられる形式:
- 直値:
#nnn
mov
,movk
: 16bitまで- 加減算、比較:0〜4095、また12ビットまでシフト可能
- ビット積、和、排他的論理和:ビットパターンに制限あり
- 間接:
[reg]
- 間接+オフセット:
[reg, #ofs]
- プレインデックス:
[reg, #add]!
(命令実行前にreg
にadd
が加算される) - ポストインデックス:
[reg], #add
(命令実行後にreg
にadd
が加算される) - レジスタオフセット:
[reg1, reg2 (, shift)]
(reg2
はshift
で×2, ×4, ×8, ×16が可能)
呼出規約(ABI)
関数呼出におけるレジスタ利用の取り決め(呼出規約)は、
レジスタ | 用途 |
---|---|
x0 〜x7 |
関数へのパラメータ |
x9 〜x15 |
ローカル変数(呼び出し元保存) |
x19 〜x29 |
呼び出され側保存 |
x0 |
戻り値(浮動小数点の場合d0 ) |
他に x16
とx17
がプロシジャ間呼出のスクラッチレジスタ、x18
がプラットフォームレジスタ、とのことだけどよくわかってないので未使用。
- aarch64ではプロシジャ呼出標準というらしい
- 戻り値が構造体の場合は
x8
に書き込み先アドレスが渡される。
- 戻り値が構造体の場合は
Apple特有
- 任意長引数は常にスタック経由で渡される
- Addressing Architectural Differences in Your macOS Code | Apple Developer Documentation
Don’t Redeclare a Function to Have Variable Parameters
… On arm64, the compiler always places variadic parameters on the stack, regardless of whether registers are available.
- Addressing Architectural Differences in Your macOS Code | Apple Developer Documentation
レジスタ割付
中間表現の段階では個数が無制限の仮想レジスタを使用していて、それを物理レジスタに割り付けるという処理をしている。
aarch64はx86-64とアーキテクチャが違うので多少変更する必要があるが、制約が緩くなる
(レジスタ数が多い、乗算が%eax
に固定されてるということもない)ので基本的には問題は出ないであろう。
アセンブラ、リンカ
Macではバイナリ形式がELFではないので、アセンブラとリンカは自作版ではなくシステムのものを利用している (TODO!)。
締め
- という具合でAppleシリコンでも自作Cコンパイラを動かせるようになった
- Mac以外に、RaspberryPiの64bitなどでも動かしてみたいが未着手…
- なんか実際には長いこと苦労してた気がするんだが、書き出してみると内容あっさりしてるな…。
- 関数呼び出し時の引数の処理でスタック操作のオフセット計算で苦労していた気がする。
- 該当ソースは cc/arch/aarch64 以下
参考
- Learn the architecture - AArch64 Instruction Set Architecture
- リファレンスマニュアル:Arm Architecture Reference Manual for A-profile architecture 分量多すぎて読めん…
- Arm64(ARMv8) Assembly Programming (00) ほとんどこちらを参考にしました