ぐだぐだ低レベルプログラミング(115)ARM64(AArach64)FMAX, FMIN

Joseph Halfmoon

今回は2つのソースオペランドのうち大きい方を求めるFMAXと小さい方を求めるFMINです。でもね、この命令それほどシンプルでもありません。メンドクセー奴、NaN(Not a Number)がからんでくるから。2命令FMAXとFMAXNMの差はNaNのとりあつかい次第。NaNにあまり深入りせずにお楽に行きたいです。

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

2つのFMAX、2つのFMIN

ソースオペランド2個を比較して大きい方をデスティネーションに書き込むFMAXと小さい方を書き込むFMINです。ほぼほぼ同じ命令がニーモニック的には2個づつあります。

    1. FMAX
    2. FMAXNM
    3. FMIN
    4. FMINNM

2つのFMAX、2つのFMINの違いはといえば、ソースオペランドの片方にqNaN(静かな?NaN)が現れたときにわかります。FMAXとFMINの場合、比較対象の片方にqNaNがやってきた場合、結果もqNaNです。NaNと何かを比較してもなんだか分からんからNaN(ナン)だろ~ってか。

しかし、FMAXNMとFMINNMは違うようです。IEEE754-2008標準のお作法に則り、片方がqNaNでも、もう片方がフツーの数値ならその数値が返るようです。知らんけど。

これ以外にもNaNをゼロに”flush”する、flush to zeroモードのときにはゼロにも2種類あって。。。などメンドクセーことこの上ないですが、今回は省略。さっと「命令をなでる」だけにします。

    • 例によって単精度と倍精度の両方あるけれど単精度だけ(ArmV8.0には半精度はないよ~ん)
    • FMAXとFMAXNM、FMINとFMINNMの差が見えるところだけNaN(qNaN)を使用
今回実験のアセンブリ言語関数

「いつものように手抜きな」関数プロローグもエピローグもない1命令1関数スタイルの被テスト関数です。浮動小数点例外フラグの確認用にFPSR読み出し関数も置いておきましたが使いませんでした。手抜き。

.globl	fmaxS, fmaxnmS, fminS, fminnmS, readfpsr
.text
.balign	4

fmaxS:
    fmax s0, s1, s2
    ret

fmaxnmS:
    fmaxnm s0, s1, s2
    ret

fminS:
    fmin s0, s1, s2
    ret

fminnmS:
    fminnm s0, s1, s2
    ret

readfpsr:
    mrs x0, fpsr
    ret
C言語記述のmain関数

いつもの通り「通り一遍さわるだけの手抜きな」C言語記述のテスト駆動部が以下に。今回は各命令間の挙動の差が見えるように、全命令に似たオペランド組2パターンづつを与えてます。最初のパターンは素直に大小比較するもの。2番目のパターンはNaNが現れるもの。

C言語の場合、math.h をインクルードすればqNaN相当のNANというマクロがつかえるようです。sNaN(シグナルNaN、五月蠅い方のNaN)はどうしたらよいの?テキトーにビット演算する?まあ、今回はsNaNには踏み込まんのでいいか。

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

extern float fmaxS(float, float, float);
extern float fmaxnmS(float, float, float);
extern float fminS(float, float, float);
extern float fminnmS(float, float, float);
extern uint32_t readfpsr();

int main(void)
{
    union {
        float s;
        uint32_t u;
    } f32;

    f32.s = fmaxS(0.0f, 1.23f, 4.56f);
    printf ("fmaxS=%2.3f(0x%08x)\n", f32.s, f32.u);
    f32.s = fmaxS(0.0f, 1.23f, NAN);
    printf ("fmaxS=%2.3f(0x%08x)\n", f32.s, f32.u);

    f32.s = fmaxnmS(0.0f, 1.23f, 4.56f);
    printf ("fmaxnmS=%2.3f(0x%08x)\n", f32.s, f32.u);
    f32.s = fmaxnmS(0.0f, 1.23f, NAN);
    printf ("fmaxnmS=%2.3f(0x%08x)\n", f32.s, f32.u);

    f32.s = fminS(0.0f, 1.23f, 4.56f);
    printf ("fminS=%2.3f(0x%08x)\n", f32.s, f32.u);
    f32.s = fminS(0.0f, NAN, 4.56f);
    printf ("fminS=%2.3f(0x%08x)\n", f32.s, f32.u);

    f32.s = fminnmS(0.0f, 1.23f, 4.56f);
    printf ("fminnmS=%2.3f(0x%08x)\n", f32.s, f32.u);
    f32.s = fminnmS(0.0f, NAN, 4.56f);
    printf ("fminnmS=%2.3f(0x%08x)\n", f32.s, f32.u);

    return 0;
}
ビルドして実行

結果が以下に。FMAXresult

数値同士をくらべたときは分かりやすい結果ですが、NaNがからむとナンだかな~。でもお作法は守らないと。

ぐだぐだ低レベルプログラミング(114)ARM64(AArach64)FABS他 へ戻る

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