M1Mac向けにコンパイルする(aarch64)

2022-09-29

自作している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)
ifeq ("$(ARCH)", "arm64")
...
endif

してコンパイル対象にするバックエンドのソースファイルを切り替える。

ファイル単位ではなく部分的に判別したい場合には、コンパイラの定義済みマクロ __aarch64____x86_64__ で判定する。

aarch64の命令セット

x86との違い:

  • RISCアーキテクチャ(固定長で簡易な命令セット)
  • 汎用レジスタ:31個 x0x30
    • x31: スタックポインタsp
      • 通常命令の場合、ゼロレジスタxzrとして使える
    • x30: リンクレジスタlr
    • x29: フレームポインタfp
  • レジスタは32bit(wn)または64bit(xn)で、それ未満はメモリ操作時に1バイトや2バイトでアクセスできる
  • 演算命令でオペランドが3つ与えられる (a = b + c)
  • レジスタのプッシュ・ポップという直接の命令はなく、アドレッシングモードのプリ・ポストを使う
    • しかしスタックポインタは16バイト単位を維持する必要がある
  • コールに対するリターンアドレスはスタックではなくリンクレジスタに保持される
  • 剰余命令がない(のでいったん除算して再度乗算して求めるとか)
  • 演算命令に直値を与えられない(加減算とcmp以外):空きレジスタを使用する必要がある
  • 大きな(16bitを超える)値の代入には命令が2~4つ必要(movk)
  • 浮動小数点レジスタ:d0d31s単精度)

他に色々利用できそうな命令があるが、局所最適化をしてないので単純な命令しか使用してない。

アドレッシングモード

オペランドとしてレジスタ以外に与えられる形式:

  • 直値: #nnn
    • mov, movk: 16bitまで
    • 加減算、比較:0〜4095、また12ビットまでシフト可能
    • ビット積、和、排他的論理和:ビットパターンに制限あり
  • 間接: [reg]
  • 間接+オフセット: [reg, #ofs]
  • プレインデックス: [reg, #add]! (命令実行前にregaddが加算される)
  • ポストインデックス: [reg], #add (命令実行後にregaddが加算される)
  • レジスタオフセット: [reg1, reg2 (, shift)]reg2shiftで×2, ×4, ×8, ×16が可能)

呼出規約(ABI)

関数呼出におけるレジスタ利用の取り決め(呼出規約)は、

レジスタ 用途
x0x7 関数へのパラメータ
x9x15 ローカル変数(呼び出し元保存)
x19x29 呼び出され側保存
x0 戻り値(浮動小数点の場合d0

他に x16x17がプロシジャ間呼出のスクラッチレジスタ、x18がプラットフォームレジスタ、とのことだけどよくわかってないので未使用。

  • aarch64ではプロシジャ呼出標準というらしい
    • 戻り値が構造体の場合は x8 に書き込み先アドレスが渡される。

Apple特有

レジスタ割付

中間表現の段階では個数が無制限の仮想レジスタを使用していて、それを物理レジスタに割り付けるという処理をしている。

aarch64はx86-64とアーキテクチャが違うので多少変更する必要があるが、制約が緩くなる (レジスタ数が多い、乗算が%eaxに固定されてるということもない)ので基本的には問題は出ないであろう。

アセンブラ、リンカ

Macではバイナリ形式がELFではないので、アセンブラとリンカは自作版ではなくシステムのものを利用している (TODO!)。

締め

  • という具合でAppleシリコンでも自作Cコンパイラを動かせるようになった
    • Mac以外に、RaspberryPiの64bitなどでも動かしてみたいが未着手…
  • なんか実際には長いこと苦労してた気がするんだが、書き出してみると内容あっさりしてるな…。
    • 関数呼び出し時の引数の処理でスタック操作のオフセット計算で苦労していた気がする。
  • 該当ソースは cc/arch/aarch64 以下

参考