Author: yaohuihan-iluvatar
Date: 2026-07-06T09:06:44-04:00
New Revision: 6840e6042cf5cb92eb3ebe06cf781d6458958b69

URL: 
https://github.com/llvm/llvm-project/commit/6840e6042cf5cb92eb3ebe06cf781d6458958b69
DIFF: 
https://github.com/llvm/llvm-project/commit/6840e6042cf5cb92eb3ebe06cf781d6458958b69.diff

LOG: [Clang][ExprConstant] Normalize aux target builtin IDs before dispatch 
(#201805)

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.

Added: 
    clang/test/Sema/aux-target-builtin-constexpr.cpp

Modified: 
    clang/lib/AST/ByteCode/InterpBuiltin.cpp
    clang/lib/AST/ExprConstShared.h
    clang/lib/AST/ExprConstant.cpp

Removed: 
    


################################################################################
diff  --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp 
b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index 73672d1d99d2f..be4ef53f04cef 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -4633,9 +4633,21 @@ static bool interp__builtin_ia32_bmac(InterpState &S, 
CodePtr OpPC,
 
 bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call,
                       uint32_t BuiltinID) {
-  if (!S.getASTContext().BuiltinInfo.isConstantEvaluated(BuiltinID))
+  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);
 
+  // Convert 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 = ConvertBuiltinIDToX86BuiltinID(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 619c79a1408f3..7ba3071668bd1 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,26 @@ void HandleComplexComplexDiv(llvm::APFloat A, llvm::APFloat 
B, llvm::APFloat C,
 CharUnits GetAlignOfExpr(const ASTContext &Ctx, const Expr *E,
                          UnaryExprOrTypeTrait ExprKind);
 
+/// Convert a builtin ID to the canonical x86 builtin ID the constant 
evaluators
+/// dispatch on in their x86 target-specific cases.
+///
+/// Target-independent builtins are returned unchanged. An x86 target builtin
+/// (including an auxiliary-target x86 builtin, whose ID is shifted past the
+/// primary target's builtins) is translated to its canonical X86::BI* value.
+/// Any other target's builtin returns 0: the constant evaluators only fold x86
+/// target builtins, and target builtin IDs of 
diff erent targets overlap (each
+/// numbers from Builtin::FirstTSBuiltin), so an unrelated target's ID must not
+/// be mistaken for an x86 one.
+///
+/// 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 ConvertBuiltinIDToX86BuiltinID(const ASTContext &Ctx,
+                                        unsigned BuiltinID);
+unsigned ConvertBuiltinIDToX86BuiltinID(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 fb82e0addfdac..3e24721b14535 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -10276,6 +10276,56 @@ static CharUnits GetAlignOfType(const ASTContext &Ctx, 
QualType T,
     llvm_unreachable("GetAlignOfType on a non-alignment ExprKind");
 }
 
+// Convert a builtin ID to the canonical x86 builtin ID the constant evaluators
+// dispatch on in their x86 target-specific cases, or 0 if \p BuiltinOp is a
+// target builtin those cases should not handle.
+//
+// Target-independent builtins are returned unchanged. Target builtin IDs of
+// 
diff erent 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 when x86 owns it;
+// otherwise an overlapping ID could be misinterpreted as an unrelated x86
+// builtin.
+unsigned ConvertBuiltinIDToX86BuiltinID(const ASTContext &Ctx,
+                                        unsigned BuiltinOp) {
+  // Target-independent builtins have the same ID regardless of the target, so
+  // 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;
+
+  // 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;
+
+  // x86 and x86_64 share a single builtin set and are the only architectures
+  // whose target-specific builtins the constant evaluators currently fold.
+  switch (OwningTarget->getTriple().getArch()) {
+  case llvm::Triple::x86:
+  case llvm::Triple::x86_64:
+    return BuiltinOp;
+  default:
+    return 0;
+  }
+}
+
+unsigned ConvertBuiltinIDToX86BuiltinID(const ASTContext &Ctx,
+                                        const CallExpr *E) {
+  return ConvertBuiltinIDToX86BuiltinID(Ctx, E->getBuiltinCallee());
+}
+
 CharUnits GetAlignOfExpr(const ASTContext &Ctx, const Expr *E,
                          UnaryExprOrTypeTrait ExprKind) {
   E = E->IgnoreParens();
@@ -10350,7 +10400,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, ConvertBuiltinIDToX86BuiltinID(Info.Ctx, E));
 }
 
 // Determine if T is a character type for which we guarantee that
@@ -12287,6 +12337,8 @@ bool VectorExprEvaluator::VisitCallExpr(const CallExpr 
*E) {
   if (!IsConstantEvaluatedBuiltinCall(E))
     return ExprEvaluatorBaseTy::VisitCallExpr(E);
 
+  unsigned BuiltinOp = ConvertBuiltinIDToX86BuiltinID(Info.Ctx, E);
+
   auto EvaluateBinOpExpr =
       [&](llvm::function_ref<APInt(const APSInt &, const APSInt &)> Fn) {
         APValue SourceLHS, SourceRHS;
@@ -12453,7 +12505,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:
@@ -12469,7 +12521,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()),
@@ -12661,7 +12713,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:
@@ -12975,7 +13027,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:
@@ -13082,7 +13134,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),
@@ -13440,7 +13492,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);
 
@@ -13976,14 +14028,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()),
@@ -14053,7 +14105,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:
@@ -14087,7 +14139,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:
@@ -14147,7 +14199,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:
@@ -14166,7 +14218,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:
@@ -14294,7 +14346,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())));
@@ -14377,7 +14429,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: {
@@ -14781,12 +14833,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> {
@@ -16527,7 +16576,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, ConvertBuiltinIDToX86BuiltinID(Info.Ctx, E));
 }
 
 static bool getBuiltinAlignArguments(const CallExpr *E, EvalInfo &Info,
@@ -20112,7 +20161,9 @@ bool FloatExprEvaluator::VisitCallExpr(const CallExpr 
*E) {
   if (!IsConstantEvaluatedBuiltinCall(E))
     return ExprEvaluatorBaseTy::VisitCallExpr(E);
 
-  switch (E->getBuiltinCallee()) {
+  unsigned BuiltinOp = ConvertBuiltinIDToX86BuiltinID(Info.Ctx, 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..96af00e161a40
--- /dev/null
+++ b/clang/test/Sema/aux-target-builtin-constexpr.cpp
@@ -0,0 +1,24 @@
+// 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
+
+// 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);


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to