diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt --- a/clang/lib/AST/CMakeLists.txt +++ b/clang/lib/AST/CMakeLists.txt @@ -52,6 +52,7 @@ FormatString.cpp InheritViz.cpp Interp/Block.cpp + Interp/Boolean.cpp Interp/ByteCodeEmitter.cpp Interp/ByteCodeExprGen.cpp Interp/ByteCodeGenError.cpp @@ -62,8 +63,9 @@ Interp/EvalEmitter.cpp Interp/Frame.cpp Interp/Function.cpp - Interp/Interp.cpp + Interp/InterpLoop.cpp Interp/InterpFrame.cpp + Interp/InterpHelper.cpp Interp/InterpStack.cpp Interp/InterpState.cpp Interp/Pointer.cpp diff --git a/clang/lib/AST/Interp/Block.h b/clang/lib/AST/Interp/Block.h --- a/clang/lib/AST/Interp/Block.h +++ b/clang/lib/AST/Interp/Block.h @@ -55,6 +55,8 @@ bool isStatic() const { return IsStatic; } /// Checks if the block is temporary. bool isTemporary() const { return Desc->IsTemporary; } + /// Checks if the block is constant. + bool isConst() const { return Desc->IsConst; } /// Returns the size of the block. InterpSize getSize() const { return Desc->getAllocSize(); } /// Returns the declaration ID. diff --git a/clang/lib/AST/Interp/Boolean.h b/clang/lib/AST/Interp/Boolean.h --- a/clang/lib/AST/Interp/Boolean.h +++ b/clang/lib/AST/Interp/Boolean.h @@ -11,7 +11,6 @@ #include #include -#include "Integral.h" #include "clang/AST/APValue.h" #include "clang/AST/ComparisonCategories.h" #include "llvm/ADT/APSInt.h" @@ -21,6 +20,9 @@ namespace clang { namespace interp { +template class Integral; +template class FixedIntegral; + /// Wrapper around boolean types. class Boolean { private: @@ -34,6 +36,9 @@ /// Zero-initializes a boolean. Boolean() : V(false) {} + /// Initializes a boolean from an APSInt. + explicit Boolean(const llvm::APSInt &V) : V(V != 0) {} + bool operator<(Boolean RHS) const { return V < RHS.V; } bool operator>(Boolean RHS) const { return V > RHS.V; } bool operator<=(Boolean RHS) const { return V <= RHS.V; } @@ -43,18 +48,30 @@ bool operator>(unsigned RHS) const { return static_cast(V) > RHS; } - Boolean operator-() const { return Boolean(V); } + Boolean operator+(Boolean RHS) const { return Boolean(V | RHS.V); } + Boolean operator-(Boolean RHS) const { return Boolean(V ^ RHS.V); } + Boolean operator*(Boolean RHS) const { return Boolean(V & RHS.V); } + Boolean operator/(Boolean RHS) const { return Boolean(V); } + Boolean operator%(Boolean RHS) const { return Boolean(V % RHS.V); } + Boolean operator&(Boolean RHS) const { return Boolean(V & RHS.V); } + Boolean operator|(Boolean RHS) const { return Boolean(V | RHS.V); } + Boolean operator^(Boolean RHS) const { return Boolean(V ^ RHS.V); } + + Boolean operator-() const { return *this; } Boolean operator~() const { return Boolean(true); } + Boolean operator>>(unsigned RHS) const { return Boolean(V && RHS == 0); } + Boolean operator<<(unsigned RHS) const { return *this; } + explicit operator unsigned() const { return V; } explicit operator int64_t() const { return V; } explicit operator uint64_t() const { return V; } - APSInt toAPSInt() const { - return APSInt(APInt(1, static_cast(V), false), true); + llvm::APSInt toAPSInt() const { + return llvm::APSInt(llvm::APInt(1, static_cast(V), false), true); } - APSInt toAPSInt(unsigned NumBits) const { - return APSInt(toAPSInt().zextOrTrunc(NumBits), true); + llvm::APSInt toAPSInt(unsigned NumBits) const { + return llvm::APSInt(toAPSInt().zextOrTrunc(NumBits), true); } APValue toAPValue() const { return APValue(toAPSInt()); } @@ -62,6 +79,8 @@ constexpr static unsigned bitWidth() { return true; } bool isZero() const { return !V; } + bool isTrue() const { return !isZero(); } + bool isFalse() const { return isZero(); } bool isMin() const { return isZero(); } constexpr static bool isMinusOne() { return false; } @@ -72,7 +91,11 @@ constexpr static bool isPositive() { return !isNegative(); } ComparisonCategoryResult compare(const Boolean &RHS) const { - return Compare(V, RHS.V); + if (!V && RHS.V) + return ComparisonCategoryResult::Less; + if (V && !RHS.V) + return ComparisonCategoryResult::Greater; + return ComparisonCategoryResult::Equal; } unsigned countLeadingZeros() const { return V ? 0 : 1; } @@ -91,17 +114,14 @@ } template - static typename std::enable_if::type from( - Integral Value) { - return Boolean(!Value.isZero()); - } + static Boolean from(const Integral &Value); template - static Boolean from(Integral<0, SrcSign> Value) { - return Boolean(!Value.isZero()); - } + static Boolean from(const FixedIntegral &I); + + static Boolean from(Boolean Value) { return Value; } - static Boolean zero() { return from(false); } + static Boolean zero() { return Boolean(false); } template static Boolean from(T Value, unsigned NumBits) { @@ -122,7 +142,7 @@ } static bool add(Boolean A, Boolean B, unsigned OpBits, Boolean *R) { - *R = Boolean(A.V || B.V); + *R = Boolean(A.V | B.V); return false; } @@ -132,7 +152,7 @@ } static bool mul(Boolean A, Boolean B, unsigned OpBits, Boolean *R) { - *R = Boolean(A.V && B.V); + *R = Boolean(A.V & B.V); return false; } }; diff --git a/clang/lib/AST/Interp/Boolean.cpp b/clang/lib/AST/Interp/Boolean.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/AST/Interp/Boolean.cpp @@ -0,0 +1,7 @@ +//===--- Boolean.cpp - Wrapper for boolean types ----------------*- 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 +// +//===----------------------------------------------------------------------===// diff --git a/clang/lib/AST/Interp/ByteCodeEmitter.h b/clang/lib/AST/Interp/ByteCodeEmitter.h --- a/clang/lib/AST/Interp/ByteCodeEmitter.h +++ b/clang/lib/AST/Interp/ByteCodeEmitter.h @@ -40,7 +40,7 @@ llvm::Expected compileFunc(const FunctionDecl *F); protected: - ByteCodeEmitter(Context &Ctx, Program &P) : Ctx(Ctx), P(P) {} + ByteCodeEmitter(Context &Ctx, Program &P, State &S) : Ctx(Ctx), P(P) {} virtual ~ByteCodeEmitter() {} diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.h b/clang/lib/AST/Interp/ByteCodeExprGen.h --- a/clang/lib/AST/Interp/ByteCodeExprGen.h +++ b/clang/lib/AST/Interp/ByteCodeExprGen.h @@ -33,7 +33,6 @@ template class LocalScope; template class RecordScope; template class VariableScope; -template class DeclScope; template class OptionScope; /// Compilation context for expressions. @@ -58,18 +57,24 @@ Context &Ctx; /// Program to link to. Program &P; + /// Execution state. + State &S; public: /// Initializes the compiler and the backend emitter. template - ByteCodeExprGen(Context &Ctx, Program &P, Tys &&... Args) - : Emitter(Ctx, P, Args...), Ctx(Ctx), P(P) {} + ByteCodeExprGen(Context &Ctx, Program &P, State &S, Tys &&... Args) + : Emitter(Ctx, P, S, Args...), Ctx(Ctx), P(P), S(S) {} // Expression visitors - result returned on stack. + bool VisitDeclRefExpr(const DeclRefExpr *E); bool VisitCastExpr(const CastExpr *E); + bool VisitConstantExpr(const ConstantExpr *E); bool VisitIntegerLiteral(const IntegerLiteral *E); bool VisitParenExpr(const ParenExpr *E); bool VisitBinaryOperator(const BinaryOperator *E); + bool VisitUnaryMinus(const UnaryOperator *E); + bool VisitCallExpr(const CallExpr *E); protected: bool visitExpr(const Expr *E) override; @@ -79,23 +84,18 @@ /// Emits scope cleanup instructions. void emitCleanup(); - /// Returns a record type from a record or pointer type. - const RecordType *getRecordTy(QualType Ty); - - /// Returns a record from a record or pointer type. - Record *getRecord(QualType Ty); - Record *getRecord(const RecordDecl *RD); - /// Returns the size int bits of an integer. unsigned getIntWidth(QualType Ty) { - auto &ASTContext = Ctx.getASTContext(); - return ASTContext.getIntWidth(Ty); + return Ctx.getASTContext().getIntWidth(Ty); } - /// Returns the value of CHAR_BIT. unsigned getCharBit() const { - auto &ASTContext = Ctx.getASTContext(); - return ASTContext.getTargetInfo().getCharWidth(); + return Ctx.getASTContext().getTargetInfo().getCharWidth(); + } + + /// Canonicalizes an array type. + const ConstantArrayType *getAsConstantArrayType(QualType AT) { + return Ctx.getASTContext().getAsConstantArrayType(AT); } /// Classifies a type. @@ -108,6 +108,7 @@ /// Checks if a pointer needs adjustment. bool needsAdjust(QualType Ty) const { + // TODO: member pointers and functions do not need adjustment. return true; } @@ -123,31 +124,10 @@ bool discard(const Expr *E); /// Evaluates an expression and places result on stack. bool visit(const Expr *E); - /// Compiles an initializer for a local. - bool visitInitializer(const Expr *E, InitFnRef GenPtr); /// Visits an expression and converts it to a boolean. bool visitBool(const Expr *E); - /// Visits an initializer for a local. - bool visitLocalInitializer(const Expr *Init, unsigned I) { - return visitInitializer(Init, [this, I, Init] { - return this->emitGetPtrLocal(I, Init); - }); - } - - /// Visits an initializer for a global. - bool visitGlobalInitializer(const Expr *Init, unsigned I) { - return visitInitializer(Init, [this, I, Init] { - return this->emitGetPtrGlobal(I, Init); - }); - } - - /// Visits a delegated initializer. - bool visitThisInitializer(const Expr *I) { - return visitInitializer(I, [this, I] { return this->emitThis(I); }); - } - /// Creates a local primitive value. unsigned allocateLocalPrimitive(DeclTy &&Decl, PrimType Ty, bool IsMutable, bool IsExtended = false); @@ -160,11 +140,11 @@ friend class VariableScope; friend class LocalScope; friend class RecordScope; - friend class DeclScope; friend class OptionScope; - /// Emits a zero initializer. - bool visitZeroInitializer(PrimType T, const Expr *E); + /// Emits a direct function call. + bool emitFunctionCall(const FunctionDecl *Callee, llvm::Optional T, + const Expr *Call); enum class DerefKind { /// Value is read and pushed to stack. @@ -181,14 +161,17 @@ bool dereference(const Expr *LV, DerefKind AK, llvm::function_ref Direct, llvm::function_ref Indirect); - bool dereferenceParam(const Expr *LV, PrimType T, const ParmVarDecl *PD, + bool dereferenceParam(const DeclRefExpr *LV, PrimType T, const ParmVarDecl *PD, DerefKind AK, llvm::function_ref Direct, llvm::function_ref Indirect); - bool dereferenceVar(const Expr *LV, PrimType T, const VarDecl *PD, + bool dereferenceVar(const DeclRefExpr *LV, PrimType T, const VarDecl *PD, DerefKind AK, llvm::function_ref Direct, llvm::function_ref Indirect); + /// Converts an lvalue to an rvalue. + bool lvalueToRvalue(const Expr *LV, const Expr *E); + /// Emits an APInt constant. bool emitConst(PrimType T, unsigned NumBits, const llvm::APInt &Value, const Expr *E); @@ -201,25 +184,20 @@ return emitConst(*Ctx.classify(Ty), NumBits, WrappedValue, E); } - /// Returns a pointer to a variable declaration. - bool getPtrVarDecl(const VarDecl *VD, const Expr *E); - - /// Returns the index of a global. - llvm::Optional getGlobalIdx(const VarDecl *VD); + /// Compiles a list of arguments. + bool visitArguments(QualType CalleeTy, ArrayRef Args); + /// Compiles an argument. + bool visitArgument(const Expr *E, bool Discard); /// Emits the initialized pointer. bool emitInitFn() { - assert(InitFn && "missing initializer"); - return (*InitFn)(); + return InitFn ? (*InitFn)() : this->emitTrap(SourceInfo{}); } protected: /// Variable to storage mapping. llvm::DenseMap Locals; - /// OpaqueValueExpr to location mapping. - llvm::DenseMap OpaqueExprs; - /// Current scope. VariableScope *VarScope = nullptr; @@ -231,6 +209,24 @@ /// Expression being initialized. llvm::Optional InitFn = {}; + + /// Enumeration of initializer kinds. + enum class InitKind { + /// Regular invocation. + ROOT, + /// Base class initializer. + BASE, + /// Activates a union field. + UNION, + }; + + /// Initialiser kinds for the current object. + InitKind Initialiser = InitKind::ROOT; + + /// Mapping from var decls to captured fields. + llvm::DenseMap CaptureFields; + /// Override for 'this' captured by lambdas. + llvm::Optional CaptureThis; }; extern template class ByteCodeExprGen; diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp --- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp @@ -14,6 +14,7 @@ #include "PrimType.h" #include "Program.h" #include "State.h" +#include "clang/AST/RecordLayout.h" using namespace clang; using namespace clang::interp; @@ -25,53 +26,49 @@ namespace clang { namespace interp { -/// Scope used to handle temporaries in toplevel variable declarations. -template class DeclScope final : public LocalScope { -public: - DeclScope(ByteCodeExprGen *Ctx, const VarDecl *VD) - : LocalScope(Ctx), Scope(Ctx->P, VD) {} - - void addExtended(const Scope::Local &Local) override { - return this->addLocal(Local); - } - -private: - Program::DeclScope Scope; -}; - /// Scope used to handle initialization methods. template class OptionScope { public: using InitFnRef = typename ByteCodeExprGen::InitFnRef; + using InitKind = typename ByteCodeExprGen::InitKind; using ChainedInitFnRef = std::function; /// Root constructor, compiling or discarding primitives. OptionScope(ByteCodeExprGen *Ctx, bool NewDiscardResult) : Ctx(Ctx), OldDiscardResult(Ctx->DiscardResult), - OldInitFn(std::move(Ctx->InitFn)) { + OldInitFn(std::move(Ctx->InitFn)), + OldInitialiser(Ctx->Initialiser) { Ctx->DiscardResult = NewDiscardResult; Ctx->InitFn = llvm::Optional{}; + Ctx->Initialiser = InitKind::ROOT; } /// Root constructor, setting up compilation state. - OptionScope(ByteCodeExprGen *Ctx, InitFnRef NewInitFn) + OptionScope(ByteCodeExprGen *Ctx, InitFnRef NewInitFn, + InitKind NewInitialiser = InitKind::ROOT) : Ctx(Ctx), OldDiscardResult(Ctx->DiscardResult), - OldInitFn(std::move(Ctx->InitFn)) { + OldInitFn(std::move(Ctx->InitFn)), + OldInitialiser(Ctx->Initialiser) { Ctx->DiscardResult = true; Ctx->InitFn = NewInitFn; + Ctx->Initialiser = NewInitialiser; + } + + ~OptionScope() { + Ctx->DiscardResult = OldDiscardResult; + Ctx->InitFn = std::move(OldInitFn); + Ctx->Initialiser = OldInitialiser; } +protected: /// Extends the chain of initialisation pointers. - OptionScope(ByteCodeExprGen *Ctx, ChainedInitFnRef NewInitFn) + OptionScope(ByteCodeExprGen *Ctx, ChainedInitFnRef NewInitFn, + InitKind NewInitialiser) : Ctx(Ctx), OldDiscardResult(Ctx->DiscardResult), - OldInitFn(std::move(Ctx->InitFn)) { + OldInitFn(std::move(Ctx->InitFn)), OldInitialiser(Ctx->Initialiser) { assert(OldInitFn && "missing initializer"); Ctx->InitFn = [this, NewInitFn] { return NewInitFn(*OldInitFn); }; - } - - ~OptionScope() { - Ctx->DiscardResult = OldDiscardResult; - Ctx->InitFn = std::move(OldInitFn); + Ctx->Initialiser = NewInitialiser; } private: @@ -81,29 +78,101 @@ bool OldDiscardResult; /// Old pointer emitter to restore. llvm::Optional OldInitFn; + /// Base flag to restore. + InitKind OldInitialiser; }; } // namespace interp } // namespace clang +template +bool ByteCodeExprGen::VisitDeclRefExpr(const DeclRefExpr *DE) { + if (DiscardResult) + return true; + + if (auto *PD = dyn_cast(DE->getDecl())) { + QualType Ty = PD->getType(); + auto It = this->Params.find(PD); + if (It == this->Params.end()) { + // Pointers to parameters are not constant expressions. + return this->emitTrap(DE); + } else { + // Generate a pointer to a parameter. + if (Ty->isReferenceType() || !classify(Ty)) + return this->emitGetParamPtr(It->second, DE); + else + return this->emitGetPtrParam(It->second, DE); + } + } + if (auto *VD = dyn_cast(DE->getDecl())) { + auto It = Locals.find(VD); + if (It == Locals.end()) { + // TODO: access globals. + return this->bail(DE); + } else { + // Generate a pointer to a local. + if (VD->getType()->isReferenceType()) + return this->emitGetLocal(PT_Ptr, It->second.Offset, DE); + else + return this->emitGetPtrLocal(It->second.Offset, DE); + } + } + if (auto *ED = dyn_cast(DE->getDecl())) { + QualType Ty = ED->getType(); + if (Optional T = classify(Ty)) + return this->emitConst(*T, getIntWidth(Ty), ED->getInitVal(), DE); + return false; + } + if (auto *BD = dyn_cast(DE->getDecl())) + return this->Visit(BD->getBinding()); + + // TODO: compile other decls. + return this->bail(DE); +} + template bool ByteCodeExprGen::VisitCastExpr(const CastExpr *CE) { auto *SubExpr = CE->getSubExpr(); switch (CE->getCastKind()) { - case CK_LValueToRValue: { - return dereference( - CE->getSubExpr(), DerefKind::Read, - [](PrimType) { - // Value loaded - nothing to do here. - return true; - }, - [this, CE](PrimType T) { - // Pointer on stack - dereference it. - if (!this->emitLoadPop(T, CE)) - return false; - return DiscardResult ? this->emitPop(T, CE) : true; - }); + case CK_LValueToRValue: + return lvalueToRvalue(CE->getSubExpr(), CE); + + case CK_IntegralToBoolean: { + if (DiscardResult) + return discard(SubExpr); + + if (!visit(SubExpr)) + return false; + + return this->emitTest(*classify(SubExpr->getType()), CE); + } + + case CK_IntegralCast: { + if (DiscardResult) + return discard(SubExpr); + + if (!visit(SubExpr)) + return false; + + QualType ArgTy = SubExpr->getType(); + QualType RetTy = CE->getType(); + auto ArgT = *classify(ArgTy); + auto RetT = *classify(RetTy); + if (isFixedIntegral(RetT)) + return this->bail(CE); + else + return this->emitCast(ArgT, RetT, CE); + } + + case CK_PointerToBoolean: { + if (DiscardResult) + return discard(SubExpr); + if (!visit(SubExpr)) + return false; + if (auto T = classify(SubExpr->getType())) + return this->emitTest(*T, CE); + return this->emitTrap(CE); } case CK_ArrayToPointerDecay: @@ -125,6 +194,29 @@ } } +template +bool ByteCodeExprGen::VisitConstantExpr(const ConstantExpr *CE) { + if (DiscardResult) + return true; + + switch (CE->getResultStorageKind()) { + case ConstantExpr::RSK_Int64: { + if (!CE->isRValue()) + return this->Visit(CE->getSubExpr()); + QualType Ty = CE->getType(); + PrimType T = *classify(Ty); + return this->emitConst(T, getIntWidth(Ty), CE->getResultAsAPSInt(), CE); + } + + case ConstantExpr::RSK_APValue: + return this->bail(CE); + + case ConstantExpr::RSK_None: + return this->Visit(CE->getSubExpr()); + } + return false; +} + template bool ByteCodeExprGen::VisitIntegerLiteral(const IntegerLiteral *LE) { if (DiscardResult) @@ -134,7 +226,7 @@ QualType LitTy = LE->getType(); if (Optional T = classify(LitTy)) return emitConst(*T, getIntWidth(LitTy), LE->getValue(), LE); - return this->bail(LE); + return false; } template @@ -149,6 +241,11 @@ // Deal with operations which have composite or void types. switch (BO->getOpcode()) { + case BO_PtrMemD: + case BO_PtrMemI: + return this->bail(BO); + case BO_Cmp: + return this->bail(BO); case BO_Comma: if (!discard(LHS)) return false; @@ -167,6 +264,30 @@ } if (Optional T = classify(BO->getType())) { + switch (BO->getOpcode()) { + case BO_Add: { + if (isPointer(*LT) && !isPointer(*RT)) + return this->bail(BO); + if (isPointer(*RT) && !isPointer(*LT)) + return this->bail(BO); + break; + } + case BO_Sub: { + if (isPointer(*LT) && isPointer(*RT)) + return this->bail(BO); + if (isPointer(*LT) && isPointer(*RT)) + return this->bail(BO); + break; + } + + case BO_LOr: + case BO_LAnd: + return this->bail(BO); + + default: + break; + } + if (!visit(LHS)) return false; if (!visit(RHS)) @@ -179,24 +300,14 @@ }; switch (BO->getOpcode()) { - case BO_EQ: - return Discard(this->emitEQ(*LT, BO)); - case BO_NE: - return Discard(this->emitNE(*LT, BO)); - case BO_LT: - return Discard(this->emitLT(*LT, BO)); - case BO_LE: - return Discard(this->emitLE(*LT, BO)); - case BO_GT: - return Discard(this->emitGT(*LT, BO)); - case BO_GE: - return Discard(this->emitGE(*LT, BO)); - case BO_Sub: - return Discard(this->emitSub(*T, BO)); - case BO_Add: - return Discard(this->emitAdd(*T, BO)); - case BO_Mul: - return Discard(this->emitMul(*T, BO)); + case BO_EQ: return Discard(this->emitEQ(*LT, BO)); + case BO_NE: return Discard(this->emitNE(*LT, BO)); + case BO_LT: return Discard(this->emitLT(*LT, BO)); + case BO_LE: return Discard(this->emitLE(*LT, BO)); + case BO_GT: return Discard(this->emitGT(*LT, BO)); + case BO_GE: return Discard(this->emitGE(*LT, BO)); + case BO_Sub: return Discard(this->emitSub(*T, BO)); + case BO_Add: return Discard(this->emitAdd(*T, BO)); default: return this->bail(BO); } @@ -206,51 +317,97 @@ } template -bool ByteCodeExprGen::discard(const Expr *E) { - OptionScope Scope(this, /*discardResult=*/true); - return this->Visit(E); +bool ByteCodeExprGen::VisitUnaryMinus(const UnaryOperator *UM) { + if (!visit(UM->getSubExpr())) + return false; + + if (Optional T = classify(UM->getType())) { + if (!this->emitMinus(*T, UM)) + return false; + return DiscardResult ? this->emitPop(*T, UM) : true; + } + + return this->bail(UM); } template -bool ByteCodeExprGen::visit(const Expr *E) { - OptionScope Scope(this, /*discardResult=*/false); - return this->Visit(E); +bool ByteCodeExprGen::VisitCallExpr(const CallExpr *CE) { + // Emit the pointer to build the return value into. + if (InitFn && !emitInitFn()) + return false; + + auto Args = llvm::makeArrayRef(CE->getArgs(), CE->getNumArgs()); + auto T = classify(CE->getCallReturnType(Ctx.getASTContext())); + + // Emit the call. + if (unsigned BuiltinOp = CE->getBuiltinCallee()) { + // TODO: implement builtins. + return this->bail(CE); + } else if (const FunctionDecl *Callee = CE->getDirectCallee()) { + // Emit a direct call if the callee is known. + if (isa(Callee) && !Callee->isStatic()) { + return this->bail(CE); + } else { + // Lower arguments. + if (!visitArguments(CE->getCallee()->getType(), Args)) + return false; + return emitFunctionCall(Callee, T, CE); + } + } else { + // TODO: implement indirect calls. + return this->bail(CE); + } } template -bool ByteCodeExprGen::visitBool(const Expr *E) { - if (Optional T = classify(E->getType())) { - return visit(E); +bool ByteCodeExprGen::emitFunctionCall(const FunctionDecl *Callee, + Optional T, + const Expr *E) { + if (Expected Func = P.getOrCreateFunction(S, Callee)) { + if (*Func) { + if (!this->emitCall(*Func, E)) + return false; + } else { + if (!this->emitNoCall(Callee, E)) + return false; + } } else { + consumeError(Func.takeError()); return this->bail(E); } + return DiscardResult && T ? this->emitPop(*T, E) : true; } template -bool ByteCodeExprGen::visitZeroInitializer(PrimType T, const Expr *E) { - switch (T) { - case PT_Bool: - return this->emitZeroBool(E); - case PT_Sint8: - return this->emitZeroSint8(E); - case PT_Uint8: - return this->emitZeroUint8(E); - case PT_Sint16: - return this->emitZeroSint16(E); - case PT_Uint16: - return this->emitZeroUint16(E); - case PT_Sint32: - return this->emitZeroSint32(E); - case PT_Uint32: - return this->emitZeroUint32(E); - case PT_Sint64: - return this->emitZeroSint64(E); - case PT_Uint64: - return this->emitZeroUint64(E); - case PT_Ptr: - return this->emitNullPtr(E); +bool ByteCodeExprGen::discard(const Expr *E) { + QualType Ty = E->getType(); + if (Ty->isVoidType() || !E->isRValue() || classify(Ty)) { + OptionScope Scope(this, /*discardResult=*/true); + return this->Visit(E); + } + // TODO: implement composite initializers. + return this->bail(E); +} + +template +bool ByteCodeExprGen::visit(const Expr *E) { + if (!E->isRValue() || classify(E->getType())) { + OptionScope Scope(this, /*discardResult=*/false); + return this->Visit(E); + } + // TODO: complex value initializers. + return this->bail(E); +} + +template +bool ByteCodeExprGen::visitBool(const Expr *E) { + if (Optional T = classify(E->getType())) { + if (!visit(E)) + return false; + return (*T != PT_Bool) ? this->emitTest(*T, E) : true; + } else { + return this->bail(E); } - llvm_unreachable("unknown primitive type"); } template @@ -263,11 +420,15 @@ if (auto *DE = dyn_cast(LV)) { if (!DE->getDecl()->getType()->isReferenceType()) { if (auto *PD = dyn_cast(DE->getDecl())) - return dereferenceParam(LV, *T, PD, AK, Direct, Indirect); + return dereferenceParam(DE, *T, PD, AK, Direct, Indirect); if (auto *VD = dyn_cast(DE->getDecl())) - return dereferenceVar(LV, *T, VD, AK, Direct, Indirect); + return dereferenceVar(DE, *T, VD, AK, Direct, Indirect); } } + if (auto *ME = dyn_cast(LV)) { + if (!ME->getMemberDecl()->getType()->isReferenceType()) + return this->bail(LV); + } } if (!visit(LV)) @@ -280,7 +441,7 @@ template bool ByteCodeExprGen::dereferenceParam( - const Expr *LV, PrimType T, const ParmVarDecl *PD, DerefKind AK, + const DeclRefExpr *LV, PrimType T, const ParmVarDecl *PD, DerefKind AK, llvm::function_ref Direct, llvm::function_ref Indirect) { auto It = this->Params.find(PD); @@ -309,20 +470,13 @@ return true; } - // If the param is a pointer, we can dereference a dummy value. - if (!DiscardResult && T == PT_Ptr && AK == DerefKind::Read) { - if (auto Idx = P.getOrCreateDummy(PD)) - return this->emitGetPtrGlobal(*Idx, PD); - return false; - } - - // Value cannot be produced - try to emit pointer and do stuff with it. - return visit(LV) && Indirect(T); + // TODO: try to produce a pointer to read from/write to. + return this->bail(LV); } template bool ByteCodeExprGen::dereferenceVar( - const Expr *LV, PrimType T, const VarDecl *VD, DerefKind AK, + const DeclRefExpr *LV, PrimType T, const VarDecl *VD, DerefKind AK, llvm::function_ref Direct, llvm::function_ref Indirect) { auto It = Locals.find(VD); @@ -350,44 +504,24 @@ return false; return DiscardResult ? true : this->emitGetPtrLocal(L.Offset, LV); } - } else if (auto Idx = getGlobalIdx(VD)) { - switch (AK) { - case DerefKind::Read: - if (!this->emitGetGlobal(T, *Idx, LV)) - return false; - return DiscardResult ? this->emitPop(T, LV) : true; - - case DerefKind::Write: - if (!Direct(T)) - return false; - if (!this->emitSetGlobal(T, *Idx, LV)) - return false; - return DiscardResult ? true : this->emitGetPtrGlobal(*Idx, LV); - - case DerefKind::ReadWrite: - if (!this->emitGetGlobal(T, *Idx, LV)) - return false; - if (!Direct(T)) - return false; - if (!this->emitSetGlobal(T, *Idx, LV)) - return false; - return DiscardResult ? true : this->emitGetPtrGlobal(*Idx, LV); - } } - // If the declaration is a constant value, emit it here even - // though the declaration was not evaluated in the current scope. - // The access mode can only be read in this case. - if (!DiscardResult && AK == DerefKind::Read) { - if (VD->hasLocalStorage() && VD->hasInit() && !VD->isConstexpr()) { - QualType VT = VD->getType(); - if (VT.isConstQualified() && VT->isFundamentalType()) - return this->Visit(VD->getInit()); - } - } + // TODO: handle globals. + return this->bail(LV); +} - // Value cannot be produced - try to emit pointer. - return visit(LV) && Indirect(T); +template +bool ByteCodeExprGen::lvalueToRvalue(const Expr *LV, const Expr *E) { + return dereference( + LV, DerefKind::Read, + [](PrimType) { + // Value loaded - nothing to do here. + return true; + }, + [this, E](PrimType T) { + // Pointer on stack - dereference it. + return this->emitLoadPop(T, E); + }); } template @@ -433,134 +567,71 @@ } template -llvm::Optional -ByteCodeExprGen::allocateLocal(DeclTy &&Src, bool IsExtended) { - QualType Ty; - - const ValueDecl *Key = nullptr; - bool IsTemporary = false; - if (auto *VD = dyn_cast_or_null(Src.dyn_cast())) { - Key = VD; - Ty = VD->getType(); - } - if (auto *E = Src.dyn_cast()) { - IsTemporary = true; - Ty = E->getType(); - } - - Descriptor *D = P.createDescriptor(Src, Ty.getTypePtr(), - Ty.isConstQualified(), IsTemporary); - if (!D) - return {}; - - Scope::Local Local = this->createLocal(D); - if (Key) - Locals.insert({Key, Local}); - VarScope->add(Local, IsExtended); - return Local.Offset; -} - -template -bool ByteCodeExprGen::visitInitializer( - const Expr *Init, InitFnRef InitFn) { - OptionScope Scope(this, InitFn); - return this->Visit(Init); -} - -template -bool ByteCodeExprGen::getPtrVarDecl(const VarDecl *VD, const Expr *E) { - // Generate a pointer to the local, loading refs. - if (Optional Idx = getGlobalIdx(VD)) { - if (VD->getType()->isReferenceType()) - return this->emitGetGlobalPtr(*Idx, E); - else - return this->emitGetPtrGlobal(*Idx, E); - } - return this->bail(VD); -} - -template -llvm::Optional -ByteCodeExprGen::getGlobalIdx(const VarDecl *VD) { - if (VD->isConstexpr()) { - // Constexpr decl - it must have already been defined. - return P.getGlobal(VD); - } - if (!VD->hasLocalStorage()) { - // Not constexpr, but a global var - can have pointer taken. - Program::DeclScope Scope(P, VD); - return P.getOrCreateGlobal(VD); +bool ByteCodeExprGen::visitArguments(QualType CalleeTy, + ArrayRef Args) { + if (auto *MemberTy = CalleeTy->getAs()) + CalleeTy = MemberTy->getPointeeType(); + if (auto *PtrTy = CalleeTy->getAs()) + CalleeTy = PtrTy->getPointeeType(); + + if (auto *Ty = CalleeTy->getAs()) { + unsigned NumParams = Ty->getNumParams(); + for (unsigned I = 0, N = Args.size(); I < N; ++I) + if (!visitArgument(Args[I], I >= NumParams)) + return false; + } else { + for (unsigned I = 0, N = Args.size(); I < N; ++I) + if (!visitArgument(Args[I], /*discard=*/true)) + return false; } - return {}; -} -template -const RecordType *ByteCodeExprGen::getRecordTy(QualType Ty) { - if (auto *PT = dyn_cast(Ty)) - return PT->getPointeeType()->getAs(); - else - return Ty->getAs(); + return true; } template -Record *ByteCodeExprGen::getRecord(QualType Ty) { - if (auto *RecordTy = getRecordTy(Ty)) { - return getRecord(RecordTy->getDecl()); - } - return nullptr; -} +bool ByteCodeExprGen::visitArgument(const Expr *E, bool Discard) { + // Primitive or pointer argument - leave it on the stack. + if (Optional T = classify(E)) + return Discard ? discard(E) : visit(E); -template -Record *ByteCodeExprGen::getRecord(const RecordDecl *RD) { - return P.getOrCreateRecord(RD); + // TODO: composite arguments. + return this->bail(E); } template bool ByteCodeExprGen::visitExpr(const Expr *Exp) { ExprScope RootScope(this); - if (!visit(Exp)) - return false; - if (Optional T = classify(Exp)) - return this->emitRet(*T, Exp); - else - return this->emitRetValue(Exp); -} + if (Optional ExpTy = classify(Exp)) { + if (Exp->isGLValue()) { + // If the expression is an lvalue, dereference it. + if (!lvalueToRvalue(Exp, Exp)) + return false; -template -bool ByteCodeExprGen::visitDecl(const VarDecl *VD) { - const Expr *Init = VD->getInit(); - - if (Optional I = P.createGlobal(VD)) { - if (Optional T = classify(VD->getType())) { - { - // Primitive declarations - compute the value and set it. - DeclScope LocalScope(this, VD); - if (!visit(Init)) - return false; + // Find the type of the expression. + PrimType Ty; + if (Optional T = classify(Exp->getType())) { + Ty = *T; + } else { + Ty = PT_Ptr; } - // If the declaration is global, save the value for later use. - if (!this->emitDup(*T, VD)) - return false; - if (!this->emitInitGlobal(*T, *I, VD)) - return false; - return this->emitRet(*T, VD); + return this->emitRet(Ty, Exp); } else { - { - // Composite declarations - allocate storage and initialize it. - DeclScope LocalScope(this, VD); - if (!visitGlobalInitializer(Init, *I)) - return false; - } - - // Return a pointer to the global. - if (!this->emitGetPtrGlobal(*I, VD)) + // Otherwise, simply evaluate the rvalue. + if (!visit(Exp)) return false; - return this->emitRetValue(VD); + return this->emitRet(*ExpTy, Exp); } + } else { + // TODO: implement composite initializers. + return this->bail(Exp); } +} +template +bool ByteCodeExprGen::visitDecl(const VarDecl *VD) { + // TODO: implement global declarations. return this->bail(VD); } diff --git a/clang/lib/AST/Interp/ByteCodeStmtGen.h b/clang/lib/AST/Interp/ByteCodeStmtGen.h --- a/clang/lib/AST/Interp/ByteCodeStmtGen.h +++ b/clang/lib/AST/Interp/ByteCodeStmtGen.h @@ -66,6 +66,9 @@ /// Compiles a variable declaration. bool visitVarDecl(const VarDecl *VD); + /// Visits a field initializer. + bool visitCtorInit(Record *This, const CXXCtorInitializer *Init); + private: /// Type of the expression returned by the function. llvm::Optional ReturnType; diff --git a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp --- a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp @@ -15,6 +15,7 @@ #include "Program.h" #include "State.h" #include "clang/Basic/LLVM.h" +#include "clang/AST/ASTLambda.h" using namespace clang; using namespace clang::interp; @@ -25,10 +26,9 @@ /// Scope managing label targets. template class LabelScope { public: - virtual ~LabelScope() { } - protected: LabelScope(ByteCodeStmtGen *Ctx) : Ctx(Ctx) {} + /// ByteCodeStmtGen instance. ByteCodeStmtGen *Ctx; }; @@ -95,8 +95,10 @@ ReturnType = this->classify(F->getReturnType()); // Set up fields and context if a constructor. - if (auto *MD = dyn_cast(F)) + if (auto *MD = dyn_cast(F)) { + // TODO: implement constructor lowering return this->bail(MD); + } if (auto *Body = F->getBody()) if (!visitStmt(Body)) @@ -170,12 +172,8 @@ this->emitCleanup(); return this->emitRet(*ReturnType, RS); } else { - // RVO - construct the value in the return location. - auto ReturnLocation = [this, RE] { return this->emitGetParamPtr(0, RE); }; - if (!this->visitInitializer(RE, ReturnLocation)) - return false; - this->emitCleanup(); - return this->emitRetVoid(RS); + // TODO: RVO - construct the value in the return location. + return this->bail(RS); } } else { this->emitCleanup(); @@ -245,12 +243,8 @@ // Set the value. return this->emitSetLocal(*T, Off, VD); } else { - // Composite types - allocate storage and initialize it. - if (auto Off = this->allocateLocal(VD)) { - return this->visitLocalInitializer(VD->getInit(), *Off); - } else { - return this->bail(VD); - } + // TODO: implement composite initialisers. + return this->bail(VD); } } diff --git a/clang/lib/AST/Interp/Context.h b/clang/lib/AST/Interp/Context.h --- a/clang/lib/AST/Interp/Context.h +++ b/clang/lib/AST/Interp/Context.h @@ -19,6 +19,7 @@ #include "Context.h" #include "InterpStack.h" #include "clang/AST/APValue.h" +#include "clang/Basic/LangOptions.h" #include "llvm/ADT/PointerIntPair.h" namespace clang { @@ -61,6 +62,11 @@ /// Returns CHAR_BIT. unsigned getCharBit() const; + /// Checks is language mode is OpenCL. + bool isOpenCL() { return getLangOpts().OpenCL; } + /// Checks is language mode is C++20. + bool isCPlusPlus2a() { return getLangOpts().CPlusPlus2a; } + /// Classifies an expression. llvm::Optional classify(QualType T); diff --git a/clang/lib/AST/Interp/Context.cpp b/clang/lib/AST/Interp/Context.cpp --- a/clang/lib/AST/Interp/Context.cpp +++ b/clang/lib/AST/Interp/Context.cpp @@ -11,7 +11,7 @@ #include "ByteCodeExprGen.h" #include "ByteCodeStmtGen.h" #include "EvalEmitter.h" -#include "Interp.h" +#include "InterpLoop.h" #include "InterpFrame.h" #include "InterpStack.h" #include "PrimType.h" @@ -28,7 +28,8 @@ bool Context::isPotentialConstantExpr(State &Parent, const FunctionDecl *FD) { Function *Func = P->getFunction(FD); if (!Func) { - if (auto R = ByteCodeStmtGen(*this, *P).compileFunc(FD)) { + ByteCodeStmtGen C(*this, *P, Parent); + if (auto R = C.compileFunc(FD)) { Func = *R; } else { handleAllErrors(R.takeError(), [&Parent](ByteCodeGenError &Err) { @@ -59,13 +60,19 @@ const LangOptions &Context::getLangOpts() const { return Ctx.getLangOpts(); } llvm::Optional Context::classify(QualType T) { - if (T->isReferenceType() || T->isPointerType()) { + // Pointer type check. + if (T->isReferenceType()) + return PT_Ptr; + if (T->isPointerType()) + return PT_Ptr; + if (T->isNullPtrType()) return PT_Ptr; - } + // Boolean check. if (T->isBooleanType()) return PT_Bool; + // Signed integral type check. if (T->isSignedIntegerOrEnumerationType()) { switch (Ctx.getIntWidth(T)) { case 64: @@ -81,6 +88,7 @@ } } + // Unsigned integral type check. if (T->isUnsignedIntegerOrEnumerationType()) { switch (Ctx.getIntWidth(T)) { case 64: @@ -96,9 +104,7 @@ } } - if (T->isNullPtrType()) - return PT_Ptr; - + // Atomic types are simply unwrapped. if (auto *AT = dyn_cast(T)) return classify(AT->getValueType()); diff --git a/clang/lib/AST/Interp/Descriptor.h b/clang/lib/AST/Interp/Descriptor.h --- a/clang/lib/AST/Interp/Descriptor.h +++ b/clang/lib/AST/Interp/Descriptor.h @@ -70,6 +70,8 @@ Record *const ElemRecord = nullptr; /// Descriptor of the array element. Descriptor *const ElemDesc = nullptr; + /// Type of primitive elements. + llvm::Optional ElemTy = {}; /// Flag indicating if the block is mutable. const bool IsConst = false; /// Flag indicating if a field is mutable. diff --git a/clang/lib/AST/Interp/Descriptor.cpp b/clang/lib/AST/Interp/Descriptor.cpp --- a/clang/lib/AST/Interp/Descriptor.cpp +++ b/clang/lib/AST/Interp/Descriptor.cpp @@ -188,26 +188,26 @@ Descriptor::Descriptor(const DeclTy &D, PrimType Type, bool IsConst, bool IsTemporary, bool IsMutable) : Source(D), ElemSize(primSize(Type)), Size(ElemSize), AllocSize(Size), - IsConst(IsConst), IsMutable(IsMutable), IsTemporary(IsTemporary), - CtorFn(getCtorPrim(Type)), DtorFn(getDtorPrim(Type)), - MoveFn(getMovePrim(Type)) { + ElemTy(Type), IsConst(IsConst), IsMutable(IsMutable), + IsTemporary(IsTemporary), CtorFn(getCtorPrim(Type)), + DtorFn(getDtorPrim(Type)), MoveFn(getMovePrim(Type)) { assert(Source && "Missing source"); } Descriptor::Descriptor(const DeclTy &D, PrimType Type, size_t NumElems, bool IsConst, bool IsTemporary, bool IsMutable) : Source(D), ElemSize(primSize(Type)), Size(ElemSize * NumElems), - AllocSize(align(Size) + sizeof(InitMap *)), IsConst(IsConst), - IsMutable(IsMutable), IsTemporary(IsTemporary), IsArray(true), - CtorFn(getCtorArrayPrim(Type)), DtorFn(getDtorArrayPrim(Type)), - MoveFn(getMoveArrayPrim(Type)) { + AllocSize(align(Size) + sizeof(InitMap *)), ElemTy(Type), + IsConst(IsConst), IsMutable(IsMutable), IsTemporary(IsTemporary), + IsArray(true), CtorFn(getCtorArrayPrim(Type)), + DtorFn(getDtorArrayPrim(Type)), MoveFn(getMoveArrayPrim(Type)) { assert(Source && "Missing source"); } Descriptor::Descriptor(const DeclTy &D, PrimType Type, bool IsTemporary, UnknownSize) : Source(D), ElemSize(primSize(Type)), Size(UnknownSizeMark), - AllocSize(alignof(void *)), IsConst(true), IsMutable(false), + AllocSize(alignof(void *)), ElemTy(Type), IsConst(true), IsMutable(false), IsTemporary(IsTemporary), IsArray(true), CtorFn(getCtorArrayPrim(Type)), DtorFn(getDtorArrayPrim(Type)), MoveFn(getMoveArrayPrim(Type)) { assert(Source && "Missing source"); diff --git a/clang/lib/AST/Interp/EvalEmitter.h b/clang/lib/AST/Interp/EvalEmitter.h --- a/clang/lib/AST/Interp/EvalEmitter.h +++ b/clang/lib/AST/Interp/EvalEmitter.h @@ -113,9 +113,9 @@ bool isActive() { return CurrentLabel == ActiveLabel; } /// Helper to invoke a method. - bool ExecuteCall(Function *F, Pointer &&This, const SourceInfo &Info); + bool executeCall(Function *F, Pointer &&This, const SourceInfo &Info); /// Helper to emit a diagnostic on a missing method. - bool ExecuteNoCall(const FunctionDecl *F, const SourceInfo &Info); + bool executeNoCall(const FunctionDecl *F, const SourceInfo &Info); protected: #define GET_EVAL_PROTO diff --git a/clang/lib/AST/Interp/EvalEmitter.cpp b/clang/lib/AST/Interp/EvalEmitter.cpp --- a/clang/lib/AST/Interp/EvalEmitter.cpp +++ b/clang/lib/AST/Interp/EvalEmitter.cpp @@ -9,6 +9,8 @@ #include "EvalEmitter.h" #include "Context.h" #include "Interp.h" +#include "InterpHelper.h" +#include "InterpLoop.h" #include "Opcode.h" #include "Program.h" #include "clang/AST/DeclCXX.h" @@ -42,9 +44,7 @@ return false; } -void EvalEmitter::emitLabel(LabelTy Label) { - CurrentLabel = Label; -} +void EvalEmitter::emitLabel(LabelTy Label) { CurrentLabel = Label; } EvalEmitter::LabelTy EvalEmitter::getLabel() { return NextLabel++; } @@ -95,104 +95,24 @@ return true; } +bool EvalEmitter::emitCall(Function *Fn, const SourceInfo &Info) { + CurrentSource = Info; + if (!isActive()) + return true; + + // Interpret the function in its own frame. + return executeCall(Fn, Pointer(), Info); +} + template bool EvalEmitter::emitRet(const SourceInfo &Info) { if (!isActive()) return true; using T = typename PrimConv::T; - return ReturnValue(S.Stk.pop(), Result); + return PrimitiveToValue(S.Stk.pop(), Result); } bool EvalEmitter::emitRetVoid(const SourceInfo &Info) { 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 (auto *AT = Ty->getAs()) - Ty = AT->getValueType(); - - if (auto *RT = Ty->getAs()) { - auto *Record = Ptr.getRecord(); - assert(Record && "Missing record descriptor"); - - bool Ok = true; - if (RT->getDecl()->isUnion()) { - const FieldDecl *ActiveField = nullptr; - APValue Value; - for (auto &F : Record->fields()) { - const Pointer &FP = Ptr.atField(F.Offset); - QualType FieldTy = F.Decl->getType(); - if (FP.isActive()) { - if (llvm::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 (llvm::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 (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 (llvm::Optional T = Ctx.classify(ElemTy)) { - TYPE_SWITCH(*T, Ok &= ReturnValue(EP.deref(), Slot)); - } else { - Ok &= Composite(ElemTy, EP.narrow(), Slot); - } - } - return Ok; - } - llvm_unreachable("invalid value to return"); - }; - - // Return the composite type. - const auto &Ptr = S.Stk.pop(); - return Composite(Ptr.getType(), Ptr, Result); -} - bool EvalEmitter::emitGetPtrLocal(uint32_t I, const SourceInfo &Info) { if (!isActive()) return true; @@ -244,6 +164,38 @@ return true; } +bool EvalEmitter::emitNoCall(const FunctionDecl *F, const SourceInfo &Info) { + CurrentSource = Info; + if (!isActive()) + return true; + return executeNoCall(F, Info); +} + +bool EvalEmitter::executeCall(Function *F, Pointer &&This, + const SourceInfo &Info) { + if (!S.CheckCallable(OpPC, F->getDecl())) + return false; + if (S.checkingPotentialConstantExpression()) + return false; + S.Current = new InterpFrame(S, F, S.Current, OpPC, std::move(This)); + return Interpret(S, Result); +} + +bool EvalEmitter::executeNoCall(const FunctionDecl *F, const SourceInfo &Info) { + if (!S.CheckCallable(OpPC, F)) + return false; + if (S.checkingPotentialConstantExpression()) + return false; + if (S.getLangOpts().CPlusPlus11) { + S.FFDiag(Info, diag::note_constexpr_invalid_function, 1) + << F->isConstexpr() << (bool)dyn_cast(F) << F; + S.Note(F->getLocation(), diag::note_declared_at); + } else { + S.FFDiag(Info, diag::note_invalid_subexpr_in_const_expr); + } + return false; +} + //===----------------------------------------------------------------------===// // Opcode evaluators //===----------------------------------------------------------------------===// diff --git a/clang/lib/AST/Interp/Function.h b/clang/lib/AST/Interp/Function.h --- a/clang/lib/AST/Interp/Function.h +++ b/clang/lib/AST/Interp/Function.h @@ -96,18 +96,21 @@ /// Returns a specific scope. Scope &getScope(unsigned Idx) { return Scopes[Idx]; } + /// Solves a call relocation. + void relocateCall(CodePtr Loc, Function *Callee); + /// Returns the source information at a given PC. SourceInfo getSource(CodePtr PC) const; /// Checks if the function is valid to call in constexpr. bool isConstexpr() const { return IsValid; } - /// Checks if the function is virtual. - bool isVirtual() const; - /// Checks if the function is a constructor. bool isConstructor() const { return isa(F); } + /// Checks if the function was default. + bool isDefaulted() const { return F->isDefaulted(); } + private: /// Construct a function representing an actual function. Function(Program &P, const FunctionDecl *F, unsigned ArgSize, diff --git a/clang/lib/AST/Interp/Function.cpp b/clang/lib/AST/Interp/Function.cpp --- a/clang/lib/AST/Interp/Function.cpp +++ b/clang/lib/AST/Interp/Function.cpp @@ -31,18 +31,21 @@ return It->second; } +void Function::relocateCall(CodePtr Loc, Function *Callee) { + using namespace llvm::support; + + char *Ptr = Code.data() + (Loc - getCodeBegin()); + const auto Addr = reinterpret_cast(Callee); + *(reinterpret_cast(Ptr) - 1) = OP_Call; + + endian::write(Ptr, Addr); +} + SourceInfo Function::getSource(CodePtr PC) const { unsigned Offset = PC - getCodeBegin(); using Elem = std::pair; auto It = std::lower_bound(SrcMap.begin(), SrcMap.end(), Elem{Offset, {}}, [](Elem A, Elem B) { return A.first < B.first; }); - if (It == SrcMap.end() || It->first != Offset) - llvm::report_fatal_error("missing source location"); + assert(It != SrcMap.end() && It->first == Offset); return It->second; } - -bool Function::isVirtual() const { - if (auto *M = dyn_cast(F)) - return M->isVirtual(); - return false; -} diff --git a/clang/lib/AST/Interp/Integral.h b/clang/lib/AST/Interp/Integral.h --- a/clang/lib/AST/Interp/Integral.h +++ b/clang/lib/AST/Interp/Integral.h @@ -13,13 +13,14 @@ #ifndef LLVM_CLANG_AST_INTERP_INTEGRAL_H #define LLVM_CLANG_AST_INTERP_INTEGRAL_H -#include "clang/AST/ComparisonCategories.h" +#include +#include +#include "Boolean.h" #include "clang/AST/APValue.h" +#include "clang/AST/ComparisonCategories.h" #include "llvm/ADT/APSInt.h" #include "llvm/Support/MathExtras.h" #include "llvm/Support/raw_ostream.h" -#include -#include namespace clang { namespace interp { @@ -30,45 +31,71 @@ /// Helper to compare two comparable types. template ComparisonCategoryResult Compare(const T &X, const T &Y) { - if (X < Y) - return ComparisonCategoryResult::Less; - if (X > Y) - return ComparisonCategoryResult::Greater; + if (X < Y) return ComparisonCategoryResult::Less; + if (X > Y) return ComparisonCategoryResult::Greater; return ComparisonCategoryResult::Equal; } // Helper structure to select the representation. -template struct Repr; -template <> struct Repr<8, false> { using Type = uint8_t; }; -template <> struct Repr<16, false> { using Type = uint16_t; }; -template <> struct Repr<32, false> { using Type = uint32_t; }; -template <> struct Repr<64, false> { using Type = uint64_t; }; -template <> struct Repr<8, true> { using Type = int8_t; }; -template <> struct Repr<16, true> { using Type = int16_t; }; -template <> struct Repr<32, true> { using Type = int32_t; }; -template <> struct Repr<64, true> { using Type = int64_t; }; +template +struct Repr; +template <> +struct Repr<8, false> { + using Type = uint8_t; +}; +template <> +struct Repr<16, false> { + using Type = uint16_t; +}; +template <> +struct Repr<32, false> { + using Type = uint32_t; +}; +template <> +struct Repr<64, false> { + using Type = uint64_t; +}; +template <> +struct Repr<8, true> { + using Type = int8_t; +}; +template <> +struct Repr<16, true> { + using Type = int16_t; +}; +template <> +struct Repr<32, true> { + using Type = int32_t; +}; +template <> +struct Repr<64, true> { + using Type = int64_t; +}; /// Wrapper around numeric types. /// /// These wrappers are required to shared an interface between APSint and /// builtin primitive numeral types, while optimising for storage and /// allowing methods operating on primitive type to compile to fast code. -template class Integral { -private: - template friend class Integral; +template +class Integral { + private: + template + friend class Integral; // The primitive representing the integral. using T = typename Repr::Type; T V; /// Primitive representing limits. - static const auto Min = std::numeric_limits::min(); - static const auto Max = std::numeric_limits::max(); + static constexpr auto Min = std::numeric_limits::min(); + static constexpr auto Max = std::numeric_limits::max(); /// Construct an integral from anything that is convertible to storage. - template explicit Integral(T V) : V(V) {} + template + explicit Integral(T V) : V(V) {} -public: + public: /// Zero-initializes an integral. Integral() : V(0) {} @@ -91,9 +118,21 @@ return V >= 0 && static_cast(V) > RHS; } + Integral operator+(Integral RHS) const { return Integral(V + RHS.V); } + Integral operator-(Integral RHS) const { return Integral(V - RHS.V); } + Integral operator*(Integral RHS) const { return Integral(V * RHS.V); } + Integral operator/(Integral RHS) const { return Integral(V / RHS.V); } + Integral operator%(Integral RHS) const { return Integral(V % RHS.V); } + Integral operator&(Integral RHS) const { return Integral(V & RHS.V); } + Integral operator|(Integral RHS) const { return Integral(V | RHS.V); } + Integral operator^(Integral RHS) const { return Integral(V ^ RHS.V); } + Integral operator-() const { return Integral(-V); } Integral operator~() const { return Integral(~V); } + Integral operator>>(unsigned RHS) const { return Integral(V >> RHS); } + Integral operator<<(unsigned RHS) const { return Integral(V << RHS); } + template explicit operator Integral() const { return Integral(V); @@ -135,11 +174,12 @@ return Compare(V, RHS.V); } - unsigned countLeadingZeros() const { return llvm::countLeadingZeros(V); } + unsigned countLeadingZeros() const { + return llvm::countLeadingZeros(toUnsigned().V); + } Integral truncate(unsigned TruncBits) const { - if (TruncBits >= Bits) - return *this; + if (TruncBits >= Bits) return *this; const T BitMask = (T(1) << T(TruncBits)) - 1; const T SignBit = T(1) << (TruncBits - 1); const T ExtMask = ~BitMask; @@ -148,12 +188,8 @@ void print(llvm::raw_ostream &OS) const { OS << V; } - static Integral min(unsigned NumBits) { - return Integral(Min); - } - static Integral max(unsigned NumBits) { - return Integral(Max); - } + static Integral min(unsigned NumBits) { return Integral(Min); } + static Integral max(unsigned NumBits) { return Integral(Max); } template static typename std::enable_if::value, Integral>::type @@ -162,21 +198,24 @@ } template - static typename std::enable_if::type - from(Integral Value) { + static Integral from(const Integral &Value) { return Integral(Value.V); } - template static Integral from(Integral<0, SrcSign> Value) { + template + static Integral from(Integral<0, SrcSign> Value) { if (SrcSign) return Integral(Value.V.getSExtValue()); else return Integral(Value.V.getZExtValue()); } + static Integral from(Boolean Value) { return Integral(!Value.isZero()); } + static Integral zero() { return from(0); } - template static Integral from(T Value, unsigned NumBits) { + template + static Integral from(T Value, unsigned NumBits) { return Integral(Value); } @@ -204,7 +243,7 @@ return CheckMulUB(A.V, B.V, R->V); } -private: + private: template static typename std::enable_if::value, bool>::type CheckAddUB(T A, T B, T &R) { @@ -263,7 +302,12 @@ return OS; } -} // namespace interp -} // namespace clang +template +Boolean Boolean::from(const Integral &V) { + return Boolean(!V.isZero()); +} + +} // namespace interp +} // namespace clang #endif diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h --- a/clang/lib/AST/Interp/Interp.h +++ b/clang/lib/AST/Interp/Interp.h @@ -6,17 +6,16 @@ // //===----------------------------------------------------------------------===// // -// Definition of the interpreter state and entry point. +// Implementation of the opcodes. // //===----------------------------------------------------------------------===// #ifndef LLVM_CLANG_AST_INTERP_INTERP_H #define LLVM_CLANG_AST_INTERP_INTERP_H -#include -#include #include "Function.h" #include "InterpFrame.h" +#include "InterpHelper.h" #include "InterpStack.h" #include "InterpState.h" #include "Opcode.h" @@ -30,6 +29,8 @@ #include "llvm/ADT/APFloat.h" #include "llvm/ADT/APSInt.h" #include "llvm/Support/Endian.h" +#include +#include namespace clang { namespace interp { @@ -37,62 +38,6 @@ using APInt = llvm::APInt; using APSInt = llvm::APSInt; -/// Convers a value to an APValue. -template bool ReturnValue(const T &V, APValue &R) { - R = V.toAPValue(); - return true; -} - -/// Checks if the variable has externally defined storage. -bool CheckExtern(InterpState &S, CodePtr OpPC, const Pointer &Ptr); - -/// Checks if the array is offsetable. -bool CheckArray(InterpState &S, CodePtr OpPC, const Pointer &Ptr); - -/// Checks if a pointer is live and accesible. -bool CheckLive(InterpState &S, CodePtr OpPC, const Pointer &Ptr, - AccessKinds AK); -/// Checks if a pointer is null. -bool CheckNull(InterpState &S, CodePtr OpPC, const Pointer &Ptr, - CheckSubobjectKind CSK); - -/// Checks if a pointer is in range. -bool CheckRange(InterpState &S, CodePtr OpPC, const Pointer &Ptr, - AccessKinds AK); - -/// Checks if a field from which a pointer is going to be derived is valid. -bool CheckRange(InterpState &S, CodePtr OpPC, const Pointer &Ptr, - CheckSubobjectKind CSK); - -/// Checks if a pointer points to const storage. -bool CheckConst(InterpState &S, CodePtr OpPC, const Pointer &Ptr); - -/// Checks if a pointer points to a mutable field. -bool CheckMutable(InterpState &S, CodePtr OpPC, const Pointer &Ptr); - -/// Checks if a value can be loaded from a block. -bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr); - -/// Checks if a value can be stored in a block. -bool CheckStore(InterpState &S, CodePtr OpPC, const Pointer &Ptr); - -/// Checks if a method can be invoked on an object. -bool CheckInvoke(InterpState &S, CodePtr OpPC, const Pointer &Ptr); - -/// Checks if a value can be initialized. -bool CheckInit(InterpState &S, CodePtr OpPC, const Pointer &Ptr); - -/// Checks if a method can be called. -bool CheckCallable(InterpState &S, CodePtr OpPC, Function *F); - -/// Checks the 'this' pointer. -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); - -template inline bool IsTrue(const T &V) { return !V.isZero(); } - //===----------------------------------------------------------------------===// // Add, Sub, Mul //===----------------------------------------------------------------------===// @@ -115,7 +60,7 @@ APSInt Value = OpAP()(LHS.toAPSInt(Bits), RHS.toAPSInt(Bits)); // Report undefined behaviour, stopping if required. - const Expr *E = S.Current->getExpr(OpPC); + const Expr *E = S.getSource(OpPC).asExpr(); QualType Type = E->getType(); if (S.checkingForUndefinedBehavior()) { auto Trunc = Value.trunc(Result.bitWidth()).toString(10); @@ -153,123 +98,30 @@ } //===----------------------------------------------------------------------===// -// EQ, NE, GT, GE, LT, LE +// Minus, Test //===----------------------------------------------------------------------===// -using CompareFn = llvm::function_ref; - -template -bool CmpHelper(InterpState &S, CodePtr OpPC, CompareFn Fn) { - using BoolT = PrimConv::T; - const T &RHS = S.Stk.pop(); - const T &LHS = S.Stk.pop(); - S.Stk.push(BoolT::from(Fn(LHS.compare(RHS)))); - return true; -} - -template -bool CmpHelperEQ(InterpState &S, CodePtr OpPC, CompareFn Fn) { - return CmpHelper(S, OpPC, Fn); -} - -template <> -inline bool CmpHelper(InterpState &S, CodePtr OpPC, CompareFn Fn) { - using BoolT = PrimConv::T; - const Pointer &RHS = S.Stk.pop(); - const Pointer &LHS = S.Stk.pop(); - - if (!Pointer::hasSameBase(LHS, RHS)) { - const SourceInfo &Loc = S.Current->getSource(OpPC); - S.FFDiag(Loc, diag::note_invalid_subexpr_in_const_expr); - return false; - } else { - unsigned VL = LHS.getByteOffset(); - unsigned VR = RHS.getByteOffset(); - S.Stk.push(BoolT::from(Fn(Compare(VL, VR)))); - return true; - } -} - -template <> -inline bool CmpHelperEQ(InterpState &S, CodePtr OpPC, CompareFn Fn) { - using BoolT = PrimConv::T; - const Pointer &RHS = S.Stk.pop(); - const Pointer &LHS = S.Stk.pop(); - - if (LHS.isZero() || RHS.isZero()) { - if (LHS.isZero() && RHS.isZero()) - S.Stk.push(BoolT::from(Fn(ComparisonCategoryResult::Equal))); - else - S.Stk.push(BoolT::from(Fn(ComparisonCategoryResult::Nonequal))); - return true; - } - - if (!Pointer::hasSameBase(LHS, RHS)) { - S.Stk.push(BoolT::from(Fn(ComparisonCategoryResult::Unordered))); - return true; - } else { - unsigned VL = LHS.getByteOffset(); - unsigned VR = RHS.getByteOffset(); - S.Stk.push(BoolT::from(Fn(Compare(VL, VR)))); - return true; - } -} - template ::T> -bool EQ(InterpState &S, CodePtr OpPC) { - return CmpHelperEQ(S, OpPC, [](ComparisonCategoryResult R) { - return R == ComparisonCategoryResult::Equal; - }); -} +bool Minus(InterpState &S, CodePtr OpPC) { + const T &Arg = S.Stk.pop(); -template ::T> -bool NE(InterpState &S, CodePtr OpPC) { - return CmpHelperEQ(S, OpPC, [](ComparisonCategoryResult R) { - return R != ComparisonCategoryResult::Equal; - }); -} + if (Arg.isSigned() && Arg.isMin()) { + // Push the truncated value in case the interpreter continues. + S.Stk.push(T::min(Arg.bitWidth())); -template ::T> -bool LT(InterpState &S, CodePtr OpPC) { - return CmpHelper(S, OpPC, [](ComparisonCategoryResult R) { - return R == ComparisonCategoryResult::Less; - }); -} - -template ::T> -bool LE(InterpState &S, CodePtr OpPC) { - return CmpHelper(S, OpPC, [](ComparisonCategoryResult R) { - return R == ComparisonCategoryResult::Less || - R == ComparisonCategoryResult::Equal; - }); -} - -template ::T> -bool GT(InterpState &S, CodePtr OpPC) { - return CmpHelper(S, OpPC, [](ComparisonCategoryResult R) { - return R == ComparisonCategoryResult::Greater; - }); -} + // Compute the actual value for the diagnostic. + const size_t Bits = Arg.bitWidth() + 1; + const APSInt Value = std::negate()(Arg.toAPSInt(Bits)); + return S.reportOverflow(S.getSource(OpPC).asExpr(), Value); + } -template ::T> -bool GE(InterpState &S, CodePtr OpPC) { - return CmpHelper(S, OpPC, [](ComparisonCategoryResult R) { - return R == ComparisonCategoryResult::Greater || - R == ComparisonCategoryResult::Equal; - }); + S.Stk.push(std::negate()(Arg)); + return true; } -//===----------------------------------------------------------------------===// -// InRange -//===----------------------------------------------------------------------===// - template ::T> -bool InRange(InterpState &S, CodePtr OpPC) { - const T RHS = S.Stk.pop(); - const T LHS = S.Stk.pop(); - const T Value = S.Stk.pop(); - - S.Stk.push(LHS <= Value && Value <= RHS); +bool Test(InterpState &S, CodePtr OpPC) { + S.Stk.push(!S.Stk.pop().isZero()); return true; } @@ -330,171 +182,6 @@ return true; } -template ::T> -bool GetField(InterpState &S, CodePtr OpPC, uint32_t I) { - const Pointer &Obj = S.Stk.peek(); - if (!CheckNull(S, OpPC, Obj, CSK_Field)) - return false; - if (!CheckRange(S, OpPC, Obj, CSK_Field)) - return false; - const Pointer &Field = Obj.atField(I); - if (!CheckLoad(S, OpPC, Field)) - return false; - S.Stk.push(Field.deref()); - return true; -} - -template ::T> -bool SetField(InterpState &S, CodePtr OpPC, uint32_t I) { - const T &Value = S.Stk.pop(); - const Pointer &Obj = S.Stk.peek(); - if (!CheckNull(S, OpPC, Obj, CSK_Field)) - return false; - if (!CheckRange(S, OpPC, Obj, CSK_Field)) - return false; - const Pointer &Field = Obj.atField(I); - if (!CheckStore(S, OpPC, Field)) - return false; - Field.deref() = Value; - return true; -} - -template ::T> -bool GetFieldPop(InterpState &S, CodePtr OpPC, uint32_t I) { - const Pointer &Obj = S.Stk.pop(); - if (!CheckNull(S, OpPC, Obj, CSK_Field)) - return false; - if (!CheckRange(S, OpPC, Obj, CSK_Field)) - return false; - const Pointer &Field = Obj.atField(I); - if (!CheckLoad(S, OpPC, Field)) - return false; - S.Stk.push(Field.deref()); - return true; -} - -template ::T> -bool GetThisField(InterpState &S, CodePtr OpPC, uint32_t I) { - if (S.checkingPotentialConstantExpression()) - return false; - const Pointer &This = S.Current->getThis(); - if (!CheckThis(S, OpPC, This)) - return false; - const Pointer &Field = This.atField(I); - if (!CheckLoad(S, OpPC, Field)) - return false; - S.Stk.push(Field.deref()); - return true; -} - -template ::T> -bool SetThisField(InterpState &S, CodePtr OpPC, uint32_t I) { - if (S.checkingPotentialConstantExpression()) - return false; - const T &Value = S.Stk.pop(); - const Pointer &This = S.Current->getThis(); - if (!CheckThis(S, OpPC, This)) - return false; - const Pointer &Field = This.atField(I); - if (!CheckStore(S, OpPC, Field)) - return false; - Field.deref() = Value; - return true; -} - -template ::T> -bool GetGlobal(InterpState &S, CodePtr OpPC, uint32_t I) { - auto *B = S.P.getGlobal(I); - if (B->isExtern()) - return false; - S.Stk.push(B->deref()); - return true; -} - -template ::T> -bool SetGlobal(InterpState &S, CodePtr OpPC, uint32_t I) { - // TODO: emit warning. - return false; -} - -template ::T> -bool InitGlobal(InterpState &S, CodePtr OpPC, uint32_t I) { - S.P.getGlobal(I)->deref() = S.Stk.pop(); - return true; -} - -template ::T> -bool InitThisField(InterpState &S, CodePtr OpPC, uint32_t I) { - if (S.checkingPotentialConstantExpression()) - return false; - const Pointer &This = S.Current->getThis(); - if (!CheckThis(S, OpPC, This)) - return false; - const Pointer &Field = This.atField(I); - Field.deref() = S.Stk.pop(); - Field.initialize(); - return true; -} - -template ::T> -bool InitThisBitField(InterpState &S, CodePtr OpPC, const Record::Field *F) { - if (S.checkingPotentialConstantExpression()) - return false; - const Pointer &This = S.Current->getThis(); - if (!CheckThis(S, OpPC, This)) - return false; - const Pointer &Field = This.atField(F->Offset); - const auto &Value = S.Stk.pop(); - Field.deref() = Value.truncate(F->Decl->getBitWidthValue(S.getCtx())); - Field.initialize(); - return true; -} - -template ::T> -bool InitThisFieldActive(InterpState &S, CodePtr OpPC, uint32_t I) { - if (S.checkingPotentialConstantExpression()) - return false; - const Pointer &This = S.Current->getThis(); - if (!CheckThis(S, OpPC, This)) - return false; - const Pointer &Field = This.atField(I); - Field.deref() = S.Stk.pop(); - Field.activate(); - Field.initialize(); - return true; -} - -template ::T> -bool InitField(InterpState &S, CodePtr OpPC, uint32_t I) { - const T &Value = S.Stk.pop(); - const Pointer &Field = S.Stk.pop().atField(I); - Field.deref() = Value; - Field.activate(); - Field.initialize(); - return true; -} - -template ::T> -bool InitBitField(InterpState &S, CodePtr OpPC, const Record::Field *F) { - const T &Value = S.Stk.pop(); - const Pointer &Field = S.Stk.pop().atField(F->Offset); - Field.deref() = Value.truncate(F->Decl->getBitWidthValue(S.getCtx())); - Field.activate(); - Field.initialize(); - return true; -} - -template ::T> -bool InitFieldActive(InterpState &S, CodePtr OpPC, uint32_t I) { - const T &Value = S.Stk.pop(); - const Pointer &Ptr = S.Stk.pop(); - const Pointer &Field = Ptr.atField(I); - Field.deref() = Value; - Field.activate(); - Field.initialize(); - return true; -} - //===----------------------------------------------------------------------===// // GetPtr Local/Param/Global/Field/This //===----------------------------------------------------------------------===// @@ -512,105 +199,6 @@ return true; } -inline bool GetPtrGlobal(InterpState &S, CodePtr OpPC, uint32_t I) { - S.Stk.push(S.P.getPtrGlobal(I)); - return true; -} - -inline bool GetPtrField(InterpState &S, CodePtr OpPC, uint32_t Off) { - const Pointer &Ptr = S.Stk.pop(); - if (!CheckNull(S, OpPC, Ptr, CSK_Field)) - return false; - if (!CheckExtern(S, OpPC, Ptr)) - return false; - if (!CheckRange(S, OpPC, Ptr, CSK_Field)) - return false; - S.Stk.push(Ptr.atField(Off)); - return true; -} - -inline bool GetPtrThisField(InterpState &S, CodePtr OpPC, uint32_t Off) { - if (S.checkingPotentialConstantExpression()) - return false; - const Pointer &This = S.Current->getThis(); - if (!CheckThis(S, OpPC, This)) - return false; - S.Stk.push(This.atField(Off)); - return true; -} - -inline bool GetPtrActiveField(InterpState &S, CodePtr OpPC, uint32_t Off) { - const Pointer &Ptr = S.Stk.pop(); - if (!CheckNull(S, OpPC, Ptr, CSK_Field)) - return false; - if (!CheckRange(S, OpPC, Ptr, CSK_Field)) - return false; - Pointer Field = Ptr.atField(Off); - Ptr.deactivate(); - Field.activate(); - S.Stk.push(std::move(Field)); - return true; -} - -inline bool GetPtrActiveThisField(InterpState &S, CodePtr OpPC, uint32_t Off) { - if (S.checkingPotentialConstantExpression()) - return false; - const Pointer &This = S.Current->getThis(); - if (!CheckThis(S, OpPC, This)) - return false; - Pointer Field = This.atField(Off); - This.deactivate(); - Field.activate(); - S.Stk.push(std::move(Field)); - return true; -} - -inline bool GetPtrBase(InterpState &S, CodePtr OpPC, uint32_t Off) { - const Pointer &Ptr = S.Stk.pop(); - if (!CheckNull(S, OpPC, Ptr, CSK_Base)) - return false; - S.Stk.push(Ptr.atField(Off)); - return true; -} - -inline bool GetPtrThisBase(InterpState &S, CodePtr OpPC, uint32_t Off) { - if (S.checkingPotentialConstantExpression()) - return false; - const Pointer &This = S.Current->getThis(); - if (!CheckThis(S, OpPC, This)) - return false; - S.Stk.push(This.atField(Off)); - return true; -} - -inline bool VirtBaseHelper(InterpState &S, CodePtr OpPC, const RecordDecl *Decl, - const Pointer &Ptr) { - Pointer Base = Ptr; - while (Base.isBaseClass()) - Base = Base.getBase(); - - auto *Field = Base.getRecord()->getVirtualBase(Decl); - S.Stk.push(Base.atField(Field->Offset)); - return true; -} - -inline bool GetPtrVirtBase(InterpState &S, CodePtr OpPC, const RecordDecl *D) { - const Pointer &Ptr = S.Stk.pop(); - if (!CheckNull(S, OpPC, Ptr, CSK_Base)) - return false; - return VirtBaseHelper(S, OpPC, D, Ptr); -} - -inline bool GetPtrThisVirtBase(InterpState &S, CodePtr OpPC, - const RecordDecl *D) { - if (S.checkingPotentialConstantExpression()) - return false; - const Pointer &This = S.Current->getThis(); - if (!CheckThis(S, OpPC, This)) - return false; - return VirtBaseHelper(S, OpPC, D, S.Current->getThis()); -} - //===----------------------------------------------------------------------===// // Load, Store, Init //===----------------------------------------------------------------------===// @@ -618,7 +206,7 @@ template ::T> bool Load(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.peek(); - if (!CheckLoad(S, OpPC, Ptr)) + if (!S.CheckLoad(OpPC, Ptr)) return false; S.Stk.push(Ptr.deref()); return true; @@ -627,7 +215,7 @@ template ::T> bool LoadPop(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop(); - if (!CheckLoad(S, OpPC, Ptr)) + if (!S.CheckLoad(OpPC, Ptr)) return false; S.Stk.push(Ptr.deref()); return true; @@ -637,7 +225,7 @@ bool Store(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.peek(); - if (!CheckStore(S, OpPC, Ptr)) + if (!S.CheckStore(OpPC, Ptr)) return false; Ptr.deref() = Value; return true; @@ -647,146 +235,12 @@ bool StorePop(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop(); const Pointer &Ptr = S.Stk.pop(); - if (!CheckStore(S, OpPC, Ptr)) + if (!S.CheckStore(OpPC, Ptr)) return false; Ptr.deref() = Value; return true; } -template ::T> -bool StoreBitField(InterpState &S, CodePtr OpPC) { - const T &Value = S.Stk.pop(); - const Pointer &Ptr = S.Stk.peek(); - if (!CheckStore(S, OpPC, Ptr)) - return false; - if (auto *FD = Ptr.getField()) { - Ptr.deref() = Value.truncate(FD->getBitWidthValue(S.getCtx())); - } else { - Ptr.deref() = Value; - } - return true; -} - -template ::T> -bool StoreBitFieldPop(InterpState &S, CodePtr OpPC) { - const T &Value = S.Stk.pop(); - const Pointer &Ptr = S.Stk.pop(); - if (!CheckStore(S, OpPC, Ptr)) - return false; - if (auto *FD = Ptr.getField()) { - Ptr.deref() = Value.truncate(FD->getBitWidthValue(S.getCtx())); - } else { - Ptr.deref() = Value; - } - return true; -} - -template ::T> -bool InitPop(InterpState &S, CodePtr OpPC) { - const T &Value = S.Stk.pop(); - const Pointer &Ptr = S.Stk.pop(); - if (!CheckInit(S, OpPC, Ptr)) - return false; - Ptr.initialize(); - new (&Ptr.deref()) T(Value); - return true; -} - -template ::T> -bool InitElem(InterpState &S, CodePtr OpPC, uint32_t Idx) { - const T &Value = S.Stk.pop(); - const Pointer &Ptr = S.Stk.peek().atIndex(Idx); - if (!CheckInit(S, OpPC, Ptr)) - return false; - Ptr.initialize(); - new (&Ptr.deref()) T(Value); - return true; -} - -template ::T> -bool InitElemPop(InterpState &S, CodePtr OpPC, uint32_t Idx) { - const T &Value = S.Stk.pop(); - const Pointer &Ptr = S.Stk.pop().atIndex(Idx); - if (!CheckInit(S, OpPC, Ptr)) - return false; - Ptr.initialize(); - new (&Ptr.deref()) T(Value); - return true; -} - -//===----------------------------------------------------------------------===// -// AddOffset, SubOffset -//===----------------------------------------------------------------------===// - -template bool OffsetHelper(InterpState &S, CodePtr OpPC) { - // Fetch the pointer and the offset. - const T &Offset = S.Stk.pop(); - const Pointer &Ptr = S.Stk.pop(); - if (!CheckNull(S, OpPC, Ptr, CSK_ArrayIndex)) - return false; - if (!CheckRange(S, OpPC, Ptr, CSK_ArrayToPointer)) - return false; - - // Get a version of the index comparable to the type. - T Index = T::from(Ptr.getIndex(), Offset.bitWidth()); - // A zero offset does not change the pointer, but in the case of an array - // it has to be adjusted to point to the first element instead of the array. - if (Offset.isZero()) { - S.Stk.push(Index.isZero() ? Ptr.atIndex(0) : Ptr); - return true; - } - // Arrays of unknown bounds cannot have pointers into them. - if (!CheckArray(S, OpPC, Ptr)) - return false; - - // Compute the largest index into the array. - unsigned MaxIndex = Ptr.getNumElems(); - - // Helper to report an invalid offset, computed as APSInt. - auto InvalidOffset = [&]() { - const unsigned Bits = Offset.bitWidth(); - APSInt APOffset(Offset.toAPSInt().extend(Bits + 2), false); - APSInt APIndex(Index.toAPSInt().extend(Bits + 2), false); - APSInt NewIndex = Add ? (APIndex + APOffset) : (APIndex - APOffset); - S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index) - << NewIndex - << /*array*/ static_cast(!Ptr.inArray()) - << static_cast(MaxIndex); - return false; - }; - - // If the new offset would be negative, bail out. - if (Add && Offset.isNegative() && (Offset.isMin() || -Offset > Index)) - return InvalidOffset(); - if (!Add && Offset.isPositive() && Index < Offset) - return InvalidOffset(); - - // If the new offset would be out of bounds, bail out. - unsigned MaxOffset = MaxIndex - Ptr.getIndex(); - if (Add && Offset.isPositive() && Offset > MaxOffset) - return InvalidOffset(); - if (!Add && Offset.isNegative() && (Offset.isMin() || -Offset > MaxOffset)) - return InvalidOffset(); - - // Offset is valid - compute it on unsigned. - int64_t WideIndex = static_cast(Index); - int64_t WideOffset = static_cast(Offset); - int64_t Result = Add ? (WideIndex + WideOffset) : (WideIndex - WideOffset); - S.Stk.push(Ptr.atIndex(static_cast(Result))); - return true; -} - -template ::T> -bool AddOffset(InterpState &S, CodePtr OpPC) { - return OffsetHelper(S, OpPC); -} - -template ::T> -bool SubOffset(InterpState &S, CodePtr OpPC) { - return OffsetHelper(S, OpPC); -} - - //===----------------------------------------------------------------------===// // Destroy //===----------------------------------------------------------------------===// @@ -797,7 +251,7 @@ } //===----------------------------------------------------------------------===// -// Cast, CastFP +// Cast //===----------------------------------------------------------------------===// template bool Cast(InterpState &S, CodePtr OpPC) { @@ -823,108 +277,6 @@ return true; } -//===----------------------------------------------------------------------===// -// This, ImplicitThis -//===----------------------------------------------------------------------===// - -inline bool This(InterpState &S, CodePtr OpPC) { - // Cannot read 'this' in this mode. - if (S.checkingPotentialConstantExpression()) { - return false; - } - - const Pointer &This = S.Current->getThis(); - if (!CheckThis(S, OpPC, This)) - return false; - - S.Stk.push(This); - return true; -} - -//===----------------------------------------------------------------------===// -// Shr, Shl -//===----------------------------------------------------------------------===// - -template ::T> -unsigned Trunc(InterpState &S, CodePtr OpPC, unsigned Bits, const T &V) { - // C++11 [expr.shift]p1: Shift width must be less than the bit width of - // the shifted type. - if (Bits > 1 && V >= T::from(Bits, V.bitWidth())) { - const Expr *E = S.Current->getExpr(OpPC); - const APSInt Val = V.toAPSInt(); - QualType Ty = E->getType(); - S.CCEDiag(E, diag::note_constexpr_large_shift) << Val << Ty << Bits; - return Bits; - } else { - return static_cast(V); - } -} - -template ::T> -inline bool ShiftRight(InterpState &S, CodePtr OpPC, const T &V, unsigned RHS) { - if (RHS >= V.bitWidth()) { - S.Stk.push(T::from(0, V.bitWidth())); - } else { - S.Stk.push(T::from(V >> RHS, V.bitWidth())); - } - return true; -} - -template ::T> -inline bool ShiftLeft(InterpState &S, CodePtr OpPC, const T &V, unsigned RHS) { - if (V.isSigned() && !S.getLangOpts().CPlusPlus2a) { - // C++11 [expr.shift]p2: A signed left shift must have a non-negative - // operand, and must not overflow the corresponding unsigned type. - // C++2a [expr.shift]p2: E1 << E2 is the unique value congruent to - // E1 x 2^E2 module 2^N. - if (V.isNegative()) { - const Expr *E = S.Current->getExpr(OpPC); - S.CCEDiag(E, diag::note_constexpr_lshift_of_negative) << V.toAPSInt(); - } else if (V.countLeadingZeros() < RHS) { - S.CCEDiag(S.Current->getExpr(OpPC), diag::note_constexpr_lshift_discards); - } - } - - if (V.bitWidth() == 1) { - S.Stk.push(V); - } else if (RHS >= V.bitWidth()) { - S.Stk.push(T::from(0, V.bitWidth())); - } else { - S.Stk.push(T::from(V.toUnsigned() << RHS, V.bitWidth())); - } - return true; -} - -template -inline bool Shr(InterpState &S, CodePtr OpPC) { - const auto &RHS = S.Stk.pop::T>(); - const auto &LHS = S.Stk.pop::T>(); - const unsigned Bits = LHS.bitWidth(); - - if (RHS.isSigned() && RHS.isNegative()) { - const SourceInfo &Loc = S.Current->getSource(OpPC); - S.CCEDiag(Loc, diag::note_constexpr_negative_shift) << RHS.toAPSInt(); - return ShiftLeft(S, OpPC, LHS, Trunc(S, OpPC, Bits, -RHS)); - } else { - return ShiftRight(S, OpPC, LHS, Trunc(S, OpPC, Bits, RHS)); - } -} - -template -inline bool Shl(InterpState &S, CodePtr OpPC) { - const auto &RHS = S.Stk.pop::T>(); - const auto &LHS = S.Stk.pop::T>(); - const unsigned Bits = LHS.bitWidth(); - - if (RHS.isSigned() && RHS.isNegative()) { - const SourceInfo &Loc = S.Current->getSource(OpPC); - S.CCEDiag(Loc, diag::note_constexpr_negative_shift) << RHS.toAPSInt(); - return ShiftRight(S, OpPC, LHS, Trunc(S, OpPC, Bits, -RHS)); - } else { - return ShiftLeft(S, OpPC, LHS, Trunc(S, OpPC, Bits, RHS)); - } -} - //===----------------------------------------------------------------------===// // NoRet //===----------------------------------------------------------------------===// @@ -936,23 +288,16 @@ } //===----------------------------------------------------------------------===// -// NarrowPtr, ExpandPtr +// Trap //===----------------------------------------------------------------------===// -inline bool NarrowPtr(InterpState &S, CodePtr OpPC) { - const Pointer &Ptr = S.Stk.pop(); - S.Stk.push(Ptr.narrow()); - return true; -} - -inline bool ExpandPtr(InterpState &S, CodePtr OpPC) { - const Pointer &Ptr = S.Stk.pop(); - S.Stk.push(Ptr.expand()); - return true; +inline bool Trap(InterpState &S, CodePtr OpPC) { + const SourceLocation &Loc = S.getSource(OpPC).getLoc(); + S.FFDiag(Loc, diag::note_invalid_subexpr_in_const_expr); + return false; } -/// Interpreter entry point. -bool Interpret(InterpState &S, APValue &Result); +#include "Opcodes/Comparison.h" } // namespace interp } // namespace clang diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp deleted file mode 100644 --- a/clang/lib/AST/Interp/Interp.cpp +++ /dev/null @@ -1,417 +0,0 @@ -//===--- InterpState.cpp - Interpreter for the constexpr 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 "Interp.h" -#include -#include -#include "Function.h" -#include "InterpFrame.h" -#include "InterpStack.h" -#include "Opcode.h" -#include "PrimType.h" -#include "Program.h" -#include "State.h" -#include "clang/AST/ASTContext.h" -#include "clang/AST/ASTDiagnostic.h" -#include "clang/AST/CXXInheritance.h" -#include "clang/AST/Expr.h" -#include "clang/AST/ExprCXX.h" -#include "llvm/ADT/APSInt.h" - -using namespace clang; -using namespace clang::interp; - -//===----------------------------------------------------------------------===// -// Ret -//===----------------------------------------------------------------------===// - -template ::T> -static bool Ret(InterpState &S, CodePtr &PC, APValue &Result) { - S.CallStackDepth--; - const T &Ret = S.Stk.pop(); - - assert(S.Current->getFrameOffset() == S.Stk.size() && "Invalid frame"); - if (!S.checkingPotentialConstantExpression()) - S.Current->popArgs(); - - if (InterpFrame *Caller = S.Current->Caller) { - PC = S.Current->getRetPC(); - delete S.Current; - S.Current = Caller; - S.Stk.push(Ret); - } else { - delete S.Current; - S.Current = nullptr; - if (!ReturnValue(Ret, Result)) - return false; - } - return true; -} - -static bool RetVoid(InterpState &S, CodePtr &PC, APValue &Result) { - S.CallStackDepth--; - - assert(S.Current->getFrameOffset() == S.Stk.size() && "Invalid frame"); - if (!S.checkingPotentialConstantExpression()) - S.Current->popArgs(); - - if (InterpFrame *Caller = S.Current->Caller) { - PC = S.Current->getRetPC(); - delete S.Current; - S.Current = Caller; - } else { - delete S.Current; - S.Current = nullptr; - } - return true; -} - -static bool RetValue(InterpState &S, CodePtr &Pt, APValue &Result) { - llvm::report_fatal_error("Interpreter cannot return values"); -} - -//===----------------------------------------------------------------------===// -// Jmp, Jt, Jf -//===----------------------------------------------------------------------===// - -static bool Jmp(InterpState &S, CodePtr &PC, int32_t Offset) { - PC += Offset; - return true; -} - -static bool Jt(InterpState &S, CodePtr &PC, int32_t Offset) { - if (S.Stk.pop()) { - PC += Offset; - } - return true; -} - -static bool Jf(InterpState &S, CodePtr &PC, int32_t Offset) { - if (!S.Stk.pop()) { - PC += Offset; - } - return true; -} - -static bool CheckInitialized(InterpState &S, CodePtr OpPC, const Pointer &Ptr, - AccessKinds AK) { - if (Ptr.isInitialized()) - return true; - if (!S.checkingPotentialConstantExpression()) { - const SourceInfo &Loc = S.Current->getSource(OpPC); - S.FFDiag(Loc, diag::note_constexpr_access_uninit) << AK << false; - } - return false; -} - -static bool CheckActive(InterpState &S, CodePtr OpPC, const Pointer &Ptr, - AccessKinds AK) { - if (Ptr.isActive()) - return true; - - // Get the inactive field descriptor. - const FieldDecl *InactiveField = Ptr.getField(); - - // Walk up the pointer chain to find the union which is not active. - Pointer U = Ptr.getBase(); - while (!U.isActive()) { - U = U.getBase(); - } - - // Find the active field of the union. - Record *R = U.getRecord(); - assert(R && R->isUnion() && "Not a union"); - const FieldDecl *ActiveField = nullptr; - for (unsigned I = 0, N = R->getNumFields(); I < N; ++I) { - const Pointer &Field = U.atField(R->getField(I)->Offset); - if (Field.isActive()) { - ActiveField = Field.getField(); - break; - } - } - - const SourceInfo &Loc = S.Current->getSource(OpPC); - S.FFDiag(Loc, diag::note_constexpr_access_inactive_union_member) - << AK << InactiveField << !ActiveField << ActiveField; - return false; -} - -static bool CheckTemporary(InterpState &S, CodePtr OpPC, const Pointer &Ptr, - AccessKinds AK) { - if (auto ID = Ptr.getDeclID()) { - if (!Ptr.isStaticTemporary()) - return true; - - if (Ptr.getDeclDesc()->getType().isConstQualified()) - return true; - - if (S.P.getCurrentDecl() == ID) - return true; - - const SourceInfo &E = S.Current->getSource(OpPC); - S.FFDiag(E, diag::note_constexpr_access_static_temporary, 1) << AK; - S.Note(Ptr.getDeclLoc(), diag::note_constexpr_temporary_here); - return false; - } - return true; -} - -static bool CheckGlobal(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { - if (auto ID = Ptr.getDeclID()) { - if (!Ptr.isStatic()) - return true; - - if (S.P.getCurrentDecl() == ID) - return true; - - S.FFDiag(S.Current->getLocation(OpPC), diag::note_constexpr_modify_global); - return false; - } - return true; -} - -namespace clang { -namespace interp { - -bool CheckExtern(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { - if (!Ptr.isExtern()) - return true; - - if (!S.checkingPotentialConstantExpression()) { - auto *VD = Ptr.getDeclDesc()->asValueDecl(); - const SourceInfo &Loc = S.Current->getSource(OpPC); - S.FFDiag(Loc, diag::note_constexpr_ltor_non_constexpr, 1) << VD; - S.Note(VD->getLocation(), diag::note_declared_at); - } - return false; -} - -bool CheckArray(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { - if (!Ptr.isUnknownSizeArray()) - return true; - const SourceInfo &E = S.Current->getSource(OpPC); - S.FFDiag(E, diag::note_constexpr_unsized_array_indexed); - return false; -} - -bool CheckLive(InterpState &S, CodePtr OpPC, const Pointer &Ptr, - AccessKinds AK) { - const auto &Src = S.Current->getSource(OpPC); - if (Ptr.isZero()) { - - if (Ptr.isField()) - S.FFDiag(Src, diag::note_constexpr_null_subobject) << CSK_Field; - else - S.FFDiag(Src, diag::note_constexpr_access_null) << AK; - - return false; - } - - if (!Ptr.isLive()) { - bool IsTemp = Ptr.isTemporary(); - - S.FFDiag(Src, diag::note_constexpr_lifetime_ended, 1) << AK << !IsTemp; - - if (IsTemp) - S.Note(Ptr.getDeclLoc(), diag::note_constexpr_temporary_here); - else - S.Note(Ptr.getDeclLoc(), diag::note_declared_at); - - return false; - } - - return true; -} - -bool CheckNull(InterpState &S, CodePtr OpPC, const Pointer &Ptr, - CheckSubobjectKind CSK) { - if (!Ptr.isZero()) - return true; - const SourceInfo &Loc = S.Current->getSource(OpPC); - S.FFDiag(Loc, diag::note_constexpr_null_subobject) << CSK; - return false; -} - -bool CheckRange(InterpState &S, CodePtr OpPC, const Pointer &Ptr, - AccessKinds AK) { - if (!Ptr.isOnePastEnd()) - return true; - const SourceInfo &Loc = S.Current->getSource(OpPC); - S.FFDiag(Loc, diag::note_constexpr_access_past_end) << AK; - return false; -} - -bool CheckRange(InterpState &S, CodePtr OpPC, const Pointer &Ptr, - CheckSubobjectKind CSK) { - if (!Ptr.isElementPastEnd()) - return true; - const SourceInfo &Loc = S.Current->getSource(OpPC); - S.FFDiag(Loc, diag::note_constexpr_past_end_subobject) << CSK; - return false; -} - -bool CheckConst(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { - assert(Ptr.isLive() && "Pointer is not live"); - if (!Ptr.isConst()) { - return true; - } - - const QualType Ty = Ptr.getType(); - const SourceInfo &Loc = S.Current->getSource(OpPC); - S.FFDiag(Loc, diag::note_constexpr_modify_const_type) << Ty; - return false; -} - -bool CheckMutable(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { - assert(Ptr.isLive() && "Pointer is not live"); - if (!Ptr.isMutable()) { - return true; - } - - const SourceInfo &Loc = S.Current->getSource(OpPC); - const FieldDecl *Field = Ptr.getField(); - S.FFDiag(Loc, diag::note_constexpr_access_mutable, 1) << AK_Read << Field; - S.Note(Field->getLocation(), diag::note_declared_at); - return false; -} - -bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { - if (!CheckLive(S, OpPC, Ptr, AK_Read)) - return false; - if (!CheckExtern(S, OpPC, Ptr)) - return false; - if (!CheckRange(S, OpPC, Ptr, AK_Read)) - return false; - if (!CheckInitialized(S, OpPC, Ptr, AK_Read)) - return false; - if (!CheckActive(S, OpPC, Ptr, AK_Read)) - return false; - if (!CheckTemporary(S, OpPC, Ptr, AK_Read)) - return false; - if (!CheckMutable(S, OpPC, Ptr)) - return false; - return true; -} - -bool CheckStore(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { - if (!CheckLive(S, OpPC, Ptr, AK_Assign)) - return false; - if (!CheckExtern(S, OpPC, Ptr)) - return false; - if (!CheckRange(S, OpPC, Ptr, AK_Assign)) - return false; - if (!CheckGlobal(S, OpPC, Ptr)) - return false; - if (!CheckConst(S, OpPC, Ptr)) - return false; - return true; -} - -bool CheckInvoke(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { - if (!CheckLive(S, OpPC, Ptr, AK_MemberCall)) - return false; - if (!CheckExtern(S, OpPC, Ptr)) - return false; - if (!CheckRange(S, OpPC, Ptr, AK_MemberCall)) - return false; - return true; -} - -bool CheckInit(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { - if (!CheckLive(S, OpPC, Ptr, AK_Assign)) - return false; - if (!CheckRange(S, OpPC, Ptr, AK_Assign)) - return false; - return true; -} - -bool CheckCallable(InterpState &S, CodePtr OpPC, Function *F) { - const SourceLocation &Loc = S.Current->getLocation(OpPC); - - if (F->isVirtual()) { - if (!S.getLangOpts().CPlusPlus2a) { - S.CCEDiag(Loc, diag::note_constexpr_virtual_call); - return false; - } - } - - if (!F->isConstexpr()) { - if (S.getLangOpts().CPlusPlus11) { - const FunctionDecl *DiagDecl = F->getDecl(); - - // If this function is not constexpr because it is an inherited - // non-constexpr constructor, diagnose that directly. - auto *CD = dyn_cast(DiagDecl); - if (CD && CD->isInheritingConstructor()) { - auto *Inherited = CD->getInheritedConstructor().getConstructor(); - if (!Inherited->isConstexpr()) - DiagDecl = CD = Inherited; - } - - // FIXME: If DiagDecl is an implicitly-declared special member function - // or an inheriting constructor, we should be much more explicit about why - // it's not constexpr. - if (CD && CD->isInheritingConstructor()) - S.FFDiag(Loc, diag::note_constexpr_invalid_inhctor, 1) - << CD->getInheritedConstructor().getConstructor()->getParent(); - else - S.FFDiag(Loc, diag::note_constexpr_invalid_function, 1) - << DiagDecl->isConstexpr() << (bool)CD << DiagDecl; - S.Note(DiagDecl->getLocation(), diag::note_declared_at); - } else { - S.FFDiag(Loc, diag::note_invalid_subexpr_in_const_expr); - } - return false; - } - - return true; -} - -bool CheckThis(InterpState &S, CodePtr OpPC, const Pointer &This) { - if (!This.isZero()) - return true; - - const SourceInfo &Loc = S.Current->getSource(OpPC); - - bool IsImplicit = false; - if (auto *E = dyn_cast_or_null(Loc.asExpr())) - IsImplicit = E->isImplicit(); - - if (S.getLangOpts().CPlusPlus11) - S.FFDiag(Loc, diag::note_constexpr_this) << IsImplicit; - else - S.FFDiag(Loc); - - return false; -} - -bool CheckPure(InterpState &S, CodePtr OpPC, const CXXMethodDecl *MD) { - if (!MD->isPure()) - return true; - const SourceInfo &E = S.Current->getSource(OpPC); - S.FFDiag(E, diag::note_constexpr_pure_virtual_call, 1) << MD; - S.Note(MD->getLocation(), diag::note_declared_at); - return false; -} -bool Interpret(InterpState &S, APValue &Result) { - CodePtr PC = S.Current->getPC(); - - for (;;) { - auto Op = PC.read(); - CodePtr OpPC = PC; - - switch (Op) { -#define GET_INTERP -#include "Opcodes.inc" -#undef GET_INTERP - } - } -} - -} // namespace interp -} // namespace clang diff --git a/clang/lib/AST/Interp/InterpFrame.h b/clang/lib/AST/Interp/InterpFrame.h --- a/clang/lib/AST/Interp/InterpFrame.h +++ b/clang/lib/AST/Interp/InterpFrame.h @@ -50,6 +50,9 @@ /// Returns the parent frame object. Frame *getCaller() const; + /// Returns the interp caller object. + InterpFrame *getInterpCaller() { return Caller; } + /// Returns the location of the call to the frame. SourceLocation getCallLocation() const; @@ -105,11 +108,6 @@ /// Returns the return address of the frame. CodePtr getRetPC() const { return RetPC; } - /// Map a location to a source. - virtual SourceInfo getSource(CodePtr PC) const; - const Expr *getExpr(CodePtr PC) const; - SourceLocation getLocation(CodePtr PC) const; - private: /// Returns an original argument from the stack. template const T &stackRef(unsigned Offset) { diff --git a/clang/lib/AST/Interp/InterpFrame.cpp b/clang/lib/AST/Interp/InterpFrame.cpp --- a/clang/lib/AST/Interp/InterpFrame.cpp +++ b/clang/lib/AST/Interp/InterpFrame.cpp @@ -8,8 +8,8 @@ #include "InterpFrame.h" #include "Function.h" -#include "Interp.h" #include "InterpStack.h" +#include "InterpState.h" #include "PrimType.h" #include "Program.h" #include "clang/AST/DeclCXX.h" @@ -38,6 +38,20 @@ InterpFrame::~InterpFrame() { if (Func && Func->isConstructor() && This.isBaseClass()) This.initialize(); + + if (Func && Func->isConstructor()) { + if (This.isBaseClass()) { + // Base classes must set their initialised flag. + This.initialize(); + } else if (!This.isRoot()) { + // Records in unions must be activated. + if (auto *R = This.getBase().getRecord()) { + if (R->isUnion()) + This.activate(); + } + } + } + for (auto &Param : Params) S.deallocate(reinterpret_cast(Param.second.get())); } @@ -89,10 +103,8 @@ if (!Ty->isReferenceType()) OS << "&"; llvm::SmallVector Levels; - for (Pointer F = P; !F.isRoot(); ) { + for (Pointer F = P; !F.isRoot(); F = F.getContainingObject()) Levels.push_back(F); - F = F.isArrayElement() ? F.getArray().expand() : F.getBase(); - } printDesc(P.getDeclDesc()); for (auto It = Levels.rbegin(); It != Levels.rend(); ++It) { @@ -144,12 +156,12 @@ SourceLocation InterpFrame::getCallLocation() const { if (!Caller->Func) - return S.getLocation(nullptr, {}); - return S.getLocation(Caller->Func, RetPC - sizeof(uintptr_t)); + return S.getSource(nullptr, {}).getLoc(); + return S.getSource(Caller->Func, RetPC - sizeof(uintptr_t)).getLoc(); } const FunctionDecl *InterpFrame::getCallee() const { - return Func->getDecl(); + return Func ? Func->getDecl() : nullptr; } Pointer InterpFrame::getLocalPointer(unsigned Offset) { @@ -178,16 +190,3 @@ Params.insert({Off, std::move(Memory)}); return Pointer(B); } - -SourceInfo InterpFrame::getSource(CodePtr PC) const { - return S.getSource(Func, PC); -} - -const Expr *InterpFrame::getExpr(CodePtr PC) const { - return S.getExpr(Func, PC); -} - -SourceLocation InterpFrame::getLocation(CodePtr PC) const { - return S.getLocation(Func, PC); -} - diff --git a/clang/lib/AST/Interp/InterpHelper.h b/clang/lib/AST/Interp/InterpHelper.h new file mode 100644 --- /dev/null +++ b/clang/lib/AST/Interp/InterpHelper.h @@ -0,0 +1,27 @@ +//===--- HeapUtils.h - Utilities to handle the heap -------------*- 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_INTERPHELPER_H +#define LLVM_CLANG_AST_INTERP_INTERPHELPER_H + +#include "Pointer.h" +#include "Source.h" + +namespace clang { +namespace interp { + +/// Convers a primitive value to an APValue. +template bool PrimitiveToValue(const T &V, APValue &R) { + R = V.toAPValue(); + return true; +} + +} // namespace interp +} // namespace clang + +#endif diff --git a/clang/lib/AST/Interp/InterpHelper.cpp b/clang/lib/AST/Interp/InterpHelper.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/AST/Interp/InterpHelper.cpp @@ -0,0 +1,18 @@ +//===--- InterpHelper.cpp - Auxiliary utilities -----------------*- 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 "InterpHelper.h" +#include "InterpState.h" +#include "PrimType.h" +#include "Record.h" +#include "clang/AST/Type.h" +#include "llvm/ADT/APFloat.h" +#include "llvm/ADT/APSInt.h" + +using namespace clang; +using namespace interp; diff --git a/clang/lib/AST/Interp/InterpLoop.h b/clang/lib/AST/Interp/InterpLoop.h new file mode 100644 --- /dev/null +++ b/clang/lib/AST/Interp/InterpLoop.h @@ -0,0 +1,28 @@ +//===--- InterpLoop.h - Interpreter for the constexpr 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 +// +//===----------------------------------------------------------------------===// +// +// Definition of the interpreter state and entry point. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_INTERP_INTERPLOOP_H +#define LLVM_CLANG_AST_INTERP_INTERPLOOP_H + +#include "InterpState.h" + +namespace clang { +namespace interp { + +/// Interpreter entry point. +bool Interpret(InterpState &S, APValue &Result); + +} // namespace interp +} // namespace clang + +#endif + diff --git a/clang/lib/AST/Interp/InterpLoop.cpp b/clang/lib/AST/Interp/InterpLoop.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/AST/Interp/InterpLoop.cpp @@ -0,0 +1,176 @@ +//===--- InterpLoop.cpp - Interpreter for the constexpr 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 +// +//===----------------------------------------------------------------------===// +// +// Definition of the interpreter state and entry point. +// +//===----------------------------------------------------------------------===// + +#include "InterpLoop.h" +#include "Function.h" +#include "Interp.h" +#include "InterpFrame.h" +#include "InterpHelper.h" +#include "InterpStack.h" +#include "Opcode.h" +#include "PrimType.h" +#include "Program.h" +#include "State.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/ASTDiagnostic.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" +#include "llvm/ADT/APSInt.h" +#include +#include + +using namespace clang; +using namespace clang::interp; + +//===----------------------------------------------------------------------===// +// Ret +//===----------------------------------------------------------------------===// + +template ::T> +static bool Ret(InterpState &S, CodePtr &PC, APValue &Result) { + S.CallStackDepth--; + const T &Ret = S.Stk.pop(); + + assert(S.Current->getFrameOffset() == S.Stk.size() && "Invalid frame"); + if (!S.checkingPotentialConstantExpression()) + S.Current->popArgs(); + + if (InterpFrame *Caller = S.Current->Caller) { + PC = S.Current->getRetPC(); + delete S.Current; + S.Current = Caller; + S.Stk.push(Ret); + } else { + delete S.Current; + S.Current = nullptr; + if (!PrimitiveToValue(Ret, Result)) + return false; + } + return true; +} + +static bool RetVoid(InterpState &S, CodePtr &PC, APValue &Result) { + S.CallStackDepth--; + + assert(S.Current->getFrameOffset() == S.Stk.size() && "Invalid frame"); + if (!S.checkingPotentialConstantExpression()) + S.Current->popArgs(); + + if (InterpFrame *Caller = S.Current->Caller) { + PC = S.Current->getRetPC(); + delete S.Current; + S.Current = Caller; + } else { + delete S.Current; + S.Current = nullptr; + } + return true; +} + +//===----------------------------------------------------------------------===// +// Jmp, Jt, Jf +//===----------------------------------------------------------------------===// + +static bool Jmp(InterpState &S, CodePtr &PC, int32_t Offset) { + PC += Offset; + return true; +} + +static bool Jt(InterpState &S, CodePtr &PC, int32_t Offset) { + if (S.Stk.pop().isTrue()) + PC += Offset; + return true; +} + +static bool Jf(InterpState &S, CodePtr &PC, int32_t Offset) { + if (S.Stk.pop().isFalse()) + PC += Offset; + return true; +} + +//===----------------------------------------------------------------------===// +// Call, NoCall, Invoke, NoInvoke, IndirectInvoke +//===----------------------------------------------------------------------===// + +static bool HandleCall(InterpState &S, CodePtr &PC, CodePtr OpPC, Function *F, + Pointer &&This) { + if (!S.CheckCallable(OpPC, F->getDecl())) + return false; + if (S.checkingPotentialConstantExpression()) + return false; + + // Adjust the state. + S.CallStackDepth++; + + // Construct a new frame. + S.Current = new InterpFrame(S, F, S.Current, PC, std::move(This)); + + // Transfer control to the callee. + PC = F->getCodeBegin(); + return true; +} + +static bool HandleNoCall(InterpState &S, CodePtr PC, const FunctionDecl *FD) { + CodePtr OpPC = PC - sizeof(FD); + if (!S.CheckCallable(OpPC, FD)) + return false; + if (S.checkingPotentialConstantExpression()) + return false; + const SourceInfo &CallLoc = S.getSource(OpPC); + if (S.getLangOpts().CPlusPlus11) { + S.FFDiag(CallLoc, diag::note_constexpr_invalid_function, 1) + << FD->isConstexpr() << isa(FD) << FD; + S.Note(FD->getLocation(), diag::note_declared_at); + } else { + S.FFDiag(CallLoc, diag::note_invalid_subexpr_in_const_expr); + } + return false; +} + +static bool Call(InterpState &S, CodePtr &PC, Function *F) { + return HandleCall(S, PC, PC - sizeof(F), F, {}); +} + +static bool NoCall(InterpState &S, CodePtr &PC, const FunctionDecl *FD) { + CodePtr OpPC = PC - sizeof(FD); + + // Function was not defined - emit a diagnostic. + if (auto *F = S.P.getFunction(FD)) { + // Function was defined in the meantime - rewrite opcode. + S.Current->getFunction()->relocateCall(OpPC, F); + return HandleCall(S, PC, OpPC, F, {}); + } + // Function was not defined - emit a diagnostic. + return HandleNoCall(S, PC, FD); +} + +namespace clang { +namespace interp { + +bool Interpret(InterpState &S, APValue &Result) { + CodePtr PC = S.Current->getPC(); + + for (;;) { + auto Op = PC.read(); + CodePtr OpPC = PC; + + switch (Op) { +#define GET_INTERP +#include "Opcodes.inc" +#undef GET_INTERP + } + } +} + +} // namespace interp +} // namespace clang diff --git a/clang/lib/AST/Interp/InterpState.h b/clang/lib/AST/Interp/InterpState.h --- a/clang/lib/AST/Interp/InterpState.h +++ b/clang/lib/AST/Interp/InterpState.h @@ -31,7 +31,7 @@ class SourceMapper; /// Interpreter context. -class InterpState final : public State, public SourceMapper { +class InterpState final : public State { public: InterpState(State &Parent, Program &P, InterpStack &Stk, Context &Ctx, SourceMapper *M = nullptr); @@ -81,10 +81,47 @@ void deallocate(Block *B); /// Delegates source mapping to the mapper. - SourceInfo getSource(Function *F, CodePtr PC) const override { + SourceInfo getSource(Function *F, CodePtr PC) const { return M ? M->getSource(F, PC) : F->getSource(PC); } + /// Maps the location to the innermost non-default action. + SourceInfo getSource(CodePtr PC) const; + +public: + /// Checks if the array is offsetable. + bool CheckArray(CodePtr PC, const Pointer &Ptr); + /// Checks if a pointer is live and accesible. + bool CheckLive(CodePtr PC, const Pointer &Ptr, AccessKinds AK); + /// Checks if a pointer is null. + bool CheckNull(CodePtr PC, const Pointer &Ptr, CheckSubobjectKind CSK); + /// Checks if a pointer is in range. + bool CheckRange(CodePtr PC, const Pointer &Ptr, AccessKinds AK); + /// Checks if a field from which a pointer is going to be derived is valid. + bool CheckRange(CodePtr PC, const Pointer &Ptr, CheckSubobjectKind CSK); + /// Checks if a value can be loaded from a block. + bool CheckLoad(CodePtr PC, const Pointer &Ptr, AccessKinds AK = AK_Read); + /// Checks if a value can be stored in a block. + bool CheckStore(CodePtr PC, const Pointer &Ptr, AccessKinds AK = AK_Assign); + /// Checks if a method can be called. + bool CheckCallable(CodePtr PC, const FunctionDecl *FD); + +private: + /// Checks if an extern is being accessed. + bool CheckExtern(CodePtr PC, const Pointer &Ptr); + /// Checks if storage was initialized. + bool CheckInitialized(CodePtr PC, const Pointer &Ptr, AccessKinds AK); + /// Checks if a field is active if in a union. + bool CheckActive(CodePtr PC, const Pointer &Ptr, AccessKinds AK); + /// Checks if a termpoary is accessed. + bool CheckTemporary(CodePtr PC, const Pointer &Ptr, AccessKinds AK); + /// Checks if a global is illegally accessed. + bool CheckGlobal(CodePtr PC, const Pointer &Ptr); + /// Checks if a mutable field is accessed. + bool CheckMutable(CodePtr PC, const Pointer &Ptr); + /// Checks if a pointer points to const storage. + bool CheckConst(CodePtr PC, const Pointer &Ptr); + private: /// AST Walker state. State &Parent; diff --git a/clang/lib/AST/Interp/InterpState.cpp b/clang/lib/AST/Interp/InterpState.cpp --- a/clang/lib/AST/Interp/InterpState.cpp +++ b/clang/lib/AST/Interp/InterpState.cpp @@ -12,9 +12,9 @@ #include "InterpFrame.h" #include "InterpStack.h" #include "Opcode.h" -#include "PrimType.h" #include "Program.h" #include "State.h" +#include "clang/AST/ExprCXX.h" using namespace clang; using namespace clang::interp; @@ -72,3 +72,249 @@ Desc->DtorFn(B, B->data(), Desc); } } + +SourceInfo InterpState::getSource(CodePtr PC) const { + for (InterpFrame *F = Current; F; F = F->getInterpCaller()) { + Function *Fn = F->getFunction(); + if (!Fn) + break; + if (!Fn->isDefaulted()) + return getSource(Fn, PC); + PC = F->getRetPC(); + } + return getSource(nullptr, {}); +} + +bool InterpState::CheckArray(CodePtr PC, const Pointer &Ptr) { + if (!Ptr.isUnknownSizeArray()) + return true; + const SourceInfo &I = getSource(PC); + FFDiag(I, diag::note_constexpr_unsized_array_indexed); + return false; +} + +bool InterpState::CheckLive(CodePtr PC, const Pointer &Ptr, AccessKinds AK) { + if (Ptr.isZero()) { + const SourceInfo &I = getSource(PC); + if (Ptr.isField()) + FFDiag(I, diag::note_constexpr_null_subobject) << CSK_Field; + else + FFDiag(I, diag::note_constexpr_access_null) << AK; + + return false; + } + + if (!Ptr.isLive()) { + bool IsTemp = Ptr.isTemporary(); + const SourceInfo &I = getSource(PC); + FFDiag(I, diag::note_constexpr_lifetime_ended, 1) << AK << !IsTemp; + + if (IsTemp) + Note(Ptr.getDeclLoc(), diag::note_constexpr_temporary_here); + else + Note(Ptr.getDeclLoc(), diag::note_declared_at); + + return false; + } + + return true; +} + +bool InterpState::CheckNull(CodePtr PC, const Pointer &Ptr, + CheckSubobjectKind CSK) { + if (!Ptr.isZero()) + return true; + const SourceInfo &I = getSource(PC); + FFDiag(I, diag::note_constexpr_null_subobject) << CSK; + return false; +} + +bool InterpState::CheckRange(CodePtr PC, const Pointer &Ptr, AccessKinds AK) { + if (!Ptr.isOnePastEnd()) + return true; + const SourceInfo &I = getSource(PC); + FFDiag(I, diag::note_constexpr_access_past_end) << AK; + return false; +} + +bool InterpState::CheckRange(CodePtr PC, const Pointer &Ptr, + CheckSubobjectKind CSK) { + if (!Ptr.isElementPastEnd()) + return true; + const SourceInfo &I = getSource(PC); + FFDiag(I, diag::note_constexpr_past_end_subobject) << CSK; + return false; +} + +bool InterpState::CheckLoad(CodePtr PC, const Pointer &Ptr, AccessKinds AK) { + if (!CheckLive(PC, Ptr, AK)) + return false; + if (!CheckExtern(PC, Ptr)) + return false; + if (!CheckRange(PC, Ptr, AK)) + return false; + if (!CheckInitialized(PC, Ptr, AK)) + return false; + if (!CheckActive(PC, Ptr, AK)) + return false; + if (!CheckTemporary(PC, Ptr, AK)) + return false; + if (!CheckMutable(PC, Ptr)) + return false; + return true; +} + +bool InterpState::CheckStore(CodePtr PC, const Pointer &Ptr, AccessKinds AK) { + if (!CheckLive(PC, Ptr, AK)) + return false; + if (!CheckExtern(PC, Ptr)) + return false; + if (!CheckRange(PC, Ptr, AK)) + return false; + if (!CheckGlobal(PC, Ptr)) + return false; + if (!CheckConst(PC, Ptr)) + return false; + return true; +} + +bool InterpState::CheckCallable(CodePtr PC, const FunctionDecl *FD) { + if (auto *MD = dyn_cast(FD)) { + if (MD->isVirtual()) { + if (!getLangOpts().CPlusPlus2a) { + CCEDiag(getSource(PC), diag::note_constexpr_virtual_call); + return false; + } + } + } + + if (!FD->isConstexpr()) { + if (getLangOpts().CPlusPlus11) { + // If this function is not constexpr because it is an inherited + // non-constexpr constructor, diagnose that directly. + auto *CD = dyn_cast(FD); + if (CD && CD->isInheritingConstructor()) { + auto *Inherited = CD->getInheritedConstructor().getConstructor(); + if (!Inherited->isConstexpr()) + FD = CD = Inherited; + } + + // FIXME: If FD is an implicitly-declared special member function + // or an inheriting constructor, we should be much more explicit about why + // it's not constexpr. + if (CD && CD->isInheritingConstructor()) + FFDiag(getSource(PC), diag::note_constexpr_invalid_inhctor, 1) + << CD->getInheritedConstructor().getConstructor()->getParent(); + else + FFDiag(getSource(PC), diag::note_constexpr_invalid_function, 1) + << FD->isConstexpr() << (bool)CD << FD; + Note(FD->getLocation(), diag::note_declared_at); + } else { + FFDiag(getSource(PC), diag::note_invalid_subexpr_in_const_expr); + } + return false; + } + + return true; +} + +bool InterpState::CheckExtern(CodePtr PC, const Pointer &Ptr) { + if (!Ptr.isExtern()) + return true; + + if (!checkingPotentialConstantExpression()) { + auto *VD = Ptr.getDeclDesc()->asValueDecl(); + const SourceInfo &I = getSource(PC); + FFDiag(I, diag::note_constexpr_ltor_non_constexpr, 1) << VD; + Note(VD->getLocation(), diag::note_declared_at); + } + return false; +} + +bool InterpState::CheckInitialized(CodePtr PC, const Pointer &Ptr, + AccessKinds AK) { + if (Ptr.isInitialized()) + return true; + if (!checkingPotentialConstantExpression()) { + const SourceInfo &I = getSource(PC); + FFDiag(I, diag::note_constexpr_access_uninit) << AK << false; + } + return false; +} + +bool InterpState::CheckActive(CodePtr PC, const Pointer &Ptr, + AccessKinds AK) { + if (Ptr.isActive()) + return true; + + // Get the inactive field descriptor. + const FieldDecl *InactiveField = Ptr.getField(); + + // Walk up the pointer chain to find the union which is not active. + Pointer U = Ptr.getBase(); + while (!U.isActive()) { + U = U.getBase(); + } + + // Find the active field of the union. + Record *R = U.getRecord(); + assert(R && R->isUnion() && "Not a union"); + const FieldDecl *ActiveField = nullptr; + for (unsigned I = 0, N = R->getNumFields(); I < N; ++I) { + const Pointer &Field = U.atField(R->getField(I)->Offset); + if (Field.isActive()) { + ActiveField = Field.getField(); + break; + } + } + + const SourceInfo &I = getSource(PC); + FFDiag(I, diag::note_constexpr_access_inactive_union_member) + << AK << InactiveField << !ActiveField << ActiveField; + return false; +} + +bool InterpState::CheckTemporary(CodePtr PC, const Pointer &Ptr, + AccessKinds AK) { + // TODO: implement check + return true; +} + +bool InterpState::CheckGlobal(CodePtr PC, const Pointer &Ptr) { + // TODO: implement check + return true; +} + +bool InterpState::CheckMutable(CodePtr PC, const Pointer &Ptr) { + assert(Ptr.isLive() && "Pointer is not live"); + if (!Ptr.isMutable()) { + return true; + } + + Pointer FieldPtr = Ptr; + const FieldDecl *MutableField = nullptr; + do { + MutableField = FieldPtr.getField(); + if (MutableField && MutableField->isMutable()) + break; + FieldPtr = FieldPtr.getContainingObject(); + } while (!FieldPtr.isRoot()); + assert(MutableField && "missing mutable field"); + + const SourceInfo &I = getSource(PC); + FFDiag(I, diag::note_constexpr_access_mutable, 1) << Ptr.getField(); + Note(MutableField->getLocation(), diag::note_declared_at); + return false; +} + +bool InterpState::CheckConst(CodePtr PC, const Pointer &Ptr) { + assert(Ptr.isLive() && "Pointer is not live"); + if (!Ptr.isConst()) { + return true; + } + + const QualType Ty = Ptr.getType(); + const SourceInfo &I = getSource(PC); + FFDiag(I, diag::note_constexpr_modify_const_type) << Ty; + return false; +} diff --git a/clang/lib/AST/Interp/Opcodes.td b/clang/lib/AST/Interp/Opcodes.td --- a/clang/lib/AST/Interp/Opcodes.td +++ b/clang/lib/AST/Interp/Opcodes.td @@ -41,11 +41,12 @@ def ArgSint64 : ArgType { let Name = "int64_t"; } def ArgUint64 : ArgType { let Name = "uint64_t"; } def ArgBool : ArgType { let Name = "bool"; } +def ArgGlobal : ArgType { let Name = "GlobalLocation"; } def ArgFunction : ArgType { let Name = "Function *"; } def ArgRecord : ArgType { let Name = "Record *"; } -def ArgSema : ArgType { let Name = "const fltSemantics *"; } +def ArgSema : ArgType { let Name = "const llvm::fltSemantics *"; } def ArgExpr : ArgType { let Name = "const Expr *"; } def ArgFloatingLiteral : ArgType { let Name = "const FloatingLiteral *"; } @@ -73,12 +74,20 @@ let Types = [Ptr]; } +def AluFPTypeClass : TypeClass { + let Types = AluTypeClass.Types; +} + +def AluFPRealFPTypeClass : TypeClass { + let Types = AluTypeClass.Types; +} + def AllTypeClass : TypeClass { - let Types = !listconcat(AluTypeClass.Types, PtrTypeClass.Types); + let Types = !listconcat(AluFPRealFPTypeClass.Types, PtrTypeClass.Types); } -def ComparableTypeClass : TypeClass { - let Types = !listconcat(AluTypeClass.Types, [Ptr]); +def ArithTypeClass : TypeClass { + let Types = !listconcat(AluFPRealFPTypeClass.Types, [Ptr]); } class SingletonTypeClass : TypeClass { @@ -101,7 +110,12 @@ } class AluOpcode : Opcode { - let Types = [AluTypeClass]; + let Types = [AluFPTypeClass]; + let HasGroup = 1; +} + +class AluRealOpcode : Opcode { + let Types = [AluFPRealFPTypeClass]; let HasGroup = 1; } @@ -122,6 +136,32 @@ // [Bool] -> [], jumps if false. def Jf : JumpOpcode; +//===----------------------------------------------------------------------===// +// Call opcodes +// +// The default calling convention, which does not support varargs, pops the +// arguments on exit. Vararg functions require arguments to be caller-popped. +// +//===----------------------------------------------------------------------===// + +// [Args...] -> [] +def Call : Opcode { + let Args = [ArgFunction]; + let ChangesPC = 1; + let HasCustomEval = 1; +} + +//===----------------------------------------------------------------------===// +// Invalid relocation trap +//===----------------------------------------------------------------------===// + +// [] -> EXIT +def NoCall : Opcode { + let Args = [ArgFunctionDecl]; + let ChangesPC = 1; + let HasCustomEval = 1; +} + //===----------------------------------------------------------------------===// // Returns //===----------------------------------------------------------------------===// @@ -140,12 +180,6 @@ let ChangesPC = 1; let HasCustomEval = 1; } -// [Value] -> [] -def RetValue : Opcode { - let CanReturn = 1; - let ChangesPC = 1; - let HasCustomEval = 1; -} // [] -> EXIT def NoRet : Opcode {} @@ -205,58 +239,6 @@ // Offset of parameter. let Args = [ArgUint32]; } -// [] -> [Pointer] -def GetPtrGlobal : Opcode { - // Index of global. - let Args = [ArgUint32]; -} -// [Pointer] -> [Pointer] -def GetPtrField : Opcode { - // Offset of field. - let Args = [ArgUint32]; -} -// [Pointer] -> [Pointer] -def GetPtrActiveField : Opcode { - // Offset of field. - let Args = [ArgUint32]; -} -// [] -> [Pointer] -def GetPtrActiveThisField : Opcode { - // Offset of field. - let Args = [ArgUint32]; -} -// [] -> [Pointer] -def GetPtrThisField : Opcode { - // Offset of field. - let Args = [ArgUint32]; -} -// [Pointer] -> [Pointer] -def GetPtrBase : Opcode { - // Offset of field, which is a base. - let Args = [ArgUint32]; -} -// [Pointer] -> [Pointer] -def GetPtrVirtBase : Opcode { - // RecordDecl of base class. - let Args = [ArgRecordDecl]; -} -// [] -> [Pointer] -def GetPtrThisBase : Opcode { - // Offset of field, which is a base. - let Args = [ArgUint32]; -} -// [] -> [Pointer] -def GetPtrThisVirtBase : Opcode { - // RecordDecl of base class. - let Args = [ArgRecordDecl]; -} -// [] -> [Pointer] -def This : Opcode; - -// [Pointer] -> [Pointer] -def NarrowPtr : Opcode; -// [Pointer] -> [Pointer] -def ExpandPtr : Opcode; //===----------------------------------------------------------------------===// // Direct field accessors @@ -268,8 +250,14 @@ let HasGroup = 1; } +class GlobalOpcode : Opcode { + let Types = [AllTypeClass]; + let Args = [ArgGlobal]; + let HasGroup = 1; +} + class BitFieldOpcode : Opcode { - let Types = [AluTypeClass]; + let Types = [AluFPTypeClass]; let Args = [ArgRecordField]; let HasGroup = 1; } @@ -279,43 +267,11 @@ // [] -> [Pointer] def SetLocal : AccessOpcode { let HasCustomEval = 1; } -// [] -> [Value] -def GetGlobal : AccessOpcode; -// [Value] -> [] -def InitGlobal : AccessOpcode; -// [Value] -> [] -def SetGlobal : AccessOpcode; - // [] -> [Value] def GetParam : AccessOpcode; // [Value] -> [] def SetParam : AccessOpcode; -// [Pointer] -> [Pointer, Value] -def GetField : AccessOpcode; -// [Pointer] -> [Value] -def GetFieldPop : AccessOpcode; -// [] -> [Value] -def GetThisField : AccessOpcode; - -// [Pointer, Value] -> [Pointer] -def SetField : AccessOpcode; -// [Value] -> [] -def SetThisField : AccessOpcode; - -// [Value] -> [] -def InitThisField : AccessOpcode; -// [Value] -> [] -def InitThisFieldActive : AccessOpcode; -// [Value] -> [] -def InitThisBitField : BitFieldOpcode; -// [Pointer, Value] -> [] -def InitField : AccessOpcode; -// [Pointer, Value] -> [] -def InitBitField : BitFieldOpcode; -// [Pointer, Value] -> [] -def InitFieldActive : AccessOpcode; - //===----------------------------------------------------------------------===// // Pointer access //===----------------------------------------------------------------------===// @@ -335,53 +291,31 @@ let HasGroup = 1; } -class StoreBitFieldOpcode : Opcode { - let Types = [AluTypeClass]; - let HasGroup = 1; -} - // [Pointer, Value] -> [Pointer] def Store : StoreOpcode {} // [Pointer, Value] -> [] def StorePop : StoreOpcode {} -// [Pointer, Value] -> [Pointer] -def StoreBitField : StoreBitFieldOpcode {} -// [Pointer, Value] -> [] -def StoreBitFieldPop : StoreBitFieldOpcode {} - -// [Pointer, Value] -> [] -def InitPop : StoreOpcode {} -// [Pointer, Value] -> [Pointer] -def InitElem : Opcode { - let Types = [AllTypeClass]; - let Args = [ArgUint32]; - let HasGroup = 1; -} -// [Pointer, Value] -> [] -def InitElemPop : Opcode { - let Types = [AllTypeClass]; - let Args = [ArgUint32]; - let HasGroup = 1; -} - //===----------------------------------------------------------------------===// -// Pointer arithmetic. +// Binary operators. //===----------------------------------------------------------------------===// -// [Pointer, Integral] -> [Pointer] -def AddOffset : AluOpcode; -// [Pointer, Integral] -> [Pointer] -def SubOffset : AluOpcode; +// [Real, Real] -> [Real] +def Sub : AluRealOpcode; +def Add : AluRealOpcode; +def Mul : AluRealOpcode; //===----------------------------------------------------------------------===// -// Binary operators. +// Unary operators. //===----------------------------------------------------------------------===// -// [Real, Real] -> [Real] -def Sub : AluOpcode; -def Add : AluOpcode; -def Mul : AluOpcode; +// [Real] -> [Real] +def Minus : AluRealOpcode; +// [Integral] -> [Integral] +def Test : Opcode { + let Types = [AllTypeClass]; + let HasGroup = 1; +} //===----------------------------------------------------------------------===// // Comparison opcodes. @@ -396,7 +330,7 @@ def NE : EqualityOpcode; class ComparisonOpcode : Opcode { - let Types = [ComparableTypeClass]; + let Types = [ArithTypeClass]; let HasGroup = 1; } @@ -420,3 +354,19 @@ let Types = [AllTypeClass]; let HasGroup = 1; } + +//===----------------------------------------------------------------------===// +// Cast operators +//===----------------------------------------------------------------------===// + +// [Value] -> [Value] +def Cast : Opcode { + let Types = [AluFPTypeClass, AluTypeClass]; + let HasGroup = 1; +} + +//===----------------------------------------------------------------------===// +// Trap instruction. +//===----------------------------------------------------------------------===// + +def Trap : Opcode; diff --git a/clang/lib/AST/Interp/Opcodes/Comparison.h b/clang/lib/AST/Interp/Opcodes/Comparison.h new file mode 100644 --- /dev/null +++ b/clang/lib/AST/Interp/Opcodes/Comparison.h @@ -0,0 +1,121 @@ +//===--- Comparison.h - Comparison instructions 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 +// +//===----------------------------------------------------------------------===// +// +// Implementation of the comparison opcodes: EQ, NE, GT, GE, LT, LE, InRange +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_INTERP_OPCODES_COMPARISON_H +#define LLVM_CLANG_AST_INTERP_OPCODES_COMPARISON_H + +namespace detail { +using Flag = ComparisonCategoryResult; +using CmpResult = llvm::Optional; + +template +CmpResult CmpPointer(InterpState &S, CodePtr OpPC, const T &LHS, const T &RHS); + +template ::T> +typename std::enable_if::type +CmpImpl(InterpState &S, CodePtr OpPC, const T &LHS, const T &RHS) { + return LHS.compare(RHS); +} + +template ::T> +typename std::enable_if::type +CmpImpl(InterpState &S, CodePtr OpPC, const T &LHS, const T &RHS) { + // Handle null pointer comparisons. + if (LHS.isZero() && RHS.isZero()) { + return Flag::Equal; + } + if (LHS.isZero() || RHS.isZero()) { + return Flag::Nonequal; + } + // Handle general ordering. + return CmpPointer(S, OpPC, LHS, RHS); +} + +template <> +inline CmpResult CmpPointer(InterpState &S, CodePtr OpPC, + const Pointer &LHS, const Pointer &RHS) { + if (!Pointer::hasSameBase(LHS, RHS)) { + const SourceInfo &Loc = S.getSource(OpPC); + S.FFDiag(Loc, diag::note_invalid_subexpr_in_const_expr); + return {}; + } else { + unsigned VL = LHS.getByteOffset(); + unsigned VR = RHS.getByteOffset(); + return Compare(VL, VR); + } +} + +template +llvm::Optional CmpWrapper(InterpState &S, CodePtr OpPC) { + using T = typename PrimConv::T; + const T &RHS = S.Stk.pop(); + const T &LHS = S.Stk.pop(); + return CmpImpl(S, OpPC, LHS, RHS); +} + +} // namespace detail + +template bool EQ(InterpState &S, CodePtr OpPC) { + if (auto R = detail::CmpWrapper(S, OpPC)) { + bool Result = *R == detail::Flag::Equal; + S.Stk.push(Boolean::from(Result)); + return true; + } + return false; +} + +template bool NE(InterpState &S, CodePtr OpPC) { + if (auto R = detail::CmpWrapper(S, OpPC)) { + bool Result = *R != detail::Flag::Equal; + S.Stk.push(Boolean::from(Result)); + return true; + } + return false; +} + +template bool LT(InterpState &S, CodePtr OpPC) { + if (auto R = detail::CmpWrapper(S, OpPC)) { + bool Result = *R == detail::Flag::Less; + S.Stk.push(Boolean::from(Result)); + return true; + } + return false; +} + +template bool LE(InterpState &S, CodePtr OpPC) { + if (auto R = detail::CmpWrapper(S, OpPC)) { + bool Result = *R == detail::Flag::Less || *R == detail::Flag::Equal; + S.Stk.push(Boolean::from(Result)); + return true; + } + return false; +} + +template bool GT(InterpState &S, CodePtr OpPC) { + if (auto R = detail::CmpWrapper(S, OpPC)) { + bool Result = *R == detail::Flag::Greater; + S.Stk.push(Boolean::from(Result)); + return true; + } + return false; +} + +template bool GE(InterpState &S, CodePtr OpPC) { + if (auto R = detail::CmpWrapper(S, OpPC)) { + bool Result = *R == detail::Flag::Greater || *R == detail::Flag::Equal; + S.Stk.push(Boolean::from(Result)); + return true; + } + return false; +} + +#endif diff --git a/clang/lib/AST/Interp/Pointer.h b/clang/lib/AST/Interp/Pointer.h --- a/clang/lib/AST/Interp/Pointer.h +++ b/clang/lib/AST/Interp/Pointer.h @@ -18,7 +18,6 @@ #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/Expr.h" -#include "clang/AST/ComparisonCategories.h" #include "llvm/ADT/PointerUnion.h" #include "llvm/Support/raw_ostream.h" @@ -120,9 +119,9 @@ return Pointer(Pointee, Base, Base + getSize() + Adjust); } - // Do not step out of array elements. + // Do not step out of array elements, but step from first to root. if (Base != Offset) - return *this; + return getOffset() == 0 ? Pointer(Pointee, Base, Base) : *this; // If at base, point to an array of base types. if (Base == 0) @@ -136,6 +135,9 @@ return Pointer(Pointee, Next, Offset); } + /// Compares two pointers for equality. + bool operator==(const Pointer &RHS) const; + /// Checks if the pointer is null. bool isZero() const { return Pointee == nullptr; } /// Checks if the pointer is live. @@ -166,6 +168,10 @@ assert(Offset != Base && "not an array element"); return Pointer(Pointee, Base, Base); } + /// Returns the containing object. + Pointer getContainingObject() const { + return isArrayElement() ? getArray() : getBase(); + } /// Accessors for information about the innermost field. Descriptor *getFieldDesc() const { @@ -216,7 +222,10 @@ bool isRoot() const { return (Base == 0 || Base == RootPtrMark) && Offset == 0; } - + /// Returns the element type, if a primitive. + llvm::Optional elementPrimType() const { + return getFieldDesc()->ElemTy; + } /// Returns the record descriptor of a class. Record *getRecord() const { return getFieldDesc()->ElemRecord; } /// Returns the field information. @@ -252,9 +261,7 @@ llvm::Optional getDeclID() const { return Pointee->getDeclID(); } /// Returns the byte offset from the start. - unsigned getByteOffset() const { - return Offset; - } + unsigned getByteOffset() const { return Offset; } /// Returns the number of elements. unsigned getNumElems() const { return getSize() / elemSize(); } diff --git a/clang/lib/AST/Interp/Pointer.cpp b/clang/lib/AST/Interp/Pointer.cpp --- a/clang/lib/AST/Interp/Pointer.cpp +++ b/clang/lib/AST/Interp/Pointer.cpp @@ -9,6 +9,7 @@ #include "Pointer.h" #include "Block.h" #include "Function.h" +#include "Pointer.h" #include "PrimType.h" using namespace clang; @@ -191,3 +192,7 @@ bool Pointer::hasSameArray(const Pointer &A, const Pointer &B) { return A.Base == B.Base && A.getFieldDesc()->IsArray; } + +bool Pointer::operator==(const Pointer &RHS) const { + return Base == RHS.Base && Offset == RHS.Offset; +} diff --git a/clang/lib/AST/Interp/PrimType.h b/clang/lib/AST/Interp/PrimType.h --- a/clang/lib/AST/Interp/PrimType.h +++ b/clang/lib/AST/Interp/PrimType.h @@ -1,4 +1,4 @@ -//===--- PrimType.h - Types for the constexpr VM --------------------*- C++ -*-===// +//===--- PrimType.h - Types for the constexpr 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. @@ -10,8 +10,8 @@ // //===----------------------------------------------------------------------===// -#ifndef LLVM_CLANG_AST_INTERP_TYPE_H -#define LLVM_CLANG_AST_INTERP_TYPE_H +#ifndef LLVM_CLANG_AST_INTERP_PRIMTYPE_H +#define LLVM_CLANG_AST_INTERP_PRIMTYPE_H #include #include @@ -58,7 +58,7 @@ return ((Size + alignof(void *) - 1) / alignof(void *)) * alignof(void *); } -inline bool isPrimitiveIntegral(PrimType Type) { +constexpr bool isPrimitiveIntegral(PrimType Type) { switch (Type) { case PT_Bool: case PT_Sint8: @@ -75,6 +75,33 @@ } } +constexpr bool isFixedIntegral(PrimType Type) { + // TODO: add fixed integral types here. + switch (Type) { + default: + return false; + } +} + +constexpr bool isIntegral(PrimType Type) { + return isPrimitiveIntegral(Type) || isFixedIntegral(Type); +} + +inline bool isFixedReal(PrimType Type) { + // TODO: add fixed real types here. + return false; +} + +constexpr bool isPointer(PrimType Type) { + // TODO: add other pointer types here. + switch (Type) { + case PT_Ptr: + return true; + default: + return false; + } +} + } // namespace interp } // namespace clang @@ -82,23 +109,25 @@ /// The macro implicitly exposes a type T in the scope of the inner block. #define TYPE_SWITCH_CASE(Name, B) \ case Name: { using T = PrimConv::T; do {B;} while(0); break; } +#define NUMERIC_CASES(B) \ + TYPE_SWITCH_CASE(PT_Sint8, B) \ + TYPE_SWITCH_CASE(PT_Uint8, B) \ + TYPE_SWITCH_CASE(PT_Sint16, B) \ + TYPE_SWITCH_CASE(PT_Uint16, B) \ + TYPE_SWITCH_CASE(PT_Sint32, B) \ + TYPE_SWITCH_CASE(PT_Uint32, B) \ + TYPE_SWITCH_CASE(PT_Sint64, B) \ + TYPE_SWITCH_CASE(PT_Uint64, B) \ + TYPE_SWITCH_CASE(PT_Bool, B) #define TYPE_SWITCH(Expr, B) \ switch (Expr) { \ - TYPE_SWITCH_CASE(PT_Sint8, B) \ - TYPE_SWITCH_CASE(PT_Uint8, B) \ - TYPE_SWITCH_CASE(PT_Sint16, B) \ - TYPE_SWITCH_CASE(PT_Uint16, B) \ - TYPE_SWITCH_CASE(PT_Sint32, B) \ - TYPE_SWITCH_CASE(PT_Uint32, B) \ - TYPE_SWITCH_CASE(PT_Sint64, B) \ - TYPE_SWITCH_CASE(PT_Uint64, B) \ - TYPE_SWITCH_CASE(PT_Bool, B) \ + NUMERIC_CASES(B) \ TYPE_SWITCH_CASE(PT_Ptr, B) \ } #define COMPOSITE_TYPE_SWITCH(Expr, B, D) \ switch (Expr) { \ TYPE_SWITCH_CASE(PT_Ptr, B) \ - default: do { D; } while(0); break; \ + default: do { D; } while (0); break; \ } #define INT_TYPE_SWITCH(Expr, B) \ switch (Expr) { \ @@ -112,4 +141,5 @@ TYPE_SWITCH_CASE(PT_Uint64, B) \ default: llvm_unreachable("not an integer"); \ } + #endif diff --git a/clang/lib/AST/Interp/Program.h b/clang/lib/AST/Interp/Program.h --- a/clang/lib/AST/Interp/Program.h +++ b/clang/lib/AST/Interp/Program.h @@ -13,8 +13,6 @@ #ifndef LLVM_CLANG_AST_INTERP_PROGRAM_H #define LLVM_CLANG_AST_INTERP_PROGRAM_H -#include -#include #include "Function.h" #include "Pointer.h" #include "PrimType.h" @@ -24,6 +22,8 @@ #include "llvm/ADT/PointerUnion.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Allocator.h" +#include +#include namespace clang { class RecordDecl; @@ -38,38 +38,30 @@ class State; class Record; class Scope; +class Program; -/// The program contains and links the bytecode for all functions. -class Program { +/// Type used to reference a global. +class GlobalLocation { public: - Program(Context &Ctx) : Ctx(Ctx) {} - - /// Emits a string literal among global data. - unsigned createGlobalString(const StringLiteral *S); - - /// Returns a pointer to a global. - Pointer getPtrGlobal(unsigned Idx); + GlobalLocation() : Index(static_cast(-1)) {} + GlobalLocation(unsigned Index) : Index(Index) {} - /// Returns the value of a global. - Block *getGlobal(unsigned Idx) { - assert(Idx < Globals.size()); - return Globals[Idx]->block(); - } - - /// Finds a global's index. - llvm::Optional getGlobal(const ValueDecl *VD); - - /// Returns or creates a global an creates an index to it. - llvm::Optional getOrCreateGlobal(const ValueDecl *VD); + void print(llvm::raw_ostream &OS) { OS << "{" << Index << "}"; } - /// Returns or creates a dummy value for parameters. - llvm::Optional getOrCreateDummy(const ParmVarDecl *PD); +private: + friend class Program; + unsigned Index; +}; - /// Creates a global and returns its index. - llvm::Optional createGlobal(const ValueDecl *VD); +inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, GlobalLocation G) { + G.print(OS); + return OS; +} - /// Creates a global from a lifetime-extended temporary. - llvm::Optional createGlobal(const Expr *E); +/// The program contains and links the bytecode for all functions. +class Program { +public: + Program(Context &Ctx) : Ctx(Ctx) {} /// Creates a new function from a code range. template @@ -79,8 +71,7 @@ return Func; } /// Creates an anonymous function. - template - Function *createFunction(Ts &&... Args) { + template Function *createFunction(Ts &&... Args) { auto *Func = new Function(*this, std::forward(Args)...); AnonFuncs.emplace_back(Func); return Func; @@ -92,48 +83,19 @@ /// Returns a pointer to a function if it exists and can be compiled. /// If a function couldn't be compiled, an error is returned. /// If a function was not yet defined, a null pointer is returned. - llvm::Expected getOrCreateFunction(const FunctionDecl *F); - - /// Returns a record or creates one if it does not exist. - Record *getOrCreateRecord(const RecordDecl *RD); + llvm::Expected getOrCreateFunction(State &S, + const FunctionDecl *F); /// Creates a descriptor for a primitive type. Descriptor *createDescriptor(const DeclTy &D, PrimType Type, - bool IsConst = false, - bool IsTemporary = false, + bool IsConst = false, bool IsTemporary = false, bool IsMutable = false) { return allocateDescriptor(D, Type, IsConst, IsTemporary, IsMutable); } - /// Creates a descriptor for a composite type. - Descriptor *createDescriptor(const DeclTy &D, const Type *Ty, - bool IsConst = false, bool IsTemporary = false, - bool IsMutable = false); - - /// Context to manage declaration lifetimes. - class DeclScope { - public: - DeclScope(Program &P, const VarDecl *VD) : P(P) { P.startDeclaration(VD); } - ~DeclScope() { P.endDeclaration(); } - - private: - Program &P; - }; - - /// Returns the current declaration ID. - llvm::Optional getCurrentDecl() const { - if (CurrentDeclaration == NoDeclaration) - return llvm::Optional{}; - return LastDeclaration; - } - private: friend class DeclScope; - - llvm::Optional createGlobal(const DeclTy &D, QualType Ty, - bool IsStatic, bool IsExtern); - - /// Reference to the VM context. + /// Reference to the VM context. Context &Ctx; /// Mapping from decls to cached bytecode functions. llvm::DenseMap> Funcs; @@ -146,68 +108,14 @@ /// Custom allocator for global storage. using PoolAllocTy = llvm::BumpPtrAllocatorImpl; - /// Descriptor + storage for a global object. - /// - /// Global objects never go out of scope, thus they do not track pointers. - class Global { - public: - /// Create a global descriptor for string literals. - template - Global(Tys... Args) : B(std::forward(Args)...) {} - - /// Allocates the global in the pool, reserving storate for data. - void *operator new(size_t Meta, PoolAllocTy &Alloc, size_t Data) { - return Alloc.Allocate(Meta + Data, alignof(void *)); - } - - /// Return a pointer to the data. - char *data() { return B.data(); } - /// Return a pointer to the block. - Block *block() { return &B; } - - private: - /// Required metadata - does not actually track pointers. - Block B; - }; - /// Allocator for globals. PoolAllocTy Allocator; - /// Global objects. - std::vector Globals; - /// Cached global indices. - llvm::DenseMap GlobalIndices; - - /// Mapping from decls to record metadata. - llvm::DenseMap Records; - - /// Dummy parameter to generate pointers from. - llvm::DenseMap DummyParams; - /// Creates a new descriptor. - template - Descriptor *allocateDescriptor(Ts &&... Args) { + template Descriptor *allocateDescriptor(Ts &&... Args) { return new (Allocator) Descriptor(std::forward(Args)...); } - /// No declaration ID. - static constexpr unsigned NoDeclaration = (unsigned)-1; - /// Last declaration ID. - unsigned LastDeclaration = 0; - /// Current declaration ID. - unsigned CurrentDeclaration = NoDeclaration; - - /// Starts evaluating a declaration. - void startDeclaration(const VarDecl *Decl) { - LastDeclaration += 1; - CurrentDeclaration = LastDeclaration; - } - - /// Ends a global declaration. - void endDeclaration() { - CurrentDeclaration = NoDeclaration; - } - public: /// Dumps the disassembled bytecode to \c llvm::errs(). void dump() const; diff --git a/clang/lib/AST/Interp/Program.cpp b/clang/lib/AST/Interp/Program.cpp --- a/clang/lib/AST/Interp/Program.cpp +++ b/clang/lib/AST/Interp/Program.cpp @@ -18,347 +18,23 @@ using namespace clang; using namespace clang::interp; -unsigned Program::createGlobalString(const StringLiteral *S) { - const size_t CharWidth = S->getCharByteWidth(); - const size_t BitWidth = CharWidth * Ctx.getCharBit(); - - PrimType CharType; - switch (CharWidth) { - case 1: - CharType = PT_Sint8; - break; - case 2: - CharType = PT_Uint16; - break; - case 4: - CharType = PT_Uint32; - break; - default: - llvm_unreachable("unsupported character width"); - } - - // Create a descriptor for the string. - Descriptor *Desc = allocateDescriptor(S, CharType, S->getLength() + 1, - /*isConst=*/true, - /*isTemporary=*/false, - /*isMutable=*/false); - - // Allocate storage for the string. - // The byte length does not include the null terminator. - unsigned I = Globals.size(); - unsigned Sz = Desc->getAllocSize(); - auto *G = new (Allocator, Sz) Global(Desc, /*isStatic=*/true, - /*isExtern=*/false); - Globals.push_back(G); - - // Construct the string in storage. - const Pointer Ptr(G->block()); - for (unsigned I = 0, N = S->getLength(); I <= N; ++I) { - Pointer Field = Ptr.atIndex(I).narrow(); - const uint32_t CodePoint = I == N ? 0 : S->getCodeUnit(I); - switch (CharType) { - case PT_Sint8: { - using T = PrimConv::T; - Field.deref() = T::from(CodePoint, BitWidth); - break; - } - case PT_Uint16: { - using T = PrimConv::T; - Field.deref() = T::from(CodePoint, BitWidth); - break; - } - case PT_Uint32: { - using T = PrimConv::T; - Field.deref() = T::from(CodePoint, BitWidth); - break; - } - default: - llvm_unreachable("unsupported character type"); - } - } - return I; -} - -Pointer Program::getPtrGlobal(unsigned Idx) { - assert(Idx < Globals.size()); - return Pointer(Globals[Idx]->block()); -} - -llvm::Optional Program::getGlobal(const ValueDecl *VD) { - auto It = GlobalIndices.find(VD); - if (It != GlobalIndices.end()) - return It->second; - - // Find any previous declarations which were aleady evaluated. - llvm::Optional Index; - for (const Decl *P = VD; P; P = P->getPreviousDecl()) { - auto It = GlobalIndices.find(P); - if (It != GlobalIndices.end()) { - Index = It->second; - break; - } - } - - // Map the decl to the existing index. - if (Index) { - GlobalIndices[VD] = *Index; - return {}; - } - - return Index; -} - -llvm::Optional Program::getOrCreateGlobal(const ValueDecl *VD) { - if (auto Idx = getGlobal(VD)) - return Idx; - - if (auto Idx = createGlobal(VD)) { - GlobalIndices[VD] = *Idx; - return Idx; - } - return {}; -} - -llvm::Optional Program::getOrCreateDummy(const ParmVarDecl *PD) { - auto &ASTCtx = Ctx.getASTContext(); - - // Create a pointer to an incomplete array of the specified elements. - QualType ElemTy = PD->getType()->castAs()->getPointeeType(); - QualType Ty = ASTCtx.getIncompleteArrayType(ElemTy, ArrayType::Normal, 0); - - // Dedup blocks since they are immutable and pointers cannot be compared. - auto It = DummyParams.find(PD); - if (It != DummyParams.end()) - return It->second; - - if (auto Idx = createGlobal(PD, Ty, /*isStatic=*/true, /*isExtern=*/true)) { - DummyParams[PD] = *Idx; - return Idx; - } - return {}; -} - -llvm::Optional Program::createGlobal(const ValueDecl *VD) { - bool IsStatic, IsExtern; - if (auto *Var = dyn_cast(VD)) { - IsStatic = !Var->hasLocalStorage(); - IsExtern = !Var->getAnyInitializer(); - } else { - IsStatic = false; - IsExtern = true; - } - if (auto Idx = createGlobal(VD, VD->getType(), IsStatic, IsExtern)) { - for (const Decl *P = VD; P; P = P->getPreviousDecl()) - GlobalIndices[P] = *Idx; - return *Idx; - } - return {}; -} - -llvm::Optional Program::createGlobal(const Expr *E) { - return createGlobal(E, E->getType(), /*isStatic=*/true, /*isExtern=*/false); -} - -llvm::Optional Program::createGlobal(const DeclTy &D, QualType Ty, - bool IsStatic, bool IsExtern) { - // Create a descriptor for the global. - Descriptor *Desc; - const bool IsConst = Ty.isConstQualified(); - const bool IsTemporary = D.dyn_cast(); - if (auto T = Ctx.classify(Ty)) { - Desc = createDescriptor(D, *T, IsConst, IsTemporary); - } else { - Desc = createDescriptor(D, Ty.getTypePtr(), IsConst, IsTemporary); - } - if (!Desc) - return {}; - - // Allocate a block for storage. - unsigned I = Globals.size(); - - auto *G = new (Allocator, Desc->getAllocSize()) - Global(getCurrentDecl(), Desc, IsStatic, IsExtern); - G->block()->invokeCtor(); - - Globals.push_back(G); - - return I; -} - Function *Program::getFunction(const FunctionDecl *F) { F = F->getDefinition(); auto It = Funcs.find(F); return It == Funcs.end() ? nullptr : It->second.get(); } -llvm::Expected Program::getOrCreateFunction(const FunctionDecl *F) { +llvm::Expected Program::getOrCreateFunction(State &S, + const FunctionDecl *F) { if (Function *Func = getFunction(F)) { return Func; } // Try to compile the function if it wasn't compiled yet. if (const FunctionDecl *FD = F->getDefinition()) - return ByteCodeStmtGen(Ctx, *this).compileFunc(FD); + return ByteCodeStmtGen(Ctx, *this, S).compileFunc(FD); // A relocation which traps if not resolved. return nullptr; } -Record *Program::getOrCreateRecord(const RecordDecl *RD) { - // Use the actual definition as a key. - RD = RD->getDefinition(); - if (!RD) - return nullptr; - - // Deduplicate records. - auto It = Records.find(RD); - if (It != Records.end()) { - return It->second; - } - - // Number of bytes required by fields and base classes. - unsigned Size = 0; - // Number of bytes required by virtual base. - unsigned VirtSize = 0; - - // Helper to get a base descriptor. - auto GetBaseDesc = [this](const RecordDecl *BD, Record *BR) -> Descriptor * { - if (!BR) - return nullptr; - return allocateDescriptor(BD, BR, /*isConst=*/false, - /*isTemporary=*/false, - /*isMutable=*/false); - }; - - // Reserve space for base classes. - Record::BaseList Bases; - Record::VirtualBaseList VirtBases; - if (auto *CD = dyn_cast(RD)) { - for (const CXXBaseSpecifier &Spec : CD->bases()) { - if (Spec.isVirtual()) - continue; - - const RecordDecl *BD = Spec.getType()->castAs()->getDecl(); - Record *BR = getOrCreateRecord(BD); - if (Descriptor *Desc = GetBaseDesc(BD, BR)) { - Size += align(sizeof(InlineDescriptor)); - Bases.push_back({BD, Size, Desc, BR}); - Size += align(BR->getSize()); - continue; - } - return nullptr; - } - - for (const CXXBaseSpecifier &Spec : CD->vbases()) { - const RecordDecl *BD = Spec.getType()->castAs()->getDecl(); - Record *BR = getOrCreateRecord(BD); - - if (Descriptor *Desc = GetBaseDesc(BD, BR)) { - VirtSize += align(sizeof(InlineDescriptor)); - VirtBases.push_back({BD, VirtSize, Desc, BR}); - VirtSize += align(BR->getSize()); - continue; - } - return nullptr; - } - } - - // Reserve space for fields. - Record::FieldList Fields; - for (const FieldDecl *FD : RD->fields()) { - // Reserve space for the field's descriptor and the offset. - Size += align(sizeof(InlineDescriptor)); - - // Classify the field and add its metadata. - QualType FT = FD->getType(); - const bool IsConst = FT.isConstQualified(); - const bool IsMutable = FD->isMutable(); - Descriptor *Desc; - if (llvm::Optional T = Ctx.classify(FT)) { - Desc = createDescriptor(FD, *T, IsConst, /*isTemporary=*/false, - IsMutable); - } else { - Desc = createDescriptor(FD, FT.getTypePtr(), IsConst, - /*isTemporary=*/false, IsMutable); - } - if (!Desc) - return nullptr; - Fields.push_back({FD, Size, Desc}); - Size += align(Desc->getAllocSize()); - } - - Record *R = new (Allocator) Record(RD, std::move(Bases), std::move(Fields), - std::move(VirtBases), VirtSize, Size); - Records.insert({RD, R}); - return R; -} - -Descriptor *Program::createDescriptor(const DeclTy &D, const Type *Ty, - bool IsConst, bool IsTemporary, - bool IsMutable) { - // Classes and structures. - if (auto *RT = Ty->getAs()) { - if (auto *Record = getOrCreateRecord(RT->getDecl())) - return allocateDescriptor(D, Record, IsConst, IsTemporary, IsMutable); - } - - // Arrays. - if (auto ArrayType = Ty->getAsArrayTypeUnsafe()) { - QualType ElemTy = ArrayType->getElementType(); - // Array of well-known bounds. - if (auto CAT = dyn_cast(ArrayType)) { - size_t NumElems = CAT->getSize().getZExtValue(); - if (llvm::Optional T = Ctx.classify(ElemTy)) { - // Arrays of primitives. - unsigned ElemSize = primSize(*T); - if (std::numeric_limits::max() / ElemSize <= NumElems) { - return {}; - } - return allocateDescriptor(D, *T, NumElems, IsConst, IsTemporary, - IsMutable); - } else { - // Arrays of composites. In this case, the array is a list of pointers, - // followed by the actual elements. - Descriptor *Desc = - createDescriptor(D, ElemTy.getTypePtr(), IsConst, IsTemporary); - if (!Desc) - return nullptr; - InterpSize ElemSize = Desc->getAllocSize() + sizeof(InlineDescriptor); - if (std::numeric_limits::max() / ElemSize <= NumElems) - return {}; - return allocateDescriptor(D, Desc, NumElems, IsConst, IsTemporary, - IsMutable); - } - } - - // Array of unknown bounds - cannot be accessed and pointer arithmetic - // is forbidden on pointers to such objects. - if (isa(ArrayType)) { - if (llvm::Optional T = Ctx.classify(ElemTy)) { - return allocateDescriptor(D, *T, IsTemporary, - Descriptor::UnknownSize{}); - } else { - Descriptor *Desc = - createDescriptor(D, ElemTy.getTypePtr(), IsConst, IsTemporary); - if (!Desc) - return nullptr; - return allocateDescriptor(D, Desc, IsTemporary, - Descriptor::UnknownSize{}); - } - } - } - - // Atomic types. - if (auto *AT = Ty->getAs()) { - const Type *InnerTy = AT->getValueType().getTypePtr(); - return createDescriptor(D, InnerTy, IsConst, IsTemporary, IsMutable); - } - - // Complex types - represented as arrays of elements. - if (auto *CT = Ty->getAs()) { - PrimType ElemTy = *Ctx.classify(CT->getElementType()); - return allocateDescriptor(D, ElemTy, 2, IsConst, IsTemporary, IsMutable); - } - - return nullptr; -} diff --git a/clang/lib/AST/Interp/Source.h b/clang/lib/AST/Interp/Source.h --- a/clang/lib/AST/Interp/Source.h +++ b/clang/lib/AST/Interp/Source.h @@ -20,6 +20,7 @@ namespace clang { namespace interp { class Function; +class GlobalLocation; /// Pointer into the code segment. class CodePtr { @@ -45,31 +46,16 @@ /// Reads data and advances the pointer. template T read() { - T Value = ReadHelper(Ptr); + T Result; + memcpy(&Result, Ptr, sizeof(T)); Ptr += sizeof(T); - return Value; + return Result; } private: /// Constructor used by Function to generate pointers. CodePtr(const char *Ptr) : Ptr(Ptr) {} - /// Helper to decode a value or a pointer. - template - static typename std::enable_if::value, T>::type - ReadHelper(const char *Ptr) { - using namespace llvm::support; - return endian::read(Ptr); - } - - template - static typename std::enable_if::value, T>::type - ReadHelper(const char *Ptr) { - using namespace llvm::support; - auto Punned = endian::read(Ptr); - return reinterpret_cast(Punned); - } - private: friend class Function; @@ -105,11 +91,6 @@ /// Returns source information for a given PC in a function. virtual SourceInfo getSource(Function *F, CodePtr PC) const = 0; - - /// Returns the expression if an opcode belongs to one, null otherwise. - const Expr *getExpr(Function *F, CodePtr PC) const; - /// Returns the location from which an opcode originates. - SourceLocation getLocation(Function *F, CodePtr PC) const; }; } // namespace interp diff --git a/clang/lib/AST/Interp/Source.cpp b/clang/lib/AST/Interp/Source.cpp --- a/clang/lib/AST/Interp/Source.cpp +++ b/clang/lib/AST/Interp/Source.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "Source.h" +#include "Program.h" #include "clang/AST/Expr.h" using namespace clang; @@ -27,13 +28,3 @@ return dyn_cast(S); return nullptr; } - -const Expr *SourceMapper::getExpr(Function *F, CodePtr PC) const { - if (const Expr *E = getSource(F, PC).asExpr()) - return E; - llvm::report_fatal_error("missing source expression"); -} - -SourceLocation SourceMapper::getLocation(Function *F, CodePtr PC) const { - return getSource(F, PC).getLoc(); -} diff --git a/clang/test/AST/Interp/cond.cpp b/clang/test/AST/Interp/cond.cpp --- a/clang/test/AST/Interp/cond.cpp +++ b/clang/test/AST/Interp/cond.cpp @@ -9,3 +9,43 @@ return a - b; } } + +static_assert(cond_then_else(10, 20) == 10); +static_assert(cond_then_else(30, 10) == 20); + +constexpr int cond_empty_then(int a) { + if (a < 0) + ; + return 1; +} + +static_assert(cond_empty_then(0) == 1); + +constexpr int cond_else(int a) { + if (a < 0) + return 10; + return 1; +} + +static_assert(cond_else(-1) == 10); +static_assert(cond_else(1) == 1); + +constexpr int cond_decl(int a) { + if (int b = a + 1) + return b - 1; + else + return b + 1; +} + +static_assert(cond_decl(-1) == 1); +static_assert(cond_decl(5) == 5); + +constexpr int cond_decl_init(int a) { + if (int b = a + 1; b != 0) + return b - 1; + else + return b + 1; +} + +static_assert(cond_decl_init(-1) == 1); +static_assert(cond_decl_init(5) == 5);