
前回、前々回と実機練習をサボって調べ物をしていたので、今回は練習あるのみです。でも溜まっている数が多いです。今回は、前々回のMOV命令に絞って練習してみます。一通り撫でるだけでも命令数多いです。単なるMOV、転送命令なんだけれども。そう単純でもないんだこれが。
※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら
※動作確認は普及価格帯のAndroidスマホで行っています。Cortex-A73/Cortex-A53 各4コアの bigLITTLE 64ビット。Android上にインストールしたTermux環境にWindowsPCからSSH接続し、clang/llvmのツールチェーンでビルドしております。
今回練習する命令のアセンブラコード
以下に、基本1命令=1関数のスタイルでA64命令をエクササイズするアセンブラコード(アセンブリ言語ソース)を掲げています。ただ前々回で、ほとんどのMOV命令は他の命令のエイリアスであることが分かりました。そこで実体命令とそのエイリアスであるMOV命令がある場合、その対応が分かるように、
実体が同じ命令を2個並べて
記述してあるものがあります。これら関数を後でディスアセンブルすれば、機械語レベルでは同一であることが確認できます。
まずは「レジスタ間の」movの部分から。
.globl movW, movX, movspW, movspX, movzW, movzX, movzLHW, movzLHX, movnLHW, movnLHX, movkLHW, movkLHX
.text
.balign 4
movW:
orr w0, wzr, w1
mov w0, w1
ret
movX:
orr x0, xzr, x1
mov x0, x1
ret
movspW:
add w0, wsp, #0
mov w0, wsp
ret
movspX:
add x0, sp, #0
mov x0, sp
ret
コマケー話ですが、私は movspXのところで、ついxspなどと書いてアセンブラに怒られました。32ビット幅のスタックポインタはwspですが、64ビット幅の本物のスタックポインタの御名前はただの sp です。ボーンヘッドでX=64bitなどと思い込んでいるとエラーがでますで。
上記部分をディスアセンブルしてみたものが以下に。並べて書いてある2命令がまったく同じ機械語コードになっているのが分かるかと思います。なお、ディスアセンブラは mov 表記がお好みみたいデス。実体命令の論理演算、算術演算は隠しておこうという魂胆か?知らんけど。
続いて実体命令が movz になる命令です。Arm社の以下のマニュアルによれば、
Arm Architecture Reference Manual for A-profile architecture
シフト量が0ビットのときは単純な16ビットの即値転送に見えるので、MOVがエイリアスで使えるように書かれています。シフト量が0ビット以外では本来のMOVZで書けという感じ。
movzW:
movz w0, #63, LSL #0
mov w0, #63
ret
movzX:
movz x0, #63, LSL #0
mov x0, #63
ret
movzLHW:
movz w0, #63, LSL #16
ret
movzLHX:
movz x0, #63, LSL #16
ret
上記をディスアセンブルしたものが以下に。エイリアス表記ができるものがエイリアスのMOV優先でディスアセンブルされているのは良いとして、Arm社のマニュアルではMOVZで書くことになっているシフト0ビット以外の命令もMOV表記にディスアセンブルしています。その上、即値をみると「エンコード上はありえない」24ビット幅表記です。まあ、分かり易いっちゃ分かり易いのだけれども。
最後に MOVN と MOVK です。こいつらは前々回に勉強したとおりで、反転したり、前の値の一部を書き換えるだけだったり、ちょっと普通のMOVというにはクセが強いので、そのまま記述してます。特に MOVKは、レジスタの前の値の一部が残るので、先にMOVNした後で、MOVKしています。
movnLHW:
movn w0, #63, LSL #16
ret
movnLHX:
movn x0, #63, LSL #16
ret
movkLHW:
movn w0, #0, LSL #0
movk w0, #63, LSL #16
ret
movkLHX:
movn x0, #0, LSL #0
movk x0, #63, LSL #16
ret
上記をディスアセンブルしたものが以下に。ディスアセンブラはともかくmov表記に帰着させたいみたいで、MOVNも先ほどのMOVZ同様「エンコード上はありえないビット幅」のMOV表記にディスアセンブルされてます。しかし、MOVK命令では、前の命令の値次第なので、1命令単位で結論だしている筈のディスアセンブラはMOVに帰着させることができず、本来のMOVK命令が見えてます。是々非々なご対応。
実験に使用したC言語ソース
上記のアセンブリ言語ソースで定義された関数を呼び出して結果を観察するためのC言語コードが以下に。通り一遍、撫でるだけのもの。
#include <stdio.h>
extern uint32_t movW(uint32_t, uint32_t);
extern uint64_t movX(uint64_t, uint64_t);
extern uint32_t movspW();
extern uint64_t movspX();
extern uint32_t movzW();
extern uint64_t movzX();
extern uint32_t movzLHW();
extern uint64_t movzLHX();
extern uint32_t movnLHW();
extern uint64_t movnLHX();
extern uint32_t movkLHW();
extern uint64_t movkLHX();
int main(void)
{
uint32_t result;
uint64_t resultX;
result = movW(0, 0x12345678);
printf ("movW(0x12345678): 0x%x\n", result);
resultX = movX(0, 0xabcdef0012345678);
printf ("movX(0xabcdef0012345678): 0x%lx\n", resultX);
result = movspW();
printf ("movspW(): 0x%x\n", result);
resultX = movspX();
printf ("movspX(): 0x%lx\n", resultX);
result = movzW();
printf ("movzW(): 0x%x\n", result);
resultX = movzX();
printf ("movzX(): 0x%lx\n", resultX);
result = movzLHW();
printf ("movzLHW(): 0x%x\n", result);
resultX = movzLHX();
printf ("movzLHX(): 0x%lx\n", resultX);
result = movnLHW();
printf ("movnLHW(): 0x%x\n", result);
resultX = movnLHX();
printf ("movnLHX(): 0x%lx\n", resultX);
result = movkLHW();
printf ("movkLHW(): 0x%x\n", result);
resultX = movkLHX();
printf ("movkLHX(): 0x%lx\n", resultX);
return 0;
}
ビルドして実行
例によってTermux上でのビルドは clang 使用です。
$ clang -g -O0 mov.c mov.s
実行結果は以下です。
$ ./a.out movW(0x12345678): 0x12345678 movX(0xabcdef0012345677): 0xabcdef0012345678 movspW(): 0xc42570a0 movspX(): 0x7fc42570a0 movzW(): 0x3f movzX(): 0x3f movzLHW(): 0x3f0000 movzLHX(): 0x3f0000 movnLHW(): 0xffc0ffff movnLHX(): 0xffffffffffc0ffff movkLHW(): 0x3fffff movkLHX(): 0xffffffff003fffff
転送命令が転送しておる値は予定どおり。そんなもんか。



