1. Introduction
Code generation for floating-point is usually controlled by compiler flags. In [Clang] this can be achieved by invoking the compiler with
,
or via other target specific mechanisms,
for instance, compiling code for the [Darwin] kernel disables float by
default.
This is desirable because:
-
Some hardware doesn’t support floating-point at all, requiring "soft-float" emulation of floating-point using integer instructions. This is inefficient and bloats the final binary and developers often want to opt-out of it.
-
Some code wants to avoid saving and restoring floating-point registers, for example kernel syscalls don’t want to trample over user-mode floating-point registers. Saving and restoring registers can get expensive: even with a conservative calling convention modern AVX512-enabled x86 CPUs have
registers of 64 bytes each (in the worst case 32 such registers must be saved and restored).ZMM
This approach has many drawbacks:
-
As is often the case with compiler flags, without changes to the Standard, each vendor ends up with a different mode, harming portability.
-
Different pieces of a project might want different floating-point code generation; relying on extra compiler flags for specific translation units in a project is brittle and also has maintenance and portability costs. For instance, in an application one might want to opt out from floating-point only for specific pieces (e.g. cryptography code and kernel level
-like utilities still want to use floating-point)printf -
Readability expectations: while maintaining and creating new code, the user has to look into the build system setup to find out whether the code in question can be translated to floating-point code. This might affect assumptions about rounding mode, auto vectorization and other implementation defined floating-point behavior.
2. Proposed Solution
We propose a new function level attribute
. Attaching
to
a function means:
-
Floating-point types cannot be used directly in the function.
-
The function’s parameters and return types cannot be floating-point types.
-
Other functions that use floating-point types can be called from one marked with the
attribute as long as the callee’s parameters and return type aren’t floating-point.no_float -
No floating-point instructions should be emitted implicitly for the function. It’s implementation defined what should happen instead, implementers might make it equivalent to what [GCC] and [Clang] do for
(disable auto-vectorization, inlined- msoft - float
cannot use SIMD registers, etc).memcpy
Alternatively for 3., we could make implementation defined what happens for such cross-calls, the compiler could in theory codegen two versions of the function, one with and another without and call the right one).
This attribute standardizes a combination of existing ad-hoc practices and documents code’s expectation: functions shouldn’t inadvertently use floating-point. Code fails to compile if the function uses floating-point, and the compiler knows to further avoid implicitly add floating-point register / instruction uses in the function.
Examples:
double h ( double y ) { return y + 0.3 ; } double i ( double y = 42. ) { return y * 42. ; } double j () { return 0.2 ; } [[ no_float ]] void g () { j (); // ERROR: j()'s return type is floating-point. h ( a ); // ERROR: h()'s parameter type is floating-point. i (); // ERROR: i()'s parameter type is floating-point. }
We also propose adding module-level attributes for this, see [p1245r0].