前回から、条件フラグNZCV関係に踏み入っております。やる前から分かっていたことですが、真か偽か判定するだけの1ビットのフラグですが、複数条件重なれば、霧のかかった老人の頭では、いずれが真か偽、是々非々、是々非々。特に今回の命令群は条件フラグの操作をするのに条件を判断して行うという奇怪さであります。
※「ぐだぐだ低レベルプログラミング」投稿順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
条件フラグに影響を与える演算命令を整理
A64(AArch64)では、条件フラグNZCVを上げ下げする演算命令は限られます。基本、ニーモニックの後ろに “S” がついているものども。しかし、例によってエイリアスもあり、Sがつかない比較命令、テスト命令も存在します。冒頭のアイキャッチ画像で思いつくところを表にまとめてみました。
CCMP、CCMN命令
さて今回実習してみますのは以下の命令です(表の赤枠部分。)
-
- CCMP
- CCMN
さきほどの表をみれば、似たものにCMP(実はSUBSのエイリアス)、CMN(実はADDSのエイリアス)という「似た」命令があります。頭に一文字Cが多いのは何故?と問えば
CはコンディショナルのC
なのでした。やりたいことはCMPとかCMNと同じ比較してのフラグの上げ下げなのですが、それをやる前提として、条件、たとえばEQ(Zフラグが真)とか、GT(NフラグとVフラグが一致し、かつZフラグが偽)とかを見るのです。フラグを上げ下げする操作の前にフラグを判定してから行うと。こういう命令をフツーに持っているArm、恐ろしい「RISC」であります。
なお、前提条件が偽だったらどうするの?という疑問には、命令がもつ即値フィールドの値をそのままNZCVの4フラグに反映させると。こうして書いていても眩暈がしそうな感じです。
気をとりなおして、実習用のアセンブリ言語ソースを書いてみました。いつもは1関数=1命令でシンプルですが、今回は
-
- 前提条件を事前設定するための「普通の」cmp命令
- その後に本題の ccmpまたはccmn命令
- 最後にNZCVのフラグを返す mrs命令
という3段構えです。比較対象は、レジスタとレジスタ、レジスタと短い(5ビット)即値の2通りあり、勿論レジスタには32ビットのWレジスタと64ビットのXレジスタの両方をとれます。さらにいろいろバリエーションをつけていくととてつもない組み合わせを書かねばなりません。そこで
-
- 判定する条件はEQのみ(Zフラグのみ)
- 条件不一致のときには、NZCV全フラグを「立て」て抜ける
ということで組み合わせを圧縮。ソースが以下に。
.globl ccmpW, ccmpX, ccmnW, ccmnX, ccmpiW, ccmpiX, ccmniW, ccmniX .text .balign 4 ccmpW: cmp w0, w1 ccmp w0, w2, NZCV, EQ mrs x0, NZCV ret ccmpX: cmp x0, x1 ccmp x0, x2, NZCV, EQ mrs x0, NZCV ret ccmnW: cmp w0, w1 ccmn w0, w2, NZCV, EQ mrs x0, NZCV ret ccmnX: cmp x0, x1 ccmn x0, x2, NZCV, EQ mrs x0, NZCV ret ccmpiW: cmp w0, w1 ccmp w0, #1, NZCV, EQ mrs x0, NZCV ret ccmpiX: cmp x0, x1 ccmp x0, #1, NZCV, EQ mrs x0, NZCV ret ccmniW: cmp w0, w1 ccmn w0, #1, NZCV, EQ mrs x0, NZCV ret ccmniX: cmp x0, x1 ccmn x0, #1, NZCV, EQ mrs x0, NZCV ret
アセンブル関数を呼び出してテストするCのコード
ざっと触ってみるだけのコードですが、
-
- 条件が一致しないとき
- 条件が一致するけれどフラグが立つとき
- 条件が一致するけれどフラグが立たないとき
くらいの引数の組み合わせはやっておく必要がありますな。32ビット、64ビットに関わらず、引数は0と1と-1ばかりですが組み合わせを列挙すると以下のようになりました。
#include <stdio.h> #include <stdint.h> extern uint64_t ccmpW(int32_t, int32_t, int32_t); extern uint64_t ccmpX(int64_t, int64_t, int64_t); extern uint64_t ccmnW(int32_t, int32_t, int32_t); extern uint64_t ccmnX(int64_t, int64_t, int64_t); extern uint64_t ccmpiW(int32_t, int32_t); extern uint64_t ccmpiX(int64_t, int64_t); extern uint64_t ccmniW(int32_t, int32_t); extern uint64_t ccmniX(int64_t, int64_t); int main(void) { uint64_t resultX; resultX = ccmpW(1, 0, 1); printf ("ccmpW(1, 0, 1): %016lx\n", resultX); resultX = ccmpW(1, 1, 1); printf ("ccmpW(1, 1, 1): %016lx\n", resultX); resultX = ccmpW(1, 1, -1); printf ("ccmpW(1, 1, -1): %016lx\n", resultX); resultX = ccmpX(1, 0, 1); printf ("ccmpX(1, 0, 1): %016lx\n", resultX); resultX = ccmpX(1, 1, 1); printf ("ccmpX(1, 1, -1): %016lx\n", resultX); resultX = ccmpX(1, 1, -1); printf ("ccmpX(1, 1, -1): %016lx\n", resultX); resultX = ccmnW(1, 0, -1); printf ("ccmnW(1, 0, -1): %016lx\n", resultX); resultX = ccmnW(1, 1, -1); printf ("ccmnW(1, 1, -1): %016lx\n", resultX); resultX = ccmnW(1, 1, 1); printf ("ccmnW(1, 1, -1): %016lx\n", resultX); resultX = ccmnX(1, 0, -1); printf ("ccmnX(1, 0, -1): %016lx\n", resultX); resultX = ccmnX(1, 1, -1); printf ("ccmnX(1, 1, -1): %016lx\n", resultX); resultX = ccmnX(1, 1, 1); printf ("ccmnX(1, 1, -1): %016lx\n", resultX); resultX = ccmpiW(1, 0); printf ("ccmpiW(1, 0): %016lx\n", resultX); resultX = ccmpiW(1, 1); printf ("ccmpiW(1, 1): %016lx\n", resultX); resultX = ccmpiX(1, 0); printf ("ccmpiX(1, 0): %016lx\n", resultX); resultX = ccmpiX(1, 1); printf ("ccmpiX(1, 1): %016lx\n", resultX); resultX = ccmniW(-1, 0); printf ("ccmniW(-1, 0): %016lx\n", resultX); resultX = ccmniW(-1, -1); printf ("ccmniW(-1, -1): %016lx\n", resultX); resultX = ccmniX(-1, 0); printf ("ccmniX(-1, 0): %016lx\n", resultX); resultX = ccmniX(-1, -1); printf ("ccmniX(-1, -1): %016lx\n", resultX); return 0; }
ビルドして実行
コマンドラインが以下に。
gcc -g -O0 -o comp comp.c comp.s
実行結果が以下に。
いや、結果を見るだけでも目が回りそう。見たところはあってそうです(いい加減だな、自分。)フラグはメンドイのにさらにメンドイ命令があるArmじゃのう。