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

Joseph Halfmoon

今回はたった1命令、FCVTをエクササイズしてみます。練習に使っているArm Cortex-A72は半精度浮動小数のサポートが無く、以前残念に思ってましたが、今回は無くて良かった、という感じです。単精度があったら組み合わせは3倍、やめてくれです。前回登場のFPCRレジスタのRMビットが活躍します。

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

FCVT命令

FCVT(Floating-point conversion)命令は、複数ある浮動小数点数のフォーマット間の変換命令です。ARMv8.0は、単精度浮動小数と倍精度浮動小数の2つの形式しかないので、単精度から倍精度、倍精度から単精度の2通りの変換しかありません。しかし、先ほど述べましたとおり、ARMv8.2以降の半精度浮動小数をサポートするプロセッサでは、組み合わせは3倍の6通りとなります。

変換は、同じ値を同じ値に「変換」するわけなのでうまく変換できるケースでは見た目が大きく変わることはありません。しかし、フォーマットが変われば表現しきれない数が現れてしまうのは当然。FCVTを練習する場合、その辺のコマケー話、つまりはFPCRとかFPSRとかにかかわる話を避けて通れません。その中には例外に落ちていくケースもある筈。しかし今回は例外には行きません。控えめに丸めモードの制御のみやってみようと思います。

変換先が単精度浮動小数で倍精度の浮動小数の下の方を「丸めないと収まらない」ケースが問題となります。丸めモード(RM mode bit)は前回やったFPCRレジスタのビット23とビット22に格納されています。

    • 00 Nearest
    • 01 Plus Infinity
    • 10 Minus Infinity
    • 11 Zero

上記の4通りです。初期状態では00になっているようです。

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

今回も例によって関数プロローグもエピローグもない、手抜きな被テストアセンブラ関数群です。今回のコードは前回のコードの一部を流用した上にFPCRへの書き込みとFCVT命令を追加したものです。

.globl    readfpcr, readfpsr, writefpcr, fcvtSD, fcvtDS
.text
.balign    4

readfpcr:
    mrs x0, fpcr
    ret

readfpsr:
    mrs x0, fpsr
    ret

writefpcr:
    msr fpcr, x0
    ret

fcvtSD:
    fcvt s0, d1
    ret

fcvtDS:
    fcvt d0, s1
    ret
C言語記述のmain関数

たった1つのFCVTをテスト、それも符合を除けば同じような値を変換するだけなのにケースが多いのは、丸めモードにより変換結果が異なってくるので4通りのケースに分かれるためです。

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

extern uint64_t readfpcr(uint64_t);
extern uint64_t readfpsr(uint64_t);
extern void writefpcr(uint64_t);
extern float fcvtSD(float, double);
extern double fcvtDS(double, float);


int main(void)
{
    uint64_t result;
    uint32_t result32;
    float resultS;
    double resultD;

    result32 = ((uint32_t)readfpcr(0) >> 22) & 3;
    printf ("RM    :    %d\n", result32);
    resultS = fcvtSD(0.0f, 1.23456789012);
    printf ("fcvtSD:    %16e \n", resultS);
    resultD = fcvtDS(0.0, 1.234567f);
    printf ("fcvtDS:    %16e \n", resultD);
    resultS = fcvtSD(0.0f, -1.23456789012);
    printf ("fcvtSD:    %16e \n", resultS);
    resultD = fcvtDS(0.0, -1.234567f);
    printf ("fcvtDS:    %16e \n", resultD);

    writefpcr(3 << 22);
    result32 = ((uint32_t)readfpcr(0) >> 22) & 3;
    printf ("RM    :    %d\n", result32);
    resultS = fcvtSD(0.0f, 1.23456789012);
    printf ("fcvtSD:    %16e \n", resultS);
    resultD = fcvtDS(0.0, 1.234567f);
    printf ("fcvtDS:    %16e \n", resultD);
    resultS = fcvtSD(0.0f, -1.23456789012);
    printf ("fcvtSD:    %16e \n", resultS);
    resultD = fcvtDS(0.0, -1.234567f);
    printf ("fcvtDS:    %16e \n", resultD);

    writefpcr(1 << 22);
    result32 = ((uint32_t)readfpcr(0) >> 22) & 3;
    printf ("RM    :    %d\n", result32);
    resultS = fcvtSD(0.0f, 1.23456789012);
    printf ("fcvtSD:    %16e \n", resultS);
    resultD = fcvtDS(0.0, 1.234567f);
    printf ("fcvtDS:    %16e \n", resultD);
    resultS = fcvtSD(0.0f, -1.23456789012);
    printf ("fcvtSD:    %16e \n", resultS);
    resultD = fcvtDS(0.0, -1.234567f);
    printf ("fcvtDS:    %16e \n", resultD);

    writefpcr(2 << 22);
    result32 = ((uint32_t)readfpcr(0) >> 22) & 3;
    printf ("RM    :    %d\n", result32);
    resultS = fcvtSD(0.0f, 1.23456789012);
    printf ("fcvtSD:    %16e \n", resultS);
    resultD = fcvtDS(0.0, 1.234567f);
    printf ("fcvtDS:    %16e \n", resultD);
    resultS = fcvtSD(0.0f, -1.23456789012);
    printf ("fcvtSD:    %16e \n", resultS);
    resultD = fcvtDS(0.0, -1.234567f);
    printf ("fcvtDS:    %16e \n", resultD);

    return 0;
}
ビルドして実行

似たような値が何度も繰り返されてますが、神(悪魔?)は細部に宿る、RMモードによって変換結果の最下位ビットがどう変わるのかご覧くだされ。fcvt_results

これだから浮動小数点系の命令はメンドイよなあ。文句言ってもしかたないなあ。

ぐだぐだ低レベルプログラミング(106)ARM64(AArach64)FPSR/FPCR へ戻る

ぐだぐだ低レベルプログラミング(108)ARM64(AArach64)FCVTxy、整数変換 へ進む