[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 01:18:43 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/5] 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/5] 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/5] 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/5] 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);

>From 051b0799af1d3c81ac30a0502f4c66256707f576 Mon Sep 17 00:00:00 2001
From: Vinay Deshmukh <vinay_deshmukh at outlook.com>
Date: Tue, 20 Oct 2026 09:18:20 +0100
Subject: [PATCH 5/5] missing tests

---
 clang/test/Sema/constant-builtins-trunc.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/clang/test/Sema/constant-builtins-trunc.cpp b/clang/test/Sema/constant-builtins-trunc.cpp
index d4f3e730e9b5c..0f464bc8803bc 100644
--- a/clang/test/Sema/constant-builtins-trunc.cpp
+++ b/clang/test/Sema/constant-builtins-trunc.cpp
@@ -56,8 +56,10 @@ static_assert(__builtin_truncf128(+2.7f) == 2.0f);
 
 
 LIST_TRUNC_TESTS(float, __builtin_trunc);
+LIST_TRUNC_TESTS(float, __builtin_truncf);
 LIST_TRUNC_TESTS(double, __builtin_trunc);
 LIST_TRUNC_TESTS((long double), __builtin_trunc);
+LIST_TRUNC_TESTS((long double), __builtin_truncl);
 LIST_TRUNC_TESTS(__fp16, __builtin_truncf16)
 #ifdef __FLOAT128__
 LIST_TRUNC_TESTS(__float128, __builtin_truncf128)



More information about the libcxx-commits mailing list