]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | /* |
2 | * Mark-and-sweep garbage collection. | |
3 | */ | |
4 | ||
5 | #include "duk_internal.h" | |
6 | ||
7 | #ifdef DUK_USE_MARK_AND_SWEEP | |
8 | ||
9 | DUK_LOCAL_DECL void duk__mark_heaphdr(duk_heap *heap, duk_heaphdr *h); | |
10 | DUK_LOCAL_DECL void duk__mark_tval(duk_heap *heap, duk_tval *tv); | |
11 | ||
12 | /* | |
13 | * Misc | |
14 | */ | |
15 | ||
16 | /* Select a thread for mark-and-sweep use. | |
17 | * | |
18 | * XXX: This needs to change later. | |
19 | */ | |
20 | DUK_LOCAL duk_hthread *duk__get_temp_hthread(duk_heap *heap) { | |
21 | if (heap->curr_thread) { | |
22 | return heap->curr_thread; | |
23 | } | |
24 | return heap->heap_thread; /* may be NULL, too */ | |
25 | } | |
26 | ||
27 | /* | |
28 | * Marking functions for heap types: mark children recursively | |
29 | */ | |
30 | ||
31 | DUK_LOCAL void duk__mark_hstring(duk_heap *heap, duk_hstring *h) { | |
32 | DUK_UNREF(heap); | |
33 | DUK_UNREF(h); | |
34 | ||
35 | DUK_DDD(DUK_DDDPRINT("duk__mark_hstring: %p", (void *) h)); | |
36 | DUK_ASSERT(h); | |
37 | ||
38 | /* nothing to process */ | |
39 | } | |
40 | ||
41 | DUK_LOCAL void duk__mark_hobject(duk_heap *heap, duk_hobject *h) { | |
42 | duk_uint_fast32_t i; | |
43 | ||
44 | DUK_DDD(DUK_DDDPRINT("duk__mark_hobject: %p", (void *) h)); | |
45 | ||
46 | DUK_ASSERT(h); | |
47 | ||
48 | /* XXX: use advancing pointers instead of index macros -> faster and smaller? */ | |
49 | ||
50 | for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(h); i++) { | |
51 | duk_hstring *key = DUK_HOBJECT_E_GET_KEY(heap, h, i); | |
52 | if (!key) { | |
53 | continue; | |
54 | } | |
55 | duk__mark_heaphdr(heap, (duk_heaphdr *) key); | |
56 | if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(heap, h, i)) { | |
57 | duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_PTR(heap, h, i)->a.get); | |
58 | duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_PTR(heap, h, i)->a.set); | |
59 | } else { | |
60 | duk__mark_tval(heap, &DUK_HOBJECT_E_GET_VALUE_PTR(heap, h, i)->v); | |
61 | } | |
62 | } | |
63 | ||
64 | for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ASIZE(h); i++) { | |
65 | duk__mark_tval(heap, DUK_HOBJECT_A_GET_VALUE_PTR(heap, h, i)); | |
66 | } | |
67 | ||
68 | /* hash part is a 'weak reference' and does not contribute */ | |
69 | ||
70 | duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HOBJECT_GET_PROTOTYPE(heap, h)); | |
71 | ||
72 | if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) { | |
73 | duk_hcompiledfunction *f = (duk_hcompiledfunction *) h; | |
74 | duk_tval *tv, *tv_end; | |
75 | duk_hobject **fn, **fn_end; | |
76 | ||
77 | /* 'data' is reachable through every compiled function which | |
78 | * contains a reference. | |
79 | */ | |
80 | ||
81 | duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HCOMPILEDFUNCTION_GET_DATA(heap, f)); | |
82 | ||
11fdf7f2 TL |
83 | if (DUK_HCOMPILEDFUNCTION_GET_DATA(heap, f) != NULL) { |
84 | tv = DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(heap, f); | |
85 | tv_end = DUK_HCOMPILEDFUNCTION_GET_CONSTS_END(heap, f); | |
86 | while (tv < tv_end) { | |
87 | duk__mark_tval(heap, tv); | |
88 | tv++; | |
89 | } | |
7c673cae | 90 | |
11fdf7f2 TL |
91 | fn = DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(heap, f); |
92 | fn_end = DUK_HCOMPILEDFUNCTION_GET_FUNCS_END(heap, f); | |
93 | while (fn < fn_end) { | |
94 | duk__mark_heaphdr(heap, (duk_heaphdr *) *fn); | |
95 | fn++; | |
96 | } | |
97 | } else { | |
98 | /* May happen in some out-of-memory corner cases. */ | |
99 | DUK_D(DUK_DPRINT("duk_hcompiledfunction 'data' is NULL, skipping marking")); | |
7c673cae FG |
100 | } |
101 | } else if (DUK_HOBJECT_IS_NATIVEFUNCTION(h)) { | |
102 | duk_hnativefunction *f = (duk_hnativefunction *) h; | |
103 | DUK_UNREF(f); | |
104 | /* nothing to mark */ | |
105 | } else if (DUK_HOBJECT_IS_BUFFEROBJECT(h)) { | |
106 | duk_hbufferobject *b = (duk_hbufferobject *) h; | |
107 | duk__mark_heaphdr(heap, (duk_heaphdr *) b->buf); | |
108 | } else if (DUK_HOBJECT_IS_THREAD(h)) { | |
109 | duk_hthread *t = (duk_hthread *) h; | |
110 | duk_tval *tv; | |
111 | ||
112 | tv = t->valstack; | |
11fdf7f2 | 113 | while (tv < t->valstack_top) { |
7c673cae FG |
114 | duk__mark_tval(heap, tv); |
115 | tv++; | |
116 | } | |
117 | ||
118 | for (i = 0; i < (duk_uint_fast32_t) t->callstack_top; i++) { | |
119 | duk_activation *act = t->callstack + i; | |
120 | duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_ACT_GET_FUNC(act)); | |
121 | duk__mark_heaphdr(heap, (duk_heaphdr *) act->var_env); | |
122 | duk__mark_heaphdr(heap, (duk_heaphdr *) act->lex_env); | |
123 | #ifdef DUK_USE_NONSTD_FUNC_CALLER_PROPERTY | |
124 | duk__mark_heaphdr(heap, (duk_heaphdr *) act->prev_caller); | |
125 | #endif | |
126 | } | |
127 | ||
128 | #if 0 /* nothing now */ | |
129 | for (i = 0; i < (duk_uint_fast32_t) t->catchstack_top; i++) { | |
130 | duk_catcher *cat = t->catchstack + i; | |
131 | } | |
132 | #endif | |
133 | ||
134 | duk__mark_heaphdr(heap, (duk_heaphdr *) t->resumer); | |
135 | ||
136 | /* XXX: duk_small_uint_t would be enough for this loop */ | |
137 | for (i = 0; i < DUK_NUM_BUILTINS; i++) { | |
138 | duk__mark_heaphdr(heap, (duk_heaphdr *) t->builtins[i]); | |
139 | } | |
140 | } | |
141 | } | |
142 | ||
143 | /* recursion tracking happens here only */ | |
144 | DUK_LOCAL void duk__mark_heaphdr(duk_heap *heap, duk_heaphdr *h) { | |
145 | DUK_DDD(DUK_DDDPRINT("duk__mark_heaphdr %p, type %ld", | |
146 | (void *) h, | |
147 | (h != NULL ? (long) DUK_HEAPHDR_GET_TYPE(h) : (long) -1))); | |
148 | if (!h) { | |
149 | return; | |
150 | } | |
11fdf7f2 TL |
151 | #if defined(DUK_USE_ROM_OBJECTS) |
152 | if (DUK_HEAPHDR_HAS_READONLY(h)) { | |
153 | DUK_DDD(DUK_DDDPRINT("readonly object %p, skip", (void *) h)); | |
154 | return; | |
155 | } | |
156 | #endif | |
7c673cae FG |
157 | if (DUK_HEAPHDR_HAS_REACHABLE(h)) { |
158 | DUK_DDD(DUK_DDDPRINT("already marked reachable, skip")); | |
159 | return; | |
160 | } | |
161 | DUK_HEAPHDR_SET_REACHABLE(h); | |
162 | ||
163 | if (heap->mark_and_sweep_recursion_depth >= DUK_USE_MARK_AND_SWEEP_RECLIMIT) { | |
164 | /* log this with a normal debug level because this should be relatively rare */ | |
165 | DUK_D(DUK_DPRINT("mark-and-sweep recursion limit reached, marking as temproot: %p", (void *) h)); | |
166 | DUK_HEAP_SET_MARKANDSWEEP_RECLIMIT_REACHED(heap); | |
167 | DUK_HEAPHDR_SET_TEMPROOT(h); | |
168 | return; | |
169 | } | |
170 | ||
171 | heap->mark_and_sweep_recursion_depth++; | |
172 | ||
173 | switch ((int) DUK_HEAPHDR_GET_TYPE(h)) { | |
174 | case DUK_HTYPE_STRING: | |
175 | duk__mark_hstring(heap, (duk_hstring *) h); | |
176 | break; | |
177 | case DUK_HTYPE_OBJECT: | |
178 | duk__mark_hobject(heap, (duk_hobject *) h); | |
179 | break; | |
180 | case DUK_HTYPE_BUFFER: | |
181 | /* nothing to mark */ | |
182 | break; | |
183 | default: | |
184 | DUK_D(DUK_DPRINT("attempt to mark heaphdr %p with invalid htype %ld", (void *) h, (long) DUK_HEAPHDR_GET_TYPE(h))); | |
185 | DUK_UNREACHABLE(); | |
186 | } | |
187 | ||
188 | heap->mark_and_sweep_recursion_depth--; | |
189 | } | |
190 | ||
191 | DUK_LOCAL void duk__mark_tval(duk_heap *heap, duk_tval *tv) { | |
192 | DUK_DDD(DUK_DDDPRINT("duk__mark_tval %p", (void *) tv)); | |
193 | if (!tv) { | |
194 | return; | |
195 | } | |
196 | if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) { | |
197 | duk__mark_heaphdr(heap, DUK_TVAL_GET_HEAPHDR(tv)); | |
198 | } | |
199 | } | |
200 | ||
201 | /* | |
202 | * Mark the heap. | |
203 | */ | |
204 | ||
205 | DUK_LOCAL void duk__mark_roots_heap(duk_heap *heap) { | |
206 | duk_small_uint_t i; | |
207 | ||
208 | DUK_DD(DUK_DDPRINT("duk__mark_roots_heap: %p", (void *) heap)); | |
209 | ||
210 | duk__mark_heaphdr(heap, (duk_heaphdr *) heap->heap_thread); | |
211 | duk__mark_heaphdr(heap, (duk_heaphdr *) heap->heap_object); | |
212 | ||
213 | for (i = 0; i < DUK_HEAP_NUM_STRINGS; i++) { | |
214 | duk_hstring *h = DUK_HEAP_GET_STRING(heap, i); | |
215 | duk__mark_heaphdr(heap, (duk_heaphdr *) h); | |
216 | } | |
217 | ||
218 | duk__mark_tval(heap, &heap->lj.value1); | |
219 | duk__mark_tval(heap, &heap->lj.value2); | |
220 | ||
221 | #if defined(DUK_USE_DEBUGGER_SUPPORT) | |
222 | for (i = 0; i < heap->dbg_breakpoint_count; i++) { | |
223 | duk__mark_heaphdr(heap, (duk_heaphdr *) heap->dbg_breakpoints[i].filename); | |
224 | } | |
225 | #endif | |
226 | } | |
227 | ||
228 | /* | |
229 | * Mark refzero_list objects. | |
230 | * | |
231 | * Objects on the refzero_list have no inbound references. They might have | |
232 | * outbound references to objects that we might free, which would invalidate | |
233 | * any references held by the refzero objects. A refzero object might also | |
234 | * be rescued by refcount finalization. Refzero objects are treated as | |
235 | * reachability roots to ensure they (or anything they point to) are not | |
236 | * freed in mark-and-sweep. | |
237 | */ | |
238 | ||
239 | #ifdef DUK_USE_REFERENCE_COUNTING | |
240 | DUK_LOCAL void duk__mark_refzero_list(duk_heap *heap) { | |
241 | duk_heaphdr *hdr; | |
242 | ||
243 | DUK_DD(DUK_DDPRINT("duk__mark_refzero_list: %p", (void *) heap)); | |
244 | ||
245 | hdr = heap->refzero_list; | |
246 | while (hdr) { | |
247 | duk__mark_heaphdr(heap, hdr); | |
248 | hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); | |
249 | } | |
250 | } | |
251 | #endif | |
252 | ||
253 | /* | |
254 | * Mark unreachable, finalizable objects. | |
255 | * | |
256 | * Such objects will be moved aside and their finalizers run later. They have | |
257 | * to be treated as reachability roots for their properties etc to remain | |
258 | * allocated. This marking is only done for unreachable values which would | |
259 | * be swept later (refzero_list is thus excluded). | |
260 | * | |
261 | * Objects are first marked FINALIZABLE and only then marked as reachability | |
262 | * roots; otherwise circular references might be handled inconsistently. | |
263 | */ | |
264 | ||
265 | DUK_LOCAL void duk__mark_finalizable(duk_heap *heap) { | |
266 | duk_hthread *thr; | |
267 | duk_heaphdr *hdr; | |
268 | duk_size_t count_finalizable = 0; | |
269 | ||
270 | DUK_DD(DUK_DDPRINT("duk__mark_finalizable: %p", (void *) heap)); | |
271 | ||
272 | thr = duk__get_temp_hthread(heap); | |
273 | DUK_ASSERT(thr != NULL); | |
274 | ||
275 | hdr = heap->heap_allocated; | |
276 | while (hdr) { | |
277 | /* A finalizer is looked up from the object and up its prototype chain | |
278 | * (which allows inherited finalizers). A prototype loop must not cause | |
279 | * an error to be thrown here; duk_hobject_hasprop_raw() will ignore a | |
280 | * prototype loop silently and indicate that the property doesn't exist. | |
281 | */ | |
282 | ||
283 | if (!DUK_HEAPHDR_HAS_REACHABLE(hdr) && | |
284 | DUK_HEAPHDR_GET_TYPE(hdr) == DUK_HTYPE_OBJECT && | |
285 | !DUK_HEAPHDR_HAS_FINALIZED(hdr) && | |
286 | duk_hobject_hasprop_raw(thr, (duk_hobject *) hdr, DUK_HTHREAD_STRING_INT_FINALIZER(thr))) { | |
287 | ||
288 | /* heaphdr: | |
289 | * - is not reachable | |
290 | * - is an object | |
291 | * - is not a finalized object | |
292 | * - has a finalizer | |
293 | */ | |
294 | ||
295 | DUK_DD(DUK_DDPRINT("unreachable heap object will be " | |
296 | "finalized -> mark as finalizable " | |
297 | "and treat as a reachability root: %p", | |
298 | (void *) hdr)); | |
11fdf7f2 | 299 | DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY(hdr)); |
7c673cae FG |
300 | DUK_HEAPHDR_SET_FINALIZABLE(hdr); |
301 | count_finalizable ++; | |
302 | } | |
303 | ||
304 | hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); | |
305 | } | |
306 | ||
307 | if (count_finalizable == 0) { | |
308 | return; | |
309 | } | |
310 | ||
311 | DUK_DD(DUK_DDPRINT("marked %ld heap objects as finalizable, now mark them reachable", | |
312 | (long) count_finalizable)); | |
313 | ||
314 | hdr = heap->heap_allocated; | |
315 | while (hdr) { | |
316 | if (DUK_HEAPHDR_HAS_FINALIZABLE(hdr)) { | |
317 | duk__mark_heaphdr(heap, hdr); | |
318 | } | |
319 | ||
320 | hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); | |
321 | } | |
322 | ||
323 | /* Caller will finish the marking process if we hit a recursion limit. */ | |
324 | } | |
325 | ||
326 | /* | |
327 | * Mark objects on finalize_list. | |
328 | * | |
329 | */ | |
330 | ||
331 | DUK_LOCAL void duk__mark_finalize_list(duk_heap *heap) { | |
332 | duk_heaphdr *hdr; | |
333 | #ifdef DUK_USE_DEBUG | |
334 | duk_size_t count_finalize_list = 0; | |
335 | #endif | |
336 | ||
337 | DUK_DD(DUK_DDPRINT("duk__mark_finalize_list: %p", (void *) heap)); | |
338 | ||
339 | hdr = heap->finalize_list; | |
340 | while (hdr) { | |
341 | duk__mark_heaphdr(heap, hdr); | |
342 | hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); | |
343 | #ifdef DUK_USE_DEBUG | |
344 | count_finalize_list++; | |
345 | #endif | |
346 | } | |
347 | ||
348 | #ifdef DUK_USE_DEBUG | |
349 | if (count_finalize_list > 0) { | |
350 | DUK_D(DUK_DPRINT("marked %ld objects on the finalize_list as reachable (previous finalizer run skipped)", | |
351 | (long) count_finalize_list)); | |
352 | } | |
353 | #endif | |
354 | } | |
355 | ||
356 | /* | |
357 | * Fallback marking handler if recursion limit is reached. | |
358 | * | |
359 | * Iterates 'temproots' until recursion limit is no longer hit. Note | |
360 | * that temproots may reside either in heap allocated list or the | |
361 | * refzero work list. This is a slow scan, but guarantees that we | |
362 | * finish with a bounded C stack. | |
363 | * | |
364 | * Note that nodes may have been marked as temproots before this | |
365 | * scan begun, OR they may have been marked during the scan (as | |
366 | * we process nodes recursively also during the scan). This is | |
367 | * intended behavior. | |
368 | */ | |
369 | ||
370 | #ifdef DUK_USE_DEBUG | |
371 | DUK_LOCAL void duk__handle_temproot(duk_heap *heap, duk_heaphdr *hdr, duk_size_t *count) { | |
372 | #else | |
373 | DUK_LOCAL void duk__handle_temproot(duk_heap *heap, duk_heaphdr *hdr) { | |
374 | #endif | |
375 | if (!DUK_HEAPHDR_HAS_TEMPROOT(hdr)) { | |
376 | DUK_DDD(DUK_DDDPRINT("not a temp root: %p", (void *) hdr)); | |
377 | return; | |
378 | } | |
379 | ||
380 | DUK_DDD(DUK_DDDPRINT("found a temp root: %p", (void *) hdr)); | |
381 | DUK_HEAPHDR_CLEAR_TEMPROOT(hdr); | |
382 | DUK_HEAPHDR_CLEAR_REACHABLE(hdr); /* done so that duk__mark_heaphdr() works correctly */ | |
383 | duk__mark_heaphdr(heap, hdr); | |
384 | ||
385 | #ifdef DUK_USE_DEBUG | |
386 | (*count)++; | |
387 | #endif | |
388 | } | |
389 | ||
390 | DUK_LOCAL void duk__mark_temproots_by_heap_scan(duk_heap *heap) { | |
391 | duk_heaphdr *hdr; | |
392 | #ifdef DUK_USE_DEBUG | |
393 | duk_size_t count; | |
394 | #endif | |
395 | ||
396 | DUK_DD(DUK_DDPRINT("duk__mark_temproots_by_heap_scan: %p", (void *) heap)); | |
397 | ||
398 | while (DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap)) { | |
399 | DUK_DD(DUK_DDPRINT("recursion limit reached, doing heap scan to continue from temproots")); | |
400 | ||
401 | #ifdef DUK_USE_DEBUG | |
402 | count = 0; | |
403 | #endif | |
404 | DUK_HEAP_CLEAR_MARKANDSWEEP_RECLIMIT_REACHED(heap); | |
405 | ||
406 | hdr = heap->heap_allocated; | |
407 | while (hdr) { | |
408 | #ifdef DUK_USE_DEBUG | |
409 | duk__handle_temproot(heap, hdr, &count); | |
410 | #else | |
411 | duk__handle_temproot(heap, hdr); | |
412 | #endif | |
413 | hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); | |
414 | } | |
415 | ||
416 | /* must also check refzero_list */ | |
417 | #ifdef DUK_USE_REFERENCE_COUNTING | |
418 | hdr = heap->refzero_list; | |
419 | while (hdr) { | |
420 | #ifdef DUK_USE_DEBUG | |
421 | duk__handle_temproot(heap, hdr, &count); | |
422 | #else | |
423 | duk__handle_temproot(heap, hdr); | |
424 | #endif | |
425 | hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); | |
426 | } | |
427 | #endif /* DUK_USE_REFERENCE_COUNTING */ | |
428 | ||
429 | #ifdef DUK_USE_DEBUG | |
430 | DUK_DD(DUK_DDPRINT("temproot mark heap scan processed %ld temp roots", (long) count)); | |
431 | #endif | |
432 | } | |
433 | } | |
434 | ||
435 | /* | |
436 | * Finalize refcounts for heap elements just about to be freed. | |
437 | * This must be done for all objects before freeing to avoid any | |
438 | * stale pointer dereferences. | |
439 | * | |
440 | * Note that this must deduce the set of objects to be freed | |
441 | * identically to duk__sweep_heap(). | |
442 | */ | |
443 | ||
444 | #ifdef DUK_USE_REFERENCE_COUNTING | |
445 | DUK_LOCAL void duk__finalize_refcounts(duk_heap *heap) { | |
446 | duk_hthread *thr; | |
447 | duk_heaphdr *hdr; | |
448 | ||
449 | thr = duk__get_temp_hthread(heap); | |
450 | DUK_ASSERT(thr != NULL); | |
451 | ||
452 | DUK_DD(DUK_DDPRINT("duk__finalize_refcounts: heap=%p, hthread=%p", | |
453 | (void *) heap, (void *) thr)); | |
454 | ||
455 | hdr = heap->heap_allocated; | |
456 | while (hdr) { | |
457 | if (!DUK_HEAPHDR_HAS_REACHABLE(hdr)) { | |
458 | /* | |
459 | * Unreachable object about to be swept. Finalize target refcounts | |
460 | * (objects which the unreachable object points to) without doing | |
461 | * refzero processing. Recursive decrefs are also prevented when | |
462 | * refzero processing is disabled. | |
463 | * | |
464 | * Value cannot be a finalizable object, as they have been made | |
465 | * temporarily reachable for this round. | |
466 | */ | |
467 | ||
468 | DUK_DDD(DUK_DDDPRINT("unreachable object, refcount finalize before sweeping: %p", (void *) hdr)); | |
469 | duk_heaphdr_refcount_finalize(thr, hdr); | |
470 | } | |
471 | ||
472 | hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); | |
473 | } | |
474 | } | |
475 | #endif /* DUK_USE_REFERENCE_COUNTING */ | |
476 | ||
477 | /* | |
478 | * Clear (reachable) flags of refzero work list. | |
479 | */ | |
480 | ||
481 | #ifdef DUK_USE_REFERENCE_COUNTING | |
482 | DUK_LOCAL void duk__clear_refzero_list_flags(duk_heap *heap) { | |
483 | duk_heaphdr *hdr; | |
484 | ||
485 | DUK_DD(DUK_DDPRINT("duk__clear_refzero_list_flags: %p", (void *) heap)); | |
486 | ||
487 | hdr = heap->refzero_list; | |
488 | while (hdr) { | |
489 | DUK_HEAPHDR_CLEAR_REACHABLE(hdr); | |
490 | DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(hdr)); | |
491 | DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(hdr)); | |
492 | DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(hdr)); | |
493 | hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); | |
494 | } | |
495 | } | |
496 | #endif /* DUK_USE_REFERENCE_COUNTING */ | |
497 | ||
498 | /* | |
499 | * Clear (reachable) flags of finalize_list | |
500 | * | |
501 | * We could mostly do in the sweep phase when we move objects from the | |
502 | * heap into the finalize_list. However, if a finalizer run is skipped | |
503 | * during a mark-and-sweep, the objects on the finalize_list will be marked | |
504 | * reachable during the next mark-and-sweep. Since they're already on the | |
505 | * finalize_list, no-one will be clearing their REACHABLE flag so we do it | |
506 | * here. (This now overlaps with the sweep handling in a harmless way.) | |
507 | */ | |
508 | ||
509 | DUK_LOCAL void duk__clear_finalize_list_flags(duk_heap *heap) { | |
510 | duk_heaphdr *hdr; | |
511 | ||
512 | DUK_DD(DUK_DDPRINT("duk__clear_finalize_list_flags: %p", (void *) heap)); | |
513 | ||
514 | hdr = heap->finalize_list; | |
515 | while (hdr) { | |
516 | DUK_HEAPHDR_CLEAR_REACHABLE(hdr); | |
517 | DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(hdr)); | |
518 | DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(hdr)); | |
519 | DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(hdr)); | |
520 | hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); | |
521 | } | |
522 | } | |
523 | ||
524 | /* | |
525 | * Sweep stringtable | |
526 | */ | |
527 | ||
528 | #if defined(DUK_USE_STRTAB_CHAIN) | |
529 | ||
530 | /* XXX: skip count_free w/o debug? */ | |
531 | #if defined(DUK_USE_HEAPPTR16) | |
532 | DUK_LOCAL void duk__sweep_string_chain16(duk_heap *heap, duk_uint16_t *slot, duk_size_t *count_keep, duk_size_t *count_free) { | |
533 | duk_uint16_t h16 = *slot; | |
534 | duk_hstring *h; | |
535 | duk_uint16_t null16 = heap->heapptr_null16; | |
536 | ||
537 | if (h16 == null16) { | |
538 | /* nop */ | |
539 | return; | |
540 | } | |
541 | h = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, h16); | |
542 | DUK_ASSERT(h != NULL); | |
543 | ||
544 | if (DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h)) { | |
545 | DUK_HEAPHDR_CLEAR_REACHABLE((duk_heaphdr *) h); | |
546 | (*count_keep)++; | |
547 | } else { | |
548 | #if defined(DUK_USE_REFERENCE_COUNTING) | |
549 | DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h) == 0); | |
550 | #endif | |
551 | /* deal with weak references first */ | |
552 | duk_heap_strcache_string_remove(heap, (duk_hstring *) h); | |
553 | *slot = null16; | |
554 | ||
555 | /* free inner references (these exist e.g. when external | |
556 | * strings are enabled) | |
557 | */ | |
558 | duk_free_hstring_inner(heap, h); | |
559 | DUK_FREE(heap, h); | |
560 | (*count_free)++; | |
561 | } | |
562 | } | |
563 | #else /* DUK_USE_HEAPPTR16 */ | |
564 | DUK_LOCAL void duk__sweep_string_chain(duk_heap *heap, duk_hstring **slot, duk_size_t *count_keep, duk_size_t *count_free) { | |
565 | duk_hstring *h = *slot; | |
566 | ||
567 | if (h == NULL) { | |
568 | /* nop */ | |
569 | return; | |
570 | } | |
571 | ||
572 | if (DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h)) { | |
573 | DUK_HEAPHDR_CLEAR_REACHABLE((duk_heaphdr *) h); | |
574 | (*count_keep)++; | |
575 | } else { | |
576 | #if defined(DUK_USE_REFERENCE_COUNTING) | |
577 | DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h) == 0); | |
578 | #endif | |
579 | /* deal with weak references first */ | |
580 | duk_heap_strcache_string_remove(heap, (duk_hstring *) h); | |
581 | *slot = NULL; | |
582 | ||
583 | /* free inner references (these exist e.g. when external | |
584 | * strings are enabled) | |
585 | */ | |
586 | duk_free_hstring_inner(heap, h); | |
587 | DUK_FREE(heap, h); | |
588 | (*count_free)++; | |
589 | } | |
590 | } | |
591 | #endif /* DUK_USE_HEAPPTR16 */ | |
592 | ||
593 | DUK_LOCAL void duk__sweep_stringtable_chain(duk_heap *heap, duk_size_t *out_count_keep) { | |
594 | duk_strtab_entry *e; | |
595 | duk_uint_fast32_t i; | |
596 | duk_size_t count_free = 0; | |
597 | duk_size_t count_keep = 0; | |
598 | duk_size_t j, n; | |
599 | #if defined(DUK_USE_HEAPPTR16) | |
600 | duk_uint16_t *lst; | |
601 | #else | |
602 | duk_hstring **lst; | |
603 | #endif | |
604 | ||
605 | DUK_DD(DUK_DDPRINT("duk__sweep_stringtable: %p", (void *) heap)); | |
606 | ||
607 | /* Non-zero refcounts should not happen for unreachable strings, | |
608 | * because we refcount finalize all unreachable objects which | |
609 | * should have decreased unreachable string refcounts to zero | |
610 | * (even for cycles). | |
611 | */ | |
612 | ||
613 | for (i = 0; i < DUK_STRTAB_CHAIN_SIZE; i++) { | |
614 | e = heap->strtable + i; | |
615 | if (e->listlen == 0) { | |
616 | #if defined(DUK_USE_HEAPPTR16) | |
617 | duk__sweep_string_chain16(heap, &e->u.str16, &count_keep, &count_free); | |
618 | #else | |
619 | duk__sweep_string_chain(heap, &e->u.str, &count_keep, &count_free); | |
620 | #endif | |
621 | } else { | |
622 | #if defined(DUK_USE_HEAPPTR16) | |
623 | lst = (duk_uint16_t *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.strlist16); | |
624 | #else | |
625 | lst = e->u.strlist; | |
626 | #endif | |
627 | for (j = 0, n = e->listlen; j < n; j++) { | |
628 | #if defined(DUK_USE_HEAPPTR16) | |
629 | duk__sweep_string_chain16(heap, lst + j, &count_keep, &count_free); | |
630 | #else | |
631 | duk__sweep_string_chain(heap, lst + j, &count_keep, &count_free); | |
632 | #endif | |
633 | } | |
634 | } | |
635 | } | |
636 | ||
637 | DUK_D(DUK_DPRINT("mark-and-sweep sweep stringtable: %ld freed, %ld kept", | |
638 | (long) count_free, (long) count_keep)); | |
639 | *out_count_keep = count_keep; | |
640 | } | |
641 | #endif /* DUK_USE_STRTAB_CHAIN */ | |
642 | ||
643 | #if defined(DUK_USE_STRTAB_PROBE) | |
644 | DUK_LOCAL void duk__sweep_stringtable_probe(duk_heap *heap, duk_size_t *out_count_keep) { | |
645 | duk_hstring *h; | |
646 | duk_uint_fast32_t i; | |
647 | #ifdef DUK_USE_DEBUG | |
648 | duk_size_t count_free = 0; | |
649 | #endif | |
650 | duk_size_t count_keep = 0; | |
651 | ||
652 | DUK_DD(DUK_DDPRINT("duk__sweep_stringtable: %p", (void *) heap)); | |
653 | ||
654 | for (i = 0; i < heap->st_size; i++) { | |
655 | #if defined(DUK_USE_HEAPPTR16) | |
11fdf7f2 | 656 | h = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, heap->strtable16[i]); |
7c673cae FG |
657 | #else |
658 | h = heap->strtable[i]; | |
659 | #endif | |
660 | if (h == NULL || h == DUK_STRTAB_DELETED_MARKER(heap)) { | |
661 | continue; | |
662 | } else if (DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h)) { | |
663 | DUK_HEAPHDR_CLEAR_REACHABLE((duk_heaphdr *) h); | |
664 | count_keep++; | |
665 | continue; | |
666 | } | |
667 | ||
668 | #ifdef DUK_USE_DEBUG | |
669 | count_free++; | |
670 | #endif | |
671 | ||
672 | #if defined(DUK_USE_REFERENCE_COUNTING) | |
673 | /* Non-zero refcounts should not happen for unreachable strings, | |
674 | * because we refcount finalize all unreachable objects which | |
675 | * should have decreased unreachable string refcounts to zero | |
676 | * (even for cycles). | |
677 | */ | |
678 | DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h) == 0); | |
679 | #endif | |
680 | ||
681 | DUK_DDD(DUK_DDDPRINT("sweep string, not reachable: %p", (void *) h)); | |
682 | ||
683 | /* deal with weak references first */ | |
684 | duk_heap_strcache_string_remove(heap, (duk_hstring *) h); | |
685 | ||
686 | /* remove the string (mark DELETED), could also call | |
687 | * duk_heap_string_remove() but that would be slow and | |
688 | * pointless because we already know the slot. | |
689 | */ | |
690 | #if defined(DUK_USE_HEAPPTR16) | |
691 | heap->strtable16[i] = heap->heapptr_deleted16; | |
692 | #else | |
693 | heap->strtable[i] = DUK_STRTAB_DELETED_MARKER(heap); | |
694 | #endif | |
695 | ||
696 | /* free inner references (these exist e.g. when external | |
697 | * strings are enabled) | |
698 | */ | |
699 | duk_free_hstring_inner(heap, (duk_hstring *) h); | |
700 | ||
701 | /* finally free the struct itself */ | |
702 | DUK_FREE(heap, h); | |
703 | } | |
704 | ||
705 | #ifdef DUK_USE_DEBUG | |
706 | DUK_D(DUK_DPRINT("mark-and-sweep sweep stringtable: %ld freed, %ld kept", | |
707 | (long) count_free, (long) count_keep)); | |
708 | #endif | |
709 | *out_count_keep = count_keep; | |
710 | } | |
711 | #endif /* DUK_USE_STRTAB_PROBE */ | |
712 | ||
713 | /* | |
714 | * Sweep heap | |
715 | */ | |
716 | ||
717 | DUK_LOCAL void duk__sweep_heap(duk_heap *heap, duk_int_t flags, duk_size_t *out_count_keep) { | |
718 | duk_heaphdr *prev; /* last element that was left in the heap */ | |
719 | duk_heaphdr *curr; | |
720 | duk_heaphdr *next; | |
721 | #ifdef DUK_USE_DEBUG | |
722 | duk_size_t count_free = 0; | |
723 | duk_size_t count_finalize = 0; | |
724 | duk_size_t count_rescue = 0; | |
725 | #endif | |
726 | duk_size_t count_keep = 0; | |
727 | ||
728 | DUK_UNREF(flags); | |
729 | DUK_DD(DUK_DDPRINT("duk__sweep_heap: %p", (void *) heap)); | |
730 | ||
731 | prev = NULL; | |
732 | curr = heap->heap_allocated; | |
733 | heap->heap_allocated = NULL; | |
734 | while (curr) { | |
11fdf7f2 | 735 | /* Strings and ROM objects are never placed on the heap allocated list. */ |
7c673cae | 736 | DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) != DUK_HTYPE_STRING); |
11fdf7f2 | 737 | DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY(curr)); |
7c673cae FG |
738 | |
739 | next = DUK_HEAPHDR_GET_NEXT(heap, curr); | |
740 | ||
741 | if (DUK_HEAPHDR_HAS_REACHABLE(curr)) { | |
742 | /* | |
743 | * Reachable object, keep | |
744 | */ | |
745 | ||
746 | DUK_DDD(DUK_DDDPRINT("sweep, reachable: %p", (void *) curr)); | |
747 | ||
748 | if (DUK_HEAPHDR_HAS_FINALIZABLE(curr)) { | |
749 | /* | |
750 | * If object has been marked finalizable, move it to the | |
751 | * "to be finalized" work list. It will be collected on | |
752 | * the next mark-and-sweep if it is still unreachable | |
753 | * after running the finalizer. | |
754 | */ | |
755 | ||
756 | DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr)); | |
757 | DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT); | |
758 | DUK_DDD(DUK_DDDPRINT("object has finalizer, move to finalization work list: %p", (void *) curr)); | |
759 | ||
760 | #ifdef DUK_USE_DOUBLE_LINKED_HEAP | |
761 | if (heap->finalize_list) { | |
762 | DUK_HEAPHDR_SET_PREV(heap, heap->finalize_list, curr); | |
763 | } | |
764 | DUK_HEAPHDR_SET_PREV(heap, curr, NULL); | |
765 | #endif | |
766 | DUK_HEAPHDR_SET_NEXT(heap, curr, heap->finalize_list); | |
11fdf7f2 | 767 | DUK_ASSERT_HEAPHDR_LINKS(heap, curr); |
7c673cae FG |
768 | heap->finalize_list = curr; |
769 | #ifdef DUK_USE_DEBUG | |
770 | count_finalize++; | |
771 | #endif | |
772 | } else { | |
773 | /* | |
774 | * Object will be kept; queue object back to heap_allocated (to tail) | |
775 | */ | |
776 | ||
777 | if (DUK_HEAPHDR_HAS_FINALIZED(curr)) { | |
778 | /* | |
779 | * Object's finalizer was executed on last round, and | |
780 | * object has been happily rescued. | |
781 | */ | |
782 | ||
783 | DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr)); | |
784 | DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT); | |
785 | DUK_DD(DUK_DDPRINT("object rescued during mark-and-sweep finalization: %p", (void *) curr)); | |
786 | #ifdef DUK_USE_DEBUG | |
787 | count_rescue++; | |
788 | #endif | |
789 | } else { | |
790 | /* | |
791 | * Plain, boring reachable object. | |
792 | */ | |
11fdf7f2 | 793 | DUK_DD(DUK_DDPRINT("keep object: %!iO", curr)); |
7c673cae FG |
794 | count_keep++; |
795 | } | |
796 | ||
797 | if (!heap->heap_allocated) { | |
798 | heap->heap_allocated = curr; | |
799 | } | |
800 | if (prev) { | |
801 | DUK_HEAPHDR_SET_NEXT(heap, prev, curr); | |
802 | } | |
803 | #ifdef DUK_USE_DOUBLE_LINKED_HEAP | |
804 | DUK_HEAPHDR_SET_PREV(heap, curr, prev); | |
805 | #endif | |
11fdf7f2 TL |
806 | DUK_ASSERT_HEAPHDR_LINKS(heap, prev); |
807 | DUK_ASSERT_HEAPHDR_LINKS(heap, curr); | |
7c673cae FG |
808 | prev = curr; |
809 | } | |
810 | ||
811 | DUK_HEAPHDR_CLEAR_REACHABLE(curr); | |
812 | DUK_HEAPHDR_CLEAR_FINALIZED(curr); | |
813 | DUK_HEAPHDR_CLEAR_FINALIZABLE(curr); | |
814 | ||
815 | DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(curr)); | |
816 | DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr)); | |
817 | DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr)); | |
818 | ||
819 | curr = next; | |
820 | } else { | |
821 | /* | |
822 | * Unreachable object, free | |
823 | */ | |
824 | ||
825 | DUK_DDD(DUK_DDDPRINT("sweep, not reachable: %p", (void *) curr)); | |
826 | ||
827 | #if defined(DUK_USE_REFERENCE_COUNTING) | |
828 | /* Non-zero refcounts should not happen because we refcount | |
829 | * finalize all unreachable objects which should cancel out | |
830 | * refcounts (even for cycles). | |
831 | */ | |
832 | DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(curr) == 0); | |
833 | #endif | |
834 | DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr)); | |
835 | ||
836 | if (DUK_HEAPHDR_HAS_FINALIZED(curr)) { | |
837 | DUK_DDD(DUK_DDDPRINT("finalized object not rescued: %p", (void *) curr)); | |
838 | } | |
839 | ||
840 | /* Note: object cannot be a finalizable unreachable object, as | |
841 | * they have been marked temporarily reachable for this round, | |
842 | * and are handled above. | |
843 | */ | |
844 | ||
845 | #ifdef DUK_USE_DEBUG | |
846 | count_free++; | |
847 | #endif | |
848 | ||
849 | /* weak refs should be handled here, but no weak refs for | |
850 | * any non-string objects exist right now. | |
851 | */ | |
852 | ||
853 | /* free object and all auxiliary (non-heap) allocs */ | |
854 | duk_heap_free_heaphdr_raw(heap, curr); | |
855 | ||
856 | curr = next; | |
857 | } | |
858 | } | |
859 | if (prev) { | |
860 | DUK_HEAPHDR_SET_NEXT(heap, prev, NULL); | |
861 | } | |
11fdf7f2 | 862 | DUK_ASSERT_HEAPHDR_LINKS(heap, prev); |
7c673cae FG |
863 | |
864 | #ifdef DUK_USE_DEBUG | |
865 | DUK_D(DUK_DPRINT("mark-and-sweep sweep objects (non-string): %ld freed, %ld kept, %ld rescued, %ld queued for finalization", | |
866 | (long) count_free, (long) count_keep, (long) count_rescue, (long) count_finalize)); | |
867 | #endif | |
868 | *out_count_keep = count_keep; | |
869 | } | |
870 | ||
871 | /* | |
872 | * Run (object) finalizers in the "to be finalized" work list. | |
873 | */ | |
874 | ||
11fdf7f2 | 875 | DUK_LOCAL void duk__run_object_finalizers(duk_heap *heap, duk_small_uint_t flags) { |
7c673cae FG |
876 | duk_heaphdr *curr; |
877 | duk_heaphdr *next; | |
878 | #ifdef DUK_USE_DEBUG | |
879 | duk_size_t count = 0; | |
880 | #endif | |
881 | duk_hthread *thr; | |
882 | ||
883 | DUK_DD(DUK_DDPRINT("duk__run_object_finalizers: %p", (void *) heap)); | |
884 | ||
885 | thr = duk__get_temp_hthread(heap); | |
886 | DUK_ASSERT(thr != NULL); | |
887 | ||
888 | curr = heap->finalize_list; | |
889 | while (curr) { | |
890 | DUK_DDD(DUK_DDDPRINT("mark-and-sweep finalize: %p", (void *) curr)); | |
891 | ||
892 | DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT); /* only objects have finalizers */ | |
893 | DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(curr)); /* flags have been already cleared */ | |
894 | DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(curr)); | |
895 | DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr)); | |
896 | DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr)); | |
11fdf7f2 | 897 | DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY(curr)); /* No finalizers for ROM objects */ |
7c673cae | 898 | |
11fdf7f2 TL |
899 | if (DUK_LIKELY((flags & DUK_MS_FLAG_SKIP_FINALIZERS) == 0)) { |
900 | /* Run the finalizer, duk_hobject_run_finalizer() sets FINALIZED. | |
901 | * Next mark-and-sweep will collect the object unless it has | |
902 | * become reachable (i.e. rescued). FINALIZED prevents the | |
903 | * finalizer from being executed again before that. | |
904 | */ | |
905 | duk_hobject_run_finalizer(thr, (duk_hobject *) curr); /* must never longjmp */ | |
906 | DUK_ASSERT(DUK_HEAPHDR_HAS_FINALIZED(curr)); | |
907 | } else { | |
908 | /* Used during heap destruction: don't actually run finalizers | |
909 | * because we're heading into forced finalization. Instead, | |
910 | * queue finalizable objects back to the heap_allocated list. | |
911 | */ | |
912 | DUK_D(DUK_DPRINT("skip finalizers flag set, queue object to heap_allocated without finalizing")); | |
913 | DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr)); | |
914 | } | |
7c673cae FG |
915 | |
916 | /* queue back to heap_allocated */ | |
917 | next = DUK_HEAPHDR_GET_NEXT(heap, curr); | |
918 | DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, curr); | |
919 | ||
920 | curr = next; | |
921 | #ifdef DUK_USE_DEBUG | |
922 | count++; | |
923 | #endif | |
924 | } | |
925 | ||
926 | /* finalize_list will always be processed completely */ | |
927 | heap->finalize_list = NULL; | |
928 | ||
929 | #ifdef DUK_USE_DEBUG | |
930 | DUK_D(DUK_DPRINT("mark-and-sweep finalize objects: %ld finalizers called", (long) count)); | |
931 | #endif | |
932 | } | |
933 | ||
934 | /* | |
935 | * Object compaction. | |
936 | * | |
937 | * Compaction is assumed to never throw an error. | |
938 | */ | |
939 | ||
940 | DUK_LOCAL int duk__protected_compact_object(duk_context *ctx) { | |
941 | /* XXX: for threads, compact value stack, call stack, catch stack? */ | |
942 | ||
943 | duk_hobject *obj = duk_get_hobject(ctx, -1); | |
944 | DUK_ASSERT(obj != NULL); | |
945 | duk_hobject_compact_props((duk_hthread *) ctx, obj); | |
946 | return 0; | |
947 | } | |
948 | ||
949 | #ifdef DUK_USE_DEBUG | |
950 | DUK_LOCAL void duk__compact_object_list(duk_heap *heap, duk_hthread *thr, duk_heaphdr *start, duk_size_t *p_count_check, duk_size_t *p_count_compact, duk_size_t *p_count_bytes_saved) { | |
951 | #else | |
952 | DUK_LOCAL void duk__compact_object_list(duk_heap *heap, duk_hthread *thr, duk_heaphdr *start) { | |
953 | #endif | |
954 | duk_heaphdr *curr; | |
955 | #ifdef DUK_USE_DEBUG | |
956 | duk_size_t old_size, new_size; | |
957 | #endif | |
958 | duk_hobject *obj; | |
959 | ||
960 | DUK_UNREF(heap); | |
961 | ||
962 | curr = start; | |
963 | while (curr) { | |
964 | DUK_DDD(DUK_DDDPRINT("mark-and-sweep compact: %p", (void *) curr)); | |
965 | ||
966 | if (DUK_HEAPHDR_GET_TYPE(curr) != DUK_HTYPE_OBJECT) { | |
967 | goto next; | |
968 | } | |
969 | obj = (duk_hobject *) curr; | |
970 | ||
971 | #ifdef DUK_USE_DEBUG | |
972 | old_size = DUK_HOBJECT_P_COMPUTE_SIZE(DUK_HOBJECT_GET_ESIZE(obj), | |
973 | DUK_HOBJECT_GET_ASIZE(obj), | |
974 | DUK_HOBJECT_GET_HSIZE(obj)); | |
975 | #endif | |
976 | ||
977 | DUK_DD(DUK_DDPRINT("compact object: %p", (void *) obj)); | |
978 | duk_push_hobject((duk_context *) thr, obj); | |
979 | /* XXX: disable error handlers for duration of compaction? */ | |
980 | duk_safe_call((duk_context *) thr, duk__protected_compact_object, 1, 0); | |
981 | ||
982 | #ifdef DUK_USE_DEBUG | |
983 | new_size = DUK_HOBJECT_P_COMPUTE_SIZE(DUK_HOBJECT_GET_ESIZE(obj), | |
984 | DUK_HOBJECT_GET_ASIZE(obj), | |
985 | DUK_HOBJECT_GET_HSIZE(obj)); | |
986 | #endif | |
987 | ||
988 | #ifdef DUK_USE_DEBUG | |
989 | (*p_count_compact)++; | |
990 | (*p_count_bytes_saved) += (duk_size_t) (old_size - new_size); | |
991 | #endif | |
992 | ||
993 | next: | |
994 | curr = DUK_HEAPHDR_GET_NEXT(heap, curr); | |
995 | #ifdef DUK_USE_DEBUG | |
996 | (*p_count_check)++; | |
997 | #endif | |
998 | } | |
999 | } | |
1000 | ||
1001 | DUK_LOCAL void duk__compact_objects(duk_heap *heap) { | |
1002 | /* XXX: which lists should participate? to be finalized? */ | |
1003 | #ifdef DUK_USE_DEBUG | |
1004 | duk_size_t count_check = 0; | |
1005 | duk_size_t count_compact = 0; | |
1006 | duk_size_t count_bytes_saved = 0; | |
1007 | #endif | |
1008 | duk_hthread *thr; | |
1009 | ||
1010 | DUK_DD(DUK_DDPRINT("duk__compact_objects: %p", (void *) heap)); | |
1011 | ||
1012 | thr = duk__get_temp_hthread(heap); | |
1013 | DUK_ASSERT(thr != NULL); | |
1014 | ||
1015 | #ifdef DUK_USE_DEBUG | |
1016 | duk__compact_object_list(heap, thr, heap->heap_allocated, &count_check, &count_compact, &count_bytes_saved); | |
1017 | duk__compact_object_list(heap, thr, heap->finalize_list, &count_check, &count_compact, &count_bytes_saved); | |
1018 | #ifdef DUK_USE_REFERENCE_COUNTING | |
1019 | duk__compact_object_list(heap, thr, heap->refzero_list, &count_check, &count_compact, &count_bytes_saved); | |
1020 | #endif | |
1021 | #else | |
1022 | duk__compact_object_list(heap, thr, heap->heap_allocated); | |
1023 | duk__compact_object_list(heap, thr, heap->finalize_list); | |
1024 | #ifdef DUK_USE_REFERENCE_COUNTING | |
1025 | duk__compact_object_list(heap, thr, heap->refzero_list); | |
1026 | #endif | |
1027 | #endif | |
1028 | ||
1029 | #ifdef DUK_USE_DEBUG | |
1030 | DUK_D(DUK_DPRINT("mark-and-sweep compact objects: %ld checked, %ld compaction attempts, %ld bytes saved by compaction", | |
1031 | (long) count_check, (long) count_compact, (long) count_bytes_saved)); | |
1032 | #endif | |
1033 | } | |
1034 | ||
1035 | /* | |
1036 | * Assertion helpers. | |
1037 | */ | |
1038 | ||
1039 | #ifdef DUK_USE_ASSERTIONS | |
1040 | DUK_LOCAL void duk__assert_heaphdr_flags(duk_heap *heap) { | |
1041 | duk_heaphdr *hdr; | |
1042 | ||
1043 | hdr = heap->heap_allocated; | |
1044 | while (hdr) { | |
1045 | DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(hdr)); | |
1046 | DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(hdr)); | |
1047 | DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(hdr)); | |
1048 | /* may have FINALIZED */ | |
1049 | hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); | |
1050 | } | |
1051 | ||
1052 | #ifdef DUK_USE_REFERENCE_COUNTING | |
1053 | hdr = heap->refzero_list; | |
1054 | while (hdr) { | |
1055 | DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(hdr)); | |
1056 | DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(hdr)); | |
1057 | DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(hdr)); | |
1058 | DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(hdr)); | |
1059 | hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); | |
1060 | } | |
1061 | #endif /* DUK_USE_REFERENCE_COUNTING */ | |
1062 | } | |
1063 | ||
1064 | #ifdef DUK_USE_REFERENCE_COUNTING | |
1065 | DUK_LOCAL void duk__assert_valid_refcounts(duk_heap *heap) { | |
1066 | duk_heaphdr *hdr = heap->heap_allocated; | |
1067 | while (hdr) { | |
1068 | if (DUK_HEAPHDR_GET_REFCOUNT(hdr) == 0 && | |
1069 | DUK_HEAPHDR_HAS_FINALIZED(hdr)) { | |
1070 | /* An object may be in heap_allocated list with a zero | |
1071 | * refcount if it has just been finalized and is waiting | |
1072 | * to be collected by the next cycle. | |
1073 | */ | |
1074 | } else if (DUK_HEAPHDR_GET_REFCOUNT(hdr) == 0) { | |
1075 | /* An object may be in heap_allocated list with a zero | |
1076 | * refcount also if it is a temporary object created by | |
1077 | * a finalizer; because finalization now runs inside | |
1078 | * mark-and-sweep, such objects will not be queued to | |
1079 | * refzero_list and will thus appear here with refcount | |
1080 | * zero. | |
1081 | */ | |
1082 | #if 0 /* this case can no longer occur because refcount is unsigned */ | |
1083 | } else if (DUK_HEAPHDR_GET_REFCOUNT(hdr) < 0) { | |
1084 | DUK_D(DUK_DPRINT("invalid refcount: %ld, %p -> %!O", | |
1085 | (hdr != NULL ? (long) DUK_HEAPHDR_GET_REFCOUNT(hdr) : (long) 0), | |
1086 | (void *) hdr, (duk_heaphdr *) hdr)); | |
1087 | DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(hdr) > 0); | |
1088 | #endif | |
1089 | } | |
1090 | hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); | |
1091 | } | |
1092 | } | |
1093 | #endif /* DUK_USE_REFERENCE_COUNTING */ | |
1094 | #endif /* DUK_USE_ASSERTIONS */ | |
1095 | ||
1096 | /* | |
1097 | * Finalizer torture. Do one fake finalizer call which causes side effects | |
1098 | * similar to one or more finalizers on actual objects. | |
1099 | */ | |
1100 | ||
1101 | #if defined(DUK_USE_MARKANDSWEEP_FINALIZER_TORTURE) | |
1102 | DUK_LOCAL duk_ret_t duk__markandsweep_fake_finalizer(duk_context *ctx) { | |
1103 | DUK_D(DUK_DPRINT("fake mark-and-sweep torture finalizer executed")); | |
1104 | ||
1105 | /* Require a lot of stack to force a value stack grow/shrink. | |
1106 | * Recursive mark-and-sweep is prevented by allocation macros | |
1107 | * so this won't trigger another mark-and-sweep. | |
1108 | */ | |
1109 | duk_require_stack(ctx, 100000); | |
1110 | ||
1111 | /* XXX: do something to force a callstack grow/shrink, perhaps | |
1112 | * just a manual forced resize or a forced relocating realloc? | |
1113 | */ | |
1114 | ||
1115 | return 0; | |
1116 | } | |
1117 | ||
1118 | DUK_LOCAL void duk__markandsweep_torture_finalizer(duk_hthread *thr) { | |
1119 | duk_context *ctx; | |
1120 | duk_int_t rc; | |
1121 | ||
1122 | DUK_ASSERT(thr != NULL); | |
1123 | ctx = (duk_context *) thr; | |
1124 | ||
1125 | /* Avoid fake finalization when callstack limit has been reached. | |
1126 | * Otherwise a callstack limit error will be created, then refzero'ed. | |
1127 | */ | |
1128 | if (thr->heap->call_recursion_depth >= thr->heap->call_recursion_limit || | |
1129 | thr->callstack_size + 2 * DUK_CALLSTACK_GROW_STEP >= thr->callstack_max /*approximate*/) { | |
1130 | DUK_D(DUK_DPRINT("call recursion depth reached, avoid fake mark-and-sweep torture finalizer")); | |
1131 | return; | |
1132 | } | |
1133 | ||
1134 | /* Run fake finalizer. Avoid creating unnecessary garbage. */ | |
1135 | duk_push_c_function(ctx, duk__markandsweep_fake_finalizer, 0 /*nargs*/); | |
1136 | rc = duk_pcall(ctx, 0 /*nargs*/); | |
1137 | DUK_UNREF(rc); /* ignored */ | |
1138 | duk_pop(ctx); | |
1139 | } | |
1140 | #endif /* DUK_USE_MARKANDSWEEP_FINALIZER_TORTURE */ | |
1141 | ||
1142 | /* | |
1143 | * Main mark-and-sweep function. | |
1144 | * | |
1145 | * 'flags' represents the features requested by the caller. The current | |
1146 | * heap->mark_and_sweep_base_flags is ORed automatically into the flags; | |
1147 | * the base flags mask typically prevents certain mark-and-sweep operations | |
1148 | * to avoid trouble. | |
1149 | */ | |
1150 | ||
1151 | DUK_INTERNAL duk_bool_t duk_heap_mark_and_sweep(duk_heap *heap, duk_small_uint_t flags) { | |
1152 | duk_hthread *thr; | |
1153 | duk_size_t count_keep_obj; | |
1154 | duk_size_t count_keep_str; | |
1155 | #ifdef DUK_USE_VOLUNTARY_GC | |
1156 | duk_size_t tmp; | |
1157 | #endif | |
1158 | ||
1159 | /* XXX: thread selection for mark-and-sweep is currently a hack. | |
1160 | * If we don't have a thread, the entire mark-and-sweep is now | |
1161 | * skipped (although we could just skip finalizations). | |
1162 | */ | |
11fdf7f2 TL |
1163 | |
1164 | /* If thr != NULL, the thr may still be in the middle of | |
1165 | * initialization. | |
1166 | * XXX: Improve the thread viability test. | |
7c673cae FG |
1167 | */ |
1168 | thr = duk__get_temp_hthread(heap); | |
1169 | if (thr == NULL) { | |
11fdf7f2 | 1170 | DUK_D(DUK_DPRINT("gc skipped because we don't have a temp thread")); |
7c673cae FG |
1171 | |
1172 | /* reset voluntary gc trigger count */ | |
1173 | #ifdef DUK_USE_VOLUNTARY_GC | |
1174 | heap->mark_and_sweep_trigger_counter = DUK_HEAP_MARK_AND_SWEEP_TRIGGER_SKIP; | |
1175 | #endif | |
1176 | return 0; /* OK */ | |
1177 | } | |
1178 | ||
11fdf7f2 TL |
1179 | /* If debugger is paused, garbage collection is disabled by default. */ |
1180 | /* XXX: will need a force flag if garbage collection is triggered | |
1181 | * explicitly during paused state. | |
1182 | */ | |
1183 | #if defined(DUK_USE_DEBUGGER_SUPPORT) | |
1184 | if (DUK_HEAP_IS_PAUSED(heap)) { | |
1185 | /* Checking this here rather that in memory alloc primitives | |
1186 | * reduces checking code there but means a failed allocation | |
1187 | * will go through a few retries before giving up. That's | |
1188 | * fine because this only happens during debugging. | |
1189 | */ | |
1190 | DUK_D(DUK_DPRINT("gc skipped because debugger is paused")); | |
1191 | return 0; | |
1192 | } | |
1193 | #endif | |
1194 | ||
7c673cae FG |
1195 | DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) starting, requested flags: 0x%08lx, effective flags: 0x%08lx", |
1196 | (unsigned long) flags, (unsigned long) (flags | heap->mark_and_sweep_base_flags))); | |
1197 | ||
1198 | flags |= heap->mark_and_sweep_base_flags; | |
1199 | ||
1200 | /* | |
1201 | * Assertions before | |
1202 | */ | |
1203 | ||
1204 | #ifdef DUK_USE_ASSERTIONS | |
1205 | DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)); | |
1206 | DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap)); | |
1207 | DUK_ASSERT(heap->mark_and_sweep_recursion_depth == 0); | |
1208 | duk__assert_heaphdr_flags(heap); | |
1209 | #ifdef DUK_USE_REFERENCE_COUNTING | |
1210 | /* Note: DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap) may be true; a refcount | |
1211 | * finalizer may trigger a mark-and-sweep. | |
1212 | */ | |
1213 | duk__assert_valid_refcounts(heap); | |
1214 | #endif /* DUK_USE_REFERENCE_COUNTING */ | |
1215 | #endif /* DUK_USE_ASSERTIONS */ | |
1216 | ||
1217 | /* | |
1218 | * Begin | |
1219 | */ | |
1220 | ||
1221 | DUK_HEAP_SET_MARKANDSWEEP_RUNNING(heap); | |
1222 | ||
1223 | /* | |
1224 | * Mark roots, hoping that recursion limit is not normally hit. | |
1225 | * If recursion limit is hit, run additional reachability rounds | |
1226 | * starting from "temproots" until marking is complete. | |
1227 | * | |
1228 | * Marking happens in two phases: first we mark actual reachability | |
1229 | * roots (and run "temproots" to complete the process). Then we | |
1230 | * check which objects are unreachable and are finalizable; such | |
1231 | * objects are marked as FINALIZABLE and marked as reachability | |
1232 | * (and "temproots" is run again to complete the process). | |
1233 | * | |
1234 | * The heap finalize_list must also be marked as a reachability root. | |
1235 | * There may be objects on the list from a previous round if the | |
1236 | * previous run had finalizer skip flag. | |
1237 | */ | |
1238 | ||
1239 | duk__mark_roots_heap(heap); /* main reachability roots */ | |
1240 | #ifdef DUK_USE_REFERENCE_COUNTING | |
1241 | duk__mark_refzero_list(heap); /* refzero_list treated as reachability roots */ | |
1242 | #endif | |
1243 | duk__mark_temproots_by_heap_scan(heap); /* temproots */ | |
1244 | ||
1245 | duk__mark_finalizable(heap); /* mark finalizable as reachability roots */ | |
1246 | duk__mark_finalize_list(heap); /* mark finalizer work list as reachability roots */ | |
1247 | duk__mark_temproots_by_heap_scan(heap); /* temproots */ | |
1248 | ||
1249 | /* | |
1250 | * Sweep garbage and remove marking flags, and move objects with | |
1251 | * finalizers to the finalizer work list. | |
1252 | * | |
1253 | * Objects to be swept need to get their refcounts finalized before | |
1254 | * they are swept. In other words, their target object refcounts | |
1255 | * need to be decreased. This has to be done before freeing any | |
1256 | * objects to avoid decref'ing dangling pointers (which may happen | |
1257 | * even without bugs, e.g. with reference loops) | |
1258 | * | |
1259 | * Because strings don't point to other heap objects, similar | |
1260 | * finalization is not necessary for strings. | |
1261 | */ | |
1262 | ||
1263 | /* XXX: more emergency behavior, e.g. find smaller hash sizes etc */ | |
1264 | ||
1265 | #ifdef DUK_USE_REFERENCE_COUNTING | |
1266 | duk__finalize_refcounts(heap); | |
1267 | #endif | |
1268 | duk__sweep_heap(heap, flags, &count_keep_obj); | |
1269 | #if defined(DUK_USE_STRTAB_CHAIN) | |
1270 | duk__sweep_stringtable_chain(heap, &count_keep_str); | |
1271 | #elif defined(DUK_USE_STRTAB_PROBE) | |
1272 | duk__sweep_stringtable_probe(heap, &count_keep_str); | |
1273 | #else | |
1274 | #error internal error, invalid strtab options | |
1275 | #endif | |
1276 | #ifdef DUK_USE_REFERENCE_COUNTING | |
1277 | duk__clear_refzero_list_flags(heap); | |
1278 | #endif | |
1279 | duk__clear_finalize_list_flags(heap); | |
1280 | ||
1281 | /* | |
1282 | * Object compaction (emergency only). | |
1283 | * | |
1284 | * Object compaction is a separate step after sweeping, as there is | |
1285 | * more free memory for it to work with. Also, currently compaction | |
1286 | * may insert new objects into the heap allocated list and the string | |
1287 | * table which we don't want to do during a sweep (the reachability | |
1288 | * flags of such objects would be incorrect). The objects inserted | |
1289 | * are currently: | |
1290 | * | |
1291 | * - a temporary duk_hbuffer for a new properties allocation | |
1292 | * - if array part is abandoned, string keys are interned | |
1293 | * | |
1294 | * The object insertions go to the front of the list, so they do not | |
1295 | * cause an infinite loop (they are not compacted). | |
1296 | */ | |
1297 | ||
1298 | if ((flags & DUK_MS_FLAG_EMERGENCY) && | |
1299 | !(flags & DUK_MS_FLAG_NO_OBJECT_COMPACTION)) { | |
1300 | duk__compact_objects(heap); | |
1301 | } | |
1302 | ||
1303 | /* | |
1304 | * String table resize check. | |
1305 | * | |
1306 | * Note: this may silently (and safely) fail if GC is caused by an | |
1307 | * allocation call in stringtable resize_hash(). Resize_hash() | |
1308 | * will prevent a recursive call to itself by setting the | |
1309 | * DUK_MS_FLAG_NO_STRINGTABLE_RESIZE in heap->mark_and_sweep_base_flags. | |
1310 | */ | |
1311 | ||
1312 | /* XXX: stringtable emergency compaction? */ | |
1313 | ||
11fdf7f2 TL |
1314 | /* XXX: remove this feature entirely? it would only matter for |
1315 | * emergency GC. Disable for lowest memory builds. | |
1316 | */ | |
7c673cae FG |
1317 | #if defined(DUK_USE_MS_STRINGTABLE_RESIZE) |
1318 | if (!(flags & DUK_MS_FLAG_NO_STRINGTABLE_RESIZE)) { | |
1319 | DUK_DD(DUK_DDPRINT("resize stringtable: %p", (void *) heap)); | |
1320 | duk_heap_force_strtab_resize(heap); | |
1321 | } else { | |
1322 | DUK_D(DUK_DPRINT("stringtable resize skipped because DUK_MS_FLAG_NO_STRINGTABLE_RESIZE is set")); | |
1323 | } | |
1324 | #endif | |
1325 | ||
1326 | /* | |
1327 | * Finalize objects in the finalization work list. Finalized | |
1328 | * objects are queued back to heap_allocated with FINALIZED set. | |
1329 | * | |
1330 | * Since finalizers may cause arbitrary side effects, they are | |
1331 | * prevented during string table and object property allocation | |
1332 | * resizing using the DUK_MS_FLAG_NO_FINALIZERS flag in | |
1333 | * heap->mark_and_sweep_base_flags. In this case the objects | |
1334 | * remain in the finalization work list after mark-and-sweep | |
1335 | * exits and they may be finalized on the next pass. | |
1336 | * | |
1337 | * Finalization currently happens inside "MARKANDSWEEP_RUNNING" | |
1338 | * protection (no mark-and-sweep may be triggered by the | |
1339 | * finalizers). As a side effect: | |
1340 | * | |
1341 | * 1) an out-of-memory error inside a finalizer will not | |
1342 | * cause a mark-and-sweep and may cause the finalizer | |
1343 | * to fail unnecessarily | |
1344 | * | |
1345 | * 2) any temporary objects whose refcount decreases to zero | |
1346 | * during finalization will not be put into refzero_list; | |
1347 | * they can only be collected by another mark-and-sweep | |
1348 | * | |
1349 | * This is not optimal, but since the sweep for this phase has | |
1350 | * already happened, this is probably good enough for now. | |
1351 | */ | |
1352 | ||
1353 | #if defined(DUK_USE_MARKANDSWEEP_FINALIZER_TORTURE) | |
1354 | /* Cannot simulate individual finalizers because finalize_list only | |
1355 | * contains objects with actual finalizers. But simulate side effects | |
1356 | * from finalization by doing a bogus function call and resizing the | |
1357 | * stacks. | |
1358 | */ | |
1359 | if (flags & DUK_MS_FLAG_NO_FINALIZERS) { | |
1360 | DUK_D(DUK_DPRINT("skip mark-and-sweep torture finalizer, DUK_MS_FLAG_NO_FINALIZERS is set")); | |
1361 | } else if (!(thr->valstack != NULL && thr->callstack != NULL && thr->catchstack != NULL)) { | |
1362 | DUK_D(DUK_DPRINT("skip mark-and-sweep torture finalizer, thread not yet viable")); | |
1363 | } else { | |
1364 | DUK_D(DUK_DPRINT("run mark-and-sweep torture finalizer")); | |
1365 | duk__markandsweep_torture_finalizer(thr); | |
1366 | } | |
1367 | #endif /* DUK_USE_MARKANDSWEEP_FINALIZER_TORTURE */ | |
1368 | ||
1369 | if (flags & DUK_MS_FLAG_NO_FINALIZERS) { | |
1370 | DUK_D(DUK_DPRINT("finalizer run skipped because DUK_MS_FLAG_NO_FINALIZERS is set")); | |
1371 | } else { | |
11fdf7f2 | 1372 | duk__run_object_finalizers(heap, flags); |
7c673cae FG |
1373 | } |
1374 | ||
1375 | /* | |
1376 | * Finish | |
1377 | */ | |
1378 | ||
1379 | DUK_HEAP_CLEAR_MARKANDSWEEP_RUNNING(heap); | |
1380 | ||
1381 | /* | |
1382 | * Assertions after | |
1383 | */ | |
1384 | ||
1385 | #ifdef DUK_USE_ASSERTIONS | |
1386 | DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)); | |
1387 | DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap)); | |
1388 | DUK_ASSERT(heap->mark_and_sweep_recursion_depth == 0); | |
1389 | duk__assert_heaphdr_flags(heap); | |
1390 | #ifdef DUK_USE_REFERENCE_COUNTING | |
1391 | /* Note: DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap) may be true; a refcount | |
1392 | * finalizer may trigger a mark-and-sweep. | |
1393 | */ | |
1394 | duk__assert_valid_refcounts(heap); | |
1395 | #endif /* DUK_USE_REFERENCE_COUNTING */ | |
1396 | #endif /* DUK_USE_ASSERTIONS */ | |
1397 | ||
1398 | /* | |
1399 | * Reset trigger counter | |
1400 | */ | |
1401 | ||
1402 | #ifdef DUK_USE_VOLUNTARY_GC | |
1403 | tmp = (count_keep_obj + count_keep_str) / 256; | |
1404 | heap->mark_and_sweep_trigger_counter = (duk_int_t) ( | |
1405 | (tmp * DUK_HEAP_MARK_AND_SWEEP_TRIGGER_MULT) + | |
1406 | DUK_HEAP_MARK_AND_SWEEP_TRIGGER_ADD); | |
1407 | DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) finished: %ld objects kept, %ld strings kept, trigger reset to %ld", | |
1408 | (long) count_keep_obj, (long) count_keep_str, (long) heap->mark_and_sweep_trigger_counter)); | |
1409 | #else | |
1410 | DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) finished: %ld objects kept, %ld strings kept, no voluntary trigger", | |
1411 | (long) count_keep_obj, (long) count_keep_str)); | |
1412 | #endif | |
1413 | ||
1414 | return 0; /* OK */ | |
1415 | } | |
1416 | ||
1417 | #else /* DUK_USE_MARK_AND_SWEEP */ | |
1418 | ||
1419 | /* no mark-and-sweep gc */ | |
1420 | ||
1421 | #endif /* DUK_USE_MARK_AND_SWEEP */ |