c++floating-pointclang

#pragma STDC FENV_ACCESS


I want to use SVE instructions to round FP32 to FP16. ARM docs say that the SVE instructions will round according to the current rounding mode. So, I need to set the rounding mode, since I don't know what code ran before mine.

Edit: to clarify, I just want the default mode, RTNE.

The C++ docs for fenv say:

The floating-point environment access and modification is only meaningful when #pragma STDC FENV_ACCESS is supported and is set to ON.

But, if I look at another page on cppreference:

The ISO C++ language standard does not require the compilers to support any pragmas.

OK, so I need use a pragma, but the standard doesn't require compilers to support any pragmas. Surely, at least clang supports it, right?

error: ignoring ‘#pragma STDC FENV_ACCESS’ [-Werror=unknown-pragmas]

Also, cppreference says that if I suppress that error and enable the FENV_ACCESS pragma, it will make all of my code slower:

optimizations that could subvert flag tests and mode changes (e.g., global common subexpression elimination, code motion, and constant folding) are prohibited.

On the other hand, I need to modify the floating-point environment. I need my rounding to work correctly.

What am I supposed to do here? Currently, I am just not setting the pragma and hoping for the best, but what is the correct solution?


Solution

  • Peculiar indeed. If you really have to set rounding mode, the only truly supported option seems to be moving all the floating point math to a C library that makes sure to reset the rounding mode after itself.

    First, what you have found is true - C++ standard intends this functionality to be exactly as in C, but at the same time explicitly says they do not require support of #pragma FENV_ACCESS in the compiler. Everything is left implementation-defined. See this point from standard:

    The contents and meaning of the header are the same as the C standard library header <fenv.h>. [Note 1: This document does not require an implementation to support the FENV_ACCESS pragma; it is implementation-defined ([cpp.pragma]) whether the pragma is supported. As a consequence, it is implementation-defined whether these functions can be used to test floating-point status flags, set floating-point control modes, or run under non-default mode settings. If the pragma is used to enable control over the floating-point environment, this document does not specify the effect on floating-point evaluation in constant expressions. — end note]

    What you can do (I think) is check the default round style with std::numeric_limits<double>::round_style. Pretty much the best you can do in standard C++ is check what your compiler returns for given architecture (hoping that it's not std::round_indeterminate), add assert to verify it didn't change with compiler upgrade

    static_assert(std::numeric_limits<double>::round_style == std::round_to_nearest);
    

    and hope it never fires.

    clang does not state support for C++ explicitly, but given it only mentions C when talking about these pragmas, I think it's safe bet that it's only supported in C. From clang 19.1.0 documentation:

    [...] C and C++ restrict access to the floating point environment by default, and the compiler is allowed to assume that all operations are performed in the default environment. [...] C provides two pragmas to allow code to dynamically modify the floating point environment:

    • #pragma STDC FENV_ACCESS ON allows dynamic changes to the entire floating point environment.
    • #pragma STDC FENV_ROUND FE_DYNAMIC allows dynamic changes to just the floating point rounding mode. This may be more optimizable than FENV_ACCESS ON because the compiler can still ignore the possibility of floating-point exceptions by default.

    [...] See the C standard for more information about these pragmas.

    There are compiler flags that control floating point math behaviour, including simulating these pragmas, but this is one giant booby trap filled with Undefined Behaviour (to be fair, pragmas are as well). You should read all of the documentation for floating point options before tampering with them. Doc for clang 19.1.0 can be found here.


    One more thing about what you wrote:

    So, I need to set the rounding mode, since I don't know what code ran before mine.

    If it's a different program, it shouldn't matter. If within the same program one calls a function with different pragma/flag set, you are already in Undefined Behaviour land. So hopefully all the libraries you use clean up after themselves if they ever try to modify it.