ぐだぐだ低レベルプログラミング(65) ARM64(AArch64)、レジスタ拡張付きadd

Joseph Halfmoon

Armの64ビット命令をさらっと舐めるつもりが、add命令も既に4回目です。今回動かしてみますのが、符号拡張、ゼロ拡張、狭いビット幅のオペランドを広いビット幅に拡張してから足し込む命令です。とりあえず拡張前はバイト縛りといたしましたが、それでも選択肢が多すぎ。テストの組み合わせは8命令です。Armの命令多すぎないか。

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

※動作確認は、普及価格帯の年月を経たAndroidスマホです。それでもCortex-A73/Cortex-A53 各4コアの bigLITTLE 、64ビットで動作してます。Android上にインストールしたTermux環境で、clang/llvmのツールチェーンでビルドしております。

今回動作確認する add 命令

以前に作製しましたadd命令のとりえるバリエーションをまとめた図に今回ターゲットとする領域を赤枠で示します。「拡張系」の組み合わせ、結構多いです。今回はバイトからの拡張だけやって、後は以下同文式で済ませる目論見です。赤枠4個にたいして8種類を実験する必要性を認めました。ADD_EXT_BYTE

赤枠4個なのに倍の8命令を確かめたくなるのは、「シフト」のフィールドがあるためです。シフト付きの命令は前回やりました。前回のは「フルビット幅」で左/右、算術/論理すべてできるシフトでした。今回はちんまりと左論理シフトのみで最大でも4ビットまでです。デフォルトはシフト無(0ビットシフト。)

    1. 符号付き、符号無の2通り
    2. 転送先が32ビット幅か、64ビット幅かの2通り
    3. シフト無(0)かシフト1ビット(代表例ということで)あの2通り

上記の3条件で合計8通りです。

ソースコード

例によって関数の実体が1命令の被テスト関数群のアセンブラ・ソースが以下に。32ビットのときは良いのですが、64ビット命令では、レジスタ名がX X ときて最後Wとなるので何故かヤマシイ感じがします。しかしバイトからの拡張の場合、そう書かねばなりません。また、全ての命令にことさらにシフト量を書いていますが、#0はデフォルトにまかせて書かない方が普通じゃないかと思います。

.globl  addextBZW0, addextBZW1, addextBSW0, addextBSW1, addextBZX0, addextBZX1, addextBSX0, addextBSX1
.text
.balign 4

addextBZW0:
        add             w0, w1, w2, UXTB #0
        ret

addextBZW1:
        add             w0, w1, w2, UXTB #1
        ret

addextBSW0:
        add             w0, w1, w2, SXTB #0
        ret

addextBSW1:
        add             w0, w1, w2, SXTB #1
        ret

addextBZX0:
        add             x0, x1, w2, UXTB #0
        ret

addextBZX1:
        add             x0, x1, w2, UXTB #1
        ret

addextBSX0:
        add             x0, x1, w2, SXTB #0
        ret

addextBSX1:
        add             x0, x1, w2, SXTB #1
        ret

上記の被テスト関数群をテストするためのCのmain()関数が以下に。関数プロトタイプを見ると引数が皆符号無なのに、結果を符号付きにしているのでちょいと気持ち悪いかもしれません。でもま、アセンブラ・レベルじゃ結局ビット列だ、と。今回はprintfで「符号」が見やすいように、との意図での変則設定です。

#include <stdio.h>

#define TSTV  (0xFF)

extern int32_t addextBZW0(uint32_t, uint32_t, uint8_t);
extern int32_t addextBZW1(uint32_t, uint32_t, uint8_t);
extern int32_t addextBSW0(uint32_t, uint32_t, uint8_t);
extern int32_t addextBSW1(uint32_t, uint32_t, uint8_t);
extern int64_t addextBZX0(uint64_t, uint64_t, uint8_t);
extern int64_t addextBZX1(uint64_t, uint64_t, uint8_t);
extern int64_t addextBSX0(uint64_t, uint64_t, uint8_t);
extern int64_t addextBSX1(uint64_t, uint64_t, uint8_t);

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

        result = addextBZW0(0, 2, TSTV);
        printf ("addextBZW0 2+TSTV: %d\n", result);
        result = addextBZW1(0, 2, TSTV);
        printf ("addextBZW1 2+TSTV<<1: %d\n", result);
        result = addextBSW0(0, 2, TSTV);
        printf ("addextBSW0 2+TSTV: %d\n", result);
        result = addextBSW1(0, 2, TSTV);
        printf ("addextBSW1 2+TSTV<<1: %d\n", result);
        resultX = addextBZX0(0, 2, TSTV);
        printf  ("addextBZX0 2+TSTV: %ld\n", resultX);
        resultX = addextBZX1(0, 2, TSTV);
        printf  ("addextBZX1 2+TSTV<<1: %ld\n", resultX);
        resultX = addextBSX0(0, 2, TSTV);
        printf  ("addextBSX0 2+TSTV: %ld\n", resultX);
        resultX = addextBSX1(0, 2, TSTV);
        printf  ("addextBSX1 1+TSTV<<1: %ld\n", resultX);

        return 0;
}
ビルドして実行

冒頭のアイキャッチ画像はスマホ画面のキャプチャですが、実際にはWindowsPCからスマホにSSH接続し、パソコン上のウインドウから操作しています。やっぱキーボードあった方が楽だし。

ビルドのコマンドラインは以下です。

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

今回は gdb 使ってないですが、たまにつかうので、基本 -g で -O0です。実行結果が以下に。

Results

TSTVと書いてあるのは、0xFFです。バイト幅で符号無解釈で255、符号付き解釈でー1ですな。拡張後1ビット左シフトすると、符号無で510、符号付きでー2となります。当たり前か。

    • 32ビットレジスタ、シフト0ビット、符号無だと、2+255で257
    • 32ビットレジスタ、シフト1ビット、符号無だと、2+510で512
    • 32ビットレジスタ、シフト0ビット、符号有だと、2+(ー1)で1
    • 32ビットレジスタ、シフト1ビット、符号有だと、2+(ー2)で0

ソースをご覧いただけば次の64ビットレジスタのケースはちゃんと64ビット幅で計算しておるのでありますが、結果は上記と変わりませぬ。

符号拡張、ゼロ拡張できたかな?

ぐだぐだ低レベルプログラミング(64) ARM64(AArch64)、シフト付きadd へ戻る

ぐだぐだ低レベルプログラミング(66) ARM64(AArch64)、adc、加算キャリー付き へ進む