🔖

Arm64 (AArch64) のFPCRにC言語からアクセスする

2023/08/02に公開

Arm64 (AArch64) で浮動小数点数に関する挙動を変えるにはFPCR (Floating-Point Control Register) を触ります。FPCRで制御できる代表的な項目は丸めモードですが、他にもトラップの制御や非正規化数のflush to zeroの有効化、NaNのペイロードの伝播方法の変更など、色々あります。

32ビットArm (AArch32) の頃はFPSCR (Floating-Point Status and Control Register) と言って浮動小数点数例外の状態フラグも同じレジスターで管理されていましたが、64ビット化にあたって状態フラグはFPSRという別のレジスターに分離されました。

Arm64のFPCRは64ビットの幅がありますが、執筆時点では使用されている部分は下位32ビットに収まっており、上位32ビットはreservedとなっています。

ACLE (Arm C Language Extensions)

ArmのC拡張、ACLEに対応した処理系では __arm_rsr64, __arm_wsr64 などの組み込み関数を使ってFPCRを含む特殊なレジスターにアクセスできます。引数に文字列としてレジスター名を与えます。

執筆時点ではClangがこれらの組み込み関数に対応しています。GCCは14以降で対応しています。MSVCは不明です。

使用例:

#include <arm_acle.h>

__attribute__((always_inline))
uint64_t get_fpcr(void)
{
    return __arm_rsr64("fpcr");
}
__attribute__((always_inline))
void set_fpcr(uint64_t x)
{
    __arm_wsr64("fpcr", x);
}

FPSRにも同様の方法でアクセスできます。

【追記】ACLEを改めて読んだところ、 "fpcr" という名前での指定は「できる」とは書かれていない気がしてきました。Clangで動くのでヨシ!なのかもしれませんが、どう解釈するべきなんですかね。

GCC

GCC 13までACLEの対応が部分的で、特に __arm_rsr64, __arm_wsr64 には対応していません。代わりに、FPCRやFPSRにアクセスするための組み込み関数を提供しています。

フルの64ビット幅にアクセスできる __builtin_aarch64_{get,set}_fpcr64 が追加されたのはGCC 11で、それ以前は __builtin_aarch64_{get,set}_fpcr でFPCRの下位32ビットしかアクセスできませんでした。__builtin_aarch64_{get,set}_fpcr が実装されたのはGCC 5です。

ちなみに、GCC 10以降では __has_builtin が使えるので、それで __builtin_aarch64_{get,set}_fpcr64 を検出することもできます。

使用例:

__attribute__((always_inline))
uint64_t get_fpcr(void)
{
#if __GNUC__ >= 11
    return __builtin_aarch64_get_fpcr64();
#elif __GNUC__ >= 5
    return (uint64_t)__builtin_aarch64_get_fpcr();
#else
#error unsupported compiler
#endif
}
__attribute__((always_inline))
void set_fpcr(uint64_t x)
{
#if __GNUC__ >= 11
    __builtin_aarch64_set_fpcr64(x);
#elif __GNUC__ >= 5
    __builtin_aarch64_set_fpcr((unsigned long)x);
#else
#error unsupported compiler
#endif
}

インラインアセンブリー

GCCとClangのどちらでも使える方法として、インラインアセンブリーがあります。筆者が __arm_rsr64, __arm_wsr64 の存在を知るまではClang向けにはこれを使っていたので、ここで供養しておきます。

__attribute__((always_inline))
uint64_t get_fpcr(void)
{
    uint64_t fpcr;
    asm volatile("mrs %0, fpcr" : "=r"(fpcr));
    return fpcr;
}
__attribute__((always_inline))
void set_fpcr(uint64_t x)
{
    asm volatile("msr fpcr, %0" : : "r"(x));
}

コンパイラー間で共通する方法が使えないと面倒ですね。なんとかしてくれ。

Discussion