diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp index 0bbab0cc06655b..f4bf162a1454c1 100644 --- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp @@ -802,7 +802,11 @@ unsigned ByteCodeExprGen::allocateLocalPrimitive(DeclTy &&Src, PrimType Ty, bool IsConst, bool IsExtended) { - Descriptor *D = P.createDescriptor(Src, Ty, IsConst, Src.is()); + // FIXME: There are cases where Src.is() is wrong, e.g. + // (int){12} in C. Consider using Expr::isTemporaryObject() instead + // or isa(). + Descriptor *D = P.createDescriptor(Src, Ty, Descriptor::InlineDescMD, IsConst, + Src.is()); Scope::Local Local = this->createLocal(D); if (auto *VD = dyn_cast_or_null(Src.dyn_cast())) Locals.insert({VD, Local}); @@ -831,7 +835,8 @@ ByteCodeExprGen::allocateLocal(DeclTy &&Src, bool IsExtended) { } Descriptor *D = P.createDescriptor( - Src, Ty.getTypePtr(), Ty.isConstQualified(), IsTemporary, false, Init); + Src, Ty.getTypePtr(), Descriptor::InlineDescMD, Ty.isConstQualified(), + IsTemporary, /*IsMutable=*/false, Init); if (!D) return {}; diff --git a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp index 44997857f6a596..60506a759f45cf 100644 --- a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp @@ -402,24 +402,26 @@ bool ByteCodeStmtGen::visitVarDecl(const VarDecl *VD) { if (std::optional T = this->classify(VD->getType())) { const Expr *Init = VD->getInit(); - if (!Init) - return false; - unsigned Offset = this->allocateLocalPrimitive(VD, *T, VD->getType().isConstQualified()); // Compile the initializer in its own scope. - { + if (Init) { ExprScope Scope(this); if (!this->visit(Init)) return false; + + return this->emitSetLocal(*T, Offset, VD); } - // Set the value. - return this->emitSetLocal(*T, Offset, VD); + return true; } // Composite types - allocate storage and initialize it. - if (std::optional Offset = this->allocateLocal(VD)) + if (std::optional Offset = this->allocateLocal(VD)) { + if (!VD->getInit()) + return true; + return this->visitLocalInitializer(VD->getInit(), *Offset); + } return this->bail(VD); } diff --git a/clang/lib/AST/Interp/Context.cpp b/clang/lib/AST/Interp/Context.cpp index 6fd645b87d22be..16471242f32825 100644 --- a/clang/lib/AST/Interp/Context.cpp +++ b/clang/lib/AST/Interp/Context.cpp @@ -125,7 +125,7 @@ unsigned Context::getCharBit() const { bool Context::Run(State &Parent, Function *Func, APValue &Result) { InterpState State(Parent, *P, Stk, *this); - State.Current = new InterpFrame(State, Func, nullptr, {}, {}); + State.Current = new InterpFrame(State, Func, /*Caller=*/nullptr, {}); if (Interpret(State, Result)) return true; Stk.clear(); diff --git a/clang/lib/AST/Interp/Descriptor.cpp b/clang/lib/AST/Interp/Descriptor.cpp index f645063acdd01a..5575fc1e2a6e7a 100644 --- a/clang/lib/AST/Interp/Descriptor.cpp +++ b/clang/lib/AST/Interp/Descriptor.cpp @@ -191,19 +191,22 @@ static BlockMoveFn getMoveArrayPrim(PrimType Type) { COMPOSITE_TYPE_SWITCH(Type, return moveArrayTy, return nullptr); } -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)) { +Descriptor::Descriptor(const DeclTy &D, PrimType Type, MetadataSize MD, + bool IsConst, bool IsTemporary, bool IsMutable) + : Source(D), ElemSize(primSize(Type)), Size(ElemSize), + MDSize(MD.value_or(0)), AllocSize(align(Size + MDSize)), IsConst(IsConst), + IsMutable(IsMutable), IsTemporary(IsTemporary), CtorFn(getCtorPrim(Type)), + DtorFn(getDtorPrim(Type)), MoveFn(getMovePrim(Type)) { + assert(AllocSize >= Size); assert(Source && "Missing source"); } -Descriptor::Descriptor(const DeclTy &D, PrimType Type, size_t NumElems, - bool IsConst, bool IsTemporary, bool IsMutable) +Descriptor::Descriptor(const DeclTy &D, PrimType Type, MetadataSize MD, + size_t NumElems, bool IsConst, bool IsTemporary, + bool IsMutable) : Source(D), ElemSize(primSize(Type)), Size(ElemSize * NumElems), - AllocSize(align(Size) + sizeof(InitMap *)), IsConst(IsConst), + MDSize(MD.value_or(0)), + AllocSize(align(Size) + sizeof(InitMap *) + MDSize), IsConst(IsConst), IsMutable(IsMutable), IsTemporary(IsTemporary), IsArray(true), CtorFn(getCtorArrayPrim(Type)), DtorFn(getDtorArrayPrim(Type)), MoveFn(getMoveArrayPrim(Type)) { @@ -212,39 +215,42 @@ Descriptor::Descriptor(const DeclTy &D, PrimType Type, size_t NumElems, Descriptor::Descriptor(const DeclTy &D, PrimType Type, bool IsTemporary, UnknownSize) - : Source(D), ElemSize(primSize(Type)), Size(UnknownSizeMark), + : Source(D), ElemSize(primSize(Type)), Size(UnknownSizeMark), MDSize(0), AllocSize(alignof(void *)), IsConst(true), IsMutable(false), IsTemporary(IsTemporary), IsArray(true), CtorFn(getCtorArrayPrim(Type)), DtorFn(getDtorArrayPrim(Type)), MoveFn(getMoveArrayPrim(Type)) { assert(Source && "Missing source"); } -Descriptor::Descriptor(const DeclTy &D, Descriptor *Elem, unsigned NumElems, - bool IsConst, bool IsTemporary, bool IsMutable) +Descriptor::Descriptor(const DeclTy &D, Descriptor *Elem, MetadataSize MD, + unsigned NumElems, bool IsConst, bool IsTemporary, + bool IsMutable) : Source(D), ElemSize(Elem->getAllocSize() + sizeof(InlineDescriptor)), - 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) { + Size(ElemSize * NumElems), MDSize(MD.value_or(0)), + AllocSize(std::max(alignof(void *), Size) + MDSize), + ElemDesc(Elem), IsConst(IsConst), IsMutable(IsMutable), + IsTemporary(IsTemporary), IsArray(true), CtorFn(ctorArrayDesc), + DtorFn(dtorArrayDesc), MoveFn(moveArrayDesc) { assert(Source && "Missing source"); } Descriptor::Descriptor(const DeclTy &D, Descriptor *Elem, bool IsTemporary, UnknownSize) : Source(D), ElemSize(Elem->getAllocSize() + sizeof(InlineDescriptor)), - Size(UnknownSizeMark), AllocSize(alignof(void *)), ElemDesc(Elem), - IsConst(true), IsMutable(false), IsTemporary(IsTemporary), IsArray(true), - CtorFn(ctorArrayDesc), DtorFn(dtorArrayDesc), MoveFn(moveArrayDesc) { + Size(UnknownSizeMark), MDSize(0), AllocSize(alignof(void *)), + ElemDesc(Elem), IsConst(true), IsMutable(false), IsTemporary(IsTemporary), + IsArray(true), CtorFn(ctorArrayDesc), DtorFn(dtorArrayDesc), + MoveFn(moveArrayDesc) { assert(Source && "Missing source"); } -Descriptor::Descriptor(const DeclTy &D, Record *R, bool IsConst, - bool IsTemporary, bool IsMutable) +Descriptor::Descriptor(const DeclTy &D, Record *R, MetadataSize MD, + bool IsConst, bool IsTemporary, bool IsMutable) : Source(D), ElemSize(std::max(alignof(void *), R->getFullSize())), - Size(ElemSize), AllocSize(Size), ElemRecord(R), IsConst(IsConst), - IsMutable(IsMutable), IsTemporary(IsTemporary), CtorFn(ctorRecord), - DtorFn(dtorRecord), MoveFn(moveRecord) { + Size(ElemSize), MDSize(MD.value_or(0)), AllocSize(Size + MDSize), + ElemRecord(R), IsConst(IsConst), IsMutable(IsMutable), + IsTemporary(IsTemporary), CtorFn(ctorRecord), DtorFn(dtorRecord), + MoveFn(moveRecord) { assert(Source && "Missing source"); } diff --git a/clang/lib/AST/Interp/Descriptor.h b/clang/lib/AST/Interp/Descriptor.h index b2f50815fe8484..ae39d38d818492 100644 --- a/clang/lib/AST/Interp/Descriptor.h +++ b/clang/lib/AST/Interp/Descriptor.h @@ -47,6 +47,34 @@ using BlockMoveFn = void (*)(Block *Storage, char *SrcFieldPtr, /// Object size as used by the interpreter. using InterpSize = unsigned; +/// Inline descriptor embedded in structures and arrays. +/// +/// Such descriptors precede all composite array elements and structure fields. +/// If the base of a pointer is not zero, the base points to the end of this +/// structure. The offset field is used to traverse the pointer chain up +/// to the root structure which allocated the object. +struct InlineDescriptor { + /// Offset inside the structure/array. + unsigned Offset; + + /// Flag indicating if the storage is constant or not. + /// Relevant for primitive fields. + unsigned IsConst : 1; + /// For primitive fields, it indicates if the field was initialized. + /// Primitive fields in static storage are always initialized. + /// Arrays are always initialized, even though their elements might not be. + /// Base classes are initialized after the constructor is invoked. + unsigned IsInitialized : 1; + /// Flag indicating if the field is an embedded base class. + unsigned IsBase : 1; + /// Flag indicating if the field is the active member of a union. + unsigned IsActive : 1; + /// Flag indicating if the field is mutable (if in a record). + unsigned IsMutable : 1; // TODO: Rename to IsFieldMutable. + + Descriptor *Desc; +}; + /// Describes a memory block created by an allocation site. struct Descriptor final { private: @@ -56,6 +84,8 @@ struct Descriptor final { const InterpSize ElemSize; /// Size of the storage, in host bytes. const InterpSize Size; + // Size of the metadata. + const InterpSize MDSize; /// Size of the allocation (storage + metadata), in host bytes. const InterpSize AllocSize; @@ -66,6 +96,9 @@ struct Descriptor final { /// Token to denote structures of unknown size. struct UnknownSize {}; + using MetadataSize = std::optional; + static constexpr MetadataSize InlineDescMD = sizeof(InlineDescriptor); + /// Pointer to the record, if block contains records. Record *const ElemRecord = nullptr; /// Descriptor of the array element. @@ -85,26 +118,26 @@ struct Descriptor final { const BlockMoveFn MoveFn = nullptr; /// Allocates a descriptor for a primitive. - Descriptor(const DeclTy &D, PrimType Type, bool IsConst, bool IsTemporary, - bool IsMutable); + Descriptor(const DeclTy &D, PrimType Type, MetadataSize MD, bool IsConst, + bool IsTemporary, bool IsMutable); /// Allocates a descriptor for an array of primitives. - Descriptor(const DeclTy &D, PrimType Type, size_t NumElems, bool IsConst, - bool IsTemporary, bool IsMutable); + Descriptor(const DeclTy &D, PrimType Type, MetadataSize MD, size_t NumElems, + bool IsConst, bool IsTemporary, bool IsMutable); /// Allocates a descriptor for an array of primitives of unknown size. Descriptor(const DeclTy &D, PrimType Type, bool IsTemporary, UnknownSize); /// Allocates a descriptor for an array of composites. - Descriptor(const DeclTy &D, Descriptor *Elem, unsigned NumElems, bool IsConst, - bool IsTemporary, bool IsMutable); + Descriptor(const DeclTy &D, Descriptor *Elem, MetadataSize MD, + unsigned NumElems, bool IsConst, bool IsTemporary, bool IsMutable); /// Allocates a descriptor for an array of composites of unknown size. Descriptor(const DeclTy &D, Descriptor *Elem, bool IsTemporary, UnknownSize); /// Allocates a descriptor for a record. - Descriptor(const DeclTy &D, Record *R, bool IsConst, bool IsTemporary, - bool IsMutable); + Descriptor(const DeclTy &D, Record *R, MetadataSize MD, bool IsConst, + bool IsTemporary, bool IsMutable); QualType getType() const; SourceLocation getLocation() const; @@ -134,6 +167,8 @@ struct Descriptor final { unsigned getAllocSize() const { return AllocSize; } /// returns the size of an element when the structure is viewed as an array. unsigned getElemSize() const { return ElemSize; } + /// Returns the size of the metadata. + unsigned getMetadataSize() const { return MDSize; } /// Returns the number of elements stored in the block. unsigned getNumElems() const { @@ -154,34 +189,6 @@ struct Descriptor final { bool isArray() const { return IsArray; } }; -/// Inline descriptor embedded in structures and arrays. -/// -/// Such descriptors precede all composite array elements and structure fields. -/// If the base of a pointer is not zero, the base points to the end of this -/// structure. The offset field is used to traverse the pointer chain up -/// to the root structure which allocated the object. -struct InlineDescriptor { - /// Offset inside the structure/array. - unsigned Offset; - - /// Flag indicating if the storage is constant or not. - /// Relevant for primitive fields. - unsigned IsConst : 1; - /// For primitive fields, it indicates if the field was initialized. - /// Primitive fields in static storage are always initialized. - /// Arrays are always initialized, even though their elements might not be. - /// Base classes are initialized after the constructor is invoked. - unsigned IsInitialized : 1; - /// Flag indicating if the field is an embedded base class. - unsigned IsBase : 1; - /// Flag indicating if the field is the active member of a union. - unsigned IsActive : 1; - /// Flag indicating if the field is mutable (if in a record). - unsigned IsMutable : 1; - - Descriptor *Desc; -}; - /// Bitfield tracking the initialisation status of elements of primitive arrays. /// A pointer to this is embedded at the end of all primitive arrays. /// If the map was not yet created and nothing was initialized, the pointer to diff --git a/clang/lib/AST/Interp/EvalEmitter.cpp b/clang/lib/AST/Interp/EvalEmitter.cpp index 12854c1c043744..7b6e5561d978a8 100644 --- a/clang/lib/AST/Interp/EvalEmitter.cpp +++ b/clang/lib/AST/Interp/EvalEmitter.cpp @@ -23,7 +23,8 @@ EvalEmitter::EvalEmitter(Context &Ctx, Program &P, State &Parent, InterpStack &Stk, APValue &Result) : Ctx(Ctx), P(P), S(Parent, P, Stk, Ctx, this), Result(Result) { // Create a dummy frame for the interpreter which does not have locals. - S.Current = new InterpFrame(S, nullptr, nullptr, CodePtr(), Pointer()); + S.Current = + new InterpFrame(S, /*Func=*/nullptr, /*Caller=*/nullptr, CodePtr()); } llvm::Expected EvalEmitter::interpretExpr(const Expr *E) { @@ -54,6 +55,12 @@ Scope::Local EvalEmitter::createLocal(Descriptor *D) { auto *B = new (Memory.get()) Block(D, /*isStatic=*/false); B->invokeCtor(); + // Initialize local variable inline descriptor. + InlineDescriptor &Desc = *reinterpret_cast(B->rawData()); + Desc.Desc = D; + Desc.Offset = sizeof(InlineDescriptor); + Desc.IsActive = true; + // Register the local. unsigned Off = Locals.size(); Locals.insert({Off, std::move(Memory)}); @@ -199,7 +206,8 @@ bool EvalEmitter::emitGetPtrLocal(uint32_t I, const SourceInfo &Info) { auto It = Locals.find(I); assert(It != Locals.end() && "Missing local variable"); - S.Stk.push(reinterpret_cast(It->second.get())); + Block *B = reinterpret_cast(It->second.get()); + S.Stk.push(B, sizeof(InlineDescriptor)); return true; } @@ -213,7 +221,7 @@ bool EvalEmitter::emitGetLocal(uint32_t I, const SourceInfo &Info) { auto It = Locals.find(I); assert(It != Locals.end() && "Missing local variable"); auto *B = reinterpret_cast(It->second.get()); - S.Stk.push(*reinterpret_cast(B + 1)); + S.Stk.push(*reinterpret_cast(B->data())); return true; } @@ -227,7 +235,10 @@ bool EvalEmitter::emitSetLocal(uint32_t I, const SourceInfo &Info) { auto It = Locals.find(I); assert(It != Locals.end() && "Missing local variable"); auto *B = reinterpret_cast(It->second.get()); - *reinterpret_cast(B + 1) = S.Stk.pop(); + *reinterpret_cast(B->data()) = S.Stk.pop(); + InlineDescriptor &Desc = *reinterpret_cast(B->rawData()); + Desc.IsInitialized = true; + return true; } diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h index e252c54c88731e..07515548a06bae 100644 --- a/clang/lib/AST/Interp/Interp.h +++ b/clang/lib/AST/Interp/Interp.h @@ -569,7 +569,10 @@ bool Const(InterpState &S, CodePtr OpPC, const T &Arg) { template ::T> bool GetLocal(InterpState &S, CodePtr OpPC, uint32_t I) { - S.Stk.push(S.Current->getLocal(I)); + const Pointer &Ptr = S.Current->getLocalPointer(I); + if (!CheckLoad(S, OpPC, Ptr)) + return false; + S.Stk.push(Ptr.deref()); return true; } @@ -912,6 +915,8 @@ bool Store(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.peek(); if (!CheckStore(S, OpPC, Ptr)) return false; + if (!Ptr.isRoot()) + Ptr.initialize(); Ptr.deref() = Value; return true; } @@ -922,6 +927,8 @@ bool StorePop(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop(); if (!CheckStore(S, OpPC, Ptr)) return false; + if (!Ptr.isRoot()) + Ptr.initialize(); Ptr.deref() = Value; return true; } @@ -932,6 +939,8 @@ bool StoreBitField(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.peek(); if (!CheckStore(S, OpPC, Ptr)) return false; + if (!Ptr.isRoot()) + Ptr.initialize(); if (auto *FD = Ptr.getField()) { Ptr.deref() = Value.truncate(FD->getBitWidthValue(S.getCtx())); } else { @@ -946,6 +955,8 @@ bool StoreBitFieldPop(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop(); if (!CheckStore(S, OpPC, Ptr)) return false; + if (!Ptr.isRoot()) + Ptr.initialize(); if (auto *FD = Ptr.getField()) { Ptr.deref() = Value.truncate(FD->getBitWidthValue(S.getCtx())); } else { diff --git a/clang/lib/AST/Interp/InterpBlock.h b/clang/lib/AST/Interp/InterpBlock.h index b8b5b39f55d446..f790c50a91237f 100644 --- a/clang/lib/AST/Interp/InterpBlock.h +++ b/clang/lib/AST/Interp/InterpBlock.h @@ -31,7 +31,21 @@ enum PrimType : unsigned; /// A memory block, either on the stack or in the heap. /// -/// The storage described by the block immediately follows it in memory. +/// The storage described by the block is immediately followed by +/// optional metadata, which is followed by the actual data. +/// +/// Block* rawData() data() +/// │ │ │ +/// │ │ │ +/// ▼ ▼ ▼ +/// ┌───────────────┬─────────────────────────┬─────────────────┐ +/// │ Block │ Metadata │ Data │ +/// │ sizeof(Block) │ Desc->getMetadataSize() │ Desc->getSize() │ +/// └───────────────┴─────────────────────────┴─────────────────┘ +/// +/// Desc->getAllocSize() describes the size after the Block, i.e. +/// the data size and the metadata size. +/// class Block final { public: // Creates a new block. @@ -59,7 +73,24 @@ class Block final { std::optional getDeclID() const { return DeclID; } /// Returns a pointer to the stored data. - char *data() { return reinterpret_cast(this + 1); } + /// You are allowed to read Desc->getSize() bytes from this address. + char *data() { + // rawData might contain metadata as well. + size_t DataOffset = Desc->getMetadataSize(); + return rawData() + DataOffset; + } + const char *data() const { + // rawData might contain metadata as well. + size_t DataOffset = Desc->getMetadataSize(); + return rawData() + DataOffset; + } + + /// Returns a pointer to the raw data, including metadata. + /// You are allowed to read Desc->getAllocSize() bytes from this address. + char *rawData() { return reinterpret_cast(this) + sizeof(Block); } + const char *rawData() const { + return reinterpret_cast(this) + sizeof(Block); + } /// Returns a view over the data. template @@ -67,7 +98,7 @@ class Block final { /// Invokes the constructor. void invokeCtor() { - std::memset(data(), 0, getSize()); + std::memset(rawData(), 0, Desc->getAllocSize()); if (Desc->CtorFn) Desc->CtorFn(this, data(), Desc->IsConst, Desc->IsMutable, /*isActive=*/true, Desc); diff --git a/clang/lib/AST/Interp/InterpFrame.cpp b/clang/lib/AST/Interp/InterpFrame.cpp index 619c56fe4bf7a4..9acfbe3700e982 100644 --- a/clang/lib/AST/Interp/InterpFrame.cpp +++ b/clang/lib/AST/Interp/InterpFrame.cpp @@ -20,8 +20,8 @@ using namespace clang; using namespace clang::interp; InterpFrame::InterpFrame(InterpState &S, const Function *Func, - InterpFrame *Caller, CodePtr RetPC, Pointer &&This) - : Caller(Caller), S(S), Func(Func), This(std::move(This)), RetPC(RetPC), + InterpFrame *Caller, CodePtr RetPC) + : Caller(Caller), S(S), Func(Func), RetPC(RetPC), ArgSize(Func ? Func->getArgSize() : 0), Args(static_cast(S.Stk.top())), FrameOffset(S.Stk.size()) { if (Func) { @@ -31,6 +31,10 @@ InterpFrame::InterpFrame(InterpState &S, const Function *Func, for (auto &Local : Scope.locals()) { Block *B = new (localBlock(Local.Offset)) Block(Local.Desc); B->invokeCtor(); + InlineDescriptor *ID = localInlineDesc(Local.Offset); + ID->Desc = Local.Desc; + ID->IsActive = true; + ID->Offset = sizeof(InlineDescriptor); } } } @@ -38,11 +42,7 @@ InterpFrame::InterpFrame(InterpState &S, const Function *Func, } InterpFrame::InterpFrame(InterpState &S, const 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); - + : InterpFrame(S, Func, S.Current, RetPC) { // As per our calling convention, the this pointer is // part of the ArgSize. // If the function has RVO, the RVO pointer is first. @@ -58,16 +58,6 @@ InterpFrame::InterpFrame(InterpState &S, const Function *Func, CodePtr RetPC) 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() { @@ -186,10 +176,10 @@ const FunctionDecl *InterpFrame::getCallee() const { return Func->getDecl(); } -Pointer InterpFrame::getLocalPointer(unsigned Offset) { +Pointer InterpFrame::getLocalPointer(unsigned Offset) const { assert(Offset < Func->getFrameSize() && "Invalid local offset."); - return Pointer( - reinterpret_cast(Locals.get() + Offset - sizeof(Block))); + return Pointer(reinterpret_cast(localBlock(Offset)), + sizeof(InlineDescriptor)); } Pointer InterpFrame::getParamPointer(unsigned Off) { diff --git a/clang/lib/AST/Interp/InterpFrame.h b/clang/lib/AST/Interp/InterpFrame.h index bcb45a1b9174f5..bfa02c90ebec4e 100644 --- a/clang/lib/AST/Interp/InterpFrame.h +++ b/clang/lib/AST/Interp/InterpFrame.h @@ -33,7 +33,7 @@ class InterpFrame final : public Frame { /// Creates a new frame for a method call. InterpFrame(InterpState &S, const Function *Func, InterpFrame *Caller, - CodePtr RetPC, Pointer &&This); + CodePtr RetPC); /// Creates a new frame with the values that make sense. /// I.e., the caller is the current frame of S, @@ -76,10 +76,11 @@ class InterpFrame final : public Frame { /// Mutates a local variable. template void setLocal(unsigned Offset, const T &Value) { localRef(Offset) = Value; + localInlineDesc(Offset)->IsInitialized = true; } /// Returns a pointer to a local variables. - Pointer getLocalPointer(unsigned Offset); + Pointer getLocalPointer(unsigned Offset) const; /// Returns the value of an argument. template const T &getParam(unsigned Offset) const { @@ -128,7 +129,7 @@ class InterpFrame final : public Frame { /// Returns an offset to a local. template T &localRef(unsigned Offset) const { - return *reinterpret_cast(Locals.get() + Offset); + return getLocalPointer(Offset).deref(); } /// Returns a pointer to a local's block. @@ -136,6 +137,11 @@ class InterpFrame final : public Frame { return Locals.get() + Offset - sizeof(Block); } + // Returns the inline descriptor of the local. + InlineDescriptor *localInlineDesc(unsigned Offset) const { + return reinterpret_cast(Locals.get() + Offset); + } + private: /// Reference to the interpreter state. InterpState &S; diff --git a/clang/lib/AST/Interp/Pointer.cpp b/clang/lib/AST/Interp/Pointer.cpp index b849acb52f0c1b..fd8c98fae03967 100644 --- a/clang/lib/AST/Interp/Pointer.cpp +++ b/clang/lib/AST/Interp/Pointer.cpp @@ -16,6 +16,9 @@ using namespace clang::interp; Pointer::Pointer(Block *Pointee) : Pointer(Pointee, 0, 0) {} +Pointer::Pointer(Block *Pointee, unsigned BaseAndOffset) + : Pointer(Pointee, BaseAndOffset, BaseAndOffset) {} + Pointer::Pointer(const Pointer &P) : Pointer(P.Pointee, P.Base, P.Offset) {} Pointer::Pointer(Pointer &&P) diff --git a/clang/lib/AST/Interp/Pointer.h b/clang/lib/AST/Interp/Pointer.h index 38341ae2322d94..44c485681af54a 100644 --- a/clang/lib/AST/Interp/Pointer.h +++ b/clang/lib/AST/Interp/Pointer.h @@ -33,6 +33,31 @@ enum PrimType : unsigned; /// /// This object can be allocated into interpreter stack frames. If pointing to /// a live block, it is a link in the chain of pointers pointing to the block. +/// +/// In the simplest form, a Pointer has a Block* (the pointee) and both Base +/// and Offset are 0, which means it will point to raw data. +/// +/// The Base field is used to access metadata about the data. For primitive +/// arrays, the Base is followed by an InitMap. In a variety of cases, the +/// Base is preceded by an InlineDescriptor, which is used to track the +/// initialization state, among other things. +/// +/// The Offset field is used to access the actual data. In other words, the +/// data the pointer decribes can be found at +/// Pointee->rawData() + Pointer.Offset. +/// +/// +/// Pointee Offset +/// │ │ +/// │ │ +/// ▼ ▼ +/// ┌───────┬────────────┬─────────┬────────────────────────────┐ +/// │ Block │ InlineDesc │ InitMap │ Actual Data │ +/// └───────┴────────────┴─────────┴────────────────────────────┘ +/// ▲ +/// │ +/// │ +/// Base class Pointer { private: static constexpr unsigned PastEndMark = (unsigned)-1; @@ -41,6 +66,7 @@ class Pointer { public: Pointer() {} Pointer(Block *B); + Pointer(Block *B, unsigned BaseAndOffset); Pointer(const Pointer &P); Pointer(Pointer &&P); ~Pointer(); @@ -276,12 +302,12 @@ class Pointer { /// Dereferences the pointer, if it's live. template T &deref() const { assert(isLive() && "Invalid pointer"); - return *reinterpret_cast(Pointee->data() + Offset); + return *reinterpret_cast(Pointee->rawData() + Offset); } /// Dereferences a primitive element. template T &elem(unsigned I) const { - return reinterpret_cast(Pointee->data())[I]; + return reinterpret_cast(Pointee->rawData())[I]; } /// Initializes a field. @@ -318,12 +344,13 @@ class Pointer { /// Returns a descriptor at a given offset. InlineDescriptor *getDescriptor(unsigned Offset) const { assert(Offset != 0 && "Not a nested pointer"); - return reinterpret_cast(Pointee->data() + Offset) - 1; + return reinterpret_cast(Pointee->rawData() + Offset) - + 1; } /// Returns a reference to the pointer which stores the initialization map. InitMap *&getInitMap() const { - return *reinterpret_cast(Pointee->data() + Base); + return *reinterpret_cast(Pointee->rawData() + Base); } /// The block the pointer is pointing to. diff --git a/clang/lib/AST/Interp/Program.cpp b/clang/lib/AST/Interp/Program.cpp index 3e1d0bc6ad204b..067f6d90b6c114 100644 --- a/clang/lib/AST/Interp/Program.cpp +++ b/clang/lib/AST/Interp/Program.cpp @@ -53,10 +53,11 @@ unsigned Program::createGlobalString(const StringLiteral *S) { } // 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, std::nullopt, S->getLength() + 1, + /*isConst=*/true, + /*isTemporary=*/false, + /*isMutable=*/false); // Allocate storage for the string. // The byte length does not include the null terminator. @@ -184,9 +185,10 @@ std::optional Program::createGlobal(const DeclTy &D, QualType Ty, const bool IsConst = Ty.isConstQualified(); const bool IsTemporary = D.dyn_cast(); if (auto T = Ctx.classify(Ty)) { - Desc = createDescriptor(D, *T, IsConst, IsTemporary); + Desc = createDescriptor(D, *T, std::nullopt, IsConst, IsTemporary); } else { - Desc = createDescriptor(D, Ty.getTypePtr(), IsConst, IsTemporary); + Desc = createDescriptor(D, Ty.getTypePtr(), std::nullopt, IsConst, + IsTemporary); } if (!Desc) return {}; @@ -236,7 +238,7 @@ Record *Program::getOrCreateRecord(const RecordDecl *RD) { auto GetBaseDesc = [this](const RecordDecl *BD, Record *BR) -> Descriptor * { if (!BR) return nullptr; - return allocateDescriptor(BD, BR, /*isConst=*/false, + return allocateDescriptor(BD, BR, std::nullopt, /*isConst=*/false, /*isTemporary=*/false, /*isMutable=*/false); }; @@ -286,10 +288,10 @@ Record *Program::getOrCreateRecord(const RecordDecl *RD) { const bool IsMutable = FD->isMutable(); Descriptor *Desc; if (std::optional T = Ctx.classify(FT)) { - Desc = createDescriptor(FD, *T, IsConst, /*isTemporary=*/false, - IsMutable); + Desc = createDescriptor(FD, *T, std::nullopt, IsConst, + /*isTemporary=*/false, IsMutable); } else { - Desc = createDescriptor(FD, FT.getTypePtr(), IsConst, + Desc = createDescriptor(FD, FT.getTypePtr(), std::nullopt, IsConst, /*isTemporary=*/false, IsMutable); } if (!Desc) @@ -305,12 +307,14 @@ Record *Program::getOrCreateRecord(const RecordDecl *RD) { } Descriptor *Program::createDescriptor(const DeclTy &D, const Type *Ty, + Descriptor::MetadataSize MDSize, bool IsConst, bool IsTemporary, bool IsMutable, const Expr *Init) { // Classes and structures. if (auto *RT = Ty->getAs()) { if (auto *Record = getOrCreateRecord(RT->getDecl())) - return allocateDescriptor(D, Record, IsConst, IsTemporary, IsMutable); + return allocateDescriptor(D, Record, MDSize, IsConst, IsTemporary, + IsMutable); } // Arrays. @@ -325,21 +329,21 @@ Descriptor *Program::createDescriptor(const DeclTy &D, const Type *Ty, if (std::numeric_limits::max() / ElemSize <= NumElems) { return {}; } - return allocateDescriptor(D, *T, NumElems, IsConst, IsTemporary, + return allocateDescriptor(D, *T, MDSize, 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); + Descriptor *ElemDesc = createDescriptor( + D, ElemTy.getTypePtr(), std::nullopt, IsConst, IsTemporary); if (!ElemDesc) return nullptr; InterpSize ElemSize = ElemDesc->getAllocSize() + sizeof(InlineDescriptor); if (std::numeric_limits::max() / ElemSize <= NumElems) return {}; - return allocateDescriptor(D, ElemDesc, NumElems, IsConst, IsTemporary, - IsMutable); + return allocateDescriptor(D, ElemDesc, MDSize, NumElems, IsConst, + IsTemporary, IsMutable); } } @@ -350,8 +354,8 @@ Descriptor *Program::createDescriptor(const DeclTy &D, const Type *Ty, return allocateDescriptor(D, *T, IsTemporary, Descriptor::UnknownSize{}); } else { - Descriptor *Desc = - createDescriptor(D, ElemTy.getTypePtr(), IsConst, IsTemporary); + Descriptor *Desc = createDescriptor(D, ElemTy.getTypePtr(), MDSize, + IsConst, IsTemporary); if (!Desc) return nullptr; return allocateDescriptor(D, Desc, IsTemporary, @@ -363,13 +367,15 @@ Descriptor *Program::createDescriptor(const DeclTy &D, const Type *Ty, // Atomic types. if (auto *AT = Ty->getAs()) { const Type *InnerTy = AT->getValueType().getTypePtr(); - return createDescriptor(D, InnerTy, IsConst, IsTemporary, IsMutable); + return createDescriptor(D, InnerTy, MDSize, 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 allocateDescriptor(D, ElemTy, MDSize, 2, IsConst, IsTemporary, + IsMutable); } return nullptr; diff --git a/clang/lib/AST/Interp/Program.h b/clang/lib/AST/Interp/Program.h index c5591c58e457c6..f49ca6db13e112 100644 --- a/clang/lib/AST/Interp/Program.h +++ b/clang/lib/AST/Interp/Program.h @@ -114,14 +114,15 @@ class Program final { /// Creates a descriptor for a primitive type. Descriptor *createDescriptor(const DeclTy &D, PrimType Type, - bool IsConst = false, - bool IsTemporary = false, + Descriptor::MetadataSize MDSize = std::nullopt, + bool IsConst = false, bool IsTemporary = false, bool IsMutable = false) { - return allocateDescriptor(D, Type, IsConst, IsTemporary, IsMutable); + return allocateDescriptor(D, Type, MDSize, IsConst, IsTemporary, IsMutable); } /// Creates a descriptor for a composite type. Descriptor *createDescriptor(const DeclTy &D, const Type *Ty, + Descriptor::MetadataSize MDSize = std::nullopt, bool IsConst = false, bool IsTemporary = false, bool IsMutable = false, const Expr *Init = nullptr); diff --git a/clang/test/AST/Interp/cxx20.cpp b/clang/test/AST/Interp/cxx20.cpp index 38f05844a1ec94..5ec3e364b7ad45 100644 --- a/clang/test/AST/Interp/cxx20.cpp +++ b/clang/test/AST/Interp/cxx20.cpp @@ -56,33 +56,51 @@ constexpr int pointerAssign2() { } static_assert(pointerAssign2() == 12, ""); - constexpr int unInitLocal() { int a; - return a; // ref-note{{read of uninitialized object}} + return a; // ref-note {{read of uninitialized object}} \ + // expected-note {{read of object outside its lifetime}} + // FIXME: ^^^ Wrong diagnostic. } -static_assert(unInitLocal() == 0, ""); // expected-error {{not an integral constant expression}} \ - // ref-error {{not an integral constant expression}} \ - // ref-note {{in call to 'unInitLocal()'}} - -/// TODO: The example above is correctly rejected by the new constexpr -/// interpreter, but for the wrong reasons. We don't reject it because -/// it is an uninitialized read, we reject it simply because -/// the local variable does not have an initializer. -/// -/// The code below should be accepted but is also being rejected -/// right now. -#if 0 +static_assert(unInitLocal() == 0, ""); // ref-error {{not an integral constant expression}} \ + // ref-note {{in call to 'unInitLocal()'}} \ + // expected-error {{not an integral constant expression}} \ + // expected-note {{in call to 'unInitLocal()'}} \ + constexpr int initializedLocal() { int a; - int b; - a = 20; return a; } static_assert(initializedLocal() == 20); -/// Similar here, but the uninitialized local is passed as a function parameter. +constexpr int initializedLocal2() { + int a[2]; + return *a; // expected-note {{read of object outside its lifetime}} \ + // ref-note {{read of uninitialized object is not allowed in a constant expression}} +} +static_assert(initializedLocal2() == 20); // expected-error {{not an integral constant expression}} \ + // expected-note {{in call to}} \ + // ref-error {{not an integral constant expression}} \ + // ref-note {{in call to}} + + +struct Int { int a; }; +constexpr int initializedLocal3() { + Int i; + return i.a; // expected-note {{read of object outside its lifetime}} \ + // ref-note {{read of uninitialized object is not allowed in a constant expression}} +} +static_assert(initializedLocal3() == 20); // expected-error {{not an integral constant expression}} \ + // expected-note {{in call to}} \ + // ref-error {{not an integral constant expression}} \ + // ref-note {{in call to}} + + + +#if 0 +// FIXME: This code should be rejected because we pass an uninitialized value +// as a function parameter. constexpr int inc(int a) { return a + 1; } constexpr int f() { int i; diff --git a/clang/test/AST/Interp/literals.cpp b/clang/test/AST/Interp/literals.cpp index dc99f83fb1df87..cd5c9da9bda51b 100644 --- a/clang/test/AST/Interp/literals.cpp +++ b/clang/test/AST/Interp/literals.cpp @@ -407,8 +407,7 @@ namespace IncDec { return 1; } static_assert(uninit(), ""); // ref-error {{not an integral constant expression}} \ - // ref-note {{in call to 'uninit()'}} \ - // expected-error {{not an integral constant expression}} + // ref-note {{in call to 'uninit()'}} constexpr int OverFlow() { // ref-error {{never produces a constant expression}} int a = INT_MAX; diff --git a/clang/test/AST/Interp/loops.cpp b/clang/test/AST/Interp/loops.cpp index 4157f65e216884..d0386e3ac759ad 100644 --- a/clang/test/AST/Interp/loops.cpp +++ b/clang/test/AST/Interp/loops.cpp @@ -5,6 +5,7 @@ // ref-no-diagnostics // expected-no-diagnostics +// expected-cpp20-no-diagnostics namespace WhileLoop { constexpr int f() { @@ -165,8 +166,6 @@ namespace DoWhileLoop { static_assert(f5(true) == 8, ""); static_assert(f5(false) == 5, ""); - /// FIXME: This should be accepted in C++20 but is currently being rejected - /// because the variable declaration doesn't have an initializier. #if __cplusplus >= 202002L constexpr int f6() { int i; @@ -176,7 +175,7 @@ namespace DoWhileLoop { } while (true); return i; } - static_assert(f6() == 5, ""); // expected-cpp20-error {{not an integral constant}} + static_assert(f6() == 5, ""); #endif #if 0