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

Joseph Halfmoon

前回前々回と浮動小数点レジスタと整数レジスタの間での整数/固定小数点数変換をエクササイズしてきました。しかし今回は浮動小数点レジスタ間での「整数変換」です。フォーマット上は浮動小数、でも中身は整数ピッタンコの値というやつ。またまたメンドイ「丸め」が登場して命令ニーモニック数が激増。A64ホントに命令多いな。

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

Floating-point round to an integer of the same size as the register

命令群としては、上のタイトルのようなのカテゴリです。浮動小数点レジスタの中に入っている浮動小数点数を、整数相当の値に丸めて、やはり浮動小数点レジスタ(当然フォーマットは浮動小数)に代入する命令です。

ここでも「命令の少ない」ARMv8p0は嬉しいです。単精度レジスタ間、倍精度レジスタ間での整数変換だけで、半精度はサポートされていないので登場しません。またバージョンの上がった機種では単精度倍精度間のような異なる幅のレジスタ間の変換もサポートされているようですが(別ニーモニック)それもありません。良かった命令の数が少なくて。

しかし整数変換にはお約束です。丸めが関係してきます。FCVT命令群同様、丸めがニーモニックで指定されている命令があり、ニーモニックの数が膨れあがってます。多少の救いはFCVT系と違い符合付、符号無の種別がないことです。浮動小数点フォーマットなので漏れなく符合はついてくるっと。

さて丸めモードがニーモニックに記されている命令は以下の5個です。

    1. FRINTA、最近接Away
    2. FRINTM、負の無限大方向
    3. FRINTN、最近接偶数
    4. FRINTP、正の無限大方向
    5. FRINTZ、ゼロ方向

それ以外にFPCRレジスタの丸めモード制御ビットRMによって丸めモードが決定される命令が2個あります

    • FRINTI
    • FRINTX

FRINTIは期待どおりというか、RMビットの値で挙動が変わる命令です。一筋縄ではいかない?のがFRINTXの方です。表ではFRINTIと変わらない丸めを行ってくれるのですが、もし変換前の数値が変換後の数値と異なる値だった場合、Inexact例外が発生するという裏の顔をもってます。恐ろしい。変換前の数値が整数相当でなく、小数点以下の数を含んでいた場合にハタを上げることができるのでした。

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

いつものように手抜きな、関数プロローグもエピローグもない1命令1関数スタイルです。さらに手抜きは、単精度レジスタ間も倍精度レジスタ間も見た目はそんなに変わらんじゃろうから、単精度だけね、と倍精度間を切り捨ててしまっていることです。ゼロ方向丸めじゃなかろうよ。それでもやること多すぎ。

.globl    frintaS, frintmS, frintnS, frintpS, frintzS, frintiS, frintxS, readfpcr, readfpsr, writefpcr
.text
.balign    4

frintaS:
    frinta s0, s1
    ret

frintmS:
    frintm s0, s1
    ret

frintnS:
    frintn s0, s1
    ret

frintpS:
    frintp s0, s1
    ret

frintzS:
    frintz s0, s1
    ret

frintiS:
    frinti s0, s1
    ret

frintxS:
    frintx s0, s1
    ret

readfpcr:
    mrs x0, fpcr
    ret

readfpsr:
    mrs x0, fpsr
    ret

writefpcr:
    msr fpcr, x0
    ret
C言語記述のmain関数

これまた手抜きなC言語記述のテスト駆動部が以下に。今回は丸める値は 1.5と-1.5に絞りました。一番差が分かりやすいものね。手抜きとも言う?最初に直に丸めるだけの奴らをやり、例外フラグが立つFRINITX命令は最後のお楽しみです。なお、例外はイネーブルになっていません。例外フラグが立ってもそれだけでおしまいの予定。

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

extern float frintaS(float, float);
extern float frintmS(float, float);
extern float frintnS(float, float);
extern float frintpS(float, float);
extern float frintzS(float, float);
extern float frintiS(float, float);
extern float frintxS(float, float);
extern uint64_t readfpcr(uint64_t);
extern uint64_t readfpsr(uint64_t);
extern void writefpcr(uint64_t);

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

    resultS = frintaS(0.0f, 1.5f);
    printf  ("frintaS(0.0f, 1.5f): %f\n", resultS);
    resultS = frintmS(0.0f, 1.5f);
    printf  ("frintmS(0.0f, 1.5f): %f\n", resultS);
    resultS = frintnS(0.0f, 1.5f);
    printf  ("frintnS(0.0f, 1.5f): %f\n", resultS);
    resultS = frintpS(0.0f, 1.5f);
    printf  ("frintpS(0.0f, 1.5f): %f\n", resultS);
    resultS = frintzS(0.0f, 1.5f);
    printf  ("frintzS(0.0f, 1.5f): %f\n", resultS);

    resultS = frintaS(0.0f, -1.5f);
    printf  ("frintaS(0.0f, -1.5f): %f\n", resultS);
    resultS = frintmS(0.0f, -1.5f);
    printf  ("frintmS(0.0f, -1.5f): %f\n", resultS);
    resultS = frintnS(0.0f, -1.5f);
    printf  ("frintnS(0.0f, -1.5f): %f\n", resultS);
    resultS = frintpS(0.0f, -1.5f);
    printf  ("frintpS(0.0f, -1.5f): %f\n", resultS);
    resultS = frintzS(0.0f, -1.5f);
    printf  ("frintzS(0.0f, -1.5f): %f\n", resultS);

    result32 = ((uint32_t)readfpcr(0) >> 22) & 3;
    printf ("RM    :    %d\n", result32);
    resultS = frintiS(0.0f, 1.5f);
    printf  ("frintiS(0.0f, 1.5f): %f\n", resultS);
    resultS = frintxS(0.0f, 1.0f);
    printf  ("frintxS(0.0f, 1.0f): %f\n", resultS);
    result32 = (uint32_t)readfpsr(0);
    printf ("PSR   :    %08x\n", result32);
    resultS = frintxS(0.0f, 1.5f);
    printf  ("frintxS(0.0f, 1.5f): %f\n", resultS);
    result32 = (uint32_t)readfpsr(0);
    printf ("PSR   :    %08x\n", result32);

    return 0;
}
実行結果

実行結果が以下に。frintResults

 

まずは丸めモードがニーモニックに含まれる奴らで1.5と-1.5を変換してます。丸めモードの差がわかりますかね。「浮動小数点業界」の人たちはこの辺で血で血を洗う抗争をしているらしいっす。知らんけど。

続いてfrintiをやってますが、RMの値は成り行きの0(最近接Awayだった筈)だけの手抜き。メンドイのでモード変更してません。

そしてお楽しみのfrintxですが、1.0という整数相当の値を変換したらば1.0だと。当たり前か。このときのPSRは平穏無事で全部ゼロです。しかし、1.5などという整数相当でない値を引数に与えると、結果はそのときのRMの設定から2.0と丸めてくれたものを返してくれますが、一番したの赤いオシルシを御覧じろ。PSRレジスタのビット4、IXC(Inexact)フラグが立ってます。天網恢恢疎にして漏らさず。違うか?

ぐだぐだ低レベルプログラミング(110)ARM64(AArach64)SCVTF、UCVTF へ戻る

ぐだぐだ低レベルプログラミング(112)ARM64(AArach64)積和は”fused” へ進む