[clang] [clang] constexpr `__builtin_elementwise_{max, min}` (PR #153563)
Iris Shi via cfe-commits
cfe-commits at lists.llvm.org
Sun May 10 06:18:58 PDT 2026
https://github.com/el-ev updated https://github.com/llvm/llvm-project/pull/153563
>From c7ad740d6fcb47681212b3b15648ad14e8ffaeb1 Mon Sep 17 00:00:00 2001
From: Iris Shi <0.0 at owo.li>
Date: Thu, 7 May 2026 11:07:50 +0800
Subject: [PATCH] [clang] constexpr `__builtin_elementwise_{max,min}num`
---
clang/docs/ReleaseNotes.rst | 3 +
clang/lib/AST/ByteCode/InterpBuiltin.cpp | 122 +++++++------------
clang/lib/AST/ExprConstant.cpp | 48 +++-----
clang/test/Sema/constant-builtins-vector.cpp | 52 ++++++++
4 files changed, 115 insertions(+), 110 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index c17143e3c0398..6a4d84d403e48 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -237,6 +237,9 @@ Non-comprehensive list of changes in this release
- Deprecated float types support from ``__builtin_elementwise_max`` and
``__builtin_elementwise_min``.
+- ``__builtin_elementwise_maxnum`` and ``__builtin_elementwise_minnum`` can now
+ be used in constant expressions.
+
- Added header ``endian.h`` which contains byte order helpers specified in POSIX
- Added #pragma loop licm(disable) for llvm.loop.licm.disable metadata
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index 11ca93c251380..4090a416250b5 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -2621,13 +2621,29 @@ static bool interp__builtin_elementwise_fp_binop(
Fn,
bool IsScalar = false) {
assert((Call->getNumArgs() == 2) || (Call->getNumArgs() == 3));
+
+ // Single floating-point case.
+ if (!Call->getArg(0)->getType()->isVectorType()) {
+ assert(!Call->getArg(1)->getType()->isVectorType());
+ assert(Call->getNumArgs() == 2);
+ const Floating &RHS = S.Stk.pop<Floating>();
+ const Floating &LHS = S.Stk.pop<Floating>();
+ std::optional<APFloat> Result =
+ Fn(LHS.getAPFloat(), RHS.getAPFloat(), std::nullopt);
+ if (!Result)
+ return false;
+ Floating R = S.allocFloat(Result->getSemantics());
+ R.copy(*Result);
+ S.Stk.push<Floating>(R);
+ return true;
+ }
+
const auto *VT = Call->getArg(0)->getType()->castAs<VectorType>();
assert(VT->getElementType()->isFloatingType());
unsigned NumElems = VT->getNumElements();
// Vector case.
- assert(Call->getArg(0)->getType()->isVectorType() &&
- Call->getArg(1)->getType()->isVectorType());
+ assert(Call->getArg(1)->getType()->isVectorType());
assert(VT->getElementType() ==
Call->getArg(1)->getType()->castAs<VectorType>()->getElementType());
assert(VT->getNumElements() ==
@@ -2819,82 +2835,6 @@ interp__builtin_ia32_pack(InterpState &S, CodePtr, const CallExpr *E,
return true;
}
-static bool interp__builtin_elementwise_maxmin(InterpState &S, CodePtr OpPC,
- const CallExpr *Call,
- unsigned BuiltinID) {
- assert(Call->getNumArgs() == 2);
-
- QualType Arg0Type = Call->getArg(0)->getType();
-
- // TODO: Support floating-point types.
- if (!(Arg0Type->isIntegerType() ||
- (Arg0Type->isVectorType() &&
- Arg0Type->castAs<VectorType>()->getElementType()->isIntegerType())))
- return false;
-
- if (!Arg0Type->isVectorType()) {
- assert(!Call->getArg(1)->getType()->isVectorType());
- APSInt RHS;
- if (!popToAPSInt(S, Call->getArg(1), RHS))
- return false;
- APSInt LHS;
- if (!popToAPSInt(S, Arg0Type, LHS))
- return false;
- APInt Result;
- if (BuiltinID == Builtin::BI__builtin_elementwise_max) {
- Result = std::max(LHS, RHS);
- } else if (BuiltinID == Builtin::BI__builtin_elementwise_min) {
- Result = std::min(LHS, RHS);
- } else {
- llvm_unreachable("Wrong builtin ID");
- }
-
- pushInteger(S, APSInt(Result, !LHS.isSigned()), Call->getType());
- return true;
- }
-
- // Vector case.
- assert(Call->getArg(0)->getType()->isVectorType() &&
- Call->getArg(1)->getType()->isVectorType());
- const auto *VT = Call->getArg(0)->getType()->castAs<VectorType>();
- assert(VT->getElementType() ==
- Call->getArg(1)->getType()->castAs<VectorType>()->getElementType());
- assert(VT->getNumElements() ==
- Call->getArg(1)->getType()->castAs<VectorType>()->getNumElements());
- assert(VT->getElementType()->isIntegralOrEnumerationType());
-
- const Pointer &RHS = S.Stk.pop<Pointer>();
- const Pointer &LHS = S.Stk.pop<Pointer>();
- const Pointer &Dst = S.Stk.peek<Pointer>();
- PrimType ElemT = *S.getContext().classify(VT->getElementType());
- unsigned NumElems = VT->getNumElements();
- for (unsigned I = 0; I != NumElems; ++I) {
- APSInt Elem1;
- APSInt Elem2;
- INT_TYPE_SWITCH_NO_BOOL(ElemT, {
- Elem1 = LHS.elem<T>(I).toAPSInt();
- Elem2 = RHS.elem<T>(I).toAPSInt();
- });
-
- APSInt Result;
- if (BuiltinID == Builtin::BI__builtin_elementwise_max) {
- Result = APSInt(std::max(Elem1, Elem2),
- Call->getType()->isUnsignedIntegerOrEnumerationType());
- } else if (BuiltinID == Builtin::BI__builtin_elementwise_min) {
- Result = APSInt(std::min(Elem1, Elem2),
- Call->getType()->isUnsignedIntegerOrEnumerationType());
- } else {
- llvm_unreachable("Wrong builtin ID");
- }
-
- INT_TYPE_SWITCH_NO_BOOL(ElemT,
- { Dst.elem<T>(I) = static_cast<T>(Result); });
- }
- Dst.initializeAllElements();
-
- return true;
-}
-
static bool interp__builtin_ia32_pmul(
InterpState &S, CodePtr OpPC, const CallExpr *Call,
llvm::function_ref<APInt(const APSInt &, const APSInt &, const APSInt &,
@@ -5452,8 +5392,30 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call,
[](const APSInt &LHS, const APSInt &RHS) { return LHS.rotr(RHS); });
case Builtin::BI__builtin_elementwise_max:
- case Builtin::BI__builtin_elementwise_min:
- return interp__builtin_elementwise_maxmin(S, OpPC, Call, BuiltinID);
+ case Builtin::BI__builtin_elementwise_min: {
+ QualType Arg0Type = Call->getArg(0)->getType();
+ QualType ElemType = Arg0Type->isVectorType()
+ ? Arg0Type->castAs<VectorType>()->getElementType()
+ : Arg0Type;
+ if (!ElemType->isIntegerType())
+ return false;
+ bool IsMax = BuiltinID == Builtin::BI__builtin_elementwise_max;
+ return interp__builtin_elementwise_int_binop(
+ S, OpPC, Call, [IsMax](const APSInt &LHS, const APSInt &RHS) {
+ return IsMax ? std::max(LHS, RHS) : std::min(LHS, RHS);
+ });
+ }
+
+ case Builtin::BI__builtin_elementwise_maxnum:
+ case Builtin::BI__builtin_elementwise_minnum: {
+ bool IsMax = BuiltinID == Builtin::BI__builtin_elementwise_maxnum;
+ return interp__builtin_elementwise_fp_binop(
+ S, OpPC, Call,
+ [IsMax](const APFloat &LHS, const APFloat &RHS,
+ std::optional<APSInt>) -> std::optional<APFloat> {
+ return IsMax ? maxnum(LHS, RHS) : minnum(LHS, RHS);
+ });
+ }
case clang::X86::BI__builtin_ia32_phaddw128:
case clang::X86::BI__builtin_ia32_phaddw256:
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 3f3a80f5b77a3..3fdb4ea649c4c 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -12867,38 +12867,24 @@ bool VectorExprEvaluator::VisitCallExpr(const CallExpr *E) {
case Builtin::BI__builtin_elementwise_max:
case Builtin::BI__builtin_elementwise_min: {
- APValue SourceLHS, SourceRHS;
- if (!EvaluateAsRValue(Info, E->getArg(0), SourceLHS) ||
- !EvaluateAsRValue(Info, E->getArg(1), SourceRHS))
- return false;
-
+ bool IsMax = E->getBuiltinCallee() == Builtin::BI__builtin_elementwise_max;
QualType DestEltTy = E->getType()->castAs<VectorType>()->getElementType();
-
if (!DestEltTy->isIntegerType())
return false;
+ return EvaluateBinOpExpr([IsMax](const APSInt &LHS, const APSInt &RHS) {
+ return IsMax ? std::max(LHS, RHS) : std::min(LHS, RHS);
+ });
+ }
- unsigned SourceLen = SourceLHS.getVectorLength();
- SmallVector<APValue, 4> ResultElements;
- ResultElements.reserve(SourceLen);
-
- for (unsigned EltNum = 0; EltNum < SourceLen; ++EltNum) {
- APSInt LHS = SourceLHS.getVectorElt(EltNum).getInt();
- APSInt RHS = SourceRHS.getVectorElt(EltNum).getInt();
- switch (E->getBuiltinCallee()) {
- case Builtin::BI__builtin_elementwise_max:
- ResultElements.push_back(
- APValue(APSInt(std::max(LHS, RHS),
- DestEltTy->isUnsignedIntegerOrEnumerationType())));
- break;
- case Builtin::BI__builtin_elementwise_min:
- ResultElements.push_back(
- APValue(APSInt(std::min(LHS, RHS),
- DestEltTy->isUnsignedIntegerOrEnumerationType())));
- break;
- }
- }
-
- return Success(APValue(ResultElements.data(), ResultElements.size()), E);
+ case Builtin::BI__builtin_elementwise_maxnum:
+ case Builtin::BI__builtin_elementwise_minnum: {
+ bool IsMax =
+ E->getBuiltinCallee() == Builtin::BI__builtin_elementwise_maxnum;
+ return EvaluateFpBinOpExpr(
+ [IsMax](const APFloat &LHS, const APFloat &RHS,
+ std::optional<APSInt>) -> std::optional<APFloat> {
+ return IsMax ? maxnum(LHS, RHS) : minnum(LHS, RHS);
+ });
}
case X86::BI__builtin_ia32_vpshldd128:
case X86::BI__builtin_ia32_vpshldd256:
@@ -19918,7 +19904,8 @@ bool FloatExprEvaluator::VisitCallExpr(const CallExpr *E) {
case Builtin::BI__builtin_fmaxf:
case Builtin::BI__builtin_fmaxl:
case Builtin::BI__builtin_fmaxf16:
- case Builtin::BI__builtin_fmaxf128: {
+ case Builtin::BI__builtin_fmaxf128:
+ case Builtin::BI__builtin_elementwise_maxnum: {
APFloat RHS(0.);
if (!EvaluateFloat(E->getArg(0), Result, Info) ||
!EvaluateFloat(E->getArg(1), RHS, Info))
@@ -19931,7 +19918,8 @@ bool FloatExprEvaluator::VisitCallExpr(const CallExpr *E) {
case Builtin::BI__builtin_fminf:
case Builtin::BI__builtin_fminl:
case Builtin::BI__builtin_fminf16:
- case Builtin::BI__builtin_fminf128: {
+ case Builtin::BI__builtin_fminf128:
+ case Builtin::BI__builtin_elementwise_minnum: {
APFloat RHS(0.);
if (!EvaluateFloat(E->getArg(0), Result, Info) ||
!EvaluateFloat(E->getArg(1), RHS, Info))
diff --git a/clang/test/Sema/constant-builtins-vector.cpp b/clang/test/Sema/constant-builtins-vector.cpp
index 56919ade81e43..ae0630893d78a 100644
--- a/clang/test/Sema/constant-builtins-vector.cpp
+++ b/clang/test/Sema/constant-builtins-vector.cpp
@@ -890,6 +890,58 @@ static_assert(__builtin_bit_cast(unsigned, __builtin_elementwise_min((vector4cha
static_assert(__builtin_bit_cast(unsigned, __builtin_elementwise_min((vector4uchar){1, 2, 3, 4}, (vector4uchar){4, 3, 2, 1})) == 0x01020201U);
static_assert(__builtin_bit_cast(unsigned long long, __builtin_elementwise_min((vector4short){1, -2, 3, -4}, (vector4short){4, -3, 2, -1})) == (LITTLE_END ? 0xFFFC0002FFFD0001 : 0x0001FFFD0002FFFC));
+static_assert(__builtin_elementwise_maxnum(1.0f, 2.0f) == 2.0f);
+static_assert(__builtin_elementwise_maxnum(-1.0f, 1.0f) == 1.0f);
+static_assert(__builtin_elementwise_maxnum(1.0, 2.0) == 2.0);
+static_assert(__builtin_elementwise_maxnum(-1.0, 1.0) == 1.0);
+static_assert(__builtin_elementwise_maxnum(__builtin_nanf(""), 1.0f) == 1.0f);
+static_assert(__builtin_elementwise_maxnum(1.0f, __builtin_nanf("")) == 1.0f);
+static_assert(__builtin_isnan(__builtin_elementwise_maxnum(__builtin_nanf(""), __builtin_nanf(""))));
+static_assert(__builtin_elementwise_maxnum(__builtin_inff(), 1.0f) == __builtin_inff());
+static_assert(__builtin_elementwise_maxnum(-__builtin_inff(), 1.0f) == 1.0f);
+
+static_assert(__builtin_elementwise_minnum(1.0f, 2.0f) == 1.0f);
+static_assert(__builtin_elementwise_minnum(-1.0f, 1.0f) == -1.0f);
+static_assert(__builtin_elementwise_minnum(1.0, 2.0) == 1.0);
+static_assert(__builtin_elementwise_minnum(-1.0, 1.0) == -1.0);
+static_assert(__builtin_elementwise_minnum(__builtin_nanf(""), 1.0f) == 1.0f);
+static_assert(__builtin_elementwise_minnum(1.0f, __builtin_nanf("")) == 1.0f);
+static_assert(__builtin_isnan(__builtin_elementwise_minnum(__builtin_nanf(""), __builtin_nanf(""))));
+static_assert(__builtin_elementwise_minnum(__builtin_inff(), 1.0f) == 1.0f);
+static_assert(__builtin_elementwise_minnum(-__builtin_inff(), 1.0f) == -__builtin_inff());
+
+constexpr vector4float maxf_vec =
+ __builtin_elementwise_maxnum((vector4float){1.0f, -2.0f, 3.0f, -4.0f},
+ (vector4float){4.0f, -3.0f, 2.0f, -1.0f});
+static_assert(maxf_vec[0] == 4.0f && maxf_vec[1] == -2.0f &&
+ maxf_vec[2] == 3.0f && maxf_vec[3] == -1.0f);
+constexpr vector4float minf_vec =
+ __builtin_elementwise_minnum((vector4float){1.0f, -2.0f, 3.0f, -4.0f},
+ (vector4float){4.0f, -3.0f, 2.0f, -1.0f});
+static_assert(minf_vec[0] == 1.0f && minf_vec[1] == -3.0f &&
+ minf_vec[2] == 2.0f && minf_vec[3] == -4.0f);
+constexpr vector4double maxd_vec =
+ __builtin_elementwise_maxnum((vector4double){1.0, -2.0, 3.0, -4.0},
+ (vector4double){4.0, -3.0, 2.0, -1.0});
+static_assert(maxd_vec[0] == 4.0 && maxd_vec[1] == -2.0 &&
+ maxd_vec[2] == 3.0 && maxd_vec[3] == -1.0);
+constexpr vector4double mind_vec =
+ __builtin_elementwise_minnum((vector4double){1.0, -2.0, 3.0, -4.0},
+ (vector4double){4.0, -3.0, 2.0, -1.0});
+static_assert(mind_vec[0] == 1.0 && mind_vec[1] == -3.0 &&
+ mind_vec[2] == 2.0 && mind_vec[3] == -4.0);
+
+constexpr vector4float maxf_nan_inf = __builtin_elementwise_maxnum(
+ (vector4float){__builtin_nanf(""), 1.0f, __builtin_inff(), 1.0f},
+ (vector4float){1.0f, __builtin_nanf(""), 1.0f, -__builtin_inff()});
+static_assert(maxf_nan_inf[0] == 1.0f && maxf_nan_inf[1] == 1.0f &&
+ maxf_nan_inf[2] == __builtin_inff() && maxf_nan_inf[3] == 1.0f);
+constexpr vector4float minf_nan_inf = __builtin_elementwise_minnum(
+ (vector4float){__builtin_nanf(""), 1.0f, __builtin_inff(), 1.0f},
+ (vector4float){1.0f, __builtin_nanf(""), 1.0f, -__builtin_inff()});
+static_assert(minf_nan_inf[0] == 1.0f && minf_nan_inf[1] == 1.0f &&
+ minf_nan_inf[2] == 1.0f && minf_nan_inf[3] == -__builtin_inff());
+
static_assert(__builtin_elementwise_abs(10) == 10);
static_assert(__builtin_elementwise_abs(-10) == 10);
static_assert(__builtin_bit_cast(unsigned, __builtin_elementwise_abs((vector4char){-1, -2, -3, 4})) == (LITTLE_END ? 0x04030201 : 0x01020304));
More information about the cfe-commits
mailing list