OILS / mycpp / gc_alloc.h View on Github | oilshell.org

258 lines, 166 significant
1// gc_alloc.h: Functions that wrap gHeap.Allocate()
2
3#ifndef MYCPP_GC_ALLOC_H
4#define MYCPP_GC_ALLOC_H
5
6#include <string.h> // strlen
7
8#include <new> // placement new
9#include <utility> // std::forward
10
11#include "mycpp/gc_obj.h" // for RawObject, ObjHeader
12#include "mycpp/gc_slab.h" // for NewSlab()
13#include "mycpp/gc_str.h" // for NewStr()
14
15#if defined(BUMP_LEAK)
16 #include "mycpp/bump_leak_heap.h"
17extern BumpLeakHeap gHeap;
18#elif defined(MARK_SWEEP)
19 #include "mycpp/mark_sweep_heap.h"
20extern MarkSweepHeap gHeap;
21#endif
22
23#define VALIDATE_ROOTS 0
24
25#if VALIDATE_ROOTS
26static void ValidateRoot(const RawObject* obj) {
27 if (obj == nullptr) {
28 return;
29 }
30
31 ObjHeader* header = ObjHeader::FromObject(obj);
32 // log("obj %p header %p", obj, header);
33
34 switch (header->heap_tag) {
35 case HeapTag::Global:
36 case HeapTag::Opaque:
37 case HeapTag::Scanned:
38 case HeapTag::FixedSize:
39 break;
40
41 default:
42 log("root %p heap %d type %d mask %d len %d", obj, header->heap_tag,
43 header->type_tag, header->u_mask_npointers);
44 FAIL(kShouldNotGetHere);
45 break;
46 }
47}
48#endif
49
50// mycpp generates code that keeps track of the root set
51class StackRoot {
52 public:
53 StackRoot(void* root) {
54 RawObject** obj = reinterpret_cast<RawObject**>(root);
55#if VALIDATE_ROOTS
56 ValidateRoot(*obj);
57#endif
58 gHeap.PushRoot(obj);
59 }
60
61 ~StackRoot() {
62 gHeap.PopRoot();
63 }
64};
65
66// sugar for tests
67class StackRoots {
68 public:
69 // Note: void** seems logical, because these are pointers to pointers, but
70 // the C++ compiler doesn't like it.
71 StackRoots(std::initializer_list<void*> roots) {
72 n_ = roots.size();
73
74#if VALIDATE_ROOTS
75 int i = 0;
76#endif
77
78 for (auto root : roots) { // can't use roots[i]
79 RawObject** obj = reinterpret_cast<RawObject**>(root);
80#if VALIDATE_ROOTS
81 ValidateRoot(*obj);
82 i++;
83#endif
84
85 gHeap.PushRoot(obj);
86 }
87 }
88
89 ~StackRoots() {
90 for (int i = 0; i < n_; ++i) {
91 gHeap.PopRoot();
92 }
93 }
94
95 private:
96 int n_;
97};
98
99// Note:
100// - This function causes code bloat due to template expansion on hundreds of
101// types. Could switch to a GC_NEW() macro
102// - GCC generates slightly larger code if you factor out void* place and new
103// (place) T()
104//
105// Variadic templates:
106// https://eli.thegreenplace.net/2014/variadic-templates-in-c/
107template <typename T, typename... Args>
108T* Alloc(Args&&... args) {
109 // Alloc() allocates space for both a header and object and guarantees that
110 // they're adjacent in memory (so that they're at known offsets from one
111 // another). However, this means that the address that the object is
112 // constructed at is offset from the address returned by the memory allocator
113 // (by the size of the header), and therefore may not be sufficiently aligned.
114 // Here we assert that the object will be sufficiently aligned by making the
115 // equivalent assertion that zero padding would be required to align it.
116 // Note: the required padding is given by the following (according to
117 // https://en.wikipedia.org/wiki/Data_structure_alignment):
118 // `padding = -offset & (align - 1)`.
119 static_assert((-sizeof(ObjHeader) & (alignof(T) - 1)) == 0,
120 "Expected no padding");
121
122 DCHECK(gHeap.is_initialized_);
123
124 constexpr size_t num_bytes = sizeof(ObjHeader) + sizeof(T);
125#if MARK_SWEEP
126 int obj_id;
127 int pool_id;
128 void* place = gHeap.Allocate(num_bytes, &obj_id, &pool_id);
129#else
130 void* place = gHeap.Allocate(num_bytes);
131#endif
132 ObjHeader* header = new (place) ObjHeader(T::obj_header());
133#if MARK_SWEEP
134 header->obj_id = obj_id;
135 #ifndef NO_POOL_ALLOC
136 header->pool_id = pool_id;
137 #endif
138#endif
139 void* obj = header->ObjectAddress();
140 // Now that mycpp generates code to initialize every field, we should
141 // get rid of this.
142 // TODO: fix uftrace failure, maybe by upgrading, or working around
143 memset(obj, 0, sizeof(T));
144 return new (obj) T(std::forward<Args>(args)...);
145}
146
147//
148// String "Constructors". We need these because of the "flexible array"
149// pattern. I don't think "new BigStr()" can do that, and placement new would
150// require mycpp to generate 2 statements everywhere.
151//
152
153inline BigStr* NewStr(int len) {
154 if (len == 0) { // e.g. BufLineReader::readline() can use this optimization
155 return kEmptyString;
156 }
157
158 int obj_len = kStrHeaderSize + len + 1; // NUL terminator
159 const size_t num_bytes = sizeof(ObjHeader) + obj_len;
160#if MARK_SWEEP
161 int obj_id;
162 int pool_id;
163 void* place = gHeap.Allocate(num_bytes, &obj_id, &pool_id);
164#else
165 void* place = gHeap.Allocate(num_bytes);
166#endif
167 ObjHeader* header = new (place) ObjHeader(BigStr::obj_header());
168
169 auto s = new (header->ObjectAddress()) BigStr();
170
171 s->data_[len] = '\0'; // NUL terminate
172 s->len_ = len;
173 s->hash_ = 0;
174 s->is_hashed_ = 0;
175
176#if MARK_SWEEP
177 header->obj_id = obj_id;
178 #ifndef NO_POOL_ALLOC
179 header->pool_id = pool_id;
180 #endif
181#endif
182 return s;
183}
184
185// Call OverAllocatedStr() when you don't know the length of the string up
186// front, e.g. with snprintf(). CALLER IS RESPONSIBLE for calling
187// s->MaybeShrink() afterward!
188inline BigStr* OverAllocatedStr(int len) {
189 int obj_len = kStrHeaderSize + len + 1; // NUL terminator
190 const size_t num_bytes = sizeof(ObjHeader) + obj_len;
191#if MARK_SWEEP
192 int obj_id;
193 int pool_id;
194 void* place = gHeap.Allocate(num_bytes, &obj_id, &pool_id);
195#else
196 void* place = gHeap.Allocate(num_bytes);
197#endif
198 ObjHeader* header = new (place) ObjHeader(BigStr::obj_header());
199 auto s = new (header->ObjectAddress()) BigStr();
200 s->hash_ = 0;
201 s->is_hashed_ = 0;
202
203#if MARK_SWEEP
204 header->obj_id = obj_id;
205 #ifndef NO_POOL_ALLOC
206 header->pool_id = pool_id;
207 #endif
208#endif
209 return s;
210}
211
212// Copy C string into the managed heap.
213inline BigStr* StrFromC(const char* data, int len) {
214 // Optimization that could be taken out once we have SmallStr
215 if (len == 0) {
216 return kEmptyString;
217 }
218 BigStr* s = NewStr(len);
219 memcpy(s->data_, data, len);
220 DCHECK(s->data_[len] == '\0'); // should be true because Heap was zeroed
221
222 return s;
223}
224
225inline BigStr* StrFromC(const char* data) {
226 return StrFromC(data, strlen(data));
227}
228
229// Create a slab with a number of entries of a certain type.
230// Note: entries will be zero'd because we use calloc(). TODO: Consider
231// zeroing them separately.
232template <typename T>
233inline Slab<T>* NewSlab(int len) {
234 int obj_len = len * sizeof(T);
235 const size_t num_bytes = sizeof(ObjHeader) + obj_len;
236#if MARK_SWEEP
237 int obj_id;
238 int pool_id;
239 void* place = gHeap.Allocate(num_bytes, &obj_id, &pool_id);
240#else
241 void* place = gHeap.Allocate(num_bytes);
242#endif
243 ObjHeader* header = new (place) ObjHeader(Slab<T>::obj_header(len));
244 void* obj = header->ObjectAddress();
245 if (std::is_pointer<T>()) {
246 memset(obj, 0, obj_len);
247 }
248 auto slab = new (obj) Slab<T>(len);
249#if MARK_SWEEP
250 header->obj_id = obj_id;
251 #ifndef NO_POOL_ALLOC
252 header->pool_id = pool_id;
253 #endif
254#endif
255 return slab;
256}
257
258#endif // MYCPP_GC_ALLOC_H