summaryrefslogtreecommitdiff
path: root/rts/js/gc.js
blob: 00def79daaaae18db5f08b47d4ede20c36f74dff (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
//#OPTIONS: CPP

/*
  Do garbage collection where the JavaScript GC doesn't suffice or needs some help:

  - run finalizers for weak references
  - find unreferenced CAFs and reset them (unless h$retainCAFs is set)
  - shorten stacks that are mostly empty
  - reset unused parts of stacks to null
  - reset registers to null
  - reset return variables to null
  - throw exceptions to threads that are blocked on an unreachable MVar/STM transaction
  - drop unnecessary references for selector thunks

  The gc uses the .m field to store its mark in all the objects it marks. for heap objects,
  the .m field is also used for other things, like stable names, the gc only changes
  the two least significant bits for these.

  The gc starts with all threads as roots in addition to callbacks passed to JavaScript
  that that are retained. If you have custom JavaScript data structures that contain
  Haskell heap object references, you can use extensible retention to find these
  references and add thm to the work queue. h$registerExtensibleRetensionRoot(f) calls
  f(currentMark) at the start of every gc, h$registerExtensibleRetention(f) calls f(o, currentMark)
  for every unknown object found on the Haskell heap.

  Extensible retention is a low-level mechanism and should typically only be used by
  bindings that guarantee that the shape of the JS objects exactly matches what
  the scanner expects. Care should be taken to make sure that the objects never
  escape the reach of the scanner.

  Having correct reachability information is important, even if you choose to turn off
  features like weak references and deallocating CAFs in production, since it helps
  debugging by providing the profiler with accurate data and by properly raising
  exceptions when threads become blocked indefinitely, usually indicating a bug or
  memory leak.

  assumptions:
  - all threads suspended, no active registers
  - h$currentThread == null or at least unused:
       1. all reachable threads must be in h$threads or h$blocked
       2. no registers contain any usable value
  notes:
  - gc() may replace the stack of any thread, make sure to reload h$stack after gc()
*/

/*
  fixme, todo:
  - mark posted exceptions to thread
*/

#ifdef GHCJS_TRACE_GC
function h$traceGC() { h$log.apply(h$log, arguments); }
#define TRACE_GC(args...) h$traceGC(args)
#else
#define TRACE_GC(args...)
#endif

// these macros use a local mark variable
#define IS_MARKED(obj) ((typeof obj.m === 'number' && (obj.m & 3) === mark) || (typeof obj.m === 'object' && ((obj.m.m & 3) === mark)))
#define IS_MARKED_M(obj) ((obj.m & 3) === mark)
#define MARK_OBJ(obj) if(typeof obj.m === 'number') obj.m = (obj.m&-4)|mark; else obj.m.m = (obj.m.m & -4)|mark;

var h$gcMark = 2; // 2 or 3 (objects initialized with 0)

#ifdef GHCJS_TRACE_GC
var h$gcTime = 0;
#endif

#ifdef GHCJS_RETAIN_CAFS
var h$retainCAFs = true;
#else
var h$retainCAFs = false;
#endif

// FIXME remove this? declared in rts.js now
// var h$CAFs = [];
// var h$CAFsReset = [];

// 
var h$extensibleRetentionRoots     = [];
var h$extensibleRetentionCallbacks = [];


/*
   after registering an extensible extension root f,
   f(currentMark) is called at the start of each gc invocation and is
   expected to return an array with Haskell heap objects
   to be treated as extra roots.
 */
function h$registerExtensibleRetentionRoot(f) {
    h$extensibleRetentionRoots.push(f);
}

function h$unregisterExtensibleRetentionRoot(f) {
    h$extensibleRetentionRoots = h$extensibleRetentionRoots.filter(function(g) { return f !== g; });
}

/*
  after registering an extensible retention callback f,
  f(o, currentMark) is called for every unknown object encountered on the
  Haskell heap. f should return an array with found objects. If no objects
  are found, f should return a boolean indicating whether the gc should skip
  processing the objects with other extensible retention callbacks.

  The gc may encounter the same object multiple times during the same scan,
  so a callback should attempt to quickly return if the object has been scanned
  already.

   return value:
     - array          scan objects contained in array, do not call other extension callbacks
     - true           do not call other extension callbacks with this object
     - false          call other extension callbacks with this object

  Use -DGHCJS_TRACE_GC_UNKNOWN to find the JavaScript objects reachable
  (through JSVal) on the Haskell heap for which none of the registered
  extensible retention callbacks has returned true or an array.
 */
function h$registerExtensibleRetention(f) {
    h$extensibleRetentionCallbacks.push(f);
}

function h$unregisterExtensibleRetention(f) {
    h$extensibleRetentionCallbacks = h$extensibleRetentionCallbacks.filter(function(g) { return f !== g; });
}

// check whether the object is marked by the latest gc
function h$isMarked(obj) {
  return (typeof obj === 'object' || typeof obj === 'function') &&
        ((typeof obj.m === 'number' && (obj.m & 3) ===  h$gcMark) || (obj.m && typeof obj.m === 'object' && obj.m.m === h$gcMark));
}

// do a quick gc of a thread:
// - reset the stack (possibly shrinking storage for it)
// - reset all global data
// checks all known threads if t is null, but not h$currentThread
function h$gcQuick(t) {
#ifdef GHCJS_DISABLE_GC
    return;
#endif
    if(h$currentThread !== null) throw "h$gcQuick: GC can only run when no thread is running";
#ifdef GHCJS_TRACE_GC
    var start = Date.now();
#endif
    h$resetRegisters();
    h$resetResultVars();
    var i;
    if(t !== null) { // reset specified threads
        if(t instanceof h$Thread) {  // only thread t
            h$resetThread(t);
        } else { // assume it's an array
            for(var i=0;i<t.length;i++) h$resetThread(t[i]);
        }
    } else { // all threads, h$currentThread assumed unused
        var nt, runnable = h$threads.iter();
        while((nt = runnable()) !== null) h$resetThread(nt);
        var iter = h$blocked.iter();
        while((nt = iter.next()) !== null) h$resetThread(nt);
    }
#ifdef GHCJS_TRACE_GC
    var time = Date.now() - start;
    h$gcTime += time;
    TRACE_GC("time (quick): " + time + "ms")
    TRACE_GC("time (total): " + h$gcTime + "ms")
#endif
}

// run full marking for threads in h$blocked and h$threads, optionally t if t /= null
#ifdef GHCJS_TRACE_GC
var h$marked = 0;
#endif
function h$gc(t) {
#ifdef GHCJS_DISABLE_GC
    return;
#endif
#ifndef GHCJS_BROWSER
    // fixme, should enable again later when proper CAF management
    // and retention of the standard handles in GHCJSi work
    if(h$isGHCJSi()) return;
#endif

    if(h$currentThread !== null) throw "h$gc: GC can only be run when no thread is running";
#ifdef GHCJS_TRACE_GC
    h$marked = 0;
    TRACE_GC("gc: " + (t!==null?h$threadString(t):"null"))
    var start = Date.now();
#endif
    TRACE_GC("full gc of thread " + h$threadString(t))
    h$resetRegisters();
    h$resetResultVars();
    h$gcMark = 5-h$gcMark;
    var i;
    TRACE_GC("scanning extensible retention roots")
    for(i=h$extensibleRetentionRoots.length-1;i>=0;i--) {
      var a = h$extensibleRetentionRoots[i](h$gcMark);
      if(a) h$follow(a, a.length-1);
    }
    TRACE_GC("scanning threads, runnable: " + h$threads.length() + " blocked: " + h$blocked.size() + " t: " + t)

    // mark al runnable threads and the running thread
    if(t !== null) {
	h$markThread(t);
	h$resetThread(t);
    }
    var nt, runnable = h$threads.iter();
    while((nt = runnable()) !== null) {
	h$markThread(nt);
	h$resetThread(nt);
    }
    
    // some blocked threads are always considered reachable, mark them
    //   - delayed threads
    //   - threads blocked on async FFI
    var iter = h$blocked.iter();
    while((nt = iter.next()) !== null) {
        if(nt.delayed ||
	   (nt.blockedOn instanceof h$MVar && nt.stack && nt.stack[nt.sp] === h$unboxFFIResult)) {
            h$markThread(nt);
        }
	h$resetThread(nt);
    }
    TRACE_GC("scanning permanent retention roots")
    iter = h$extraRoots.iter();
    while((nt = iter.next()) !== null) h$follow(nt.root);

    TRACE_GC("scanning stable pointers")
    for(i=0;i<h$stablePtrData.length;i++) {
      if(h$stablePtrData[i]) h$follow(h$stablePtrData[i]);
    }

    // clean up threads waiting on unreachable synchronization primitives
    h$resolveDeadlocks();

    // clean up unreachable weak refs
    var toFinalize = h$markRetained();
    h$finalizeWeaks(toFinalize);

    h$finalizeCAFs();   // restore all unreachable CAFs to unevaluated state

    var now = Date.now();
    h$lastGc = now;
#ifdef GHCJS_TRACE_GC
    var time = now - start;
    h$gcTime += time;
    TRACE_GC("time: " + time + "ms")
    TRACE_GC("time (total): " + h$gcTime + "ms")
    TRACE_GC("marked objects: " + h$marked)
#endif
    h$debugAlloc_verifyReachability(h$gcMark);
}

function h$markWeaks() {
  var i, w, marked, mark = h$gcMark;
  do {
    marked = false;
    for (i = 0; i < h$weakPointerList.length; ++i) {
      w = h$weakPointerList[i];
      if (IS_MARKED_M(w.keym)) {
	if (w.val !== null && !IS_MARKED(w.val)) {
          h$follow(w.val);
	  marked = true;
	}
	if (w.finalizer !== null && !IS_MARKED(w.finalizer)) {
          h$follow(w.finalizer);
	  marked = true;
	}
      }
    }
  } while(marked);
}


function h$markRetained() {
    var iter, marked, w, i, mark = h$gcMark;
    var newList = [];
    var toFinalize = [];

    /*
      2. Scan the Weak Pointer List. If a weak pointer object has a key that is
      marked (i.e. reachable), then mark all heap reachable from its value
      or its finalizer, and move the weak pointer object to a new list
    */
    do {
        TRACE_GC("mark retained iteration 1/2")
        marked = false;

        for (i = 0; i < h$weakPointerList.length; ++i) {
            w = h$weakPointerList[i];
            if (w === null) {
                // don't handle items deleted in earlier iteration
                continue;
            }
            if (IS_MARKED_M(w.keym)) {
                if (w.val !== null && !IS_MARKED(w.val)) {
                    h$follow(w.val);
                }

                if (w.finalizer !== null && !IS_MARKED(w.finalizer)) {
                    h$follow(w.finalizer);
                }

                newList.push(w);
                // instead of removing the item from the h$weakpointerList
                // we set it to null if we push it to newList.
                h$weakPointerList[i] = null;

                marked = true;
            }
        }

        /*
           3. Repeat from step (2), until a complete scan of Weak Pointer List finds
              no weak pointer object with a marked keym.
        */
    } while(marked);


    /*
      4. Scan the Weak Pointer List again. If the weak pointer object is reachable
         then tombstone it. If the weak pointer object has a finalizer then move
         it to the Finalization Pending List, and mark all the heap reachable
         from the finalizer. If the finalizer refers to the key (and/or value),
         this step will "resurrect" it.
    */

    for (i = 0; i < h$weakPointerList.length; ++i) {
        w = h$weakPointerList[i];
        if (w === null) {
            // don't handle items deleted in step 2
            continue;
        }

        TRACE_GC("mark retained iteration 2/2")
        if(w.val !== null) {
            w.val = null;
        }

        if(w.finalizer !== null) {
            if(!IS_MARKED(w.finalizer)) {
                TRACE_GC("following finalizer")
                h$follow(w.finalizer);
            }
            toFinalize.push(w);
        }
    }

    /*
       5. The list accumulated in step (3) becomes the new Weak Pointer List.
          Mark any unreachable weak pointer objects on this list as reachable.
    */
    h$weakPointerList = newList;

    // marking the weak pointer objects as reachable is not necessary

    return toFinalize;
}

function h$markThread(t) {
    var mark = h$gcMark;
    TRACE_GC("marking thread: " + h$threadString(t))
    if(IS_MARKED(t)) return;
    h$follow(t);
}

#define ADDW(x) work[w++] = x;
#define ADDW2(x,y) { work[w++] = x; work[w++] = y; }
#define ADDW3(x,y,z) { work[w++] = x; work[w++] = y; work[w++] = z; }
#define ADDW4(x,y,z,v) { work[w++] = x; work[w++] = y; work[w++] = z; work[w++] = v; }

// big object, not handled by 0..7 cases
// keep out of h$follow to prevent deopt
function h$followObjGen(c, work, w) {
   ADDW(c.d1);
   var d = c.d2;
   for(var x in d) {
//              if(d.hasOwnProperty(x)) {
     ADDW(d[x]);
//              }
   }
    return w;
}

// follow all references in the object obj and mark them with the current mark
// if sp is a number, obj is assumed to be an array for which indices [0..sp] need
// to be followed (used for thread stacks)
function h$follow(obj, sp) {
    var i, ii, iter, c, work, w;
#ifdef GHCJS_TRACE_GC
    var start = Date.now();
#endif
    TRACE_GC("following")
    var work, mark = h$gcMark;
    if(typeof sp === 'number') {
        work = obj.slice(0, sp+1);
        w = sp + 1;
    } else {
        work = [obj];
        w = 1;
    }
    while(w > 0) {
        TRACE_GC("work length: " + work.length + " w: " + w)
        c = work[--w];
        TRACE_GC("[" + work.length + "] mark step: " + typeof c)
#ifdef GHCJS_TRACE_GC
        if(typeof c === 'object') {
            if(c !== null) {
                TRACE_GC("object: " + c.toString())
                TRACE_GC("object props: " + h$collectProps(c))
                TRACE_GC("object mark: " + c.m + " (" + typeof(c.m) + ") (current: " + mark + ")")
            } else {
                TRACE_GC("object: " + c)
            }
        }
#endif
        if(c !== null && c !== undefined && typeof c === 'object' && ((typeof c.m === 'number' && (c.m&3) !== mark) || (typeof c.m === 'object' && c.m !== null && typeof c.m.m === 'number' && (c.m.m&3) !== mark))) {
            var doMark = false;
            var cf = c.f;
            TRACE_GC("first accepted")
            if(typeof cf === 'function' && (typeof c.m === 'number' || typeof c.m === 'object')) {
                TRACE_GC("marking heap object: " + c.f.n + " size: " + c.f.size)
                // only change the two least significant bits for heap objects
                MARK_OBJ(c);
                // dynamic references
                var d = c.d2;
                switch(cf.size) {
                case 0: break;
                case 1: ADDW(c.d1); break;
                case 2: ADDW2(c.d1, d); break;
                case 3: var d3=c.d2; ADDW3(c.d1, d3.d1, d3.d2); break;
                case 4: var d4=c.d2; ADDW4(c.d1, d4.d1, d4.d2, d4.d3); break;
                case 5: var d5=c.d2; ADDW4(c.d1, d5.d1, d5.d2, d5.d3); ADDW(d5.d4); break;
                case 6: var d6=c.d2; ADDW4(c.d1, d6.d1, d6.d2, d6.d3); ADDW2(d6.d4, d6.d5); break;
                case 7: var d7=c.d2; ADDW4(c.d1, d7.d1, d7.d2, d7.d3); ADDW3(d7.d4, d7.d5, d7.d6); break;
                case 8: var d8=c.d2; ADDW4(c.d1, d8.d1, d8.d2, d8.d3); ADDW4(d8.d4, d8.d5, d8.d6, d8.d7); break;
                case 9: var d9=c.d2; ADDW4(c.d1, d9.d1, d9.d2, d9.d3); ADDW4(d9.d4, d9.d5, d9.d6, d9.d7); ADDW(d9.d8); break;
                case 10: var d10=c.d2; ADDW4(c.d1, d10.d1, d10.d2, d10.d3); ADDW4(d10.d4, d10.d5, d10.d6, d10.d7); ADDW2(d10.d8, d10.d9); break;
                case 11: var d11=c.d2; ADDW4(c.d1, d11.d1, d11.d2, d11.d3); ADDW4(d11.d4, d11.d5, d11.d6, d11.d7); ADDW3(d11.d8, d11.d9, d11.d10); break;
                case 12: var d12=c.d2; ADDW4(c.d1, d12.d1, d12.d2, d12.d3); ADDW4(d12.d4, d12.d5, d12.d6, d12.d7); ADDW4(d12.d8, d12.d9, d12.d10, d12.d11); break;
                default: w = h$followObjGen(c,work,w);
                }
                // static references
                var s = cf.s;
                if(s !== null) {
                    TRACE_GC("adding static marks")
                    for(var i=0;i<s.length;i++) ADDW(s[i]);
                }
            } else if(typeof c.len === 'number' && c.buf instanceof ArrayBuffer) {
                TRACE_GC("marking ByteArray")
                MARK_OBJ(c);
            } else if(c instanceof h$Weak) {
                MARK_OBJ(c);
            } else if(c instanceof h$MVar) {
                TRACE_GC("marking MVar")
                MARK_OBJ(c);
                iter = c.writers.iter();
                while((ii = iter()) !== null) {
		    ADDW(ii[1]); // value
		    ADDW(ii[0]); // thread
		}
		iter = c.readers.iter();
		while((ii = iter()) !== null) {
		    ADDW(ii);
		}
	        if(c.waiters) {
		  for(i=c.waiters.length-1;i>=0;i--) {
		    ADDW(c.waiters[i]);
		  }
		}
                if(c.val !== null && !IS_MARKED(c.val)) ADDW(c.val);
            } else if(c instanceof h$MutVar) {
                TRACE_GC("marking MutVar")
                MARK_OBJ(c);
                ADDW(c.val);
            } else if(c instanceof h$TVar) {
                TRACE_GC("marking TVar")
                MARK_OBJ(c);
                ADDW(c.val);
		iter = c.blocked.iter();
		while((ii = iter.next()) !== null) {
		    ADDW(ii);
		}
		if(c.invariants) {
		    iter = c.invariants.iter();
		    while((ii = iter.next()) !== null) {
			ADDW(ii);
		    }
		}
            } else if(c instanceof h$Thread) {
                TRACE_GC("marking Thread")
                MARK_OBJ(c);
                if(c.stack) {
                    for(i=c.sp;i>=0;i--) {
			ADDW(c.stack[i]);
		    }
                }
		for(i=0;i<c.excep.length;i++) {
		    ADDW(c.excep[i][0]); // the posting thread
		    ADDW(c.excep[i][1]); // the exception
		}
            } else if(c instanceof h$Transaction) {
                // - the accessed TVar values don't need to be marked
                // - parents are also on the stack, so they should've been marked already
                TRACE_GC("marking STM transaction")
                MARK_OBJ(c);
                for(i=c.invariants.length-1;i>=0;i--) {
		    ADDW(c.invariants[i].action);
		}
                ADDW(c.action);
                iter = c.tvars.iter();
                while((ii = iter.nextVal()) !== null) {
		    ADDW(ii.val);
		}
            } else if(c instanceof Array && c.__ghcjsArray) {
		// only for Haskell arrays with lifted values
                MARK_OBJ(c);
                TRACE_GC("marking array")
                for(i=0;i<c.length;i++) {
                    var x = c[i];
                    if(typeof x === 'object' && x !== null && !IS_MARKED(x)) {
		        ADDW(x);
		    }
                }
            } else if(typeof c === 'object') {
                TRACE_GC("extensible retention marking")
#ifdef GHCJS_TRACE_GC_UNKNOWN
                var extensibleMatched = false;
#endif
                for(i=h$extensibleRetentionCallbacks.length-1;i>=0;i--) {
                    var x = h$extensibleRetentionCallbacks[i](c, mark);
                    if(x === false) continue;
#ifdef GHCJS_TRACE_GC_UNKNOWN
                    extensibleMatched = true;
#endif
                    if(x !== true) {
                      for(j=x.length-1;j>=0;j--) {
		          ADDW(x[j]);
		      }
                    }
                    break;
                }
#ifdef GHCJS_TRACE_GC_UNKNOWN
                if(!extensibleMatched) {
                    TRACE_GC("unknown object: " + h$collectProps(c))
                }
#endif
            } // otherwise: not an object, no followable values
        }
    }
    TRACE_GC("h$follow: " + (Date.now()-start) + "ms")
}

// resetThread clears the stack above the stack pointer
// and shortens the stack array if there is too much
// unused space
function h$resetThread(t) {
#ifdef GHCJS_TRACE_GC
    var start = Date.now();
#endif
    var stack = t.stack;
    if(!stack) return;
    var sp = t.sp;
    if(stack.length - sp > sp && stack.length > 100) {
        t.stack = t.stack.slice(0,sp+1);
    } else {
        for(var i=sp+1;i<stack.length;i++) {
            stack[i] = null;
        }
    }
    TRACE_GC("h$resetThread: " + (Date.now()-start) + "ms")
}

/*
   Post exceptions to all threads that are waiting on an unreachable synchronization
   object and haven't been marked reachable themselves.

   All woken up threads are marked.
 */
function h$resolveDeadlocks() {
    TRACE_GC("resolving deadlocks")
    var kill, t, iter, bo, mark = h$gcMark;
    do {
        h$markWeaks();
        // deal with unreachable blocked threads: kill an unreachable thread and restart the process
        kill = null;
        iter = h$blocked.iter();
        while((t = iter.next()) !== null) {
            // we're done if the thread is already reachable
            if(IS_MARKED(t)) continue;

            // check what we're blocked on
            bo = t.blockedOn;
            if(bo instanceof h$MVar) {
                // blocked on MVar
                if(bo.m === mark) throw "assertion failed: thread should have been marked";
                // MVar unreachable
                kill = h$baseZCGHCziJSziPrimziInternalziblockedIndefinitelyOnMVar;
                break;
            } else if(t.blockedOn instanceof h$TVarsWaiting) {
                // blocked in STM transaction
                kill = h$baseZCGHCziJSziPrimziInternalziblockedIndefinitelyOnSTM;
                break;
            } else {
                // blocked on something else, we can't do anything
            }
        }
        if(kill) {
            h$killThread(t, kill);
            h$markThread(t);
        }
    } while(kill);
}

// register a CAF (after initialising the heap object)
function h$addCAF(o) {
  h$CAFs.push(o);
  h$CAFsReset.push([o.f, o.d1, o.d2]);
}

// reset unreferenced CAFs to their initial value
function h$finalizeCAFs() {
    if(h$retainCAFs) return;
#ifdef GHCJS_TRACE_GC
    var start = Date.now();
#endif
    var mark = h$gcMark;
    for(var i=0;i<h$CAFs.length;i++) {
        var c = h$CAFs[i];
        if(c.m & 3 !== mark) {
            var cr = h$CAFsReset[i];
            if(c.f !== cr[0]) { // has been updated, reset it
                TRACE_GC("resetting CAF: " + cr.n)
                c.f = cr[0];
                c.d1 = cr[1];
                c.d2 = cr[2];
            }
        }
    }
    TRACE_GC("h$finalizeCAFs: " + (Date.now()-start) + "ms")
}