//#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=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 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=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=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=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