[clang] [clang][bytecode] Yet another __builtin_constant_p implementation (PR #130143)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Thu Mar 6 09:14:49 PST 2025
https://github.com/tbaederr created https://github.com/llvm/llvm-project/pull/130143
I think this is the one. Patch needs a little cleanup and documentation, which I'll do later.
I'll also later add the test results. For now, check the CI once.
>From 61da70b5a1df38d66ba93ca49093d0802e7ef1b3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Thu, 6 Mar 2025 16:18:49 +0100
Subject: [PATCH] [clang][bytecode] Yet another __builtin_constant_p
implementation
---
clang/lib/AST/ByteCode/ByteCodeEmitter.cpp | 11 +++
clang/lib/AST/ByteCode/ByteCodeEmitter.h | 5 ++
clang/lib/AST/ByteCode/Compiler.cpp | 22 +++++
clang/lib/AST/ByteCode/Compiler.h | 7 +-
clang/lib/AST/ByteCode/EvalEmitter.cpp | 21 +++++
clang/lib/AST/ByteCode/EvalEmitter.h | 6 ++
clang/lib/AST/ByteCode/Interp.cpp | 74 +++++++++++++++++
clang/lib/AST/ByteCode/Interp.h | 22 +++++
clang/lib/AST/ByteCode/InterpBuiltin.cpp | 79 ------------------
clang/lib/AST/ByteCode/InterpState.h | 3 +
clang/lib/AST/ByteCode/Opcodes.td | 10 ++-
.../test/AST/ByteCode/builtin-constant-p.cpp | 80 ++++++++++++++++++-
clang/test/CodeGenCXX/builtin-constant-p.cpp | 1 +
13 files changed, 257 insertions(+), 84 deletions(-)
diff --git a/clang/lib/AST/ByteCode/ByteCodeEmitter.cpp b/clang/lib/AST/ByteCode/ByteCodeEmitter.cpp
index 5bd1b73133d65..d07b45de8e601 100644
--- a/clang/lib/AST/ByteCode/ByteCodeEmitter.cpp
+++ b/clang/lib/AST/ByteCode/ByteCodeEmitter.cpp
@@ -367,6 +367,17 @@ bool ByteCodeEmitter::fallthrough(const LabelTy &Label) {
return true;
}
+bool ByteCodeEmitter::speculate(const CallExpr *E, const LabelTy &EndLabel) {
+ const Expr *Arg = E->getArg(0);
+ PrimType T = Ctx.classify(Arg->getType()).value_or(PT_Ptr);
+ bool CheckPointer = (T == PT_Ptr);
+ if (!this->emitBCP(getOffset(EndLabel), CheckPointer, T, E))
+ return false;
+ if (!this->visit(Arg))
+ return false;
+ return true;
+}
+
//===----------------------------------------------------------------------===//
// Opcode emitters
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/AST/ByteCode/ByteCodeEmitter.h b/clang/lib/AST/ByteCode/ByteCodeEmitter.h
index ac728830527a2..a45f7325569f9 100644
--- a/clang/lib/AST/ByteCode/ByteCodeEmitter.h
+++ b/clang/lib/AST/ByteCode/ByteCodeEmitter.h
@@ -48,6 +48,9 @@ class ByteCodeEmitter {
virtual bool visitFunc(const FunctionDecl *E) = 0;
virtual bool visitExpr(const Expr *E, bool DestroyToplevelScope) = 0;
virtual bool visitDeclAndReturn(const VarDecl *E, bool ConstantContext) = 0;
+ virtual bool visit(const Expr *E) = 0;
+ virtual bool discard(const Expr *E) = 0;
+ virtual bool emitBool(bool V, const Expr *E) = 0;
/// Emits jumps.
bool jumpTrue(const LabelTy &Label);
@@ -55,6 +58,8 @@ class ByteCodeEmitter {
bool jump(const LabelTy &Label);
bool fallthrough(const LabelTy &Label);
+ bool speculate(const CallExpr *E, const LabelTy &EndLabel);
+
/// We're always emitting bytecode.
bool isActive() const { return true; }
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 281fb7e14a57d..3eca12897681d 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -4681,6 +4681,28 @@ bool Compiler<Emitter>::visitAPValueInitializer(const APValue &Val,
template <class Emitter>
bool Compiler<Emitter>::VisitBuiltinCallExpr(const CallExpr *E,
unsigned BuiltinID) {
+
+ if (BuiltinID == Builtin::BI__builtin_constant_p) {
+ // Void argument is always invalid and harder to handle later.
+ if (E->getArg(0)->getType()->isVoidType()) {
+ if (DiscardResult)
+ return true;
+ return this->emitConst(0, E);
+ }
+
+ if (!this->emitStartSpeculation(E))
+ return false;
+ LabelTy EndLabel = this->getLabel();
+ if (!this->speculate(E, EndLabel))
+ return false;
+ this->fallthrough(EndLabel);
+ if (!this->emitEndSpeculation(E))
+ return false;
+ if (DiscardResult)
+ return this->emitPop(classifyPrim(E), E);
+ return true;
+ }
+
const Function *Func = getFunction(E->getDirectCallee());
if (!Func)
return false;
diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h
index 77fcc3d1b41ce..4e5e13833de32 100644
--- a/clang/lib/AST/ByteCode/Compiler.h
+++ b/clang/lib/AST/ByteCode/Compiler.h
@@ -274,14 +274,14 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
/// Evaluates an expression and places the result on the stack. If the
/// expression is of composite type, a local variable will be created
/// and a pointer to said variable will be placed on the stack.
- bool visit(const Expr *E);
+ bool visit(const Expr *E) override;
/// Compiles an initializer. This is like visit() but it will never
/// create a variable and instead rely on a variable already having
/// been created. visitInitializer() then relies on a pointer to this
/// variable being on top of the stack.
bool visitInitializer(const Expr *E);
/// Evaluates an expression for side effects and discards the result.
- bool discard(const Expr *E);
+ bool discard(const Expr *E) override;
/// Just pass evaluation on to \p E. This leaves all the parsing flags
/// intact.
bool delegate(const Expr *E);
@@ -342,6 +342,9 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
/// Emits an integer constant.
template <typename T> bool emitConst(T Value, PrimType Ty, const Expr *E);
template <typename T> bool emitConst(T Value, const Expr *E);
+ bool emitBool(bool V, const Expr *E) override {
+ return this->emitConst(V, E);
+ }
llvm::RoundingMode getRoundingMode(const Expr *E) const {
FPOptions FPO = E->getFPFeaturesInEffect(Ctx.getLangOpts());
diff --git a/clang/lib/AST/ByteCode/EvalEmitter.cpp b/clang/lib/AST/ByteCode/EvalEmitter.cpp
index 95149efbea992..7d1b0dc563885 100644
--- a/clang/lib/AST/ByteCode/EvalEmitter.cpp
+++ b/clang/lib/AST/ByteCode/EvalEmitter.cpp
@@ -127,6 +127,27 @@ bool EvalEmitter::fallthrough(const LabelTy &Label) {
return true;
}
+bool EvalEmitter::speculate(const CallExpr *E, const LabelTy &EndLabel) {
+ size_t StackSizeBefore = S.Stk.size();
+ const Expr *Arg = E->getArg(0);
+ if (!this->visit(Arg)) {
+ S.Stk.clearTo(StackSizeBefore);
+ return this->emitBool(false, E);
+ }
+
+ PrimType T = Ctx.classify(Arg->getType()).value_or(PT_Ptr);
+ bool CheckPointer = (T == PT_Ptr);
+ if (CheckPointer) {
+ const auto &Ptr = S.Stk.pop<Pointer>();
+ return this->emitBool(CheckBCPResult(S, Ptr), E);
+ }
+
+ // Otherwise, this is fine!
+ if (!this->emitPop(T, E))
+ return false;
+ return this->emitBool(true, E);
+}
+
template <PrimType OpType> bool EvalEmitter::emitRet(const SourceInfo &Info) {
if (!isActive())
return true;
diff --git a/clang/lib/AST/ByteCode/EvalEmitter.h b/clang/lib/AST/ByteCode/EvalEmitter.h
index 2cac2ba2ef221..85668863bb1ae 100644
--- a/clang/lib/AST/ByteCode/EvalEmitter.h
+++ b/clang/lib/AST/ByteCode/EvalEmitter.h
@@ -55,12 +55,18 @@ class EvalEmitter : public SourceMapper {
virtual bool visitExpr(const Expr *E, bool DestroyToplevelScope) = 0;
virtual bool visitDeclAndReturn(const VarDecl *VD, bool ConstantContext) = 0;
virtual bool visitFunc(const FunctionDecl *F) = 0;
+ virtual bool visit(const Expr *E) = 0;
+ virtual bool discard(const Expr *E) = 0;
+ virtual bool emitBool(bool V, const Expr *E) = 0;
/// Emits jumps.
bool jumpTrue(const LabelTy &Label);
bool jumpFalse(const LabelTy &Label);
bool jump(const LabelTy &Label);
bool fallthrough(const LabelTy &Label);
+ bool speculate(const CallExpr *E, const LabelTy &EndLabel);
+
+ bool emitBCP(SourceInfo SI);
/// Since expressions can only jump forward, predicated execution is
/// used to deal with if-else statements.
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 1107c0c32792f..dd0ca5415c669 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -54,6 +54,64 @@ static bool Jf(InterpState &S, CodePtr &PC, int32_t Offset) {
return true;
}
+static bool BCP(InterpState &S, CodePtr &RealPC, int32_t Offset,
+ bool CheckLValue, PrimType PT) {
+ [[maybe_unused]] CodePtr PCBefore = RealPC;
+ size_t StackSizeBefore = S.Stk.size();
+
+ auto InterpUntilLabel = [&S, RealPC]() -> bool {
+ const InterpFrame *StartFrame = S.Current;
+ CodePtr PC = RealPC;
+
+ for (;;) {
+ auto Op = PC.read<Opcode>();
+ if (Op == OP_EndSpeculation)
+ return true;
+ CodePtr OpPC = PC;
+
+ switch (Op) {
+#define GET_INTERP
+#include "Opcodes.inc"
+#undef GET_INTERP
+ }
+ }
+ llvm_unreachable("We didn't see an EndSpeculation op?");
+ };
+
+ if (InterpUntilLabel()) {
+ if (CheckLValue) {
+ const auto &Ptr = S.Stk.pop<Pointer>();
+ assert(S.Stk.size() == StackSizeBefore);
+ S.Stk.push<Integral<32, true>>(
+ Integral<32, true>::from(CheckBCPResult(S, Ptr)));
+ } else {
+ // Pop the result from the stack and return success.
+ TYPE_SWITCH(PT, S.Stk.pop<T>(););
+ assert(S.Stk.size() == StackSizeBefore);
+ S.Stk.push<Integral<32, true>>(Integral<32, true>::from(1));
+ }
+ } else {
+ S.Stk.clearTo(StackSizeBefore);
+ S.Stk.push<Integral<32, true>>(Integral<32, true>::from(0));
+ }
+
+ // RealPC should not have been modified.
+ assert(*RealPC == *PCBefore);
+
+ // Jump to end label. This is a little tricker than just RealPC += Offset
+ // because our usual jump instructions don't have any arguments, to the offset
+ // we get is a little too much and we need to subtract the size of the
+ // bool and PrimType arguments again.
+ int32_t ParamSize = align(sizeof(bool)) + align(sizeof(PrimType));
+ assert(Offset >= ParamSize);
+ RealPC += Offset - ParamSize;
+
+ [[maybe_unused]] CodePtr PCCopy = RealPC;
+ assert(PCCopy.read<Opcode>() == OP_EndSpeculation);
+
+ return true;
+}
+
static void diagnoseMissingInitializer(InterpState &S, CodePtr OpPC,
const ValueDecl *VD) {
const SourceInfo &E = S.Current->getSource(OpPC);
@@ -290,6 +348,22 @@ void cleanupAfterFunctionCall(InterpState &S, CodePtr OpPC,
TYPE_SWITCH(Ty, S.Stk.discard<T>());
}
+bool CheckBCPResult(InterpState &S, const Pointer &Ptr) {
+ if (Ptr.isDummy())
+ return false;
+ if (Ptr.isZero())
+ return true;
+ if (!Ptr.isBlockPointer())
+ return false;
+ if (Ptr.isTypeidPointer())
+ return true;
+
+ if (const Expr *Base = Ptr.getDeclDesc()->asExpr())
+ return isa<StringLiteral>(Base);
+
+ return false;
+}
+
bool CheckExtern(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
if (!Ptr.isExtern())
return true;
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index d8f90e45b0ced..401bbefaf9a92 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -159,6 +159,7 @@ bool CheckLiteralType(InterpState &S, CodePtr OpPC, const Type *T);
bool InvalidShuffleVectorIndex(InterpState &S, CodePtr OpPC, uint32_t Index);
bool CheckBitCast(InterpState &S, CodePtr OpPC, bool HasIndeterminateBits,
bool TargetIsUCharOrByte);
+bool CheckBCPResult(InterpState &S, const Pointer &Ptr);
template <typename T>
static bool handleOverflow(InterpState &S, CodePtr OpPC, const T &SrcValue) {
@@ -2776,8 +2777,29 @@ inline bool Unsupported(InterpState &S, CodePtr OpPC) {
return false;
}
+inline bool StartSpeculation(InterpState &S, CodePtr OpPC) {
+ ++S.SpeculationDepth;
+ if (S.SpeculationDepth != 1)
+ return true;
+
+ assert(S.PrevDiags == nullptr);
+ S.PrevDiags = S.getEvalStatus().Diag;
+ S.getEvalStatus().Diag = nullptr;
+ return true;
+}
+inline bool EndSpeculation(InterpState &S, CodePtr OpPC) {
+ assert(S.SpeculationDepth != 0);
+ --S.SpeculationDepth;
+ if (S.SpeculationDepth == 0) {
+ S.getEvalStatus().Diag = S.PrevDiags;
+ S.PrevDiags = nullptr;
+ }
+ return true;
+}
+
/// Do nothing and just abort execution.
inline bool Error(InterpState &S, CodePtr OpPC) { return false; }
+
inline bool SideEffect(InterpState &S, CodePtr OpPC) {
return S.noteSideEffect();
}
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index df9c2bc24b15f..00f99745862ee 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -1483,80 +1483,6 @@ static bool interp__builtin_ptrauth_string_discriminator(
return true;
}
-// FIXME: This implementation is not complete.
-// The Compiler instance we create cannot access the current stack frame, local
-// variables, function parameters, etc. We also need protection from
-// side-effects, fatal errors, etc.
-static bool interp__builtin_constant_p(InterpState &S, CodePtr OpPC,
- const InterpFrame *Frame,
- const Function *Func,
- const CallExpr *Call) {
- const Expr *Arg = Call->getArg(0);
- QualType ArgType = Arg->getType();
-
- auto returnInt = [&S, Call](bool Value) -> bool {
- pushInteger(S, Value, Call->getType());
- return true;
- };
-
- // __builtin_constant_p always has one operand. The rules which gcc follows
- // are not precisely documented, but are as follows:
- //
- // - If the operand is of integral, floating, complex or enumeration type,
- // and can be folded to a known value of that type, it returns 1.
- // - If the operand can be folded to a pointer to the first character
- // of a string literal (or such a pointer cast to an integral type)
- // or to a null pointer or an integer cast to a pointer, it returns 1.
- //
- // Otherwise, it returns 0.
- //
- // FIXME: GCC also intends to return 1 for literals of aggregate types, but
- // its support for this did not work prior to GCC 9 and is not yet well
- // understood.
- if (ArgType->isIntegralOrEnumerationType() || ArgType->isFloatingType() ||
- ArgType->isAnyComplexType() || ArgType->isPointerType() ||
- ArgType->isNullPtrType()) {
- auto PrevDiags = S.getEvalStatus().Diag;
- S.getEvalStatus().Diag = nullptr;
- InterpStack Stk;
- Compiler<EvalEmitter> C(S.Ctx, S.P, S, Stk);
- auto Res = C.interpretExpr(Arg, /*ConvertResultToRValue=*/Arg->isGLValue());
- S.getEvalStatus().Diag = PrevDiags;
- if (Res.isInvalid()) {
- C.cleanup();
- Stk.clear();
- return returnInt(false);
- }
-
- if (!Res.empty()) {
- const APValue &LV = Res.toAPValue();
- if (LV.isLValue()) {
- APValue::LValueBase Base = LV.getLValueBase();
- if (Base.isNull()) {
- // A null base is acceptable.
- return returnInt(true);
- } else if (const auto *E = Base.dyn_cast<const Expr *>()) {
- if (!isa<StringLiteral>(E))
- return returnInt(false);
- return returnInt(LV.getLValueOffset().isZero());
- } else if (Base.is<TypeInfoLValue>()) {
- // Surprisingly, GCC considers __builtin_constant_p(&typeid(int)) to
- // evaluate to true.
- return returnInt(true);
- } else {
- // Any other base is not constant enough for GCC.
- return returnInt(false);
- }
- }
- }
-
- // Otherwise, any constant value is good enough.
- return returnInt(true);
- }
-
- return returnInt(false);
-}
-
static bool interp__builtin_operator_new(InterpState &S, CodePtr OpPC,
const InterpFrame *Frame,
const Function *Func,
@@ -2468,11 +2394,6 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const Function *F,
return false;
break;
- case Builtin::BI__builtin_constant_p:
- if (!interp__builtin_constant_p(S, OpPC, Frame, F, Call))
- return false;
- break;
-
case Builtin::BI__noop:
pushInteger(S, 0, Call->getType());
break;
diff --git a/clang/lib/AST/ByteCode/InterpState.h b/clang/lib/AST/ByteCode/InterpState.h
index d6adfff1a713a..74001b80d9c00 100644
--- a/clang/lib/AST/ByteCode/InterpState.h
+++ b/clang/lib/AST/ByteCode/InterpState.h
@@ -144,6 +144,9 @@ class InterpState final : public State, public SourceMapper {
SourceLocation EvalLocation;
/// Declaration we're initializing/evaluting, if any.
const VarDecl *EvaluatingDecl = nullptr;
+ /// Things needed to do speculative execution.
+ SmallVectorImpl<PartialDiagnosticAt> *PrevDiags = nullptr;
+ unsigned SpeculationDepth = 0;
llvm::SmallVector<
std::pair<const Expr *, const LifetimeExtendedTemporaryDecl *>>
diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td
index 41e4bae65c195..07b16f88aee5f 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -58,7 +58,7 @@ def ArgRecordField : ArgType { let Name = "const Record::Field *"; }
def ArgFltSemantics : ArgType { let Name = "const llvm::fltSemantics *"; }
def ArgRoundingMode : ArgType { let Name = "llvm::RoundingMode"; }
def ArgLETD: ArgType { let Name = "const LifetimeExtendedTemporaryDecl *"; }
-def ArgCastKind : ArgType { let Name = "CastKind"; }
+def ArgCastKind : ArgType { let Name = "interp::CastKind"; }
def ArgCallExpr : ArgType { let Name = "const CallExpr *"; }
def ArgExpr : ArgType { let Name = "const Expr *"; }
def ArgOffsetOfExpr : ArgType { let Name = "const OffsetOfExpr *"; }
@@ -172,6 +172,14 @@ def Jt : JumpOpcode;
// [Bool] -> [], jumps if false.
def Jf : JumpOpcode;
+def StartSpeculation : Opcode;
+def EndSpeculation : Opcode;
+def BCP : Opcode {
+ let ChangesPC = 1;
+ let HasCustomEval = 1;
+ let Args = [ArgSint32, ArgBool, ArgPrimType];
+}
+
//===----------------------------------------------------------------------===//
// Returns
//===----------------------------------------------------------------------===//
diff --git a/clang/test/AST/ByteCode/builtin-constant-p.cpp b/clang/test/AST/ByteCode/builtin-constant-p.cpp
index 62899b60064c2..09a81c4f03e21 100644
--- a/clang/test/AST/ByteCode/builtin-constant-p.cpp
+++ b/clang/test/AST/ByteCode/builtin-constant-p.cpp
@@ -1,6 +1,7 @@
-// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -verify=expected,both %s
-// RUN: %clang_cc1 -verify=ref,both %s
+// RUN: %clang_cc1 -std=c++20 -fexperimental-new-constant-interpreter -verify=expected,both %s
+// RUN: %clang_cc1 -std=c++20 -verify=ref,both %s
+using intptr_t = __INTPTR_TYPE__;
static_assert(__builtin_constant_p(12), "");
static_assert(__builtin_constant_p(1.0), "");
@@ -18,3 +19,78 @@ constexpr int foo(int &a) {
return __builtin_constant_p(a);
}
static_assert(!foo(z));
+
+static_assert(__builtin_constant_p(__builtin_constant_p(1)));
+
+constexpr bool nested(int& a) {
+ return __builtin_constant_p(__builtin_constant_p(a));
+}
+static_assert(nested(z));
+
+constexpr bool Local() {
+ int z = 10;
+ return __builtin_constant_p(z);
+}
+static_assert(Local());
+
+constexpr bool Local2() {
+ int z = 10;
+ return __builtin_constant_p(&z);
+}
+static_assert(!Local2());
+
+constexpr bool Parameter(int a) {
+ return __builtin_constant_p(a);
+}
+static_assert(Parameter(10));
+
+constexpr bool InvalidLocal() {
+ int *z;
+ {
+ int b = 10;
+ z = &b;
+ }
+ return __builtin_constant_p(z);
+}
+static_assert(!InvalidLocal());
+
+template<typename T> constexpr bool bcp(T t) {
+ return __builtin_constant_p(t);
+}
+
+constexpr intptr_t ptr_to_int(const void *p) {
+ return __builtin_constant_p(1) ? (intptr_t)p : (intptr_t)p; // expected-note {{cast that performs the conversions of a reinterpret_cast}}
+}
+
+/// This is from test/SemaCXX/builtin-constant-p.cpp, but it makes no sense.
+/// ptr_to_int is called before bcp(), so it fails. GCC does not accept this either.
+static_assert(bcp(ptr_to_int("foo"))); // expected-error {{not an integral constant expression}} \
+ // expected-note {{in call to}}
+
+constexpr bool AndFold(const int &a, const int &b) {
+ return __builtin_constant_p(a && b);
+}
+
+static_assert(AndFold(10, 20));
+static_assert(!AndFold(z, 10));
+static_assert(!AndFold(10, z));
+
+
+struct F {
+ int a;
+};
+
+constexpr F f{12};
+static_assert(__builtin_constant_p(f.a));
+
+constexpr bool Member() {
+ F f;
+ return __builtin_constant_p(f.a);
+}
+static_assert(!Member());
+
+constexpr bool Discard() {
+ (void)__builtin_constant_p(10);
+ return true;
+}
+static_assert(Discard());
diff --git a/clang/test/CodeGenCXX/builtin-constant-p.cpp b/clang/test/CodeGenCXX/builtin-constant-p.cpp
index 866faa5ec9761..39416b94375fe 100644
--- a/clang/test/CodeGenCXX/builtin-constant-p.cpp
+++ b/clang/test/CodeGenCXX/builtin-constant-p.cpp
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -triple=x86_64-linux-gnu -emit-llvm -o - %s | FileCheck %s
+// RUN: %clang_cc1 -triple=x86_64-linux-gnu -emit-llvm -o - %s -fexperimental-new-constant-interpreter | FileCheck %s
// Don't crash if the argument to __builtin_constant_p isn't scalar.
template <typename T>
More information about the cfe-commits
mailing list