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 @@ -64,7 +64,7 @@ ByteCodeExprGen(Context &Ctx, Program &P, Tys &&... Args) : Emitter(Ctx, P, Args...), Ctx(Ctx), P(P) {} - // Expression visitors - result returned on stack. + // Expression visitors - result returned on interp stack. bool VisitCastExpr(const CastExpr *E); bool VisitIntegerLiteral(const IntegerLiteral *E); bool VisitParenExpr(const ParenExpr *E); @@ -77,6 +77,9 @@ bool VisitDeclRefExpr(const DeclRefExpr *E); bool VisitImplicitValueInitExpr(const ImplicitValueInitExpr *E); bool VisitSubstNonTypeTemplateParmExpr(const SubstNonTypeTemplateParmExpr *E); + bool VisitArraySubscriptExpr(const ArraySubscriptExpr *E); + bool VisitInitListExpr(const InitListExpr *E); + bool VisitConstantExpr(const ConstantExpr *E); protected: bool visitExpr(const Expr *E) override; @@ -130,29 +133,45 @@ 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); + /// Compiles an initializer. + bool visitInitializer(const Expr *E); + /// Compiles an array initializer. + bool visitArrayInitializer(const Expr *Initializer); /// 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); - }); + if (!this->emitGetPtrLocal(I, Init)) + return false; + + if (!visitInitializer(Init)) + return false; + + return this->emitPopPtr(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); - }); + if (!this->emitGetPtrGlobal(I, Init)) + return false; + + if (!visitInitializer(Init)) + return false; + + return this->emitPopPtr(Init); } /// Visits a delegated initializer. bool visitThisInitializer(const Expr *I) { - return visitInitializer(I, [this, I] { return this->emitThis(I); }); + if (!this->emitThis(I)) + return false; + + if (!visitInitializer(I)) + return false; + + return this->emitPopPtr(I); } /// Creates a local primitive value. 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 @@ -231,12 +231,55 @@ return false; } +template +bool ByteCodeExprGen::VisitArraySubscriptExpr( + const ArraySubscriptExpr *E) { + const Expr *Base = E->getBase(); + const Expr *Index = E->getIdx(); + + // Take pointer of LHS, add offset from RHS, narrow result. + // What's left on the stack after this is a pointer. + if (Optional IndexT = classify(Index->getType())) { + if (!this->Visit(Base)) + return false; + + if (!this->Visit(Index)) + return false; + + if (!this->emitAddOffset(*IndexT, E)) + return false; + + if (!this->emitNarrowPtr(E)) + return false; + + return true; + } + + return false; +} + +template +bool ByteCodeExprGen::VisitInitListExpr(const InitListExpr *E) { + for (const Expr *Init : E->inits()) { + if (!this->visit(Init)) + return false; + } + return true; +} + template bool ByteCodeExprGen::VisitSubstNonTypeTemplateParmExpr( const SubstNonTypeTemplateParmExpr *E) { return this->visit(E->getReplacement()); } +template +bool ByteCodeExprGen::VisitConstantExpr(const ConstantExpr *E) { + // TODO: Check if the ConstantExpr already has a value set and if so, + // use that instead of evaluating it again. + return this->visit(E->getSubExpr()); +} + template bool ByteCodeExprGen::discard(const Expr *E) { OptionScope Scope(this, /*NewDiscardResult=*/true); @@ -492,11 +535,62 @@ return Local.Offset; } +// NB: When calling this function, we have a pointer to the +// array-to-initialize on the stack. template -bool ByteCodeExprGen::visitInitializer( - const Expr *Init, InitFnRef InitFn) { - OptionScope Scope(this, InitFn); - return this->Visit(Init); +bool ByteCodeExprGen::visitArrayInitializer(const Expr *Initializer) { + assert(Initializer->getType()->isArrayType()); + + // TODO: Fillers? + if (const auto *InitList = dyn_cast(Initializer)) { + unsigned ElementIndex = 0; + for (const Expr *Init : InitList->inits()) { + QualType InitType = Init->getType(); + + if (InitType->isArrayType()) { + // Advance the pointer currently on the stack to the given + // dimension and narrow(). + if (!this->emitDupPtr(Init)) + return false; + if (!this->emitConstUint32(ElementIndex, Init)) + return false; + if (!this->emitAddOffsetUint32(Init)) + return false; + if (!this->emitNarrowPtr(Init)) + return false; + if (!visitArrayInitializer(Init)) + return false; + if (!this->emitPopPtr(Init)) + return false; + } else if (Optional T = classify(InitType)) { + // Visit the primitive element like normal. + if (!this->visit(Init)) + return false; + if (!this->emitInitElem(*T, ElementIndex, Init)) + return false; + } else { + assert(false && "Unhandled type in array initializer initlist"); + } + + ++ElementIndex; + } + + } else { + assert(false && "Unknown expression for array initialization"); + } + + return true; +} + +template +bool ByteCodeExprGen::visitInitializer(const Expr *Initializer) { + QualType InitializerType = Initializer->getType(); + + if (InitializerType->isArrayType()) + return visitArrayInitializer(Initializer); + + // Otherwise, visit the expression like normal. + return this->Visit(Initializer); } template 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 @@ -171,8 +171,7 @@ 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)) + if (!this->visitInitializer(RE)) return false; this->emitCleanup(); return this->emitRetVoid(RS); @@ -232,21 +231,20 @@ template bool ByteCodeStmtGen::visitVarDecl(const VarDecl *VD) { - auto DT = VD->getType(); - if (!VD->hasLocalStorage()) { // No code generation required. return true; } // Integers, pointers, primitives. - if (Optional T = this->classify(DT)) { + if (Optional T = this->classify(VD->getType())) { const Expr *Init = VD->getInit(); if (!Init) return false; - auto Off = this->allocateLocalPrimitive(VD, *T, DT.isConstQualified()); + unsigned Offset = + this->allocateLocalPrimitive(VD, *T, VD->getType().isConstQualified()); // Compile the initializer in its own scope. { ExprScope Scope(this); @@ -254,15 +252,14 @@ return false; } // 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); - } + return this->emitSetLocal(*T, Offset, VD); } + + // Composite types - allocate storage and initialize it. + if (Optional Offset = this->allocateLocal(VD)) + return this->visitLocalInitializer(VD->getInit(), *Offset); + + return this->bail(VD); } namespace clang { 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 @@ -721,6 +721,9 @@ return true; } +/// 1) Pops the value from the stack +/// 2) Peeks a pointer and gets its index \Idx +/// 3) Sets the value on the pointer, leaving the pointer on the stack. template ::T> bool InitElem(InterpState &S, CodePtr OpPC, uint32_t Idx) { const T &Value = S.Stk.pop(); @@ -732,6 +735,7 @@ return true; } +/// The same as InitElem, but pops the pointer as well. template ::T> bool InitElemPop(InterpState &S, CodePtr OpPC, uint32_t Idx) { const T &Value = S.Stk.pop(); 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 @@ -106,7 +106,7 @@ // Build the path into the object. Pointer Ptr = *this; - while (Ptr.isField()) { + while (Ptr.isField() || Ptr.isArrayElement()) { if (Ptr.isArrayElement()) { Path.push_back(APValue::LValuePathEntry::ArrayIndex(Ptr.getIndex())); Ptr = Ptr.getArray(); @@ -154,7 +154,8 @@ void Pointer::initialize() const { assert(Pointee && "Cannot initialize null pointer"); Descriptor *Desc = getFieldDesc(); - if (Desc->isPrimitiveArray()) { + + if (Desc->isArray()) { if (!Pointee->IsStatic) { // Primitive array initializer. InitMap *&Map = getInitMap(); 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 @@ -334,14 +334,15 @@ } else { // Arrays of composites. In this case, the array is a list of pointers, // followed by the actual elements. - Descriptor *Desc = + Descriptor *ElemDesc = createDescriptor(D, ElemTy.getTypePtr(), IsConst, IsTemporary); - if (!Desc) + if (!ElemDesc) return nullptr; - InterpSize ElemSize = Desc->getAllocSize() + sizeof(InlineDescriptor); + InterpSize ElemSize = + ElemDesc->getAllocSize() + sizeof(InlineDescriptor); if (std::numeric_limits::max() / ElemSize <= NumElems) return {}; - return allocateDescriptor(D, Desc, NumElems, IsConst, IsTemporary, + return allocateDescriptor(D, ElemDesc, NumElems, IsConst, IsTemporary, IsMutable); } } diff --git a/clang/test/AST/Interp/arrays.cpp b/clang/test/AST/Interp/arrays.cpp --- a/clang/test/AST/Interp/arrays.cpp +++ b/clang/test/AST/Interp/arrays.cpp @@ -1,13 +1,85 @@ // RUN: %clang_cc1 -fexperimental-new-constant-interpreter -verify %s // RUN: %clang_cc1 -verify=ref %s +constexpr int m = 3; +constexpr const int *foo[][5] = { + {nullptr, &m, nullptr, nullptr, nullptr}, + {nullptr, nullptr, &m, nullptr, nullptr}, + {nullptr, nullptr, nullptr, &m, nullptr}, +}; + +static_assert(foo[0][0] == nullptr, ""); +static_assert(foo[0][1] == &m, ""); +static_assert(foo[0][2] == nullptr, ""); +static_assert(foo[0][3] == nullptr, ""); +static_assert(foo[0][4] == nullptr, ""); +static_assert(foo[1][0] == nullptr, ""); +static_assert(foo[1][1] == nullptr, ""); +static_assert(foo[1][2] == &m, ""); +static_assert(foo[1][3] == nullptr, ""); +static_assert(foo[1][4] == nullptr, ""); +static_assert(foo[2][0] == nullptr, ""); +static_assert(foo[2][1] == nullptr, ""); +static_assert(foo[2][2] == nullptr, ""); +static_assert(foo[2][3] == &m, ""); +static_assert(foo[2][4] == nullptr, ""); + + +/// A init list for a primitive value. +constexpr int f{5}; +static_assert(f == 5, ""); + + +constexpr int getElement(int i) { + int values[] = {1, 4, 9, 16, 25, 36}; + return values[i]; +} +static_assert(getElement(1) == 4, ""); +static_assert(getElement(5) == 36, ""); + + +template +constexpr T getElementOf(T* array, int i) { + return array[i]; +} +static_assert(getElementOf(foo[0], 1) == &m, ""); + -/// expected-no-diagnostics -/// ref-no-diagnostics +constexpr int data[] = {5, 4, 3, 2, 1}; +static_assert(data[0] == 4, ""); // expected-error{{failed}} \ + // expected-note{{5 == 4}} \ + // ref-error{{failed}} \ + // ref-note{{5 == 4}} + + +constexpr int dynamic[] = { + f, 3, 2 + 5, data[3], *getElementOf(foo[2], 3) +}; +static_assert(dynamic[0] == f, ""); +static_assert(dynamic[3] == 2, ""); + + +constexpr int dependent[4] = { + 0, 1, dependent[0], dependent[1] +}; +static_assert(dependent[2] == dependent[0], ""); +static_assert(dependent[3] == dependent[1], ""); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wc99-extensions" #pragma clang diagnostic ignored "-Winitializer-overrides" +constexpr int DI[] = { + [0] = 10, + [1] = 20, + 30, + 40, + [1] = 50 +}; +static_assert(DI[0] == 10, ""); +static_assert(DI[1] == 50, ""); +static_assert(DI[2] == 30, ""); +static_assert(DI[3] == 40, ""); + /// FIXME: The example below tests ImplicitValueInitExprs, but we can't /// currently evaluate other parts of it. #if 0