Skip to content

Commit d08cf79

Browse files
authored
[clang][bytecode] Implement __builtin_constant_p (#130143)
Use the regular code paths for interpreting. Add new instructions: `StartSpeculation` will reset the diagnostics pointers to `nullptr`, 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 implement `speculate()` by simply visiting the first argument of `__builtin_constant_p`. If the evaluation fails, we push a `0` on the stack, otherwise a `1`. For `ByteCodeEmitter`, add another instrucion called `BCP`, that interprets all the instructions following it until the next `EndSpeculation` instruction. If any of those instructions fails, we jump to the `EndLabel`, which brings us right before the `EndSpeculation`. We then push the result on the stack.
1 parent 7602d78 commit d08cf79

14 files changed

+306
-83
lines changed

clang/lib/AST/ByteCode/ByteCodeEmitter.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,16 @@ bool ByteCodeEmitter::fallthrough(const LabelTy &Label) {
367367
return true;
368368
}
369369

370+
bool ByteCodeEmitter::speculate(const CallExpr *E, const LabelTy &EndLabel) {
371+
const Expr *Arg = E->getArg(0);
372+
PrimType T = Ctx.classify(Arg->getType()).value_or(PT_Ptr);
373+
if (!this->emitBCP(getOffset(EndLabel), T, E))
374+
return false;
375+
if (!this->visit(Arg))
376+
return false;
377+
return true;
378+
}
379+
370380
//===----------------------------------------------------------------------===//
371381
// Opcode emitters
372382
//===----------------------------------------------------------------------===//

clang/lib/AST/ByteCode/ByteCodeEmitter.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,16 @@ class ByteCodeEmitter {
4848
virtual bool visitFunc(const FunctionDecl *E) = 0;
4949
virtual bool visitExpr(const Expr *E, bool DestroyToplevelScope) = 0;
5050
virtual bool visitDeclAndReturn(const VarDecl *E, bool ConstantContext) = 0;
51+
virtual bool visit(const Expr *E) = 0;
52+
virtual bool emitBool(bool V, const Expr *E) = 0;
5153

5254
/// Emits jumps.
5355
bool jumpTrue(const LabelTy &Label);
5456
bool jumpFalse(const LabelTy &Label);
5557
bool jump(const LabelTy &Label);
5658
bool fallthrough(const LabelTy &Label);
59+
/// Speculative execution.
60+
bool speculate(const CallExpr *E, const LabelTy &EndLabel);
5761

5862
/// We're always emitting bytecode.
5963
bool isActive() const { return true; }

clang/lib/AST/ByteCode/Compiler.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4681,6 +4681,28 @@ bool Compiler<Emitter>::visitAPValueInitializer(const APValue &Val,
46814681
template <class Emitter>
46824682
bool Compiler<Emitter>::VisitBuiltinCallExpr(const CallExpr *E,
46834683
unsigned BuiltinID) {
4684+
4685+
if (BuiltinID == Builtin::BI__builtin_constant_p) {
4686+
// Void argument is always invalid and harder to handle later.
4687+
if (E->getArg(0)->getType()->isVoidType()) {
4688+
if (DiscardResult)
4689+
return true;
4690+
return this->emitConst(0, E);
4691+
}
4692+
4693+
if (!this->emitStartSpeculation(E))
4694+
return false;
4695+
LabelTy EndLabel = this->getLabel();
4696+
if (!this->speculate(E, EndLabel))
4697+
return false;
4698+
this->fallthrough(EndLabel);
4699+
if (!this->emitEndSpeculation(E))
4700+
return false;
4701+
if (DiscardResult)
4702+
return this->emitPop(classifyPrim(E), E);
4703+
return true;
4704+
}
4705+
46844706
const Function *Func = getFunction(E->getDirectCallee());
46854707
if (!Func)
46864708
return false;

clang/lib/AST/ByteCode/Compiler.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
274274
/// Evaluates an expression and places the result on the stack. If the
275275
/// expression is of composite type, a local variable will be created
276276
/// and a pointer to said variable will be placed on the stack.
277-
bool visit(const Expr *E);
277+
bool visit(const Expr *E) override;
278278
/// Compiles an initializer. This is like visit() but it will never
279279
/// create a variable and instead rely on a variable already having
280280
/// been created. visitInitializer() then relies on a pointer to this
@@ -342,6 +342,9 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
342342
/// Emits an integer constant.
343343
template <typename T> bool emitConst(T Value, PrimType Ty, const Expr *E);
344344
template <typename T> bool emitConst(T Value, const Expr *E);
345+
bool emitBool(bool V, const Expr *E) override {
346+
return this->emitConst(V, E);
347+
}
345348

346349
llvm::RoundingMode getRoundingMode(const Expr *E) const {
347350
FPOptions FPO = E->getFPFeaturesInEffect(Ctx.getLangOpts());

clang/lib/AST/ByteCode/EvalEmitter.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,33 @@ bool EvalEmitter::fallthrough(const LabelTy &Label) {
127127
return true;
128128
}
129129

130+
bool EvalEmitter::speculate(const CallExpr *E, const LabelTy &EndLabel) {
131+
size_t StackSizeBefore = S.Stk.size();
132+
const Expr *Arg = E->getArg(0);
133+
if (!this->visit(Arg)) {
134+
S.Stk.clearTo(StackSizeBefore);
135+
136+
if (S.inConstantContext() || Arg->HasSideEffects(S.getASTContext()))
137+
return this->emitBool(false, E);
138+
return Invalid(S, OpPC);
139+
}
140+
141+
PrimType T = Ctx.classify(Arg->getType()).value_or(PT_Ptr);
142+
if (T == PT_Ptr) {
143+
const auto &Ptr = S.Stk.pop<Pointer>();
144+
return this->emitBool(CheckBCPResult(S, Ptr), E);
145+
} else if (T == PT_FnPtr) {
146+
S.Stk.discard<FunctionPointer>();
147+
// Never accepted
148+
return this->emitBool(false, E);
149+
}
150+
151+
// Otherwise, this is fine!
152+
if (!this->emitPop(T, E))
153+
return false;
154+
return this->emitBool(true, E);
155+
}
156+
130157
template <PrimType OpType> bool EvalEmitter::emitRet(const SourceInfo &Info) {
131158
if (!isActive())
132159
return true;

clang/lib/AST/ByteCode/EvalEmitter.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,16 @@ class EvalEmitter : public SourceMapper {
5555
virtual bool visitExpr(const Expr *E, bool DestroyToplevelScope) = 0;
5656
virtual bool visitDeclAndReturn(const VarDecl *VD, bool ConstantContext) = 0;
5757
virtual bool visitFunc(const FunctionDecl *F) = 0;
58+
virtual bool visit(const Expr *E) = 0;
59+
virtual bool emitBool(bool V, const Expr *E) = 0;
5860

5961
/// Emits jumps.
6062
bool jumpTrue(const LabelTy &Label);
6163
bool jumpFalse(const LabelTy &Label);
6264
bool jump(const LabelTy &Label);
6365
bool fallthrough(const LabelTy &Label);
66+
/// Speculative execution.
67+
bool speculate(const CallExpr *E, const LabelTy &EndLabel);
6468

6569
/// Since expressions can only jump forward, predicated execution is
6670
/// used to deal with if-else statements.

clang/lib/AST/ByteCode/Interp.cpp

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,79 @@ static bool Jf(InterpState &S, CodePtr &PC, int32_t Offset) {
5454
return true;
5555
}
5656

57+
// https://github.com/llvm/llvm-project/issues/102513
58+
#if defined(_MSC_VER) && !defined(__clang__) && !defined(NDEBUG)
59+
#pragma optimize("", off)
60+
#endif
61+
// FIXME: We have the large switch over all opcodes here again, and in
62+
// Interpret().
63+
static bool BCP(InterpState &S, CodePtr &RealPC, int32_t Offset, PrimType PT) {
64+
[[maybe_unused]] CodePtr PCBefore = RealPC;
65+
size_t StackSizeBefore = S.Stk.size();
66+
67+
auto SpeculativeInterp = [&S, RealPC]() -> bool {
68+
const InterpFrame *StartFrame = S.Current;
69+
CodePtr PC = RealPC;
70+
71+
for (;;) {
72+
auto Op = PC.read<Opcode>();
73+
if (Op == OP_EndSpeculation)
74+
return true;
75+
CodePtr OpPC = PC;
76+
77+
switch (Op) {
78+
#define GET_INTERP
79+
#include "Opcodes.inc"
80+
#undef GET_INTERP
81+
}
82+
}
83+
llvm_unreachable("We didn't see an EndSpeculation op?");
84+
};
85+
86+
if (SpeculativeInterp()) {
87+
if (PT == PT_Ptr) {
88+
const auto &Ptr = S.Stk.pop<Pointer>();
89+
assert(S.Stk.size() == StackSizeBefore);
90+
S.Stk.push<Integral<32, true>>(
91+
Integral<32, true>::from(CheckBCPResult(S, Ptr)));
92+
} else if (PT == PT_FnPtr) {
93+
S.Stk.discard<FunctionPointer>();
94+
S.Stk.push<Integral<32, true>>(Integral<32, true>::from(0));
95+
} else {
96+
// Pop the result from the stack and return success.
97+
TYPE_SWITCH(PT, S.Stk.pop<T>(););
98+
assert(S.Stk.size() == StackSizeBefore);
99+
S.Stk.push<Integral<32, true>>(Integral<32, true>::from(1));
100+
}
101+
} else {
102+
if (!S.inConstantContext())
103+
return Invalid(S, RealPC);
104+
105+
S.Stk.clearTo(StackSizeBefore);
106+
S.Stk.push<Integral<32, true>>(Integral<32, true>::from(0));
107+
}
108+
109+
// RealPC should not have been modified.
110+
assert(*RealPC == *PCBefore);
111+
112+
// Jump to end label. This is a little tricker than just RealPC += Offset
113+
// because our usual jump instructions don't have any arguments, to the offset
114+
// we get is a little too much and we need to subtract the size of the
115+
// bool and PrimType arguments again.
116+
int32_t ParamSize = align(sizeof(PrimType));
117+
assert(Offset >= ParamSize);
118+
RealPC += Offset - ParamSize;
119+
120+
[[maybe_unused]] CodePtr PCCopy = RealPC;
121+
assert(PCCopy.read<Opcode>() == OP_EndSpeculation);
122+
123+
return true;
124+
}
125+
// https://github.com/llvm/llvm-project/issues/102513
126+
#if defined(_MSC_VER) && !defined(__clang__) && !defined(NDEBUG)
127+
#pragma optimize("", on)
128+
#endif
129+
57130
static void diagnoseMissingInitializer(InterpState &S, CodePtr OpPC,
58131
const ValueDecl *VD) {
59132
const SourceInfo &E = S.Current->getSource(OpPC);
@@ -290,6 +363,22 @@ void cleanupAfterFunctionCall(InterpState &S, CodePtr OpPC,
290363
TYPE_SWITCH(Ty, S.Stk.discard<T>());
291364
}
292365

366+
bool CheckBCPResult(InterpState &S, const Pointer &Ptr) {
367+
if (Ptr.isDummy())
368+
return false;
369+
if (Ptr.isZero())
370+
return true;
371+
if (Ptr.isIntegralPointer())
372+
return true;
373+
if (Ptr.isTypeidPointer())
374+
return true;
375+
376+
if (const Expr *Base = Ptr.getDeclDesc()->asExpr())
377+
return isa<StringLiteral>(Base);
378+
379+
return false;
380+
}
381+
293382
bool CheckExtern(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
294383
if (!Ptr.isExtern())
295384
return true;

clang/lib/AST/ByteCode/Interp.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ bool CheckLiteralType(InterpState &S, CodePtr OpPC, const Type *T);
159159
bool InvalidShuffleVectorIndex(InterpState &S, CodePtr OpPC, uint32_t Index);
160160
bool CheckBitCast(InterpState &S, CodePtr OpPC, bool HasIndeterminateBits,
161161
bool TargetIsUCharOrByte);
162+
bool CheckBCPResult(InterpState &S, const Pointer &Ptr);
162163

163164
template <typename T>
164165
static bool handleOverflow(InterpState &S, CodePtr OpPC, const T &SrcValue) {
@@ -2776,8 +2777,29 @@ inline bool Unsupported(InterpState &S, CodePtr OpPC) {
27762777
return false;
27772778
}
27782779

2780+
inline bool StartSpeculation(InterpState &S, CodePtr OpPC) {
2781+
++S.SpeculationDepth;
2782+
if (S.SpeculationDepth != 1)
2783+
return true;
2784+
2785+
assert(S.PrevDiags == nullptr);
2786+
S.PrevDiags = S.getEvalStatus().Diag;
2787+
S.getEvalStatus().Diag = nullptr;
2788+
return true;
2789+
}
2790+
inline bool EndSpeculation(InterpState &S, CodePtr OpPC) {
2791+
assert(S.SpeculationDepth != 0);
2792+
--S.SpeculationDepth;
2793+
if (S.SpeculationDepth == 0) {
2794+
S.getEvalStatus().Diag = S.PrevDiags;
2795+
S.PrevDiags = nullptr;
2796+
}
2797+
return true;
2798+
}
2799+
27792800
/// Do nothing and just abort execution.
27802801
inline bool Error(InterpState &S, CodePtr OpPC) { return false; }
2802+
27812803
inline bool SideEffect(InterpState &S, CodePtr OpPC) {
27822804
return S.noteSideEffect();
27832805
}

clang/lib/AST/ByteCode/InterpBuiltin.cpp

Lines changed: 0 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,80 +1483,6 @@ static bool interp__builtin_ptrauth_string_discriminator(
14831483
return true;
14841484
}
14851485

1486-
// FIXME: This implementation is not complete.
1487-
// The Compiler instance we create cannot access the current stack frame, local
1488-
// variables, function parameters, etc. We also need protection from
1489-
// side-effects, fatal errors, etc.
1490-
static bool interp__builtin_constant_p(InterpState &S, CodePtr OpPC,
1491-
const InterpFrame *Frame,
1492-
const Function *Func,
1493-
const CallExpr *Call) {
1494-
const Expr *Arg = Call->getArg(0);
1495-
QualType ArgType = Arg->getType();
1496-
1497-
auto returnInt = [&S, Call](bool Value) -> bool {
1498-
pushInteger(S, Value, Call->getType());
1499-
return true;
1500-
};
1501-
1502-
// __builtin_constant_p always has one operand. The rules which gcc follows
1503-
// are not precisely documented, but are as follows:
1504-
//
1505-
// - If the operand is of integral, floating, complex or enumeration type,
1506-
// and can be folded to a known value of that type, it returns 1.
1507-
// - If the operand can be folded to a pointer to the first character
1508-
// of a string literal (or such a pointer cast to an integral type)
1509-
// or to a null pointer or an integer cast to a pointer, it returns 1.
1510-
//
1511-
// Otherwise, it returns 0.
1512-
//
1513-
// FIXME: GCC also intends to return 1 for literals of aggregate types, but
1514-
// its support for this did not work prior to GCC 9 and is not yet well
1515-
// understood.
1516-
if (ArgType->isIntegralOrEnumerationType() || ArgType->isFloatingType() ||
1517-
ArgType->isAnyComplexType() || ArgType->isPointerType() ||
1518-
ArgType->isNullPtrType()) {
1519-
auto PrevDiags = S.getEvalStatus().Diag;
1520-
S.getEvalStatus().Diag = nullptr;
1521-
InterpStack Stk;
1522-
Compiler<EvalEmitter> C(S.Ctx, S.P, S, Stk);
1523-
auto Res = C.interpretExpr(Arg, /*ConvertResultToRValue=*/Arg->isGLValue());
1524-
S.getEvalStatus().Diag = PrevDiags;
1525-
if (Res.isInvalid()) {
1526-
C.cleanup();
1527-
Stk.clear();
1528-
return returnInt(false);
1529-
}
1530-
1531-
if (!Res.empty()) {
1532-
const APValue &LV = Res.toAPValue();
1533-
if (LV.isLValue()) {
1534-
APValue::LValueBase Base = LV.getLValueBase();
1535-
if (Base.isNull()) {
1536-
// A null base is acceptable.
1537-
return returnInt(true);
1538-
} else if (const auto *E = Base.dyn_cast<const Expr *>()) {
1539-
if (!isa<StringLiteral>(E))
1540-
return returnInt(false);
1541-
return returnInt(LV.getLValueOffset().isZero());
1542-
} else if (Base.is<TypeInfoLValue>()) {
1543-
// Surprisingly, GCC considers __builtin_constant_p(&typeid(int)) to
1544-
// evaluate to true.
1545-
return returnInt(true);
1546-
} else {
1547-
// Any other base is not constant enough for GCC.
1548-
return returnInt(false);
1549-
}
1550-
}
1551-
}
1552-
1553-
// Otherwise, any constant value is good enough.
1554-
return returnInt(true);
1555-
}
1556-
1557-
return returnInt(false);
1558-
}
1559-
15601486
static bool interp__builtin_operator_new(InterpState &S, CodePtr OpPC,
15611487
const InterpFrame *Frame,
15621488
const Function *Func,
@@ -2468,11 +2394,6 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const Function *F,
24682394
return false;
24692395
break;
24702396

2471-
case Builtin::BI__builtin_constant_p:
2472-
if (!interp__builtin_constant_p(S, OpPC, Frame, F, Call))
2473-
return false;
2474-
break;
2475-
24762397
case Builtin::BI__noop:
24772398
pushInteger(S, 0, Call->getType());
24782399
break;

clang/lib/AST/ByteCode/InterpState.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ class InterpState final : public State, public SourceMapper {
144144
SourceLocation EvalLocation;
145145
/// Declaration we're initializing/evaluting, if any.
146146
const VarDecl *EvaluatingDecl = nullptr;
147+
/// Things needed to do speculative execution.
148+
SmallVectorImpl<PartialDiagnosticAt> *PrevDiags = nullptr;
149+
unsigned SpeculationDepth = 0;
147150

148151
llvm::SmallVector<
149152
std::pair<const Expr *, const LifetimeExtendedTemporaryDecl *>>

0 commit comments

Comments
 (0)