はじめに (2015-12-11, 2020-07-04)

2011年10月にARMの64ビットアーキテクチャが発表され、最初のARM64のCPU であるApple A7が載った iPhone 5S が2013年9月に発売されてから、 7年ほど経過しました。 ARM の64ビットCPU はすでにスマートフォンの中の普通のCPUとなっています。iPhone 5s 以降、 iPhone6s ではApple A9、 iPhoneXS でが A12 が使われてます。 Androidの機種では QualcommSnapdragon 410 ぐらいから始まって、Snapdragon 765Snapdragon 865 といったCPUが採用されるようです。 シングルボードコンピュータ としても https://www.96boards.orgQualcomm の DragonBoard 410cHiKeyLeMakerHikey(LeMaker版) が 1万円程度で入手できました。

さらにOrange Pi PC2 という 4 コアの 64 ビット の ARMv8 (Allwinner H5) を使ったシングルボードコンピュータが2016年末に登場し、送料込み2500円程度で入手できました(Orange Pi PC2 の性能と Ubuntu のインストール)。

2020年6月現在では、4コアのCortex-A72(1.5GHz)が搭載された Raspberry Pi4 が5000円強で購入できます。 メモリ 8GB でも8000円強です。 自宅サーバとして使用するには十分な性能です。 さらにスーパーコンピュータの分野でも、ARM64のCPUを使った 富岳TOP500 のランキングで1位を獲得しています。

PC の分野でも Apple が Mac の CPU を ARM64 に変更する予定 (macOS Big Sur Enables Transition to Apple Silicon) となっています。 もう完全にメジャーな CPU アーキテクチャになりました。

個人でも簡単に入手して Arm64 のアセンブリプログラミングができるので、 「ARM64アセンブリプログラミングシリーズ」 を始めてみます。 よく使われる命令から初めて、分りやすい解説ができたらいいなと思います。

下の表は、bash と python3.4、rvtl-arm64を逆アセンブルして得られた 676,628 命令のうち、よく使われている上位 95% の命令です。 8000ページ以上の公式マニュアルを見ると500以上の命令がありますが、 よく使われる命令はこんなものです。 この程度ならば何とかなりそうですね。

命令頻度(%)機能
mov 16レジスタ間のコピー
ldr 13メモリからレジスタに読み出し
b 10無条件分岐
bl 9サブルーチン呼び出し
add 8加算
cmp 6比較
str 6レジスタからメモリへ格納
ldp 5スタックから取り出し
stp 4スタックへ格納
adrp 3レジスタにアドレスを設定
cbz 3比較して 0 なら分岐
b.eq 3等しければ分岐
ret 3サブルーチンから戻る
sub 2減算
b.ne 2異なれば分岐
adr 1レジスタにアドレスを設定
cbnz 1 比較して非 0 なら分岐
and 1ビット積

ARM64とは

ARM 系の CPU は、ARM Ltd.が設計した CPU を複数の CPU メーカがライセンス生産しています。 アーキテクチャとコア、チップ、命令セットに別の名前が付いています。 実際の CPUチップ の型名はメーカ毎にいろいろな名前があります。それぞれの CPU は、ARM Ltd.が設計した CPU コアのアーキテクチャバージョンで大まかに区別できます。

例えば、Raspberry Pi1ARMv6 アーキテクチャで ARM1176JZF-S コア の BCM2835 チップを使っています。Raspberry Pi2ARMv7 アーキテクチャで Cortex-A7 コア が4つ入った BCM2836 チップを使っています。ARMv6 と ARMv7 は 32ビットのアーキテクチャです。

64ビットのアーキテクチャとしては、Qualcomm の DragonBoard 410cARMv8 アーキテクチャで Cortex A53 コアのSnapdragon 410というチップを使っています。 ARMv8 アーキテクチャは32ビットの実行ステート (AArch32) も含んでいるため、32ビットと64ビットの両方が使えます。 「 ARMv8 アーキテクチャの AArch64 実行状態をサポートする A64 命令セット」が正しい表現と思いますが、短く「 ARM64 」と呼ぶことにします

汎用レジスタの数が ARM32 の 16 個 から ARM64 では、 30 個 (X0 .. X29) に増え、汎用レジスタのサイズも64ビットに拡張されています。 命令のサイズは 32 ビット固定長のままとなっています。 命令の大きさを変えずに大きなデータを扱うため、ARM32とは命令のビットパターン(エンコード)が全く異なっています。 AMD が設計した x86-64 のように 32 ビットの x86 との互換性を重視した複雑なエンコードではなく、ARM はシンプルでキレイなビットパターンとして 64 ビット命令を再設計することを選んだようです。 アセンブラの表記法が32ビット命令と近い書き方になっているものの、レジスタ、アドレッシングモード、命令ともに似ているようで違ったものになっています。 また、浮動小数点演算とSIMDはオプション扱いではなくなっています。常にハードウェアで浮動小数点演算を行うようになります。

演算結果で変化するフラグの条件で命令を実行したり、スキップしたりする条件実行の機能を多くの種類の命令で持っていることが、他の CPU にはない ARM32 の特徴となっています。 ARM64では多くの命令でこの条件実行の機能はなくなり、条件付命令は分岐(b)、比較(ccmp等)、選択(csel)とわずかに残るのみになりました。 また、スタックへの複数レジスタの退避/復帰が 1 命令で行える便利な LDM / STM 命令 もなくなり、2つのレジスタに限定された LDP / STP 命令に変わっています。

32ビットと64ビットの命令の互換性を重視しなかった理由は、64ビットの命令も 32 ビット固定長にしたところにあります。 ARM の演算命令は 32 ビットでも 64 ビットでも、1命令中にレジスタを 3 つ指定することができます。 加算命令を例にすると「A=B+C」といったように2つのレジスタの演算結果を別のレジスタに格納することができます。「A=A+B」のように基本的に 1 命令中に 2つのレジスタしか使えない x86 とは異なっています。

30 個のレジスタを指定するためには 5 ビット(0..31)が必要です。 さらに64ビットの汎用レジスタの内容を8ビット、16ビット、32ビットといった小さい単位でもデータを扱おうとすると、そこでもサイズの区別のために2ビットが必要となります。 単純に考えるとレジスタの指定だけでも3つのレジスタで 21ビット が必要となり、32ビットの固定長の命令とするためには無理があります。

インテルの32ビットCPU(x86) の64ビット版である x86-64 では、1つのレジスタを 64 ビットで使う場合はRAX、32ビットではEAX, 16ビットではAX, 8ビットでは AL レジスタとして指定できますが、次の例のように 長いものでは 8 バイト以上を使う可変長の命令となっています。

  67 89 0c 95 40 68 60 00    mov  DWORD PTR [edx * 4 + 0x606840], ecx
  03 75 28                   add    esi, DWORD PTR [rbp + 0x28]
  48 8d 7c dd 00             lea    rdi, [rbp + rbx * 8 + 0x0]
  48 8d 05 1b 03 00 00       lea    rax, [rip + 0x31b]

ARM64では、すべての命令を 32 ビットで表現するために色々な工夫がされていて、まず最初に ARM32 でほとんどの命令で可能であった条件実行をなくすことで 4 ビットを節約しています。さらにレジスタを 8 ビットと 16 ビットで指定することをあきらめることで 1 ビット、3 レジスタで 3 ビットの節約ができます。 指定するレジスタをビットマップで指定できる ARM32 の LDM / STM 命令 はレジスタが倍増したため不可能になりました。

このような工夫を積み重ねることで、できるだけ ARM32 との互換性を考えながら、64ビットCPUでも苦労して命令長を 32 ビットに収める様子が想像できて楽しめます(笑)。

参考資料

最も詳細なドキュメントは、ARM Architecture Reference Manual で、 https://developer.arm.com/architectures/cpu-architecture/a-profile/docから入手できます。 2020年07月現在の最新版は Armv8.6 (2020-03-31, DDI0487F_b_armv8_arm.pdf, 8128page) です。 32ビット命令を含めて8000ページ以上の膨大な量のドキュメントです。 個別の命令のエンコードや疑似コードで詳細な動作が記述されています。 読みやすいものではありません。

目次

ARM64 アセンブリプログラミング



続く...