| //===--- Pointer.h - Types for the constexpr VM -----------------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Defines the classes responsible for pointer tracking. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef LLVM_CLANG_AST_INTERP_POINTER_H |
| #define LLVM_CLANG_AST_INTERP_POINTER_H |
| |
| #include "Descriptor.h" |
| #include "Function.h" |
| #include "InitMap.h" |
| #include "InterpBlock.h" |
| #include "clang/AST/ComparisonCategories.h" |
| #include "clang/AST/Decl.h" |
| #include "clang/AST/DeclCXX.h" |
| #include "clang/AST/Expr.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| namespace clang { |
| namespace interp { |
| class Block; |
| class DeadBlock; |
| class Pointer; |
| class Context; |
| |
| class Pointer; |
| inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Pointer &P); |
| |
| struct PtrView { |
| static constexpr unsigned PastEndMark = ~0u; |
| |
| Block *Pointee; |
| unsigned Base; |
| uint64_t Offset; |
| |
| bool isZero() const { return !Pointee; } |
| bool isLive() const { return Pointee && !Pointee->isDead(); } |
| bool isDummy() const { return Pointee && Pointee->isDummy(); } |
| bool isActive() const { return isRoot() || getInlineDesc()->IsActive; } |
| bool isArrayRoot() const { return inArray() && Offset == Base; } |
| bool isElementPastEnd() const { return Offset == PastEndMark; } |
| bool isZeroSizeArray() const { return getFieldDesc()->isZeroSizeArray(); } |
| bool isMutable() const { |
| return !isRoot() && getInlineDesc()->IsFieldMutable; |
| } |
| bool inUnion() const { return getInlineDesc()->InUnion; }; |
| bool inArray() const { return getFieldDesc()->IsArray; } |
| bool inPrimitiveArray() const { return getFieldDesc()->isPrimitiveArray(); } |
| const Block *block() const { return Pointee; } |
| |
| unsigned getEvalID() { return Pointee->getEvalID(); } |
| |
| bool isRoot() const { |
| return Base == Pointee->getDescriptor()->getMetadataSize(); |
| } |
| |
| InlineDescriptor *getInlineDesc() const { |
| assert(Base != sizeof(GlobalInlineDescriptor)); |
| assert(Base <= Pointee->getSize()); |
| assert(Base >= sizeof(InlineDescriptor)); |
| return getDescriptor(Base); |
| } |
| |
| InlineDescriptor *getDescriptor(unsigned Offset) const { |
| assert(Offset != 0 && "Not a nested pointer"); |
| return reinterpret_cast<InlineDescriptor *>(Pointee->rawData() + Offset) - |
| 1; |
| } |
| |
| const Descriptor *getFieldDesc() const { |
| if (isRoot()) |
| return Pointee->getDescriptor(); |
| return getInlineDesc()->Desc; |
| } |
| |
| const Descriptor *getDeclDesc() const { return Pointee->getDescriptor(); } |
| |
| size_t elemSize() const { return getFieldDesc()->getElemSize(); } |
| |
| [[nodiscard]] PtrView narrow() const { |
| // Null pointers cannot be narrowed. |
| if (isZero() || isUnknownSizeArray()) |
| return *this; |
| |
| if (inArray()) { |
| // Pointer is one past end - magic offset marks that. |
| if (isOnePastEnd()) |
| return PtrView{Pointee, Base, PastEndMark}; |
| |
| if (Offset != Base) { |
| // If we're pointing to a primitive array element, there's nothing to |
| // do. |
| if (inPrimitiveArray()) |
| return *this; |
| // Pointer is to a composite array element - enter it. |
| return PtrView{Pointee, static_cast<unsigned>(Offset), Offset}; |
| } |
| } |
| // Otherwise, we're pointing to a non-array element or |
| // are already narrowed to a composite array element. Nothing to do. |
| return *this; |
| } |
| |
| [[nodiscard]] PtrView expand() const { |
| if (isElementPastEnd()) { |
| // Revert to an outer one-past-end pointer. |
| unsigned Adjust; |
| if (inPrimitiveArray()) |
| Adjust = sizeof(InitMapPtr); |
| else |
| Adjust = sizeof(InlineDescriptor); |
| return PtrView{Pointee, Base, Base + getSize() + Adjust}; |
| } |
| |
| // Do not step out of array elements. |
| if (Base != Offset) |
| return *this; |
| |
| if (isRoot()) |
| return PtrView{Pointee, Base, Base}; |
| |
| // Step into the containing array, if inside one. |
| unsigned Next = Base - getInlineDesc()->Offset; |
| const Descriptor *Desc = |
| (Next == Pointee->getDescriptor()->getMetadataSize()) |
| ? getDeclDesc() |
| : getDescriptor(Next)->Desc; |
| if (!Desc->IsArray) |
| return *this; |
| return PtrView{Pointee, Next, Offset}; |
| } |
| |
| [[nodiscard]] PtrView getArray() const { |
| assert(Offset != Base && "not an array element"); |
| return PtrView{Pointee, Base, Base}; |
| } |
| |
| const Record *getRecord() const { return getFieldDesc()->ElemRecord; } |
| const Record *getElemRecord() const { |
| const Descriptor *ElemDesc = getFieldDesc()->ElemDesc; |
| return ElemDesc ? ElemDesc->ElemRecord : nullptr; |
| } |
| const FieldDecl *getField() const { return getFieldDesc()->asFieldDecl(); } |
| |
| bool isField() const { |
| return !isZero() && !isRoot() && getFieldDesc()->asDecl(); |
| } |
| |
| bool isBaseClass() const { return isField() && getInlineDesc()->IsBase; } |
| bool isVirtualBaseClass() const { |
| return isField() && getInlineDesc()->IsVirtualBase; |
| } |
| bool isUnknownSizeArray() const { |
| return getFieldDesc()->isUnknownSizeArray(); |
| } |
| |
| bool isPastEnd() const { return Offset > Pointee->getSize(); } |
| |
| unsigned getOffset() const { |
| assert(Offset != PastEndMark); |
| |
| unsigned Adjust = 0; |
| if (Offset != Base) { |
| if (getFieldDesc()->ElemDesc) |
| Adjust = sizeof(InlineDescriptor); |
| else |
| Adjust = sizeof(InitMapPtr); |
| } |
| return Offset - Base - Adjust; |
| } |
| size_t getSize() const { return getFieldDesc()->getSize(); } |
| |
| bool isOnePastEnd() const { |
| if (!Pointee) |
| return false; |
| |
| if (isUnknownSizeArray()) |
| return false; |
| return isPastEnd() || (getSize() == getOffset()); |
| } |
| |
| PtrView atIndex(unsigned Idx) const { |
| unsigned Off = Idx * elemSize(); |
| if (getFieldDesc()->ElemDesc) |
| Off += sizeof(InlineDescriptor); |
| else |
| Off += sizeof(InitMapPtr); |
| return PtrView{Pointee, Base, Base + Off}; |
| } |
| |
| int64_t getIndex() const { |
| if (isZero()) |
| return 0; |
| // narrow()ed element in a composite array. |
| if (Base > sizeof(InlineDescriptor) && Base == Offset) |
| return 0; |
| |
| if (auto ElemSize = elemSize()) |
| return getOffset() / ElemSize; |
| return 0; |
| } |
| |
| unsigned getNumElems() const { return getSize() / elemSize(); } |
| |
| bool isArrayElement() const { |
| if (inArray() && Base != Offset) |
| return true; |
| |
| // Might be a narrow()'ed element in a composite array. |
| // Check the inline descriptor. |
| if (Base >= sizeof(InlineDescriptor) && getInlineDesc()->IsArrayElement) |
| return true; |
| |
| return false; |
| } |
| |
| template <typename T> T &deref() const { |
| assert(isLive() && "Invalid pointer"); |
| assert(Pointee); |
| |
| if (isArrayRoot()) |
| return *reinterpret_cast<T *>(Pointee->rawData() + Base + |
| sizeof(InitMapPtr)); |
| |
| return *reinterpret_cast<T *>(Pointee->rawData() + Offset); |
| } |
| |
| template <typename T> T &elem(unsigned I) const { |
| assert(isLive() && "Invalid pointer"); |
| assert(Pointee); |
| assert(getFieldDesc()->isPrimitiveArray()); |
| assert(I < getFieldDesc()->getNumElems()); |
| |
| unsigned ElemByteOffset = I * getFieldDesc()->getElemSize(); |
| unsigned ReadOffset = Base + sizeof(InitMapPtr) + ElemByteOffset; |
| assert(ReadOffset + sizeof(T) <= Pointee->getDescriptor()->getAllocSize()); |
| |
| return *reinterpret_cast<T *>(Pointee->rawData() + ReadOffset); |
| } |
| |
| [[nodiscard]] PtrView getBase() const { |
| unsigned NewBase = Base - getInlineDesc()->Offset; |
| return PtrView{Pointee, NewBase, NewBase}; |
| } |
| |
| [[nodiscard]] PtrView atField(unsigned Offset) const { |
| unsigned F = this->Offset + Offset; |
| return PtrView{Pointee, F, F}; |
| } |
| |
| QualType getType() const { |
| if (isRoot() && Base == Offset) { |
| // If this pointer points to the root of a declaration, try to consult |
| // the ValueDecl directly, since that has a type with more information, |
| // e.g. the correct ElaboratedTypeKeyword. |
| if (const ValueDecl *VD = getDeclDesc()->asValueDecl()) |
| return VD->getType(); |
| return getDeclDesc()->getType(); |
| } |
| if (inPrimitiveArray() && Offset != Base) { |
| // Unfortunately, complex and vector types are not array types in clang, |
| // but they are for us. |
| if (const auto *AT = getFieldDesc()->getType()->getAsArrayTypeUnsafe()) |
| return AT->getElementType(); |
| if (const auto *CT = getFieldDesc()->getType()->getAs<ComplexType>()) |
| return CT->getElementType(); |
| if (const auto *CT = getFieldDesc()->getType()->getAs<VectorType>()) |
| return CT->getElementType(); |
| } |
| |
| return getFieldDesc()->getType(); |
| } |
| |
| bool isInitialized() const { |
| if (isRoot() && Base == sizeof(GlobalInlineDescriptor) && Offset == Base) { |
| const auto &GD = Pointee->getBlockDesc<GlobalInlineDescriptor>(); |
| return GD.InitState == GlobalInitState::Initialized; |
| } |
| |
| assert(Pointee && "Cannot check if null pointer was initialized"); |
| const Descriptor *Desc = getFieldDesc(); |
| assert(Desc); |
| if (Desc->isPrimitiveArray()) |
| return isElementInitialized(getIndex()); |
| |
| if (Base == 0) |
| return true; |
| // Field has its bit in an inline descriptor. |
| return getInlineDesc()->IsInitialized; |
| } |
| |
| void initializeElement(unsigned Index) const; |
| bool allElementsInitialized() const; |
| bool isElementInitialized(unsigned Index) const; |
| InitMapPtr &getInitMap() const { |
| return *reinterpret_cast<InitMapPtr *>(Pointee->rawData() + Base); |
| } |
| void initialize() const; |
| void activate() const; |
| |
| void setLifeState(Lifetime L) const; |
| Lifetime getLifetime() const; |
| void startLifetime() const { setLifeState(Lifetime::Started); } |
| void endLifetime() const { setLifeState(Lifetime::Ended); } |
| |
| bool operator==(const PtrView &Other) const { |
| return Other.Pointee == Pointee && Base == Other.Base && |
| Offset == Other.Offset; |
| } |
| |
| bool operator!=(const PtrView &Other) const { return !(Other == *this); } |
| }; |
| |
| struct BlockPointer { |
| /// The block the pointer is pointing to. |
| Block *Pointee; |
| /// Start of the current subfield. |
| unsigned Base; |
| /// Previous link in the pointer chain. |
| Pointer *Prev; |
| /// Next link in the pointer chain. |
| Pointer *Next; |
| }; |
| |
| struct IntPointer { |
| const Type *Ty; |
| uint64_t Value; |
| |
| std::optional<IntPointer> atOffset(const Context &Ctx, unsigned Offset) const; |
| IntPointer baseCast(const Context &Ctx, unsigned BaseOffset) const; |
| |
| QualType getPointeeType() const { |
| if (!Ty) |
| return QualType(); |
| |
| QualType QT(Ty, 0); |
| if (QT->isPointerOrReferenceType()) |
| QT = QT->getPointeeType(); |
| else if (QT->isArrayType()) |
| QT = QT->getAsArrayTypeUnsafe()->getElementType(); |
| |
| return QT.IgnoreParens(); |
| } |
| }; |
| |
| struct FunctionPointer { |
| const Function *Func; |
| }; |
| |
| struct TypeidPointer { |
| const Type *TypePtr; |
| const Type *TypeInfoType; |
| }; |
| |
| enum class Storage { Int, Block, Fn, Typeid }; |
| |
| /// A pointer to a memory block, live or dead. |
| /// |
| /// 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. |
| /// |
| /// \verbatim |
| /// Pointee Offset |
| /// │ │ |
| /// │ │ |
| /// ▼ ▼ |
| /// ┌───────┬────────────┬─────────┬────────────────────────────┐ |
| /// │ Block │ InlineDesc │ InitMap │ Actual Data │ |
| /// └───────┴────────────┴─────────┴────────────────────────────┘ |
| /// ▲ |
| /// │ |
| /// │ |
| /// Base |
| /// \endverbatim |
| class Pointer { |
| public: |
| Pointer() : StorageKind(Storage::Int), Int{nullptr, 0} {} |
| Pointer(IntPointer &&IntPtr) |
| : StorageKind(Storage::Int), Int(std::move(IntPtr)) {} |
| Pointer(Block *B); |
| Pointer(Block *B, uint64_t BaseAndOffset); |
| Pointer(const Pointer &P); |
| Pointer(Pointer &&P); |
| Pointer(uint64_t Address, const Type *Ty, uint64_t Offset = 0) |
| : Offset(Offset), StorageKind(Storage::Int), Int{Ty, Address} {} |
| Pointer(const Function *F, uint64_t Offset = 0) |
| : Offset(Offset), StorageKind(Storage::Fn), Fn{F} {} |
| Pointer(const Type *TypePtr, const Type *TypeInfoType, uint64_t Offset = 0) |
| : Offset(Offset), StorageKind(Storage::Typeid) { |
| Typeid.TypePtr = TypePtr; |
| Typeid.TypeInfoType = TypeInfoType; |
| } |
| |
| Pointer(Block *Pointee, unsigned Base, uint64_t Offset); |
| explicit Pointer(PtrView V) : Pointer(V.Pointee, V.Base, V.Offset) {} |
| ~Pointer(); |
| |
| Pointer &operator=(const Pointer &P); |
| Pointer &operator=(Pointer &&P); |
| |
| /// Equality operators are just for tests. |
| bool operator==(const Pointer &P) const { |
| if (P.StorageKind != StorageKind) |
| return false; |
| if (isIntegralPointer()) |
| return P.Int.Value == Int.Value && P.Int.Ty == Int.Ty && |
| P.Offset == Offset; |
| |
| if (isFunctionPointer()) |
| return P.Fn.Func == Fn.Func && P.Offset == Offset; |
| |
| return P.view() == view(); |
| } |
| |
| bool operator!=(const Pointer &P) const { return !(P == *this); } |
| |
| /// Converts the pointer to an APValue. |
| APValue toAPValue(const ASTContext &ASTCtx) const; |
| |
| /// Converts the pointer to a string usable in diagnostics. |
| std::string toDiagnosticString(const ASTContext &Ctx) const; |
| |
| uint64_t getIntegerRepresentation() const { |
| if (isIntegralPointer()) |
| return Int.Value + (Offset * elemSize()); |
| if (isFunctionPointer()) |
| return reinterpret_cast<uint64_t>(Fn.Func) + Offset; |
| return reinterpret_cast<uint64_t>(BS.Pointee) + Offset; |
| } |
| |
| PtrView view() const { |
| assert(isBlockPointer()); |
| return PtrView{BS.Pointee, BS.Base, Offset}; |
| } |
| |
| /// Converts the pointer to an APValue that is an rvalue. |
| std::optional<APValue> toRValue(const Context &Ctx, |
| QualType ResultType) const; |
| |
| /// Offsets a pointer inside an array. |
| [[nodiscard]] Pointer atIndex(uint64_t Idx) const { |
| if (isIntegralPointer()) |
| return Pointer(Int.Value, Int.Ty, Idx); |
| if (isFunctionPointer()) |
| return Pointer(Fn.Func, Idx); |
| |
| return Pointer(view().atIndex(Idx)); |
| } |
| |
| /// Creates a pointer to a field. |
| [[nodiscard]] Pointer atField(unsigned Off) const { |
| return Pointer(view().atField(Off)); |
| } |
| |
| /// Subtract the given offset from the current Base and Offset |
| /// of the pointer. |
| [[nodiscard]] Pointer atFieldSub(unsigned Off) const { |
| assert(Offset >= Off); |
| unsigned O = Offset - Off; |
| return Pointer(BS.Pointee, O, O); |
| } |
| |
| /// Restricts the scope of an array element pointer. |
| [[nodiscard]] Pointer narrow() const { |
| if (!isBlockPointer()) |
| return *this; |
| return Pointer(view().narrow()); |
| } |
| |
| /// Expands a pointer to the containing array, undoing narrowing. |
| [[nodiscard]] Pointer expand() const { |
| if (!isBlockPointer()) |
| return *this; |
| return Pointer(view().expand()); |
| } |
| |
| /// Checks if the pointer is null. |
| bool isZero() const { |
| switch (StorageKind) { |
| case Storage::Int: |
| return Int.Value == 0 && Offset == 0; |
| case Storage::Block: |
| return BS.Pointee == nullptr; |
| case Storage::Fn: |
| return !Fn.Func; |
| case Storage::Typeid: |
| return false; |
| } |
| llvm_unreachable("Unknown clang::interp::Storage enum"); |
| } |
| /// Checks if the pointer is live. |
| bool isLive() const { |
| if (!isBlockPointer()) |
| return true; |
| return view().isLive(); |
| } |
| /// Checks if the item is a field in an object. |
| bool isField() const { |
| if (!isBlockPointer()) |
| return false; |
| |
| return view().isField(); |
| } |
| |
| /// Accessor for information about the declaration site. |
| const Descriptor *getDeclDesc() const { |
| if (!isBlockPointer()) |
| return nullptr; |
| |
| assert(isBlockPointer()); |
| assert(BS.Pointee); |
| return BS.Pointee->Desc; |
| } |
| SourceLocation getDeclLoc() const { return getDeclDesc()->getLocation(); } |
| |
| /// Returns the expression or declaration the pointer has been created for. |
| DeclTy getSource() const { |
| if (isBlockPointer()) |
| return getDeclDesc()->getSource(); |
| if (isFunctionPointer()) { |
| const Function *F = Fn.Func; |
| return F ? F->getDecl() : DeclTy(); |
| } |
| llvm_unreachable("Unsupported pointer type in getSource()"); |
| return DeclTy(); |
| } |
| |
| /// Returns a pointer to the object of which this pointer is a field. |
| [[nodiscard]] Pointer getBase() const { return Pointer(view().getBase()); } |
| /// Returns the parent array. |
| [[nodiscard]] Pointer getArray() const { return Pointer(view().getArray()); } |
| |
| /// Accessors for information about the innermost field. |
| const Descriptor *getFieldDesc() const { |
| if (isIntegralPointer()) |
| return nullptr; |
| |
| if (isRoot()) |
| return getDeclDesc(); |
| return getInlineDesc()->Desc; |
| } |
| |
| /// Returns the type of the innermost field. |
| QualType getType() const { |
| switch (StorageKind) { |
| case Storage::Int: |
| return Int.getPointeeType(); |
| case Storage::Block: |
| return view().getType(); |
| case Storage::Fn: |
| return Fn.Func->getDecl()->getType(); |
| case Storage::Typeid: |
| return QualType(Typeid.TypeInfoType, 0); |
| } |
| llvm_unreachable("Unhandled StorageKind"); |
| } |
| |
| const VarDecl *getRootVarDecl() const; |
| |
| [[nodiscard]] Pointer getDeclPtr() const { return Pointer(BS.Pointee); } |
| |
| /// Returns the element size of the innermost field. |
| size_t elemSize() const { |
| if (isIntegralPointer()) { |
| // FIXME: Remove this and handle int ptrs specially? |
| return 1; |
| } |
| |
| return view().elemSize(); |
| } |
| /// Returns the total size of the innermost field. |
| size_t getSize() const { |
| assert(isBlockPointer()); |
| return getFieldDesc()->getSize(); |
| } |
| |
| /// Returns the offset into an array. |
| unsigned getOffset() const { |
| assert(Offset != PtrView::PastEndMark && "invalid offset"); |
| return view().getOffset(); |
| } |
| |
| /// Whether this array refers to an array, but not |
| /// to the first element. |
| bool isArrayRoot() const { return view().isArrayRoot(); } |
| |
| /// Checks if the innermost field is an array. |
| bool inArray() const { |
| if (isBlockPointer()) |
| return view().inArray(); |
| return false; |
| } |
| bool inUnion() const { |
| if (isBlockPointer() && BS.Base >= sizeof(InlineDescriptor)) |
| return view().inUnion(); |
| return false; |
| }; |
| |
| /// Checks if the structure is a primitive array. |
| bool inPrimitiveArray() const { |
| if (isBlockPointer()) |
| return view().inPrimitiveArray(); |
| return false; |
| } |
| /// Checks if the structure is an array of unknown size. |
| bool isUnknownSizeArray() const { |
| if (!isBlockPointer()) |
| return false; |
| return getFieldDesc()->isUnknownSizeArray(); |
| } |
| /// Checks if the pointer points to an array. |
| bool isArrayElement() const { |
| if (!isBlockPointer()) |
| return false; |
| |
| return view().isArrayElement(); |
| } |
| /// Pointer points directly to a block. |
| bool isRoot() const { |
| if (isZero() || !isBlockPointer()) |
| return true; |
| return view().isRoot(); |
| } |
| /// If this pointer has an InlineDescriptor we can use to initialize. |
| bool canBeInitialized() const { |
| if (!isBlockPointer()) |
| return false; |
| |
| return BS.Pointee && BS.Base > 0; |
| } |
| |
| [[nodiscard]] const BlockPointer &asBlockPointer() const { |
| assert(isBlockPointer()); |
| return BS; |
| } |
| [[nodiscard]] const IntPointer &asIntPointer() const { |
| assert(isIntegralPointer()); |
| return Int; |
| } |
| [[nodiscard]] const FunctionPointer &asFunctionPointer() const { |
| assert(isFunctionPointer()); |
| return Fn; |
| } |
| [[nodiscard]] const TypeidPointer &asTypeidPointer() const { |
| assert(isTypeidPointer()); |
| return Typeid; |
| } |
| |
| bool isBlockPointer() const { return StorageKind == Storage::Block; } |
| bool isIntegralPointer() const { return StorageKind == Storage::Int; } |
| bool isFunctionPointer() const { return StorageKind == Storage::Fn; } |
| bool isTypeidPointer() const { return StorageKind == Storage::Typeid; } |
| |
| /// Returns the record descriptor of a class. |
| const Record *getRecord() const { |
| if (!isBlockPointer()) |
| return nullptr; |
| return view().getRecord(); |
| } |
| /// Returns the element record type, if this is a non-primive array. |
| const Record *getElemRecord() const { return view().getElemRecord(); } |
| /// Returns the field information. |
| const FieldDecl *getField() const { |
| if (const Descriptor *FD = getFieldDesc()) |
| return FD->asFieldDecl(); |
| return nullptr; |
| } |
| |
| /// Checks if the storage is extern. |
| bool isExtern() const { |
| if (isBlockPointer()) |
| return BS.Pointee && BS.Pointee->isExtern(); |
| return false; |
| } |
| /// Checks if the storage is static. |
| bool isStatic() const { |
| if (!isBlockPointer()) |
| return true; |
| assert(BS.Pointee); |
| return BS.Pointee->isStatic(); |
| } |
| /// Checks if the storage is temporary. |
| bool isTemporary() const { |
| if (isBlockPointer()) { |
| assert(BS.Pointee); |
| return BS.Pointee->isTemporary(); |
| } |
| return false; |
| } |
| /// Checks if the storage has been dynamically allocated. |
| bool isDynamic() const { |
| if (isBlockPointer()) { |
| assert(BS.Pointee); |
| return BS.Pointee->isDynamic(); |
| } |
| return false; |
| } |
| /// Checks if the storage is a static temporary. |
| bool isStaticTemporary() const { return isStatic() && isTemporary(); } |
| |
| /// Checks if the field is mutable. |
| bool isMutable() const { |
| if (!isBlockPointer()) |
| return false; |
| return view().isMutable(); |
| } |
| |
| bool isWeak() const { |
| if (isFunctionPointer()) { |
| if (!Fn.Func || !Fn.Func->getDecl()) |
| return false; |
| |
| return Fn.Func->getDecl()->isWeak(); |
| } |
| if (!isBlockPointer()) |
| return false; |
| |
| assert(isBlockPointer()); |
| return BS.Pointee->isWeak(); |
| } |
| /// Checks if the object is active. |
| bool isActive() const { |
| if (!isBlockPointer()) |
| return true; |
| return view().isActive(); |
| } |
| /// Checks if a structure is a base class. |
| bool isBaseClass() const { return view().isBaseClass(); } |
| bool isVirtualBaseClass() const { return view().isVirtualBaseClass(); } |
| |
| /// Checks if the pointer points to a dummy value. |
| bool isDummy() const { |
| if (!isBlockPointer()) |
| return false; |
| return view().isDummy(); |
| } |
| |
| /// Checks if an object or a subfield is mutable. |
| bool isConst() const { |
| if (isIntegralPointer()) |
| return true; |
| return isRoot() ? getDeclDesc()->IsConst : getInlineDesc()->IsConst; |
| } |
| bool isConstInMutable() const { |
| if (!isBlockPointer()) |
| return false; |
| return isRoot() ? false : getInlineDesc()->IsConstInMutable; |
| } |
| |
| /// Checks if an object or a subfield is volatile. |
| bool isVolatile() const { |
| if (!isBlockPointer()) |
| return false; |
| return isRoot() ? getDeclDesc()->IsVolatile : getInlineDesc()->IsVolatile; |
| } |
| |
| /// Returns the declaration ID. |
| UnsignedOrNone getDeclID() const { |
| if (isBlockPointer()) { |
| assert(BS.Pointee); |
| return BS.Pointee->getDeclID(); |
| } |
| return std::nullopt; |
| } |
| |
| /// Returns the byte offset from the start. |
| uint64_t getByteOffset() const { |
| if (isIntegralPointer()) |
| return Int.Value + Offset; |
| if (isTypeidPointer()) |
| return reinterpret_cast<uintptr_t>(Typeid.TypePtr) + Offset; |
| if (isOnePastEnd()) |
| return PtrView::PastEndMark; |
| return Offset; |
| } |
| |
| /// Returns the number of elements. |
| unsigned getNumElems() const { |
| if (!isBlockPointer()) |
| return ~0u; |
| return view().getNumElems(); |
| } |
| |
| const Block *block() const { return BS.Pointee; } |
| |
| /// If backed by actual data (i.e. a block pointer), return |
| /// an address to that data. |
| const std::byte *getRawAddress() const { |
| assert(isBlockPointer()); |
| return BS.Pointee->rawData() + Offset; |
| } |
| |
| /// Returns the index into an array. |
| int64_t getIndex() const { |
| if (!isBlockPointer()) |
| return getIntegerRepresentation(); |
| |
| return view().getIndex(); |
| } |
| |
| /// Checks if the index is one past end. |
| bool isOnePastEnd() const { |
| if (!isBlockPointer()) |
| return false; |
| |
| if (!BS.Pointee) |
| return false; |
| |
| if (isUnknownSizeArray()) |
| return false; |
| |
| return isPastEnd() || (getSize() == getOffset()); |
| } |
| |
| /// Checks if the pointer points past the end of the object. |
| bool isPastEnd() const { |
| if (isIntegralPointer()) |
| return false; |
| |
| return !isZero() && Offset > BS.Pointee->getSize(); |
| } |
| |
| /// Checks if the pointer is an out-of-bounds element pointer. |
| bool isElementPastEnd() const { return Offset == PtrView::PastEndMark; } |
| |
| /// Checks if the pointer is pointing to a zero-size array. |
| bool isZeroSizeArray() const { |
| if (isFunctionPointer()) |
| return false; |
| if (const auto *Desc = getFieldDesc()) |
| return Desc->isZeroSizeArray(); |
| return false; |
| } |
| |
| /// Checks whether the pointer can be dereferenced to the given PrimType. |
| bool canDeref(PrimType T) const { |
| if (const Descriptor *FieldDesc = getFieldDesc()) { |
| return (FieldDesc->isPrimitive() || FieldDesc->isPrimitiveArray()) && |
| FieldDesc->getPrimType() == T; |
| } |
| return false; |
| } |
| |
| /// Dereferences the pointer, if it's live. |
| template <typename T> T &deref() const { |
| assert(isLive() && "Invalid pointer"); |
| assert(isBlockPointer()); |
| assert(BS.Pointee); |
| assert(isDereferencable()); |
| assert(Offset + sizeof(T) <= BS.Pointee->getDescriptor()->getAllocSize()); |
| |
| return view().deref<T>(); |
| } |
| |
| /// Dereferences the element at index \p I. |
| /// This is equivalent to atIndex(I).deref<T>(). |
| template <typename T> T &elem(unsigned I) const { |
| assert(isLive() && "Invalid pointer"); |
| assert(isBlockPointer()); |
| assert(BS.Pointee); |
| assert(isDereferencable()); |
| assert(getFieldDesc()->isPrimitiveArray()); |
| assert(I < getFieldDesc()->getNumElems()); |
| |
| return view().elem<T>(I); |
| } |
| |
| bool isConstexprUnknown() const { |
| if (!isBlockPointer()) |
| return false; |
| return getDeclDesc()->IsConstexprUnknown; |
| } |
| |
| /// Whether this block can be read from at all. This is only true for |
| /// block pointers that point to a valid location inside that block. |
| bool isDereferencable() const { |
| if (!isBlockPointer()) |
| return false; |
| if (isDummy()) |
| return false; |
| if (isConstexprUnknown()) |
| return false; |
| if (isPastEnd()) |
| return false; |
| |
| return true; |
| } |
| |
| /// Initializes a field. |
| void initialize() const { |
| if (!isBlockPointer()) |
| return; |
| view().initialize(); |
| } |
| /// Initialized the given element of a primitive array. |
| void initializeElement(unsigned Index) const { |
| view().initializeElement(Index); |
| } |
| /// Initialize all elements of a primitive array at once. This can be |
| /// used in situations where we *know* we have initialized *all* elements |
| /// of a primtive array. |
| void initializeAllElements() const; |
| /// Checks if an object was initialized. |
| bool isInitialized() const; |
| /// Like isInitialized(), but for primitive arrays. |
| bool isElementInitialized(unsigned Index) const { |
| if (!isBlockPointer()) |
| return true; |
| |
| return view().isElementInitialized(Index); |
| } |
| bool allElementsInitialized() const { |
| assert(getFieldDesc()->isPrimitiveArray()); |
| assert(isArrayRoot()); |
| return view().allElementsInitialized(); |
| } |
| bool allElementsAlive() const; |
| bool isElementAlive(unsigned Index) const; |
| |
| /// Activates a field. |
| void activate() const { view().activate(); } |
| /// Deactivates an entire strurcutre. |
| void deactivate() const { |
| // TODO: this only appears in constructors, so nothing to deactivate. |
| } |
| |
| Lifetime getLifetime() const { |
| if (!isBlockPointer()) |
| return Lifetime::Started; |
| if (BS.Base < sizeof(InlineDescriptor)) |
| return Lifetime::Started; |
| |
| if (inArray() && !isArrayRoot()) { |
| InitMapPtr &IM = getInitMap(); |
| |
| if (!IM.hasInitMap()) { |
| if (IM.allInitialized()) |
| return Lifetime::Started; |
| return getArray().getLifetime(); |
| } |
| |
| return IM->isElementAlive(getIndex()) ? Lifetime::Started |
| : Lifetime::Ended; |
| } |
| |
| return getInlineDesc()->LifeState; |
| } |
| |
| /// Start the lifetime of this pointer. This works for pointer with an |
| /// InlineDescriptor as well as primitive array elements. Pointers are usually |
| /// alive by default, unless the underlying object has been allocated with |
| /// std::allocator. This function is used by std::construct_at. |
| void startLifetime() const { setLifeState(Lifetime::Started); } |
| /// Ends the lifetime of the pointer. This works for pointer with an |
| /// InlineDescriptor as well as primitive array elements. This function is |
| /// used by std::destroy_at. |
| void endLifetime() const { setLifeState(Lifetime::Ended); } |
| |
| void setLifeState(Lifetime L) const { |
| if (!isBlockPointer()) |
| return; |
| view().setLifeState(L); |
| }; |
| |
| /// Strip base casts from this Pointer. |
| /// The result is either a root pointer or something |
| /// that isn't a base class anymore. |
| [[nodiscard]] Pointer stripBaseCasts() const { |
| PtrView V = view(); |
| while (V.isBaseClass()) |
| V = V.getBase(); |
| return Pointer(V); |
| } |
| |
| /// Compare two pointers. |
| ComparisonCategoryResult compare(const Pointer &Other) const { |
| if (!hasSameBase(*this, Other)) |
| return ComparisonCategoryResult::Unordered; |
| |
| if (Offset < Other.Offset) |
| return ComparisonCategoryResult::Less; |
| if (Offset > Other.Offset) |
| return ComparisonCategoryResult::Greater; |
| |
| return ComparisonCategoryResult::Equal; |
| } |
| |
| /// Checks if two pointers are comparable. |
| static bool hasSameBase(const Pointer &A, const Pointer &B); |
| /// Checks if two pointers can be subtracted. |
| static bool hasSameArray(const Pointer &A, const Pointer &B); |
| /// Checks if both given pointers point to the same block. |
| static bool pointToSameBlock(const Pointer &A, const Pointer &B); |
| |
| static std::optional<std::pair<PtrView, PtrView>> |
| computeSplitPoint(const Pointer &A, const Pointer &B); |
| |
| /// Whether this points to a block that's been created for a "literal lvalue", |
| /// i.e. a non-MaterializeTemporaryExpr Expr. |
| bool pointsToLiteral() const; |
| bool pointsToStringLiteral() const; |
| /// Whether this points to a block created for an AddrLabelExpr. |
| bool pointsToLabel() const; |
| /// Returns the AddrLabelExpr the Pointer points to, if any. |
| const AddrLabelExpr *getPointedToLabel() const { |
| if (const Descriptor *Desc = getDeclDesc()) |
| return dyn_cast_if_present<AddrLabelExpr>(Desc->asExpr()); |
| return nullptr; |
| } |
| |
| /// Prints the pointer. |
| void print(llvm::raw_ostream &OS) const; |
| |
| /// Compute an integer that can be used to compare this pointer to |
| /// another one. This is usually NOT the same as the pointer offset |
| /// regarding the AST record layout. |
| std::optional<size_t> |
| computeOffsetForComparison(const ASTContext &ASTCtx) const; |
| |
| private: |
| friend class Block; |
| friend class DeadBlock; |
| friend class MemberPointer; |
| friend class InterpState; |
| friend class DynamicAllocator; |
| friend class Program; |
| |
| /// Returns the embedded descriptor preceding a field. |
| InlineDescriptor *getInlineDesc() const { |
| assert(isBlockPointer()); |
| assert(BS.Base != sizeof(GlobalInlineDescriptor)); |
| assert(BS.Base <= BS.Pointee->getSize()); |
| assert(BS.Base >= sizeof(InlineDescriptor)); |
| return getDescriptor(BS.Base); |
| } |
| |
| /// Returns a descriptor at a given offset. |
| InlineDescriptor *getDescriptor(unsigned Offset) const { |
| assert(Offset != 0 && "Not a nested pointer"); |
| assert(isBlockPointer()); |
| assert(!isZero()); |
| return view().getDescriptor(Offset); |
| } |
| |
| /// Returns a reference to the InitMapPtr which stores the initialization map. |
| InitMapPtr &getInitMap() const { |
| assert(isBlockPointer()); |
| assert(!isZero()); |
| return view().getInitMap(); |
| } |
| |
| /// Offset into the storage. |
| uint64_t Offset = 0; |
| |
| Storage StorageKind = Storage::Int; |
| union { |
| IntPointer Int; |
| BlockPointer BS; |
| FunctionPointer Fn; |
| TypeidPointer Typeid; |
| }; |
| }; |
| |
| inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Pointer &P) { |
| P.print(OS); |
| OS << ' '; |
| if (P.isZero()) |
| return OS; |
| |
| if (const Descriptor *D = P.getFieldDesc()) |
| D->dump(OS); |
| if (P.isArrayElement()) { |
| if (P.isOnePastEnd()) |
| OS << " one-past-the-end"; |
| else { |
| OS << ' '; |
| std::string Indices; |
| llvm::raw_string_ostream SS(Indices); |
| Pointer K = P; |
| while (K.isArrayElement()) { |
| SS << ']' << K.expand().getIndex() << '['; |
| K = K.expand().getArray(); |
| } |
| std::reverse(Indices.begin(), Indices.end()); |
| OS << Indices; |
| } |
| } else if (P.isBlockPointer() && P.isArrayRoot()) |
| OS << " arrayroot"; |
| |
| if (P.isBlockPointer() && P.block() && P.block()->isDummy()) |
| OS << " dummy"; |
| if (!P.isLive()) |
| OS << " dead"; |
| if (P.isBlockPointer() && P.isBaseClass()) |
| OS << " base-class"; |
| return OS; |
| } |
| |
| } // namespace interp |
| } // namespace clang |
| |
| #endif |