[libcxx-commits] [clang] [libcxx] [Clang] Allow __builtin_fma/fmaf/fmal to be used in a constant expression (PR #158048)
Joshua Cranmer via libcxx-commits
libcxx-commits at lists.llvm.org
Fri Sep 19 12:21:53 PDT 2025
jcranmer-intel wrote:
The topic is a little difficult to broach to the C/C++ committees because it's an interaction of aspects of each language that the other committee doesn't want to talk about for the most part, so you end up having to interpolate general design guidelines to figure out what to do.
> If the rounding mode is dynamic (and `FENV_ACCESS` is "on"), it is only acceptable to fold cases that are not manifestly constant-evaluated if the result is exact.
The basic rule in C is that all floating-point operations "as if" at execution time unless they can't be done at execution time. Interpolating that to C++ semantics, then all floating-point operations are "as if" at execution time unless manifestly constant-evaluated.
Frontend-level constant folding of not manifestly constant-evaluated expressions can happen only if happening at translation time is not observable, meaning:
- If `FENV_ACCESS` is off and `FENV_ROUND` is missing, we are in the fully default FP environments. FP exceptions are not visible, no funky control bits are set, and the rounding mode is default. It is safe to do the translation at compile-time.
- If `FENV_ACCESS` is off and `FENV_ROUND` is present, then we are in the fully default FP environment except that the rounding mode isn't necessarily in the default rounding mode. We can do the translation at compile-time, even if `FENV_ROUND` is `FE_DYNAMIC` (since the translator is explicitly allowed to assume that it's in the default round per the C specification).
- If `FENV_ACCESS` is on and `FENV_ROUND` is missing, we are in an arbitrary FP environment. FP exceptions are visible, funky control bits may be set, and the rounding mode may be in any state. It is not safe to do the translation at compile-time, since we cannot guarantee that a weird control bit (like x87 precision control) is not in play.
- If `FENV_ACCESS` is on and `FENV_ROUND` is present, then we at least know the rounding mode. But since we still can't assume the absence of funky control bits, it's not safe to do the translation at compile-time.
Because of the potential for funky control bits, I lean towards the stance of prohibiting non-mandatory constant evaluation of floating-point expressions when `FENV_ACCESS` is on, even if the expression would be exact. But `FENV_ACCESS` being off lets us exercise the as-if rule pretty freely.
> `FENV_ROUND` is specified, by the C standard, to affect the behaviour of `fma` by means of macro replacement: a call that avoids the macro replacement, e.g., `(fma)(x, y, z)` is supposed to operate using the dynamic rounding mode.
The C standard doesn't discuss the behavior of `__builtin_fma`, so we have to interpolate what its behavior should be from the more general rules of `FENV_ROUND`. The way I read the intent is that `FENV_ROUND` applies the behavior of the rounding mode to all known operations and restores the dynamic rounding mode for all unknown function calls, with standard library calls being considered to be a known operation when they are not macro-replaced.
> As for manifestly constant-evaluated contexts, we do expect rounding to occur; however, whether the default rounding mode or the "constant rounding mode" should be used is a matter of design that should be backed by documentation. The documentation actively suggests that the default rounding mode is used: https://releases.llvm.org/21.1.0/tools/clang/docs/UsersManual.html#cmdoption-f-no-rounding-math.
That documentation appears to have been written in 2019 and not updated since then, which is before `FENV_ROUND` was incorporated into C2x, long before any work was done in Clang to support `FENV_ROUND`. Especially as Clang's `FENV_ROUND` implementation is still incomplete enough that we don't claim to support it yet, I wouldn't take it as any definitive evidence of a decision having been made.
To my mind, the most defensible behavior for `__builtin_fma` (and future similar builtins) is this: they have the same behavior with respect to constant expression evaluation, rounding mode, and strict FP mode (i.e., `FENV_ACCESS`) as a humble `+` operator. That is, in the cases where they are manifestly constant-evaluated, they take on the rounding mode implied by `FENV_ROUND`.
https://github.com/llvm/llvm-project/pull/158048
More information about the libcxx-commits
mailing list