diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt index fe3f8c485ec1c..ebcb3952198a5 100644 --- a/clang/lib/AST/CMakeLists.txt +++ b/clang/lib/AST/CMakeLists.txt @@ -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 diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index f20850d14c0c8..7002dd9efe840 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -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; @@ -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) @@ -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(Ctx).getInterpContext(); if (!InterpCtx.evaluateAsInitializer(Info, VD, Value)) return false; + + return CheckConstantExpression(Info, DeclLoc, DeclTy, Value, + ConstantExprKind::Normal); } else { LValue LVal; LVal.set(VD); @@ -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); diff --git a/clang/lib/AST/Interp/ByteCodeEmitter.cpp b/clang/lib/AST/Interp/ByteCodeEmitter.cpp index 045263447cbc9..fd2a92d9d3f91 100644 --- a/clang/lib/AST/Interp/ByteCodeEmitter.cpp +++ b/clang/lib/AST/Interp/ByteCodeEmitter.cpp @@ -20,8 +20,7 @@ using namespace clang; using namespace clang::interp; -Expected -ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl) { +Function *ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl) { // Set up argument indices. unsigned ParamOffset = 0; SmallVector ParamTypes; @@ -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(*BailLocation); - Func->setIsFullyCompiled(true); return Func; } @@ -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 diff --git a/clang/lib/AST/Interp/ByteCodeEmitter.h b/clang/lib/AST/Interp/ByteCodeEmitter.h index 5520f8c300610..03de286582c91 100644 --- a/clang/lib/AST/Interp/ByteCodeEmitter.h +++ b/clang/lib/AST/Interp/ByteCodeEmitter.h @@ -17,7 +17,6 @@ #include "PrimType.h" #include "Program.h" #include "Source.h" -#include "llvm/Support/Error.h" namespace clang { namespace interp { @@ -32,7 +31,7 @@ class ByteCodeEmitter { public: /// Compiles the function into the module. - llvm::Expected compileFunc(const FunctionDecl *FuncDecl); + Function *compileFunc(const FunctionDecl *FuncDecl); protected: ByteCodeEmitter(Context &Ctx, Program &P) : Ctx(Ctx), P(P) {} @@ -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); @@ -81,8 +75,6 @@ class ByteCodeEmitter { LabelTy NextLabel = 0; /// Offset of the next local variable. unsigned NextLocalOffset = 0; - /// Location of a failure. - std::optional BailLocation; /// Label information for linker. llvm::DenseMap LabelOffsets; /// Location of label relocations. diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp index 5839123a5b95f..4d2fee498877c 100644 --- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp @@ -371,7 +371,7 @@ bool ByteCodeExprGen::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) { @@ -451,7 +451,7 @@ bool ByteCodeExprGen::VisitBinaryOperator(const BinaryOperator *BO) { case BO_LAnd: llvm_unreachable("Already handled earlier"); default: - return this->bail(BO); + return false; } llvm_unreachable("Unhandled binary op"); @@ -504,7 +504,7 @@ bool ByteCodeExprGen::VisitPointerArithBinOp(const BinaryOperator *E) { else if (Op == BO_Sub) return this->emitSubOffset(OffsetType, E); - return this->bail(E); + return false; } template @@ -2358,7 +2358,9 @@ bool ByteCodeExprGen::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 @@ -2378,7 +2380,7 @@ bool ByteCodeExprGen::visitVarDecl(const VarDecl *VD) { std::optional GlobalIndex = P.createGlobal(VD, Init); if (!GlobalIndex) - return this->bail(VD); + return false; assert(Init); { diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.h b/clang/lib/AST/Interp/ByteCodeExprGen.h index bbb13e97e7256..a48fb376d8083 100644 --- a/clang/lib/AST/Interp/ByteCodeExprGen.h +++ b/clang/lib/AST/Interp/ByteCodeExprGen.h @@ -191,10 +191,6 @@ class ByteCodeExprGen : public ConstStmtVisitor, bool>, if (!visitInitializer(Init)) return false; - if ((Init->getType()->isArrayType() || Init->getType()->isRecordType()) && - !this->emitCheckGlobalCtor(Init)) - return false; - return this->emitPopPtr(Init); } diff --git a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp index b1ab5fcf9cb64..d38cde3a4ae8c 100644 --- a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp @@ -262,7 +262,7 @@ bool ByteCodeStmtGen::visitStmt(const Stmt *S) { default: { if (auto *Exp = dyn_cast(S)) return this->discard(Exp); - return this->bail(S); + return false; } } } diff --git a/clang/lib/AST/Interp/Context.cpp b/clang/lib/AST/Interp/Context.cpp index 17abb71635839..75a300bcbace1 100644 --- a/clang/lib/AST/Interp/Context.cpp +++ b/clang/lib/AST/Interp/Context.cpp @@ -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(*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(*this, *P).compileFunc(FD); APValue DummyResult; if (!Run(Parent, Func, DummyResult)) { @@ -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 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 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 C(*this, *P, Parent, Stk, Result); + + 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(); - return false; +#endif + Result = Res.toAPValue(); + return true; } bool Context::evaluateAsInitializer(State &Parent, const VarDecl *VD, APValue &Result) { assert(Stk.empty()); ByteCodeExprGen 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 RValueResult = Res.toRValue(); + if (!RValueResult) + return false; + Result = *RValueResult; + + } else + Result = Res.toAPValue(); + return true; } const LangOptions &Context::getLangOpts() const { return Ctx.getLangOpts(); } @@ -234,12 +278,8 @@ const Function *Context::getOrCreateFunction(const FunctionDecl *FD) { return Func; if (!Func || WasNotDefined) { - if (auto R = ByteCodeStmtGen(*this, *P).compileFunc(FD)) - Func = *R; - else { - llvm::consumeError(R.takeError()); - return nullptr; - } + if (auto F = ByteCodeStmtGen(*this, *P).compileFunc(FD)) + Func = F; } return Func; diff --git a/clang/lib/AST/Interp/Context.h b/clang/lib/AST/Interp/Context.h index 7649caef22428..ab83a8d132246 100644 --- a/clang/lib/AST/Interp/Context.h +++ b/clang/lib/AST/Interp/Context.h @@ -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); diff --git a/clang/lib/AST/Interp/EvalEmitter.cpp b/clang/lib/AST/Interp/EvalEmitter.cpp index 0ff0bde8fd17e..a60f893de8bda 100644 --- a/clang/lib/AST/Interp/EvalEmitter.cpp +++ b/clang/lib/AST/Interp/EvalEmitter.cpp @@ -19,7 +19,7 @@ using namespace clang::interp; EvalEmitter::EvalEmitter(Context &Ctx, Program &P, State &Parent, InterpStack &Stk, APValue &Result) - : Ctx(Ctx), P(P), S(Parent, P, Stk, Ctx, this), Result(Result) { + : Ctx(Ctx), P(P), S(Parent, P, Stk, Ctx, this), EvalResult(&Ctx) { // Create a dummy frame for the interpreter which does not have locals. S.Current = new InterpFrame(S, /*Func=*/nullptr, /*Caller=*/nullptr, CodePtr()); @@ -33,20 +33,22 @@ EvalEmitter::~EvalEmitter() { } } -llvm::Expected EvalEmitter::interpretExpr(const Expr *E) { - if (this->visitExpr(E)) - return true; - if (BailLocation) - return llvm::make_error(*BailLocation); - return false; +EvaluationResult EvalEmitter::interpretExpr(const Expr *E) { + EvalResult.setSource(E); + + if (!this->visitExpr(E)) + EvalResult.setInvalid(); + + return std::move(this->EvalResult); } -llvm::Expected EvalEmitter::interpretDecl(const VarDecl *VD) { - if (this->visitDecl(VD)) - return true; - if (BailLocation) - return llvm::make_error(*BailLocation); - return false; +EvaluationResult EvalEmitter::interpretDecl(const VarDecl *VD) { + EvalResult.setSource(VD); + + if (!this->visitDecl(VD)) + EvalResult.setInvalid(); + + return std::move(this->EvalResult); } void EvalEmitter::emitLabel(LabelTy Label) { @@ -77,12 +79,6 @@ Scope::Local EvalEmitter::createLocal(Descriptor *D) { return {Off, D}; } -bool EvalEmitter::bail(const SourceLocation &Loc) { - if (!BailLocation) - BailLocation = Loc; - return false; -} - bool EvalEmitter::jumpTrue(const LabelTy &Label) { if (isActive()) { if (S.Stk.pop()) @@ -116,125 +112,37 @@ template bool EvalEmitter::emitRet(const SourceInfo &Info) { if (!isActive()) return true; using T = typename PrimConv::T; - return ReturnValue(S.Stk.pop(), Result); + EvalResult.setValue(S.Stk.pop().toAPValue()); + return true; } -bool EvalEmitter::emitRetVoid(const SourceInfo &Info) { return true; } +template <> bool EvalEmitter::emitRet(const SourceInfo &Info) { + if (!isActive()) + return true; + EvalResult.setPointer(S.Stk.pop()); + return true; +} +template <> bool EvalEmitter::emitRet(const SourceInfo &Info) { + if (!isActive()) + return true; + EvalResult.setFunctionPointer(S.Stk.pop()); + return true; +} + +bool EvalEmitter::emitRetVoid(const SourceInfo &Info) { + EvalResult.setValid(); + return true; +} bool EvalEmitter::emitRetValue(const SourceInfo &Info) { - // Method to recursively traverse composites. - std::function Composite; - Composite = [this, &Composite](QualType Ty, const Pointer &Ptr, APValue &R) { - if (const auto *AT = Ty->getAs()) - Ty = AT->getValueType(); - - if (const auto *RT = Ty->getAs()) { - const auto *Record = Ptr.getRecord(); - assert(Record && "Missing record descriptor"); - - bool Ok = true; - if (RT->getDecl()->isUnion()) { - const FieldDecl *ActiveField = nullptr; - APValue Value; - for (const auto &F : Record->fields()) { - const Pointer &FP = Ptr.atField(F.Offset); - QualType FieldTy = F.Decl->getType(); - if (FP.isActive()) { - if (std::optional T = Ctx.classify(FieldTy)) { - TYPE_SWITCH(*T, Ok &= ReturnValue(FP.deref(), Value)); - } else { - Ok &= Composite(FieldTy, FP, Value); - } - break; - } - } - R = APValue(ActiveField, Value); - } else { - unsigned NF = Record->getNumFields(); - unsigned NB = Record->getNumBases(); - unsigned NV = Ptr.isBaseClass() ? 0 : Record->getNumVirtualBases(); - - R = APValue(APValue::UninitStruct(), NB, NF); - - for (unsigned I = 0; I < NF; ++I) { - const Record::Field *FD = Record->getField(I); - QualType FieldTy = FD->Decl->getType(); - const Pointer &FP = Ptr.atField(FD->Offset); - APValue &Value = R.getStructField(I); - - if (std::optional T = Ctx.classify(FieldTy)) { - TYPE_SWITCH(*T, Ok &= ReturnValue(FP.deref(), Value)); - } else { - Ok &= Composite(FieldTy, FP, Value); - } - } - - for (unsigned I = 0; I < NB; ++I) { - const Record::Base *BD = Record->getBase(I); - QualType BaseTy = Ctx.getASTContext().getRecordType(BD->Decl); - const Pointer &BP = Ptr.atField(BD->Offset); - Ok &= Composite(BaseTy, BP, R.getStructBase(I)); - } - - for (unsigned I = 0; I < NV; ++I) { - const Record::Base *VD = Record->getVirtualBase(I); - QualType VirtBaseTy = Ctx.getASTContext().getRecordType(VD->Decl); - const Pointer &VP = Ptr.atField(VD->Offset); - Ok &= Composite(VirtBaseTy, VP, R.getStructBase(NB + I)); - } - } - return Ok; - } - - if (Ty->isIncompleteArrayType()) { - R = APValue(APValue::UninitArray(), 0, 0); - return true; - } - - if (const auto *AT = Ty->getAsArrayTypeUnsafe()) { - const size_t NumElems = Ptr.getNumElems(); - QualType ElemTy = AT->getElementType(); - R = APValue(APValue::UninitArray{}, NumElems, NumElems); - - bool Ok = true; - for (unsigned I = 0; I < NumElems; ++I) { - APValue &Slot = R.getArrayInitializedElt(I); - const Pointer &EP = Ptr.atIndex(I); - if (std::optional T = Ctx.classify(ElemTy)) { - TYPE_SWITCH(*T, Ok &= ReturnValue(EP.deref(), Slot)); - } else { - Ok &= Composite(ElemTy, EP.narrow(), Slot); - } - } - return Ok; - } - - // Complex types. - if (const auto *CT = Ty->getAs()) { - QualType ElemTy = CT->getElementType(); - std::optional ElemT = Ctx.classify(ElemTy); - assert(ElemT); - - if (ElemTy->isIntegerType()) { - INT_TYPE_SWITCH(*ElemT, { - auto V1 = Ptr.atIndex(0).deref(); - auto V2 = Ptr.atIndex(1).deref(); - Result = APValue(V1.toAPSInt(), V2.toAPSInt()); - return true; - }); - } else if (ElemTy->isFloatingType()) { - Result = APValue(Ptr.atIndex(0).deref().getAPFloat(), - Ptr.atIndex(1).deref().getAPFloat()); - return true; - } - return false; - } - llvm_unreachable("invalid value to return"); - }; - - // Return the composite type. const auto &Ptr = S.Stk.pop(); - return Composite(Ptr.getType(), Ptr, Result); + if (std::optional APV = Ptr.toRValue(S.getCtx())) { + EvalResult.setValue(*APV); + return true; + } + + EvalResult.setInvalid(); + return false; } bool EvalEmitter::emitGetPtrLocal(uint32_t I, const SourceInfo &Info) { diff --git a/clang/lib/AST/Interp/EvalEmitter.h b/clang/lib/AST/Interp/EvalEmitter.h index 5a9be18c34a03..deb2ebc4e61fa 100644 --- a/clang/lib/AST/Interp/EvalEmitter.h +++ b/clang/lib/AST/Interp/EvalEmitter.h @@ -13,6 +13,7 @@ #ifndef LLVM_CLANG_AST_INTERP_EVALEMITTER_H #define LLVM_CLANG_AST_INTERP_EVALEMITTER_H +#include "EvaluationResult.h" #include "InterpState.h" #include "PrimType.h" #include "Source.h" @@ -33,8 +34,10 @@ class EvalEmitter : public SourceMapper { using AddrTy = uintptr_t; using Local = Scope::Local; - llvm::Expected interpretExpr(const Expr *E); - llvm::Expected interpretDecl(const VarDecl *VD); + EvaluationResult interpretExpr(const Expr *E); + EvaluationResult interpretDecl(const VarDecl *VD); + + InterpState &getState() { return S; } protected: EvalEmitter(Context &Ctx, Program &P, State &Parent, InterpStack &Stk, @@ -51,10 +54,6 @@ class EvalEmitter : public SourceMapper { virtual bool visitExpr(const Expr *E) = 0; virtual bool visitDecl(const VarDecl *VD) = 0; - 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); @@ -86,7 +85,7 @@ class EvalEmitter : public SourceMapper { /// Callee evaluation state. InterpState S; /// Location to write the result to. - APValue &Result; + EvaluationResult EvalResult; /// Temporaries which require storage. llvm::DenseMap> Locals; @@ -100,8 +99,6 @@ class EvalEmitter : public SourceMapper { // The emitter always tracks the current instruction and sets OpPC to a token // value which is mapped to the location of the opcode being evaluated. CodePtr OpPC; - /// Location of a failure. - std::optional BailLocation; /// Location of the current instruction. SourceInfo CurrentSource; diff --git a/clang/lib/AST/Interp/EvaluationResult.cpp b/clang/lib/AST/Interp/EvaluationResult.cpp new file mode 100644 index 0000000000000..a14dc87f1dfde --- /dev/null +++ b/clang/lib/AST/Interp/EvaluationResult.cpp @@ -0,0 +1,196 @@ +//===----- EvaluationResult.cpp - Result class for the VM ------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "EvaluationResult.h" +#include "Context.h" +#include "InterpState.h" +#include "Record.h" +#include "clang/AST/ExprCXX.h" + +namespace clang { +namespace interp { + +APValue EvaluationResult::toAPValue() const { + assert(!empty()); + switch (Kind) { + case LValue: + // Either a pointer or a function pointer. + if (const auto *P = std::get_if(&Value)) + return P->toAPValue(); + else if (const auto *FP = std::get_if(&Value)) + return FP->toAPValue(); + else + llvm_unreachable("Unhandled LValue type"); + break; + case RValue: + return std::get(Value); + case Valid: + return APValue(); + default: + llvm_unreachable("Unhandled result kind?"); + } +} + +std::optional EvaluationResult::toRValue() const { + if (Kind == RValue) + return toAPValue(); + + assert(Kind == LValue); + + // We have a pointer and want an RValue. + if (const auto *P = std::get_if(&Value)) + return P->toRValue(*Ctx); + else if (const auto *FP = std::get_if(&Value)) // Nope + return FP->toAPValue(); + llvm_unreachable("Unhandled lvalue kind"); +} + +static void DiagnoseUninitializedSubobject(InterpState &S, SourceLocation Loc, + const FieldDecl *SubObjDecl) { + assert(SubObjDecl && "Subobject declaration does not exist"); + S.FFDiag(Loc, diag::note_constexpr_uninitialized) + << /*(name)*/ 1 << SubObjDecl; + S.Note(SubObjDecl->getLocation(), + diag::note_constexpr_subobject_declared_here); +} + +static bool CheckFieldsInitialized(InterpState &S, SourceLocation Loc, + const Pointer &BasePtr, const Record *R); + +static bool CheckArrayInitialized(InterpState &S, SourceLocation Loc, + const Pointer &BasePtr, + const ConstantArrayType *CAT) { + bool Result = true; + size_t NumElems = CAT->getSize().getZExtValue(); + QualType ElemType = CAT->getElementType(); + + if (ElemType->isRecordType()) { + const Record *R = BasePtr.getElemRecord(); + for (size_t I = 0; I != NumElems; ++I) { + Pointer ElemPtr = BasePtr.atIndex(I).narrow(); + Result &= CheckFieldsInitialized(S, Loc, ElemPtr, R); + } + } else if (const auto *ElemCAT = dyn_cast(ElemType)) { + for (size_t I = 0; I != NumElems; ++I) { + Pointer ElemPtr = BasePtr.atIndex(I).narrow(); + Result &= CheckArrayInitialized(S, Loc, ElemPtr, ElemCAT); + } + } else { + for (size_t I = 0; I != NumElems; ++I) { + if (!BasePtr.atIndex(I).isInitialized()) { + DiagnoseUninitializedSubobject(S, Loc, BasePtr.getField()); + Result = false; + } + } + } + + return Result; +} + +static bool CheckFieldsInitialized(InterpState &S, SourceLocation Loc, + const Pointer &BasePtr, const Record *R) { + assert(R); + bool Result = true; + // Check all fields of this record are initialized. + for (const Record::Field &F : R->fields()) { + Pointer FieldPtr = BasePtr.atField(F.Offset); + QualType FieldType = F.Decl->getType(); + + if (FieldType->isRecordType()) { + Result &= CheckFieldsInitialized(S, Loc, FieldPtr, FieldPtr.getRecord()); + } else if (FieldType->isIncompleteArrayType()) { + // Nothing to do here. + } else if (FieldType->isArrayType()) { + const auto *CAT = + cast(FieldType->getAsArrayTypeUnsafe()); + Result &= CheckArrayInitialized(S, Loc, FieldPtr, CAT); + } else if (!FieldPtr.isInitialized()) { + DiagnoseUninitializedSubobject(S, Loc, F.Decl); + Result = false; + } + } + + // Check Fields in all bases + for (const Record::Base &B : R->bases()) { + Pointer P = BasePtr.atField(B.Offset); + if (!P.isInitialized()) { + S.FFDiag(BasePtr.getDeclDesc()->asDecl()->getLocation(), + diag::note_constexpr_uninitialized_base) + << B.Desc->getType(); + return false; + } + Result &= CheckFieldsInitialized(S, Loc, P, B.R); + } + + // TODO: Virtual bases + + return Result; +} + +bool EvaluationResult::checkFullyInitialized(InterpState &S) const { + assert(Source); + assert(isLValue()); + + // Our Source must be a VarDecl. + const Decl *SourceDecl = Source.dyn_cast(); + assert(SourceDecl); + const auto *VD = cast(SourceDecl); + assert(VD->getType()->isRecordType() || VD->getType()->isArrayType()); + SourceLocation InitLoc = VD->getAnyInitializer()->getExprLoc(); + + const Pointer &Ptr = *std::get_if(&Value); + assert(!Ptr.isZero()); + + if (const Record *R = Ptr.getRecord()) + return CheckFieldsInitialized(S, InitLoc, Ptr, R); + const auto *CAT = + cast(Ptr.getType()->getAsArrayTypeUnsafe()); + return CheckArrayInitialized(S, InitLoc, Ptr, CAT); + + return true; +} + +void EvaluationResult::dump() const { + assert(Ctx); + auto &OS = llvm::errs(); + const ASTContext &ASTCtx = Ctx->getASTContext(); + + switch (Kind) { + case Empty: + OS << "Empty\n"; + break; + case RValue: + OS << "RValue: "; + std::get(Value).dump(OS, ASTCtx); + break; + case LValue: { + assert(Source); + QualType SourceType; + if (const auto *D = Source.dyn_cast()) { + if (const auto *VD = dyn_cast(D)) + SourceType = VD->getType(); + } else if (const auto *E = Source.dyn_cast()) { + SourceType = E->getType(); + } + + OS << "LValue: "; + if (const auto *P = std::get_if(&Value)) + P->toAPValue().printPretty(OS, ASTCtx, SourceType); + else if (const auto *FP = std::get_if(&Value)) // Nope + FP->toAPValue().printPretty(OS, ASTCtx, SourceType); + OS << "\n"; + break; + } + + default: + llvm_unreachable("Can't print that."); + } +} + +} // namespace interp +} // namespace clang diff --git a/clang/lib/AST/Interp/EvaluationResult.h b/clang/lib/AST/Interp/EvaluationResult.h new file mode 100644 index 0000000000000..2b9fc16f1a0ab --- /dev/null +++ b/clang/lib/AST/Interp/EvaluationResult.h @@ -0,0 +1,111 @@ +//===------ EvaluationResult.h - Result class for the VM -------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_INTERP_EVALUATION_RESULT_H +#define LLVM_CLANG_AST_INTERP_EVALUATION_RESULT_H + +#include "FunctionPointer.h" +#include "Pointer.h" +#include "clang/AST/APValue.h" +#include "clang/AST/Decl.h" +#include "clang/AST/Expr.h" +#include +#include + +namespace clang { +namespace interp { +class EvalEmitter; +class Context; + +/// Defines the result of an evaluation. +/// +/// The result might be in different forms--one of the pointer types, +/// an APValue, or nothing. +/// +/// We use this class to inspect and diagnose the result, as well as +/// convert it to the requested form. +class EvaluationResult final { +public: + enum ResultKind { + Empty, // Initial state. + LValue, // Result is an lvalue/pointer. + RValue, // Result is an rvalue. + Invalid, // Result is invalid. + Valid, // Result is valid and empty. + }; + + using DeclTy = llvm::PointerUnion; + +private: + const Context *Ctx = nullptr; + std::variant Value; + ResultKind Kind = Empty; + DeclTy Source = nullptr; // Currently only needed for dump(). + + EvaluationResult(ResultKind Kind) : Kind(Kind) { + // Leave everything empty. Can be used as an + // error marker or for void return values. + assert(Kind == Valid || Kind == Invalid); + } + + void setSource(DeclTy D) { Source = D; } + + void setValue(const APValue &V) { + assert(empty()); + assert(!V.isLValue()); + Value = std::move(V); + Kind = RValue; + } + void setPointer(const Pointer P) { + assert(empty()); + Value = P; + Kind = LValue; + } + void setFunctionPointer(const FunctionPointer &P) { + assert(empty()); + Value = P; + Kind = LValue; + } + void setInvalid() { + assert(empty()); + Kind = Invalid; + } + void setValid() { + assert(empty()); + Kind = Valid; + } + +public: + EvaluationResult(const Context *Ctx) : Ctx(Ctx) {} + + bool empty() const { return Kind == Empty; } + bool isInvalid() const { return Kind == Invalid; } + bool isLValue() const { return Kind == LValue; } + bool isRValue() const { return Kind == RValue; } + + /// Returns an APValue for the evaluation result. The returned + /// APValue might be an LValue or RValue. + APValue toAPValue() const; + + /// If the result is an LValue, convert that to an RValue + /// and return it. This may fail, e.g. if the result is an + /// LValue and we can't read from it. + std::optional toRValue() const; + + bool checkFullyInitialized(InterpState &S) const; + + /// Dump to stderr. + void dump() const; + + friend class EvalEmitter; +}; + +} // namespace interp +} // namespace clang + +#endif diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp index b95a52199846f..6e5b2204e4005 100644 --- a/clang/lib/AST/Interp/Interp.cpp +++ b/clang/lib/AST/Interp/Interp.cpp @@ -474,98 +474,6 @@ bool CheckPure(InterpState &S, CodePtr OpPC, const CXXMethodDecl *MD) { return false; } -static void DiagnoseUninitializedSubobject(InterpState &S, const SourceInfo &SI, - const FieldDecl *SubObjDecl) { - assert(SubObjDecl && "Subobject declaration does not exist"); - S.FFDiag(SI, diag::note_constexpr_uninitialized) - << /*(name)*/ 1 << SubObjDecl; - S.Note(SubObjDecl->getLocation(), - diag::note_constexpr_subobject_declared_here); -} - -static bool CheckFieldsInitialized(InterpState &S, CodePtr OpPC, - const Pointer &BasePtr, const Record *R); - -static bool CheckArrayInitialized(InterpState &S, CodePtr OpPC, - const Pointer &BasePtr, - const ConstantArrayType *CAT) { - bool Result = true; - size_t NumElems = CAT->getSize().getZExtValue(); - QualType ElemType = CAT->getElementType(); - - if (ElemType->isRecordType()) { - const Record *R = BasePtr.getElemRecord(); - for (size_t I = 0; I != NumElems; ++I) { - Pointer ElemPtr = BasePtr.atIndex(I).narrow(); - Result &= CheckFieldsInitialized(S, OpPC, ElemPtr, R); - } - } else if (const auto *ElemCAT = dyn_cast(ElemType)) { - for (size_t I = 0; I != NumElems; ++I) { - Pointer ElemPtr = BasePtr.atIndex(I).narrow(); - Result &= CheckArrayInitialized(S, OpPC, ElemPtr, ElemCAT); - } - } else { - for (size_t I = 0; I != NumElems; ++I) { - if (!BasePtr.atIndex(I).isInitialized()) { - DiagnoseUninitializedSubobject(S, S.Current->getSource(OpPC), - BasePtr.getField()); - Result = false; - } - } - } - - return Result; -} - -static bool CheckFieldsInitialized(InterpState &S, CodePtr OpPC, - const Pointer &BasePtr, const Record *R) { - assert(R); - bool Result = true; - // Check all fields of this record are initialized. - for (const Record::Field &F : R->fields()) { - Pointer FieldPtr = BasePtr.atField(F.Offset); - QualType FieldType = F.Decl->getType(); - - if (FieldType->isRecordType()) { - Result &= CheckFieldsInitialized(S, OpPC, FieldPtr, FieldPtr.getRecord()); - } else if (FieldType->isIncompleteArrayType()) { - // Nothing to do here. - } else if (FieldType->isArrayType()) { - const auto *CAT = - cast(FieldType->getAsArrayTypeUnsafe()); - Result &= CheckArrayInitialized(S, OpPC, FieldPtr, CAT); - } else if (!FieldPtr.isInitialized()) { - DiagnoseUninitializedSubobject(S, S.Current->getSource(OpPC), F.Decl); - Result = false; - } - } - - // Check Fields in all bases - for (const Record::Base &B : R->bases()) { - Pointer P = BasePtr.atField(B.Offset); - if (!P.isInitialized()) { - S.FFDiag(BasePtr.getDeclDesc()->asDecl()->getLocation(), - diag::note_constexpr_uninitialized_base) - << B.Desc->getType(); - return false; - } - Result &= CheckFieldsInitialized(S, OpPC, P, B.R); - } - - // TODO: Virtual bases - - return Result; -} - -bool CheckCtorCall(InterpState &S, CodePtr OpPC, const Pointer &This) { - assert(!This.isZero()); - if (const Record *R = This.getRecord()) - return CheckFieldsInitialized(S, OpPC, This, R); - const auto *CAT = - cast(This.getType()->getAsArrayTypeUnsafe()); - return CheckArrayInitialized(S, OpPC, This, CAT); -} - bool CheckPotentialReinterpretCast(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { if (!S.inConstantContext()) diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h index dbbc4c09ce42a..fb7bb017f8e14 100644 --- a/clang/lib/AST/Interp/Interp.h +++ b/clang/lib/AST/Interp/Interp.h @@ -111,9 +111,6 @@ bool CheckThis(InterpState &S, CodePtr OpPC, const Pointer &This); /// Checks if a method is pure virtual. bool CheckPure(InterpState &S, CodePtr OpPC, const CXXMethodDecl *MD); -/// Checks that all fields are initialized after a constructor call. -bool CheckCtorCall(InterpState &S, CodePtr OpPC, const Pointer &This); - /// Checks if reinterpret casts are legal in the current context. bool CheckPotentialReinterpretCast(InterpState &S, CodePtr OpPC, const Pointer &Ptr); @@ -1061,8 +1058,12 @@ inline bool InitGlobalTempComp(InterpState &S, CodePtr OpPC, const Pointer &P = S.Stk.peek(); APValue *Cached = Temp->getOrCreateValue(true); - *Cached = P.toRValue(S.getCtx()); - return true; + if (std::optional APV = P.toRValue(S.getCtx())) { + *Cached = *APV; + return true; + } + + return false; } template ::T> @@ -1860,11 +1861,6 @@ inline bool ArrayElemPtrPop(InterpState &S, CodePtr OpPC) { return NarrowPtr(S, OpPC); } -inline bool CheckGlobalCtor(InterpState &S, CodePtr OpPC) { - const Pointer &Obj = S.Stk.peek(); - return CheckCtorCall(S, OpPC, Obj); -} - inline bool Call(InterpState &S, CodePtr OpPC, const Function *Func) { if (Func->hasThisPointer()) { size_t ThisOffset = diff --git a/clang/lib/AST/Interp/Opcodes.td b/clang/lib/AST/Interp/Opcodes.td index e01b6b9eea7db..adcecdfa162b9 100644 --- a/clang/lib/AST/Interp/Opcodes.td +++ b/clang/lib/AST/Interp/Opcodes.td @@ -375,8 +375,6 @@ def GetLocal : AccessOpcode { let HasCustomEval = 1; } // [] -> [Pointer] def SetLocal : AccessOpcode { let HasCustomEval = 1; } -def CheckGlobalCtor : Opcode {} - // [] -> [Value] def GetGlobal : AccessOpcode; def GetGlobalUnchecked : AccessOpcode; diff --git a/clang/lib/AST/Interp/Pointer.cpp b/clang/lib/AST/Interp/Pointer.cpp index e979b99b0fdd0..5af1d6d52e93e 100644 --- a/clang/lib/AST/Interp/Pointer.cpp +++ b/clang/lib/AST/Interp/Pointer.cpp @@ -231,33 +231,143 @@ bool Pointer::hasSameArray(const Pointer &A, const Pointer &B) { return hasSameBase(A, B) && A.Base == B.Base && A.getFieldDesc()->IsArray; } -APValue Pointer::toRValue(const Context &Ctx) const { - // Primitives. - if (getFieldDesc()->isPrimitive()) { - PrimType PT = *Ctx.classify(getType()); - TYPE_SWITCH(PT, return deref().toAPValue()); - llvm_unreachable("Unhandled PrimType?"); - } +std::optional Pointer::toRValue(const Context &Ctx) const { + // Method to recursively traverse composites. + std::function Composite; + Composite = [&Composite, &Ctx](QualType Ty, const Pointer &Ptr, APValue &R) { + if (const auto *AT = Ty->getAs()) + Ty = AT->getValueType(); + + // Invalid pointers. + if (Ptr.isDummy() || !Ptr.isLive() || + (!Ptr.isUnknownSizeArray() && Ptr.isOnePastEnd())) + return false; - APValue Result; - // Records. - if (getFieldDesc()->isRecord()) { - const Record *R = getRecord(); - Result = - APValue(APValue::UninitStruct(), R->getNumBases(), R->getNumFields()); - - for (unsigned I = 0; I != R->getNumFields(); ++I) { - const Pointer &FieldPtr = this->atField(R->getField(I)->Offset); - Result.getStructField(I) = FieldPtr.toRValue(Ctx); + // Primitive values. + if (std::optional T = Ctx.classify(Ty)) { + if (T == PT_Ptr || T == PT_FnPtr) { + R = Ptr.toAPValue(); + } else { + TYPE_SWITCH(*T, R = Ptr.deref().toAPValue()); + } + return true; } - for (unsigned I = 0; I != R->getNumBases(); ++I) { - const Pointer &BasePtr = this->atField(R->getBase(I)->Offset); - Result.getStructBase(I) = BasePtr.toRValue(Ctx); + if (const auto *RT = Ty->getAs()) { + const auto *Record = Ptr.getRecord(); + assert(Record && "Missing record descriptor"); + + bool Ok = true; + if (RT->getDecl()->isUnion()) { + const FieldDecl *ActiveField = nullptr; + APValue Value; + for (const auto &F : Record->fields()) { + const Pointer &FP = Ptr.atField(F.Offset); + QualType FieldTy = F.Decl->getType(); + if (FP.isActive()) { + if (std::optional T = Ctx.classify(FieldTy)) { + TYPE_SWITCH(*T, Value = FP.deref().toAPValue()); + } else { + Ok &= Composite(FieldTy, FP, Value); + } + break; + } + } + R = APValue(ActiveField, Value); + } else { + unsigned NF = Record->getNumFields(); + unsigned NB = Record->getNumBases(); + unsigned NV = Ptr.isBaseClass() ? 0 : Record->getNumVirtualBases(); + + R = APValue(APValue::UninitStruct(), NB, NF); + + for (unsigned I = 0; I < NF; ++I) { + const Record::Field *FD = Record->getField(I); + QualType FieldTy = FD->Decl->getType(); + const Pointer &FP = Ptr.atField(FD->Offset); + APValue &Value = R.getStructField(I); + + if (std::optional T = Ctx.classify(FieldTy)) { + TYPE_SWITCH(*T, Value = FP.deref().toAPValue()); + } else { + Ok &= Composite(FieldTy, FP, Value); + } + } + + for (unsigned I = 0; I < NB; ++I) { + const Record::Base *BD = Record->getBase(I); + QualType BaseTy = Ctx.getASTContext().getRecordType(BD->Decl); + const Pointer &BP = Ptr.atField(BD->Offset); + Ok &= Composite(BaseTy, BP, R.getStructBase(I)); + } + + for (unsigned I = 0; I < NV; ++I) { + const Record::Base *VD = Record->getVirtualBase(I); + QualType VirtBaseTy = Ctx.getASTContext().getRecordType(VD->Decl); + const Pointer &VP = Ptr.atField(VD->Offset); + Ok &= Composite(VirtBaseTy, VP, R.getStructBase(NB + I)); + } + } + return Ok; } - } - // TODO: Arrays + if (Ty->isIncompleteArrayType()) { + R = APValue(APValue::UninitArray(), 0, 0); + return true; + } + + if (const auto *AT = Ty->getAsArrayTypeUnsafe()) { + const size_t NumElems = Ptr.getNumElems(); + QualType ElemTy = AT->getElementType(); + R = APValue(APValue::UninitArray{}, NumElems, NumElems); + + bool Ok = true; + for (unsigned I = 0; I < NumElems; ++I) { + APValue &Slot = R.getArrayInitializedElt(I); + const Pointer &EP = Ptr.atIndex(I); + if (std::optional T = Ctx.classify(ElemTy)) { + TYPE_SWITCH(*T, Slot = EP.deref().toAPValue()); + } else { + Ok &= Composite(ElemTy, EP.narrow(), Slot); + } + } + return Ok; + } + // Complex types. + if (const auto *CT = Ty->getAs()) { + QualType ElemTy = CT->getElementType(); + std::optional ElemT = Ctx.classify(ElemTy); + assert(ElemT); + + if (ElemTy->isIntegerType()) { + INT_TYPE_SWITCH(*ElemT, { + auto V1 = Ptr.atIndex(0).deref(); + auto V2 = Ptr.atIndex(1).deref(); + R = APValue(V1.toAPSInt(), V2.toAPSInt()); + return true; + }); + } else if (ElemTy->isFloatingType()) { + R = APValue(Ptr.atIndex(0).deref().getAPFloat(), + Ptr.atIndex(1).deref().getAPFloat()); + return true; + } + return false; + } + + llvm_unreachable("invalid value to return"); + }; + + if (isZero()) + return APValue(static_cast(nullptr), CharUnits::Zero(), {}, false, + true); + + if (isDummy() || !isLive()) + return std::nullopt; + + // Return the composite type. + APValue Result; + if (!Composite(getType(), *this, Result)) + return std::nullopt; return Result; } diff --git a/clang/lib/AST/Interp/Pointer.h b/clang/lib/AST/Interp/Pointer.h index a8f6e62fa76d3..8ccaff41ded8d 100644 --- a/clang/lib/AST/Interp/Pointer.h +++ b/clang/lib/AST/Interp/Pointer.h @@ -98,7 +98,7 @@ class Pointer { } /// Converts the pointer to an APValue that is an rvalue. - APValue toRValue(const Context &Ctx) const; + std::optional toRValue(const Context &Ctx) const; /// Offsets a pointer inside an array. [[nodiscard]] Pointer atIndex(unsigned Idx) const { @@ -379,6 +379,7 @@ class Pointer { return *reinterpret_cast(Pointee->rawData() + Base + sizeof(InitMapPtr)); + assert(Offset + sizeof(T) <= Pointee->getDescriptor()->getAllocSize()); return *reinterpret_cast(Pointee->rawData() + Offset); } diff --git a/clang/test/AST/Interp/records.cpp b/clang/test/AST/Interp/records.cpp index a1ced049dcedb..f5033c45eeb5e 100644 --- a/clang/test/AST/Interp/records.cpp +++ b/clang/test/AST/Interp/records.cpp @@ -170,7 +170,6 @@ class Bar { // expected-note {{definition of 'Bar' is not complete}} \ // ref-error {{has incomplete type 'const Bar'}} }; constexpr Bar B; // expected-error {{must be initialized by a constant expression}} \ - // expected-error {{failed to evaluate an expression}} \ // ref-error {{must be initialized by a constant expression}} constexpr Bar *pb = nullptr;