ぐだぐだ低レベルプログラミング(73) ARM64(AArch64)、MOV命令の実習

Joseph Halfmoon

前回前々回と実機練習をサボって調べ物をしていたので、今回は練習あるのみです。でも溜まっている数が多いです。今回は、前々回の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 表記がお好みみたいデス。実体命令の論理演算、算術演算は隠しておこうという魂胆か?知らんけど。

movDIS0

続いて実体命令が 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ビット幅表記です。まあ、分かり易いっちゃ分かり易いのだけれども。

movDIS1

最後に 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命令が見えてます。是々非々なご対応。

movDIS2

実験に使用した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

転送命令が転送しておる値は予定どおり。そんなもんか。

ぐだぐだ低レベルプログラミング(72) ARM64(AArch64)、ビットフィールドMOV へ戻る

ぐだぐだ低レベルプログラミング(74) ARM64(AArch64)、BFM命令、別名不在 へ進む