Skip to content

[clang][Interp] Add an EvaluationResult class #71315

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
Jan 19, 2024
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
1 change: 1 addition & 0 deletions clang/lib/AST/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ add_clang_library(clangAST
Interp/Function.cpp
Interp/InterpBuiltin.cpp
Interp/Floating.cpp
Interp/EvaluationResult.cpp
Interp/Interp.cpp
Interp/InterpBlock.cpp
Interp/InterpFrame.cpp
Expand Down
23 changes: 18 additions & 5 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15439,11 +15439,13 @@ static bool EvaluateAsRValue(EvalInfo &Info, const Expr *E, APValue &Result) {
if (Info.EnableNewConstInterp) {
if (!Info.Ctx.getInterpContext().evaluateAsRValue(Info, E, Result))
return false;
} else {
if (!::Evaluate(Result, Info, E))
return false;
return CheckConstantExpression(Info, E->getExprLoc(), E->getType(), Result,
ConstantExprKind::Normal);
}

if (!::Evaluate(Result, Info, E))
return false;

// Implicit lvalue-to-rvalue cast.
if (E->isGLValue()) {
LValue LV;
Expand Down Expand Up @@ -15671,6 +15673,13 @@ bool Expr::EvaluateAsConstantExpr(EvalResult &Result, const ASTContext &Ctx,
EvalInfo Info(Ctx, Result, EM);
Info.InConstantContext = true;

if (Info.EnableNewConstInterp) {
if (!Info.Ctx.getInterpContext().evaluate(Info, this, Result.Val))
return false;
return CheckConstantExpression(Info, getExprLoc(),
getStorageType(Ctx, this), Result.Val, Kind);
}

// The type of the object we're initializing is 'const T' for a class NTTP.
QualType T = getType();
if (Kind == ConstantExprKind::ClassTemplateArgument)
Expand Down Expand Up @@ -15746,10 +15755,16 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx,
Info.setEvaluatingDecl(VD, Value);
Info.InConstantContext = IsConstantInitialization;

SourceLocation DeclLoc = VD->getLocation();
QualType DeclTy = VD->getType();

if (Info.EnableNewConstInterp) {
auto &InterpCtx = const_cast<ASTContext &>(Ctx).getInterpContext();
if (!InterpCtx.evaluateAsInitializer(Info, VD, Value))
return false;

return CheckConstantExpression(Info, DeclLoc, DeclTy, Value,
ConstantExprKind::Normal);
} else {
LValue LVal;
LVal.set(VD);
Expand Down Expand Up @@ -15779,8 +15794,6 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx,
llvm_unreachable("Unhandled cleanup; missing full expression marker?");
}

SourceLocation DeclLoc = VD->getLocation();
QualType DeclTy = VD->getType();
return CheckConstantExpression(Info, DeclLoc, DeclTy, Value,
ConstantExprKind::Normal) &&
CheckMemoryLeaks(Info);
Expand Down
13 changes: 1 addition & 12 deletions clang/lib/AST/Interp/ByteCodeEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
using namespace clang;
using namespace clang::interp;

Expected<Function *>
ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl) {
Function *ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl) {
// Set up argument indices.
unsigned ParamOffset = 0;
SmallVector<PrimType, 8> ParamTypes;
Expand Down Expand Up @@ -120,10 +119,6 @@ ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl) {

// Compile the function body.
if (!IsEligibleForCompilation || !visitFunc(FuncDecl)) {
// Return a dummy function if compilation failed.
if (BailLocation)
return llvm::make_error<ByteCodeGenError>(*BailLocation);

Func->setIsFullyCompiled(true);
return Func;
}
Expand Down Expand Up @@ -183,12 +178,6 @@ int32_t ByteCodeEmitter::getOffset(LabelTy Label) {
return 0ull;
}

bool ByteCodeEmitter::bail(const SourceLocation &Loc) {
if (!BailLocation)
BailLocation = Loc;
return false;
}

/// Helper to write bytecode and bail out if 32-bit offsets become invalid.
/// Pointers will be automatically marshalled as 32-bit IDs.
template <typename T>
Expand Down
10 changes: 1 addition & 9 deletions clang/lib/AST/Interp/ByteCodeEmitter.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
#include "PrimType.h"
#include "Program.h"
#include "Source.h"
#include "llvm/Support/Error.h"

namespace clang {
namespace interp {
Expand All @@ -32,7 +31,7 @@ class ByteCodeEmitter {

public:
/// Compiles the function into the module.
llvm::Expected<Function *> compileFunc(const FunctionDecl *FuncDecl);
Function *compileFunc(const FunctionDecl *FuncDecl);

protected:
ByteCodeEmitter(Context &Ctx, Program &P) : Ctx(Ctx), P(P) {}
Expand All @@ -49,11 +48,6 @@ class ByteCodeEmitter {
virtual bool visitExpr(const Expr *E) = 0;
virtual bool visitDecl(const VarDecl *E) = 0;

/// Bails out if a given node cannot be compiled.
bool bail(const Stmt *S) { return bail(S->getBeginLoc()); }
bool bail(const Decl *D) { return bail(D->getBeginLoc()); }
bool bail(const SourceLocation &Loc);

/// Emits jumps.
bool jumpTrue(const LabelTy &Label);
bool jumpFalse(const LabelTy &Label);
Expand Down Expand Up @@ -81,8 +75,6 @@ class ByteCodeEmitter {
LabelTy NextLabel = 0;
/// Offset of the next local variable.
unsigned NextLocalOffset = 0;
/// Location of a failure.
std::optional<SourceLocation> BailLocation;
/// Label information for linker.
llvm::DenseMap<LabelTy, unsigned> LabelOffsets;
/// Location of label relocations.
Expand Down
12 changes: 7 additions & 5 deletions clang/lib/AST/Interp/ByteCodeExprGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ bool ByteCodeExprGen<Emitter>::VisitBinaryOperator(const BinaryOperator *BO) {
}

if (!LT || !RT || !T)
return this->bail(BO);
return false;

// Pointer arithmetic special case.
if (BO->getOpcode() == BO_Add || BO->getOpcode() == BO_Sub) {
Expand Down Expand Up @@ -451,7 +451,7 @@ bool ByteCodeExprGen<Emitter>::VisitBinaryOperator(const BinaryOperator *BO) {
case BO_LAnd:
llvm_unreachable("Already handled earlier");
default:
return this->bail(BO);
return false;
}

llvm_unreachable("Unhandled binary op");
Expand Down Expand Up @@ -504,7 +504,7 @@ bool ByteCodeExprGen<Emitter>::VisitPointerArithBinOp(const BinaryOperator *E) {
else if (Op == BO_Sub)
return this->emitSubOffset(OffsetType, E);

return this->bail(E);
return false;
}

template <class Emitter>
Expand Down Expand Up @@ -2358,7 +2358,9 @@ bool ByteCodeExprGen<Emitter>::visitDecl(const VarDecl *VD) {
// Return the value
if (VarT)
return this->emitRet(*VarT, VD);
return this->emitRetValue(VD);

// Return non-primitive values as pointers here.
return this->emitRet(PT_Ptr, VD);
}

template <class Emitter>
Expand All @@ -2378,7 +2380,7 @@ bool ByteCodeExprGen<Emitter>::visitVarDecl(const VarDecl *VD) {
std::optional<unsigned> GlobalIndex = P.createGlobal(VD, Init);

if (!GlobalIndex)
return this->bail(VD);
return false;

assert(Init);
{
Expand Down
4 changes: 0 additions & 4 deletions clang/lib/AST/Interp/ByteCodeExprGen.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,6 @@ class ByteCodeExprGen : public ConstStmtVisitor<ByteCodeExprGen<Emitter>, bool>,
if (!visitInitializer(Init))
return false;

if ((Init->getType()->isArrayType() || Init->getType()->isRecordType()) &&
!this->emitCheckGlobalCtor(Init))
return false;

return this->emitPopPtr(Init);
}

Expand Down
2 changes: 1 addition & 1 deletion clang/lib/AST/Interp/ByteCodeStmtGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ bool ByteCodeStmtGen<Emitter>::visitStmt(const Stmt *S) {
default: {
if (auto *Exp = dyn_cast<Expr>(S))
return this->discard(Exp);
return this->bail(S);
return false;
}
}
}
Expand Down
106 changes: 73 additions & 33 deletions clang/lib/AST/Interp/Context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,8 @@ Context::~Context() {}
bool Context::isPotentialConstantExpr(State &Parent, const FunctionDecl *FD) {
assert(Stk.empty());
Function *Func = P->getFunction(FD);
if (!Func || !Func->hasBody()) {
if (auto R = ByteCodeStmtGen<ByteCodeEmitter>(*this, *P).compileFunc(FD)) {
Func = *R;
} else {
handleAllErrors(R.takeError(), [&Parent](ByteCodeGenError &Err) {
Parent.FFDiag(Err.getRange().getBegin(),
diag::err_experimental_clang_interp_failed)
<< Err.getRange();
});
return false;
}
}
if (!Func || !Func->hasBody())
Func = ByteCodeStmtGen<ByteCodeEmitter>(*this, *P).compileFunc(FD);

APValue DummyResult;
if (!Run(Parent, Func, DummyResult)) {
Expand All @@ -54,36 +44,90 @@ bool Context::isPotentialConstantExpr(State &Parent, const FunctionDecl *FD) {
bool Context::evaluateAsRValue(State &Parent, const Expr *E, APValue &Result) {
assert(Stk.empty());
ByteCodeExprGen<EvalEmitter> C(*this, *P, Parent, Stk, Result);
if (Check(Parent, C.interpretExpr(E))) {
assert(Stk.empty());
#ifndef NDEBUG
// Make sure we don't rely on some value being still alive in
// InterpStack memory.

auto Res = C.interpretExpr(E);

if (Res.isInvalid()) {
Stk.clear();
return false;
}

assert(Stk.empty());
#ifndef NDEBUG
// Make sure we don't rely on some value being still alive in
// InterpStack memory.
Stk.clear();
#endif
return true;

// Implicit lvalue-to-rvalue conversion.
if (E->isGLValue()) {
std::optional<APValue> RValueResult = Res.toRValue();
if (!RValueResult) {
return false;
}
Result = *RValueResult;
} else {
Result = Res.toAPValue();
}

return true;
}

bool Context::evaluate(State &Parent, const Expr *E, APValue &Result) {
assert(Stk.empty());
ByteCodeExprGen<EvalEmitter> C(*this, *P, Parent, Stk, Result);

auto Res = C.interpretExpr(E);
if (Res.isInvalid()) {
Stk.clear();
return false;
}

assert(Stk.empty());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be asserting that we have an rvalue as opposed to an lvalue?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could have either here and just return what we have.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, okay!

#ifndef NDEBUG
// Make sure we don't rely on some value being still alive in
// InterpStack memory.
Stk.clear();
return false;
#endif
Result = Res.toAPValue();
return true;
}

bool Context::evaluateAsInitializer(State &Parent, const VarDecl *VD,
APValue &Result) {
assert(Stk.empty());
ByteCodeExprGen<EvalEmitter> C(*this, *P, Parent, Stk, Result);
if (Check(Parent, C.interpretDecl(VD))) {
assert(Stk.empty());
#ifndef NDEBUG
// Make sure we don't rely on some value being still alive in
// InterpStack memory.

auto Res = C.interpretDecl(VD);
if (Res.isInvalid()) {
Stk.clear();
#endif
return true;
return false;
}

assert(Stk.empty());
#ifndef NDEBUG
// Make sure we don't rely on some value being still alive in
// InterpStack memory.
Stk.clear();
return false;
#endif

// Ensure global variables are fully initialized.
if (shouldBeGloballyIndexed(VD) && !Res.isInvalid() &&
(VD->getType()->isRecordType() || VD->getType()->isArrayType())) {
assert(Res.isLValue());

if (!Res.checkFullyInitialized(C.getState()))
return false;

// lvalue-to-rvalue conversion.
std::optional<APValue> RValueResult = Res.toRValue();
if (!RValueResult)
return false;
Result = *RValueResult;
Comment on lines +122 to +126
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably turn this into a function rather than reimplement it in different places.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two versions are slightly different: in evaluateInitializer(), we always do the conversion for global variables, but in the version in evaluateAsRValue only for GLValues. I could make the EvaluationResult API a littler easier to use for the cases in Context though, i.e. bool toRValue(APValue &Result) instead of returning the std::optional.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, that seems fine to do in a follow-up.


} else
Result = Res.toAPValue();
return true;
}

const LangOptions &Context::getLangOpts() const { return Ctx.getLangOpts(); }
Expand Down Expand Up @@ -234,12 +278,8 @@ const Function *Context::getOrCreateFunction(const FunctionDecl *FD) {
return Func;

if (!Func || WasNotDefined) {
if (auto R = ByteCodeStmtGen<ByteCodeEmitter>(*this, *P).compileFunc(FD))
Func = *R;
else {
llvm::consumeError(R.takeError());
return nullptr;
}
if (auto F = ByteCodeStmtGen<ByteCodeEmitter>(*this, *P).compileFunc(FD))
Func = F;
}

return Func;
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/AST/Interp/Context.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ class Context final {
/// Evaluates a toplevel expression as an rvalue.
bool evaluateAsRValue(State &Parent, const Expr *E, APValue &Result);

/// Like evaluateAsRvalue(), but does no implicit lvalue-to-rvalue conversion.
bool evaluate(State &Parent, const Expr *E, APValue &Result);

/// Evaluates a toplevel initializer.
bool evaluateAsInitializer(State &Parent, const VarDecl *VD, APValue &Result);

Expand Down
Loading