ぐだぐだ低レベルプログラミング(70) ARM64(AArch64)、論理命令AND一族#2

Joseph Halfmoon

前回に続き、A64の論理演算命令群、AND一族の2回目です。今回は演算結果をフラグに反映する「S」のついた命令どもをエクササイズしてみます。またands命令の「エイリアス」、tst命令についてもそのコード生成確認とともに練習してみます。Arm64ビットのクセに慣れればこれはこれでらくちん。

※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら

※動作確認は普及価格帯のAndroidスマホで行っています。Cortex-A73/Cortex-A53 各4コアの bigLITTLE 64ビット。Android上にインストールしたTermux環境にWindowsPCからSSH接続し、clang/llvmのツールチェーンでビルドしております。

今回練習するアセンブラコード

冒頭のアイキャッチ画像にAND一族の表を掲げてありますが、今回練習する命令はその赤枠の部分です。

    • 1命令=1関数のスタイルの「ミニマム」なコードです。
    • フラグに結果が反映された演算結果を戻り値にするためにcset しています(今気が付きましたが一部x0とすべきところw0としてしまってますが、結果変わらないのでオーライと。どこがオーライ?)
    • 即値をとる命令については即値のとりえる最大幅のオール1とのANDをとるようにしています。
    • TST命令については、ことさらに同じ機械語コードが生成されるANDS命令をまずおいて、その直後にTST命令をおいてあります。後で同じコードが生成されていることを確認します。

アセンブリ言語ソースが以下に

.globl  andsImmW, andsImmX, tstImmW, tstImmX, andsW, andsX, tstW, tstX, bicsW, bicsX
.text
.balign 4

andsImmW:
    ands    w0, w1, #0x1FFF
    cset    w0, EQ
    ret

andsImmX:
    ands    x0, x1, #0x3FFF
    cset    w0, EQ
    ret

tstImmW:
    ands    wzr, w1, #0x1FFF
    tst     w1, #0x1FFF
    cset    w0, EQ
    ret

tstImmX:
    ands    xzr, x1, #0x3FFF
    tst     x1, #0x3FFF
    cset    w0, EQ
    ret

andsW:
    ands    w0, w1, w2
    cset    w0, EQ
    ret

andsX:
    ands    x0, x1, x2
    cset    x0, EQ
    ret

tstW:
    ands    wzr, w1, w2
    tst     w1, w2
    cset    w0, EQ
    ret

tstX:
    ands    xzr, x1, x2
    tst     x1, x2
    cset    x0, EQ
    ret
bicsW:
    bics    w0, w1, w2
    cset    x0, EQ
    ret

bicsX:
    bics    x0, x1, x2
    cset    x0, EQ
    ret

TST imm命令の以下の記述ペアが同一の機械語になることの確認

    ands    wzr, w1, #0x1FFF
    tst     w1, #0x1FFF

    ands    xzr, x1, #0x3FFF
    tst     x1, #0x3FFF

ディスアセンブルかけた結果が以下に。
tstImmDump

TST 命令の以下の記述ペアが同一の機械語になることの確認

    ands    wzr, w1, w2
    tst     w1, w2

    ands    xzr, x1, x2
    tst     x1, x2

ディスアセンブルかけた結果が以下に。tstDump

実験に使用したC言語ソース

上記のアセンブリ言語ソースで定義された関数を呼び出して結果を観察するためのC言語コードが以下に。1命令につき1ケースしかテストしていない「ほぼほぼザル」なコードです。よゐこは真似してはいけないヤツ。

#include <stdio.h>

extern uint32_t andsImmW(uint32_t, uint32_t);
extern uint64_t andsImmX(uint64_t, uint64_t);
extern uint32_t andsW(uint32_t, uint32_t, uint32_t);
extern uint64_t andsX(uint64_t, uint64_t, uint32_t);
extern uint32_t tstImmW(uint32_t, uint32_t);
extern uint64_t tstImmX(uint64_t, uint64_t);
extern uint32_t tstW(uint32_t, uint32_t, uint32_t);
extern uint64_t tstX(uint64_t, uint64_t, uint32_t);
extern uint32_t bicsW(uint32_t, uint32_t, uint32_t);
extern uint64_t bicsX(uint64_t, uint64_t, uint32_t);

int main(void)
{
    int32_t result;
    int64_t resultX;

    result = andsImmW(0, 0xE000);
    printf ("andsImmW(0, 0xE000):  EQ=0x%x\n", result);
    resultX = andsImmX(0, 0xC000);
    printf ("andsImmX(0, 0xC000):  EQ=0x%lx\n", resultX);

    result = tstImmW(0, 0xE000);
    printf ("tstImmW(0, 0xE000):   EQ=0x%x\n", result);
    resultX = tstImmX(0, 0xC000);
    printf ("tstImmX(0, 0xC000):   EQ=0x%lx\n", resultX);

    result = andsW(0, 0xFFFFFFFE, 1);
    printf ("andsW(0,  0xFFFFFFFE, 1): EQ=0x%x\n", result);
    resultX = andsX(0, 0x1FFFFFFFE, 1);
    printf  ("andsX(0, 0x1FFFFFFFE, 1): EQ=0x%lx\n", resultX);

    result = tstW(0, 0xFFFFFFFE, 1);
    printf ("tstW(0,   0xFFFFFFFE, 1): EQ=0x%x\n", result);
    resultX = tstX(0, 0x1FFFFFFFE, 1);
    printf  ("tstX(0,  0x1FFFFFFFE, 1): EQ=0x%lx\n", resultX);

    result = bicsW(0, 0xFFFFFFFE, 1);
    printf ("bicsW(0,  0xFFFFFFFE, 1): EQ=0x%x\n", result);
    resultX = bicsX(0, 0x1FFFFFFFE, 1);
    printf  ("bicsX(0, 0x1FFFFFFFE, 1): EQ=0x%lx\n", resultX);

    return 0;
}
ビルドして実行

例によってTermux上でのビルドは clang 使用です。

$ clang -g -O0 -o ands ands.c ands.s

実行結果が以下に。

results

予定通りの結果が得られましたぞ。今回は10命令も練習できたですが、命令は多く先は長いデス。

ぐだぐだ低レベルプログラミング(69) ARM64(AArch64)、論理命令AND一族#1 へ戻る

ぐだぐだ低レベルプログラミング(71) ARM64(AArch64)、MOVの込み入った事情