/home/andy/git/oilshell/oil/mycpp/gc_heap.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // gc_heap.cc |
2 | | |
3 | | #include <sys/mman.h> // mprotect() |
4 | | |
5 | | #include "mycpp/gc_types.h" |
6 | | |
7 | | using gc_heap::Heap; |
8 | | using gc_heap::Local; |
9 | | using gc_heap::Obj; |
10 | | |
11 | | namespace gc_heap { |
12 | | |
13 | | GLOBAL_STR(kEmptyString, ""); |
14 | | |
15 | | Heap gHeap; |
16 | | |
17 | | // LayoutForwarded and LayoutFixed aren't real types. You can cast arbitrary |
18 | | // objs to them to access a HOMOGENEOUS REPRESENTATION useful for garbage |
19 | | // collection. |
20 | | |
21 | | class LayoutForwarded : public Obj { |
22 | | public: |
23 | | Obj* new_location; // valid if and only if heap_tag_ == Tag::Forwarded |
24 | | }; |
25 | | |
26 | | // for Tag::FixedSize |
27 | | class LayoutFixed : public Obj { |
28 | | public: |
29 | | Obj* children_[16]; // only the entries denoted in field_mask will be valid |
30 | | }; |
31 | | |
32 | 101 | void Space::Init(int space_size) { |
33 | | #if GC_PROTECT |
34 | | void* p = mmap(nullptr, space_size, PROT_READ | PROT_WRITE, |
35 | | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
36 | | begin_ = static_cast<char*>(p); |
37 | | #else |
38 | 101 | begin_ = static_cast<char*>(malloc(space_size)); |
39 | 101 | #endif |
40 | 101 | size_ = space_size; |
41 | | |
42 | 101 | Clear(); |
43 | 101 | } |
44 | | |
45 | 20 | void Space::Free() { |
46 | | #if GC_PROTECT |
47 | | Protect(); // There is no way of deallocating I guess |
48 | | #else |
49 | 20 | free(begin_); |
50 | 20 | #endif |
51 | 20 | } |
52 | | |
53 | | #if GC_PROTECT |
54 | | void Space::Protect() { |
55 | | int m = mprotect(begin_, size_, PROT_NONE); |
56 | | assert(m == 0); |
57 | | } |
58 | | |
59 | | void Space::Unprotect() { |
60 | | int m = mprotect(begin_, size_, PROT_READ | PROT_WRITE); |
61 | | assert(m == 0); |
62 | | } |
63 | | #endif |
64 | | |
65 | 1.46k | Obj* Heap::Relocate(Obj* obj, Obj* header) { |
66 | | // Move an object from one space to another. |
67 | | // If there's no vtable, then obj == header. Otherwise header points to the |
68 | | // Obj header, which is right after the vtable. |
69 | | |
70 | 1.46k | switch (header->heap_tag_) { |
71 | 367 | case Tag::Forwarded: { |
72 | 367 | auto f = reinterpret_cast<LayoutForwarded*>(header); |
73 | 367 | return f->new_location; |
74 | 0 | } |
75 | | |
76 | 430 | case Tag::Global: { // e.g. GlobalStr isn't copied or forwarded |
77 | | // log("*** GLOBAL POINTER"); |
78 | 430 | return obj; |
79 | 0 | } |
80 | | |
81 | 669 | default: { |
82 | 669 | assert(header->heap_tag_ == Tag::Opaque || |
83 | 669 | header->heap_tag_ == Tag::FixedSize || |
84 | 669 | header->heap_tag_ == Tag::Scanned); |
85 | | |
86 | 0 | auto new_location = reinterpret_cast<Obj*>(free_); |
87 | | // Note: if we wanted to save space on ASDL records, we could calculate |
88 | | // their length from the field_mask here. How much would it slow down GC? |
89 | 669 | int n = header->obj_len_; |
90 | 669 | assert(n > 0); // detect common problem |
91 | 0 | memcpy(new_location, obj, n); |
92 | | // log("memcpy %d bytes from %p -> %p", n, obj, new_location); |
93 | | #if 0 |
94 | | if (obj->heap_tag_ == Tag::Opaque) { |
95 | | Str* s = static_cast<Str*>(obj); |
96 | | log("from = %s", s->data_); |
97 | | Str* s2 = static_cast<Str*>(new_location); |
98 | | log("to = %s", s2->data_); |
99 | | } |
100 | | #endif |
101 | | // aligned() like Heap::Allocate() |
102 | 669 | free_ += aligned(n); |
103 | | |
104 | | #if GC_STATS |
105 | | num_live_objs_++; |
106 | | #endif |
107 | | |
108 | 669 | auto f = reinterpret_cast<LayoutForwarded*>(header); |
109 | 669 | f->heap_tag_ = Tag::Forwarded; |
110 | 669 | f->new_location = new_location; |
111 | 669 | return new_location; |
112 | 0 | } |
113 | 1.46k | } // switch |
114 | 1.46k | } |
115 | | |
116 | 2.13k | inline Obj* ObjHeader(Obj* obj) { |
117 | | // If we see a vtable pointer, return the Obj* header immediately following. |
118 | | // Otherwise just return Obj itself. |
119 | 2.13k | return (obj->heap_tag_ & 0x1) == 0 |
120 | 2.13k | ? reinterpret_cast<Obj*>(reinterpret_cast<char*>(obj) + |
121 | 2 | sizeof(void*)) |
122 | 2.13k | : obj; |
123 | 2.13k | } |
124 | | |
125 | 364 | void Heap::Collect() { |
126 | | #if GC_STATS |
127 | | log("--> COLLECT with %d roots", roots_top_); |
128 | | num_collections_++; |
129 | | #endif |
130 | | |
131 | | #if GC_PROTECT |
132 | | to_space_.Unprotect(); |
133 | | #endif |
134 | | |
135 | | // If we grew one space, the other one has to catch up. |
136 | 364 | if (to_space_.size_ < from_space_.size_) { |
137 | 9 | to_space_.Free(); |
138 | 9 | to_space_.Init(from_space_.size_); |
139 | 9 | } |
140 | | |
141 | 364 | char* scan = to_space_.begin_; // boundary between black and gray |
142 | 364 | free_ = scan; // where to copy new entries |
143 | 364 | limit_ = scan + to_space_.size_; |
144 | | |
145 | | #if GC_STATS |
146 | | num_live_objs_ = 0; |
147 | | #endif |
148 | | |
149 | 1.86k | for (int i = 0; i < roots_top_; ++i) { |
150 | 1.50k | Obj** handle = roots_[i]; |
151 | 1.50k | auto root = *handle; |
152 | | #if GC_VERBOSE |
153 | | log("%d. handle %p", i, handle); |
154 | | log(" root %p", root); |
155 | | #endif |
156 | | |
157 | 1.50k | if (root) { // could be nullptr |
158 | 1.16k | auto header = ObjHeader(root); |
159 | | |
160 | | // This updates the underlying Str/List/Dict with a forwarding pointer, |
161 | | // i.e. for other objects that are pointing to it |
162 | 1.16k | Obj* new_location = Relocate(root, header); |
163 | | #if TODO_BUG |
164 | | for (int j = 0; j < roots_top_; ++j) { |
165 | | Obj** handle2 = roots_[j]; |
166 | | auto root2 = *handle2; |
167 | | if (root2) { |
168 | | switch (root2->heap_tag_) { |
169 | | case Tag::Forwarded: |
170 | | case Tag::Global: |
171 | | case Tag::Opaque: |
172 | | case Tag::FixedSize: |
173 | | case Tag::Scanned: |
174 | | break; |
175 | | default: |
176 | | assert(0); // NOTE(Jesse): Pretty sure this is InvalidCodePath(); |
177 | | } |
178 | | } |
179 | | } |
180 | | |
181 | | #endif |
182 | | |
183 | | // log(" new location %p", new_location); |
184 | | |
185 | | // This update is for the "double indirection", so future accesses to a |
186 | | // local variable use the new location |
187 | 1.16k | *handle = new_location; |
188 | 1.16k | } |
189 | 1.50k | } |
190 | | |
191 | 1.03k | while (scan < free_) { |
192 | 669 | auto obj = reinterpret_cast<Obj*>(scan); |
193 | 669 | auto header = ObjHeader(obj); |
194 | | |
195 | 669 | switch (header->heap_tag_) { |
196 | 125 | case Tag::FixedSize: { |
197 | 125 | auto fixed = reinterpret_cast<LayoutFixed*>(header); |
198 | 125 | int mask = fixed->field_mask_; |
199 | 2.12k | for (int i = 0; i < 16; ++i) { |
200 | 2.00k | if (mask & (1 << i)) { |
201 | 149 | Obj* child = fixed->children_[i]; |
202 | | // log("i = %d, p = %p, heap_tag = %d", i, child, child->heap_tag_); |
203 | 149 | if (child) { |
204 | 148 | auto child_header = ObjHeader(child); |
205 | | // log(" fixed: child %d from %p", i, child); |
206 | 148 | fixed->children_[i] = Relocate(child, child_header); |
207 | | // log(" to %p", fixed->children_[i]); |
208 | 148 | } |
209 | 149 | } |
210 | 2.00k | } |
211 | 125 | break; |
212 | 0 | } |
213 | 14 | case Tag::Scanned: { |
214 | 14 | assert(header == obj); // no inheritance |
215 | 0 | auto slab = reinterpret_cast<Slab<void*>*>(header); |
216 | 14 | int n = (slab->obj_len_ - kSlabHeaderSize) / sizeof(void*); |
217 | 208 | for (int i = 0; i < n; ++i) { |
218 | 194 | Obj* child = reinterpret_cast<Obj*>(slab->items_[i]); |
219 | 194 | if (child) { // note: List<> may have nullptr; Dict is sparse |
220 | 151 | auto child_header = ObjHeader(child); |
221 | 151 | slab->items_[i] = Relocate(child, child_header); |
222 | 151 | } |
223 | 194 | } |
224 | 14 | break; |
225 | 0 | } |
226 | 530 | default: { |
227 | 530 | assert(header->heap_tag_ == Tag::Forwarded || |
228 | 530 | header->heap_tag_ == Tag::Global || |
229 | 530 | header->heap_tag_ == Tag::Opaque); |
230 | 530 | } |
231 | | |
232 | | // other tags like Tag::Opaque have no children |
233 | 669 | } |
234 | | // aligned() like Heap::Allocate() |
235 | 669 | scan += aligned(header->obj_len_); |
236 | 669 | } |
237 | | |
238 | | // We just copied everything from_space_ -> to_space_. Maintain |
239 | | // invariant of the space we will allocate from next time. |
240 | 364 | from_space_.Clear(); |
241 | | #if GC_PROTECT |
242 | | from_space_.Protect(); |
243 | | // log("begin = %x", *from_space_.begin_); |
244 | | #endif |
245 | | |
246 | 364 | Swap(); |
247 | | |
248 | | #if GC_VERBOSE |
249 | | Report(); |
250 | | #endif |
251 | 364 | } |
252 | | |
253 | 1.08k | bool str_equals(Str* left, Str* right) { |
254 | | // Fast path for identical strings. String deduplication during GC could |
255 | | // make this more likely. String interning could guarantee it, allowing us |
256 | | // to remove memcmp(). |
257 | 1.08k | if (left == right) { |
258 | 21 | return true; |
259 | 21 | } |
260 | | // obj_len_ equal implies string lengths are equal |
261 | 1.06k | if (left->obj_len_ == right->obj_len_) { |
262 | 278 | return memcmp(left->data_, right->data_, len(left)) == 0; |
263 | 278 | } |
264 | 785 | return false; |
265 | 1.06k | } |
266 | | |
267 | 4 | bool maybe_str_equals(Str* left, Str* right) { |
268 | 4 | if (left && right) { |
269 | 2 | return str_equals(left, right); |
270 | 2 | } |
271 | | |
272 | 2 | if (!left && !right) { |
273 | 0 | return true; // None == None |
274 | 0 | } |
275 | | |
276 | 2 | return false; // one is None and one is a Str* |
277 | 2 | } |
278 | | |
279 | | #if GC_STATS |
280 | | void ShowFixedChildren(Obj* obj) { |
281 | | assert(obj->heap_tag_ == Tag::FixedSize); |
282 | | auto fixed = reinterpret_cast<LayoutFixed*>(obj); |
283 | | log("MASK:"); |
284 | | |
285 | | // Note: can this be optimized with the equivalent x & (x-1) trick? |
286 | | // We need the index |
287 | | // There is a de Brjuin sequence solution? |
288 | | // https://stackoverflow.com/questions/757059/position-of-least-significant-bit-that-is-set |
289 | | |
290 | | int mask = fixed->field_mask_; |
291 | | for (int i = 0; i < 16; ++i) { |
292 | | if (mask & (1 << i)) { |
293 | | Obj* child = fixed->children_[i]; |
294 | | if (child) { |
295 | | // make sure we get Tag::Opaque, Tag::Scanned, etc. |
296 | | log("i = %d, p = %p, heap_tag = %d", i, child, child->heap_tag_); |
297 | | } |
298 | | } |
299 | | } |
300 | | } |
301 | | #endif |
302 | | |
303 | | } // namespace gc_heap |