Skip to content

[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

Merged
merged 1 commit into from
Mar 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions clang/lib/AST/ByteCode/ByteCodeEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,16 @@ 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);
if (!this->emitBCP(getOffset(EndLabel), T, E))
return false;
if (!this->visit(Arg))
return false;
return true;
}

//===----------------------------------------------------------------------===//
// Opcode emitters
//===----------------------------------------------------------------------===//
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/AST/ByteCode/ByteCodeEmitter.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,16 @@ 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 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);
/// Speculative execution.
bool speculate(const CallExpr *E, const LabelTy &EndLabel);

/// We're always emitting bytecode.
bool isActive() const { return true; }
Expand Down
22 changes: 22 additions & 0 deletions clang/lib/AST/ByteCode/Compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 4 additions & 1 deletion clang/lib/AST/ByteCode/Compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ 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
Expand Down Expand Up @@ -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());
Expand Down
27 changes: 27 additions & 0 deletions clang/lib/AST/ByteCode/EvalEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,33 @@ 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);

if (S.inConstantContext() || Arg->HasSideEffects(S.getASTContext()))
return this->emitBool(false, E);
return Invalid(S, OpPC);
}

PrimType T = Ctx.classify(Arg->getType()).value_or(PT_Ptr);
if (T == PT_Ptr) {
const auto &Ptr = S.Stk.pop<Pointer>();
return this->emitBool(CheckBCPResult(S, Ptr), E);
} else if (T == PT_FnPtr) {
S.Stk.discard<FunctionPointer>();
// Never accepted
return this->emitBool(false, 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;
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/AST/ByteCode/EvalEmitter.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,16 @@ 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 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);
/// Speculative execution.
bool speculate(const CallExpr *E, const LabelTy &EndLabel);

/// Since expressions can only jump forward, predicated execution is
/// used to deal with if-else statements.
Expand Down
89 changes: 89 additions & 0 deletions clang/lib/AST/ByteCode/Interp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,79 @@ static bool Jf(InterpState &S, CodePtr &PC, int32_t Offset) {
return true;
}

// https://github.com/llvm/llvm-project/issues/102513
#if defined(_MSC_VER) && !defined(__clang__) && !defined(NDEBUG)
#pragma optimize("", off)
#endif
// FIXME: We have the large switch over all opcodes here again, and in
// Interpret().
static bool BCP(InterpState &S, CodePtr &RealPC, int32_t Offset, PrimType PT) {
[[maybe_unused]] CodePtr PCBefore = RealPC;
size_t StackSizeBefore = S.Stk.size();

auto SpeculativeInterp = [&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 (SpeculativeInterp()) {
if (PT == PT_Ptr) {
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 if (PT == PT_FnPtr) {
S.Stk.discard<FunctionPointer>();
S.Stk.push<Integral<32, true>>(Integral<32, true>::from(0));
} 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 {
if (!S.inConstantContext())
return Invalid(S, RealPC);

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(PrimType));
assert(Offset >= ParamSize);
RealPC += Offset - ParamSize;

[[maybe_unused]] CodePtr PCCopy = RealPC;
assert(PCCopy.read<Opcode>() == OP_EndSpeculation);

return true;
}
// https://github.com/llvm/llvm-project/issues/102513
#if defined(_MSC_VER) && !defined(__clang__) && !defined(NDEBUG)
#pragma optimize("", on)
#endif

static void diagnoseMissingInitializer(InterpState &S, CodePtr OpPC,
const ValueDecl *VD) {
const SourceInfo &E = S.Current->getSource(OpPC);
Expand Down Expand Up @@ -290,6 +363,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.isIntegralPointer())
return true;
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;
Expand Down
22 changes: 22 additions & 0 deletions clang/lib/AST/ByteCode/Interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
}
Expand Down
79 changes: 0 additions & 79 deletions clang/lib/AST/ByteCode/InterpBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/AST/ByteCode/InterpState.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 *>>
Expand Down
Loading
Loading