[clang] [clang] constexpr `__builtin_elementwise_{max, min}` (PR #153563)
Iris Shi via cfe-commits
cfe-commits at lists.llvm.org
Wed May 6 20:08:49 PDT 2026
https://github.com/el-ev updated https://github.com/llvm/llvm-project/pull/153563
>From 23c9920efdb4471fd81e78600c23674c54fa2f1f 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}`
---
clang/docs/ReleaseNotes.rst | 3 +
clang/lib/AST/ByteCode/InterpBuiltin.cpp | 118 ++++++-------------
clang/lib/AST/ExprConstant.cpp | 49 +++-----
clang/test/Sema/constant-builtins-vector.cpp | 63 +++++++++-
4 files changed, 114 insertions(+), 119 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index cb19b80b7e994..de042ae1cc651 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -230,6 +230,9 @@ Non-comprehensive list of changes in this release
- Deprecated float types support from ``__builtin_elementwise_max`` and
``__builtin_elementwise_min``.
+- ``__builtin_elementwise_max`` and ``__builtin_elementwise_min`` can now be
+ used in constant expressions for floating-point arguments.
+
- 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..e025712662582 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,26 @@ 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;
+ bool IsMax = BuiltinID == Builtin::BI__builtin_elementwise_max;
+ if (ElemType->isIntegerType())
+ 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);
+ });
+ if (ElemType->isRealFloatingType())
+ 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);
+ });
+ return false;
+ }
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..b800a84a2eaad 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -12867,38 +12867,19 @@ 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;
-
- 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);
+ if (DestEltTy->isIntegerType())
+ return EvaluateBinOpExpr([IsMax](const APSInt &LHS, const APSInt &RHS) {
+ return IsMax ? std::max(LHS, RHS) : std::min(LHS, RHS);
+ });
+ if (DestEltTy->isRealFloatingType())
+ return EvaluateFpBinOpExpr(
+ [IsMax](const APFloat &LHS, const APFloat &RHS,
+ std::optional<APSInt>) -> std::optional<APFloat> {
+ return IsMax ? maxnum(LHS, RHS) : minnum(LHS, RHS);
+ });
+ return false;
}
case X86::BI__builtin_ia32_vpshldd128:
case X86::BI__builtin_ia32_vpshldd256:
@@ -19918,7 +19899,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_max: {
APFloat RHS(0.);
if (!EvaluateFloat(E->getArg(0), Result, Info) ||
!EvaluateFloat(E->getArg(1), RHS, Info))
@@ -19931,7 +19913,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_min: {
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..58bb7abceca4e 100644
--- a/clang/test/Sema/constant-builtins-vector.cpp
+++ b/clang/test/Sema/constant-builtins-vector.cpp
@@ -1,10 +1,10 @@
-// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -Wno-bit-int-extension %s
-// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -Wno-bit-int-extension -triple ppc64-unknown-linux %s
-// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -Wno-bit-int-extension -triple ppc64le-unknown-linux %s
+// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -Wno-bit-int-extension -Wno-deprecated-builtins %s
+// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -Wno-bit-int-extension -Wno-deprecated-builtins -triple ppc64-unknown-linux %s
+// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -Wno-bit-int-extension -Wno-deprecated-builtins -triple ppc64le-unknown-linux %s
-// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -Wno-bit-int-extension %s -fexperimental-new-constant-interpreter
-// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -Wno-bit-int-extension -triple ppc64-unknown-linux %s -fexperimental-new-constant-interpreter
-// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -Wno-bit-int-extension -triple ppc64le-unknown-linux %s -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -Wno-bit-int-extension -Wno-deprecated-builtins %s -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -Wno-bit-int-extension -Wno-deprecated-builtins -triple ppc64-unknown-linux %s -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -Wno-bit-int-extension -Wno-deprecated-builtins -triple ppc64le-unknown-linux %s -fexperimental-new-constant-interpreter
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define LITTLE_END 1
@@ -878,6 +878,16 @@ static_assert(__builtin_elementwise_max(1, 2) == 2);
static_assert(__builtin_elementwise_max(-1, 1) == 1);
static_assert(__builtin_elementwise_max(1U, 2U) == 2U);
static_assert(__builtin_elementwise_max(~0U, 0U) == ~0U);
+static_assert(__builtin_elementwise_max(1.0f, 2.0f) == 2.0f);
+static_assert(__builtin_elementwise_max(-1.0f, 1.0f) == 1.0f);
+static_assert(__builtin_elementwise_max(1.0, 2.0) == 2.0);
+static_assert(__builtin_elementwise_max(-1.0, 1.0) == 1.0);
+// maxnum returns the non-NaN argument when one operand is NaN.
+static_assert(__builtin_elementwise_max(__builtin_nanf(""), 1.0f) == 1.0f);
+static_assert(__builtin_elementwise_max(1.0f, __builtin_nanf("")) == 1.0f);
+static_assert(__builtin_isnan(__builtin_elementwise_max(__builtin_nanf(""), __builtin_nanf(""))));
+static_assert(__builtin_elementwise_max(__builtin_inff(), 1.0f) == __builtin_inff());
+static_assert(__builtin_elementwise_max(-__builtin_inff(), 1.0f) == 1.0f);
static_assert(__builtin_bit_cast(unsigned, __builtin_elementwise_max((vector4char){1, -2, 3, -4}, (vector4char){4, -3, 2, -1})) == (LITTLE_END ? 0xFF03FE04 : 0x04FE03FF ));
static_assert(__builtin_bit_cast(unsigned, __builtin_elementwise_max((vector4uchar){1, 2, 3, 4}, (vector4uchar){4, 3, 2, 1})) == 0x04030304U);
static_assert(__builtin_bit_cast(unsigned long long, __builtin_elementwise_max((vector4short){1, -2, 3, -4}, (vector4short){4, -3, 2, -1})) == (LITTLE_END ? 0xFFFF0003FFFE0004 : 0x0004FFFE0003FFFF));
@@ -886,10 +896,51 @@ static_assert(__builtin_elementwise_min(1, 2) == 1);
static_assert(__builtin_elementwise_min(-1, 1) == -1);
static_assert(__builtin_elementwise_min(1U, 2U) == 1U);
static_assert(__builtin_elementwise_min(~0U, 0U) == 0U);
+static_assert(__builtin_elementwise_min(1.0f, 2.0f) == 1.0f);
+static_assert(__builtin_elementwise_min(-1.0f, 1.0f) == -1.0f);
+static_assert(__builtin_elementwise_min(1.0, 2.0) == 1.0);
+static_assert(__builtin_elementwise_min(-1.0, 1.0) == -1.0);
+static_assert(__builtin_elementwise_min(__builtin_nanf(""), 1.0f) == 1.0f);
+static_assert(__builtin_elementwise_min(1.0f, __builtin_nanf("")) == 1.0f);
+static_assert(__builtin_isnan(__builtin_elementwise_min(__builtin_nanf(""), __builtin_nanf(""))));
+static_assert(__builtin_elementwise_min(__builtin_inff(), 1.0f) == 1.0f);
+static_assert(__builtin_elementwise_min(-__builtin_inff(), 1.0f) == -__builtin_inff());
static_assert(__builtin_bit_cast(unsigned, __builtin_elementwise_min((vector4char){1, -2, 3, -4}, (vector4char){4, -3, 2, -1})) == (LITTLE_END ? 0xFC02FD01 : 0x01FD02FC));
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));
+constexpr vector4float maxf_vec =
+ __builtin_elementwise_max((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_min((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_max((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_min((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_max(
+ (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_min(
+ (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