Skip to content

Commit 3bdebde

Browse files
committed
[clang][bytecode] Yet another __builtin_constant_p implementation
1 parent 04d4314 commit 3bdebde

File tree

14 files changed

+303
-84
lines changed

14 files changed

+303
-84
lines changed

clang/lib/AST/ByteCode/ByteCodeEmitter.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,17 @@ 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+
bool CheckPointer = (T == PT_Ptr);
374+
if (!this->emitBCP(getOffset(EndLabel), CheckPointer, T, E))
375+
return false;
376+
if (!this->visit(Arg))
377+
return false;
378+
return true;
379+
}
380+
370381
//===----------------------------------------------------------------------===//
371382
// Opcode emitters
372383
//===----------------------------------------------------------------------===//

clang/lib/AST/ByteCode/ByteCodeEmitter.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,18 @@ 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 discard(const Expr *E) = 0;
53+
virtual bool emitBool(bool V, const Expr *E) = 0;
5154

5255
/// Emits jumps.
5356
bool jumpTrue(const LabelTy &Label);
5457
bool jumpFalse(const LabelTy &Label);
5558
bool jump(const LabelTy &Label);
5659
bool fallthrough(const LabelTy &Label);
5760

61+
bool speculate(const CallExpr *E, const LabelTy &EndLabel);
62+
5863
/// We're always emitting bytecode.
5964
bool isActive() const { return true; }
6065

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: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,14 +274,14 @@ 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
281281
/// variable being on top of the stack.
282282
bool visitInitializer(const Expr *E);
283283
/// Evaluates an expression for side effects and discards the result.
284-
bool discard(const Expr *E);
284+
bool discard(const Expr *E) override;
285285
/// Just pass evaluation on to \p E. This leaves all the parsing flags
286286
/// intact.
287287
bool delegate(const Expr *E);
@@ -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: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,34 @@ 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())
137+
return Invalid(S, OpPC);
138+
139+
return this->emitBool(false, E);
140+
}
141+
142+
PrimType T = Ctx.classify(Arg->getType()).value_or(PT_Ptr);
143+
if (T == PT_Ptr) {
144+
const auto &Ptr = S.Stk.pop<Pointer>();
145+
return this->emitBool(CheckBCPResult(S, Ptr), E);
146+
} else if (T == PT_FnPtr) {
147+
S.Stk.discard<FunctionPointer>();
148+
// Never accepted
149+
return this->emitBool(false, E);
150+
}
151+
152+
// Otherwise, this is fine!
153+
if (!this->emitPop(T, E))
154+
return false;
155+
return this->emitBool(true, E);
156+
}
157+
130158
template <PrimType OpType> bool EvalEmitter::emitRet(const SourceInfo &Info) {
131159
if (!isActive())
132160
return true;

clang/lib/AST/ByteCode/EvalEmitter.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,18 @@ 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 discard(const Expr *E) = 0;
60+
virtual bool emitBool(bool V, const Expr *E) = 0;
5861

5962
/// Emits jumps.
6063
bool jumpTrue(const LabelTy &Label);
6164
bool jumpFalse(const LabelTy &Label);
6265
bool jump(const LabelTy &Label);
6366
bool fallthrough(const LabelTy &Label);
67+
bool speculate(const CallExpr *E, const LabelTy &EndLabel);
68+
69+
bool emitBCP(SourceInfo SI);
6470

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

clang/lib/AST/ByteCode/Interp.cpp

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

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

357+
bool CheckBCPResult(InterpState &S, const Pointer &Ptr) {
358+
if (Ptr.isDummy())
359+
return false;
360+
if (Ptr.isZero())
361+
return true;
362+
if (Ptr.isIntegralPointer())
363+
return true;
364+
if (Ptr.isTypeidPointer())
365+
return true;
366+
367+
if (const Expr *Base = Ptr.getDeclDesc()->asExpr())
368+
return isa<StringLiteral>(Base);
369+
370+
return false;
371+
}
372+
293373
bool CheckExtern(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
294374
if (!Ptr.isExtern())
295375
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)