[clang] [Clang][ExprConstant] Normalize aux target builtin IDs before dispatch (PR #201805)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Jun 21 23:07:55 PDT 2026
https://github.com/yaohuihan-iluvatar updated https://github.com/llvm/llvm-project/pull/201805
>From 22ef91a395970683003d5e22f728fe4297ce2123 Mon Sep 17 00:00:00 2001
From: "yaohui.han" <yaohui.han at iluvatar.com>
Date: Fri, 5 Jun 2026 17:58:06 +0800
Subject: [PATCH 1/5] [Clang][ExprConstant] Normalize aux target builtin IDs
before dispatch
When compiling for a device target that has an auxiliary host target (for
example OpenMP or CUDA offload), builtins of the auxiliary target are
registered with IDs shifted past the primary target's builtins. The constant
evaluator passed the raw ID straight into the target-specific evaluation
switches in the Pointer/Int/Vector/Float expression evaluators, so a shifted
auxiliary ID could fall through to an unrelated builtin's case (or miss its
case entirely) and fail to fold.
Add a getConstantEvaluatedBuiltinID() helper that translates auxiliary
builtin IDs back to their canonical target IDs via
BuiltinInfo::getAuxBuiltinID(), and use it before dispatching in each of
those evaluators.
---
clang/lib/AST/ExprConstant.cpp | 54 +++++++++++--------
.../Sema/aux-target-builtin-constexpr.cpp | 20 +++++++
2 files changed, 51 insertions(+), 23 deletions(-)
create mode 100644 clang/test/Sema/aux-target-builtin-constexpr.cpp
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 220a7a22c6992..92a24a877cfe6 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -8457,6 +8457,13 @@ class ExprEvaluatorBase
Info.Ctx.BuiltinInfo.isConstantEvaluated(BuiltinOp);
}
+ unsigned getConstantEvaluatedBuiltinID(const CallExpr *E) {
+ unsigned BuiltinOp = E->getBuiltinCallee();
+ if (Info.Ctx.BuiltinInfo.isAuxBuiltinID(BuiltinOp))
+ BuiltinOp = Info.Ctx.BuiltinInfo.getAuxBuiltinID(BuiltinOp);
+ return BuiltinOp;
+ }
+
public:
ExprEvaluatorBase(EvalInfo &Info) : Info(Info) {}
@@ -10343,7 +10350,7 @@ bool PointerExprEvaluator::visitNonBuiltinCallExpr(const CallExpr *E) {
bool PointerExprEvaluator::VisitCallExpr(const CallExpr *E) {
if (!IsConstantEvaluatedBuiltinCall(E))
return visitNonBuiltinCallExpr(E);
- return VisitBuiltinCallExpr(E, E->getBuiltinCallee());
+ return VisitBuiltinCallExpr(E, getConstantEvaluatedBuiltinID(E));
}
// Determine if T is a character type for which we guarantee that
@@ -12280,6 +12287,8 @@ bool VectorExprEvaluator::VisitCallExpr(const CallExpr *E) {
if (!IsConstantEvaluatedBuiltinCall(E))
return ExprEvaluatorBaseTy::VisitCallExpr(E);
+ unsigned BuiltinOp = getConstantEvaluatedBuiltinID(E);
+
auto EvaluateBinOpExpr =
[&](llvm::function_ref<APInt(const APSInt &, const APSInt &)> Fn) {
APValue SourceLHS, SourceRHS;
@@ -12446,7 +12455,7 @@ bool VectorExprEvaluator::VisitCallExpr(const CallExpr *E) {
return Success(APValue(Result.data(), Result.size()), E);
};
- switch (E->getBuiltinCallee()) {
+ switch (BuiltinOp) {
default:
return false;
case Builtin::BI__builtin_elementwise_popcount:
@@ -12462,7 +12471,7 @@ bool VectorExprEvaluator::VisitCallExpr(const CallExpr *E) {
for (unsigned EltNum = 0; EltNum < SourceLen; ++EltNum) {
APSInt Elt = Source.getVectorElt(EltNum).getInt();
- switch (E->getBuiltinCallee()) {
+ switch (BuiltinOp) {
case Builtin::BI__builtin_elementwise_popcount:
ResultElements.push_back(APValue(
APSInt(APInt(Info.Ctx.getIntWidth(DestEltTy), Elt.popcount()),
@@ -12654,7 +12663,7 @@ bool VectorExprEvaluator::VisitCallExpr(const CallExpr *E) {
const APSInt &HiRHS = SourceRHS.getVectorElt(EltNum + 1).getInt();
unsigned BitWidth = 2 * LoLHS.getBitWidth();
- switch (E->getBuiltinCallee()) {
+ switch (BuiltinOp) {
case clang::X86::BI__builtin_ia32_pmaddubsw128:
case clang::X86::BI__builtin_ia32_pmaddubsw256:
case clang::X86::BI__builtin_ia32_pmaddubsw512:
@@ -12866,7 +12875,7 @@ bool VectorExprEvaluator::VisitCallExpr(const CallExpr *E) {
APSInt LHS = SourceLHS.getVectorElt(EltNum).getInt();
APSInt RHS = SourceRHS.getVectorElt(EltNum).getInt();
- switch (E->getBuiltinCallee()) {
+ switch (BuiltinOp) {
case clang::X86::BI__builtin_ia32_pmuludq128:
case clang::X86::BI__builtin_ia32_pmuludq256:
case clang::X86::BI__builtin_ia32_pmuludq512:
@@ -12973,7 +12982,7 @@ bool VectorExprEvaluator::VisitCallExpr(const CallExpr *E) {
for (unsigned EltNum = 0; EltNum < SourceLen; ++EltNum) {
APSInt LHS = SourceLHS.getVectorElt(EltNum).getInt();
APSInt RHS = SourceRHS.getVectorElt(EltNum).getInt();
- switch (E->getBuiltinCallee()) {
+ switch (BuiltinOp) {
case Builtin::BI__builtin_elementwise_max:
ResultElements.push_back(
APValue(APSInt(std::max(LHS, RHS),
@@ -13331,7 +13340,7 @@ bool VectorExprEvaluator::VisitCallExpr(const CallExpr *E) {
case X86::BI__builtin_ia32_cvtpd2ps_mask:
case X86::BI__builtin_ia32_cvtpd2ps512_mask: {
- const auto BuiltinID = E->getBuiltinCallee();
+ const auto BuiltinID = BuiltinOp;
bool IsMasked = (BuiltinID == X86::BI__builtin_ia32_cvtpd2ps_mask ||
BuiltinID == X86::BI__builtin_ia32_cvtpd2ps512_mask);
@@ -13867,14 +13876,14 @@ bool VectorExprEvaluator::VisitCallExpr(const CallExpr *E) {
// Without a fallback, a zero element is undefined
if (!Fallback) {
Info.FFDiag(E, diag::note_constexpr_countzeroes_zero)
- << /*IsTrailing=*/(E->getBuiltinCallee() ==
+ << /*IsTrailing=*/(BuiltinOp ==
Builtin::BI__builtin_elementwise_ctzg);
return false;
}
ResultElements.push_back(Fallback->getVectorElt(EltNum));
continue;
}
- switch (E->getBuiltinCallee()) {
+ switch (BuiltinOp) {
case Builtin::BI__builtin_elementwise_clzg:
ResultElements.push_back(APValue(
APSInt(APInt(Info.Ctx.getIntWidth(DestEltTy), LHS.countl_zero()),
@@ -13944,7 +13953,7 @@ bool VectorExprEvaluator::VisitCallExpr(const CallExpr *E) {
for (unsigned I = 0; I != EltsPerLane; I += 2) {
APSInt LHSA = SourceLHS.getVectorElt(LaneStart + I).getInt();
APSInt LHSB = SourceLHS.getVectorElt(LaneStart + I + 1).getInt();
- switch (E->getBuiltinCallee()) {
+ switch (BuiltinOp) {
case clang::X86::BI__builtin_ia32_phaddw128:
case clang::X86::BI__builtin_ia32_phaddw256:
case clang::X86::BI__builtin_ia32_phaddd128:
@@ -13978,7 +13987,7 @@ bool VectorExprEvaluator::VisitCallExpr(const CallExpr *E) {
for (unsigned I = 0; I != EltsPerLane; I += 2) {
APSInt RHSA = SourceRHS.getVectorElt(LaneStart + I).getInt();
APSInt RHSB = SourceRHS.getVectorElt(LaneStart + I + 1).getInt();
- switch (E->getBuiltinCallee()) {
+ switch (BuiltinOp) {
case clang::X86::BI__builtin_ia32_phaddw128:
case clang::X86::BI__builtin_ia32_phaddw256:
case clang::X86::BI__builtin_ia32_phaddd128:
@@ -14038,7 +14047,7 @@ bool VectorExprEvaluator::VisitCallExpr(const CallExpr *E) {
for (unsigned I = 0; I != HalfElemsPerLane; ++I) {
APFloat LHSA = SourceLHS.getVectorElt(L + (2 * I) + 0).getFloat();
APFloat LHSB = SourceLHS.getVectorElt(L + (2 * I) + 1).getFloat();
- switch (E->getBuiltinCallee()) {
+ switch (BuiltinOp) {
case clang::X86::BI__builtin_ia32_haddpd:
case clang::X86::BI__builtin_ia32_haddps:
case clang::X86::BI__builtin_ia32_haddps256:
@@ -14057,7 +14066,7 @@ bool VectorExprEvaluator::VisitCallExpr(const CallExpr *E) {
for (unsigned I = 0; I != HalfElemsPerLane; ++I) {
APFloat RHSA = SourceRHS.getVectorElt(L + (2 * I) + 0).getFloat();
APFloat RHSB = SourceRHS.getVectorElt(L + (2 * I) + 1).getFloat();
- switch (E->getBuiltinCallee()) {
+ switch (BuiltinOp) {
case clang::X86::BI__builtin_ia32_haddpd:
case clang::X86::BI__builtin_ia32_haddps:
case clang::X86::BI__builtin_ia32_haddps256:
@@ -14185,7 +14194,7 @@ bool VectorExprEvaluator::VisitCallExpr(const CallExpr *E) {
const APSInt &Hi = SourceHi.getVectorElt(EltNum).getInt();
const APSInt &Lo = SourceLo.getVectorElt(EltNum).getInt();
const APSInt &Shift = SourceShift.getVectorElt(EltNum).getInt();
- switch (E->getBuiltinCallee()) {
+ switch (BuiltinOp) {
case Builtin::BI__builtin_elementwise_fshl:
ResultElements.push_back(APValue(
APSInt(llvm::APIntOps::fshl(Hi, Lo, Shift), Hi.isUnsigned())));
@@ -14268,7 +14277,7 @@ bool VectorExprEvaluator::VisitCallExpr(const CallExpr *E) {
assert(X.getVectorLength() == A.getVectorLength());
bool IsInverse = false;
- switch (E->getBuiltinCallee()) {
+ switch (BuiltinOp) {
case X86::BI__builtin_ia32_vgf2p8affineinvqb_v16qi:
case X86::BI__builtin_ia32_vgf2p8affineinvqb_v32qi:
case X86::BI__builtin_ia32_vgf2p8affineinvqb_v64qi: {
@@ -14672,12 +14681,9 @@ bool VectorExprEvaluator::VisitCallExpr(const CallExpr *E) {
case clang::X86::BI__builtin_ia32_maxsd_round_mask:
case clang::X86::BI__builtin_ia32_maxss_round_mask:
case clang::X86::BI__builtin_ia32_maxsh_round_mask: {
- bool IsMin =
- E->getBuiltinCallee() ==
- clang::X86::BI__builtin_ia32_minsd_round_mask ||
- E->getBuiltinCallee() ==
- clang::X86::BI__builtin_ia32_minss_round_mask ||
- E->getBuiltinCallee() == clang::X86::BI__builtin_ia32_minsh_round_mask;
+ bool IsMin = BuiltinOp == clang::X86::BI__builtin_ia32_minsd_round_mask ||
+ BuiltinOp == clang::X86::BI__builtin_ia32_minss_round_mask ||
+ BuiltinOp == clang::X86::BI__builtin_ia32_minsh_round_mask;
return EvaluateScalarFpRoundMaskBinOp(
[IsMin](const APFloat &A, const APFloat &B,
std::optional<APSInt> RoundingMode) -> std::optional<APFloat> {
@@ -16417,7 +16423,7 @@ tryEvaluateBuiltinObjectSize(const Expr *E, unsigned Type, EvalInfo &Info,
bool IntExprEvaluator::VisitCallExpr(const CallExpr *E) {
if (!IsConstantEvaluatedBuiltinCall(E))
return ExprEvaluatorBaseTy::VisitCallExpr(E);
- return VisitBuiltinCallExpr(E, E->getBuiltinCallee());
+ return VisitBuiltinCallExpr(E, getConstantEvaluatedBuiltinID(E));
}
static bool getBuiltinAlignArguments(const CallExpr *E, EvalInfo &Info,
@@ -19990,7 +19996,9 @@ bool FloatExprEvaluator::VisitCallExpr(const CallExpr *E) {
if (!IsConstantEvaluatedBuiltinCall(E))
return ExprEvaluatorBaseTy::VisitCallExpr(E);
- switch (E->getBuiltinCallee()) {
+ unsigned BuiltinOp = getConstantEvaluatedBuiltinID(E);
+
+ switch (BuiltinOp) {
default:
return false;
diff --git a/clang/test/Sema/aux-target-builtin-constexpr.cpp b/clang/test/Sema/aux-target-builtin-constexpr.cpp
new file mode 100644
index 0000000000000..38d2f02343b49
--- /dev/null
+++ b/clang/test/Sema/aux-target-builtin-constexpr.cpp
@@ -0,0 +1,20 @@
+// RUN: %clang_cc1 -fopenmp -fopenmp-is-target-device \
+// RUN: -triple aarch64 -aux-triple x86_64-unknown-linux-gnu \
+// RUN: -aux-target-feature +avx512f -std=c++17 -fsyntax-only -verify %s
+
+// expected-no-diagnostics
+
+// When compiling for a device target (here aarch64) with an auxiliary x86
+// target, x86 builtins are registered as auxiliary builtins whose IDs are
+// shifted past the primary target's builtins. The constant evaluator must
+// translate such aux IDs back to their canonical X86::BI* values before
+// dispatching the target-specific constexpr switches. Without that
+// normalization the shifted ID misses its intended case and the call fails to
+// fold ("not an integral constant expression").
+
+typedef short __v8hi __attribute__((__vector_size__(16)));
+
+constexpr __v8hi V = {0, 10, 20, 30, 40, 50, 60, 70};
+
+static_assert(__builtin_ia32_vec_ext_v8hi(V, 3) == 30);
+static_assert(__builtin_ia32_vec_ext_v8hi(V, 7) == 70);
>From 75b81f4056df6f63b92b3c206d4baf7fa0e3d403 Mon Sep 17 00:00:00 2001
From: "yaohui.han" <yaohui.han at iluvatar.com>
Date: Wed, 17 Jun 2026 17:18:00 +0800
Subject: [PATCH 2/5] [Clang][ExprConstant] Add aux target check in
getConstantEvaluatedBuiltinID
---
clang/lib/AST/ExprConstant.cpp | 27 ++++++++++++++++++++++++++-
1 file changed, 26 insertions(+), 1 deletion(-)
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 92a24a877cfe6..d45ee6c9da54e 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -8459,8 +8459,33 @@ class ExprEvaluatorBase
unsigned getConstantEvaluatedBuiltinID(const CallExpr *E) {
unsigned BuiltinOp = E->getBuiltinCallee();
- if (Info.Ctx.BuiltinInfo.isAuxBuiltinID(BuiltinOp))
+
+ // Target-independent builtins have the same ID regardless of the target, so
+ // they can be dispatched as-is.
+ if (BuiltinOp < Builtin::FirstTSBuiltin)
+ return BuiltinOp;
+
+ // Target-specific builtin IDs of different targets overlap (each target
+ // numbers its builtins from Builtin::FirstTSBuiltin), and the
+ // target-specific constant-evaluation cases dispatched on this ID are
+ // X86-only. A target builtin ID may therefore only be treated as an X86
+ // builtin when the target that owns it is actually x86; otherwise the
+ // overlapping ID could be misinterpreted as an unrelated X86 builtin.
+ // Determine the owning target (translating an auxiliary ID back to its
+ // canonical value) and only keep the ID when that target is x86; for any
+ // other target return 0 so dispatch falls through to the default case
+ // ("cannot constant-fold") instead of matching an X86 case by accident.
+ const TargetInfo *OwningTarget;
+ if (Info.Ctx.BuiltinInfo.isAuxBuiltinID(BuiltinOp)) {
+ OwningTarget = Info.Ctx.getAuxTargetInfo();
BuiltinOp = Info.Ctx.BuiltinInfo.getAuxBuiltinID(BuiltinOp);
+ } else {
+ OwningTarget = &Info.Ctx.getTargetInfo();
+ }
+
+ if (!OwningTarget || !OwningTarget->getTriple().isX86())
+ return 0;
+
return BuiltinOp;
}
>From d780d1e5f74bfa315b85e80bf665933205754959 Mon Sep 17 00:00:00 2001
From: "yaohui.han" <yaohui.han at iluvatar.com>
Date: Thu, 18 Jun 2026 14:53:27 +0800
Subject: [PATCH 3/5] [Clang][ExprConstant] Add target switch in
getConstantEvaluatedBuiltinID
Now dispatch the ID for x86, waiting for other targets work.
---
clang/lib/AST/ExprConstant.cpp | 38 +++++++++++++++++++++++-----------
1 file changed, 26 insertions(+), 12 deletions(-)
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index d45ee6c9da54e..2862ff23c29e9 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -8457,6 +8457,17 @@ class ExprEvaluatorBase
Info.Ctx.BuiltinInfo.isConstantEvaluated(BuiltinOp);
}
+ // Return the target builtin ID to dispatch on in the constant evaluator's
+ // target-specific cases, or 0 if \p E does not name a builtin those cases
+ // should handle.
+ //
+ // Target-specific builtin IDs of different targets overlap (each target
+ // numbers its builtins from Builtin::FirstTSBuiltin), so a target builtin ID
+ // is only meaningful for the target that owns it. Determine the owning target
+ // (translating an auxiliary ID back to its canonical value) and only return
+ // the ID for architectures whose target-specific builtins the constant
+ // evaluator knows how to fold; otherwise an overlapping ID could be
+ // misinterpreted as an unrelated builtin of another target.
unsigned getConstantEvaluatedBuiltinID(const CallExpr *E) {
unsigned BuiltinOp = E->getBuiltinCallee();
@@ -8465,16 +8476,8 @@ class ExprEvaluatorBase
if (BuiltinOp < Builtin::FirstTSBuiltin)
return BuiltinOp;
- // Target-specific builtin IDs of different targets overlap (each target
- // numbers its builtins from Builtin::FirstTSBuiltin), and the
- // target-specific constant-evaluation cases dispatched on this ID are
- // X86-only. A target builtin ID may therefore only be treated as an X86
- // builtin when the target that owns it is actually x86; otherwise the
- // overlapping ID could be misinterpreted as an unrelated X86 builtin.
- // Determine the owning target (translating an auxiliary ID back to its
- // canonical value) and only keep the ID when that target is x86; for any
- // other target return 0 so dispatch falls through to the default case
- // ("cannot constant-fold") instead of matching an X86 case by accident.
+ // Determine the target that owns this builtin, translating an auxiliary ID
+ // back to its canonical value.
const TargetInfo *OwningTarget;
if (Info.Ctx.BuiltinInfo.isAuxBuiltinID(BuiltinOp)) {
OwningTarget = Info.Ctx.getAuxTargetInfo();
@@ -8483,10 +8486,21 @@ class ExprEvaluatorBase
OwningTarget = &Info.Ctx.getTargetInfo();
}
- if (!OwningTarget || !OwningTarget->getTriple().isX86())
+ if (!OwningTarget)
return 0;
- return BuiltinOp;
+ // Only dispatch the ID for architectures whose target-specific builtins the
+ // constant evaluator can fold. x86 and x86_64 share a single builtin set and
+ // are the only such architectures today; as other targets gain
+ // constant-evaluation support for their builtins, add the corresponding
+ // cases here so their IDs are dispatched too.
+ switch (OwningTarget->getTriple().getArch()) {
+ case llvm::Triple::x86:
+ case llvm::Triple::x86_64:
+ return BuiltinOp;
+ default:
+ return 0;
+ }
}
public:
>From 3913d2a7fd8c140f4f5f48dfd1baffc2db5f837e Mon Sep 17 00:00:00 2001
From: "yaohui.han" <yaohui.han at iluvatar.com>
Date: Thu, 18 Jun 2026 16:49:35 +0800
Subject: [PATCH 4/5] [clang][bytecode] Share getConstantEvaluatedBuiltinID
with the bytecode interpreter
Make getConstantEvaluatedBuiltinID a free function in ExprConstShared.h and use
it in the bytecode interpreter so auxiliary x86 builtin IDs are normalized to
their canonical X86::BI* value before dispatch. As the baked ID is now
canonical, InterpretBuiltin's isConstantEvaluated gate uses the raw builtin ID.
---
clang/lib/AST/ByteCode/Compiler.cpp | 7 +-
clang/lib/AST/ByteCode/Interp.cpp | 3 +-
clang/lib/AST/ByteCode/Interp.h | 7 +-
clang/lib/AST/ByteCode/InterpBuiltin.cpp | 11 +-
clang/lib/AST/ExprConstShared.h | 9 ++
clang/lib/AST/ExprConstant.cpp | 101 +++++++++---------
.../Sema/aux-target-builtin-constexpr.cpp | 4 +
7 files changed, 85 insertions(+), 57 deletions(-)
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 9444eeb0c2ad3..f36313ab15716 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "Compiler.h"
+#include "../ExprConstShared.h"
#include "ByteCodeEmitter.h"
#include "Context.h"
#include "FixedPoint.h"
@@ -2805,7 +2806,8 @@ bool Compiler<Emitter>::VisitAbstractConditionalOperator(
bool IsBcpCall = false;
if (const auto *CE = dyn_cast<CallExpr>(Condition->IgnoreParenCasts());
- CE && CE->getBuiltinCallee() == Builtin::BI__builtin_constant_p) {
+ CE && getConstantEvaluatedBuiltinID(Ctx.getASTContext(), CE) ==
+ Builtin::BI__builtin_constant_p) {
IsBcpCall = true;
}
@@ -5708,7 +5710,8 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
const FunctionDecl *FuncDecl = E->getDirectCallee();
if (FuncDecl) {
- if (unsigned BuiltinID = FuncDecl->getBuiltinID())
+ if (unsigned BuiltinID =
+ getConstantEvaluatedBuiltinID(Ctx.getASTContext(), E))
return VisitBuiltinCallExpr(E, BuiltinID);
// Calls to replaceable operator new/operator delete.
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index b022d71ae1e49..8f3613ca726fa 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -1230,7 +1230,8 @@ bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Expr *Source,
return true;
// operator new.
if (const auto *CE = dyn_cast_if_present<CallExpr>(Source);
- CE && CE->getBuiltinCallee() == Builtin::BI__builtin_operator_new)
+ CE && getConstantEvaluatedBuiltinID(S.getASTContext(), CE) ==
+ Builtin::BI__builtin_operator_new)
return true;
// std::allocator.allocate() call
if (const auto *MCE = dyn_cast_if_present<CXXMemberCallExpr>(Source);
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index d1836b6b739b2..b4f4d65682496 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -1281,8 +1281,9 @@ inline bool CmpHelper<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
return true;
}
-static inline bool IsOpaqueConstantCall(const CallExpr *E) {
- unsigned Builtin = E->getBuiltinCallee();
+static inline bool IsOpaqueConstantCall(const ASTContext &Ctx,
+ const CallExpr *E) {
+ unsigned Builtin = getConstantEvaluatedBuiltinID(Ctx, E);
return (Builtin == Builtin::BI__builtin___CFStringMakeConstantString ||
Builtin == Builtin::BI__builtin___NSStringMakeConstantString ||
Builtin == Builtin::BI__builtin_ptrauth_sign_constant ||
@@ -1385,7 +1386,7 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
return false;
}
if (const auto *CE = dyn_cast<CallExpr>(E);
- CE && IsOpaqueConstantCall(CE)) {
+ CE && IsOpaqueConstantCall(S.getASTContext(), CE)) {
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_opaque_call_comparison)
<< P.toDiagnosticString(S.getASTContext());
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index ab94bb96829ac..d38ee1eea672a 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -4503,7 +4503,16 @@ static bool interp__builtin_ia32_vpdp(InterpState &S, CodePtr OpPC,
bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call,
uint32_t BuiltinID) {
- if (!S.getASTContext().BuiltinInfo.isConstantEvaluated(BuiltinID))
+ // BuiltinID has already been normalized (see getConstantEvaluatedBuiltinID),
+ // i.e. an auxiliary target builtin ID has been translated to its canonical
+ // value. The "is constant evaluated" check below needs the *raw* builtin ID
+ // so that aux-target IDs resolve into the correct (aux-target) builtin
+ // records; recover it from the call, falling back to BuiltinID for builtins
+ // emitted without a direct callee (e.g. operator new/delete).
+ unsigned RawBuiltinID = Call->getBuiltinCallee();
+ if (!RawBuiltinID)
+ RawBuiltinID = BuiltinID;
+ if (!S.getASTContext().BuiltinInfo.isConstantEvaluated(RawBuiltinID))
return Invalid(S, OpPC);
const InterpFrame *Frame = S.Current;
diff --git a/clang/lib/AST/ExprConstShared.h b/clang/lib/AST/ExprConstShared.h
index 619c79a1408f3..afea0ceeb6571 100644
--- a/clang/lib/AST/ExprConstShared.h
+++ b/clang/lib/AST/ExprConstShared.h
@@ -29,6 +29,7 @@ class LangOptions;
class ASTContext;
class CharUnits;
class Expr;
+class CallExpr;
} // namespace clang
using namespace clang;
/// Values returned by __builtin_classify_type, chosen to match the values
@@ -78,6 +79,14 @@ void HandleComplexComplexDiv(llvm::APFloat A, llvm::APFloat B, llvm::APFloat C,
CharUnits GetAlignOfExpr(const ASTContext &Ctx, const Expr *E,
UnaryExprOrTypeTrait ExprKind);
+/// Return the (normalized) builtin ID to dispatch on in the constant
+/// evaluators' target-specific cases, or 0 if \p E does not name a builtin
+/// those cases should handle. Translates an auxiliary target builtin ID back to
+/// its canonical value and only returns IDs for architectures the constant
+/// evaluators can fold (currently x86/x86_64).
+unsigned getConstantEvaluatedBuiltinID(const ASTContext &Ctx,
+ const CallExpr *E);
+
uint8_t GFNIMultiplicativeInverse(uint8_t Byte);
uint8_t GFNIMul(uint8_t AByte, uint8_t BByte);
uint8_t GFNIAffine(uint8_t XByte, const llvm::APInt &AQword,
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 2862ff23c29e9..05c166ab1747f 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -8457,52 +8457,6 @@ class ExprEvaluatorBase
Info.Ctx.BuiltinInfo.isConstantEvaluated(BuiltinOp);
}
- // Return the target builtin ID to dispatch on in the constant evaluator's
- // target-specific cases, or 0 if \p E does not name a builtin those cases
- // should handle.
- //
- // Target-specific builtin IDs of different targets overlap (each target
- // numbers its builtins from Builtin::FirstTSBuiltin), so a target builtin ID
- // is only meaningful for the target that owns it. Determine the owning target
- // (translating an auxiliary ID back to its canonical value) and only return
- // the ID for architectures whose target-specific builtins the constant
- // evaluator knows how to fold; otherwise an overlapping ID could be
- // misinterpreted as an unrelated builtin of another target.
- unsigned getConstantEvaluatedBuiltinID(const CallExpr *E) {
- unsigned BuiltinOp = E->getBuiltinCallee();
-
- // Target-independent builtins have the same ID regardless of the target, so
- // they can be dispatched as-is.
- if (BuiltinOp < Builtin::FirstTSBuiltin)
- return BuiltinOp;
-
- // Determine the target that owns this builtin, translating an auxiliary ID
- // back to its canonical value.
- const TargetInfo *OwningTarget;
- if (Info.Ctx.BuiltinInfo.isAuxBuiltinID(BuiltinOp)) {
- OwningTarget = Info.Ctx.getAuxTargetInfo();
- BuiltinOp = Info.Ctx.BuiltinInfo.getAuxBuiltinID(BuiltinOp);
- } else {
- OwningTarget = &Info.Ctx.getTargetInfo();
- }
-
- if (!OwningTarget)
- return 0;
-
- // Only dispatch the ID for architectures whose target-specific builtins the
- // constant evaluator can fold. x86 and x86_64 share a single builtin set and
- // are the only such architectures today; as other targets gain
- // constant-evaluation support for their builtins, add the corresponding
- // cases here so their IDs are dispatched too.
- switch (OwningTarget->getTriple().getArch()) {
- case llvm::Triple::x86:
- case llvm::Triple::x86_64:
- return BuiltinOp;
- default:
- return 0;
- }
- }
-
public:
ExprEvaluatorBase(EvalInfo &Info) : Info(Info) {}
@@ -10315,6 +10269,53 @@ static CharUnits GetAlignOfType(const ASTContext &Ctx, QualType T,
llvm_unreachable("GetAlignOfType on a non-alignment ExprKind");
}
+// Return the target builtin ID to dispatch on in the constant evaluators'
+// target-specific cases, or 0 if \p E does not name a builtin those cases
+// should handle.
+//
+// Target-specific builtin IDs of different targets overlap (each target numbers
+// its builtins from Builtin::FirstTSBuiltin), so a target builtin ID is only
+// meaningful for the target that owns it. Determine the owning target
+// (translating an auxiliary ID back to its canonical value) and only return the
+// ID for architectures whose target-specific builtins the constant evaluators
+// know how to fold; otherwise an overlapping ID could be misinterpreted as an
+// unrelated builtin of another target.
+unsigned getConstantEvaluatedBuiltinID(const ASTContext &Ctx,
+ const CallExpr *E) {
+ unsigned BuiltinOp = E->getBuiltinCallee();
+
+ // Target-independent builtins have the same ID regardless of the target, so
+ // they can be dispatched as-is.
+ if (BuiltinOp < Builtin::FirstTSBuiltin)
+ return BuiltinOp;
+
+ // Determine the target that owns this builtin, translating an auxiliary ID
+ // back to its canonical value.
+ const TargetInfo *OwningTarget;
+ if (Ctx.BuiltinInfo.isAuxBuiltinID(BuiltinOp)) {
+ OwningTarget = Ctx.getAuxTargetInfo();
+ BuiltinOp = Ctx.BuiltinInfo.getAuxBuiltinID(BuiltinOp);
+ } else {
+ OwningTarget = &Ctx.getTargetInfo();
+ }
+
+ if (!OwningTarget)
+ return 0;
+
+ // Only dispatch the ID for architectures whose target-specific builtins the
+ // constant evaluators can fold. x86 and x86_64 share a single builtin set and
+ // are the only such architectures today; as other targets gain
+ // constant-evaluation support for their builtins, add the corresponding cases
+ // here so their IDs are dispatched too.
+ switch (OwningTarget->getTriple().getArch()) {
+ case llvm::Triple::x86:
+ case llvm::Triple::x86_64:
+ return BuiltinOp;
+ default:
+ return 0;
+ }
+}
+
CharUnits GetAlignOfExpr(const ASTContext &Ctx, const Expr *E,
UnaryExprOrTypeTrait ExprKind) {
E = E->IgnoreParens();
@@ -10389,7 +10390,7 @@ bool PointerExprEvaluator::visitNonBuiltinCallExpr(const CallExpr *E) {
bool PointerExprEvaluator::VisitCallExpr(const CallExpr *E) {
if (!IsConstantEvaluatedBuiltinCall(E))
return visitNonBuiltinCallExpr(E);
- return VisitBuiltinCallExpr(E, getConstantEvaluatedBuiltinID(E));
+ return VisitBuiltinCallExpr(E, getConstantEvaluatedBuiltinID(Info.Ctx, E));
}
// Determine if T is a character type for which we guarantee that
@@ -12326,7 +12327,7 @@ bool VectorExprEvaluator::VisitCallExpr(const CallExpr *E) {
if (!IsConstantEvaluatedBuiltinCall(E))
return ExprEvaluatorBaseTy::VisitCallExpr(E);
- unsigned BuiltinOp = getConstantEvaluatedBuiltinID(E);
+ unsigned BuiltinOp = getConstantEvaluatedBuiltinID(Info.Ctx, E);
auto EvaluateBinOpExpr =
[&](llvm::function_ref<APInt(const APSInt &, const APSInt &)> Fn) {
@@ -16462,7 +16463,7 @@ tryEvaluateBuiltinObjectSize(const Expr *E, unsigned Type, EvalInfo &Info,
bool IntExprEvaluator::VisitCallExpr(const CallExpr *E) {
if (!IsConstantEvaluatedBuiltinCall(E))
return ExprEvaluatorBaseTy::VisitCallExpr(E);
- return VisitBuiltinCallExpr(E, getConstantEvaluatedBuiltinID(E));
+ return VisitBuiltinCallExpr(E, getConstantEvaluatedBuiltinID(Info.Ctx, E));
}
static bool getBuiltinAlignArguments(const CallExpr *E, EvalInfo &Info,
@@ -20035,7 +20036,7 @@ bool FloatExprEvaluator::VisitCallExpr(const CallExpr *E) {
if (!IsConstantEvaluatedBuiltinCall(E))
return ExprEvaluatorBaseTy::VisitCallExpr(E);
- unsigned BuiltinOp = getConstantEvaluatedBuiltinID(E);
+ unsigned BuiltinOp = getConstantEvaluatedBuiltinID(Info.Ctx, E);
switch (BuiltinOp) {
default:
diff --git a/clang/test/Sema/aux-target-builtin-constexpr.cpp b/clang/test/Sema/aux-target-builtin-constexpr.cpp
index 38d2f02343b49..96af00e161a40 100644
--- a/clang/test/Sema/aux-target-builtin-constexpr.cpp
+++ b/clang/test/Sema/aux-target-builtin-constexpr.cpp
@@ -1,6 +1,10 @@
// RUN: %clang_cc1 -fopenmp -fopenmp-is-target-device \
// RUN: -triple aarch64 -aux-triple x86_64-unknown-linux-gnu \
// RUN: -aux-target-feature +avx512f -std=c++17 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fopenmp -fopenmp-is-target-device \
+// RUN: -triple aarch64 -aux-triple x86_64-unknown-linux-gnu \
+// RUN: -aux-target-feature +avx512f -std=c++17 -fsyntax-only -verify \
+// RUN: -fexperimental-new-constant-interpreter %s
// expected-no-diagnostics
>From 2436c4a15af60bb4734a925ec560babbb4305d86 Mon Sep 17 00:00:00 2001
From: "yaohui.han" <yaohui.han at iluvatar.com>
Date: Mon, 22 Jun 2026 14:06:25 +0800
Subject: [PATCH 5/5] [clang][bytecode] Avoid re-deriving the builtin ID from
the call expression
Bake the raw builtin ID into the bytecode again and normalize it to its
canonical value lazily inside InterpretBuiltin via an ID-based
getConstantEvaluatedBuiltinID overload, instead of calling
CallExpr::getBuiltinCallee() on the interpreter's hot path. The runtime
"is constant evaluated" gate keeps using the raw ID.
---
clang/lib/AST/ByteCode/Compiler.cpp | 3 +--
clang/lib/AST/ByteCode/InterpBuiltin.cpp | 23 +++++++++++++----------
clang/lib/AST/ExprConstShared.h | 13 ++++++++++---
clang/lib/AST/ExprConstant.cpp | 18 ++++++++++++------
4 files changed, 36 insertions(+), 21 deletions(-)
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index f36313ab15716..875a3f5c7f97e 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -5710,8 +5710,7 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
const FunctionDecl *FuncDecl = E->getDirectCallee();
if (FuncDecl) {
- if (unsigned BuiltinID =
- getConstantEvaluatedBuiltinID(Ctx.getASTContext(), E))
+ if (unsigned BuiltinID = FuncDecl->getBuiltinID())
return VisitBuiltinCallExpr(E, BuiltinID);
// Calls to replaceable operator new/operator delete.
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index d38ee1eea672a..c97aa783169a0 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -4503,18 +4503,21 @@ static bool interp__builtin_ia32_vpdp(InterpState &S, CodePtr OpPC,
bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call,
uint32_t BuiltinID) {
- // BuiltinID has already been normalized (see getConstantEvaluatedBuiltinID),
- // i.e. an auxiliary target builtin ID has been translated to its canonical
- // value. The "is constant evaluated" check below needs the *raw* builtin ID
- // so that aux-target IDs resolve into the correct (aux-target) builtin
- // records; recover it from the call, falling back to BuiltinID for builtins
- // emitted without a direct callee (e.g. operator new/delete).
- unsigned RawBuiltinID = Call->getBuiltinCallee();
- if (!RawBuiltinID)
- RawBuiltinID = BuiltinID;
- if (!S.getASTContext().BuiltinInfo.isConstantEvaluated(RawBuiltinID))
+ const ASTContext &ASTCtx = S.getASTContext();
+
+ // BuiltinID is the raw ID baked into the bytecode. The "is constant
+ // evaluated" gate needs the raw ID so that auxiliary-target IDs resolve into
+ // the correct (aux-target) builtin records.
+ if (!ASTCtx.BuiltinInfo.isConstantEvaluated(BuiltinID))
return Invalid(S, OpPC);
+ // Normalize an auxiliary x86 target builtin ID to its canonical X86::BI*
+ // value so the target-specific cases below (and the handlers they call) match.
+ // This is a cheap integer operation (a single comparison for the common,
+ // target-independent case); we deliberately avoid re-deriving the ID from the
+ // call expression, which is comparatively slow.
+ BuiltinID = getConstantEvaluatedBuiltinID(ASTCtx, BuiltinID);
+
const InterpFrame *Frame = S.Current;
switch (BuiltinID) {
case Builtin::BI__builtin_is_constant_evaluated:
diff --git a/clang/lib/AST/ExprConstShared.h b/clang/lib/AST/ExprConstShared.h
index afea0ceeb6571..dbe936e38bb0d 100644
--- a/clang/lib/AST/ExprConstShared.h
+++ b/clang/lib/AST/ExprConstShared.h
@@ -80,10 +80,17 @@ CharUnits GetAlignOfExpr(const ASTContext &Ctx, const Expr *E,
UnaryExprOrTypeTrait ExprKind);
/// Return the (normalized) builtin ID to dispatch on in the constant
-/// evaluators' target-specific cases, or 0 if \p E does not name a builtin
-/// those cases should handle. Translates an auxiliary target builtin ID back to
-/// its canonical value and only returns IDs for architectures the constant
+/// evaluators' target-specific cases, or 0 if the builtin is not one those
+/// cases should handle. Translates an auxiliary target builtin ID back to its
+/// canonical value and only returns IDs for architectures the constant
/// evaluators can fold (currently x86/x86_64).
+///
+/// The ID-based overload performs no work beyond a single comparison for
+/// target-independent builtins, so it is suitable for hot paths (e.g. the
+/// bytecode interpreter's builtin dispatch) where re-deriving the ID from the
+/// call expression would be wasteful.
+unsigned getConstantEvaluatedBuiltinID(const ASTContext &Ctx,
+ unsigned BuiltinID);
unsigned getConstantEvaluatedBuiltinID(const ASTContext &Ctx,
const CallExpr *E);
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 05c166ab1747f..14ff5cd1ee752 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -10270,8 +10270,8 @@ static CharUnits GetAlignOfType(const ASTContext &Ctx, QualType T,
}
// Return the target builtin ID to dispatch on in the constant evaluators'
-// target-specific cases, or 0 if \p E does not name a builtin those cases
-// should handle.
+// target-specific cases, or 0 if \p BuiltinOp does not name a builtin those
+// cases should handle.
//
// Target-specific builtin IDs of different targets overlap (each target numbers
// its builtins from Builtin::FirstTSBuiltin), so a target builtin ID is only
@@ -10281,11 +10281,12 @@ static CharUnits GetAlignOfType(const ASTContext &Ctx, QualType T,
// know how to fold; otherwise an overlapping ID could be misinterpreted as an
// unrelated builtin of another target.
unsigned getConstantEvaluatedBuiltinID(const ASTContext &Ctx,
- const CallExpr *E) {
- unsigned BuiltinOp = E->getBuiltinCallee();
-
+ unsigned BuiltinOp) {
// Target-independent builtins have the same ID regardless of the target, so
- // they can be dispatched as-is.
+ // they can be dispatched as-is. This is the common case and is intentionally
+ // kept to a single comparison so callers can use this on hot paths (e.g. the
+ // bytecode interpreter's builtin dispatch) without re-deriving the ID from
+ // the call expression.
if (BuiltinOp < Builtin::FirstTSBuiltin)
return BuiltinOp;
@@ -10316,6 +10317,11 @@ unsigned getConstantEvaluatedBuiltinID(const ASTContext &Ctx,
}
}
+unsigned getConstantEvaluatedBuiltinID(const ASTContext &Ctx,
+ const CallExpr *E) {
+ return getConstantEvaluatedBuiltinID(Ctx, E->getBuiltinCallee());
+}
+
CharUnits GetAlignOfExpr(const ASTContext &Ctx, const Expr *E,
UnaryExprOrTypeTrait ExprKind) {
E = E->IgnoreParens();
More information about the cfe-commits
mailing list