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

370 lines, 213 significant
1#include "mycpp/mark_sweep_heap.h"
2
3#include "mycpp/gc_alloc.h" // gHeap
4#include "mycpp/gc_list.h"
5#include "vendor/greatest.h"
6
7TEST for_code_coverage() {
8 // Add coverage for some methods
9
10 gHeap.ProcessExit();
11
12 PASS();
13}
14
15TEST mark_set_test() {
16 MarkSet mark_set;
17 mark_set.ReInit(20);
18
19 for (int i = 0; i < 20; ++i) {
20 ASSERT_EQ(false, mark_set.IsMarked(i));
21 }
22
23 for (int i = 0; i < 10; ++i) {
24 mark_set.Mark(i);
25 ASSERT_EQ(true, mark_set.IsMarked(i));
26 }
27
28 for (int i = 10; i < 20; ++i) {
29 ASSERT_EQ(false, mark_set.IsMarked(i));
30 }
31
32 mark_set.Debug();
33
34 // Another collection
35 int big = 1000;
36 mark_set.ReInit(big);
37
38 for (int i = 0; i < 20; ++i) {
39 ASSERT_EQ(false, mark_set.IsMarked(i));
40 }
41 for (int i = big - 100; i < big; ++i) {
42 ASSERT_EQ(false, mark_set.IsMarked(i));
43 }
44
45 ASSERT_EQ(false, mark_set.IsMarked(big));
46 mark_set.Mark(big);
47 ASSERT_EQ(true, mark_set.IsMarked(big));
48
49 // ASAN will detect buffer overflow
50 // mark_set.Mark(13220);
51
52 PASS();
53}
54
55TEST api_test() {
56#ifdef GC_ALWAYS
57 // no objects live
58 ASSERT_EQ_FMT(0, gHeap.MaybeCollect(), "%d");
59 {
60 BigStr *s1 = StrFromC("foo");
61 BigStr *s2 = StrFromC("bar");
62 StackRoots _r({&s1, &s2});
63
64 // 2 live objects
65 ASSERT_EQ_FMT(2, gHeap.MaybeCollect(), "%d");
66
67 // 1 live
68 s2 = nullptr;
69 ASSERT_EQ_FMT(1, gHeap.MaybeCollect(), "%d");
70 }
71 ASSERT_EQ_FMT(0, gHeap.MaybeCollect(), "%d");
72#else
73 // otherwise we didn't try to collect
74 ASSERT_EQ_FMT(-1, gHeap.MaybeCollect(), "%d");
75#endif
76
77 PASS();
78}
79
80TEST string_collection_test() {
81 BigStr *test_str = StrFromC("foo");
82
83 StackRoots _roots({&test_str});
84
85 ASSERT(items_equal(test_str, StrFromC("foo")));
86
87 gHeap.Collect();
88
89 ASSERT(items_equal(test_str, StrFromC("foo")));
90
91 PASS();
92}
93
94TEST list_collection_test() {
95 {
96 BigStr *test_str0 = nullptr;
97 BigStr *test_str1 = nullptr;
98 List<BigStr *> *test_list = nullptr;
99
100 StackRoots _roots({&test_str0, &test_str1, &test_list});
101
102 test_str0 = StrFromC("foo_0");
103 test_str1 = StrFromC("foo_1");
104 test_list = NewList<BigStr *>();
105
106 test_list->append(test_str0);
107 test_list->append(test_str1);
108
109 // Verify the list looks as we expected
110 {
111 ASSERT(items_equal(test_list->at(0), test_str0));
112 ASSERT(items_equal(test_list->at(1), test_str1));
113
114 ASSERT_EQ(test_list->at(0), test_str0);
115 ASSERT_EQ(test_list->at(1), test_str1);
116
117 ASSERT_EQ(2, len(test_list));
118 }
119
120 gHeap.Collect();
121
122 {
123 ASSERT(items_equal(test_list->at(0), test_str0));
124 ASSERT(items_equal(test_list->at(1), test_str1));
125
126 ASSERT_EQ(test_list->at(0), test_str0);
127 ASSERT_EQ(test_list->at(1), test_str1);
128 }
129
130 test_list->pop();
131 ASSERT_EQ(1, len(test_list));
132 }
133
134 gHeap.Collect();
135
136 PASS();
137}
138
139class Node {
140 public:
141 Node() : next_(nullptr) {
142 }
143
144 static constexpr ObjHeader obj_header() {
145 return ObjHeader::ClassFixed(field_mask(), sizeof(Node));
146 }
147
148 Node *next_;
149
150 static constexpr uint32_t field_mask() {
151 return maskbit(offsetof(Node, next_));
152 }
153};
154
155TEST cycle_collection_test() {
156 // Dict<BigStr*, int>* d = NewDict<BigStr*, int>();
157
158 Node *n1 = nullptr;
159 Node *n2 = nullptr;
160 StackRoots _roots({&n1, &n2});
161 n1 = Alloc<Node>();
162 n2 = Alloc<Node>();
163
164 gHeap.Collect();
165
166 n1->next_ = n2;
167 n2->next_ = n1;
168
169 gHeap.Collect();
170
171 PASS();
172}
173
174TEST pool_sanity_check() {
175 Pool<2, 32> p;
176
177 ASSERT_EQ(p.bytes_allocated(), 0);
178 ASSERT_EQ(p.num_allocated(), 0);
179 ASSERT_EQ(p.num_live(), 0);
180 ASSERT_EQ(p.kMaxObjSize, 32);
181
182 int obj_id1 = -1;
183 int obj_id2 = -1;
184 int obj_id3 = -1;
185 p.Allocate(&obj_id1);
186 p.Allocate(&obj_id2);
187 p.Allocate(&obj_id3);
188 ASSERT_EQ(p.num_allocated(), 3);
189 ASSERT_EQ(p.num_live(), 3);
190 // The third allocation should've created a new block.
191 ASSERT_EQ(p.bytes_allocated(), 128);
192 ASSERT(obj_id1 != -1);
193 ASSERT(obj_id2 != -1);
194 ASSERT(obj_id3 != -1);
195
196 p.Free();
197 PASS();
198}
199
200TEST pool_sweep() {
201 Pool<2, 32> p;
202
203 p.PrepareForGc();
204 p.Sweep();
205
206 int obj_id;
207 void *addr1 = p.Allocate(&obj_id);
208 void *addr2 = p.Allocate(&obj_id);
209 p.PrepareForGc();
210 p.Sweep();
211
212 ASSERT_EQ(p.num_live(), 0);
213
214 // Cells are reused after freeing.
215 void *addr3 = p.Allocate(&obj_id);
216 void *addr4 = p.Allocate(&obj_id);
217 ASSERT((addr1 == addr3 && addr2 == addr4) ||
218 (addr1 == addr4 && addr2 == addr3));
219
220 p.Free();
221 PASS();
222}
223
224TEST pool_marked_objs_are_kept_alive() {
225 Pool<1, 32> p;
226
227 int obj_id1;
228 int obj_id2;
229 p.Allocate(&obj_id1);
230 p.Allocate(&obj_id2);
231 p.PrepareForGc();
232 p.Mark(obj_id2);
233 p.Sweep();
234 ASSERT_EQ(p.num_live(), 1);
235
236 p.Free();
237 PASS();
238}
239
240TEST pool_size() {
241 MarkSweepHeap heap;
242 log("pool1 kMaxObjSize %d", heap.pool1_.kMaxObjSize);
243 log("pool1 kBlockSize %d", heap.pool1_.kBlockSize);
244
245 log("pool2 kMaxObjSize %d", heap.pool2_.kMaxObjSize);
246 log("pool2 kBlockSize %d", heap.pool2_.kBlockSize);
247
248 // It may do malloc(sizeof(Block)) each time, e.g. 4080 bytes
249 for (int i = 0; i < 200; ++i) {
250 int obj_id = 0;
251 heap.pool1_.Allocate(&obj_id);
252 // log("pool1 obj_id = %d", obj_id);
253 }
254
255 for (int i = 0; i < 200; ++i) {
256 int obj_id = 0;
257 heap.pool2_.Allocate(&obj_id);
258 // log("pool2 obj_id = %d", obj_id);
259 }
260
261 heap.pool1_.Free();
262 heap.pool2_.Free();
263
264 PASS();
265}
266
267SUITE(pool_alloc) {
268 RUN_TEST(pool_sanity_check);
269 RUN_TEST(pool_sweep);
270 RUN_TEST(pool_marked_objs_are_kept_alive);
271 RUN_TEST(pool_size);
272}
273
274int f(BigStr *s, List<int> *mylist) {
275 // Param Roots
276 StackRoots _roots({&s, &mylist});
277
278 // Sorted params
279 BigStr *first = nullptr;
280 List<int> *other = nullptr;
281 List<int> *other2 = nullptr;
282 BigStr *last = nullptr;
283
284 int a = 0;
285 float b = 3.5;
286
287 ptrdiff_t diff = &last - &first;
288
289 // Account for stack going up or down
290 // This is cool!
291 int n_pointers = diff > 0 ? diff : -diff;
292
293 log("a = %d, b = %f", a, b);
294
295 // 2 pointers if we don't use other2 !
296 // log("other = %p", &other);
297
298 // 3 pointers!
299 log("other = %p, other2 = %p", &other, &other2);
300
301 log("n_pointers = %d", n_pointers);
302
303 return 42;
304}
305
306TEST hybrid_root_test() {
307 log("hi = %s", "x");
308
309 f(StrFromC("hi"), nullptr);
310
311 PASS();
312}
313
314TEST timing_test() {
315 // This is what GC_TIMING does
316
317 struct timespec start, end;
318 if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start) < 0) {
319 FAIL("clock_gettime failed");
320 }
321
322 // Run with ASAN; opt makes this instant
323 uint64_t n = 0;
324 for (int i = 0; i < 10000; ++i) {
325 for (int j = 0; j < 10000; ++j) {
326 n += i + j;
327 }
328 }
329 log("n = %ld", n);
330
331 if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end) < 0) {
332 FAIL("clock_gettime failed");
333 }
334
335 log("start %d %d", start.tv_sec, start.tv_nsec);
336 log("end %d %d", end.tv_sec, end.tv_nsec);
337
338 double start_secs = start.tv_sec + start.tv_nsec / 1e9;
339 double end_secs = end.tv_sec + end.tv_nsec / 1e9;
340 double gc_millis = (end_secs - start_secs) * 1000.0;
341
342 log(" %.1f ms GC", gc_millis);
343
344 PASS();
345}
346
347GREATEST_MAIN_DEFS();
348
349int main(int argc, char **argv) {
350 gHeap.Init();
351
352 GREATEST_MAIN_BEGIN();
353
354 RUN_TEST(for_code_coverage);
355 RUN_TEST(mark_set_test);
356 RUN_TEST(api_test);
357 RUN_TEST(string_collection_test);
358 RUN_TEST(list_collection_test);
359 RUN_TEST(cycle_collection_test);
360
361 RUN_SUITE(pool_alloc);
362
363 RUN_TEST(hybrid_root_test);
364 RUN_TEST(timing_test);
365
366 gHeap.CleanProcessExit();
367
368 GREATEST_MAIN_END(); /* display results */
369 return 0;
370}