Author: Timm Bäder Date: 2023-01-18T16:55:02+01:00 New Revision: 865094746e2ad759aee52df2a291420d4fdd1e02
URL: https://github.com/llvm/llvm-project/commit/865094746e2ad759aee52df2a291420d4fdd1e02 DIFF: https://github.com/llvm/llvm-project/commit/865094746e2ad759aee52df2a291420d4fdd1e02.diff LOG: [clang][Interp] Track initialization state of local variables Use an InlineDescriptor per local variable to track whether locals have been initialized or not. This way we can support uninitialized local variables in constexpr functions. Differential Revision: https://reviews.llvm.org/D135750 Added: Modified: clang/lib/AST/Interp/ByteCodeExprGen.cpp clang/lib/AST/Interp/ByteCodeStmtGen.cpp clang/lib/AST/Interp/Context.cpp clang/lib/AST/Interp/Descriptor.cpp clang/lib/AST/Interp/Descriptor.h clang/lib/AST/Interp/EvalEmitter.cpp clang/lib/AST/Interp/Interp.h clang/lib/AST/Interp/InterpBlock.h clang/lib/AST/Interp/InterpFrame.cpp clang/lib/AST/Interp/InterpFrame.h clang/lib/AST/Interp/Pointer.cpp clang/lib/AST/Interp/Pointer.h clang/lib/AST/Interp/Program.cpp clang/lib/AST/Interp/Program.h clang/test/AST/Interp/cxx20.cpp clang/test/AST/Interp/literals.cpp clang/test/AST/Interp/loops.cpp Removed: ################################################################################ diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp index 0bbab0cc06655..f4bf162a1454c 100644 --- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp @@ -802,7 +802,11 @@ unsigned ByteCodeExprGen<Emitter>::allocateLocalPrimitive(DeclTy &&Src, PrimType Ty, bool IsConst, bool IsExtended) { - Descriptor *D = P.createDescriptor(Src, Ty, IsConst, Src.is<const Expr *>()); + // FIXME: There are cases where Src.is<Expr*>() is wrong, e.g. + // (int){12} in C. Consider using Expr::isTemporaryObject() instead + // or isa<MaterializeTemporaryExpr>(). + Descriptor *D = P.createDescriptor(Src, Ty, Descriptor::InlineDescMD, IsConst, + Src.is<const Expr *>()); Scope::Local Local = this->createLocal(D); if (auto *VD = dyn_cast_or_null<ValueDecl>(Src.dyn_cast<const Decl *>())) Locals.insert({VD, Local}); @@ -831,7 +835,8 @@ ByteCodeExprGen<Emitter>::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 44997857f6a59..60506a759f45c 100644 --- a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp @@ -402,24 +402,26 @@ bool ByteCodeStmtGen<Emitter>::visitVarDecl(const VarDecl *VD) { if (std::optional<PrimType> 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<Emitter> 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<unsigned> Offset = this->allocateLocal(VD)) + if (std::optional<unsigned> 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 6fd645b87d22b..16471242f3282 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 f645063acdd01..5575fc1e2a6e7 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<T>, 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<size_t>(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<size_t>(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<size_t>(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 b2f50815fe848..ae39d38d81849 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<InterpSize>; + 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 12854c1c04374..7b6e5561d978a 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<bool> 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<InlineDescriptor *>(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<Pointer>(reinterpret_cast<Block *>(It->second.get())); + Block *B = reinterpret_cast<Block *>(It->second.get()); + S.Stk.push<Pointer>(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<Block *>(It->second.get()); - S.Stk.push<T>(*reinterpret_cast<T *>(B + 1)); + S.Stk.push<T>(*reinterpret_cast<T *>(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<Block *>(It->second.get()); - *reinterpret_cast<T *>(B + 1) = S.Stk.pop<T>(); + *reinterpret_cast<T *>(B->data()) = S.Stk.pop<T>(); + InlineDescriptor &Desc = *reinterpret_cast<InlineDescriptor *>(B->rawData()); + Desc.IsInitialized = true; + return true; } diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h index e252c54c88731..07515548a06ba 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 <PrimType Name, class T = typename PrimConv<Name>::T> bool GetLocal(InterpState &S, CodePtr OpPC, uint32_t I) { - S.Stk.push<T>(S.Current->getLocal<T>(I)); + const Pointer &Ptr = S.Current->getLocalPointer(I); + if (!CheckLoad(S, OpPC, Ptr)) + return false; + S.Stk.push<T>(Ptr.deref<T>()); return true; } @@ -912,6 +915,8 @@ bool Store(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.peek<Pointer>(); if (!CheckStore(S, OpPC, Ptr)) return false; + if (!Ptr.isRoot()) + Ptr.initialize(); Ptr.deref<T>() = Value; return true; } @@ -922,6 +927,8 @@ bool StorePop(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop<Pointer>(); if (!CheckStore(S, OpPC, Ptr)) return false; + if (!Ptr.isRoot()) + Ptr.initialize(); Ptr.deref<T>() = Value; return true; } @@ -932,6 +939,8 @@ bool StoreBitField(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.peek<Pointer>(); if (!CheckStore(S, OpPC, Ptr)) return false; + if (!Ptr.isRoot()) + Ptr.initialize(); if (auto *FD = Ptr.getField()) { Ptr.deref<T>() = Value.truncate(FD->getBitWidthValue(S.getCtx())); } else { @@ -946,6 +955,8 @@ bool StoreBitFieldPop(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop<Pointer>(); if (!CheckStore(S, OpPC, Ptr)) return false; + if (!Ptr.isRoot()) + Ptr.initialize(); if (auto *FD = Ptr.getField()) { Ptr.deref<T>() = Value.truncate(FD->getBitWidthValue(S.getCtx())); } else { diff --git a/clang/lib/AST/Interp/InterpBlock.h b/clang/lib/AST/Interp/InterpBlock.h index b8b5b39f55d44..f790c50a91237 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<unsigned> getDeclID() const { return DeclID; } /// Returns a pointer to the stored data. - char *data() { return reinterpret_cast<char *>(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<char *>(this) + sizeof(Block); } + const char *rawData() const { + return reinterpret_cast<const char *>(this) + sizeof(Block); + } /// Returns a view over the data. template <typename T> @@ -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 619c56fe4bf7a..9acfbe3700e98 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<char *>(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<char *>(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<Pointer>(0); } - - if (unsigned FrameSize = Func->getFrameSize()) { - Locals = std::make_unique<char[]>(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<Block *>(Locals.get() + Offset - sizeof(Block))); + return Pointer(reinterpret_cast<Block *>(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 bcb45a1b9174f..bfa02c90ebec4 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 <typename T> void setLocal(unsigned Offset, const T &Value) { localRef<T>(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 <typename T> const T &getParam(unsigned Offset) const { @@ -128,7 +129,7 @@ class InterpFrame final : public Frame { /// Returns an offset to a local. template <typename T> T &localRef(unsigned Offset) const { - return *reinterpret_cast<T *>(Locals.get() + Offset); + return getLocalPointer(Offset).deref<T>(); } /// 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<InlineDescriptor *>(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 b849acb52f0c1..fd8c98fae0396 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 38341ae2322d9..44c485681af54 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 <typename T> T &deref() const { assert(isLive() && "Invalid pointer"); - return *reinterpret_cast<T *>(Pointee->data() + Offset); + return *reinterpret_cast<T *>(Pointee->rawData() + Offset); } /// Dereferences a primitive element. template <typename T> T &elem(unsigned I) const { - return reinterpret_cast<T *>(Pointee->data())[I]; + return reinterpret_cast<T *>(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<InlineDescriptor *>(Pointee->data() + Offset) - 1; + return reinterpret_cast<InlineDescriptor *>(Pointee->rawData() + Offset) - + 1; } /// Returns a reference to the pointer which stores the initialization map. InitMap *&getInitMap() const { - return *reinterpret_cast<InitMap **>(Pointee->data() + Base); + return *reinterpret_cast<InitMap **>(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 3e1d0bc6ad204..067f6d90b6c11 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<unsigned> Program::createGlobal(const DeclTy &D, QualType Ty, const bool IsConst = Ty.isConstQualified(); const bool IsTemporary = D.dyn_cast<const Expr *>(); 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<PrimType> 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<RecordType>()) { 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<unsigned>::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<unsigned>::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<AtomicType>()) { 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<ComplexType>()) { 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 c5591c58e457c..f49ca6db13e11 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 38f05844a1ec9..5ec3e364b7ad4 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 dc99f83fb1df8..cd5c9da9bda51 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 4157f65e21688..d0386e3ac759a 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 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits