ぐだぐだ低レベルプログラミング(117)ARM64(AArach64)FCSEL

Joseph Halfmoon

前回FCMP命令を練習したので「そこへの道が開いた」のがFCSEL命令であります。比較結果(条件フラグ)に基づいて「ソース1」をデスティネーションに書き込むのか「ソース2」を書き込むのか実行するもの。C言語の3項演算子みたいなものですが、肝心の条件比較は先行する命令、FCMPかCMP(整数比較)などにお任せです。

※「ぐだぐだ低レベルプログラミング」投稿順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

ARMv8もいろいろレベルがあり、Arm Cortex-A72はARMv8の中でもベーシックな(命令数の少ない)ARMv8p0です。

※A64の最新のマニュアルは以下でダウンロード可能です。

Arm Architecture Reference Manual for A-profile architecture

FCSEL命令

整数演算でも条件選択命令 CSEL がありました。しかしCSELの一族郎党数多く第83回第84回の2回にわけて実習してみたくらいです。しかし浮動小数点(スカラー)の条件選択命令FCSELは1命令のみ。第1オペランドで指定されるデスティネーション・レジスタへ第2オペランドのソースレジスタ1か、第3オペランドのソースレジスタ2の内容を選択して代入するもの。判定条件は、第4オペランドに「いつもの」スタイルで指定せよ、との思し召しです。「いつもの」を忘れてしまったよゐこは以下の「公式」をご参照くだされ。

Condition code suffixes and related flags

今回実験のアセンブリ言語関数

「いつものように手抜きな」関数プロローグもエピローグもない被テスト関数ですが「いつも」とちょっと違うのが、被テスト命令一つ実行してすぐにRETではなく、被テスト命令を実行する前にFCMPしてフラグを作っているところです。今回のは第2オペランドと第3オペランドの内容を直前のFCMPで比較し、比較した結果で軍配の上がった方を後続のFCSELでデスティネーションに送り込むという趣向です。

このところメンドイので単精度ばかりやってきましたが、今回はFCSEL1命令だけなのでゴージャス?に倍精度も練習。なお、ARMv8p0には半精度はありません。

また、条件フラグは上のコンディションコードSuffix というものを参照すると沢山あるのですが、実験しているのは EQとGTだけです。手抜き。

.globl	fcselEQS, fcselEQD, fcselGTS, fcselGTD, readfpsr, readNZCV, writeNZCV
.text
.balign	4

fcselEQS:
    fcmp  s1, s2
    fcsel s0, s1, s2, EQ
    ret

fcselEQD:
    fcmp  d1, d2
    fcsel d0, d1, d2, EQ
    ret

fcselGTS:
    fcmp  s1, s2
    fcsel s0, s1, s2, GT
    ret

fcselGTD:
    fcmp  d1, d2
    fcsel d0, d1, d2, GT
    ret

readfpsr:
    mrs x0, fpsr
    ret

readNZCV:
    mrs x0, NZCV
    ret

writeNZCV:
    msr NZCV, x0
    ret

念のため、FPSRやNZCVアクセスの関数も残しておいたのですが、今回は使いませなんだ。

C言語記述のmain関数

いつも通りの「通り一遍さわるだけの手抜きな」C言語記述のテスト駆動部です。微妙な比較を繰り返して結果だしてますが、テストケースをコピペする手抜きが原因です。よゐこはやってはいけないやつ?

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

extern float fcselEQS(float, float, float);
extern double fcselEQD(double, double, double);
extern float fcselGTS(float, float, float);
extern double fcselGTD(double, double, double);
extern uint32_t readfpsr();
extern uint32_t readNZCV();
extern void writeNZCV(uint32_t);

int main(void)
{
    union {
        double d;
        uint64_t u;
    } result64;

    union {
        float s;
        uint32_t u;
    } result32;

    uint32_t temp32;

    result32.s = fcselEQS(0.0f, 1.23f, 1.23f);
    printf     ("fcselEQS(0.0f, 1.23f, 1.23f):          %7.6f\n", result32.s);
    result32.s = fcselEQS(0.0f, 1.23f, 1.234567f);
    printf     ("fcselEQS(0.0f, 1.23f, 1.234567f):      %7.6f\n", result32.s);

    result64.d = fcselEQD(0.0, 1.23456789, 1.23456789);
    printf     ("fcselEQD(0.0, 1.23456789, 1.23456789): %10.8lf\n", result64.d);
    result64.d = fcselEQD(0.0, 1.23456789, 1.3456789);
    printf     ("fcselEQD(0.0, 1.23456789, 1.3456789):  %10.8lf\n", result64.d);

    result32.s = fcselGTS(0.0f, 1.25f, 1.23f);
    printf     ("fcselGTS(0.0f, 1.25f, 1.23f):          %7.6f\n", result32.s);
    result32.s = fcselGTS(0.0f, 1.23f, 1.234567f);
    printf     ("fcselGTS(0.0f, 1.23f, 1.234567f):      %7.6f\n", result32.s);

    result64.d = fcselGTD(0.0, 1.43456789, 1.23456789);
    printf     ("fcselGTD(0.0, 1.43456789, 1.23456789): %10.8lf\n", result64.d);
    result64.d = fcselGTD(0.0, 1.23456789, 1.3456789);
    printf     ("fcselGTD(0.0, 1.23456789, 1.3456789):  %10.8lf\n", result64.d);

    return 0;
}
ビルドして実行

ビルドして実行した結果が以下に。

fsel_result

期待どおりの選択が行われとります。よかった。それに長く続いた浮動小数点の演算命令も完了ということで嬉しい。でもスカラーだけだし。

ぐだぐだ低レベルプログラミング(116)ARM64(AArach64)FCMP へ戻る

ぐだぐだ低レベルプログラミング(118)ARM64(AArach64)Floatのロード#1 へ進む