Skip to content

Commit 4163b68

Browse files
committed
Make AllocatedValue allocate memory more dynamically based on the needs of the type.
1 parent 7025444 commit 4163b68

File tree

1 file changed

+133
-29
lines changed

1 file changed

+133
-29
lines changed

lib/Interpreter/Value.cpp

Lines changed: 133 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -32,34 +32,90 @@
3232

3333
namespace {
3434

35-
///\brief The allocation starts with this layout; it is followed by the
36-
/// value's object at m_Payload. This class does not inherit from
37-
/// llvm::RefCountedBase because deallocation cannot use this type but must
38-
/// free the character array.
35+
///\brief The layout/usage of memory allocated by AllocatedValue::Create
36+
/// is dependent on the the type of object it is representing. If the type
37+
/// has a non-trival destructor then the memory base will point to either
38+
/// a full Destructable struct (when it is also an array whose size > 1), or
39+
/// a single DtorFunc_t value when the object is a single instance or an array
40+
/// with only 1 element.
41+
///
42+
/// If neither of these are true (a POD or array to one), or directly follwing
43+
/// the prior two cases, is the memory location that can be used to placement
44+
/// new an AllocatedValue instance.
45+
///
46+
/// The AllocatedValue instance ontains a single union for reference counting
47+
/// and flags of how the layout exists in memory.
48+
///
49+
/// On 32-bit the reference count will max out a bit before 16.8 million.
50+
/// 64 bit limit is still extremely high (2^56)-1
51+
///
52+
///
53+
/// General layout of memory allocated by AllocatedValue::Create
54+
///
55+
/// +---- Destructable ---+ <- Optional, allocated for arrays
56+
/// | | TestFlag(kHasElements)
57+
/// | Size |
58+
/// | Elements |
59+
/// | Dtor | <- Can exist without prior Destructable members
60+
/// | | TestFlag(kHasDestructor)
61+
/// | |
62+
/// +---AllocatedValue ---+ <- This object
63+
/// | | TestFlag(kHasElements)
64+
/// | union { |
65+
/// | size_t m_Count |
66+
/// | char m_Bytes[8] | <- m_Bytes[7] is reserved for AllocatedValue
67+
/// | }; | & ValueExtractionSynthesizer writing info.
68+
/// | |
69+
/// +~~ Client Payload ~~~+ <- Returned from AllocatedValue::Create
70+
/// | |
71+
/// | |
72+
/// +---------------------+
73+
///
74+
/// It may be possible to ignore the caching of this info all together and
75+
/// just figure out what to do in AllocatedValue::Release by passing the
76+
/// QualType and Interpreter, but am a bit weary of this for two reasons:
77+
///
78+
/// 1. FIXME: There is still a bad lifetime cycle where a Value referencing
79+
/// an Interpreter that has been destroyed is possible.
80+
/// 2. How that might interact with decl unloading, and the possibility of
81+
/// a destructor no longer being defined after a cling::Value has been
82+
/// created to represent a fuller state of the type.
3983

4084
class AllocatedValue {
4185
public:
4286
typedef void (*DtorFunc_t)(void*);
4387

4488
private:
45-
///\brief The destructor function.
46-
DtorFunc_t m_DtorFunc;
4789

48-
///\brief The size of the allocation (for arrays)
49-
size_t m_AllocSize;
90+
struct Destructable {
91+
///\brief Size to skip to get the next element in the array.
92+
size_t Size;
5093

51-
///\brief The number of elements in the array
52-
size_t m_NElements;
94+
///\brief Total number of elements in the array.
95+
size_t Elements;
96+
97+
///\brief The destructor function.
98+
DtorFunc_t Dtor;
99+
};
53100

54101
///\brief The reference count - once 0, this object will be deallocated.
55102
/// Hopefully 2^55 - 1 references should be enough as the last byte is
56103
/// used for flag storage.
57-
enum { SizeBytes = sizeof(size_t), ConstructedByte = SizeBytes - 1 };
104+
enum {
105+
SizeBytes = sizeof(size_t),
106+
FlagsByte = SizeBytes - 1,
107+
108+
kConstructorRan = 1, // Used by ValueExtractionSynthesizer
109+
kHasDestructor = 2,
110+
kHasElements = 4
111+
};
58112
union {
59113
size_t m_Count;
60114
char m_Bytes[SizeBytes];
61115
};
62116

117+
bool TestFlags(unsigned F) const { return (m_Bytes[FlagsByte] & F) == F; }
118+
63119
size_t UpdateRefCount(int Amount) {
64120
// Bit shift the bytes used in m_Bytes for representing an integer
65121
// respecting endian-ness and which of those bytes are significant.
@@ -73,32 +129,68 @@ namespace {
73129
return RC.m_Count;
74130
}
75131

76-
static AllocatedValue* FromPtr(void* Ptr) {
77-
return reinterpret_cast<AllocatedValue*>(reinterpret_cast<char*>(Ptr) -
78-
sizeof(AllocatedValue));
132+
template <class T = AllocatedValue> static T* FromPtr(void* Ptr) {
133+
return reinterpret_cast<T*>(reinterpret_cast<char*>(Ptr) - sizeof(T));
79134
}
80135

81-
///\brief Initialize the storage management part of the allocated object.
82-
/// The allocator is referencing it, thus initialize m_RefCnt with 1.
83-
///\param [in] dtorFunc - the function to be called before deallocation.
84-
AllocatedValue(size_t Size, size_t NElem, DtorFunc_t Dtor) :
85-
m_DtorFunc(Dtor), m_AllocSize(Size), m_NElements(NElem) {
136+
///\brief Initialize the reference count and flag management.
137+
/// Everything else is in a Destructable object before -this-
138+
AllocatedValue(char Info) {
86139
m_Count = 0;
140+
m_Bytes[FlagsByte] = Info;
87141
UpdateRefCount(1);
88142
}
89143

90144
public:
91-
///\brief Create an AllocatedValue whose lifetime is reference counted.
145+
146+
///\brief Create an AllocatedValue.
92147
/// \returns The address of the writeable client data.
93148
static void* Create(size_t Size, size_t NElem, DtorFunc_t Dtor) {
94-
char* Alloc = new char[sizeof(AllocatedValue) + Size];
95-
AllocatedValue* AV = new (Alloc) AllocatedValue(Size, NElem, Dtor);
149+
size_t AllocSize = sizeof(AllocatedValue) + Size;
150+
size_t ExtraSize = 0;
151+
char Flags = 0;
152+
if (Dtor) {
153+
// Only need the elements data for arrays larger than 1.
154+
if (NElem > 1) {
155+
Flags |= kHasElements;
156+
ExtraSize = sizeof(Destructable);
157+
} else
158+
ExtraSize = sizeof(DtorFunc_t);
159+
160+
Flags |= kHasDestructor;
161+
AllocSize += ExtraSize;
162+
}
96163

164+
char* Alloc = new char[AllocSize];
165+
166+
if (Dtor) {
167+
// Move the Buffer ptr to where AllocatedValue begins
168+
Alloc += ExtraSize;
169+
// Now back up to get the location of the Destructable members
170+
// This is so writing to Destructable::Dtor will work when only
171+
// additional space for DtorFunc_t was written.
172+
Destructable* DS = FromPtr<Destructable>(Alloc);
173+
if (NElem > 1) {
174+
DS->Elements = NElem;
175+
// Hopefully there won't be any issues with object alignemnt of arrays
176+
// If there are, that would have to be dealt with here and write the
177+
// proper skip amount in DS->Size.
178+
DS->Size = Size / NElem;
179+
}
180+
DS->Dtor = Dtor;
181+
}
182+
183+
AllocatedValue* AV = new (Alloc) AllocatedValue(Flags);
184+
185+
// Just make sure alignment is as expected.
186+
static_assert(std::is_standard_layout<Destructable>::value, "padding");
187+
static_assert((sizeof(Destructable) % SizeBytes) == 0, "alignment");
97188
static_assert(std::is_standard_layout<AllocatedValue>::value, "padding");
98189
static_assert(sizeof(m_Count) == sizeof(m_Bytes), "union padding");
99190
static_assert(((offsetof(AllocatedValue, m_Count) + sizeof(m_Count)) %
100191
SizeBytes) == 0,
101192
"Buffer may not be machine aligned");
193+
// Validate the byte ValueExtractionSynthesizer will write too
102194
assert(&Alloc[sizeof(AllocatedValue) - 1] == &AV->m_Bytes[SizeBytes - 1]
103195
&& "Padded AllocatedValue");
104196

@@ -115,15 +207,27 @@ namespace {
115207
static void Release(void* Ptr) {
116208
AllocatedValue* AV = FromPtr(Ptr);
117209
if (AV->UpdateRefCount(-1) == 0) {
118-
if (AV->m_DtorFunc && AV->m_Bytes[ConstructedByte]) {
119-
assert(AV->m_NElements && "No elements!");
210+
if (AV->TestFlags(kConstructorRan|kHasDestructor)) {
211+
Destructable* Dtor = FromPtr<Destructable>(AV);
212+
size_t Elements = 1, Size = 0;
213+
if (AV->TestFlags(kHasElements)) {
214+
Elements = Dtor->Elements;
215+
Size = Dtor->Size;
216+
}
120217
char* Payload = reinterpret_cast<char*>(Ptr);
121-
const auto Skip = AV->m_AllocSize / AV->m_NElements;
122-
while (AV->m_NElements-- != 0)
123-
(*AV->m_DtorFunc)(Payload + AV->m_NElements * Skip);
218+
while (Elements-- != 0)
219+
(*Dtor->Dtor)(Payload + Elements * Size);
124220
}
125-
this->~AllocatedValue();
126-
delete [] reinterpret_cast<char*>(AV);
221+
222+
// Subtract the amount that was over-allocated from the base of -this-
223+
char* Allocated = reinterpret_cast<char*>(AV);
224+
if (AV->TestFlags(kHasElements))
225+
Allocated -= sizeof(Destructable);
226+
else if (AV->TestFlags(kHasDestructor))
227+
Allocated -= sizeof(DtorFunc_t);
228+
229+
AV->~AllocatedValue();
230+
delete [] Allocated;
127231
}
128232
}
129233
};

0 commit comments

Comments
 (0)