[clang] [Clang][ByteCode] Store type information inside the custom stack (PR #224903)
Nikolas Klauser via cfe-commits
cfe-commits at lists.llvm.org
Mon Sep 21 01:56:52 PDT 2026
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 <nikolasklauser at berlin.de>
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 <nikolasklauser at berlin.de>
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 <nikolasklauser at berlin.de>
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) {}
More information about the cfe-commits
mailing list