面倒なので避けてきましたが、いよいよやらなければならないです。条件フラグ関係。昔から慣れ親しんだモノドモですが、RISC-Vの「フラグは無、その場で判断」方式を学んだあとは、とてもメンドく感じられるようになってます。今回はフラグが格納されている(ようにみえる)レジスタへのアクセスと基本的な上げ下げを動かしてみます。
※「ぐだぐだ低レベルプログラミング」投稿順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における条件フラグは伝統的なセットです。
-
- N、ネガティブ、計算結果が負である
- Z、ゼロ、計算結果がゼロである
- C、キャリー、計算のビット幅のMSBを超えて桁上げ(借り)発生
- V、オーバーフロー、MSB-1ビットからMSB(符号ビット)に桁上げ発生
以上の4ビットです。
条件判断をするにあたっては、上記の4ビットのうち一つあるいは複数を判断して真偽を決めます。通常、フラグをレジスタとして読み書きする必要はあまりないと思います。しかし、命令セットとしてはフラグの読み書きがサポートされています。フラグはNZCVというベタな名前の64ビット幅の制御レジスタとして読み書きできます(冒頭のアイキャッチ画像のように64ビット幅のうち4ビットしか使われていないレジスタです。)NZCVは以下のような制御レジスタの転送命令をつかってアクセスできます。
-
- MRS <Xt>, NZCV
- MSR NZCV, <Xt>
NZCVについてのArm社マニュアルへのリンクはこちら。
実験に使ったアセンブリ言語ソース
基本1命令1関数スタイルの、実験用アセンブリ言語ソースが以下に。
-
- readNZCV、フラグを読み出して返す
- writeNZCV、NZCVに値を立てる(64ビット幅)
- ccmpW、32ビット引数2つを比較して、NZCVの各フラグを立てる
.globl readNZCV, writeNZCV, ccmpW .text .balign 4 readNZCV: mrs x0, NZCV ret writeNZCV: msr NZCV, x0 ret ccmpW: ccmp W0, W1, NZCV, AL mrs x0, NZCV ret
アセンブル関数を呼び出してテストするCのコード
例によってざっと触ってみるコードが以下です。
-
- まずフラグを全クリアしてクリアを確認
- その後比較をしてフラグを上げ下げして確認
#include <stdio.h> #include <stdint.h> extern uint64_t readNZCV(void); extern void writeNZCV(uint64_t); extern uint64_t ccmpW(uint32_t, uint32_t); int main(void) { uint64_t resultX; writeNZCV(0x0); resultX = readNZCV(); printf ("NZCV( 0): %016lx\n", resultX); resultX = ccmpW(1, 1); printf ("NZCV(ZC): %016lx\n", resultX); resultX = ccmpW(0xFFFFFFFF, 1); printf ("NZCV(NC): %016lx\n", resultX); resultX = ccmpW(0x7FFFFFFF, 0xFFFFFFFF); printf ("NZCV(NV): %016lx\n", resultX); return 0; }
ビルドして実行
コマンドラインが以下に。
gcc -g -O0 -o nzcv nzcv.c nzcv.s
実行結果が以下に。
予定通り、各フラグは立ったり、おりたりしてるみたいです。あたりまえか。