diff --git a/clang/lib/AST/Interp/ByteCodeEmitter.cpp b/clang/lib/AST/Interp/ByteCodeEmitter.cpp --- a/clang/lib/AST/Interp/ByteCodeEmitter.cpp +++ b/clang/lib/AST/Interp/ByteCodeEmitter.cpp @@ -31,8 +31,21 @@ // If the return is not a primitive, a pointer to the storage where the value // is initialized in is passed as the first argument. + // See 'RVO' elsewhere in the code. QualType Ty = F->getReturnType(); + bool HasRVO = false; if (!Ty->isVoidType() && !Ctx.classify(Ty)) { + HasRVO = true; + ParamTypes.push_back(PT_Ptr); + ParamOffset += align(primSize(PT_Ptr)); + } + + // If the function decl is a member decl, the next parameter is + // the 'this' pointer. This parameter is pop()ed from the + // InterStack when calling the function. + bool HasThisPointer = false; + if (const auto *MD = dyn_cast(F); MD && MD->isInstance()) { + HasThisPointer = true; ParamTypes.push_back(PT_Ptr); ParamOffset += align(primSize(PT_Ptr)); } @@ -55,8 +68,9 @@ } // Create a handle over the emitted code. - Function *Func = P.createFunction(F, ParamOffset, std::move(ParamTypes), - std::move(ParamDescriptors)); + Function *Func = + P.createFunction(F, ParamOffset, std::move(ParamTypes), + std::move(ParamDescriptors), HasThisPointer, HasRVO); // Compile the function body. if (!F->isConstexpr() || !visitFunc(F)) { // Return a dummy function if compilation failed. 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 @@ -71,9 +71,11 @@ bool VisitBinaryOperator(const BinaryOperator *E); bool VisitCXXDefaultArgExpr(const CXXDefaultArgExpr *E); bool VisitCallExpr(const CallExpr *E); + bool VisitCXXMemberCallExpr(const CXXMemberCallExpr *E); bool VisitCXXDefaultInitExpr(const CXXDefaultInitExpr *E); bool VisitCXXBoolLiteralExpr(const CXXBoolLiteralExpr *E); bool VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *E); + bool VisitCXXThisExpr(const CXXThisExpr *E); bool VisitUnaryOperator(const UnaryOperator *E); bool VisitDeclRefExpr(const DeclRefExpr *E); bool VisitImplicitValueInitExpr(const ImplicitValueInitExpr *E); @@ -99,6 +101,10 @@ Record *getRecord(QualType Ty); Record *getRecord(const RecordDecl *RD); + // Returns a function for the given FunctionDecl. + // If the function does not exist yet, it is compiled. + const Function *getFunction(const FunctionDecl *FD); + /// Returns the size int bits of an integer. unsigned getIntWidth(QualType Ty) { auto &ASTContext = Ctx.getASTContext(); 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 @@ -637,34 +637,16 @@ assert(Initializer->getType()->isRecordType()); if (const auto CtorExpr = dyn_cast(Initializer)) { - const CXXConstructorDecl *Ctor = CtorExpr->getConstructor(); - const RecordDecl *RD = Ctor->getParent(); - const Record *R = getRecord(RD); - - for (const auto *Init : Ctor->inits()) { - const FieldDecl *Member = Init->getMember(); - const Expr *InitExpr = Init->getInit(); - - if (Optional T = classify(InitExpr->getType())) { - const Record::Field *F = R->getField(Member); - - if (!this->emitDupPtr(Initializer)) - return false; + const Function *Func = getFunction(CtorExpr->getConstructor()); - if (!this->visit(InitExpr)) - return false; - - if (!this->emitInitField(*T, F->Offset, Initializer)) - return false; - } else { - assert(false && "Handle initializer for non-primitive values"); - } - } + if (!Func) + return false; - // FIXME: Actually visit() the constructor Body - const Stmt *Body = Ctor->getBody(); - (void)Body; - return true; + // The This pointer is already on the stack because this is an initializer, + // but we need to dup() so the call() below has its own copy. + if (!this->emitDupPtr(Initializer)) + return false; + return this->emitCallVoid(Func, Initializer); } else if (const auto *InitList = dyn_cast(Initializer)) { const Record *R = getRecord(InitList->getType()); @@ -688,7 +670,10 @@ return true; } else if (const CallExpr *CE = dyn_cast(Initializer)) { const Decl *Callee = CE->getCalleeDecl(); - const Function *Func = P.getFunction(dyn_cast(Callee)); + const Function *Func = getFunction(dyn_cast(Callee)); + + if (!Func) + return false; if (Func->hasRVO()) { // RVO functions expect a pointer to initialize on the stack. @@ -699,7 +684,6 @@ return this->visit(CE); } } - return false; } @@ -765,6 +749,23 @@ return P.getOrCreateRecord(RD); } +template +const Function *ByteCodeExprGen::getFunction(const FunctionDecl *FD) { + assert(FD); + const Function *Func = P.getFunction(FD); + + if (!Func) { + if (auto R = ByteCodeStmtGen(Ctx, P).compileFunc(FD)) + Func = *R; + else { + llvm::consumeError(R.takeError()); + return nullptr; + } + } + + return Func; +} + template bool ByteCodeExprGen::visitExpr(const Expr *Exp) { ExprScope RootScope(this); @@ -820,16 +821,9 @@ const Decl *Callee = E->getCalleeDecl(); if (const auto *FuncDecl = dyn_cast_or_null(Callee)) { - const Function *Func = P.getFunction(FuncDecl); - - // Templated functions might not have been compiled yet, so do it now. - if (!Func) { - if (auto R = - ByteCodeStmtGen(Ctx, P).compileFunc(FuncDecl)) - Func = *R; - } - assert(Func); - + const Function *Func = getFunction(FuncDecl); + if (!Func) + return false; // If the function is being compiled right now, this is a recursive call. // In that case, the function can't be valid yet, even though it will be // later. @@ -840,23 +834,24 @@ QualType ReturnType = E->getCallReturnType(Ctx.getASTContext()); Optional T = classify(ReturnType); + // Put arguments on the stack. + for (const auto *Arg : E->arguments()) { + if (!this->visit(Arg)) + return false; + } - if (T || ReturnType->isVoidType()) { - // Put arguments on the stack. - for (const auto *Arg : E->arguments()) { - if (!this->visit(Arg)) - return false; - } + // Primitive return value, just call it. + if (T) + return this->emitCall(*T, Func, E); - if (T) - return this->emitCall(*T, Func, E); + // Void Return value, easy. + if (ReturnType->isVoidType()) return this->emitCallVoid(Func, E); - } else { - if (Func->hasRVO()) - return this->emitCallVoid(Func, E); - assert(false && "Can't classify function return type"); - } + // Non-primitive return value with Return Value Optimization, + // we already have a pointer on the stack to write the result into. + if (Func->hasRVO()) + return this->emitCallVoid(Func, E); } else { assert(false && "We don't support non-FunctionDecl callees right now."); } @@ -864,6 +859,16 @@ return false; } +template +bool ByteCodeExprGen::VisitCXXMemberCallExpr( + const CXXMemberCallExpr *E) { + // Get a This pointer on the stack. + if (!this->visit(E->getImplicitObjectArgument())) + return false; + + return VisitCallExpr(E); +} + template bool ByteCodeExprGen::VisitCXXDefaultInitExpr( const CXXDefaultInitExpr *E) { @@ -894,6 +899,11 @@ return this->emitNullPtr(E); } +template +bool ByteCodeExprGen::VisitCXXThisExpr(const CXXThisExpr *E) { + return this->emitThis(E); +} + template bool ByteCodeExprGen::VisitUnaryOperator(const UnaryOperator *E) { const Expr *SubExpr = E->getSubExpr(); 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 @@ -94,7 +94,33 @@ // Classify the return type. ReturnType = this->classify(F->getReturnType()); - if (auto *Body = F->getBody()) + // Constructor. Set up field initializers. + if (const auto Ctor = dyn_cast(F)) { + const RecordDecl *RD = Ctor->getParent(); + const Record *R = this->getRecord(RD); + + for (const auto *Init : Ctor->inits()) { + const FieldDecl *Member = Init->getMember(); + const Expr *InitExpr = Init->getInit(); + + if (Optional T = this->classify(InitExpr->getType())) { + const Record::Field *F = R->getField(Member); + + if (!this->emitDupPtr(InitExpr)) + return false; + + if (!this->visit(InitExpr)) + return false; + + if (!this->emitInitField(*T, F->Offset, InitExpr)) + return false; + } else { + assert(false && "Handle initializer for non-primitive values"); + } + } + } + + if (const auto *Body = F->getBody()) if (!visitStmt(Body)) return false; diff --git a/clang/lib/AST/Interp/Disasm.cpp b/clang/lib/AST/Interp/Disasm.cpp --- a/clang/lib/AST/Interp/Disasm.cpp +++ b/clang/lib/AST/Interp/Disasm.cpp @@ -48,6 +48,7 @@ OS << "frame size: " << getFrameSize() << "\n"; OS << "arg size: " << getArgSize() << "\n"; OS << "rvo: " << hasRVO() << "\n"; + OS << "this arg: " << hasThisPointer() << "\n"; auto PrintName = [&OS](const char *Name) { OS << Name; 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 @@ -105,18 +105,18 @@ template bool EvalEmitter::emitCall(const Function *Func, const SourceInfo &Info) { - S.Current = - new InterpFrame(S, const_cast(Func), S.Current, {}, {}); + S.Current = new InterpFrame(S, const_cast(Func), {}); // Result of call will be on the stack and needs to be handled by the caller. return Interpret(S, Result); } bool EvalEmitter::emitCallVoid(const Function *Func, const SourceInfo &Info) { APValue VoidResult; - S.Current = - new InterpFrame(S, const_cast(Func), S.Current, {}, {}); + InterpFrame *before = S.Current; + S.Current = new InterpFrame(S, const_cast(Func), {}); bool Success = Interpret(S, VoidResult); assert(VoidResult.isAbsent()); + assert(S.Current == before); return Success; } 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 @@ -56,6 +56,21 @@ /// /// Contains links to the bytecode of the function, as well as metadata /// describing all arguments and stack-local variables. +/// +/// # Calling Convention +/// +/// When calling a function, all argument values must be on the stack. +/// +/// If the function has a This pointer (i.e. hasThisPointer() returns true, +/// the argument values need to be preceeded by a Pointer for the This object. +/// +/// If the function uses Return Value Optimization, the arguments (and +/// potentially the This pointer) need to be proceeded by a Pointer pointing +/// to the location to construct the returned value. +/// +/// After the function has been called, it will remove all arguments, +/// including RVO and This pointer, from the stack. +/// class Function final { public: using ParamDescriptor = std::pair; @@ -84,7 +99,7 @@ ParamDescriptor getParamDescriptor(unsigned Offset) const; /// Checks if the first argument is a RVO pointer. - bool hasRVO() const { return ParamTypes.size() != Params.size(); } + bool hasRVO() const { return HasRVO; } /// Range over the scope blocks. llvm::iterator_range::iterator> scopes() { @@ -115,11 +130,16 @@ /// Checks if the function is fully done compiling. bool isFullyCompiled() const { return IsFullyCompiled; } + bool hasThisPointer() const { return HasThisPointer; } + + unsigned getNumParams() const { return ParamTypes.size(); } + private: /// Construct a function representing an actual function. Function(Program &P, const FunctionDecl *F, unsigned ArgSize, llvm::SmallVector &&ParamTypes, - llvm::DenseMap &&Params); + llvm::DenseMap &&Params, + bool HasThisPointer, bool HasRVO); /// Sets the code of a function. void setCode(unsigned NewFrameSize, std::vector &&NewCode, SourceMap &&NewSrcMap, @@ -162,6 +182,13 @@ /// Flag to indicate if the function is done being /// compiled to bytecode. bool IsFullyCompiled = false; + /// Flag indicating if this function takes the this pointer + /// as the first implicit argument + bool HasThisPointer = false; + /// Whether this function has Return Value Optimization, i.e. + /// the return value is constructed in the caller's stack frame. + /// This is done for functions that return non-primive values. + bool HasRVO = false; public: /// Dumps the disassembled bytecode to \c llvm::errs(). 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 @@ -17,9 +17,11 @@ Function::Function(Program &P, const FunctionDecl *F, unsigned ArgSize, llvm::SmallVector &&ParamTypes, - llvm::DenseMap &&Params) + llvm::DenseMap &&Params, + bool HasThisPointer, bool HasRVO) : P(P), Loc(F->getBeginLoc()), F(F), ArgSize(ArgSize), - ParamTypes(std::move(ParamTypes)), Params(std::move(Params)) {} + ParamTypes(std::move(ParamTypes)), Params(std::move(Params)), + HasThisPointer(HasThisPointer), HasRVO(HasRVO) {} Function::ParamDescriptor Function::getParamDescriptor(unsigned Offset) const { auto It = Params.find(Offset); diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp --- a/clang/lib/AST/Interp/Interp.cpp +++ b/clang/lib/AST/Interp/Interp.cpp @@ -55,8 +55,7 @@ template ::T> static bool Call(InterpState &S, CodePtr &PC, const Function *Func) { - S.Current = - new InterpFrame(S, const_cast(Func), S.Current, PC, {}); + S.Current = new InterpFrame(S, const_cast(Func), PC); APValue CallResult; // Note that we cannot assert(CallResult.hasValue()) here since // Ret() above only sets the APValue if the curent frame doesn't @@ -66,8 +65,7 @@ static bool CallVoid(InterpState &S, CodePtr &PC, const Function *Func) { APValue VoidResult; - S.Current = - new InterpFrame(S, const_cast(Func), S.Current, PC, {}); + S.Current = new InterpFrame(S, const_cast(Func), PC); bool Success = Interpret(S, VoidResult); assert(VoidResult.isAbsent()); 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 @@ -35,6 +35,11 @@ InterpFrame(InterpState &S, Function *Func, InterpFrame *Caller, CodePtr RetPC, Pointer &&This); + /// Creates a new frame with the values that make sense. + /// I.e., the caller is the current frame of S, + /// and the This() pointer is the current Pointer on the top of S's stack, + InterpFrame(InterpState &S, Function *Func, CodePtr RetPC); + /// Destroys the frame, killing all live pointers to stack slots. ~InterpFrame(); 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 @@ -36,6 +36,36 @@ } } +InterpFrame::InterpFrame(InterpState &S, Function *Func, CodePtr RetPC) + : Caller(S.Current), S(S), Func(Func), RetPC(RetPC), + ArgSize(Func ? Func->getArgSize() : 0), + Args(static_cast(S.Stk.top())), FrameOffset(S.Stk.size()) { + assert(Func); + + // As per our calling convention, the this pointer is + // part of the ArgSize. + // If the function has RVO, the RVO pointer is first. + // If the fuction has a This pointer, that one is next. + // Then follow the actual arguments (but those are handled + // in getParamPointer()). + if (Func->hasThisPointer()) { + if (Func->hasRVO()) + This = stackRef(sizeof(Pointer)); + else + This = stackRef(0); + } + + if (unsigned FrameSize = Func->getFrameSize()) { + Locals = std::make_unique(FrameSize); + for (auto &Scope : Func->scopes()) { + for (auto &Local : Scope.locals()) { + Block *B = new (localBlock(Local.Offset)) Block(Local.Desc); + B->invokeCtor(); + } + } + } +} + InterpFrame::~InterpFrame() { if (Func && Func->isConstructor() && This.isBaseClass()) This.initialize(); diff --git a/clang/test/AST/Interp/records.cpp b/clang/test/AST/Interp/records.cpp --- a/clang/test/AST/Interp/records.cpp +++ b/clang/test/AST/Interp/records.cpp @@ -1,7 +1,6 @@ // RUN: %clang_cc1 -fexperimental-new-constant-interpreter -verify %s // RUN: %clang_cc1 -verify=ref %s -// ref-no-diagnostics // expected-no-diagnostics struct BoolPair { @@ -38,7 +37,6 @@ static_assert(ints2.b == -30, ""); static_assert(!ints2.c, ""); -#if 0 constexpr Ints getInts() { return {64, 128, true}; } @@ -46,7 +44,6 @@ static_assert(ints3.a == 64, ""); static_assert(ints3.b == 128, ""); static_assert(ints3.c, ""); -#endif constexpr Ints ints4 = { .a = 40 * 50, @@ -103,3 +100,38 @@ return &c; } static_assert(getPointer()->a == 100, ""); + +constexpr C RVOAndParams(const C *c) { + return C(); +} +constexpr C RVOAndParamsResult = RVOAndParams(&c); + +constexpr int locals() { + C c; + c.a = 10; + + // Assignment, not an initializer. + // c = C(); FIXME + c.a = 10; + + + // Assignment, not an initializer. + //c = RVOAndParams(&c); FIXME + + return c.a; +} +static_assert(locals() == 10, ""); + +namespace thisPointer { + struct S { + constexpr int get12() { return 12; } + }; + + constexpr int foo() { // ref-error {{never produces a constant expression}} + S *s = nullptr; + return s->get12(); // ref-note 2{{member call on dereferenced null pointer}} + } + // FIXME: The new interpreter doesn't reject this currently. + static_assert(foo() == 12, ""); // ref-error {{not an integral constant expression}} \ + // ref-note {{in call to 'foo()'}} +};