OILS / mycpp / gc_heap_test.cc View on Github | oilshell.org

479 lines, 283 significant
1// mycpp/gc_heap_test.cc
2
3#include "mycpp/runtime.h"
4#include "mycpp/test_common.h"
5#include "vendor/greatest.h"
6
7#define ASSERT_NUM_LIVE_OBJS(x) \
8 ASSERT_EQ_FMT((x), static_cast<int>(gHeap.num_live()), "%d");
9
10// Hm we're getting a warning because these aren't plain old data?
11// https://stackoverflow.com/questions/1129894/why-cant-you-use-offsetof-on-non-pod-structures-in-c
12// https://stackoverflow.com/questions/53850100/warning-offset-of-on-non-standard-layout-type-derivedclass
13
14// The structures must be layout compatible! Protect against typos.
15
16#define ASSERT_GLOBAL_STR(field) \
17 static_assert(offsetof(BigStr, field) == offsetof(GlobalStr<1>, field), \
18 "BigStr and GlobalStr should be consistent");
19ASSERT_GLOBAL_STR(len_);
20// NOTE: offsetof doesn't work with bitfields...
21// ASSERT_GLOBAL_STR(hash_);
22// ASSERT_GLOBAL_STR(is_hashed_);
23ASSERT_GLOBAL_STR(data_);
24
25static_assert(offsetof(Slab<int>, items_) ==
26 offsetof(GlobalSlab<int COMMA 1>, items_),
27 "Slab and GlobalSlab should be consistent");
28
29#define ASSERT_GLOBAL_LIST(field) \
30 static_assert( \
31 offsetof(List<int>, field) == offsetof(GlobalList<int COMMA 1>, field), \
32 "List and GlobalList should be consistent");
33
34ASSERT_GLOBAL_LIST(len_);
35ASSERT_GLOBAL_LIST(capacity_);
36ASSERT_GLOBAL_LIST(slab_);
37
38#define ASSERT_GLOBAL_DICT(field) \
39 static_assert(offsetof(Dict<int COMMA int>, field) == \
40 offsetof(GlobalDict<int COMMA int COMMA 1>, field), \
41 "Dict and GlobalDict should be consistent");
42
43ASSERT_GLOBAL_DICT(len_);
44ASSERT_GLOBAL_DICT(capacity_);
45ASSERT_GLOBAL_DICT(index_);
46ASSERT_GLOBAL_DICT(keys_);
47ASSERT_GLOBAL_DICT(values_);
48
49void ShowSlab(void* obj) {
50 auto slab = reinterpret_cast<Slab<void*>*>(obj);
51 auto* header = ObjHeader::FromObject(obj);
52 assert(header->heap_tag == HeapTag::Scanned);
53
54 int n = NUM_POINTERS(*header);
55 for (int i = 0; i < n; ++i) {
56 void* p = slab->items_[i];
57 if (p == nullptr) {
58 log("p = nullptr");
59 } else {
60 log("p = %p", p);
61 }
62 }
63}
64
65// Prints field masks for Dict and List
66TEST field_masks_test() {
67 auto L = NewList<int>();
68 StackRoots _roots({&L});
69
70 L->append(1);
71 log("List mask = %d", FIELD_MASK(*ObjHeader::FromObject(L)));
72
73 auto d = Alloc<Dict<BigStr*, int>>();
74 StackRoots _roots2({&d});
75
76 auto key = StrFromC("foo");
77 StackRoots _roots9({&key});
78 d->set(key, 3);
79
80 // oops this is bad? Because StrFromC() might move d in the middle of the
81 // expression! Gah!
82 // d->set(StrFromC("foo"), 3);
83
84 log("Dict mask = %d", FIELD_MASK(*ObjHeader::FromObject(d)));
85
86#if 0
87 ShowFixedChildren(L);
88 ShowFixedChildren(d);
89#endif
90
91 auto L2 = NewList<BigStr*>();
92 StackRoots _roots3({&L2});
93
94 auto s = StrFromC("foo");
95 StackRoots _roots4({&s});
96
97 L2->append(s);
98 L2->append(s);
99 ShowSlab(L2->slab_);
100
101 PASS();
102}
103
104TEST offsets_test() {
105 // Note: These will be different for 32 bit
106
107 ASSERT_EQ(offsetof(List<int>, slab_),
108 offsetof(GlobalList<int COMMA 1>, slab_));
109
110 if (sizeof(void*) == 8) {
111 // 0b 0000 0010
112 unsigned list_mask = List<int>::field_mask();
113 ASSERT_EQ_FMT(0x0002, list_mask, "0x%x");
114
115 // in binary: 0b 0000 0000 0001 1100
116 unsigned dict_mask = Dict<int COMMA int>::field_mask();
117 ASSERT_EQ_FMT(0x0001c, dict_mask, "0x%x");
118 }
119
120 PASS();
121}
122
123// TODO: the last one overflows
124int sizes[] = {0, 1, 2, 3, 4, 5, 8,
125 9, 12, 16, 256, 257, 1 << 30, (1 << 30) + 1};
126int nsizes = sizeof(sizes) / sizeof(sizes[0]);
127
128TEST roundup_test() {
129 for (int i = 0; i < nsizes; ++i) {
130 int n = sizes[i];
131 log("%d -> %d", n, RoundUp(n));
132 }
133
134 PASS();
135}
136
137TEST list_resize_policy_test() {
138 log("");
139 log("\tList<int>");
140 log("\tkNumItems2 %d", List<int>::kNumItems2);
141
142 auto small = NewList<int>();
143
144 for (int i = 0; i < 20; ++i) {
145 small->append(i);
146 int c = small->capacity_;
147 int slab_bytes = sizeof(ObjHeader) + c * sizeof(int);
148 log("desired %3d how many %3d slab bytes %3d", i, c, slab_bytes);
149 }
150
151 log("");
152 log("\tList<BigStr*>");
153 log("\tNumItems2 %d", List<BigStr*>::kNumItems2);
154
155 // Note: on 32-bit systems, this should be the same
156
157 auto big = NewList<BigStr*>();
158 for (int i = 0; i < 20; ++i) {
159 big->append(kEmptyString);
160 int c = big->capacity_;
161 int slab_bytes = sizeof(ObjHeader) + c * sizeof(BigStr*);
162 log("desired %3d how many %3d slab bytes %3d", i, c, slab_bytes);
163 }
164
165 PASS();
166}
167
168TEST dict_resize_policy_test() {
169 log("\tDict<int, int>");
170
171 log("\tkNumItems2 %d", Dict<int, int>::kNumItems2);
172 log("\tkHeaderFudge %d", Dict<int, int>::kHeaderFudge);
173
174 auto small = Alloc<Dict<int, int>>();
175
176 for (int i = 0; i < 20; ++i) {
177 small->set(i, i);
178 int c = small->capacity_;
179 int slab_k = sizeof(ObjHeader) + c * sizeof(int);
180 int slab_v = slab_k;
181
182 int x = small->index_len_;
183 int index_bytes = sizeof(ObjHeader) + x * sizeof(int);
184
185 log("desired %3d how many %3d k %3d v %3d index %3d %3d", i, c, slab_k,
186 slab_v, x, index_bytes);
187 }
188
189 log("");
190 log("\tDict<BigStr*, int>");
191
192 log("\tkNumItems2 %d", Dict<BigStr*, int>::kNumItems2);
193 log("\tkHeaderFudge %d", Dict<BigStr*, int>::kHeaderFudge);
194
195 auto big = Alloc<Dict<BigStr*, int>>();
196
197 for (int i = 0; i < 20; ++i) {
198 BigStr* key = str_repeat(StrFromC("x"), i);
199 big->set(key, i);
200 int c = big->capacity_;
201 int slab_k = sizeof(ObjHeader) + c * sizeof(BigStr*);
202 int slab_v = sizeof(ObjHeader) + c * sizeof(int);
203
204 int x = big->index_len_;
205 int index_bytes = sizeof(ObjHeader) + x * sizeof(int);
206
207 log("desired %3d how many %3d k %3d v %3d index %3d %3d", i, c, slab_k,
208 slab_v, x, index_bytes);
209 }
210
211 PASS();
212}
213
214const int kLineMask = 0x3; // 0b0011
215
216class Line {
217 public:
218 Line() : begin_(nullptr), end_(nullptr) {
219 }
220
221 static constexpr ObjHeader obj_header() {
222 return ObjHeader::ClassFixed(kLineMask, sizeof(Line));
223 }
224
225 Point* begin_;
226 Point* end_;
227};
228
229TEST fixed_trace_test() {
230 gHeap.Collect();
231
232 ASSERT_NUM_LIVE_OBJS(0);
233
234 Point* p = nullptr;
235 Point* p2 = nullptr;
236 Line* line = nullptr;
237
238 StackRoots _roots({&p, &p2, &line});
239
240 p = Alloc<Point>(3, 4);
241 log("point size = %d", p->size());
242
243 ASSERT_NUM_LIVE_OBJS(1);
244
245 line = Alloc<Line>();
246
247 p2 = Alloc<Point>(5, 6);
248 line->begin_ = p;
249
250 // ROOTING ISSUE: This isn't valid? Uncomment and we'll see a crash in
251 // testgc mode.
252
253 // line->end_ = Alloc<Point>(5, 6);
254
255 // I think the problem is that the allocation causes the LHS to be invalid?
256
257 line->end_ = p2;
258
259 ASSERT_NUM_LIVE_OBJS(3);
260
261 gHeap.Collect();
262 ASSERT_NUM_LIVE_OBJS(3);
263
264 // remove last reference
265 line->end_ = nullptr;
266 p2 = nullptr;
267
268 gHeap.Collect();
269 ASSERT_NUM_LIVE_OBJS(2);
270
271 PASS();
272}
273
274GLOBAL_STR(str4, "egg");
275
276TEST slab_trace_test() {
277 gHeap.Collect();
278
279 ASSERT_NUM_LIVE_OBJS(0);
280
281 {
282 List<int>* ints = nullptr;
283 StackRoots _roots({&ints});
284 ints = Alloc<List<int>>();
285 ASSERT_NUM_LIVE_OBJS(1);
286
287 ints->append(3);
288 ASSERT_NUM_LIVE_OBJS(2);
289 } // ints goes out of scope
290
291 gHeap.Collect();
292 ASSERT_NUM_LIVE_OBJS(0);
293
294 List<BigStr*>* strings = nullptr;
295 BigStr* tmp = nullptr;
296 StackRoots _roots({&strings, &tmp});
297
298 // List of strings
299 strings = Alloc<List<BigStr*>>();
300 ASSERT_NUM_LIVE_OBJS(1);
301
302 // +2: slab and string
303 tmp = StrFromC("yo");
304 strings->append(tmp);
305 ASSERT_NUM_LIVE_OBJS(3);
306
307 // +1 string
308 tmp = StrFromC("bar");
309 strings->append(tmp);
310 ASSERT_NUM_LIVE_OBJS(4);
311
312 // -1: remove reference to "bar"
313 strings->set(1, nullptr);
314 tmp = nullptr;
315 gHeap.Collect();
316 ASSERT_NUM_LIVE_OBJS(3);
317
318 // -1: set to GLOBAL instance. Remove reference to "yo".
319 strings->set(0, str4);
320 gHeap.Collect();
321 ASSERT_NUM_LIVE_OBJS(2);
322
323 PASS();
324}
325
326TEST global_trace_test() {
327 gHeap.Collect();
328
329 BigStr* l4 = nullptr;
330 List<BigStr*>* strings = nullptr;
331
332 int num_roots;
333 num_roots = gHeap.roots_.size();
334 ASSERT_EQ_FMT(0, num_roots, "%d");
335
336 StackRoots _roots({&l4, &strings});
337
338 num_roots = gHeap.roots_.size();
339 ASSERT_EQ_FMT(2, num_roots, "%d");
340
341 // 2 roots, but no live objects
342 l4 = str4;
343 ASSERT_NUM_LIVE_OBJS(0);
344
345 gHeap.Collect();
346 ASSERT_NUM_LIVE_OBJS(0);
347
348 // Heap reference to global
349
350 strings = Alloc<List<BigStr*>>();
351 ASSERT_NUM_LIVE_OBJS(1);
352
353 // We now have the Slab too
354 strings->append(nullptr);
355 ASSERT_NUM_LIVE_OBJS(2);
356
357 // Global pointer doesn't increase the count
358 strings->set(0, str4);
359 ASSERT_NUM_LIVE_OBJS(2);
360
361 // Not after GC either
362 gHeap.Collect();
363 ASSERT_NUM_LIVE_OBJS(2);
364
365 PASS();
366}
367
368// 8 byte vtable, 8 byte ObjHeader, then member_
369class BaseObj {
370 public:
371 explicit BaseObj(uint32_t obj_len) {
372 }
373 BaseObj() : BaseObj(sizeof(BaseObj)) {
374 }
375
376 virtual int Method() {
377 return 3;
378 }
379
380 static constexpr ObjHeader obj_header() {
381 return ObjHeader::ClassFixed(kZeroMask, sizeof(BaseObj));
382 }
383
384 int member_ = 254;
385};
386
387// 8 byte vtable, 8 byte ObjHeader, then member_, then derived_member_
388class DerivedObj : public BaseObj {
389 public:
390 DerivedObj() : BaseObj(sizeof(DerivedObj)) {
391 }
392 virtual int Method() {
393 return 4;
394 }
395
396 static constexpr ObjHeader obj_header() {
397 return ObjHeader::ClassFixed(kZeroMask, sizeof(DerivedObj));
398 }
399
400 int derived_member_ = 253;
401 int derived_member2_ = 252;
402};
403
404void ShowObj(ObjHeader* obj) {
405 log("obj->heap_tag %d", obj->heap_tag);
406#if 0
407 log("obj->obj_len %d", obj->obj_len);
408#endif
409}
410
411TEST inheritance_test() {
412 gHeap.Collect();
413
414 ASSERT_NUM_LIVE_OBJS(0);
415
416 DerivedObj* obj = nullptr;
417 StackRoots _roots({&obj});
418
419 ASSERT_NUM_LIVE_OBJS(0);
420 gHeap.Collect();
421 ASSERT_NUM_LIVE_OBJS(0);
422
423 obj = Alloc<DerivedObj>();
424 ASSERT_EQ_FMT(253, obj->derived_member_, "%d");
425 ASSERT_NUM_LIVE_OBJS(1);
426
427 gHeap.Collect();
428 ASSERT_NUM_LIVE_OBJS(1);
429 ASSERT_EQ_FMT(253, obj->derived_member_, "%d");
430
431 PASS();
432}
433
434TEST stack_roots_test() {
435 BigStr* s = nullptr;
436 List<int>* L = nullptr;
437
438 gHeap.Collect();
439
440 ASSERT_EQ(0, gHeap.roots_.size());
441
442 StackRoots _roots({&s, &L});
443
444 s = StrFromC("foo");
445 L = NewList<int>();
446
447 int num_roots = gHeap.roots_.size();
448 ASSERT_EQ_FMT(2, num_roots, "%u");
449
450 PASS();
451}
452
453GREATEST_MAIN_DEFS();
454
455int main(int argc, char** argv) {
456 gHeap.Init();
457
458 GREATEST_MAIN_BEGIN();
459
460 RUN_TEST(field_masks_test);
461 RUN_TEST(offsets_test);
462
463 RUN_TEST(roundup_test);
464 RUN_TEST(list_resize_policy_test);
465 RUN_TEST(dict_resize_policy_test);
466
467 RUN_TEST(fixed_trace_test);
468 RUN_TEST(slab_trace_test);
469 RUN_TEST(global_trace_test);
470
471 RUN_TEST(inheritance_test);
472
473 RUN_TEST(stack_roots_test);
474
475 gHeap.CleanProcessExit();
476
477 GREATEST_MAIN_END();
478 return 0;
479}