《Cortex-A53シミュレータ》

　RISC-V，Cortex-M0，SH-2，R3000，V850，Cortex-A7と命令セット・シミュレータを作ってきましたが，大トリはCortex-A53シミュレータです．
　Cortex-A7のシミュレータを作成したとき，Armv7-Aの命令エンコーディングの複雑さに辟易としました．その後で，Armv8-Aの命令エンコーディングをみたときに，その単純さに感動しました．「もしかしたら，Armv8-Aは複雑になり過ぎたArmv7-Aの反省の上に成り立っているのではないか」という考えが沸き起こり，Armv7-AとArmv8-Aの特徴を比較すれば「アーキテクチャの革新」が見られるかもしれないと感じました．これが，Cortex-A53シミュレータを作ろうと思ったモチベーションです．しかし，シミュレータを作る過程で思ったのですが，Armv8-Aもそんなには単純ではありませんでした．
　それはともかく，Armv8-Aのアーキテクチャの主な特徴を以下に示します．

・64ビットと32ビットの命令が存在できる（32ビットの命令はArmv7-Aと互換性はない）．
・汎用レジスタは32本．うち1本はスタック・ポインタになったりゼロ・レジスタになったりする．
・条件実行が一部の命令に制限された．
・デフォルトでノーマル・メモリに対するロード/ストアが非アラインを許す
・ロード/ストアでのベース・レジスタに対するオフセット（即値）はデータ・サイズでスケーリングされる（スケーリングしないロード/ストア命令も存在する）．
・命令を汎用的にして、多くの機能を兼用させている．
・即値との論理演算命令において，13ビットの情報から64ビットの即値を作れるように変態的（？）なエンコーディングをする．

　ここで，一番の特徴はゼロ・レジスタの導入でしょう．汎用レジスタはx0からx31（32ビットで使う場合は，w0からw31）の32本があります．ただし，x31とw31は便宜的なもので，正式には存在しません．この32番目のレジスタが命令で指定された場合は，ときと場合で変わるのですが，スタック・ポインタ（spまたはwsp）またはゼロ・レジスタ（xzrまたはwzr）として機能します．ゼロ・レジスタとは，値がゼロのレジスタです．リード時には0が読み出され，ライト時には値が書き捨てになります．
　ゼロ・レジスタの使用例としては，比較命令やレジスタ間の移動命令が考えられます。たとえば，Armv7-Aでいうところの比較命令
　cmp  rn, rm
は，Armv8-Aでは
　subs xzr, xn, xm
と減算命令を流用します．これは，ゼロ・レジスタへの書き捨ての例です．あるいは，Armv7-Aでのレジスタ間の移動
　mov rd, rn
は，Armv8-Aでは
　add xd, xn, xrz
と加算命令を流用します．これは，ゼロ・レジスタを0として使用する例です．
　ゼロ・レジスタを使用するのとは別に，Armv8-Aでは命令の汎用化が見られます．たとえば，EXTR命令を流用すれば，右シフトや右ローテートを実現できますし，UBFM/SBFM命令を流用すれば，左シフトやゼロ拡張，符号拡張が実現できます．このように命令を汎用化することで命令数を減らす工夫をしてあります．
　シミュレータを作成する場合の新機軸としては，64ビット対応があります．ELFのロードは最初から64ビット対応をしてあるので問題ないのですが，64ビットの演算は少しだけ複雑です．それよりも方針の全面見直しとなったのはロード/ストア命令の非アライン対応です．コンパイラは非アラインなアクセスをするコードを生成しないと信じていた筆者は脳天に杭を打ち込まれたような衝撃でした．最初は，これに気付いてなかったので，シミュレータをベンチマーク・テストでデバッグするときに，なぜFAILするのか分かりませんでした．結局，シミュレータ内のget_mem()，put_mem()関数をバイト単位でメモリ・アクセスを行うように改造しました．
　演算の64ビット化で少し考えたのはUMULH，SMULHという64ビット同士の乗算結果の128ビットのデータ上位64ビットを返す命令の実装です．GCCのlong long int型は通常は64ビットなので，その実現にはちょっとした工夫が必要です．その詳細説明は本誌に譲ります．
　さて，Armv8-Aのシミュレータを作成しようと思った別のモチベーションは，Windowsで動作する，Armv8-A（AArch64）のコードを生成するGCCを見つけたからです．Windows上で自由にArmv8-Aのコード生成ができなければ，シミュレータを作成しても面白くないと思いました．
　Armv8-Aのコードを生成するGCCはLinaroの以下のサイトからダウンロードできます．
●GCC-5.50
https://releases.linaro.org/components/toolchain/binaries/latest-5/aarch64-linux-gnu/
●GCC-7.3.1
https://releases.linaro.org/components/toolchain/binaries/latest/aarch64-linux-gnu/
ただし，GCC-7.3.1は，Linux版は試していませんが，WindowsのPowerShell上でもMinGW（32ビット）でも動作しませんでした．64ビットのMinGW用なのでしょうか？GCC-5.50に関しては，WindowsのPowerShell上で問題なく動作しました．当然，MinGW上でも動作します．なお，ダウンロード・ファイルは「tar.xz」という形式で圧縮されていますが，「.xz」や「.tar」形式はWindowsに備え付けの7-Zipで解凍できます（7-Zip，すげえぜ！）．
　ただし，GCC-5.50の生成するコードをみて愕然としました．全然最適化が行われていません．Dhrystoneベンチマークの1ループが約1700命令でした．Cortex-A7で動作させた場合は，1ループ当たり430命令でした．単純に考えると，Cortex-A53はCortex-A7の約4倍遅い（同一動作周波数時）ということになります．なお，Cortex-A7の場合で，最適化を行わない場合は，Dhrsytoneベンチマークの1ループは約1700命令でした．GCC-5.50にはもっと最適化を頑張ってほしいところです．
　なお，参考文献1)でArmv8-A（AArch64）とArmv7-A（AArch32）の実行速度をDhrystoneベンチマークで行っています．プラットフォームはXilinx Zynq UltraScale+ MPSoCです．結果は，AArch64の方がAArch32より18％性能が高いという結果になっています．汎用レジスタの本数が2倍使える割には思ったほど速くなっていませんね．



《参考文献》
1) Linuxをインストールして、AArch64とAArch32の性能をベンチマークしてみよう！
https://www.aps-web.jp/academy/linux_mpsoc/

