[clang] [libc] [llvm] [clang] Make __builtin_exp and __builtin_expf constexpr. (PR #199808)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Sep 13 09:43:24 PDT 2026
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 <lntue.h at gmail.com>
Date: Wed, 27 May 2026 01:56:13 +0000
Subject: [PATCH 01/16] [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 344a712ddc585c..2188e9d8f4d796 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 a539fb26abc08d..499d0e3d6e3d13 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 574dd8b04e7794..60f425589dacb4 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 029f446113af45..b49376a14644fa 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 00000000000000..215d2c27659613
--- /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 <lntue.h at gmail.com>
Date: Wed, 17 Jun 2026 05:35:55 +0000
Subject: [PATCH 02/16] 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 499d0e3d6e3d13..8153858785e8c4 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 60f425589dacb4..def6b775b99529 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 215d2c27659613..d1e220dc94d865 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 <lntue.h at gmail.com>
Date: Thu, 18 Jun 2026 20:28:05 +0000
Subject: [PATCH 03/16] 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 d1e220dc94d865..4d68f04e381a19 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 <lntue.h at gmail.com>
Date: Fri, 19 Jun 2026 04:49:06 +0000
Subject: [PATCH 04/16] 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 16b92cf8110ff6..19d2fba3e5f4bf 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 <lntue.h at gmail.com>
Date: Fri, 19 Jun 2026 15:05:57 +0000
Subject: [PATCH 05/16] 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 8153858785e8c4..dc313bf37383f4 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 <lntue.h at gmail.com>
Date: Thu, 25 Jun 2026 06:05:32 +0000
Subject: [PATCH 06/16] 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 dc313bf37383f4..1fb86dc15d9bcf 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 <lntue.h at gmail.com>
Date: Thu, 25 Jun 2026 06:07:48 +0000
Subject: [PATCH 07/16] 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 19d2fba3e5f4bf..16b92cf8110ff6 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 <lntue.h at gmail.com>
Date: Thu, 25 Jun 2026 11:47:33 +0000
Subject: [PATCH 08/16] 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 def6b775b99529..daf62851a73d1b 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 <lntue.h at gmail.com>
Date: Fri, 17 Jul 2026 04:20:33 +0000
Subject: [PATCH 09/16] 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 ce94e0bacbeca8..b81e14760fed55 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 <lntue.h at gmail.com>
Date: Sun, 19 Jul 2026 16:59:35 +0000
Subject: [PATCH 10/16] 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 f59485ec306e4c..348d65e7eb2f85 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 405f4a29ec9827..138a7a811e4b62 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 1fb86dc15d9bcf..9aa1e600fc6b42 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 daf62851a73d1b..b209aa273998ce 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 76c38f957d632f..9763ac926de82a 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 4d68f04e381a19..11120696f26157 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 <lntue.h at gmail.com>
Date: Sun, 19 Jul 2026 19:21:57 +0000
Subject: [PATCH 11/16] 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 348d65e7eb2f85..96a4bbe589147f 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 b209aa273998ce..0d6c904c2fd920 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.
>From e8c3af9a1630756c2863bcad2718f4b8a9aed5ad Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue.h at gmail.com>
Date: Sun, 13 Sep 2026 03:21:46 +0000
Subject: [PATCH 12/16] Address comments:
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Dynamic rounding mode and exception handling:
- Only suppress constant folding in dynamic rounding mode when the result
is inexact (`Status & opInexact`), allowing exact values (e.g. division
by zero to infinity) to fold.
- Address the FIXME in `checkFloatingPointResultForConstantFolding` and
`CheckFloatStatus`: permit static and global variable initializers to fold
at translation time under default FP settings (ISO C17 ยง7.6.1p2).
- `-fmath-errno` handling:
- Move `-fmath-errno` checking out of general floating-point result checking
and into math builtin evaluators, ensuring core arithmetic operators
are unaffected.
- Suppress runtime constant folding for math builtins only when domain,
pole, or range errors would set `errno` at runtime.
- Add `note_constexpr_math_errno` diagnostic note.
- Diagnostics & Rounding:
- Refactor math builtin evaluation to use `getActiveRoundingMode`,
removing hardcoded `NearestTiesToEven`.
- Update diagnostic notes to standard-aligned phrasing:
`note_constexpr_float_{overflow,underflow,divide_by_zero,invalid_op}`.
- Tests:
- Add `clang/test/CodeGen/builtin-exp-folding.c` to test runtime folding
and suppression under `-fmath-errno`, `-fno-math-errno`, `-frounding-math`,
and `-ffp-exception-behavior=strict`.
- In `clang/test/Sema/constant-builtins-exp.cpp`:
- Add underflow tests for normal inputs yielding subnormals (`-88.0f`, `-709.0`).
- Test quiet NaN with `__builtin_isnan`.
- Add tests and FIXMEs for directed rounding modes (`FE_DOWNWARD`,
`FE_UPWARD`, `FE_TOWARDZERO`).
---
.../include/clang/Basic/DiagnosticASTKinds.td | 16 +-
clang/lib/AST/ByteCode/Interp.cpp | 42 +++--
clang/lib/AST/ByteCode/InterpBuiltin.cpp | 52 +++++-
clang/lib/AST/ExprConstant.cpp | 117 ++++++++----
clang/test/CodeGen/builtin-exp-folding.c | 173 ++++++++++++++++++
clang/test/Sema/constant-builtins-exp.cpp | 102 ++++++++++-
.../SemaCXX/constexpr-x86-avx-builtins.cpp | 2 +-
.../constexpr-x86-avx512f-builtins.cpp | 6 +-
.../constexpr-x86-avx512vl-builtins.cpp | 2 +-
.../SemaCXX/constexpr-x86-sse2-builtins.cpp | 8 +-
10 files changed, 444 insertions(+), 76 deletions(-)
create mode 100644 clang/test/CodeGen/builtin-exp-folding.c
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index 0aca1f75428f8a..9be5866422ddc5 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -89,10 +89,22 @@ def note_constexpr_array_index : Note<"cannot refer to element %0 of "
"in a constant expression">;
def note_constexpr_float_arithmetic : Note<
"floating point arithmetic produces %select{an infinity|a NaN}0">;
+def note_constexpr_float_underflow : Note<
+ "call to %0 raises a floating-point underflow exception">;
+def note_constexpr_float_overflow : Note<
+ "call to %0 raises a floating-point overflow exception">;
+def note_constexpr_float_divide_by_zero : Note<
+ "call to %0 raises a floating-point divide-by-zero exception">;
+def note_constexpr_float_invalid_op : Note<
+ "call to %0 raises an invalid floating-point operation exception">;
+def note_constexpr_unsupported_rounding : Note<
+ "cannot evaluate call to %0 in '%1' rounding mode">;
def note_constexpr_dynamic_rounding : Note<
- "cannot evaluate this expression if rounding mode is dynamic">;
+ "cannot evaluate this expression in dynamic rounding mode">;
def note_constexpr_float_arithmetic_strict : Note<
- "compile time floating point arithmetic suppressed in strict evaluation modes">;
+ "compile-time floating-point evaluation suppressed in strict evaluation modes">;
+def note_constexpr_math_errno : Note<
+ "call to %0 sets 'errno' at runtime">;
def note_constexpr_pointer_subtraction_not_same_array : Note<
"subtracted pointers are not elements of the same array">;
def note_constexpr_pointer_subtraction_zero_size : Note<
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 2d0ce641406e1d..d47a7f32acb0d8 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -1246,35 +1246,41 @@ bool CheckFloatStatus(InterpState &S, CodePtr OpPC, APFloat::opStatus Status,
if (Status == APFloat::opOK)
return true;
- if (FPO.getRoundingMode() == llvm::RoundingMode::Dynamic) {
- // Some floating point exception is raised, so the output might depend on
- // rounding mode. If the requested mode is dynamic, the evaluation cannot be
- // made in compile time.
+ // If the result is inexact, it depends on the rounding mode. If the requested
+ // mode is dynamic, compile-time evaluation cannot be performed.
+ // Floating-point exceptions other than "inexact" (e.g. exact infinities from
+ // division by zero) do not depend on the rounding mode and are not suppressed
+ // here.
+ if ((Status & APFloat::opStatus::opInexact) &&
+ FPO.getRoundingMode() == llvm::RoundingMode::Dynamic) {
const SourceInfo &E = S.Current->getSource(OpPC);
S.FFDiag(E, diag::note_constexpr_dynamic_rounding);
return false;
}
+ // Note: We do not check MathErrno here. This function is called for all
+ // floating-point operations, including basic arithmetic operators
+ // (+, -, *, /) and casts, which never set errno. In addition, -fmath-errno
+ // is enabled by default on POSIX systems, so checking MathErrno here would
+ // incorrectly prevent constant folding of basic arithmetic expressions.
+ // Setting errno is specific to C standard library math functions (and varies
+ // by function: range errors, pole errors, domain errors), so MathErrno
+ // checks are handled by the math builtin evaluators directly.
+
// No fenv access and floating point exceptions are ignored, so it is safe to
- // to perform compile-time evaluation.
+ // 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.
- const SourceInfo &E = S.Current->getSource(OpPC);
- S.FFDiag(E, diag::note_constexpr_float_arithmetic_strict);
- return false;
- }
+ // Initializers for objects with static or thread storage duration (such as
+ // global variables) are evaluated at compile time during translation using
+ // default floating-point settings (C17 7.6.1p2).
+ if (S.EvaluatingDecl && S.EvaluatingDecl->hasGlobalStorage())
+ return true;
- // 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.
+ // Some floating point exception is raised and the FP mode is strict,
+ // exceptions may be trapped, or FENV access is enabled.
const SourceInfo &E = S.Current->getSource(OpPC);
S.FFDiag(E, diag::note_constexpr_float_arithmetic_strict);
return false;
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index d23fa81ff2b27e..2f420d99725e29 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -759,22 +759,54 @@ 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 = llvm::RoundingMode::NearestTiesToEven;
- if (S.inConstantContext())
- RM = getRoundingMode(FPO);
+ llvm::RoundingMode RM = getRoundingMode(FPO);
APFloat::opStatus Status = APFloat::opStatus::opOK;
std::optional<APFloat> Result = exp(Arg.getAPFloat(), RM, &Status);
- // Check for unsupported rounding modes.
- if (!Result.has_value())
+ const SourceInfo &E = S.Current->getSource(OpPC);
+
+ if (!Result.has_value()) {
+ if (S.inConstantContext())
+ S.FFDiag(E, diag::note_constexpr_unsupported_rounding)
+ << Call->getDirectCallee() << llvm::spell(RM);
+ return false;
+ }
+
+ if (S.inConstantContext()) {
+ // [library.c]p3: A call to a math function is not a core constant
+ // expression if an exception other than FE_INEXACT is raised.
+ if (Status & (~APFloat::opStatus::opInexact)) {
+ const FunctionDecl *FD = Call->getDirectCallee();
+ if (Status & APFloat::opStatus::opUnderflow)
+ S.FFDiag(E, diag::note_constexpr_float_underflow) << FD;
+ else if (Status & APFloat::opStatus::opOverflow)
+ S.FFDiag(E, diag::note_constexpr_float_overflow) << FD;
+ else if (Status & APFloat::opStatus::opDivByZero)
+ S.FFDiag(E, diag::note_constexpr_float_divide_by_zero) << FD;
+ else
+ S.FFDiag(E, diag::note_constexpr_float_invalid_op) << FD;
+ return false;
+ }
+ Floating Res = S.allocFloat(Arg.getSemantics());
+ Res.copy(*Result);
+ S.Stk.push<Floating>(Res);
+ return true;
+ }
+
+ // Under -fmath-errno, any error condition that sets errno at runtime
+ // (domain error EDOM via opInvalidOp, pole error ERANGE via opDivByZero,
+ // or range error ERANGE via opOverflow/opUnderflow) cannot be folded.
+ // This check is performed here rather than in CheckFloatStatus because errno
+ // is specific to math functions (and not set by core language operators).
+ if (S.getLangOpts().MathErrno && (Status & (~APFloat::opStatus::opInexact))) {
+ S.FFDiag(E, diag::note_constexpr_math_errno) << Call->getDirectCallee();
return false;
- // Check for raised non-FE_INEXACT exceptions.
- if (Status & (~APFloat::opStatus::opInexact))
+ }
+
+ if (!CheckFloatStatus(S, OpPC, Status, FPO))
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 446d960eda1b63..45535dd8a14719 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -2725,33 +2725,44 @@ static bool checkFloatingPointResultForConstantFolding(EvalInfo &Info,
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.
+ // If the result is inexact, it depends on the rounding mode. If the requested
+ // mode is dynamic, compile-time evaluation cannot be performed.
+ // Floating-point exceptions other than "inexact" (e.g. exact infinities from
+ // division by zero) do not depend on the rounding mode and are not suppressed
+ // here.
+ if ((St & APFloat::opStatus::opInexact) &&
+ FPO.getRoundingMode() == llvm::RoundingMode::Dynamic) {
Info.FFDiag(E, diag::note_constexpr_dynamic_rounding);
return false;
}
+ // Note: We do not check MathErrno here. This function is called for all
+ // floating-point operations, including basic arithmetic operators
+ // (+, -, *, /) and casts, which never set errno. In addition, -fmath-errno
+ // is enabled by default on POSIX systems, so checking MathErrno here would
+ // incorrectly prevent constant folding of basic arithmetic expressions.
+ // Setting errno is specific to C standard library math functions (and varies
+ // by function: range errors, pole errors, domain errors), so MathErrno
+ // checks are handled by the math builtin evaluators directly.
+
// No fenv access and floating point exceptions are ignored, so it is safe to
- // to perform compile-time evaluation.
+ // 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.
- Info.FFDiag(E, diag::note_constexpr_float_arithmetic_strict);
- return false;
+ // Initializers for objects with static or thread storage duration (such as
+ // global variables) are evaluated at compile time during translation using
+ // default floating-point settings (C17 7.6.1p2).
+ if (const auto *VD = Info.EvaluatingDecl.dyn_cast<const ValueDecl *>()) {
+ if (const auto *Var = dyn_cast<VarDecl>(VD)) {
+ if (Var->hasGlobalStorage())
+ return true;
+ }
}
- // 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.
+ // Some floating point exception is raised and the FP mode is strict,
+ // exceptions may be trapped, or FENV access is enabled.
Info.FFDiag(E, diag::note_constexpr_float_arithmetic_strict);
return false;
}
@@ -20538,6 +20549,60 @@ static bool TryEvaluateBuiltinNaN(const ASTContext &Context,
return true;
}
+/// Evaluate a unary math function (such as exp/expf) for compile-time constant
+/// evaluation or opportunistic constant folding.
+template <typename MathFn>
+static bool evaluateUnaryMathBuiltin(EvalInfo &Info, const CallExpr *E,
+ const APFloat &Input, APFloat &Result,
+ MathFn &&Func) {
+ llvm::RoundingMode RM = getActiveRoundingMode(Info, E);
+ APFloat::opStatus Status = APFloat::opStatus::opOK;
+ std::optional<APFloat> r = Func(Input, RM, &Status);
+ if (!r.has_value()) {
+ if (Info.InConstantContext)
+ Info.FFDiag(E, diag::note_constexpr_unsupported_rounding)
+ << E->getDirectCallee() << llvm::spell(RM);
+ return false;
+ }
+
+ if (Info.InConstantContext) {
+ // [library.c]p3: A call to a math function is not a core constant
+ // expression if an exception other than FE_INEXACT is raised.
+ if (Status & (~APFloat::opStatus::opInexact)) {
+ const FunctionDecl *FD = E->getDirectCallee();
+ if (Status & APFloat::opStatus::opUnderflow)
+ Info.FFDiag(E, diag::note_constexpr_float_underflow) << FD;
+ else if (Status & APFloat::opStatus::opOverflow)
+ Info.FFDiag(E, diag::note_constexpr_float_overflow) << FD;
+ else if (Status & APFloat::opStatus::opDivByZero)
+ Info.FFDiag(E, diag::note_constexpr_float_divide_by_zero) << FD;
+ else
+ Info.FFDiag(E, diag::note_constexpr_float_invalid_op) << FD;
+ return false;
+ }
+ Result = *r;
+ return true;
+ }
+
+ // Under -fmath-errno, any error condition that sets errno at runtime
+ // (domain error EDOM via opInvalidOp, pole error ERANGE via opDivByZero,
+ // or range error ERANGE via opOverflow/opUnderflow) cannot be folded.
+ // This check is performed here rather than in
+ // checkFloatingPointResultForConstantFolding because errno is specific to
+ // math functions (and not set by core language operators).
+ if (Info.getLangOpts().MathErrno &&
+ (Status & (~APFloat::opStatus::opInexact))) {
+ Info.FFDiag(E, diag::note_constexpr_math_errno) << E->getDirectCallee();
+ return false;
+ }
+
+ if (!checkFloatingPointResultForConstantFolding(Info, E, Status))
+ return false;
+
+ Result = *r;
+ return true;
+}
+
bool FloatExprEvaluator::VisitCallExpr(const CallExpr *E) {
if (!IsConstantEvaluatedBuiltinCall(E))
return ExprEvaluatorBaseTy::VisitCallExpr(E);
@@ -20627,21 +20692,11 @@ bool FloatExprEvaluator::VisitCallExpr(const CallExpr *E) {
APFloat Input(0.);
if (!EvaluateFloat(E->getArg(0), Input, Info))
return false;
- 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.
- if (!r.has_value())
- return false;
- // 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;
+ return evaluateUnaryMathBuiltin(
+ Info, E, Input, Result,
+ [](const APFloat &X, llvm::RoundingMode RM, APFloat::opStatus *Status) {
+ return exp(X, RM, Status);
+ });
}
case Builtin::BI__builtin_expl:
case Builtin::BI__builtin_expf16:
diff --git a/clang/test/CodeGen/builtin-exp-folding.c b/clang/test/CodeGen/builtin-exp-folding.c
new file mode 100644
index 00000000000000..419418a8e76631
--- /dev/null
+++ b/clang/test/CodeGen/builtin-exp-folding.c
@@ -0,0 +1,173 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -w -o - -fmath-errno %s | FileCheck %s --check-prefix=CHECK-ERRNO
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -w -o - -fno-math-errno %s | FileCheck %s --check-prefix=CHECK-NO-ERRNO
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -w -o - -frounding-math %s | FileCheck %s --check-prefix=CHECK-ROUNDING
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -w -o - -ffp-exception-behavior=strict %s | FileCheck %s --check-prefix=CHECK-STRICT
+
+// An exact or inexact exp that doesn't set errno folds even with -fmath-errno:
+float test_expf_1() {
+ return __builtin_expf(1.0f);
+}
+// CHECK-ERRNO-LABEL: @test_expf_1
+// CHECK-ERRNO: ret float 0x3FF5BF0A80000000
+// CHECK-NO-ERRNO-LABEL: @test_expf_1
+// CHECK-NO-ERRNO: ret float 0x3FF5BF0A80000000
+// CHECK-ROUNDING-LABEL: @test_expf_1
+// CHECK-ROUNDING: call float @llvm.experimental.constrained.exp.f32(float 1.000000e+00, metadata !"round.dynamic", metadata !"fpexcept.ignore")
+
+// Exact exp(0.0) folds even with -frounding-math and strict exception mode:
+float test_expf_zero() {
+ return __builtin_expf(0.0f);
+}
+// CHECK-ERRNO-LABEL: @test_expf_zero
+// CHECK-ERRNO: ret float 1.000000e+00
+// CHECK-NO-ERRNO-LABEL: @test_expf_zero
+// CHECK-NO-ERRNO: ret float 1.000000e+00
+// CHECK-ROUNDING-LABEL: @test_expf_zero
+// CHECK-ROUNDING: ret float 1.000000e+00
+// CHECK-STRICT-LABEL: @test_expf_zero
+// CHECK-STRICT: ret float 1.000000e+00
+
+// Exact infinities do not set errno, so they fold even under -fmath-errno:
+float test_expf_pos_inf() {
+ return __builtin_expf(__builtin_inff());
+}
+// CHECK-ERRNO-LABEL: @test_expf_pos_inf
+// CHECK-ERRNO: ret float +inf
+// CHECK-NO-ERRNO-LABEL: @test_expf_pos_inf
+// CHECK-NO-ERRNO: ret float +inf
+
+float test_expf_neg_inf() {
+ return __builtin_expf(-__builtin_inff());
+}
+// CHECK-ERRNO-LABEL: @test_expf_neg_inf
+// CHECK-ERRNO: ret float 0.000000e+00
+// CHECK-NO-ERRNO-LABEL: @test_expf_neg_inf
+// CHECK-NO-ERRNO: ret float 0.000000e+00
+
+// Quiet NaN does not set errno or raise exceptions, so it folds to NaN:
+float test_expf_nan() {
+ return __builtin_expf(__builtin_nanf(""));
+}
+// CHECK-ERRNO-LABEL: @test_expf_nan
+// CHECK-ERRNO: ret float 0x7FF8000000000000
+// CHECK-NO-ERRNO-LABEL: @test_expf_nan
+// CHECK-NO-ERRNO: ret float 0x7FF8000000000000
+
+
+// Inexact exp raises FE_INEXACT, so it cannot fold under strict exception mode:
+float test_expf_strict_inexact() {
+ return __builtin_expf(1.0f);
+}
+// CHECK-STRICT-LABEL: @test_expf_strict_inexact
+// CHECK-STRICT: call float @llvm.experimental.constrained.exp.f32(float 1.000000e+00, metadata !"round.tonearest", metadata !"fpexcept.strict")
+
+// Overflow sets errno under -fmath-errno, so it cannot fold:
+float test_expf_overflow() {
+ return __builtin_expf(100.0f);
+}
+// CHECK-ERRNO-LABEL: @test_expf_overflow
+// CHECK-ERRNO: call float @expf(float noundef 1.000000e+02)
+// CHECK-NO-ERRNO-LABEL: @test_expf_overflow
+// CHECK-NO-ERRNO: ret float +inf
+// CHECK-STRICT-LABEL: @test_expf_overflow
+// CHECK-STRICT: call float @llvm.experimental.constrained.exp.f32(float 1.000000e+02, metadata !"round.tonearest", metadata !"fpexcept.strict")
+
+// Underflow to zero sets errno under -fmath-errno, so it cannot fold:
+float test_expf_underflow() {
+ return __builtin_expf(-100.0f);
+}
+// CHECK-ERRNO-LABEL: @test_expf_underflow
+// CHECK-ERRNO: call float @expf(float noundef -1.000000e+02)
+// CHECK-NO-ERRNO-LABEL: @test_expf_underflow
+// CHECK-NO-ERRNO: ret float 0.000000e+00
+// CHECK-STRICT-LABEL: @test_expf_underflow
+// CHECK-STRICT: call float @llvm.experimental.constrained.exp.f32(float -1.000000e+02, metadata !"round.tonearest", metadata !"fpexcept.strict")
+
+// Underflow to denormal sets errno under -fmath-errno, so it cannot fold:
+float test_expf_denormal() {
+ return __builtin_expf(-88.0f);
+}
+// CHECK-ERRNO-LABEL: @test_expf_denormal
+// CHECK-ERRNO: call float @expf(float noundef -8.800000e+01)
+// CHECK-NO-ERRNO-LABEL: @test_expf_denormal
+// CHECK-NO-ERRNO: ret float 0x37F6DC7000000000
+// CHECK-STRICT-LABEL: @test_expf_denormal
+// CHECK-STRICT: call float @llvm.experimental.constrained.exp.f32(float -8.800000e+01, metadata !"round.tonearest", metadata !"fpexcept.strict")
+
+double test_exp_overflow() {
+ return __builtin_exp(1000.0);
+}
+// CHECK-ERRNO-LABEL: @test_exp_overflow
+// CHECK-ERRNO: call double @exp(double noundef 1.000000e+03)
+// CHECK-NO-ERRNO-LABEL: @test_exp_overflow
+// CHECK-NO-ERRNO: ret double +inf
+// CHECK-STRICT-LABEL: @test_exp_overflow
+// CHECK-STRICT: call double @llvm.experimental.constrained.exp.f64(double 1.000000e+03, metadata !"round.tonearest", metadata !"fpexcept.strict")
+
+double test_exp_underflow() {
+ return __builtin_exp(-1000.0);
+}
+// CHECK-ERRNO-LABEL: @test_exp_underflow
+// CHECK-ERRNO: call double @exp(double noundef -1.000000e+03)
+// CHECK-NO-ERRNO-LABEL: @test_exp_underflow
+// CHECK-NO-ERRNO: ret double 0.000000e+00
+// CHECK-STRICT-LABEL: @test_exp_underflow
+// CHECK-STRICT: call double @llvm.experimental.constrained.exp.f64(double -1.000000e+03, metadata !"round.tonearest", metadata !"fpexcept.strict")
+
+// Constant rounding mode is ignored for runtime calls; with -frounding-math, dynamic rounding mode prevents folding.
+// FIXME: Once FE_DOWNWARD is supported in APFloat::exp, this should constant-fold to:
+// ret double 0x4005BF0A8B145769
+double test_exp_rounding_math() {
+ #pragma STDC FENV_ROUND FE_DOWNWARD
+ return __builtin_exp(1.0);
+}
+// CHECK-ROUNDING-LABEL: @test_exp_rounding_math
+// CHECK-ROUNDING: call double @llvm.experimental.constrained.exp.f64(double 1.000000e+00, metadata !"round.dynamic", metadata !"fpexcept.ignore")
+
+// Overriding -frounding-math with static tonearest rounding pragma allows folding:
+float test_rounding_math_override() {
+ #pragma STDC FENV_ROUND FE_TONEAREST
+ return __builtin_expf(1.0f);
+}
+// CHECK-ROUNDING-LABEL: @test_rounding_math_override
+// CHECK-ROUNDING: ret float 0x3FF5BF0A80000000
+
+// Local dynamic rounding mode pragma prevents inexact folding without -frounding-math:
+float test_pragma_fenv_round_dynamic() {
+ #pragma STDC FENV_ROUND FE_DYNAMIC
+ return __builtin_expf(1.0f);
+}
+// CHECK-ERRNO-LABEL: @test_pragma_fenv_round_dynamic
+// CHECK-ERRNO: call float @llvm.experimental.constrained.exp.f32(float 1.000000e+00, metadata !"round.dynamic", metadata !"fpexcept.ignore")
+
+// Local dynamic rounding mode pragma still allows exact folding:
+float test_pragma_fenv_round_dynamic_exact() {
+ #pragma STDC FENV_ROUND FE_DYNAMIC
+ return __builtin_expf(0.0f);
+}
+// CHECK-ERRNO-LABEL: @test_pragma_fenv_round_dynamic_exact
+// CHECK-ERRNO: ret float 1.000000e+00
+
+// Pragma STDC FENV_ACCESS ON prevents folding of inexact calls:
+float test_fenv_access_inexact() {
+ #pragma STDC FENV_ACCESS ON
+ return __builtin_expf(1.0f);
+}
+// CHECK-ERRNO-LABEL: @test_fenv_access_inexact
+// CHECK-ERRNO: call float @llvm.experimental.constrained.exp.f32(float 1.000000e+00, metadata !"round.dynamic", metadata !"fpexcept.strict")
+// CHECK-NO-ERRNO-LABEL: @test_fenv_access_inexact
+// CHECK-NO-ERRNO: call float @llvm.experimental.constrained.exp.f32(float 1.000000e+00, metadata !"round.dynamic", metadata !"fpexcept.strict")
+
+// Pragma STDC FENV_ACCESS ON still permits folding of exact calls:
+float test_fenv_access_exact() {
+ #pragma STDC FENV_ACCESS ON
+ return __builtin_expf(0.0f);
+}
+// CHECK-ERRNO-LABEL: @test_fenv_access_exact
+// CHECK-ERRNO: ret float 1.000000e+00
+// CHECK-NO-ERRNO-LABEL: @test_fenv_access_exact
+// CHECK-NO-ERRNO: ret float 1.000000e+00
+
+// Global variable initializers are evaluated at compile time using default FP settings (C17 7.6.1p2):
+float global_exp_inexact = __builtin_expf(1.0f);
+// CHECK-STRICT: @global_exp_inexact = {{.*}}global float 0x3FF5BF0A80000000
diff --git a/clang/test/Sema/constant-builtins-exp.cpp b/clang/test/Sema/constant-builtins-exp.cpp
index 11120696f26157..685bfce8960c25 100644
--- a/clang/test/Sema/constant-builtins-exp.cpp
+++ b/clang/test/Sema/constant-builtins-exp.cpp
@@ -1,49 +1,139 @@
// 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
+// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify -Wno-unknown-pragmas -fmath-errno %s
+// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify -Wno-unknown-pragmas -fno-math-errno %s
+// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify -Wno-unknown-pragmas -frounding-math %s
+// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify -fexperimental-new-constant-interpreter -Wno-unknown-pragmas -frounding-math %s
constexpr float InfFloat = __builtin_inff();
constexpr float NegInfFloat = -__builtin_inff();
constexpr float qNaNFloat = __builtin_nanf("");
+constexpr float DenormFloat = 0x1.0p-140f;
+constexpr float NegDenormFloat = -0x1.0p-140f;
-static_assert(__builtin_expf(qNaNFloat) != __builtin_expf(qNaNFloat));
+static_assert(__builtin_isnan(__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}}
+static_assert(1.0f == __builtin_expf(DenormFloat));
+static_assert(1.0f == __builtin_expf(NegDenormFloat));
constexpr double InfDouble = __builtin_inf();
constexpr double NegInfDouble = -__builtin_inf();
constexpr double qNaNDouble = __builtin_nan("");
+constexpr double DenormDouble = 0x1.0p-1050;
+constexpr double NegDenormDouble = -0x1.0p-1050;
-static_assert(__builtin_exp(qNaNDouble) != __builtin_exp(qNaNDouble));
+static_assert(__builtin_isnan(__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));
+static_assert(1.0 == __builtin_exp(DenormDouble));
+static_assert(1.0 == __builtin_exp(NegDenormDouble));
// No constexpr for overflow.
+static_assert(InfFloat == __builtin_expf(100.0f)); // expected-error {{static assertion expression is not an integral constant expression}}
+// expected-note at -1 {{call to '__builtin_expf' raises a floating-point overflow exception}}
static_assert(InfDouble == __builtin_exp(1000.0)); // expected-error {{static assertion expression is not an integral constant expression}}
+// expected-note at -1 {{call to '__builtin_exp' raises a floating-point overflow exception}}
+
+// No constexpr for underflow: normal input yielding denormal output.
+static_assert(0.0f == __builtin_expf(-88.0f)); // expected-error {{static assertion expression is not an integral constant expression}}
+// expected-note at -1 {{call to '__builtin_expf' raises a floating-point underflow exception}}
+static_assert(0.0 == __builtin_exp(-709.0)); // expected-error {{static assertion expression is not an integral constant expression}}
+// expected-note at -1 {{call to '__builtin_exp' raises a floating-point underflow exception}}
-// No constexpr for underflow.
+// No constexpr for underflow: output rounding to zero.
static_assert(0.0f == __builtin_expf(-100.0f)); // expected-error {{static assertion expression is not an integral constant expression}}
+// expected-note at -1 {{call to '__builtin_expf' raises a floating-point underflow exception}}
static_assert(0.0 == __builtin_exp(-1000.0)); // expected-error {{static assertion expression is not an integral constant expression}}
+// expected-note at -1 {{call to '__builtin_exp' raises a floating-point underflow exception}}
+
+// No constexpr for signaling NaN (invalid operation).
+constexpr float sNaNFloat = __builtin_nansf("");
+constexpr double sNaNDouble = __builtin_nans("");
+static_assert(__builtin_isnan(__builtin_expf(sNaNFloat))); // expected-error {{static assertion expression is not an integral constant expression}}
+// expected-note at -1 {{call to '__builtin_expf' raises an invalid floating-point operation exception}}
+static_assert(__builtin_isnan(__builtin_exp(sNaNDouble))); // expected-error {{static assertion expression is not an integral constant expression}}
+// expected-note at -1 {{call to '__builtin_exp' raises an invalid floating-point operation exception}}
+
void test_rounding_modes() {
+ // Currently, llvm::APFloat::exp only supports NearestTiesToEven rounding mode.
+ // Other rounding modes fail evaluation with note_constexpr_unsupported_rounding.
+ // Once directed rounding modes are supported in APFloat::exp:
+ // - Inexact evaluations without overflow/underflow (e.g., expf(1.0f)) should become
+ // valid constant expressions with the appropriately rounded value.
+ // - Overflow and underflow cases should still fail constant expression evaluation, but
+ // emit note_constexpr_float_overflow or note_constexpr_float_underflow instead
+ // of note_constexpr_unsupported_rounding.
{
#pragma STDC FENV_ROUND FE_DOWNWARD
+ // FIXME: Once FE_DOWNWARD is supported in APFloat::exp, this should fold to:
+ // static_assert(0x1.5bf0a8p1f == __builtin_expf(1.0f));
+ static_assert(1.0f == __builtin_expf(1.0f)); // expected-error {{static assertion expression is not an integral constant expression}}
+ // expected-note at -1 {{cannot evaluate call to '__builtin_expf' in 'downward' rounding mode}}
+ // FIXME: Once supported, these should fail with note_constexpr_float_overflow / underflow:
static_assert(InfFloat == __builtin_expf(100.0f)); // expected-error {{static assertion expression is not an integral constant expression}}
+ // expected-note at -1 {{cannot evaluate call to '__builtin_expf' in 'downward' rounding mode}}
static_assert(InfDouble == __builtin_exp(1000.0)); // expected-error {{static assertion expression is not an integral constant expression}}
+ // expected-note at -1 {{cannot evaluate call to '__builtin_exp' in 'downward' rounding mode}}
static_assert(0.0f == __builtin_expf(-100.0f)); // expected-error {{static assertion expression is not an integral constant expression}}
+ // expected-note at -1 {{cannot evaluate call to '__builtin_expf' in 'downward' rounding mode}}
static_assert(0.0 == __builtin_exp(-1000.0)); // expected-error {{static assertion expression is not an integral constant expression}}
+ // expected-note at -1 {{cannot evaluate call to '__builtin_exp' in 'downward' rounding mode}}
}
{
#pragma STDC FENV_ROUND FE_TOWARDZERO
+ // FIXME: Once FE_TOWARDZERO is supported in APFloat::exp, this should fold to:
+ // static_assert(0x1.5bf0a8p1f == __builtin_expf(1.0f));
+ static_assert(1.0f == __builtin_expf(1.0f)); // expected-error {{static assertion expression is not an integral constant expression}}
+ // expected-note at -1 {{cannot evaluate call to '__builtin_expf' in 'towardzero' rounding mode}}
+ // FIXME: Once supported, these should fail with note_constexpr_float_overflow / underflow:
static_assert(InfFloat == __builtin_expf(100.0f)); // expected-error {{static assertion expression is not an integral constant expression}}
+ // expected-note at -1 {{cannot evaluate call to '__builtin_expf' in 'towardzero' rounding mode}}
static_assert(InfDouble == __builtin_exp(1000.0)); // expected-error {{static assertion expression is not an integral constant expression}}
+ // expected-note at -1 {{cannot evaluate call to '__builtin_exp' in 'towardzero' rounding mode}}
static_assert(0.0f == __builtin_expf(-100.0f)); // expected-error {{static assertion expression is not an integral constant expression}}
+ // expected-note at -1 {{cannot evaluate call to '__builtin_expf' in 'towardzero' rounding mode}}
static_assert(0.0 == __builtin_exp(-1000.0)); // expected-error {{static assertion expression is not an integral constant expression}}
+ // expected-note at -1 {{cannot evaluate call to '__builtin_exp' in 'towardzero' rounding mode}}
}
+ {
+ #pragma STDC FENV_ROUND FE_UPWARD
+ // FIXME: Once FE_UPWARD is supported in APFloat::exp, this should fold to:
+ // static_assert(0x1.5bf0aap1f == __builtin_expf(1.0f));
+ static_assert(1.0f == __builtin_expf(1.0f)); // expected-error {{static assertion expression is not an integral constant expression}}
+ // expected-note at -1 {{cannot evaluate call to '__builtin_expf' in 'upward' rounding mode}}
+ // FIXME: Once supported, these should fail with note_constexpr_float_overflow / underflow:
+ static_assert(InfFloat == __builtin_expf(100.0f)); // expected-error {{static assertion expression is not an integral constant expression}}
+ // expected-note at -1 {{cannot evaluate call to '__builtin_expf' in 'upward' rounding mode}}
+ static_assert(InfDouble == __builtin_exp(1000.0)); // expected-error {{static assertion expression is not an integral constant expression}}
+ // expected-note at -1 {{cannot evaluate call to '__builtin_exp' in 'upward' rounding mode}}
+ static_assert(0.0f == __builtin_expf(-100.0f)); // expected-error {{static assertion expression is not an integral constant expression}}
+ // expected-note at -1 {{cannot evaluate call to '__builtin_expf' in 'upward' rounding mode}}
+ static_assert(0.0 == __builtin_exp(-1000.0)); // expected-error {{static assertion expression is not an integral constant expression}}
+ // expected-note at -1 {{cannot evaluate call to '__builtin_exp' in 'upward' rounding mode}}
+ }
+ {
+ #pragma STDC FENV_ROUND FE_TONEAREST
+ static_assert(1.0f == __builtin_expf(0.0f));
+ static_assert(0x1.5bf0a8p1f == __builtin_expf(1.0f));
+ static_assert(1.0 == __builtin_exp(0.0));
+ static_assert(0x1.5bf0a8b145769p1 == __builtin_exp(1.0));
+ }
+}
+
+void test_fenv_access() {
+ #pragma STDC FENV_ACCESS ON
+ static_assert(1.0f == __builtin_expf(0.0f));
+ static_assert(0x1.5bf0a8p1f == __builtin_expf(1.0f));
+ static_assert(1.0 == __builtin_exp(0.0));
+ static_assert(0x1.5bf0a8b145769p1 == __builtin_exp(1.0));
+ static_assert(InfFloat == __builtin_expf(100.0f)); // expected-error {{static assertion expression is not an integral constant expression}}
+ // expected-note at -1 {{call to '__builtin_expf' raises a floating-point overflow exception}}
+ static_assert(0.0f == __builtin_expf(-100.0f)); // expected-error {{static assertion expression is not an integral constant expression}}
+ // expected-note at -1 {{call to '__builtin_expf' raises a floating-point underflow exception}}
}
diff --git a/clang/test/SemaCXX/constexpr-x86-avx-builtins.cpp b/clang/test/SemaCXX/constexpr-x86-avx-builtins.cpp
index 1eedf6072ecdb6..52371da4894b17 100644
--- a/clang/test/SemaCXX/constexpr-x86-avx-builtins.cpp
+++ b/clang/test/SemaCXX/constexpr-x86-avx-builtins.cpp
@@ -12,7 +12,7 @@ namespace Inexact {
constexpr __m256d a = { 1.0000000000000002, 0.0, 0.0, 0.0 };
constexpr __m128 r = _mm256_cvtpd_ps(a);
// expected-error at -1 {{must be initialized by a constant expression}}
-// expected-note at avxintrin.h:* {{compile time floating point arithmetic suppressed in strict evaluation modes}}
+// expected-note at avxintrin.h:* {{compile-time floating-point evaluation suppressed in strict evaluation modes}}
// expected-note at -3 {{in call to '_mm256_cvtpd_ps({1.000000e+00, 0.000000e+00, 0.000000e+00, 0.000000e+00})'}}
}
}
diff --git a/clang/test/SemaCXX/constexpr-x86-avx512f-builtins.cpp b/clang/test/SemaCXX/constexpr-x86-avx512f-builtins.cpp
index b101f84596d9e3..dbd5e9e9e25804 100644
--- a/clang/test/SemaCXX/constexpr-x86-avx512f-builtins.cpp
+++ b/clang/test/SemaCXX/constexpr-x86-avx512f-builtins.cpp
@@ -29,7 +29,7 @@ constexpr __m128 a = { 1.0f, 2.0f, 3.0f, 4.0f };
constexpr __m128d b_inexact = { 1.0000000000000002, 0.0 };
constexpr __m128 r = _mm_mask_cvtsd_ss(src, 0x1, a, b_inexact);
// expected-error at -1 {{must be initialized by a constant expression}}
-// expected-note at avx512fintrin.h:* {{compile time floating point arithmetic suppressed in strict evaluation modes}}
+// expected-note at avx512fintrin.h:* {{compile-time floating-point evaluation suppressed in strict evaluation modes}}
// expected-note at -3 {{in call to '_mm_mask_cvtsd_ss({9.000000e+00, 5.000000e+00, 6.000000e+00, 7.000000e+00}, 1, {1.000000e+00, 2.000000e+00, 3.000000e+00, 4.000000e+00}, {1.000000e+00, 0.000000e+00})'}}
}
namespace MaskOnInf {
@@ -56,7 +56,7 @@ constexpr __m128 a = { 1.0f, 2.0f, 3.0f, 4.0f };
constexpr __m128d b_sub = { 1e-310, 0.0 };
constexpr __m128 r = _mm_mask_cvtsd_ss(src, 0x1, a, b_sub);
// expected-error at -1 {{must be initialized by a constant expression}}
-// expected-note at avx512fintrin.h:* {{compile time floating point arithmetic suppressed in strict evaluation modes}}
+// expected-note at avx512fintrin.h:* {{compile-time floating-point evaluation suppressed in strict evaluation modes}}
// expected-note at -3 {{in call to '_mm_mask_cvtsd_ss({9.000000e+00, 5.000000e+00, 6.000000e+00, 7.000000e+00}, 1, {1.000000e+00, 2.000000e+00, 3.000000e+00, 4.000000e+00}, {1.000000e-310, 0.000000e+00})'}}
}
}
@@ -104,7 +104,7 @@ namespace Inexact {
constexpr __m512d a = { 1.0000000000000002, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
constexpr __m256 r = _mm512_cvtpd_ps(a);
// expected-error at -1 {{must be initialized by a constant expression}}
-// expected-note at avx512fintrin.h:* {{compile time floating point arithmetic suppressed in strict evaluation modes}}
+// expected-note at avx512fintrin.h:* {{compile-time floating-point evaluation suppressed in strict evaluation modes}}
// expected-note at -3 {{in call to '_mm512_cvtpd_ps({1.000000e+00, 0.000000e+00, 0.000000e+00, 0.000000e+00, 0.000000e+00, 0.000000e+00, 0.000000e+00, 0.000000e+00})'}}
}
}
diff --git a/clang/test/SemaCXX/constexpr-x86-avx512vl-builtins.cpp b/clang/test/SemaCXX/constexpr-x86-avx512vl-builtins.cpp
index bdce60a357f13c..f24c82f6d80241 100644
--- a/clang/test/SemaCXX/constexpr-x86-avx512vl-builtins.cpp
+++ b/clang/test/SemaCXX/constexpr-x86-avx512vl-builtins.cpp
@@ -24,7 +24,7 @@ constexpr __m128 src = { 9.0f, 9.0f, 9.0f, 9.0f };
constexpr __m128d a_inexact = { -1.0, 1.0000000000000002 };
constexpr __m128 r = _mm_mask_cvtpd_ps(src, 0x2, a_inexact);
// expected-error at -1 {{must be initialized by a constant expression}}
-// expected-note at avx512vlintrin.h:* {{compile time floating point arithmetic suppressed in strict evaluation modes}}
+// expected-note at avx512vlintrin.h:* {{compile-time floating-point evaluation suppressed in strict evaluation modes}}
// expected-note at -3 {{in call to '_mm_mask_cvtpd_ps({9.000000e+00, 9.000000e+00, 9.000000e+00, 9.000000e+00}, 2, {-1.000000e+00, 1.000000e+00})'}}
}
namespace MaskOnInf {
diff --git a/clang/test/SemaCXX/constexpr-x86-sse2-builtins.cpp b/clang/test/SemaCXX/constexpr-x86-sse2-builtins.cpp
index 251b46be7e7ff0..48a87d705b186e 100644
--- a/clang/test/SemaCXX/constexpr-x86-sse2-builtins.cpp
+++ b/clang/test/SemaCXX/constexpr-x86-sse2-builtins.cpp
@@ -14,7 +14,7 @@ constexpr __m128 a = { 0.0f, 1.0f, 2.0f, 3.0f };
constexpr __m128d b = { 1.0000000000000002, 0.0 };
constexpr __m128 r = _mm_cvtsd_ss(a, b);
// expected-error at -1 {{must be initialized by a constant expression}}
-// expected-note at emmintrin.h:* {{compile time floating point arithmetic suppressed in strict evaluation modes}}
+// expected-note at emmintrin.h:* {{compile-time floating-point evaluation suppressed in strict evaluation modes}}
// expected-note at -3 {{in call to '_mm_cvtsd_ss({0.000000e+00, 1.000000e+00, 2.000000e+00, 3.000000e+00}, {1.000000e+00, 0.000000e+00})'}}
}
namespace Inf {
@@ -38,7 +38,7 @@ constexpr __m128 a = { 0.0f, 1.0f, 2.0f, 3.0f };
constexpr __m128d b = { 1e-310, 0.0 };
constexpr __m128 r = _mm_cvtsd_ss(a, b);
// expected-error at -1 {{must be initialized by a constant expression}}
-// expected-note at emmintrin.h:* {{compile time floating point arithmetic suppressed in strict evaluation modes}}
+// expected-note at emmintrin.h:* {{compile-time floating-point evaluation suppressed in strict evaluation modes}}
// expected-note at -3 {{in call to '_mm_cvtsd_ss({0.000000e+00, 1.000000e+00, 2.000000e+00, 3.000000e+00}, {1.000000e-310, 0.000000e+00})'}}
}
}
@@ -52,7 +52,7 @@ namespace Inexact {
constexpr __m128d a = { 1.0000000000000002, 0.0 };
constexpr __m128 r = _mm_cvtpd_ps(a);
// expected-error at -1 {{must be initialized by a constant expression}}
-// expected-note at emmintrin.h:* {{compile time floating point arithmetic suppressed in strict evaluation modes}}
+// expected-note at emmintrin.h:* {{compile-time floating-point evaluation suppressed in strict evaluation modes}}
// expected-note at -3 {{in call to '_mm_cvtpd_ps({1.000000e+00, 0.000000e+00})'}}
}
namespace Inf {
@@ -73,7 +73,7 @@ namespace Subnormal {
constexpr __m128d a = { 1e-310, 0.0 };
constexpr __m128 r = _mm_cvtpd_ps(a);
// expected-error at -1 {{must be initialized by a constant expression}}
-// expected-note at emmintrin.h:* {{compile time floating point arithmetic suppressed in strict evaluation modes}}
+// expected-note at emmintrin.h:* {{compile-time floating-point evaluation suppressed in strict evaluation modes}}
// expected-note at -3 {{in call to '_mm_cvtpd_ps({1.000000e-310, 0.000000e+00})'}}
}
}
>From 34edf5d1d563103a94e135b25a4fb68ef423cb80 Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue.h at gmail.com>
Date: Sun, 13 Sep 2026 03:57:19 +0000
Subject: [PATCH 13/16] Fix merge issue and update tests.
---
clang/lib/AST/ByteCode/Interp.cpp | 16 ----------------
clang/test/CodeGen/builtin-exp-folding.c | 2 +-
clang/test/Sema/constant-builtins-exp.cpp | 2 +-
3 files changed, 2 insertions(+), 18 deletions(-)
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index d47a7f32acb0d8..7429bf3a8924e2 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -1286,22 +1286,6 @@ bool CheckFloatStatus(InterpState &S, CodePtr OpPC, APFloat::opStatus Status,
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();
- }
-
- return CheckFloatStatus(S, OpPC, Status, FPO);
-}
-
bool CheckFloatResult(InterpState &S, CodePtr OpPC, const Floating &Result,
APFloat::opStatus Status, FPOptions FPO) {
// FIXME: The standard quote below is deleted by P3899R3.
diff --git a/clang/test/CodeGen/builtin-exp-folding.c b/clang/test/CodeGen/builtin-exp-folding.c
index 419418a8e76631..69ea75a537bf05 100644
--- a/clang/test/CodeGen/builtin-exp-folding.c
+++ b/clang/test/CodeGen/builtin-exp-folding.c
@@ -1,5 +1,5 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -w -o - -fmath-errno %s | FileCheck %s --check-prefix=CHECK-ERRNO
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -w -o - -fno-math-errno %s | FileCheck %s --check-prefix=CHECK-NO-ERRNO
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -w -o - %s | FileCheck %s --check-prefix=CHECK-NO-ERRNO
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -w -o - -frounding-math %s | FileCheck %s --check-prefix=CHECK-ROUNDING
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -w -o - -ffp-exception-behavior=strict %s | FileCheck %s --check-prefix=CHECK-STRICT
diff --git a/clang/test/Sema/constant-builtins-exp.cpp b/clang/test/Sema/constant-builtins-exp.cpp
index 685bfce8960c25..a43710704e2269 100644
--- a/clang/test/Sema/constant-builtins-exp.cpp
+++ b/clang/test/Sema/constant-builtins-exp.cpp
@@ -1,7 +1,7 @@
// 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
// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify -Wno-unknown-pragmas -fmath-errno %s
-// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify -Wno-unknown-pragmas -fno-math-errno %s
+// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify -fexperimental-new-constant-interpreter -Wno-unknown-pragmas -fmath-errno %s
// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify -Wno-unknown-pragmas -frounding-math %s
// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify -fexperimental-new-constant-interpreter -Wno-unknown-pragmas -frounding-math %s
>From 06110b86ad2e4e60407b3b859fcfde77337d66fb Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue.h at gmail.com>
Date: Sun, 13 Sep 2026 04:59:17 +0000
Subject: [PATCH 14/16] Fix tests.
---
clang/test/CodeGen/builtin-exp-folding.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/clang/test/CodeGen/builtin-exp-folding.c b/clang/test/CodeGen/builtin-exp-folding.c
index 69ea75a537bf05..6e604791ce87e7 100644
--- a/clang/test/CodeGen/builtin-exp-folding.c
+++ b/clang/test/CodeGen/builtin-exp-folding.c
@@ -122,7 +122,7 @@ double test_exp_rounding_math() {
return __builtin_exp(1.0);
}
// CHECK-ROUNDING-LABEL: @test_exp_rounding_math
-// CHECK-ROUNDING: call double @llvm.experimental.constrained.exp.f64(double 1.000000e+00, metadata !"round.dynamic", metadata !"fpexcept.ignore")
+// CHECK-ROUNDING: call double @llvm.experimental.constrained.exp.f64(double 1.000000e+00, metadata !"round.downward", metadata !"fpexcept.ignore")
// Overriding -frounding-math with static tonearest rounding pragma allows folding:
float test_rounding_math_override() {
@@ -138,7 +138,9 @@ float test_pragma_fenv_round_dynamic() {
return __builtin_expf(1.0f);
}
// CHECK-ERRNO-LABEL: @test_pragma_fenv_round_dynamic
-// CHECK-ERRNO: call float @llvm.experimental.constrained.exp.f32(float 1.000000e+00, metadata !"round.dynamic", metadata !"fpexcept.ignore")
+// CHECK-ERRNO: call float @expf(float noundef 1.000000e+00)
+// CHECK-NO-ERRNO-LABEL: @test_pragma_fenv_round_dynamic
+// CHECK-NO-ERRNO: call float @llvm.exp.f32(float 1.000000e+00)
// Local dynamic rounding mode pragma still allows exact folding:
float test_pragma_fenv_round_dynamic_exact() {
@@ -147,6 +149,8 @@ float test_pragma_fenv_round_dynamic_exact() {
}
// CHECK-ERRNO-LABEL: @test_pragma_fenv_round_dynamic_exact
// CHECK-ERRNO: ret float 1.000000e+00
+// CHECK-NO-ERRNO-LABEL: @test_pragma_fenv_round_dynamic_exact
+// CHECK-NO-ERRNO: ret float 1.000000e+00
// Pragma STDC FENV_ACCESS ON prevents folding of inexact calls:
float test_fenv_access_inexact() {
@@ -154,7 +158,7 @@ float test_fenv_access_inexact() {
return __builtin_expf(1.0f);
}
// CHECK-ERRNO-LABEL: @test_fenv_access_inexact
-// CHECK-ERRNO: call float @llvm.experimental.constrained.exp.f32(float 1.000000e+00, metadata !"round.dynamic", metadata !"fpexcept.strict")
+// CHECK-ERRNO: call float @expf(float noundef 1.000000e+00)
// CHECK-NO-ERRNO-LABEL: @test_fenv_access_inexact
// CHECK-NO-ERRNO: call float @llvm.experimental.constrained.exp.f32(float 1.000000e+00, metadata !"round.dynamic", metadata !"fpexcept.strict")
>From a222bbd2dfcb4bac3a14eea13e87765eb599d072 Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue.h at gmail.com>
Date: Sun, 13 Sep 2026 05:44:50 +0000
Subject: [PATCH 15/16] Update codegen test.
---
clang/test/CodeGen/builtin-exp-folding.c | 33 ++++++++++++++----------
1 file changed, 19 insertions(+), 14 deletions(-)
diff --git a/clang/test/CodeGen/builtin-exp-folding.c b/clang/test/CodeGen/builtin-exp-folding.c
index 6e604791ce87e7..c462b424cd3194 100644
--- a/clang/test/CodeGen/builtin-exp-folding.c
+++ b/clang/test/CodeGen/builtin-exp-folding.c
@@ -3,14 +3,18 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -w -o - -frounding-math %s | FileCheck %s --check-prefix=CHECK-ROUNDING
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -w -o - -ffp-exception-behavior=strict %s | FileCheck %s --check-prefix=CHECK-STRICT
+// Global variable initializers are evaluated at compile time using default FP settings (C17 7.6.1p2):
+float global_exp_inexact = __builtin_expf(1.0f);
+// CHECK-STRICT: @global_exp_inexact = {{.*}}global float f0x402DF854
+
// An exact or inexact exp that doesn't set errno folds even with -fmath-errno:
float test_expf_1() {
return __builtin_expf(1.0f);
}
// CHECK-ERRNO-LABEL: @test_expf_1
-// CHECK-ERRNO: ret float 0x3FF5BF0A80000000
+// CHECK-ERRNO: ret float f0x402DF854
// CHECK-NO-ERRNO-LABEL: @test_expf_1
-// CHECK-NO-ERRNO: ret float 0x3FF5BF0A80000000
+// CHECK-NO-ERRNO: ret float f0x402DF854
// CHECK-ROUNDING-LABEL: @test_expf_1
// CHECK-ROUNDING: call float @llvm.experimental.constrained.exp.f32(float 1.000000e+00, metadata !"round.dynamic", metadata !"fpexcept.ignore")
@@ -49,9 +53,9 @@ float test_expf_nan() {
return __builtin_expf(__builtin_nanf(""));
}
// CHECK-ERRNO-LABEL: @test_expf_nan
-// CHECK-ERRNO: ret float 0x7FF8000000000000
+// CHECK-ERRNO: ret float +qnan
// CHECK-NO-ERRNO-LABEL: @test_expf_nan
-// CHECK-NO-ERRNO: ret float 0x7FF8000000000000
+// CHECK-NO-ERRNO: ret float +qnan
// Inexact exp raises FE_INEXACT, so it cannot fold under strict exception mode:
@@ -79,7 +83,7 @@ float test_expf_underflow() {
// CHECK-ERRNO-LABEL: @test_expf_underflow
// CHECK-ERRNO: call float @expf(float noundef -1.000000e+02)
// CHECK-NO-ERRNO-LABEL: @test_expf_underflow
-// CHECK-NO-ERRNO: ret float 0.000000e+00
+// CHECK-NO-ERRNO: ret float 3.783510e-44
// CHECK-STRICT-LABEL: @test_expf_underflow
// CHECK-STRICT: call float @llvm.experimental.constrained.exp.f32(float -1.000000e+02, metadata !"round.tonearest", metadata !"fpexcept.strict")
@@ -90,7 +94,7 @@ float test_expf_denormal() {
// CHECK-ERRNO-LABEL: @test_expf_denormal
// CHECK-ERRNO: call float @expf(float noundef -8.800000e+01)
// CHECK-NO-ERRNO-LABEL: @test_expf_denormal
-// CHECK-NO-ERRNO: ret float 0x37F6DC7000000000
+// CHECK-NO-ERRNO: ret float f0x0041EDC4
// CHECK-STRICT-LABEL: @test_expf_denormal
// CHECK-STRICT: call float @llvm.experimental.constrained.exp.f32(float -8.800000e+01, metadata !"round.tonearest", metadata !"fpexcept.strict")
@@ -130,17 +134,20 @@ float test_rounding_math_override() {
return __builtin_expf(1.0f);
}
// CHECK-ROUNDING-LABEL: @test_rounding_math_override
-// CHECK-ROUNDING: ret float 0x3FF5BF0A80000000
+// CHECK-ROUNDING: ret float f0x402DF854
-// Local dynamic rounding mode pragma prevents inexact folding without -frounding-math:
+// When FENV_ACCESS is off and without -frounding-math, FE_DYNAMIC allows the
+// translator to assume default rounding mode (C23 7.6.2p3), so it folds:
float test_pragma_fenv_round_dynamic() {
#pragma STDC FENV_ROUND FE_DYNAMIC
return __builtin_expf(1.0f);
}
// CHECK-ERRNO-LABEL: @test_pragma_fenv_round_dynamic
-// CHECK-ERRNO: call float @expf(float noundef 1.000000e+00)
+// CHECK-ERRNO: ret float f0x402DF854
// CHECK-NO-ERRNO-LABEL: @test_pragma_fenv_round_dynamic
-// CHECK-NO-ERRNO: call float @llvm.exp.f32(float 1.000000e+00)
+// CHECK-NO-ERRNO: ret float f0x402DF854
+// CHECK-ROUNDING-LABEL: @test_pragma_fenv_round_dynamic
+// CHECK-ROUNDING: call float @llvm.experimental.constrained.exp.f32(float 1.000000e+00, metadata !"round.dynamic", metadata !"fpexcept.ignore")
// Local dynamic rounding mode pragma still allows exact folding:
float test_pragma_fenv_round_dynamic_exact() {
@@ -151,6 +158,8 @@ float test_pragma_fenv_round_dynamic_exact() {
// CHECK-ERRNO: ret float 1.000000e+00
// CHECK-NO-ERRNO-LABEL: @test_pragma_fenv_round_dynamic_exact
// CHECK-NO-ERRNO: ret float 1.000000e+00
+// CHECK-ROUNDING-LABEL: @test_pragma_fenv_round_dynamic_exact
+// CHECK-ROUNDING: ret float 1.000000e+00
// Pragma STDC FENV_ACCESS ON prevents folding of inexact calls:
float test_fenv_access_inexact() {
@@ -171,7 +180,3 @@ float test_fenv_access_exact() {
// CHECK-ERRNO: ret float 1.000000e+00
// CHECK-NO-ERRNO-LABEL: @test_fenv_access_exact
// CHECK-NO-ERRNO: ret float 1.000000e+00
-
-// Global variable initializers are evaluated at compile time using default FP settings (C17 7.6.1p2):
-float global_exp_inexact = __builtin_expf(1.0f);
-// CHECK-STRICT: @global_exp_inexact = {{.*}}global float 0x3FF5BF0A80000000
>From 3283b2b27057105782c42dca70603cfbd288d798 Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue.h at gmail.com>
Date: Sun, 13 Sep 2026 16:42:37 +0000
Subject: [PATCH 16/16] Address comments.
---
clang/lib/AST/ByteCode/Interp.h | 6 ------
clang/lib/AST/ByteCode/InterpBuiltin.cpp | 10 ++++++----
clang/lib/AST/ByteCode/InterpHelpers.h | 6 ++++++
3 files changed, 12 insertions(+), 10 deletions(-)
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index a20a3f91dd2673..c419fc73c8aa69 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -259,12 +259,6 @@ bool CheckDivRem(InterpState &S, CodePtr OpPC, const T &LHS, const T &RHS) {
bool CheckFloatResult(InterpState &S, CodePtr OpPC, const Floating &Result,
APFloat::opStatus Status, FPOptions FPO);
-/// Check if the given floating-point evaluation status is allowed for
-/// compile-time constant folding during translation (as opposed to mandatory
-/// constant expression evaluation).
-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 2f420d99725e29..ddd4d863693845 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -9,7 +9,6 @@
#include "Boolean.h"
#include "Char.h"
#include "EvalEmitter.h"
-#include "Interp.h"
#include "InterpBuiltinBitCast.h"
#include "InterpHelpers.h"
#include "PrimType.h"
@@ -762,12 +761,13 @@ static bool interp__builtin_exp(InterpState &S, CodePtr OpPC,
llvm::RoundingMode RM = getRoundingMode(FPO);
APFloat::opStatus Status = APFloat::opStatus::opOK;
std::optional<APFloat> Result = exp(Arg.getAPFloat(), RM, &Status);
- const SourceInfo &E = S.Current->getSource(OpPC);
- if (!Result.has_value()) {
- if (S.inConstantContext())
+ if (!Result) {
+ if (S.inConstantContext()) {
+ const SourceInfo &E = S.Current->getSource(OpPC);
S.FFDiag(E, diag::note_constexpr_unsupported_rounding)
<< Call->getDirectCallee() << llvm::spell(RM);
+ }
return false;
}
@@ -775,6 +775,7 @@ static bool interp__builtin_exp(InterpState &S, CodePtr OpPC,
// [library.c]p3: A call to a math function is not a core constant
// expression if an exception other than FE_INEXACT is raised.
if (Status & (~APFloat::opStatus::opInexact)) {
+ const SourceInfo &E = S.Current->getSource(OpPC);
const FunctionDecl *FD = Call->getDirectCallee();
if (Status & APFloat::opStatus::opUnderflow)
S.FFDiag(E, diag::note_constexpr_float_underflow) << FD;
@@ -798,6 +799,7 @@ static bool interp__builtin_exp(InterpState &S, CodePtr OpPC,
// This check is performed here rather than in CheckFloatStatus because errno
// is specific to math functions (and not set by core language operators).
if (S.getLangOpts().MathErrno && (Status & (~APFloat::opStatus::opInexact))) {
+ const SourceInfo &E = S.Current->getSource(OpPC);
S.FFDiag(E, diag::note_constexpr_math_errno) << Call->getDirectCallee();
return false;
}
diff --git a/clang/lib/AST/ByteCode/InterpHelpers.h b/clang/lib/AST/ByteCode/InterpHelpers.h
index f183efb5b19d11..2cffc6cb680abe 100644
--- a/clang/lib/AST/ByteCode/InterpHelpers.h
+++ b/clang/lib/AST/ByteCode/InterpHelpers.h
@@ -117,6 +117,12 @@ static inline llvm::RoundingMode getRoundingMode(FPOptions FPO) {
return RM;
}
+/// Check if the given floating-point evaluation status is allowed for
+/// compile-time constant folding during translation (as opposed to mandatory
+/// constant expression evaluation).
+bool CheckFloatStatus(InterpState &S, CodePtr OpPC, APFloat::opStatus Status,
+ FPOptions FPO);
+
inline bool Invalid(InterpState &S, CodePtr OpPC) {
const SourceLocation &Loc = S.Current->getLocation(OpPC);
S.FFDiag(Loc, diag::note_invalid_subexpr_in_const_expr)
More information about the cfe-commits
mailing list