https://github.com/philnik777 updated https://github.com/llvm/llvm-project/pull/224903
>From 005721818ed263907b15b874efd7c23935b19640 Mon Sep 17 00:00:00 2001 From: Nikolas Klauser <[email protected]> Date: Sun, 20 Sep 2026 11:58:05 +0200 Subject: [PATCH 1/3] [Clang][ByteCode] Store type information inside the custom stack --- clang/lib/AST/ByteCode/FixedPoint.h | 2 +- clang/lib/AST/ByteCode/Integral.h | 2 +- clang/lib/AST/ByteCode/InterpStack.cpp | 32 ++++++------ clang/lib/AST/ByteCode/InterpStack.h | 71 +++++++++++++++++--------- clang/lib/AST/ByteCode/Pointer.h | 2 +- 5 files changed, 66 insertions(+), 43 deletions(-) diff --git a/clang/lib/AST/ByteCode/FixedPoint.h b/clang/lib/AST/ByteCode/FixedPoint.h index fcb3c79cc1097..7aab21a77c8f9 100644 --- a/clang/lib/AST/ByteCode/FixedPoint.h +++ b/clang/lib/AST/ByteCode/FixedPoint.h @@ -22,7 +22,7 @@ using APSInt = llvm::APSInt; /// Wrapper around fixed point types. class FixedPoint final { private: - llvm::APFixedPoint V; + LLVM_NO_UNIQUE_ADDRESS llvm::APFixedPoint V; public: FixedPoint(llvm::APFixedPoint &&V) : V(std::move(V)) {} diff --git a/clang/lib/AST/ByteCode/Integral.h b/clang/lib/AST/ByteCode/Integral.h index 543d7f7fd43a9..e2bde2a0c82f7 100644 --- a/clang/lib/AST/ByteCode/Integral.h +++ b/clang/lib/AST/ByteCode/Integral.h @@ -78,7 +78,6 @@ template <unsigned Bits, bool Signed> class Integral final { static_assert(std::is_trivially_copyable_v<ReprT>); template <unsigned OtherBits, bool OtherSigned> friend class Integral; - IntegralKind Kind = IntegralKind::Number; union { ReprT V; struct { @@ -90,6 +89,7 @@ template <unsigned Bits, bool Signed> class Integral final { const AddrLabelExpr *L2; } AddrLabelDiff; }; + IntegralKind Kind = IntegralKind::Number; /// Primitive representing limits. static const auto Min = std::numeric_limits<ReprT>::min(); diff --git a/clang/lib/AST/ByteCode/InterpStack.cpp b/clang/lib/AST/ByteCode/InterpStack.cpp index 839540a7912f8..b85c271931052 100644 --- a/clang/lib/AST/ByteCode/InterpStack.cpp +++ b/clang/lib/AST/ByteCode/InterpStack.cpp @@ -25,15 +25,22 @@ InterpStack::~InterpStack() { std::free(Chunk->Next); if (Chunk) std::free(Chunk); + +#if __has_cpp_attribute(no_unique_address) + TYPE_SWITCH(PrimType(), { + using Frame = StackFrame<T>; + static_assert(offsetof(Frame, type) == sizeof(Frame) - 1); + static_assert(sizeof(void *) != 8 || sizeof(Frame) == sizeof(T) || + sizeof(T) < sizeof(void *)); + }); +#endif } // We keep the last chunk around to reuse. void InterpStack::clear() { - for (PrimType Item : llvm::reverse(ItemTypes)) { - TYPE_SWITCH(Item, { this->discard<T>(); }); + while (!empty()) { + TYPE_SWITCH(getNextObjectType(), { this->discard<T>(); }); } - assert(ItemTypes.empty()); - assert(empty()); } void InterpStack::clearTo(size_t NewSize) { @@ -43,12 +50,8 @@ void InterpStack::clearTo(size_t NewSize) { return; assert(NewSize <= size()); - for (PrimType Item : llvm::reverse(ItemTypes)) { - TYPE_SWITCH(Item, { this->discard<T>(); }); - - if (size() == NewSize) - break; - } + while (size() != NewSize) + TYPE_SWITCH(getNextObjectType(), { this->discard<T>(); }); // Note: discard() above already removed the types from ItemTypes. assert(size() == NewSize); @@ -96,16 +99,13 @@ void InterpStack::shrink(size_t Size) { } void InterpStack::dump() const { - llvm::errs() << "Items: " << ItemTypes.size() << ". Size: " << size() << '\n'; - if (ItemTypes.empty()) - return; - size_t Index = 0; size_t Offset = 0; // The type of the item on the top of the stack is inserted to the back // of the vector, so the iteration has to happen backwards. - for (PrimType Item : llvm::reverse(ItemTypes)) { + while (Offset != size()) { + PrimType Item = *static_cast<PrimType *>(peekData(Offset + 1)); Offset += align(primSize(Item)); llvm::errs() << Index << '/' << Offset << ": "; @@ -122,5 +122,5 @@ void InterpStack::dump() const { void InterpStack::discardSlow() { assert(!empty()); - TYPE_SWITCH(ItemTypes.back(), { discard<T>(); }); + TYPE_SWITCH(getNextObjectType(), { discard<T>(); }); } diff --git a/clang/lib/AST/ByteCode/InterpStack.h b/clang/lib/AST/ByteCode/InterpStack.h index 2c02979ee6eec..faaab371fbf59 100644 --- a/clang/lib/AST/ByteCode/InterpStack.h +++ b/clang/lib/AST/ByteCode/InterpStack.h @@ -21,6 +21,15 @@ namespace clang { namespace interp { +template <class T> +struct datasizeof_impl { + LLVM_NO_UNIQUE_ADDRESS T v; + char first_padding_byte; +}; + +template <class T> +constexpr size_t datasizeof_v = offsetof(datasizeof_impl<T>, first_padding_byte); + /// Stack frame storing temporaries and parameters. class InterpStack final { public: @@ -29,40 +38,63 @@ class InterpStack final { /// Destroys the stack, freeing up storage. ~InterpStack(); + template <size_t N> struct Padding { + char padding[N]; + }; + + template <> struct Padding<0> {}; + + template <class T> struct alignas(void *) StackFrame { + static_assert(alignof(T) <= alignof(void *), + "Unexpected overaligned object"); + + template <class... Args> + StackFrame(Args &&...args) + : v(std::forward<Args>(args)...), type(toPrimType<T>()) {} + + static constexpr size_t getPaddingSize() { + if constexpr (sizeof(T) < sizeof(void*)) + return sizeof(void*) - datasizeof_v<T> - 1; + else if constexpr (sizeof(T) == datasizeof_v<T>) + return sizeof(void*) - 1; + else + return sizeof(T) - datasizeof_v<T> - 1; + } + + LLVM_NO_UNIQUE_ADDRESS T v; + LLVM_NO_UNIQUE_ADDRESS Padding<getPaddingSize()> padding; + PrimType type; + }; + /// Constructs a value in place on the top of the stack. template <typename T, typename... Tys> void push(Tys &&...Args) { - new (grow<aligned_size<T>()>()) T(std::forward<Tys>(Args)...); - ItemTypes.push_back(toPrimType<T>()); + using Frame = StackFrame<T>; + new (grow<sizeof(Frame)>()) Frame(std::forward<Tys>(Args)...); } /// Returns the value from the top of the stack and removes it. template <typename T> T pop() { - assert(!ItemTypes.empty()); - assert(ItemTypes.back() == toPrimType<T>()); - ItemTypes.pop_back(); + assert(getNextObjectType() == toPrimType<T>()); T *Ptr = &peekInternal<T>(); T Value = std::move(*Ptr); - shrink(aligned_size<T>()); + shrink(sizeof(StackFrame<T>)); return Value; } /// Discards the top value from the stack. template <typename T> void discard() { - assert(!ItemTypes.empty()); - assert(ItemTypes.back() == toPrimType<T>()); - ItemTypes.pop_back(); + assert(getNextObjectType() == toPrimType<T>()); T *Ptr = &peekInternal<T>(); if constexpr (!std::is_trivially_destructible_v<T>) { Ptr->~T(); } - shrink(aligned_size<T>()); + shrink(sizeof(StackFrame<T>)); } void discardSlow(); /// Returns a reference to the value on the top of the stack. template <typename T> T &peek() const { - assert(!ItemTypes.empty()); - assert(ItemTypes.back() == toPrimType<T>()); + assert(getNextObjectType() == toPrimType<T>()); return peekInternal<T>(); } @@ -88,16 +120,13 @@ class InterpStack final { void dump() const; private: - /// All stack slots are aligned to the native pointer alignment for storage. - /// The size of an object is rounded up to a pointer alignment multiple. - template <typename T> static constexpr size_t aligned_size() { - constexpr size_t PtrAlign = alignof(void *); - return ((sizeof(T) + PtrAlign - 1) / PtrAlign) * PtrAlign; + PrimType getNextObjectType() const { + return *static_cast<PrimType *>(peekData(1)); } /// Like the public peek(), but without the debug type checks. template <typename T> T &peekInternal() const { - return *reinterpret_cast<T *>(peekData(aligned_size<T>())); + return static_cast<StackFrame<T> *>(peekData(sizeof(StackFrame<T>)))->v; } /// Grows the stack to accommodate a value and returns a pointer to it. @@ -163,12 +192,6 @@ class InterpStack final { /// Total size of the stack. size_t StackSize = 0; - /// SmallVector recording the type of data we pushed into the stack. - /// We don't usually need this during normal code interpretation but - /// when aborting, we need type information to call the destructors - /// for what's left on the stack. - llvm::SmallVector<PrimType> ItemTypes; - template <typename T> static constexpr PrimType toPrimType() { if constexpr (std::is_same_v<T, Pointer>) return PT_Ptr; diff --git a/clang/lib/AST/ByteCode/Pointer.h b/clang/lib/AST/ByteCode/Pointer.h index 54e0f858b4fa0..975f1cc15aa58 100644 --- a/clang/lib/AST/ByteCode/Pointer.h +++ b/clang/lib/AST/ByteCode/Pointer.h @@ -1334,7 +1334,6 @@ class Pointer { /// Offset into the storage. uint64_t Offset = 0; - Storage StorageKind = Storage::Int; union { IntPointer Int; BlockPointer BS; @@ -1343,6 +1342,7 @@ class Pointer { StringPointer Str; OpaquePointer Opaque; }; + Storage StorageKind = Storage::Int; }; inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Pointer &P) { >From 354b68fa8a5b726fdcaf9a418397ec9c1e57b509 Mon Sep 17 00:00:00 2001 From: Nikolas Klauser <[email protected]> Date: Sun, 20 Sep 2026 12:39:47 +0200 Subject: [PATCH 2/3] Move Padding outside InterpStack --- clang/lib/AST/ByteCode/InterpStack.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/clang/lib/AST/ByteCode/InterpStack.h b/clang/lib/AST/ByteCode/InterpStack.h index faaab371fbf59..9ad1c5712d86e 100644 --- a/clang/lib/AST/ByteCode/InterpStack.h +++ b/clang/lib/AST/ByteCode/InterpStack.h @@ -30,6 +30,12 @@ struct datasizeof_impl { template <class T> constexpr size_t datasizeof_v = offsetof(datasizeof_impl<T>, first_padding_byte); +template <size_t N> struct Padding { + char padding[N]; +}; + +template <> struct Padding<0> {}; + /// Stack frame storing temporaries and parameters. class InterpStack final { public: @@ -38,12 +44,6 @@ class InterpStack final { /// Destroys the stack, freeing up storage. ~InterpStack(); - template <size_t N> struct Padding { - char padding[N]; - }; - - template <> struct Padding<0> {}; - template <class T> struct alignas(void *) StackFrame { static_assert(alignof(T) <= alignof(void *), "Unexpected overaligned object"); >From 69688e05798648cbb07f1a701d8abd885f4712ef Mon Sep 17 00:00:00 2001 From: Nikolas Klauser <[email protected]> Date: Mon, 21 Sep 2026 10:56:36 +0200 Subject: [PATCH 3/3] Address comments --- clang/lib/AST/ByteCode/Integral.h | 2 +- clang/lib/AST/ByteCode/InterpStack.cpp | 20 +++++++++-- clang/lib/AST/ByteCode/InterpStack.h | 46 ++++++++++++++++++-------- clang/lib/AST/ByteCode/Pointer.h | 14 ++++---- 4 files changed, 57 insertions(+), 25 deletions(-) diff --git a/clang/lib/AST/ByteCode/Integral.h b/clang/lib/AST/ByteCode/Integral.h index e2bde2a0c82f7..2a865a7febaa5 100644 --- a/clang/lib/AST/ByteCode/Integral.h +++ b/clang/lib/AST/ByteCode/Integral.h @@ -108,7 +108,7 @@ template <unsigned Bits, bool Signed> class Integral final { /// Constructs an integral from another integral. template <unsigned SrcBits, bool SrcSign> - explicit Integral(Integral<SrcBits, SrcSign> V) : Kind(V.Kind), V(V) {} + explicit Integral(Integral<SrcBits, SrcSign> V) : V(V), Kind(V.Kind) {} /// Pointer integral of the given kind. explicit Integral(IntegralKind Kind, const void *P, OffsetT Offset = 0) diff --git a/clang/lib/AST/ByteCode/InterpStack.cpp b/clang/lib/AST/ByteCode/InterpStack.cpp index b85c271931052..9d0ce59d5100a 100644 --- a/clang/lib/AST/ByteCode/InterpStack.cpp +++ b/clang/lib/AST/ByteCode/InterpStack.cpp @@ -27,19 +27,31 @@ InterpStack::~InterpStack() { std::free(Chunk); #if __has_cpp_attribute(no_unique_address) +#ifdef __GNUC__ +#pragma GCC diagnostic push +// Clang and GCC complain that `offsetof` isn't allowed on non-standard-layout +// types. However, it works just fine. +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#endif TYPE_SWITCH(PrimType(), { using Frame = StackFrame<T>; static_assert(offsetof(Frame, type) == sizeof(Frame) - 1); + // Currently we don't need to use extra memory to store the type information + // for any PrimType on 64 bit platforms. Nothing breaks if this changes, but + // it would result in 8 extra bytes used just for the type information. static_assert(sizeof(void *) != 8 || sizeof(Frame) == sizeof(T) || sizeof(T) < sizeof(void *)); }); +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif #endif } // We keep the last chunk around to reuse. void InterpStack::clear() { while (!empty()) { - TYPE_SWITCH(getNextObjectType(), { this->discard<T>(); }); + TYPE_SWITCH(getTopFrameType(), { this->discard<T>(); }); } } @@ -51,7 +63,7 @@ void InterpStack::clearTo(size_t NewSize) { assert(NewSize <= size()); while (size() != NewSize) - TYPE_SWITCH(getNextObjectType(), { this->discard<T>(); }); + TYPE_SWITCH(getTopFrameType(), { this->discard<T>(); }); // Note: discard() above already removed the types from ItemTypes. assert(size() == NewSize); @@ -99,6 +111,8 @@ void InterpStack::shrink(size_t Size) { } void InterpStack::dump() const { + llvm::errs() << "Size: " << size() << '\n'; + size_t Index = 0; size_t Offset = 0; @@ -122,5 +136,5 @@ void InterpStack::dump() const { void InterpStack::discardSlow() { assert(!empty()); - TYPE_SWITCH(getNextObjectType(), { discard<T>(); }); + TYPE_SWITCH(getTopFrameType(), { discard<T>(); }); } diff --git a/clang/lib/AST/ByteCode/InterpStack.h b/clang/lib/AST/ByteCode/InterpStack.h index 9ad1c5712d86e..04696ef38fba0 100644 --- a/clang/lib/AST/ByteCode/InterpStack.h +++ b/clang/lib/AST/ByteCode/InterpStack.h @@ -21,15 +21,28 @@ namespace clang { namespace interp { -template <class T> -struct datasizeof_impl { +template <class T> struct datasizeof_impl { LLVM_NO_UNIQUE_ADDRESS T v; char first_padding_byte; }; +#ifdef __GNUC__ +#pragma GCC diagnostic push +// Clang and GCC complain that `offsetof` isn't allowed on non-standard-layout +// types. However, it works just fine. +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#endif + +// `datasizeof_v` is the size of a struct ignoring padding bytes. template <class T> -constexpr size_t datasizeof_v = offsetof(datasizeof_impl<T>, first_padding_byte); +constexpr size_t datasizeof_v = + offsetof(datasizeof_impl<T>, first_padding_byte); +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +// Insert artificial padding into a struct to position members at a specific +// offset. Needs to be used with LLVM_NO_UNIQUE_ADDRESS template <size_t N> struct Padding { char padding[N]; }; @@ -44,25 +57,30 @@ class InterpStack final { /// Destroys the stack, freeing up storage. ~InterpStack(); + // StackFrame<T> is the actual object stored on the InterpStack for a given T. + // It automatically aligns the objects appropriately and saves the type of the + // object in the last byte of the allocation to allow retrieving type + // information when unwinding the stack. If available, it uses tail padding in + // the objects to store the type information to reduce the memory footprint. template <class T> struct alignas(void *) StackFrame { static_assert(alignof(T) <= alignof(void *), "Unexpected overaligned object"); template <class... Args> StackFrame(Args &&...args) - : v(std::forward<Args>(args)...), type(toPrimType<T>()) {} + : V(std::forward<Args>(args)...), type(toPrimType<T>()) {} static constexpr size_t getPaddingSize() { - if constexpr (sizeof(T) < sizeof(void*)) - return sizeof(void*) - datasizeof_v<T> - 1; + if constexpr (sizeof(T) < sizeof(void *)) + return sizeof(void *) - datasizeof_v<T> - 1; else if constexpr (sizeof(T) == datasizeof_v<T>) - return sizeof(void*) - 1; + return sizeof(void *) - 1; else return sizeof(T) - datasizeof_v<T> - 1; } - LLVM_NO_UNIQUE_ADDRESS T v; - LLVM_NO_UNIQUE_ADDRESS Padding<getPaddingSize()> padding; + LLVM_NO_UNIQUE_ADDRESS T V; + LLVM_NO_UNIQUE_ADDRESS Padding<getPaddingSize()> P; PrimType type; }; @@ -74,7 +92,7 @@ class InterpStack final { /// Returns the value from the top of the stack and removes it. template <typename T> T pop() { - assert(getNextObjectType() == toPrimType<T>()); + assert(getTopFrameType() == toPrimType<T>()); T *Ptr = &peekInternal<T>(); T Value = std::move(*Ptr); shrink(sizeof(StackFrame<T>)); @@ -83,7 +101,7 @@ class InterpStack final { /// Discards the top value from the stack. template <typename T> void discard() { - assert(getNextObjectType() == toPrimType<T>()); + assert(getTopFrameType() == toPrimType<T>()); T *Ptr = &peekInternal<T>(); if constexpr (!std::is_trivially_destructible_v<T>) { Ptr->~T(); @@ -94,7 +112,7 @@ class InterpStack final { /// Returns a reference to the value on the top of the stack. template <typename T> T &peek() const { - assert(getNextObjectType() == toPrimType<T>()); + assert(getTopFrameType() == toPrimType<T>()); return peekInternal<T>(); } @@ -120,13 +138,13 @@ class InterpStack final { void dump() const; private: - PrimType getNextObjectType() const { + PrimType getTopFrameType() const { return *static_cast<PrimType *>(peekData(1)); } /// Like the public peek(), but without the debug type checks. template <typename T> T &peekInternal() const { - return static_cast<StackFrame<T> *>(peekData(sizeof(StackFrame<T>)))->v; + return static_cast<StackFrame<T> *>(peekData(sizeof(StackFrame<T>)))->V; } /// Grows the stack to accommodate a value and returns a pointer to it. diff --git a/clang/lib/AST/ByteCode/Pointer.h b/clang/lib/AST/ByteCode/Pointer.h index 975f1cc15aa58..db7dfbfae5385 100644 --- a/clang/lib/AST/ByteCode/Pointer.h +++ b/clang/lib/AST/ByteCode/Pointer.h @@ -535,26 +535,26 @@ enum class Storage { Int, Block, Fn, Typeid, String, Opaque }; /// \endverbatim class Pointer { public: - Pointer() : StorageKind(Storage::Int), Int{nullptr, 0} {} + Pointer() : Int{nullptr, 0}, StorageKind(Storage::Int) {} Pointer(IntPointer &&IntPtr) - : StorageKind(Storage::Int), Int(std::move(IntPtr)) {} + : Int(std::move(IntPtr)), StorageKind(Storage::Int) {} 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} {} + : Offset(Offset), Int{Ty, Address}, StorageKind(Storage::Int) {} Pointer(const Function *F, uint64_t Offset = 0) - : Offset(Offset), StorageKind(Storage::Fn), Fn{F} {} + : Offset(Offset), Fn{F}, StorageKind(Storage::Fn) {} Pointer(const Type *TypePtr, const Type *TypeInfoType, uint64_t Offset = 0) : Offset(Offset), StorageKind(Storage::Typeid) { Typeid.TypePtr = TypePtr; Typeid.TypeInfoType = TypeInfoType; } Pointer(const Expr *Base, unsigned Id) - : Offset(0), StorageKind(Storage::String), Str{Base, Id} {} + : Offset(0), Str{Base, Id}, StorageKind(Storage::String) {} Pointer(StringPointer Str, uint64_t Offset = 0) - : Offset(Offset), StorageKind(Storage::String), Str(Str) {} + : Offset(Offset), Str(Str), StorageKind(Storage::String) {} Pointer(DeclOrExpr DOE, bool ConstexprUnknown = false) : Offset(0), StorageKind(Storage::Opaque) { @@ -564,7 +564,7 @@ class Pointer { Opaque.PathLength = 0; } Pointer(OpaquePointer OP, uint64_t Offset = 0) - : Offset(Offset), StorageKind(Storage::Opaque), Opaque(OP) {} + : Offset(Offset), Opaque(OP), StorageKind(Storage::Opaque) {} Pointer(Block *Pointee, unsigned Base, uint64_t Offset); explicit Pointer(PtrView V) : Pointer(V.Pointee, V.Base, V.Offset) {} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
