ぐだぐだ低レベルプログラミング(79)ARM64(AArch64)、先行ビットカウント命令

Joseph Halfmoon

前回はシフト、ローテイトの落穂ひろいでした。今回は先行する(MSB側の)ビットカウントを行う命令です。時々「こういう操作」が必要なことがあり、「こういう専用命令」が無いと時間がかかるので有った方が嬉しい気もするのです。しかしArmはRISCの割には複雑な感じを醸しだしてる気もします(個人の感想です。)

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

※以下は下記にて動作確認しています。

    • Raspberry Pi 4 model B、Cortex-A72コア ARMv8-A
    • Raspberry Pi OS (64bit) bullseye
    • gcc (Debian 10.2.1-6) 10.2.1 20210110
CLSとCLZ命令

ビットカウントといった場合、「変数(レジスタ)の中で1が立っているビットの数を数える」ことを指すことがありますが、ここではソースレジスタの「先行する」ビットを数えるという前提です。数えた結果はデスティネーションレジスタに格納します。

    • CLS (Count Leading Sign bits)
    • CLZ (Count Leading Zero bits)

以下に32ビットのソースレジスタ(Wx)に対してCLS、CLZ命令を適用した場合の数えるビット範囲のイメージを図にしています。CLSは緑のMSBと一致する黄緑のビット数を数えます。MSBと不一致となる青色ビットで数えるのを止めます。CLZはともかくMSB側から0を数えていき、1を発見したら止めます。

注意すべきなのは、サインビットを数えるCLS命令の場合、レジスタ先頭のMSBと一致する先行ビットの数を数えるのですが、数えるときにMSBは含まれない、というお約束であることです。

それに対してCLZ命令ではべたに数えるので、MSBも0であれば数に含まれます(MSBが0でなければ結果は0です。)

実験用アセンブリ言語ソース

いつもと違い、エイリアス命令などは登場しないので、ただ、32ビット(W)と64ビット(X)の違いだけです。1関数=1命令の実験です。

.globl	clsW, clsX, clzW, clzX
.text
.balign	4

clsW:
    cls w0, w1
    ret

clsX:
    cls x0, x1
    ret

clzW:
    clz w0, w1
    ret

clzX:
    clz x0, x1
    ret
アセンブル関数を呼び出してテストするCのコード

例によって「とりあえず各命令1例だけ」触ってみるコードが以下に。結果は分かり易いように10進表示です。

#include <stdio.h>
#include <stdint.h>

extern uint32_t clsW(uint32_t, int32_t);
extern uint64_t clsX(uint64_t, int64_t);
extern uint32_t clzW(uint32_t, uint32_t);
extern uint64_t clzX(uint64_t, uint64_t);

int main(void)
{
    uint32_t result;
    uint64_t resultX;

    result = clsW(0, -256);
    printf ("clsW: %d\n", result);
    resultX = clsX(0, -256);
    printf ("clsX %ld\n", resultX);

    result = clzW(0, 0x87);
    printf ("clzW: %d\n", result);
    resultX = clzX(0, 0x87);
    printf ("clzX %ld\n", resultX);

    return 0;
}
ビルドして実行

コマンドラインが以下に。

gcc -g -O0 -o clsclz clsclz.c clsclz.s

実行結果が以下に。

Results

CLSとCLZの数え方の違いが出ておりますな。

ぐだぐだ低レベルプログラミング(78)ARM64(AArch64)、シフト、ローテイトの片割れ へ戻る

ぐだぐだ低レベルプログラミング(80) ARM64(AArch64)、Reverse命令一族 へ進む