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

256 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 // mycpp doesn't generated constructors that initialize every field
141 memset(obj, 0, sizeof(T));
142 return new (obj) T(std::forward<Args>(args)...);
143}
144
145//
146// String "Constructors". We need these because of the "flexible array"
147// pattern. I don't think "new BigStr()" can do that, and placement new would
148// require mycpp to generate 2 statements everywhere.
149//
150
151inline BigStr* NewStr(int len) {
152 if (len == 0) { // e.g. BufLineReader::readline() can use this optimization
153 return kEmptyString;
154 }
155
156 int obj_len = kStrHeaderSize + len + 1; // NUL terminator
157 const size_t num_bytes = sizeof(ObjHeader) + obj_len;
158#if MARK_SWEEP
159 int obj_id;
160 int pool_id;
161 void* place = gHeap.Allocate(num_bytes, &obj_id, &pool_id);
162#else
163 void* place = gHeap.Allocate(num_bytes);
164#endif
165 ObjHeader* header = new (place) ObjHeader(BigStr::obj_header());
166
167 auto s = new (header->ObjectAddress()) BigStr();
168
169 s->data_[len] = '\0'; // NUL terminate
170 s->len_ = len;
171 s->hash_ = 0;
172 s->is_hashed_ = 0;
173
174#if MARK_SWEEP
175 header->obj_id = obj_id;
176 #ifndef NO_POOL_ALLOC
177 header->pool_id = pool_id;
178 #endif
179#endif
180 return s;
181}
182
183// Call OverAllocatedStr() when you don't know the length of the string up
184// front, e.g. with snprintf(). CALLER IS RESPONSIBLE for calling
185// s->MaybeShrink() afterward!
186inline BigStr* OverAllocatedStr(int len) {
187 int obj_len = kStrHeaderSize + len + 1; // NUL terminator
188 const size_t num_bytes = sizeof(ObjHeader) + obj_len;
189#if MARK_SWEEP
190 int obj_id;
191 int pool_id;
192 void* place = gHeap.Allocate(num_bytes, &obj_id, &pool_id);
193#else
194 void* place = gHeap.Allocate(num_bytes);
195#endif
196 ObjHeader* header = new (place) ObjHeader(BigStr::obj_header());
197 auto s = new (header->ObjectAddress()) BigStr();
198 s->hash_ = 0;
199 s->is_hashed_ = 0;
200
201#if MARK_SWEEP
202 header->obj_id = obj_id;
203 #ifndef NO_POOL_ALLOC
204 header->pool_id = pool_id;
205 #endif
206#endif
207 return s;
208}
209
210// Copy C string into the managed heap.
211inline BigStr* StrFromC(const char* data, int len) {
212 // Optimization that could be taken out once we have SmallStr
213 if (len == 0) {
214 return kEmptyString;
215 }
216 BigStr* s = NewStr(len);
217 memcpy(s->data_, data, len);
218 DCHECK(s->data_[len] == '\0'); // should be true because Heap was zeroed
219
220 return s;
221}
222
223inline BigStr* StrFromC(const char* data) {
224 return StrFromC(data, strlen(data));
225}
226
227// Create a slab with a number of entries of a certain type.
228// Note: entries will be zero'd because we use calloc(). TODO: Consider
229// zeroing them separately.
230template <typename T>
231inline Slab<T>* NewSlab(int len) {
232 int obj_len = len * sizeof(T);
233 const size_t num_bytes = sizeof(ObjHeader) + obj_len;
234#if MARK_SWEEP
235 int obj_id;
236 int pool_id;
237 void* place = gHeap.Allocate(num_bytes, &obj_id, &pool_id);
238#else
239 void* place = gHeap.Allocate(num_bytes);
240#endif
241 ObjHeader* header = new (place) ObjHeader(Slab<T>::obj_header(len));
242 void* obj = header->ObjectAddress();
243 if (std::is_pointer<T>()) {
244 memset(obj, 0, obj_len);
245 }
246 auto slab = new (obj) Slab<T>(len);
247#if MARK_SWEEP
248 header->obj_id = obj_id;
249 #ifndef NO_POOL_ALLOC
250 header->pool_id = pool_id;
251 #endif
252#endif
253 return slab;
254}
255
256#endif // MYCPP_GC_ALLOC_H