[libcxx-commits] [clang] [libcxx] [libcxx][Clang] constexpr `std::trunc` (PR #233522)
Vinay Deshmukh via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Oct 20 00:56:00 PDT 2026
https://github.com/vinay-deshmukh updated https://github.com/llvm/llvm-project/pull/233522
>From ab2f1629f54fada8a92c2eafa8015f607f0f83b2 Mon Sep 17 00:00:00 2001
From: Vinay Deshmukh <vinay_deshmukh at outlook.com>
Date: Mon, 19 Oct 2026 19:09:10 +0100
Subject: [PATCH 1/4] initial implementation
---
clang/include/clang/Basic/Builtins.td | 3 +-
clang/lib/AST/ByteCode/InterpBuiltin.cpp | 17 ++++++++++
clang/lib/AST/ExprConstant.cpp | 36 +++++++++++++++------
clang/test/Sema/constant-builtins-trunc.cpp | 28 ++++++++++++++++
4 files changed, 73 insertions(+), 11 deletions(-)
create mode 100644 clang/test/Sema/constant-builtins-trunc.cpp
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 344a712ddc585..010d9a9451320 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -526,7 +526,7 @@ def TgammaF128 : Builtin {
def TruncF16F128 : Builtin, F16F128MathTemplate {
let Spellings = ["__builtin_trunc"];
- let Attributes = [FunctionWithBuiltinPrefix, NoThrow, Const];
+ let Attributes = [FunctionWithBuiltinPrefix, NoThrow, Const, Constexpr];
let Prototype = "T(T)";
}
@@ -4501,6 +4501,7 @@ def Trunc : FPMathTemplate, LibBuiltin<"math.h"> {
let Attributes = [NoThrow, Const];
let Prototype = "T(T)";
let AddBuiltinPrefixedAlias = 1;
+ let OnlyBuiltinPrefixedAliasIsConstexpr = 1;
}
def Cabs : FPMathTemplate, LibBuiltin<"complex.h"> {
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index a539fb26abc08..e0471b0de31d3 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -4756,6 +4756,23 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call,
case Builtin::BI__builtin_fminimum_numf128:
return interp__builtin_fmin(S, OpPC, Frame, /*IsNumBuiltin=*/true);
+ case Builtin::BI__builtin_truncf:
+ case Builtin::BI__builtin_trunc:
+ case Builtin::BI__builtin_truncl:
+ case Builtin::BI__builtin_truncf16:
+ case Builtin::BI__builtin_truncf128: {
+
+ // FIXME: implement after ExprConstant.cpp
+ // APFloat RHS(0.);
+ // Result = RHS;
+ // return true;
+
+ llvm::errs() << "interp: pending vinay";
+ // llvm::report_fatal_error(
+ // "TODO: implement constexpr evaluation for __builtin_signbit");
+ return false;
+ }
+
case Builtin::BI__builtin_fmax:
case Builtin::BI__builtin_fmaxf:
case Builtin::BI__builtin_fmaxl:
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index a3385d3b7318f..d4361f0df3e3b 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -757,11 +757,7 @@ namespace {
/// std::allocator<T>::allocate).
const Expr *AllocExpr = nullptr;
- enum Kind {
- New,
- ArrayNew,
- StdAllocator
- };
+ enum Kind { New, ArrayNew, StdAllocator };
/// Get the kind of the allocation. This must match between allocation
/// and deallocation.
@@ -1959,7 +1955,7 @@ static bool IsGlobalLValue(APValue::LValueBase B) {
if (!B)
return true;
- if (const ValueDecl *D = B.dyn_cast<const ValueDecl*>()) {
+ if (const ValueDecl *D = B.dyn_cast<const ValueDecl *>()) {
// ... the address of an object with static storage duration,
if (const VarDecl *VD = dyn_cast<VarDecl>(D))
return VD->hasGlobalStorage();
@@ -1974,7 +1970,7 @@ static bool IsGlobalLValue(APValue::LValueBase B) {
if (B.is<TypeInfoLValue>() || B.is<DynamicAllocLValue>())
return true;
- const Expr *E = B.get<const Expr*>();
+ const Expr *E = B.get<const Expr *>();
switch (E->getStmtClass()) {
default:
return false;
@@ -3512,9 +3508,11 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E,
// have been value-dependent too), so diagnose that.
assert(!VD->mightBeUsableInConstantExpressions(Info.Ctx));
if (!Info.checkingPotentialConstantExpression()) {
- Info.FFDiag(E, Info.getLangOpts().CPlusPlus11
- ? diag::note_constexpr_ltor_non_constexpr
- : diag::note_constexpr_ltor_non_integral, 1)
+ Info.FFDiag(E,
+ Info.getLangOpts().CPlusPlus11
+ ? diag::note_constexpr_ltor_non_constexpr
+ : diag::note_constexpr_ltor_non_integral,
+ 1)
<< VD << VD->getType();
NoteLValueLocation(Info, Base);
}
@@ -20511,6 +20509,24 @@ bool FloatExprEvaluator::VisitCallExpr(const CallExpr *E) {
return true;
}
+ case Builtin::BI__builtin_truncf:
+ case Builtin::BI__builtin_trunc:
+ case Builtin::BI__builtin_truncl:
+ case Builtin::BI__builtin_truncf16:
+ case Builtin::BI__builtin_truncf128: {
+ // const Expr * arg0 = E->getArg(0);
+
+ // FIXME: use 0 for now, will implement later
+ APFloat RHS(0.);
+ Result = RHS;
+ return true;
+
+ // llvm::errs() << "exprconstant: pending vinay";
+ // llvm::report_fatal_error(
+ // "TODO: implement constexpr evaluation for __builtin_signbit");
+ // return false;
+ }
+
case Builtin::BI__builtin_fmax:
case Builtin::BI__builtin_fmaxf:
case Builtin::BI__builtin_fmaxl:
diff --git a/clang/test/Sema/constant-builtins-trunc.cpp b/clang/test/Sema/constant-builtins-trunc.cpp
new file mode 100644
index 0000000000000..6537b47ac430f
--- /dev/null
+++ b/clang/test/Sema/constant-builtins-trunc.cpp
@@ -0,0 +1,28 @@
+// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify %s
+
+// expected-no-diagnostics
+
+
+// TODO:
+// R-U-N:
+auto x = " // %clang_cc1 -std=c++17 -fsyntax-only -verify -fexperimental-new-constant-interpreter %s";
+
+// trunc
+// truncf
+// truncl
+
+// ASSERT_NOT_CONSTEXPR_CXX23(std::trunc(1.0f) == 1.0f);
+// ASSERT_NOT_CONSTEXPR_CXX23(std::trunc(1.0) == 1.0);
+// ASSERT_NOT_CONSTEXPR_CXX23(std::trunc(1.0L) == 1.0L);
+// ASSERT_NOT_CONSTEXPR_CXX23(std::truncf(1.0f) == 1.0f);
+// ASSERT_NOT_CONSTEXPR_CXX23(std::truncl(1.0L) == 1.0L);
+
+
+// std::trunc(1.0f)
+// std::trunc(1.0)
+// std::trunc(1.0L)
+// std::truncf(1.0f)
+// std::truncl(1.0L)
+
+constexpr float f = __builtin_trunc(+2.7f);
+static_assert(f == 0.0); // FIXME: implement it later
\ No newline at end of file
>From e218e2d1027e6bce2a627f149d4d2f30ef26e0dc Mon Sep 17 00:00:00 2001
From: Vinay Deshmukh <vinay_deshmukh at outlook.com>
Date: Mon, 19 Oct 2026 21:37:53 +0100
Subject: [PATCH 2/4] implement ExprConstant approach
---
clang/lib/AST/ExprConstant.cpp | 15 +++---
clang/test/Sema/constant-builtins-trunc.cpp | 58 +++++++++++++++++++--
2 files changed, 61 insertions(+), 12 deletions(-)
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index d4361f0df3e3b..2ddf010a64272 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -56,6 +56,7 @@
#include "clang/Basic/TargetBuiltins.h"
#include "clang/Basic/TargetInfo.h"
#include "llvm/ADT/APFixedPoint.h"
+#include "llvm/ADT/FloatingPointMode.h"
#include "llvm/ADT/Sequence.h"
#include "llvm/ADT/SmallBitVector.h"
#include "llvm/ADT/StringExtras.h"
@@ -20514,17 +20515,13 @@ bool FloatExprEvaluator::VisitCallExpr(const CallExpr *E) {
case Builtin::BI__builtin_truncl:
case Builtin::BI__builtin_truncf16:
case Builtin::BI__builtin_truncf128: {
- // const Expr * arg0 = E->getArg(0);
+ APFloat Arg0(0.);
+ if (!EvaluateFloat(E->getArg(0), Arg0, Info))
+ return false;
- // FIXME: use 0 for now, will implement later
- APFloat RHS(0.);
- Result = RHS;
+ Arg0.roundToIntegral(llvm::RoundingMode::TowardZero);
+ Result = Arg0;
return true;
-
- // llvm::errs() << "exprconstant: pending vinay";
- // llvm::report_fatal_error(
- // "TODO: implement constexpr evaluation for __builtin_signbit");
- // return false;
}
case Builtin::BI__builtin_fmax:
diff --git a/clang/test/Sema/constant-builtins-trunc.cpp b/clang/test/Sema/constant-builtins-trunc.cpp
index 6537b47ac430f..fc0b9e311a949 100644
--- a/clang/test/Sema/constant-builtins-trunc.cpp
+++ b/clang/test/Sema/constant-builtins-trunc.cpp
@@ -1,6 +1,7 @@
// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify %s
-// expected-no-diagnostics
+// FIXME
+auto y = "// expected-no-diagnostics";
// TODO:
@@ -24,5 +25,56 @@ auto x = " // %clang_cc1 -std=c++17 -fsyntax-only -verify -fexperimental-new-co
// std::truncf(1.0f)
// std::truncl(1.0L)
-constexpr float f = __builtin_trunc(+2.7f);
-static_assert(f == 0.0); // FIXME: implement it later
\ No newline at end of file
+
+constexpr double NaN = __builtin_nan("");
+constexpr double Inf = __builtin_inf();
+constexpr double NegInf = -__builtin_inf();
+
+static_assert(__builtin_trunc(+2.7f) == 2.0);
+static_assert(__builtin_trunc(+2.7) == 2.0);
+static_assert(__builtin_trunc(+2.7L) == 2.0L);
+static_assert(__builtin_truncf(+2.7f) == 2.0f);
+static_assert(__builtin_truncl(+2.7L) == 2.0L);
+static_assert(__builtin_truncf16(+2.7f) == 2.0f);
+static_assert(__builtin_truncf128(+2.7f) == 2.0f);
+
+#define TRUNC_TEST_POSITIVE(T, FUNC) \
+ static_assert(FUNC(T(2.7)) == T(2.0)); \
+ static_assert(FUNC(T(2.0)) == T(2.0)); \
+ static_assert(FUNC(T(0.0)) == T(0.0));
+
+#define TRUNC_TEST_NEGATIVE(T, FUNC) \
+ static_assert(FUNC(T(-2.7)) == T(-2.0)); \
+ static_assert(FUNC(T(-2.0)) == T(-2.0)); \
+ static_assert(FUNC(T(-0.0)) == T(-0.0));
+
+#define TRUNC_TEST_SPECIAL(T, FUNC) \
+ static_assert(FUNC(T(Inf)) == T(Inf)); \
+ static_assert(FUNC(T(NegInf)) == T(NegInf)); \
+ static_assert(__builtin_isnan(FUNC(NaN)));
+
+// FIXME: add NaN handling here as well
+
+#define LIST_TRUNC_TESTS(T, FUNC) \
+ TRUNC_TEST_POSITIVE(T, FUNC) \
+ TRUNC_TEST_NEGATIVE(T, FUNC) \
+ TRUNC_TEST_SPECIAL(T, FUNC)
+
+
+LIST_TRUNC_TESTS(float, __builtin_trunc);
+LIST_TRUNC_TESTS(double, __builtin_trunc);
+LIST_TRUNC_TESTS((long double), __builtin_trunc);
+LIST_TRUNC_TESTS(__fp16, __builtin_truncf16)
+#ifdef __FLOAT128__
+LIST_TRUNC_TESTS(__float128, __builtin_truncf128)
+#endif
+
+static_assert(__builtin_isnan(__builtin_trunc(NaN)));
+static_assert(__builtin_trunc(Inf) == Inf);
+static_assert(__builtin_trunc(NegInf) == NegInf);
+static_assert(__builtin_trunc(0.0) == 0.0);
+static_assert(__builtin_trunc(-0.0) == -0.0);
+
+// FIXME: remove, kept here temporarily
+// expected-error at +1 {{static assertion failed}}
+static_assert(0 == 1);
>From 8d6018f6edc2cf067f544e6e83fc48f8f0da5054 Mon Sep 17 00:00:00 2001
From: Vinay Deshmukh <vinay_deshmukh at outlook.com>
Date: Mon, 19 Oct 2026 22:09:17 +0100
Subject: [PATCH 3/4] InterpBuiltin.cpp
---
clang/lib/AST/ByteCode/InterpBuiltin.cpp | 27 ++++++++++++---------
clang/test/Sema/constant-builtins-trunc.cpp | 8 +-----
2 files changed, 16 insertions(+), 19 deletions(-)
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index e0471b0de31d3..f13d86f968dff 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "../ExprConstShared.h"
#include "Boolean.h"
+#include "ByteCode/Floating.h"
#include "Char.h"
#include "EvalEmitter.h"
#include "InterpBuiltinBitCast.h"
@@ -529,6 +530,18 @@ static bool interp__builtin_fmin(InterpState &S, CodePtr OpPC,
return true;
}
+static bool interp__builtin_trunc(InterpState &S, CodePtr OpPC,
+ const InterpFrame *Frame) {
+ const Floating &Arg = S.Stk.pop<Floating>();
+ Floating Result = S.allocFloat(Arg.getSemantics());
+ APFloat tmp = Arg.getAPFloat();
+ tmp.roundToIntegral(llvm::RoundingMode::TowardZero);
+
+ Result.copy(tmp);
+ S.Stk.push<Floating>(Result);
+ return true;
+}
+
static bool interp__builtin_fmax(InterpState &S, CodePtr OpPC,
const InterpFrame *Frame, bool IsNumBuiltin) {
const Floating &RHS = S.Stk.pop<Floating>();
@@ -4760,18 +4773,8 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call,
case Builtin::BI__builtin_trunc:
case Builtin::BI__builtin_truncl:
case Builtin::BI__builtin_truncf16:
- case Builtin::BI__builtin_truncf128: {
-
- // FIXME: implement after ExprConstant.cpp
- // APFloat RHS(0.);
- // Result = RHS;
- // return true;
-
- llvm::errs() << "interp: pending vinay";
- // llvm::report_fatal_error(
- // "TODO: implement constexpr evaluation for __builtin_signbit");
- return false;
- }
+ case Builtin::BI__builtin_truncf128:
+ return interp__builtin_trunc(S, OpPC, Frame);
case Builtin::BI__builtin_fmax:
case Builtin::BI__builtin_fmaxf:
diff --git a/clang/test/Sema/constant-builtins-trunc.cpp b/clang/test/Sema/constant-builtins-trunc.cpp
index fc0b9e311a949..d4f3e730e9b5c 100644
--- a/clang/test/Sema/constant-builtins-trunc.cpp
+++ b/clang/test/Sema/constant-builtins-trunc.cpp
@@ -1,13 +1,9 @@
// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify -fexperimental-new-constant-interpreter %s
// FIXME
auto y = "// expected-no-diagnostics";
-
-// TODO:
-// R-U-N:
-auto x = " // %clang_cc1 -std=c++17 -fsyntax-only -verify -fexperimental-new-constant-interpreter %s";
-
// trunc
// truncf
// truncl
@@ -53,8 +49,6 @@ static_assert(__builtin_truncf128(+2.7f) == 2.0f);
static_assert(FUNC(T(NegInf)) == T(NegInf)); \
static_assert(__builtin_isnan(FUNC(NaN)));
-// FIXME: add NaN handling here as well
-
#define LIST_TRUNC_TESTS(T, FUNC) \
TRUNC_TEST_POSITIVE(T, FUNC) \
TRUNC_TEST_NEGATIVE(T, FUNC) \
>From 3768c35aee4d3f6d6bc34915e8b1d7ba5f27ad44 Mon Sep 17 00:00:00 2001
From: Vinay Deshmukh <vinay_deshmukh at outlook.com>
Date: Tue, 20 Oct 2026 08:55:28 +0100
Subject: [PATCH 4/4] mark constexpr to see CI run
---
libcxx/include/__math/rounding_functions.h | 11 +++++++----
.../numerics/c.math/constexpr-cxx23-clang.pass.cpp | 10 +++++-----
2 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/libcxx/include/__math/rounding_functions.h b/libcxx/include/__math/rounding_functions.h
index aadeb395fa2eb..188a517d4cc25 100644
--- a/libcxx/include/__math/rounding_functions.h
+++ b/libcxx/include/__math/rounding_functions.h
@@ -222,19 +222,22 @@ template <class _A1, __enable_if_t<is_integral<_A1>::value, int> = 0>
// trunc
-[[__nodiscard__]] inline _LIBCPP_HIDE_FROM_ABI float trunc(float __x) _NOEXCEPT { return __builtin_trunc(__x); }
+[[__nodiscard__]] inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI float trunc(float __x) _NOEXCEPT {
+ return __builtin_trunc(__x);
+}
template <class = int>
-[[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI double trunc(double __x) _NOEXCEPT {
+[[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI double trunc(double __x) _NOEXCEPT {
return __builtin_trunc(__x);
}
-[[__nodiscard__]] inline _LIBCPP_HIDE_FROM_ABI long double trunc(long double __x) _NOEXCEPT {
+[[__nodiscard__]] inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI long double
+trunc(long double __x) _NOEXCEPT {
return __builtin_truncl(__x);
}
template <class _A1, __enable_if_t<is_integral<_A1>::value, int> = 0>
-[[__nodiscard__]] inline _LIBCPP_HIDE_FROM_ABI double trunc(_A1 __x) _NOEXCEPT {
+[[__nodiscard__]] inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI double trunc(_A1 __x) _NOEXCEPT {
return __builtin_trunc((double)__x);
}
diff --git a/libcxx/test/libcxx/numerics/c.math/constexpr-cxx23-clang.pass.cpp b/libcxx/test/libcxx/numerics/c.math/constexpr-cxx23-clang.pass.cpp
index beca4907284ad..6560a70aafc7a 100644
--- a/libcxx/test/libcxx/numerics/c.math/constexpr-cxx23-clang.pass.cpp
+++ b/libcxx/test/libcxx/numerics/c.math/constexpr-cxx23-clang.pass.cpp
@@ -137,11 +137,11 @@ int main(int, char**) {
ASSERT_NOT_CONSTEXPR_CXX23(std::llroundf(1.0f) == 1LL);
ASSERT_NOT_CONSTEXPR_CXX23(std::llroundl(1.0L) == 1LL);
- ASSERT_NOT_CONSTEXPR_CXX23(std::trunc(1.0f) == 1.0f);
- ASSERT_NOT_CONSTEXPR_CXX23(std::trunc(1.0) == 1.0);
- ASSERT_NOT_CONSTEXPR_CXX23(std::trunc(1.0L) == 1.0L);
- ASSERT_NOT_CONSTEXPR_CXX23(std::truncf(1.0f) == 1.0f);
- ASSERT_NOT_CONSTEXPR_CXX23(std::truncl(1.0L) == 1.0L);
+ ASSERT_CONSTEXPR_CXX23(std::trunc(1.0f) == 1.0f);
+ ASSERT_CONSTEXPR_CXX23(std::trunc(1.0) == 1.0);
+ ASSERT_CONSTEXPR_CXX23(std::trunc(1.0L) == 1.0L);
+ ASSERT_CONSTEXPR_CXX23(std::truncf(1.0f) == 1.0f);
+ ASSERT_CONSTEXPR_CXX23(std::truncl(1.0L) == 1.0L);
ASSERT_NOT_CONSTEXPR_CXX23(std::fmod(1.5f, 1.0f) == 0.5f);
ASSERT_NOT_CONSTEXPR_CXX23(std::fmod(1.5, 1.0) == 0.5);
More information about the libcxx-commits
mailing list