-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[clang][bytecode] Implement __builtin_constant_p #130143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@llvm/pr-subscribers-clang Author: Timm Baeder (tbaederr) ChangesI think this is the one. Patch needs a little cleanup and documentation, which I'll do later. Full diff: https://github.com/llvm/llvm-project/pull/130143.diff 13 Files Affected:
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>
|
Okay, this fixes 112 libc++ tests. |
CC @philnik777, I hope this makes sense. |
I think this makes sense to me. However, it's entirely unclear to me how you prevent modifications for cases like bool test(int& i) {
return __builtin_constant_p(i = 5); // How does this fail?
}
BTW, this is awesome! The rate at which the libc++ test failures are going down makes me think we might be able to switch to the new interpreter in a year or so. I'm really interested how much of a test speedup we can get from this. |
For this case: constexpr bool test(int& i) {
return __builtin_constant_p(i = 5);
}
constexpr int f() {
int a = 10;
test(a);
return a;
}
static_assert(f() == 5); I simply don't. This also works in GCC. For bool test(int& i) {
return __builtin_constant_p(i = 5);
} both interpreters produce define dso_local noundef zeroext i1 @_Z4testRi(ptr noundef nonnull align 4 dereferenceable(4) %i) #0 {
entry:
%i.addr = alloca ptr, align 8
store ptr %i, ptr %i.addr, align 8
ret i1 false
} in the bytecode case, the bcp call returns false because the bcp call is evaluated standalone, without the parameters being registered at all. That means the |
Ah, ok. That's why I couldn't figure it out :D. I'm a bit worried that you could get the constant evaluator in a bad state that way, but I trust you that that's not a problem. Nobody should ever try to modify any state in a |
Yeah. I think I'll merge this. The implementation isn't that invasive, so if any problem arises, it's not too bad to revert it again. |
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/164/builds/7889 Here is the relevant piece of the build log for the reference
|
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/199/builds/2062 Here is the relevant piece of the build log for the reference
|
Use the regular code paths for interpreting.
Add new instructions:
StartSpeculation
will reset the diagnostics pointers tonullptr
, which will keep us from reporting any diagnostics during speculation.EndSpeculation
will undo this.The rest depends on what
Emitter
we use.For
EvalEmitter
, we have no bytecode, so we implementspeculate()
by simply visiting the first argument of__builtin_constant_p
. If the evaluation fails, we push a0
on the stack, otherwise a1
.For
ByteCodeEmitter
, add another instrucion calledBCP
, that interprets all the instructions following it until the nextEndSpeculation
instruction. If any of those instructions fails, we jump to theEndLabel
, which brings us right before theEndSpeculation
. We then push the result on the stack.