はじめに (2015-12-11, 2017-01-28)

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

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

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

下の表は、bash と python3.4、rvtl-arm64を逆アセンブルして得られた 676,628 命令のうち、よく使われている上位 95% の命令です。 6000ページ近い公式マニュアルを見ると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 のサイトに ARMv8アーキテクチャ という簡単な紹介があります。

最も詳細なドキュメントは、ARM Architecture Reference Manual で、http://infocenter.arm.com/help/index.jspから入手できます。 ARM Architecture Reference Manual は無料のユーザ登録が必要です。 2015年12月現在の最新版は 8th beta release (2015-09-30, DDI0487A_h_armv8_arm.pdf, 5778page) です。 32ビット命令を含めて6000ページ近い膨大な量のドキュメントです。 個別の命令のエンコードや疑似コードで詳細な動作が記述されています。 読みやすいものではありません。

日本語の資料は PDFでARM コンパイラ armasm ユーザガイド バージョン 6.02ARM のトップページから次のように辿っていくと見つかります。

  サポート
    → リソース
    → Documentation
    → ARM アーキテクチャ
    → ARM ソフトウェア開発ツール
    → ARM Compiler 6
    → Version 6.03 Japanese
    → ARM コンパイラ armasm ユーザガイド

これもアセンブラの文法の説明が主なので、命令の動作は、 読み難い ARM Architecture Reference Manual で我慢するしかないようです。

目次

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



続く...