Index: clang/lib/AST/Interp/Boolean.h =================================================================== --- clang/lib/AST/Interp/Boolean.h +++ clang/lib/AST/Interp/Boolean.h @@ -42,6 +42,7 @@ bool operator!=(Boolean RHS) const { return V != RHS.V; } bool operator>(unsigned RHS) const { return static_cast(V) > RHS; } + bool operator>=(unsigned RHS) const { return static_cast(V) >= RHS; } Boolean operator-() const { return Boolean(V); } Boolean operator~() const { return Boolean(true); } Index: clang/lib/AST/Interp/ByteCodeExprGen.cpp =================================================================== --- clang/lib/AST/Interp/ByteCodeExprGen.cpp +++ clang/lib/AST/Interp/ByteCodeExprGen.cpp @@ -545,7 +545,6 @@ 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()) { Index: clang/lib/AST/Interp/Context.cpp =================================================================== --- clang/lib/AST/Interp/Context.cpp +++ clang/lib/AST/Interp/Context.cpp @@ -27,6 +27,7 @@ Context::~Context() {} bool Context::isPotentialConstantExpr(State &Parent, const FunctionDecl *FD) { + assert(Stk.empty()); Function *Func = P->getFunction(FD); if (!Func) { if (auto R = ByteCodeStmtGen(*this, *P).compileFunc(FD)) { @@ -39,22 +40,45 @@ } } + Stk.clear(); + if (!Func->isConstexpr()) return false; - APValue Dummy; - return Run(Parent, Func, Dummy); + // XXX We *actually* just ONLY want to assert that the stack is empty. + // (Without the clear() above). + // However, currently we emit some unnecessary Destroy() and RetVoid + // ops. Long-term, we should get rid of those. But short-term, they + // cause no harm. + assert(Stk.empty()); + return true; } bool Context::evaluateAsRValue(State &Parent, const Expr *E, APValue &Result) { + assert(Stk.empty()); ByteCodeExprGen C(*this, *P, Parent, Stk, Result); - return Check(Parent, C.interpretExpr(E)); + + if (Check(Parent, C.interpretExpr(E))) { + assert(Stk.empty()); + return true; + } + + Stk.clear(); + return false; } bool Context::evaluateAsInitializer(State &Parent, const VarDecl *VD, APValue &Result) { + assert(Stk.empty()); ByteCodeExprGen C(*this, *P, Parent, Stk, Result); - return Check(Parent, C.interpretDecl(VD)); + + if (Check(Parent, C.interpretDecl(VD))) { + assert(Stk.empty()); + return true; + } + + Stk.clear(); + return false; } const LangOptions &Context::getLangOpts() const { return Ctx.getLangOpts(); } Index: clang/lib/AST/Interp/Descriptor.h =================================================================== --- clang/lib/AST/Interp/Descriptor.h +++ clang/lib/AST/Interp/Descriptor.h @@ -78,6 +78,10 @@ const bool IsTemporary = false; /// Flag indicating if the block is an array. const bool IsArray = false; + /// NB: Might be different than (Size / ElemSize), for arrays + /// that have an array filler. + const InterpSize NumFullElems = 0; + const Expr *ArrayFiller = nullptr; /// Storage management methods. const BlockCtorFn CtorFn = nullptr; @@ -89,7 +93,8 @@ bool IsMutable); /// Allocates a descriptor for an array of primitives. - Descriptor(const DeclTy &D, PrimType Type, size_t NumElems, bool IsConst, + Descriptor(const DeclTy &D, PrimType Type, size_t NumElems, + const Expr *Filler, size_t FullElems, bool IsConst, bool IsTemporary, bool IsMutable); /// Allocates a descriptor for an array of primitives of unknown size. @@ -111,6 +116,7 @@ const Decl *asDecl() const { return Source.dyn_cast(); } const Expr *asExpr() const { return Source.dyn_cast(); } + const Expr *getArrayFiller() const { return ArrayFiller; } const ValueDecl *asValueDecl() const { return dyn_cast_or_null(asDecl()); @@ -139,6 +145,10 @@ unsigned getNumElems() const { return Size == UnknownSizeMark ? 0 : (getSize() / getElemSize()); } + /// Returns the maximum number the block may hold. + unsigned getNumFullElems() const { + return Size == UnknownSizeMark ? 0 : NumFullElems; + } /// Checks if the descriptor is of an array of primitives. bool isPrimitiveArray() const { return IsArray && !ElemDesc; } Index: clang/lib/AST/Interp/Descriptor.cpp =================================================================== --- clang/lib/AST/Interp/Descriptor.cpp +++ clang/lib/AST/Interp/Descriptor.cpp @@ -194,14 +194,19 @@ assert(Source && "Missing source"); } +/// XXX Just make a new constructor that takes 2 sizes as well as an array +/// filler expression. Descriptor::Descriptor(const DeclTy &D, PrimType Type, size_t NumElems, - bool IsConst, bool IsTemporary, bool IsMutable) + const Expr *Filler, size_t FullElems, 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), + NumFullElems(FullElems), ArrayFiller(Filler), CtorFn(getCtorArrayPrim(Type)), DtorFn(getDtorArrayPrim(Type)), MoveFn(getMoveArrayPrim(Type)) { assert(Source && "Missing source"); + assert(NumElems <= FullElems); } Descriptor::Descriptor(const DeclTy &D, PrimType Type, bool IsTemporary, @@ -219,8 +224,8 @@ Size(ElemSize * NumElems), AllocSize(std::max(alignof(void *), Size)), ElemDesc(Elem), IsConst(IsConst), IsMutable(IsMutable), IsTemporary(IsTemporary), - IsArray(true), CtorFn(ctorArrayDesc), DtorFn(dtorArrayDesc), - MoveFn(moveArrayDesc) { + IsArray(true), NumFullElems(NumElems), CtorFn(ctorArrayDesc), + DtorFn(dtorArrayDesc), MoveFn(moveArrayDesc) { assert(Source && "Missing source"); } Index: clang/lib/AST/Interp/EvalEmitter.cpp =================================================================== --- clang/lib/AST/Interp/EvalEmitter.cpp +++ clang/lib/AST/Interp/EvalEmitter.cpp @@ -186,10 +186,13 @@ } return Ok; } - if (auto *AT = Ty->getAsArrayTypeUnsafe()) { - const size_t NumElems = Ptr.getNumElems(); + if (const auto *AT = Ty->getAsArrayTypeUnsafe()) { + size_t NumElems = Ptr.getNumElems(); + const ConstantArrayType *CAT = dyn_cast(AT); + const size_t FullElems = CAT->getSize().getZExtValue(); + QualType ElemTy = AT->getElementType(); - R = APValue(APValue::UninitArray{}, NumElems, NumElems); + R = APValue(APValue::UninitArray{}, NumElems, FullElems); bool Ok = true; for (unsigned I = 0; I < NumElems; ++I) { @@ -201,6 +204,22 @@ Ok &= Composite(ElemTy, EP.narrow(), Slot); } } + + if (NumElems != FullElems) { + // Need to convert the array filler to a APValue + const Expr *AF = Ptr.getArrayFiller(); + assert(AF); + + // TODO: I have no idea if the potentially nested call + // to evaluateAsRValue() here is always going to work. + // + // We should evaluate the array filler *once* during + // normal evaluation, not when returning the value. + APValue FillerValue; + Ctx.evaluateAsRValue(S, AF, FillerValue); + FillerValue.dump(); + R.getArrayFiller() = FillerValue; + } return Ok; } llvm_unreachable("invalid value to return"); Index: clang/lib/AST/Interp/Integral.h =================================================================== --- clang/lib/AST/Interp/Integral.h +++ clang/lib/AST/Interp/Integral.h @@ -90,6 +90,9 @@ bool operator>(unsigned RHS) const { return V >= 0 && static_cast(V) > RHS; } + bool operator>=(unsigned RHS) const { + return V >= 0 && static_cast(V) >= RHS; + } Integral operator-() const { return Integral(-V); } Integral operator~() const { return Integral(~V); } Index: clang/lib/AST/Interp/Interp.h =================================================================== --- clang/lib/AST/Interp/Interp.h +++ clang/lib/AST/Interp/Interp.h @@ -772,7 +772,7 @@ return false; // Compute the largest index into the array. - unsigned MaxIndex = Ptr.getNumElems(); + unsigned MaxIndex = Ptr.getNumFullElems(); // Helper to report an invalid offset, computed as APSInt. auto InvalidOffset = [&]() { @@ -794,7 +794,7 @@ return InvalidOffset(); // If the new offset would be out of bounds, bail out. - if (Offset.isPositive() && Offset > MaxOffset) + if (Offset.isPositive() && Offset >= MaxOffset) return InvalidOffset(); } else { // If the new offset would be negative, bail out. @@ -810,7 +810,34 @@ 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))); + unsigned UResult = static_cast(Result); + + // For arrays, we might need to expand them using the filler element. + if (Ptr.needsExpansionForIndex(UResult)) { + Block *Pointee = Ptr.block(); + const Descriptor *Desc = Ptr.getFieldDesc(); + unsigned OldNumElems = Desc->getNumElems(); + unsigned NewNumElems = std::min(UResult * 2, Desc->getNumFullElems()); + assert(NewNumElems > OldNumElems); + + // TODO: Just create a different Descriptor constructor for this case + Descriptor *D = new Descriptor( + Desc->asDecl(), static_cast(4), // PT_Sint32, + NewNumElems, Desc->getArrayFiller(), Desc->getNumFullElems(), + Desc->IsConst, Desc->IsTemporary, Desc->IsMutable); + char *m = (char *)malloc(sizeof(Block) + D->getAllocSize()); + Block *_B = new (m) Block(D, Pointee->isStatic(), Pointee->isExtern()); + _B->invokeCtor(); + + memcpy(_B->data(), Pointee->data(), Desc->getAllocSize()); + + Pointee->transferPointersTo(_B); + assert(Ptr.block() == _B); + assert(Ptr.block()->hasPointer(&Ptr)); + // TODO: Actually use the array filler for the new elements! + } + + S.Stk.push(Ptr.atIndex(UResult)); return true; } Index: clang/lib/AST/Interp/InterpBlock.h =================================================================== --- clang/lib/AST/Interp/InterpBlock.h +++ clang/lib/AST/Interp/InterpBlock.h @@ -60,6 +60,7 @@ /// Returns a pointer to the stored data. char *data() { return reinterpret_cast(this + 1); } + const char *data() const { return reinterpret_cast(this + 1); } /// Returns a view over the data. template @@ -73,6 +74,17 @@ /*isActive=*/true, Desc); } + /// Checks if \P is in the list of pointers this block manages. + /// This does NOT check if P->Pointee is this block. + /// + /// Only use this function in assertions. + bool hasPointer(const Pointer *P) const; + + /// Transfer all pointers pointing to this block to \t To. + /// After calling this function, this->hasPointers() will + /// return false. + void transferPointersTo(Block *To); + protected: friend class Pointer; friend class DeadBlock; @@ -87,7 +99,6 @@ // Pointer chain management. void addPointer(Pointer *P); void removePointer(Pointer *P); - void movePointer(Pointer *From, Pointer *To); /// Start of the chain of pointers. Pointer *Pointers = nullptr; Index: clang/lib/AST/Interp/InterpBlock.cpp =================================================================== --- clang/lib/AST/Interp/InterpBlock.cpp +++ clang/lib/AST/Interp/InterpBlock.cpp @@ -16,11 +16,11 @@ using namespace clang; using namespace clang::interp; - - void Block::addPointer(Pointer *P) { - if (IsStatic) - return; + assert(Pointers != P); + assert(P); + assert(!hasPointer(P)); + if (Pointers) Pointers->Prev = P; P->Next = Pointers; @@ -29,14 +29,17 @@ } void Block::removePointer(Pointer *P) { - if (IsStatic) - return; + assert(P); + if (Pointers == P) Pointers = P->Next; if (P->Prev) P->Prev->Next = P->Next; if (P->Next) P->Next->Prev = P->Prev; + // Setting P->Pointee to null sounds like a good idea but + // it seems like some things (e.g. the Pointee->cleanup() call + // in ~Pointer()) rely on this not happening. } void Block::cleanup() { @@ -44,20 +47,35 @@ (reinterpret_cast(this + 1) - 1)->free(); } -void Block::movePointer(Pointer *From, Pointer *To) { - if (IsStatic) +void Block::transferPointersTo(Block *To) { + assert(To); + if (!Pointers) return; - To->Prev = From->Prev; - if (To->Prev) - To->Prev->Next = To; - To->Next = From->Next; - if (To->Next) - To->Next->Prev = To; - if (Pointers == From) - Pointers = To; - - From->Prev = nullptr; - From->Next = nullptr; + + Pointer *P = this->Pointers; + while (P) { + Pointer *Next = P->Next; + removePointer(P); + To->addPointer(P); + P->Pointee = To; + + P = Next; + } + assert(!hasPointers()); +} + +bool Block::hasPointer(const Pointer *P) const { + const Pointer *Iter = this->Pointers; + while (Iter) { + if (Iter == P) + return true; + + assert(Iter != P); + assert(Iter != Iter->Next); + Iter = Iter->Next; + } + + return false; } DeadBlock::DeadBlock(DeadBlock *&Root, Block *Blk) Index: clang/lib/AST/Interp/InterpFrame.h =================================================================== --- clang/lib/AST/Interp/InterpFrame.h +++ clang/lib/AST/Interp/InterpFrame.h @@ -142,6 +142,8 @@ char *Args = nullptr; /// Fixed, initial storage for known local variables. std::unique_ptr Locals; + /// Pointers we lazily create for the locals. + llvm::DenseMap LocalPointers; /// Offset on the stack at entry. const size_t FrameOffset; /// Mapping from arg offsets to their argument blocks. Index: clang/lib/AST/Interp/InterpFrame.cpp =================================================================== --- clang/lib/AST/Interp/InterpFrame.cpp +++ clang/lib/AST/Interp/InterpFrame.cpp @@ -155,8 +155,13 @@ Pointer InterpFrame::getLocalPointer(unsigned Offset) { assert(Offset < Func->getFrameSize() && "Invalid local offset."); - return Pointer( - reinterpret_cast(Locals.get() + Offset - sizeof(Block))); + if (auto It = LocalPointers.find(Offset); It != LocalPointers.end()) + return Pointer(It->second); + + auto *B = reinterpret_cast(Locals.get() + Offset - sizeof(Block)); + Pointer &P = LocalPointers[Offset]; + P = Pointer(B); + return P; } Pointer InterpFrame::getParamPointer(unsigned Off) { Index: clang/lib/AST/Interp/InterpStack.h =================================================================== --- clang/lib/AST/Interp/InterpStack.h +++ clang/lib/AST/Interp/InterpStack.h @@ -61,6 +61,9 @@ /// Clears the stack without calling any destructors. void clear(); + /// Returns true if the stack is empty. + bool empty() const { return size() == 0; } + private: /// All stack slots are aligned to the native pointer alignment for storage. /// The size of an object is rounded up to a pointer alignment multiple. Index: clang/lib/AST/Interp/Pointer.h =================================================================== --- clang/lib/AST/Interp/Pointer.h +++ clang/lib/AST/Interp/Pointer.h @@ -55,6 +55,7 @@ Pointer atIndex(unsigned Idx) const { if (Base == RootPtrMark) return Pointer(Pointee, RootPtrMark, getDeclDesc()->getSize()); + unsigned Off = Idx * elemSize(); if (getFieldDesc()->ElemDesc) Off += sizeof(InlineDescriptor); @@ -213,6 +214,8 @@ bool isRoot() const { return (Base == 0 || Base == RootPtrMark) && Offset == 0; } + /// Returns the Block this pointer points to. + Block *block() const { return Pointee; } /// Returns the record descriptor of a class. Record *getRecord() const { return getFieldDesc()->ElemRecord; } @@ -240,6 +243,19 @@ /// Checks if a structure is a base class. bool isBaseClass() const { return isField() && getInlineDesc()->IsBase; } + const Expr *getArrayFiller() const { + return getFieldDesc()->getArrayFiller(); + } + + bool needsExpansionForIndex(unsigned Idx) const { + const Descriptor *Desc = getFieldDesc(); + // The given index should never be greater than NumFullElems, + // this needs to be checked earlier. + assert(Idx < Desc->getNumFullElems()); + return Desc->isArray() && + Idx >= Desc->getNumElems(); + } + /// Checks if an object or a subfield is mutable. bool isConst() const { return Base == 0 ? getDeclDesc()->IsConst : getInlineDesc()->IsConst; @@ -256,6 +272,9 @@ /// Returns the number of elements. unsigned getNumElems() const { return getSize() / elemSize(); } + /// Returns the maximum number of elements. + unsigned getNumFullElems() const { return getFieldDesc()->getNumFullElems(); } + /// Returns the index into an array. int64_t getIndex() const { if (isElementPastEnd()) Index: clang/lib/AST/Interp/Pointer.cpp =================================================================== --- clang/lib/AST/Interp/Pointer.cpp +++ clang/lib/AST/Interp/Pointer.cpp @@ -10,18 +10,28 @@ #include "Function.h" #include "InterpBlock.h" #include "PrimType.h" +#include "Program.h" using namespace clang; using namespace clang::interp; -Pointer::Pointer(Block *Pointee) : Pointer(Pointee, 0, 0) {} +Pointer::Pointer(Block *Pointee) : Pointee(Pointee), Base(0), Offset(0) { + if (Pointee) + Pointee->addPointer(this); +} -Pointer::Pointer(const Pointer &P) : Pointer(P.Pointee, P.Base, P.Offset) {} +Pointer::Pointer(const Pointer &P) : Pointer(P.Pointee, P.Base, P.Offset) { + if (Pointee) + assert(Pointee->hasPointer(this)); +} Pointer::Pointer(Pointer &&P) : Pointee(P.Pointee), Base(P.Base), Offset(P.Offset) { - if (Pointee) - Pointee->movePointer(&P, this); + if (Pointee) { + assert(!Pointee->hasPointer(this)); + Pointee->addPointer(this); + assert(Pointee->hasPointer(this)); + } } Pointer::Pointer(Block *Pointee, unsigned Base, unsigned Offset) @@ -33,8 +43,9 @@ Pointer::~Pointer() { if (Pointee) { - Pointee->removePointer(this); Pointee->cleanup(); + Pointee->removePointer(this); + Pointee = nullptr; } } @@ -48,9 +59,12 @@ Base = P.Base; Pointee = P.Pointee; - if (Pointee) + if (Pointee) { Pointee->addPointer(this); + assert(Pointee->hasPointer(this)); + } + assert(Pointee->hasPointer(this)); if (Old) Old->cleanup(); } @@ -58,15 +72,17 @@ void Pointer::operator=(Pointer &&P) { Block *Old = Pointee; - if (Pointee) + if (this->Pointee) Pointee->removePointer(this); Offset = P.Offset; Base = P.Base; - Pointee = P.Pointee; - if (Pointee) - Pointee->movePointer(&P, this); + + if (Pointee) { + Pointee->addPointer(this); + assert(Pointee->hasPointer(this)); + } if (Old) Old->cleanup(); Index: clang/lib/AST/Interp/Program.h =================================================================== --- clang/lib/AST/Interp/Program.h +++ clang/lib/AST/Interp/Program.h @@ -56,7 +56,12 @@ /// Returns the value of a global. Block *getGlobal(unsigned Idx) { assert(Idx < Globals.size()); - return Globals[Idx]->block(); + return Globals[Idx].block(); + } + + /// Checks whether the given pointer points to a global variable. + bool isGlobalPointer(const Pointer &P) const { + return false; } /// Finds a global's index. @@ -156,35 +161,12 @@ /// 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; + /// All global variables we've created. We use Pointers instances here + /// so we get pointer tracking in the blocks. + std::vector Globals; /// Cached global indices. llvm::DenseMap GlobalIndices; @@ -200,6 +182,15 @@ return new (Allocator) Descriptor(std::forward(Args)...); } + /// Creates a new Block. + template + Block *allocateBlock(const Descriptor *D, Ts &&...Args) { + Block *B = static_cast( + Allocator.Allocate(sizeof(Block) + D->getAllocSize(), alignof(void *))); + new (B) Block(std::forward(Args)...); + return B; + } + /// No declaration ID. static constexpr unsigned NoDeclaration = (unsigned)-1; /// Last declaration ID. Index: clang/lib/AST/Interp/Program.cpp =================================================================== --- clang/lib/AST/Interp/Program.cpp +++ clang/lib/AST/Interp/Program.cpp @@ -53,21 +53,21 @@ } // Create a descriptor for the string. - Descriptor *Desc = allocateDescriptor(S, CharType, S->getLength() + 1, - /*isConst=*/true, - /*isTemporary=*/false, - /*isMutable=*/false); + Descriptor *Desc = + allocateDescriptor(S, CharType, S->getLength() + 1, + /*ArrayFiller=*/nullptr, 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); + auto *G = allocateBlock(Desc, Desc, true, false); + Globals.emplace_back(G); // Construct the string in storage. - const Pointer Ptr(G->block()); + const Pointer Ptr(G); 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); @@ -96,7 +96,7 @@ Pointer Program::getPtrGlobal(unsigned Idx) { assert(Idx < Globals.size()); - return Pointer(Globals[Idx]->block()); + return Globals[Idx]; } llvm::Optional Program::getGlobal(const ValueDecl *VD) { @@ -185,19 +185,17 @@ if (auto T = Ctx.classify(Ty)) { Desc = createDescriptor(D, *T, IsConst, IsTemporary); } else { - Desc = createDescriptor(D, Ty.getTypePtr(), IsConst, IsTemporary); + Desc = + createDescriptor(D, Ty.getTypePtr(), IsConst, IsTemporary, false, Init); } 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); + auto *G = allocateBlock(Desc, getCurrentDecl(), Desc, IsStatic, IsExtern); + G->invokeCtor(); + Globals.emplace_back(G); return I; } @@ -324,20 +322,34 @@ QualType ElemTy = ArrayType->getElementType(); // Array of well-known bounds. if (auto CAT = dyn_cast(ArrayType)) { - size_t NumElems = CAT->getSize().getZExtValue(); + const size_t NumElems = CAT->getSize().getZExtValue(); + size_t NumGivenElems = NumElems; + + const Expr *ArrayFiller = nullptr; + const auto *InitList = dyn_cast_if_present(Init); + if (InitList && InitList->hasArrayFiller()) { + ArrayFiller = InitList->getArrayFiller(); + + NumGivenElems = InitList->getNumInits(); + assert(NumGivenElems < NumElems); + // TODO: Check if the array filler is element-dependent. + // We can just copy that from ExprConstant.cpp \o/ + // TODO: Is this really only for primitive arrays? + } + if (llvm::Optional T = Ctx.classify(ElemTy)) { // Arrays of primitives. unsigned ElemSize = primSize(*T); - if (std::numeric_limits::max() / ElemSize <= NumElems) { + if (std::numeric_limits::max() / ElemSize <= NumElems) return {}; - } - return allocateDescriptor(D, *T, NumElems, IsConst, IsTemporary, - IsMutable); + return allocateDescriptor(D, *T, NumGivenElems, ArrayFiller, NumElems, + IsConst, IsTemporary, IsMutable); } else { // Arrays of composites. In this case, the array is a list of pointers, // followed by the actual elements. Descriptor *ElemDesc = createDescriptor(D, ElemTy.getTypePtr(), IsConst, IsTemporary); + if (!ElemDesc) return nullptr; InterpSize ElemSize = @@ -375,7 +387,8 @@ // 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 allocateDescriptor(D, ElemTy, 2, nullptr, 2, IsConst, IsTemporary, + IsMutable); } return nullptr; Index: clang/test/AST/Interp/arrays.cpp =================================================================== --- clang/test/AST/Interp/arrays.cpp +++ clang/test/AST/Interp/arrays.cpp @@ -98,3 +98,59 @@ struct fred y [] = { [0] = { .s[0] = 'q' } }; #endif #pragma clang diagnostic pop + +namespace indices { + constexpr int first[] = {1}; + constexpr int firstValue = first[2]; // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{cannot refer to element 2 of array of 1}} \ + // expected-error {{must be initialized by a constant expression}} \ + // expected-note {{cannot refer to element 2 of array of 1}} + + constexpr int second[10] = {17}; + constexpr int secondValue = second[10];// ref-error {{must be initialized by a constant expression}} \ + // ref-note {{read of dereferenced one-past-the-end pointer}} \ + // expected-error {{must be initialized by a constant expression}} \ + // expected-note {{cannot refer to element 10 of array of 10}} + + constexpr int negative = second[-2]; // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{cannot refer to element -2 of array of 10}} \ + // expected-error {{must be initialized by a constant expression}} \ + // expected-note {{cannot refer to element -2 of array of 10}} +}; + +namespace fillers { + constexpr int k[3] = {1337}; + constexpr int foo(int m) { + return k[m]; + } + static_assert(foo(0) == 1337, ""); + static_assert(foo(1) == 0, ""); + static_assert(foo(2) == 0, ""); + + constexpr int N = 12; + constexpr int local(unsigned i) { + int arr[N] = {1,2,3}; + return arr[i]; + } + static_assert(local(0) == 1, ""); + static_assert(local(1) == 2, ""); + static_assert(local(2) == 3, ""); + static_assert(local(3) == 0, ""); + static_assert(local(N - 1) == 0, ""); + + /// FIXME: Non-primitive array fillers are not handled properly currently, + /// but nobody notices right now because all fillers are 0 anyway and + /// that's also was we memset() the memory to. + constexpr int twodim[2][4] = { + {1,2}, + {3,4} + }; + static_assert(twodim[0][0] == 1, ""); + static_assert(twodim[0][1] == 2, ""); + static_assert(twodim[0][2] == 0, ""); + static_assert(twodim[0][3] == 0, ""); + static_assert(twodim[1][0] == 3, ""); + static_assert(twodim[1][1] == 4, ""); + static_assert(twodim[1][2] == 0, ""); + static_assert(twodim[1][3] == 0, ""); +};