/home/andy/git/oilshell/oil/mycpp/gc_heap.h
Line | Count | Source (jump to first uncovered line) |
1 | | // mycpp/gc_heap.h |
2 | | // |
3 | | // A garbage collected heap that looks like statically typed Python: Str, |
4 | | // List<T>, Dict<K, V>. |
5 | | |
6 | | #ifndef GC_HEAP_H |
7 | | #define GC_HEAP_H |
8 | | |
9 | | #include <cassert> // assert() |
10 | | #include <cstddef> // max_align_t |
11 | | #include <cstdint> // max_align_t |
12 | | #include <cstdlib> // malloc |
13 | | #include <cstring> // memcpy |
14 | | #include <initializer_list> |
15 | | #include <new> // placement new |
16 | | #include <utility> // std::forward |
17 | | |
18 | | #include "mycpp/common.h" |
19 | | |
20 | | // Design Notes: |
21 | | |
22 | | // It's a semi-space collector using the Cheney algorithm. (Later we may add a |
23 | | // "large object space", managed by mark-and-sweep after each copy step.) |
24 | | |
25 | | // Influences / Prior art: |
26 | | // |
27 | | // - OCaml / ZINC machine - statically typed, everything is a pointer (no value |
28 | | // types). |
29 | | // - femtolisp - Cheney collector; used to bootstrap Juliia. |
30 | | // - Other: ES shell, Lua, Python and especially Python dicts. |
31 | | |
32 | | // Design: |
33 | | // |
34 | | // - Graph of application types: |
35 | | // - Str* |
36 | | // - List<T>* |
37 | | // - Dict<K, V>* |
38 | | // - User-defined classes, which may have a vtable pointer. |
39 | | // - Graph of GC nodes: everything is an Obj* with an 8 byte header |
40 | | // - 1 byte heap tag, for Cheney |
41 | | // - 1 byte type tag for Zephyr ASDL unions |
42 | | // - 2 byte / 16-bit field bit mask for following pointers on user-defined |
43 | | // classes and List / Dict "headers" |
44 | | // - 4 bytes length for Cheney to determine how much to copy. |
45 | | // |
46 | | // Operations that resize: |
47 | | // |
48 | | // - List::append() and extend() can realloc |
49 | | // - Dict::set() can realloc and rehash (TODO) |
50 | | // |
51 | | // Important Types: |
52 | | // |
53 | | // - Slab<T> to trace the object graph. Slab<int> is opaque, but Slab<T*> |
54 | | // requires tracing. |
55 | | // - A List<T> is a fixed-size structure, with int fields and a pointer |
56 | | // to a single Slab<T> (the items). |
57 | | // - A Dict<K, V> is also fixed-size, with pointers to 3 slabs: the index |
58 | | // Slab<int>, the keys Slab<K>, and the index Slab<V>. |
59 | | // |
60 | | // "Ghost" layout types: |
61 | | // |
62 | | // - LayoutFixed - for walking up to 16 fields of a user-defined type. |
63 | | // - LayoutForwarded - for storing the forwarding pointer that the Cheney |
64 | | // algorithm uses. |
65 | | // |
66 | | // Stack rooting API: |
67 | | // |
68 | | // StackRoots _roots({&mystr, &mydict, &mylist}); |
69 | | // |
70 | | // This pushes local variables onto the global data structure managed by the |
71 | | // GC. |
72 | | |
73 | | // TODO: Dicts should actually use hashing! Test computational complexity. |
74 | | |
75 | | // Memory allocation APIs: |
76 | | // |
77 | | // - gc_heap::Alloc<Foo>(x) |
78 | | // The typed public API. An alternative to new Foo(x). mycpp/ASDL should |
79 | | // generate these calls. |
80 | | // - AllocStr(length), StrFromC(), NewList, NewDict: gc_heap::Alloc() doesn't |
81 | | // work |
82 | | // for these types for various reasons |
83 | | // - Heap::Allocate() |
84 | | // The untyped internal API. For AllocStr() and NewSlab(). |
85 | | // - malloc() -- for say yajl to use. Manually deallocated. |
86 | | // - new/delete -- shouldn't be in Oil? |
87 | | |
88 | | // Slab Sizing with 8-byte slab header |
89 | | // |
90 | | // 16 - 8 = 8 = 1 eight-byte or 2 four-byte elements |
91 | | // 32 - 8 = 24 = 3 eight-byte or 6 four-byte elements |
92 | | // 64 - 8 = 56 = 7 eight-byte or 14 four-byte elements |
93 | | |
94 | | // #defines for degbugging: |
95 | | // |
96 | | // GC_EVERY_ALLOC: Collect() on every Allocate(). Exposes many bugs! |
97 | | // GC_PROTECT: Use mprotect() |
98 | | // GC_VERBOSE: Log when we collect |
99 | | // GC_STATS: Collect more stats. TODO: Rename this? |
100 | | |
101 | | // Obj::heap_tag_ values. They're odd numbers to distinguish them from vtable |
102 | | // pointers. |
103 | | namespace Tag { |
104 | | const int Forwarded = 1; // For the Cheney algorithm. |
105 | | const int Global = 3; // Neither copy nor scan. |
106 | | const int Opaque = 5; // Copy but don't scan. List<int> and Str |
107 | | const int FixedSize = 7; // Fixed size headers: consult field_mask_ |
108 | | const int Scanned = 9; // Copy AND scan for non-NULL pointers. |
109 | | } // namespace Tag |
110 | | |
111 | | // Silly definition for passing types like GlobalList<T, N> and initializer |
112 | | // lists like {1, 2, 3} to macros |
113 | | |
114 | | #define COMMA , |
115 | | |
116 | | namespace gc_heap { |
117 | | |
118 | | template <class T> |
119 | | class List; |
120 | | |
121 | | constexpr int kMask = alignof(max_align_t) - 1; // e.g. 15 or 7 |
122 | | |
123 | | // Align returned pointers to the worst case of 8 bytes (64-bit pointers) |
124 | 4.60k | inline size_t aligned(size_t n) { |
125 | | // https://stackoverflow.com/questions/2022179/c-quick-calculation-of-next-multiple-of-4 |
126 | | // return (n + 7) & ~7; |
127 | | |
128 | 4.60k | return (n + kMask) & ~kMask; |
129 | 4.60k | } |
130 | | |
131 | | class Obj; |
132 | | |
133 | | const int kMaxRoots = 4 * 1024; // related to C stack size |
134 | | |
135 | | class Space { |
136 | | public: |
137 | 63 | Space() { |
138 | 63 | } |
139 | | void Init(int space_size); |
140 | | |
141 | | void Free(); |
142 | | |
143 | 465 | void Clear() { |
144 | | // Slab scanning relies on 0 bytes (nullptr). e.g. for a List<Token*>*. |
145 | | // Note: I noticed that memset() of say 400 MiB is pretty expensive. Does |
146 | | // it makes sense to zero the slabs instead? |
147 | 465 | #ifndef NO_GC_HACK |
148 | | // When not collecting, we need a huge 400 MiB heap. Try to save startup |
149 | | // time by not doing this. |
150 | 465 | memset(begin_, 0, size_); |
151 | 465 | #endif |
152 | 465 | } |
153 | | |
154 | | #if GC_PROTECT |
155 | | // To maintain invariants |
156 | | void Protect(); |
157 | | void Unprotect(); |
158 | | #endif |
159 | | #if GC_STATS |
160 | | void AssertValid(void* p) { |
161 | | if (begin_ <= p && p < begin_ + size_) { |
162 | | return; |
163 | | } |
164 | | log("p = %p isn't between %p and %p", begin_, begin_ + size_); |
165 | | InvalidCodePath(); |
166 | | } |
167 | | #endif |
168 | | |
169 | | char* begin_; |
170 | | int size_; // number of bytes |
171 | | }; |
172 | | |
173 | | class Heap { |
174 | | public: |
175 | 31 | Heap() { // default constructor does nothing -- relies on zero initialization |
176 | 31 | } |
177 | | |
178 | | // Real initialization with the initial heap size. The heap grows with |
179 | | // allocations. |
180 | 40 | void Init(int space_size) { |
181 | | // malloc() and memset() |
182 | 40 | from_space_.Init(space_size); |
183 | 40 | to_space_.Init(space_size); |
184 | | |
185 | 40 | free_ = from_space_.begin_; // where we allocate from |
186 | 40 | limit_ = free_ + space_size; |
187 | | |
188 | 40 | roots_top_ = 0; |
189 | | |
190 | | #if GC_STATS |
191 | | num_collections_ = 0; |
192 | | num_heap_growths_ = 0; |
193 | | num_forced_growths_ = 0; |
194 | | num_live_objs_ = 0; |
195 | | #endif |
196 | 40 | } |
197 | | |
198 | 3.24k | void* Bump(int n) { |
199 | 3.24k | char* p = free_; |
200 | 3.24k | free_ += n; |
201 | | #if GC_STATS |
202 | | num_live_objs_++; |
203 | | #endif |
204 | 3.24k | return p; |
205 | 3.24k | } |
206 | | |
207 | 3.24k | void* Allocate(int num_bytes) { |
208 | 3.24k | int n = aligned(num_bytes); |
209 | | // log("n = %d, p = %p", n, p); |
210 | | |
211 | | #if GC_EVERY_ALLOC |
212 | | Collect(); // force collection to find problems early |
213 | | #endif |
214 | | |
215 | 3.24k | if (free_ + n <= limit_) { // Common case: we have space for it. |
216 | 2.90k | return Bump(n); |
217 | 2.90k | } |
218 | | |
219 | | #if GC_STATS |
220 | | // log("GC free_ %p, from_space_ %p, space_size_ %d", free_, from_space_, |
221 | | // space_size_); |
222 | | #endif |
223 | | |
224 | 344 | Collect(); // Try to free some space. |
225 | | |
226 | | // log("after GC: from begin %p, free_ %p, n %d, limit_ %p", |
227 | | // from_space_.begin_, free_, n, limit_); |
228 | | |
229 | 344 | if (free_ + n <= limit_) { // Now we have space for it. |
230 | 333 | return Bump(n); |
231 | 333 | } |
232 | | |
233 | | // It's STILL too small. Resize to_space_ to ENSURE that allocation will |
234 | | // succeed, copy the heap to it, then allocate the object. |
235 | 11 | int multiple = 2; |
236 | 11 | while (from_space_.size_ + n > to_space_.size_ * multiple) { |
237 | 0 | multiple *= 2; |
238 | 0 | } |
239 | | // log("=== FORCED by multiple of %d", multiple); |
240 | 11 | to_space_.Free(); |
241 | 11 | to_space_.Init(to_space_.size_ * multiple); |
242 | | |
243 | 11 | Collect(); |
244 | | #if GC_STATS |
245 | | num_forced_growths_++; |
246 | | #endif |
247 | | |
248 | 11 | return Bump(n); |
249 | 344 | } |
250 | | |
251 | 364 | void Swap() { |
252 | | // Swap spaces for next collection. |
253 | 364 | char* tmp = from_space_.begin_; |
254 | 364 | from_space_.begin_ = to_space_.begin_; |
255 | 364 | to_space_.begin_ = tmp; |
256 | | |
257 | 364 | int tmp2 = from_space_.size_; |
258 | 364 | from_space_.size_ = to_space_.size_; |
259 | 364 | to_space_.size_ = tmp2; |
260 | 364 | } |
261 | | |
262 | 11.7k | void PushRoot(Obj** p) { |
263 | | // log("PushRoot %d", roots_top_); |
264 | 11.7k | roots_[roots_top_++] = p; |
265 | | // TODO: This should be like a malloc() failure? |
266 | 11.7k | assert(roots_top_ < kMaxRoots); |
267 | 11.7k | } |
268 | | |
269 | 11.7k | void PopRoot() { |
270 | 11.7k | roots_top_--; |
271 | | // log("PopRoot %d", roots_top_); |
272 | 11.7k | } |
273 | | |
274 | | Obj* Relocate(Obj* obj, Obj* header); |
275 | | |
276 | | // mutates free_ and other variables |
277 | | void Collect(); |
278 | | |
279 | | #if GC_STATS |
280 | | void Report() { |
281 | | log("-----"); |
282 | | log("num collections = %d", num_collections_); |
283 | | log("num heap growths = %d", num_heap_growths_); |
284 | | log("num forced heap growths = %d", num_forced_growths_); |
285 | | log("num live objects = %d", num_live_objs_); |
286 | | |
287 | | log("from_space_ %p", from_space_.begin_); |
288 | | log("to_space %p", to_space_.begin_); |
289 | | log("-----"); |
290 | | } |
291 | | #endif |
292 | | |
293 | | Space from_space_; // space we allocate from |
294 | | Space to_space_; // space that the collector copies to |
295 | | |
296 | | char* free_; // next place to allocate, from_space_ <= free_ < limit_ |
297 | | char* limit_; // end of space we're allocating from |
298 | | |
299 | | // Stack roots. The obvious data structure is a linked list, but an array |
300 | | // has better locality. |
301 | | // |
302 | | // femtolisp uses a global pointer to dynamically-allocated growable array, |
303 | | // with initial N_STACK = 262144! Kind of arbitrary. |
304 | | |
305 | | int roots_top_; |
306 | | Obj** roots_[kMaxRoots]; // These are pointers to Obj* pointers |
307 | | |
308 | | #if GC_STATS |
309 | | int num_collections_; |
310 | | int num_heap_growths_; |
311 | | int num_forced_growths_; // when a single allocation is too big |
312 | | int num_live_objs_; |
313 | | #endif |
314 | | }; |
315 | | |
316 | | // The heap is a (compound) global variable. Notes: |
317 | | // - The default constructor does nothing, to avoid initialization order |
318 | | // problems. |
319 | | // - For some applications, this can be thread_local rather than global. |
320 | | extern Heap gHeap; |
321 | | |
322 | | class StackRoots { |
323 | | public: |
324 | 8.32k | StackRoots(std::initializer_list<void*> roots) { |
325 | 8.32k | n_ = roots.size(); |
326 | 11.4k | for (auto root : roots) { // can't use roots[i] |
327 | 11.4k | gHeap.PushRoot(reinterpret_cast<Obj**>(root)); |
328 | 11.4k | } |
329 | 8.32k | } |
330 | | |
331 | 8.32k | ~StackRoots() { |
332 | | // TODO: optimize this |
333 | 19.8k | for (int i = 0; i < n_; ++i) { |
334 | 11.4k | gHeap.PopRoot(); |
335 | 11.4k | } |
336 | 8.32k | } |
337 | | |
338 | | private: |
339 | | int n_; |
340 | | }; |
341 | | |
342 | | #if GC_STATS |
343 | | void ShowFixedChildren(Obj* obj); |
344 | | #endif |
345 | | |
346 | | template <typename T> |
347 | | class Local { |
348 | | // We can garbage collect at any Alloc() invocation, so we need a level of |
349 | | // indirection for locals / pointers directly on the stack. Pointers on the |
350 | | // heap are updated by the Cheney GC algorithm. |
351 | | |
352 | | public: |
353 | | Local() : raw_pointer_(nullptr) { |
354 | | } |
355 | | |
356 | | // IMPLICIT conversion. No 'explicit'. |
357 | 0 | Local(T* raw_pointer) : raw_pointer_(raw_pointer) { |
358 | | // TODO(Jesse): Does this get called? |
359 | | // Is this NotImplemented() or InvalidCodePath() ?? |
360 | 0 | assert(0); |
361 | | // gHeap.PushRoot(this); |
362 | 0 | } |
363 | | |
364 | | // Copy constructor, e.g. f(mylocal) where f(Local<T> param); |
365 | | Local(const Local& other) : raw_pointer_(other.raw_pointer_) { |
366 | | // TODO(Jesse): Does this get called? |
367 | | // Is this NotImplemented() or InvalidCodePath() ?? |
368 | | assert(0); |
369 | | // gHeap.PushRoot(this); |
370 | | } |
371 | | |
372 | | void operator=(const Local& other) { // Assignment operator |
373 | | raw_pointer_ = other.raw_pointer_; |
374 | | |
375 | | // Note: we could try to avoid PushRoot() as an optimization. Example: |
376 | | // |
377 | | // Local<Str> a = StrFromC("foo"); |
378 | | // Local<Str> b; |
379 | | // b = a; // invokes operator=, it's already a root |
380 | | // |
381 | | // Local<Str> c = myfunc(); // T* constructor takes care of PushRoot() |
382 | | |
383 | | // log("operator="); |
384 | | |
385 | | // However the problem is that then we'll have an unbalanced PopRoot(). |
386 | | // So we keep it for now. |
387 | | gHeap.PushRoot(this); |
388 | | } |
389 | | |
390 | 0 | ~Local() { |
391 | 0 | gHeap.PopRoot(); |
392 | 0 | } |
393 | | |
394 | | // This cast operator overload allows: |
395 | | // |
396 | | // Local<Str> s = StrFromC("foo"); |
397 | | // node->mystr = s; // convert from Local to raw |
398 | | // |
399 | | // As well as: |
400 | | // |
401 | | // Local<List<Str*>> strings = Alloc<List<Str*>>(); |
402 | | // strings->append(StrFromC("foo")); // convert from local to raw |
403 | | // |
404 | | // The heap should NOT have locals! List<Str> and not List<Local<Str>>. |
405 | | // |
406 | | // Note: This could be considered dangerous if we don't maintain |
407 | | // discipline. |
408 | | // |
409 | | // https://www.informit.com/articles/article.aspx?p=31529&seqNum=7 |
410 | | // |
411 | | // Putting .get() at the call site in mycpp is more explicit. The |
412 | | // readability of the generated code is important! |
413 | | #if 1 |
414 | 0 | operator T*() { |
415 | 0 | return raw_pointer_; |
416 | 0 | } |
417 | | #endif |
418 | | |
419 | | // Allows ref->field and ref->method() |
420 | 0 | T* operator->() const { |
421 | | // log("operator->"); |
422 | 0 | return raw_pointer_; |
423 | 0 | } Unexecuted instantiation: _ZNK7gc_heap5LocalI4BaseEptEv Unexecuted instantiation: _ZNK7gc_heap5LocalI7DerivedEptEv |
424 | | T* Get() const { |
425 | | return raw_pointer_; |
426 | | } |
427 | | // called by the garbage collector when moved to a new location! |
428 | | void Update(T* moved) { |
429 | | raw_pointer_ = moved; |
430 | | } |
431 | | |
432 | | // Dereference to get the real value. Doesn't seem like we need this. |
433 | | #if 0 |
434 | | T operator*() const { |
435 | | //log("operator*"); |
436 | | return *raw_pointer_; |
437 | | } |
438 | | #endif |
439 | | |
440 | | T* raw_pointer_; |
441 | | }; |
442 | | |
443 | | template <typename T> |
444 | | class Param : public Local<T> { |
445 | | // This could be an optimization like SpiderMonkey's Handle<T> vs Rooted<T>. |
446 | | // We use the names Param<T> and Local<T>. |
447 | | |
448 | | public: |
449 | | // hm this is awkward, I think we should NOT inherit! We should only |
450 | | // convert. |
451 | | Param(const Local<T>& other) : Local<T>(nullptr) { |
452 | | this->raw_pointer_ = other.raw_pointer_; |
453 | | } |
454 | | |
455 | | ~Param() { // do not PopRoot() |
456 | | } |
457 | | |
458 | | // Construct from T* -- PushRoot() |
459 | | // Construct from Local<T> -- we don't need to PushRoot() |
460 | | // operator= -- I don't think we need to PushRoot() |
461 | | }; |
462 | | |
463 | | // Variadic templates: |
464 | | // https://eli.thegreenplace.net/2014/variadic-templates-in-c/ |
465 | | template <typename T, typename... Args> |
466 | 736 | T* Alloc(Args&&... args) { |
467 | 736 | void* place = gHeap.Allocate(sizeof(T)); |
468 | 736 | assert(place != nullptr); |
469 | | // placement new |
470 | 0 | return new (place) T(std::forward<Args>(args)...); |
471 | 736 | } _ZN7gc_heap5AllocINS_4ListIPNS_3StrEEEJEEEPT_DpOT0_ Line | Count | Source | 466 | 75 | T* Alloc(Args&&... args) { | 467 | 75 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 75 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 75 | } |
_ZN7gc_heap5AllocINS_4ListIiEEJEEEPT_DpOT0_ Line | Count | Source | 466 | 322 | T* Alloc(Args&&... args) { | 467 | 322 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 322 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 322 | } |
_ZN7gc_heap5AllocINS_4ListIbEEJEEEPT_DpOT0_ Line | Count | Source | 466 | 2 | T* Alloc(Args&&... args) { | 467 | 2 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 2 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 2 | } |
_ZN7gc_heap5AllocINS_4ListIdEEJEEEPT_DpOT0_ Line | Count | Source | 466 | 1 | T* Alloc(Args&&... args) { | 467 | 1 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 1 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 1 | } |
_ZN7gc_heap5AllocINS_4DictIiPNS_3StrEEEJEEEPT_DpOT0_ Line | Count | Source | 466 | 2 | T* Alloc(Args&&... args) { | 467 | 2 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 2 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 2 | } |
_ZN7gc_heap5AllocINS_4DictIPNS_3StrEiEEJEEEPT_DpOT0_ Line | Count | Source | 466 | 8 | T* Alloc(Args&&... args) { | 467 | 8 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 8 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 8 | } |
_ZN7gc_heap5AllocINS_4DictIPNS_3StrES3_EEJEEEPT_DpOT0_ Line | Count | Source | 466 | 1 | T* Alloc(Args&&... args) { | 467 | 1 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 1 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 1 | } |
_ZN7gc_heap5AllocINS_4DictIiiEEJEEEPT_DpOT0_ Line | Count | Source | 466 | 1 | T* Alloc(Args&&... args) { | 467 | 1 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 1 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 1 | } |
_ZN7gc_heap5AllocI5PointJiiEEEPT_DpOT0_ Line | Count | Source | 466 | 2 | T* Alloc(Args&&... args) { | 467 | 2 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 2 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 2 | } |
_ZN7gc_heap5AllocI4LineJEEEPT_DpOT0_ Line | Count | Source | 466 | 1 | T* Alloc(Args&&... args) { | 467 | 1 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 1 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 1 | } |
_ZN7gc_heap5AllocI10DerivedObjJEEEPT_DpOT0_ Line | Count | Source | 466 | 1 | T* Alloc(Args&&... args) { | 467 | 1 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 1 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 1 | } |
_ZN7gc_heap5AllocIN5mylib13BufLineReaderEJRPNS_3StrEEEEPT_DpOT0_ Line | Count | Source | 466 | 1 | T* Alloc(Args&&... args) { | 467 | 1 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 1 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 1 | } |
_ZN7gc_heap5AllocIN5mylib15CFileLineReaderEJRP8_IO_FILEEEEPT_DpOT0_ Line | Count | Source | 466 | 3 | T* Alloc(Args&&... args) { | 467 | 3 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 3 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 3 | } |
Unexecuted instantiation: _ZN7gc_heap5AllocI4BaseJiEEEPT_DpOT0_ Unexecuted instantiation: _ZN7gc_heap5AllocI7DerivedJiiEEEPT_DpOT0_ _ZN7gc_heap5AllocIN14asdl_generated17arith_expr__ConstEJiEEEPT_DpOT0_ Line | Count | Source | 466 | 1 | T* Alloc(Args&&... args) { | 467 | 1 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 1 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 1 | } |
Unexecuted instantiation: _ZN7gc_heap5AllocIN14asdl_generated17arith_expr__ConstEJRiEEEPT_DpOT0_ _ZN7gc_heap5AllocIN7classes10TextOutputEJRPN5mylib6WriterEEEEPT_DpOT0_ Line | Count | Source | 466 | 1 | T* Alloc(Args&&... args) { | 467 | 1 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 1 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 1 | } |
_ZN7gc_heap5AllocIN7classes7DerivedEJEEEPT_DpOT0_ Line | Count | Source | 466 | 1 | T* Alloc(Args&&... args) { | 467 | 1 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 1 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 1 | } |
_ZN7gc_heap5AllocIN5mylib9BufWriterEJEEEPT_DpOT0_ Line | Count | Source | 466 | 33 | T* Alloc(Args&&... args) { | 467 | 33 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 33 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 33 | } |
Unexecuted instantiation: _ZN7gc_heap5AllocIN7classes10TextOutputEJRPN5mylib9BufWriterEEEEPT_DpOT0_ _ZN7gc_heap5AllocI6Tuple2IiPNS_3StrEEJiRS3_EEEPT_DpOT0_ Line | Count | Source | 466 | 5 | T* Alloc(Args&&... args) { | 467 | 5 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 5 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 5 | } |
_ZN7gc_heap5AllocIN10containers5PointEJiiEEEPT_DpOT0_ Line | Count | Source | 466 | 1 | T* Alloc(Args&&... args) { | 467 | 1 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 1 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 1 | } |
_ZN7gc_heap5AllocIN12control_flow10ParseErrorEJRPNS_3StrEEEEPT_DpOT0_ Line | Count | Source | 466 | 1 | T* Alloc(Args&&... args) { | 467 | 1 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 1 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 1 | } |
_ZN7gc_heap5AllocI6Tuple2IPNS_3StrEiEJRS3_iEEEPT_DpOT0_ Line | Count | Source | 466 | 3 | T* Alloc(Args&&... args) { | 467 | 3 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 3 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 3 | } |
_ZN7gc_heap5AllocINS_4ListIP6Tuple2IPNS_3StrEiEEEJEEEPT_DpOT0_ Line | Count | Source | 466 | 1 | T* Alloc(Args&&... args) { | 467 | 1 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 1 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 1 | } |
_ZN7gc_heap5AllocINS_4ListIP6Tuple2IiPNS_3StrEEEEJEEEPT_DpOT0_ Line | Count | Source | 466 | 2 | T* Alloc(Args&&... args) { | 467 | 2 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 2 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 2 | } |
Unexecuted instantiation: _ZN7gc_heap5AllocI19NotImplementedErrorJEEEPT_DpOT0_ _ZN7gc_heap5AllocIN7modules3DogEJRPNS_3StrEEEEPT_DpOT0_ Line | Count | Source | 466 | 1 | T* Alloc(Args&&... args) { | 467 | 1 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 1 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 1 | } |
_ZN7gc_heap5AllocIN7module13CatEJEEEPT_DpOT0_ Line | Count | Source | 466 | 1 | T* Alloc(Args&&... args) { | 467 | 1 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 1 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 1 | } |
_ZN7gc_heap5AllocIN7modules6SphinxEJRPNS_3StrEEEEPT_DpOT0_ Line | Count | Source | 466 | 1 | T* Alloc(Args&&... args) { | 467 | 1 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 1 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 1 | } |
_ZN7gc_heap5AllocINS_4ListIPN10hnode_asdl5fieldEEEJEEEPT_DpOT0_ Line | Count | Source | 466 | 34 | T* Alloc(Args&&... args) { | 467 | 34 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 34 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 34 | } |
_ZN7gc_heap5AllocINS_4ListIPN10hnode_asdl7hnode_tEEEJEEEPT_DpOT0_ Line | Count | Source | 466 | 34 | T* Alloc(Args&&... args) { | 467 | 34 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 34 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 34 | } |
_ZN7gc_heap5AllocIN10hnode_asdl13hnode__RecordEJRPNS_3StrEPNS_4ListIPNS1_5fieldEEEbS5_S5_PNS6_IPNS1_7hnode_tEEEEEEPT_DpOT0_ Line | Count | Source | 466 | 34 | T* Alloc(Args&&... args) { | 467 | 34 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 34 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 34 | } |
Unexecuted instantiation: _ZN7gc_heap5AllocIN10hnode_asdl11hnode__LeafEJRPNS_3StrENS1_7color_eEEEEPT_DpOT0_ _ZN7gc_heap5AllocIN10hnode_asdl11hnode__LeafEJRPNS_3StrERNS1_7color_eEEEEPT_DpOT0_ Line | Count | Source | 466 | 20 | T* Alloc(Args&&... args) { | 467 | 20 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 20 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 20 | } |
Unexecuted instantiation: _ZN7gc_heap5AllocIN6format10AnsiOutputEJRPN5mylib6WriterEEEEPT_DpOT0_ Unexecuted instantiation: _ZN7gc_heap5AllocIN6format10TextOutputEJRPN5mylib6WriterEEEEPT_DpOT0_ Unexecuted instantiation: _ZN7gc_heap5AllocIN6format10TextOutputEJPN5mylib9BufWriterEEEEPT_DpOT0_ Unexecuted instantiation: _ZN7gc_heap5AllocIN6format10HtmlOutputEJPN5mylib9BufWriterEEEEPT_DpOT0_ _ZN7gc_heap5AllocIN6format10AnsiOutputEJPN5mylib9BufWriterEEEEPT_DpOT0_ Line | Count | Source | 466 | 31 | T* Alloc(Args&&... args) { | 467 | 31 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 31 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 31 | } |
_ZN7gc_heap5AllocI6Tuple2IPNS_3StrEiEJRS3_RiEEEPT_DpOT0_ Line | Count | Source | 466 | 22 | T* Alloc(Args&&... args) { | 467 | 22 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 22 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 22 | } |
_ZN7gc_heap5AllocIN6format14_PrettyPrinterEJiEEEPT_DpOT0_ Line | Count | Source | 466 | 8 | T* Alloc(Args&&... args) { | 467 | 8 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 8 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 8 | } |
_ZN7gc_heap5AllocIN5parse10ParseErrorEJPNS_3StrEEEEPT_DpOT0_ Line | Count | Source | 466 | 5 | T* Alloc(Args&&... args) { | 467 | 5 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 5 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 5 | } |
_ZN7gc_heap5AllocIN9expr_asdl9expr__VarEJRPNS_3StrEEEEPT_DpOT0_ Line | Count | Source | 466 | 9 | T* Alloc(Args&&... args) { | 467 | 9 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 9 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 9 | } |
_ZN7gc_heap5AllocIN9expr_asdl11expr__ConstEJiEEEPT_DpOT0_ Line | Count | Source | 466 | 14 | T* Alloc(Args&&... args) { | 467 | 14 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 14 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 14 | } |
_ZN7gc_heap5AllocIN9expr_asdl12expr__BinaryEJRPNS_3StrERPNS1_6expr_tES8_EEEPT_DpOT0_ Line | Count | Source | 466 | 14 | T* Alloc(Args&&... args) { | 467 | 14 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 14 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 14 | } |
_ZN7gc_heap5AllocIN5parse5LexerEJRPNS_3StrEEEEPT_DpOT0_ Line | Count | Source | 466 | 14 | T* Alloc(Args&&... args) { | 467 | 14 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 14 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 14 | } |
_ZN7gc_heap5AllocIN5parse6ParserEJRPNS1_5LexerEEEEPT_DpOT0_ Line | Count | Source | 466 | 13 | T* Alloc(Args&&... args) { | 467 | 13 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 13 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 13 | } |
_ZN7gc_heap5AllocIN6format10AnsiOutputEJPN5mylib6WriterEEEEPT_DpOT0_ Line | Count | Source | 466 | 8 | T* Alloc(Args&&... args) { | 467 | 8 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 8 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 8 | } |
_ZN7gc_heap5AllocIN15scoped_resource7MyErrorEJEEEPT_DpOT0_ Line | Count | Source | 466 | 1 | T* Alloc(Args&&... args) { | 467 | 1 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 1 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 1 | } |
_ZN7gc_heap5AllocIN15scoped_resource8DirStackEJEEEPT_DpOT0_ Line | Count | Source | 466 | 1 | T* Alloc(Args&&... args) { | 467 | 1 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 1 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 1 | } |
_ZN7gc_heap5AllocIN7strings3FooEJEEEPT_DpOT0_ Line | Count | Source | 466 | 1 | T* Alloc(Args&&... args) { | 467 | 1 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 1 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 1 | } |
_ZN7gc_heap5AllocIN9test_cast11ColorOutputEJRPN5mylib9BufWriterEEEEPT_DpOT0_ Line | Count | Source | 466 | 1 | T* Alloc(Args&&... args) { | 467 | 1 | void* place = gHeap.Allocate(sizeof(T)); | 468 | 1 | assert(place != nullptr); | 469 | | // placement new | 470 | 0 | return new (place) T(std::forward<Args>(args)...); | 471 | 1 | } |
|
472 | | |
473 | | // Return the size of a resizeable allocation. For now we just round up by |
474 | | // powers of 2. This could be optimized later. CPython has an interesting |
475 | | // policy in listobject.c. |
476 | | // |
477 | | // https://stackoverflow.com/questions/466204/rounding-up-to-next-power-of-2 |
478 | 972 | inline int RoundUp(int n) { |
479 | | // minimum size |
480 | 972 | if (n < 8) { |
481 | 447 | return 8; |
482 | 447 | } |
483 | | |
484 | | // TODO: what if int isn't 32 bits? |
485 | 525 | n--; |
486 | 525 | n |= n >> 1; |
487 | 525 | n |= n >> 2; |
488 | 525 | n |= n >> 4; |
489 | 525 | n |= n >> 8; |
490 | 525 | n |= n >> 16; |
491 | 525 | n++; |
492 | 525 | return n; |
493 | 972 | } |
494 | | |
495 | | const int kZeroMask = 0; // for types with no pointers |
496 | | // no obj_len_ computed for global List/Slab/Dict |
497 | | const int kNoObjLen = 0x0eadbeef; |
498 | | |
499 | | // Why do we need this macro instead of using inheritance? |
500 | | // - Because ASDL uses multiple inheritance for first class variants, but we |
501 | | // don't want multiple IMPLEMENTATION inheritance. Instead we just generate |
502 | | // compatible layouts. |
503 | | // - Similarly, GlobalStr is layout-compatible with Str. It can't inherit from |
504 | | // Obj like Str, because of the constexpr issue with char[N]. |
505 | | |
506 | | // heap_tag_: one of Tag:: |
507 | | // type_tag_: ASDL tag (variant) |
508 | | // field_mask_: for fixed length records, so max 16 fields |
509 | | // obj_len_: number of bytes to copy |
510 | | // TODO: with a limitation of ~15 fields, we can encode obj_len_ in |
511 | | // field_mask_, and save space on many ASDL types. |
512 | | // And we can sort integers BEFORE pointers. |
513 | | |
514 | | // TODO: ./configure could detect big or little endian, and then flip the |
515 | | // fields in OBJ_HEADER? |
516 | | // |
517 | | // https://stackoverflow.com/questions/2100331/c-macro-definition-to-determine-big-endian-or-little-endian-machine |
518 | | // |
519 | | // Because we want to do (obj->heap_tag_ & 1 == 0) to distinguish it from |
520 | | // vtable pointer. We assume low bits of a pointer are 0 but not high bits. |
521 | | |
522 | | #define OBJ_HEADER() \ |
523 | | uint8_t heap_tag_; \ |
524 | | uint8_t type_tag_; \ |
525 | | uint16_t field_mask_; \ |
526 | | uint32_t obj_len_; |
527 | | |
528 | | class Obj { |
529 | | // The unit of garbage collection. It has a header describing how to find |
530 | | // the pointers within it. |
531 | | // |
532 | | // Note: Sorting ASDL fields by (non-pointer, pointer) is a good idea, but it |
533 | | // breaks down because mycpp has inheritance. Could do this later. |
534 | | |
535 | | public: |
536 | | // Note: ASDL types are layout-compatible with Obj, but don't actually |
537 | | // inherit from it because of the 'multiple inheritance of implementation' |
538 | | // issue. So they don't call this constructor. |
539 | | constexpr Obj(uint8_t heap_tag, uint16_t field_mask, int obj_len) |
540 | | : heap_tag_(heap_tag), |
541 | | type_tag_(0), |
542 | | field_mask_(field_mask), |
543 | 3.89k | obj_len_(obj_len) { |
544 | 3.89k | } |
545 | | |
546 | 2.00k | void SetObjLen(int obj_len) { |
547 | 2.00k | this->obj_len_ = obj_len; |
548 | 2.00k | } |
549 | | |
550 | | OBJ_HEADER() |
551 | | |
552 | | DISALLOW_COPY_AND_ASSIGN(Obj) |
553 | | }; |
554 | | |
555 | | } // namespace gc_heap |
556 | | |
557 | | #endif // GC_HEAP_H |