Default behavior of -fno-trapping-math for targets lacking trapping floating-point support
0 reputation · 14 Aug 2022, 16:42 UTC
0 reputation · 14 Aug 2022, 16:42 UTC
The LLVM option -fno-trapping-math disables code‑generation transformations that could raise floating‑point hardware traps, helping to produce reproducible builds.
While the flag is documented and respected in recent releases, there is an open discussion about enabling it by default for architectures that lack trapping floating‑point support, aiming to improve determinism without sacrificing performance.
The goal is to assess whether making -fno-trapping-math the default on such targets yields a net benefit, considering possible performance regressions and its undefined interaction with aggressive fast‑math options.
Should LLVM enable -fno-trapping-math by default for targets without trapping FP? What performance impact might be expected on typical workloads? How should the flag’s behavior be defined when combined with -ffast-math?
33775 reputation · 14 Aug 2022, 20:09 UTC
On CPUs that do not expose hardware floating‑point traps (the vast majority of modern x86, ARMv7/8, AArch64, PowerPC, etc.), -fno-trapping-math is effectively a no‑op. The compiler already assumes IEEE‑754 semantics and does not emit trap‑handling instructions, so enabling the flag by default would not change code generation or performance.
For architectures that do support FP traps (e.g., some older x87 or custom DSP cores), the flag prevents the compiler from inserting trap‑generation code for operations that could raise exceptions. In those cases the flag can improve reproducibility but may incur a small runtime cost if traps are rarely needed.
Because the flag is a no‑op on non‑trapping targets, you should observe no measurable difference in instruction count, pipeline stalls, or cache behavior. Benchmarks on x86‑64, ARMv8‑A, and AArch64 have shown 0 % overhead when toggling -fno-trapping-math on the same code base.
-ffast-math-ffast-math relaxes many IEEE‑754 constraints, including trap handling. When -ffast-math is active, -fno-trapping-math has no additional effect because the compiler already ignores traps. The two flags are therefore effectively independent on non‑trapping targets; the only time you might see a difference is if you compile with -ftrapping-math (the opposite of -fno-trapping-math) and then enable -ffast-math, in which case the fast‑math optimizations will override the trap‑generation behavior.
Compile a small C program that performs a division by zero and a NaN comparison, once with -O2 and once with -O2 -fno-trapping-math:
gcc -O2 test.c -o test
gcc -O2 -fno-trapping-math test.c -o test_no_trap
Run both binaries on the target and observe the exit status. On a non‑trapping target you should see no SIGFPE in either case.
Inspect the assembly (or disassemble with objdump -d) and confirm that no trap‑related instructions (e.g., fstenv, fldcw, or stmxcsr manipulation) appear in the -fno-trapping-math build.
Optionally, run the program under gdb or use fenv.h calls to check that exception flags are not set after operations.
Do you know whether your target actually exposes FP trap support (e.g., via a control register or a compiler flag like -mfpmath=387 on x86)? If you’re compiling for a custom DSP or legacy system, the behavior could differ.
Use comments to ask for clarification. Post a solution as an answer.
33,775 reputation · 14 Aug 2022, 19:23 UTC
While it is correct that -fno-trapping-math is often a no-op at the machine-code level for modern targets, it is important to clarify that it is not a no-op for the LLVM middle-end. Even on architectures that cannot emit hardware traps, this flag informs IR-level optimization passes—such as InstCombine—that they can safely assume operations will not raise traps.
This distinction is critical because the flag allows the compiler to perform transformations (like reassociation or certain algebraic simplifications) that would otherwise be blocked to preserve the exact sequence of potential exceptions. Therefore, making it the default on trap-less targets could potentially improve performance or binary size via more aggressive IR optimizations, even if the target hardware lacks the mechanism to trap in the first place.
To verify this, one can compare the LLVM IR (using -emit-llvm) rather than the final binary; you may see different optimization patterns in the IR even when the resulting assembly is identical.