https://github.com/lntue updated https://github.com/llvm/llvm-project/pull/199808
>From dcb57bfcabc21b99b221f4acfec5c31bd6d55370 Mon Sep 17 00:00:00 2001 From: Tue Ly <[email protected]> Date: Wed, 27 May 2026 01:56:13 +0000 Subject: [PATCH 01/11] [clang] Make __builtin_exp and __builtin_expf constexpr. This is step 3 in https://discourse.llvm.org/t/rfc-make-clang-builtin-math-functions-constexpr-with-llvm-libc-to-support-c-23-constexpr-math-functions/86450 --- clang/include/clang/Basic/Builtins.td | 9 ++++++++- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 16 ++++++++++++++++ clang/lib/AST/ExprConstant.cpp | 13 +++++++++++++ clang/test/Preprocessor/feature_tests.cpp | 4 +++- clang/test/Sema/constant-builtins-exp.cpp | 19 +++++++++++++++++++ 5 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 clang/test/Sema/constant-builtins-exp.cpp diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 344a712ddc585..2188e9d8f4d79 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -4218,7 +4218,14 @@ def Erfc : FPMathTemplate, LibBuiltin<"math.h"> { let AddBuiltinPrefixedAlias = 1; } -def Exp : FPMathTemplate, LibBuiltin<"math.h"> { +def Exp : Template<["float", "double"], ["f", ""]>, LibBuiltin<"math.h"> { + let Spellings = ["exp"]; + let Attributes = [NoThrow, ConstIgnoringErrnoAndExceptions, Constexpr]; + let Prototype = "T(T)"; + let AddBuiltinPrefixedAlias = 1; +} + +def Expl : Template<["long double"], ["l"]>, LibBuiltin<"math.h"> { let Spellings = ["exp"]; let Attributes = [NoThrow, ConstIgnoringErrnoAndExceptions]; let Prototype = "T(T)"; diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index a539fb26abc08..499d0e3d6e3d1 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -710,6 +710,14 @@ static bool interp__builtin_fpclassify(InterpState &S, CodePtr OpPC, return true; } +static bool interp__builtin_exp(InterpState &S, CodePtr OpPC, + const InterpFrame *Frame) { + const Floating &Arg = S.Stk.pop<Floating>(); + APFloat Result = exp(Arg.getAPFloat()); + S.Stk.push<Floating>(Floating(Result)); + return true; +} + static inline Floating abs(InterpState &S, const Floating &In) { if (!In.isNegative()) return In; @@ -4742,6 +4750,14 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call, case Builtin::BI__builtin_copysignf128: return interp__builtin_copysign(S, OpPC, Frame); + case Builtin::BI__builtin_exp: + case Builtin::BI__builtin_expf: + return interp__builtin_exp(S, OpPC, Frame); + case Builtin::BI__builtin_expl: + case Builtin::BI__builtin_expf16: + case Builtin::BI__builtin_expf128: + return false; + case Builtin::BI__builtin_fmin: case Builtin::BI__builtin_fminf: case Builtin::BI__builtin_fminl: diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 574dd8b04e779..60f425589dacb 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -20510,6 +20510,19 @@ bool FloatExprEvaluator::VisitCallExpr(const CallExpr *E) { return true; } + case Builtin::BI__builtin_exp: + case Builtin::BI__builtin_expf: { + APFloat Input(0.); + if (!EvaluateFloat(E->getArg(0), Input, Info)) + return false; + Result = exp(Input); + return true; + } + case Builtin::BI__builtin_expl: + case Builtin::BI__builtin_expf16: + case Builtin::BI__builtin_expf128: + return false; + case Builtin::BI__builtin_fmax: case Builtin::BI__builtin_fmaxf: case Builtin::BI__builtin_fmaxl: diff --git a/clang/test/Preprocessor/feature_tests.cpp b/clang/test/Preprocessor/feature_tests.cpp index 029f446113af4..b49376a14644f 100644 --- a/clang/test/Preprocessor/feature_tests.cpp +++ b/clang/test/Preprocessor/feature_tests.cpp @@ -60,7 +60,9 @@ #if !__has_constexpr_builtin(__builtin_fmax) || \ !__has_constexpr_builtin(__builtin_fmin) || \ !__has_constexpr_builtin(__builtin_fmaximum_num) || \ - !__has_constexpr_builtin(__builtin_fminimum_num) + !__has_constexpr_builtin(__builtin_fminimum_num) || \ + !__has_constexpr_builtin(__builtin_exp) || \ + !__has_constexpr_builtin(__builtin_expf) #error Clang should have these constexpr builtins #endif diff --git a/clang/test/Sema/constant-builtins-exp.cpp b/clang/test/Sema/constant-builtins-exp.cpp new file mode 100644 index 0000000000000..215d2c2765961 --- /dev/null +++ b/clang/test/Sema/constant-builtins-exp.cpp @@ -0,0 +1,19 @@ +// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify -fexperimental-new-constant-interpreter %s +// expected-no-diagnostics + +constexpr float InfFloat = __builtin_inff(); +constexpr float NegInfFloat = -__builtin_inff(); + +static_assert(InfFloat == __builtin_expf(InfFloat)); +static_assert(0.0f == __builtin_expf(NegInfFloat)); +static_assert(1.0f == __builtin_expf(0.0f)); +static_assert(0x1.5bf0a8p1f == __builtin_expf(1.0f)); + +constexpr double InfDouble = __builtin_inf(); +constexpr double NegInfDouble = -__builtin_inf(); + +static_assert(InfDouble == __builtin_exp(InfDouble)); +static_assert(0.0 == __builtin_exp(NegInfDouble)); +static_assert(1.0 == __builtin_exp(0.0)); +static_assert(0x1.5bf0a8b145769p1 == __builtin_exp(1.0)); >From 7588cb8f64cd00ec9a76487142011a8f604aef94 Mon Sep 17 00:00:00 2001 From: Tue Ly <[email protected]> Date: Wed, 17 Jun 2026 05:35:55 +0000 Subject: [PATCH 02/11] Use updated version of APFloat::exp to correctly report supported and unsupported cases. --- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 19 +++++++++++++++---- clang/lib/AST/ExprConstant.cpp | 11 ++++++++++- clang/test/Sema/constant-builtins-exp.cpp | 11 ++++++++++- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index 499d0e3d6e3d1..8153858785e8c 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -23,6 +23,7 @@ #include "llvm/Support/AllocToken.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/SipHash.h" +#include <optional> namespace clang { namespace interp { @@ -711,10 +712,20 @@ static bool interp__builtin_fpclassify(InterpState &S, CodePtr OpPC, } static bool interp__builtin_exp(InterpState &S, CodePtr OpPC, - const InterpFrame *Frame) { + const InterpFrame *Frame, + const CallExpr *Call) { const Floating &Arg = S.Stk.pop<Floating>(); - APFloat Result = exp(Arg.getAPFloat()); - S.Stk.push<Floating>(Floating(Result)); + FPOptions FPO = Call->getFPFeaturesInEffect(S.Ctx.getLangOpts()); + llvm::RoundingMode RM = getRoundingMode(FPO); + APFloat::opStatus Status; + std::optional<APFloat> Result = exp(Arg.getAPFloat(), RM, &Status); + // Check for unsupported rounding modes. + if (!Result.has_value()) + return false; + // Check for raised non-FE_INEXACT exceptions. + if (Status & (~APFloat::opStatus::opInexact)) + return false; + S.Stk.push<Floating>(Floating(*Result)); return true; } @@ -4752,7 +4763,7 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call, case Builtin::BI__builtin_exp: case Builtin::BI__builtin_expf: - return interp__builtin_exp(S, OpPC, Frame); + return interp__builtin_exp(S, OpPC, Frame, Call); case Builtin::BI__builtin_expl: case Builtin::BI__builtin_expf16: case Builtin::BI__builtin_expf128: diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 60f425589dacb..def6b775b9952 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -20515,7 +20515,16 @@ bool FloatExprEvaluator::VisitCallExpr(const CallExpr *E) { APFloat Input(0.); if (!EvaluateFloat(E->getArg(0), Input, Info)) return false; - Result = exp(Input); + llvm::RoundingMode RM = getActiveRoundingMode(Info, E); + APFloat::opStatus Status; + std::optional<APFloat> r = exp(Input, RM, &Status); + // Check for unsupported rounding modes. + if (!r.has_value()) + return false; + // Check for raised non-FE_INEXACT exceptions. + if (Status & (~APFloat::opStatus::opInexact)) + return false; + Result = *r; return true; } case Builtin::BI__builtin_expl: diff --git a/clang/test/Sema/constant-builtins-exp.cpp b/clang/test/Sema/constant-builtins-exp.cpp index 215d2c2765961..d1e220dc94d86 100644 --- a/clang/test/Sema/constant-builtins-exp.cpp +++ b/clang/test/Sema/constant-builtins-exp.cpp @@ -1,19 +1,28 @@ // RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify %s // RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify -fexperimental-new-constant-interpreter %s -// expected-no-diagnostics constexpr float InfFloat = __builtin_inff(); constexpr float NegInfFloat = -__builtin_inff(); +constexpr float qNaNFloat = __builtin_nanf(""); +static_assert(__builtin_expf(qNaNFloat) != __builtin_expf(qNaNFloat)); static_assert(InfFloat == __builtin_expf(InfFloat)); static_assert(0.0f == __builtin_expf(NegInfFloat)); static_assert(1.0f == __builtin_expf(0.0f)); static_assert(0x1.5bf0a8p1f == __builtin_expf(1.0f)); +// No constexpr for overflow. +static_assert(InfFloat == __builtin_expf(100.0f)); // expected-error {{static assertion expression is not an integral constant expression}} + constexpr double InfDouble = __builtin_inf(); constexpr double NegInfDouble = -__builtin_inf(); +constexpr double qNaNDouble = __builtin_nan(""); +static_assert(__builtin_exp(qNaNDouble) != __builtin_exp(qNaNDouble)); static_assert(InfDouble == __builtin_exp(InfDouble)); static_assert(0.0 == __builtin_exp(NegInfDouble)); static_assert(1.0 == __builtin_exp(0.0)); static_assert(0x1.5bf0a8b145769p1 == __builtin_exp(1.0)); + +// No constexpr for overflow. +static_assert(InfDouble == __builtin_expf(1000.0)); // expected-error {{static assertion expression is not an integral constant expression}} >From 900951dfe956867b6d7827d0a4ec07ffcdd37404 Mon Sep 17 00:00:00 2001 From: Tue Ly <[email protected]> Date: Thu, 18 Jun 2026 20:28:05 +0000 Subject: [PATCH 03/11] Fix test. --- clang/test/Sema/constant-builtins-exp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/Sema/constant-builtins-exp.cpp b/clang/test/Sema/constant-builtins-exp.cpp index d1e220dc94d86..4d68f04e381a1 100644 --- a/clang/test/Sema/constant-builtins-exp.cpp +++ b/clang/test/Sema/constant-builtins-exp.cpp @@ -25,4 +25,4 @@ static_assert(1.0 == __builtin_exp(0.0)); static_assert(0x1.5bf0a8b145769p1 == __builtin_exp(1.0)); // No constexpr for overflow. -static_assert(InfDouble == __builtin_expf(1000.0)); // expected-error {{static assertion expression is not an integral constant expression}} +static_assert(InfDouble == __builtin_exp(1000.0)); // expected-error {{static assertion expression is not an integral constant expression}} >From 16a2844ab0fc896c93bb6d6fe261de3c626db5d3 Mon Sep 17 00:00:00 2001 From: Tue Ly <[email protected]> Date: Fri, 19 Jun 2026 04:49:06 +0000 Subject: [PATCH 04/11] Try to see whether FE_* exceptions are 0. --- libc/src/__support/math/check/exp_exceptions.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libc/src/__support/math/check/exp_exceptions.h b/libc/src/__support/math/check/exp_exceptions.h index 16b92cf8110ff..19d2fba3e5f4b 100644 --- a/libc/src/__support/math/check/exp_exceptions.h +++ b/libc/src/__support/math/check/exp_exceptions.h @@ -57,6 +57,12 @@ template <> struct Bounds<double> { } // namespace exp_internal template <typename T> LIBC_INLINE int exp_exceptions(T x, int rounding_mode) { + static_assert(FE_OVERFLOW != 0); + static_assert(FE_UNDERFLOW != 0); + static_assert(FE_INEXACT != 0); + static_assert(FE_INVALID != 0); + static_assert(FE_DIVBYZERO != 0); + using FPBits = typename fputil::FPBits<T>; using StorageType = typename FPBits::StorageType; >From b7936d5166a56e0c02c1f66bd19210ca449366b1 Mon Sep 17 00:00:00 2001 From: Tue Ly <[email protected]> Date: Fri, 19 Jun 2026 15:05:57 +0000 Subject: [PATCH 05/11] Allocate a new Floating. --- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index 8153858785e8c..dc313bf37383f 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -725,7 +725,9 @@ static bool interp__builtin_exp(InterpState &S, CodePtr OpPC, // Check for raised non-FE_INEXACT exceptions. if (Status & (~APFloat::opStatus::opInexact)) return false; - S.Stk.push<Floating>(Floating(*Result)); + Floating Res = S.allocFloat(Arg.getSemantics()); + Res.copy(*Result); + S.Stk.push<Floating>(Res); return true; } >From e53cfe371c441326775776cd8f6cd7d37521776e Mon Sep 17 00:00:00 2001 From: Tue Ly <[email protected]> Date: Thu, 25 Jun 2026 06:05:32 +0000 Subject: [PATCH 06/11] Initialize Status. --- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index dc313bf37383f..1fb86dc15d9bc 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -717,7 +717,7 @@ static bool interp__builtin_exp(InterpState &S, CodePtr OpPC, const Floating &Arg = S.Stk.pop<Floating>(); FPOptions FPO = Call->getFPFeaturesInEffect(S.Ctx.getLangOpts()); llvm::RoundingMode RM = getRoundingMode(FPO); - APFloat::opStatus Status; + APFloat::opStatus Status = APFloat::opStatus::opOK; std::optional<APFloat> Result = exp(Arg.getAPFloat(), RM, &Status); // Check for unsupported rounding modes. if (!Result.has_value()) >From 4014d8263999e04f1b5164b84305ba3050d881ec Mon Sep 17 00:00:00 2001 From: Tue Ly <[email protected]> Date: Thu, 25 Jun 2026 06:07:48 +0000 Subject: [PATCH 07/11] Revert libc changes. --- libc/src/__support/math/check/exp_exceptions.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/libc/src/__support/math/check/exp_exceptions.h b/libc/src/__support/math/check/exp_exceptions.h index 19d2fba3e5f4b..16b92cf8110ff 100644 --- a/libc/src/__support/math/check/exp_exceptions.h +++ b/libc/src/__support/math/check/exp_exceptions.h @@ -57,12 +57,6 @@ template <> struct Bounds<double> { } // namespace exp_internal template <typename T> LIBC_INLINE int exp_exceptions(T x, int rounding_mode) { - static_assert(FE_OVERFLOW != 0); - static_assert(FE_UNDERFLOW != 0); - static_assert(FE_INEXACT != 0); - static_assert(FE_INVALID != 0); - static_assert(FE_DIVBYZERO != 0); - using FPBits = typename fputil::FPBits<T>; using StorageType = typename FPBits::StorageType; >From 2ab459254f809f47cd50c651e5a33a9a02437267 Mon Sep 17 00:00:00 2001 From: Tue Ly <[email protected]> Date: Thu, 25 Jun 2026 11:47:33 +0000 Subject: [PATCH 08/11] Initialize Status. --- clang/lib/AST/ExprConstant.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index def6b775b9952..daf62851a73d1 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -20516,7 +20516,7 @@ bool FloatExprEvaluator::VisitCallExpr(const CallExpr *E) { if (!EvaluateFloat(E->getArg(0), Input, Info)) return false; llvm::RoundingMode RM = getActiveRoundingMode(Info, E); - APFloat::opStatus Status; + APFloat::opStatus Status = APFloat::opStatus::opOK; std::optional<APFloat> r = exp(Input, RM, &Status); // Check for unsupported rounding modes. if (!r.has_value()) >From 5cc225f5d8cfb97f85d753be304ebbf9ed9335f0 Mon Sep 17 00:00:00 2001 From: Tue Ly <[email protected]> Date: Fri, 17 Jul 2026 04:20:33 +0000 Subject: [PATCH 09/11] Remove LLVM_READONLY attribute from APFloat::exp. --- llvm/include/llvm/ADT/APFloat.h | 1 - 1 file changed, 1 deletion(-) diff --git a/llvm/include/llvm/ADT/APFloat.h b/llvm/include/llvm/ADT/APFloat.h index ce94e0bacbeca..b81e14760fed5 100644 --- a/llvm/include/llvm/ADT/APFloat.h +++ b/llvm/include/llvm/ADT/APFloat.h @@ -1804,7 +1804,6 @@ inline APFloat maximumnum(const APFloat &A, const APFloat &B) { } /// Implement IEEE 754-2019 exp functions -LLVM_READONLY LLVM_ABI std::optional<APFloat> exp(const APFloat &X, RoundingMode RM = APFloat::rmNearestTiesToEven, APFloat::opStatus *Status = nullptr); >From d0bdcbefc0480146eb0594318c155fccf40e8ac2 Mon Sep 17 00:00:00 2001 From: Tue Ly <[email protected]> Date: Sun, 19 Jul 2026 16:59:35 +0000 Subject: [PATCH 10/11] Let __builtin_exp(f) respect FENV_ACCESS and FPE strict, also update checkFloatingPointResults to match expectations. --- clang/lib/AST/ByteCode/Interp.cpp | 69 ++++++++++++++--------- clang/lib/AST/ByteCode/Interp.h | 8 +++ clang/lib/AST/ByteCode/InterpBuiltin.cpp | 8 ++- clang/lib/AST/ExprConstant.cpp | 44 +++++++++------ clang/test/CodeGen/pragma-fenv_access.c | 33 +++++++++++ clang/test/Sema/constant-builtins-exp.cpp | 25 +++++++- 6 files changed, 140 insertions(+), 47 deletions(-) diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index f59485ec306e4..348d65e7eb2f8 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -1188,51 +1188,66 @@ bool CheckThis(InterpState &S, CodePtr OpPC) { return false; } -bool CheckFloatResult(InterpState &S, CodePtr OpPC, const Floating &Result, - APFloat::opStatus Status, FPOptions FPO) { - // [expr.pre]p4: - // If during the evaluation of an expression, the result is not - // mathematically defined [...], the behavior is undefined. - // FIXME: C++ rules require us to not conform to IEEE 754 here. - if (Result.isNan()) { - const SourceInfo &E = S.Current->getSource(OpPC); - S.CCEDiag(E, diag::note_constexpr_float_arithmetic) - << /*NaN=*/true << S.Current->getRange(OpPC); - return S.noteUndefinedBehavior(); - } - +bool CheckFloatStatus(InterpState &S, CodePtr OpPC, APFloat::opStatus Status, + FPOptions FPO) { // In a constant context, assume that any dynamic rounding mode or FP // exception state matches the default floating-point environment. if (S.inConstantContext()) return true; - if ((Status & APFloat::opInexact) && - FPO.getRoundingMode() == llvm::RoundingMode::Dynamic) { - // Inexact result means that it depends on rounding mode. If the requested - // mode is dynamic, the evaluation cannot be made in compile time. + // The output result is exact and no exceptions are raised. + if (Status == APFloat::opOK) + return true; + + // No fenv access and floating point exceptions are ignored, so it is safe to + // to perform compile-time evaluation. + if (!FPO.getAllowFEnvAccess() && + FPO.getExceptionMode() == LangOptions::FPE_Ignore) + return true; + + if (FPO.getAllowFEnvAccess() && + (FPO.getRoundingMode() == llvm::RoundingMode::Dynamic)) { + // Some floating point exception is raised, so the output might depend on + // rounding mode. If the FENV_ACCESS is "on" and the rounding mode is + // dynamic, the evaluation cannot be made in compile time. const SourceInfo &E = S.Current->getSource(OpPC); S.FFDiag(E, diag::note_constexpr_dynamic_rounding); return false; } - if ((Status != APFloat::opOK) && - (FPO.getRoundingMode() == llvm::RoundingMode::Dynamic || - FPO.getExceptionMode() != LangOptions::FPE_Ignore || - FPO.getAllowFEnvAccess())) { + if (FPO.getExceptionMode() != LangOptions::FPE_Ignore) { + // Some floating point exception is raised and the FP mode is strict or the + // exceptions may be trapped. const SourceInfo &E = S.Current->getSource(OpPC); S.FFDiag(E, diag::note_constexpr_float_arithmetic_strict); return false; } - if ((Status & APFloat::opStatus::opInvalidOp) && - FPO.getExceptionMode() != LangOptions::FPE_Ignore) { + // FIXME: if: + // - evaluation triggered other FP exception, and + // - exception mode is not "ignore", and + // - the expression being evaluated is not a part of global variable + // initializer, + // the evaluation probably need to be rejected. + const SourceInfo &E = S.Current->getSource(OpPC); + S.FFDiag(E, diag::note_constexpr_float_arithmetic_strict); + return false; +} + +bool CheckFloatResult(InterpState &S, CodePtr OpPC, const Floating &Result, + APFloat::opStatus Status, FPOptions FPO) { + // [expr.pre]p4: + // If during the evaluation of an expression, the result is not + // mathematically defined [...], the behavior is undefined. + // FIXME: C++ rules require us to not conform to IEEE 754 here. + if (Result.isNan()) { const SourceInfo &E = S.Current->getSource(OpPC); - // There is no usefully definable result. - S.FFDiag(E); - return false; + S.CCEDiag(E, diag::note_constexpr_float_arithmetic) + << /*NaN=*/true << S.Current->getRange(OpPC); + return S.noteUndefinedBehavior(); } - return true; + return CheckFloatStatus(S, OpPC, Status, FPO); } bool CheckDynamicMemoryAllocation(InterpState &S, CodePtr OpPC) { diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h index 405f4a29ec982..138a7a811e4b6 100644 --- a/clang/lib/AST/ByteCode/Interp.h +++ b/clang/lib/AST/ByteCode/Interp.h @@ -246,9 +246,17 @@ bool CheckDivRem(InterpState &S, CodePtr OpPC, const T &LHS, const T &RHS) { /// Checks if the result of a floating-point operation is valid /// in the current context. +/// Notes: +/// - CheckFloatStatus is the same as checkFloatingPointResult in +/// clang/lib/AST/ExprConstant.cpp. +/// - CheckFloatResult will also check if the result is NaN, in addition to +/// CheckFloatStatus's checks. bool CheckFloatResult(InterpState &S, CodePtr OpPC, const Floating &Result, APFloat::opStatus Status, FPOptions FPO); +bool CheckFloatStatus(InterpState &S, CodePtr OpPC, APFloat::opStatus Status, + FPOptions FPO); + /// Checks why the given DeclRefExpr is invalid. bool CheckDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR); bool InvalidDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR, diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index 1fb86dc15d9bc..9aa1e600fc6b4 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -9,6 +9,7 @@ #include "Boolean.h" #include "Char.h" #include "EvalEmitter.h" +#include "Interp.h" #include "InterpBuiltinBitCast.h" #include "InterpHelpers.h" #include "PrimType.h" @@ -716,7 +717,9 @@ static bool interp__builtin_exp(InterpState &S, CodePtr OpPC, const CallExpr *Call) { const Floating &Arg = S.Stk.pop<Floating>(); FPOptions FPO = Call->getFPFeaturesInEffect(S.Ctx.getLangOpts()); - llvm::RoundingMode RM = getRoundingMode(FPO); + llvm::RoundingMode RM = llvm::RoundingMode::NearestTiesToEven; + if (S.inConstantContext()) + RM = getRoundingMode(FPO); APFloat::opStatus Status = APFloat::opStatus::opOK; std::optional<APFloat> Result = exp(Arg.getAPFloat(), RM, &Status); // Check for unsupported rounding modes. @@ -727,6 +730,9 @@ static bool interp__builtin_exp(InterpState &S, CodePtr OpPC, return false; Floating Res = S.allocFloat(Arg.getSemantics()); Res.copy(*Result); + // Add diagnostic when not in constant evaluation context. + if (!CheckFloatStatus(S, OpPC, Status, FPO)) + return false; S.Stk.push<Floating>(Res); return true; } diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index daf62851a73d1..b209aa273998c 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -2707,37 +2707,43 @@ static bool checkFloatingPointResult(EvalInfo &Info, const Expr *E, if (Info.InConstantContext) return true; + // The output result is exact and no exceptions are raised, so it is safe to + // perform compile-time evaluation. + if (St == APFloat::opOK) + return true; + FPOptions FPO = E->getFPFeaturesInEffect(Info.getLangOpts()); - if ((St & APFloat::opInexact) && - FPO.getRoundingMode() == llvm::RoundingMode::Dynamic) { - // Inexact result means that it depends on rounding mode. If the requested - // mode is dynamic, the evaluation cannot be made in compile time. + + // No fenv access and floating point exceptions are ignored, so it is safe to + // to perform compile-time evaluation. + if (!FPO.getAllowFEnvAccess() && + FPO.getExceptionMode() == LangOptions::FPE_Ignore) + return true; + + if (FPO.getAllowFEnvAccess() && + (FPO.getRoundingMode() == llvm::RoundingMode::Dynamic)) { + // Some floating point exception is raised, so the result might depend on + // rounding mode. If the FENV_ACCESS is "on" and the rounding mode is + // dynamic, the evaluation cannot be made in compile time. Info.FFDiag(E, diag::note_constexpr_dynamic_rounding); return false; } - if ((St != APFloat::opOK) && - (FPO.getRoundingMode() == llvm::RoundingMode::Dynamic || - FPO.getExceptionMode() != LangOptions::FPE_Ignore || - FPO.getAllowFEnvAccess())) { + if (FPO.getExceptionMode() != LangOptions::FPE_Ignore) { + // Some floating point exception is raised and the FP mode is strict or the + // exceptions may be trapped. Info.FFDiag(E, diag::note_constexpr_float_arithmetic_strict); return false; } - if ((St & APFloat::opStatus::opInvalidOp) && - FPO.getExceptionMode() != LangOptions::FPE_Ignore) { - // There is no usefully definable result. - Info.FFDiag(E); - return false; - } - // FIXME: if: // - evaluation triggered other FP exception, and // - exception mode is not "ignore", and // - the expression being evaluated is not a part of global variable // initializer, // the evaluation probably need to be rejected. - return true; + Info.FFDiag(E, diag::note_constexpr_float_arithmetic_strict); + return false; } static bool HandleFloatToFloatCast(EvalInfo &Info, const Expr *E, @@ -20515,7 +20521,9 @@ bool FloatExprEvaluator::VisitCallExpr(const CallExpr *E) { APFloat Input(0.); if (!EvaluateFloat(E->getArg(0), Input, Info)) return false; - llvm::RoundingMode RM = getActiveRoundingMode(Info, E); + llvm::RoundingMode RM = llvm::RoundingMode::NearestTiesToEven; + if (Info.InConstantContext) + RM = getActiveRoundingMode(Info, E); APFloat::opStatus Status = APFloat::opStatus::opOK; std::optional<APFloat> r = exp(Input, RM, &Status); // Check for unsupported rounding modes. @@ -20524,6 +20532,8 @@ bool FloatExprEvaluator::VisitCallExpr(const CallExpr *E) { // Check for raised non-FE_INEXACT exceptions. if (Status & (~APFloat::opStatus::opInexact)) return false; + if (!checkFloatingPointResult(Info, E, Status)) + return false; Result = *r; return true; } diff --git a/clang/test/CodeGen/pragma-fenv_access.c b/clang/test/CodeGen/pragma-fenv_access.c index 76c38f957d632..9763ac926de82 100644 --- a/clang/test/CodeGen/pragma-fenv_access.c +++ b/clang/test/CodeGen/pragma-fenv_access.c @@ -287,3 +287,36 @@ vector3ulong func_23(vector3float x) { } // CHECK-LABEL: @func_23 // STRICT: call <3 x i64> @llvm.experimental.constrained.fptoui.v3i64.v3f32(<3 x float> {{.*}}, metadata !"fpexcept.ignore") + +float func_26() { + #pragma STDC FENV_ACCESS ON + return __builtin_expf(1.0F); +} +// CHECK-LABEL: @func_26 +// CHECK: call float @llvm.experimental.constrained.exp.f32(float 1.000000e+00, metadata !"round.dynamic", metadata !"fpexcept.strict") + + +float func_27() { + #pragma STDC FENV_ACCESS ON + return __builtin_expf(0.0F); +} +// CHECK-LABEL: @func_27 +// CHECK: ret float 1.000000e+00 + + +float func_28() { + #pragma STDC FENV_ACCESS ON + return __builtin_expf(__builtin_inff()); +} +// CHECK-LABEL: @func_28 +// CHECK: ret float +inf + + +float func_29() { + #pragma STDC FENV_ACCESS ON + return __builtin_expf(-__builtin_inff()); +} +// CHECK-LABEL: @func_29 +// CHECK: ret float 0.000000e+00 + + diff --git a/clang/test/Sema/constant-builtins-exp.cpp b/clang/test/Sema/constant-builtins-exp.cpp index 4d68f04e381a1..11120696f2615 100644 --- a/clang/test/Sema/constant-builtins-exp.cpp +++ b/clang/test/Sema/constant-builtins-exp.cpp @@ -1,5 +1,5 @@ -// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify %s -// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify -fexperimental-new-constant-interpreter %s +// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify -Wno-unknown-pragmas %s +// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify -fexperimental-new-constant-interpreter -Wno-unknown-pragmas %s constexpr float InfFloat = __builtin_inff(); constexpr float NegInfFloat = -__builtin_inff(); @@ -26,3 +26,24 @@ static_assert(0x1.5bf0a8b145769p1 == __builtin_exp(1.0)); // No constexpr for overflow. static_assert(InfDouble == __builtin_exp(1000.0)); // expected-error {{static assertion expression is not an integral constant expression}} + +// No constexpr for underflow. +static_assert(0.0f == __builtin_expf(-100.0f)); // expected-error {{static assertion expression is not an integral constant expression}} +static_assert(0.0 == __builtin_exp(-1000.0)); // expected-error {{static assertion expression is not an integral constant expression}} + +void test_rounding_modes() { + { + #pragma STDC FENV_ROUND FE_DOWNWARD + static_assert(InfFloat == __builtin_expf(100.0f)); // expected-error {{static assertion expression is not an integral constant expression}} + static_assert(InfDouble == __builtin_exp(1000.0)); // expected-error {{static assertion expression is not an integral constant expression}} + static_assert(0.0f == __builtin_expf(-100.0f)); // expected-error {{static assertion expression is not an integral constant expression}} + static_assert(0.0 == __builtin_exp(-1000.0)); // expected-error {{static assertion expression is not an integral constant expression}} + } + { + #pragma STDC FENV_ROUND FE_TOWARDZERO + static_assert(InfFloat == __builtin_expf(100.0f)); // expected-error {{static assertion expression is not an integral constant expression}} + static_assert(InfDouble == __builtin_exp(1000.0)); // expected-error {{static assertion expression is not an integral constant expression}} + static_assert(0.0f == __builtin_expf(-100.0f)); // expected-error {{static assertion expression is not an integral constant expression}} + static_assert(0.0 == __builtin_exp(-1000.0)); // expected-error {{static assertion expression is not an integral constant expression}} + } +} >From 8a111d574bff431de678a308a76dc6a37c50f449 Mon Sep 17 00:00:00 2001 From: Tue Ly <[email protected]> Date: Sun, 19 Jul 2026 19:21:57 +0000 Subject: [PATCH 11/11] Correct the condition for -frounding-math without FENV_ACCESS. --- clang/lib/AST/ByteCode/Interp.cpp | 19 +++++++++---------- clang/lib/AST/ExprConstant.cpp | 17 ++++++++--------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index 348d65e7eb2f8..96a4bbe589147 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -1199,22 +1199,21 @@ bool CheckFloatStatus(InterpState &S, CodePtr OpPC, APFloat::opStatus Status, if (Status == APFloat::opOK) return true; - // No fenv access and floating point exceptions are ignored, so it is safe to - // to perform compile-time evaluation. - if (!FPO.getAllowFEnvAccess() && - FPO.getExceptionMode() == LangOptions::FPE_Ignore) - return true; - - if (FPO.getAllowFEnvAccess() && - (FPO.getRoundingMode() == llvm::RoundingMode::Dynamic)) { + if (FPO.getRoundingMode() == llvm::RoundingMode::Dynamic) { // Some floating point exception is raised, so the output might depend on - // rounding mode. If the FENV_ACCESS is "on" and the rounding mode is - // dynamic, the evaluation cannot be made in compile time. + // rounding mode. If the requested mode is dynamic, the evaluation cannot be + // made in compile time. const SourceInfo &E = S.Current->getSource(OpPC); S.FFDiag(E, diag::note_constexpr_dynamic_rounding); return false; } + // No fenv access and floating point exceptions are ignored, so it is safe to + // to perform compile-time evaluation. + if (!FPO.getAllowFEnvAccess() && + FPO.getExceptionMode() == LangOptions::FPE_Ignore) + return true; + if (FPO.getExceptionMode() != LangOptions::FPE_Ignore) { // Some floating point exception is raised and the FP mode is strict or the // exceptions may be trapped. diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index b209aa273998c..0d6c904c2fd92 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -2714,21 +2714,20 @@ static bool checkFloatingPointResult(EvalInfo &Info, const Expr *E, FPOptions FPO = E->getFPFeaturesInEffect(Info.getLangOpts()); + if (FPO.getRoundingMode() == llvm::RoundingMode::Dynamic) { + // Some floating point exception is raised, so the result might depend on + // rounding mode. If the requested mode is dynamic, the evaluation cannot + // be made in compile time. + Info.FFDiag(E, diag::note_constexpr_dynamic_rounding); + return false; + } + // No fenv access and floating point exceptions are ignored, so it is safe to // to perform compile-time evaluation. if (!FPO.getAllowFEnvAccess() && FPO.getExceptionMode() == LangOptions::FPE_Ignore) return true; - if (FPO.getAllowFEnvAccess() && - (FPO.getRoundingMode() == llvm::RoundingMode::Dynamic)) { - // Some floating point exception is raised, so the result might depend on - // rounding mode. If the FENV_ACCESS is "on" and the rounding mode is - // dynamic, the evaluation cannot be made in compile time. - Info.FFDiag(E, diag::note_constexpr_dynamic_rounding); - return false; - } - if (FPO.getExceptionMode() != LangOptions::FPE_Ignore) { // Some floating point exception is raised and the FP mode is strict or the // exceptions may be trapped. _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
