if (GC_SUPPORT) { EXPORTED_FUNCTIONS['_calloc'] = 1; EXPORTED_FUNCTIONS['_realloc'] = 1; var LibraryGC = { $GC__deps: ['sbrk', 'realloc', 'calloc'], $GC: { ALLOCATIONS_TO_GC: 1*1024*1024, sizes: {}, // if in this map, then a live allocated object. this is iterable scannables: {}, uncollectables: {}, finalizers: {}, finalizerArgs: {}, totalAllocations: 0, // bytes of all currently active objects recentAllocations: 0, // bytes allocated since last gc. ignores free()s init: function() { assert(!GC.initted); GC.initted = true; _GC_finalize_on_demand = _malloc(4); setValue(_GC_finalize_on_demand, 0, 'i32') _GC_java_finalization = _malloc(4); setValue(_GC_java_finalization, 0, 'i32'); _GC_finalizer_notifier = _malloc(4); setValue(_GC_finalizer_notifier, 0, 'i32'); if (ENVIRONMENT_IS_WEB) { setInterval(function() { GC.maybeCollect(); }, 1000); } else { #if ASSERTIONS Module.print('No HTML intervals, so you need to call GC.maybeCollect() or GC.collect() manually'); #endif } }, malloc: function(bytes, clear, scannable, collectable) { if (!bytes) return 0; var ptr; if (clear) { ptr = _calloc(1, bytes); } else { ptr = _malloc(bytes); } if (!collectable) { GC.uncollectables[ptr] = true; } GC.scannables[ptr] = scannable; GC.sizes[ptr] = bytes; GC.totalAllocations += bytes; GC.recentAllocations += bytes; return ptr; }, realloc: function(ptr, newBytes) { if (newBytes != 0) { var oldBytes = GC.sizes[ptr]; var newPtr = _realloc(ptr, newBytes); if (newBytes > oldBytes) { _memset(newPtr + oldBytes, 0, newBytes - oldBytes); } delete GC.sizes[ptr]; GC.sizes[newPtr] = newBytes; scannable = GC.scannables[ptr]; delete GC.scannables[ptr]; GC.scannables[newPtr] = scannable; var finalizer = GC.finalizers[ptr]; if (finalizer) { delete GC.finalizers[ptr]; GC.finalizers[newPtr] = finalizer; } var finalizerArgs = GC.finalizerArgs[ptr]; if (finalizerArgs) { delete GC.finalizerArgs[ptr]; GC.finalizerArgs[newPtr] = finalizerArgs; } var uncollectable = GC.uncollectables[ptr]; if (uncollectable) { delete GC.uncollectables[ptr]; GC.uncollectables[newPtr] = true; } GC.totalAllocations += (newBytes - oldBytes); return newPtr; } else { GC.free(ptr); return 0; } }, free: function(ptr) { // does not check if anything refers to it, this is a forced free var finalizer = GC.finalizers[ptr]; if (finalizer) { Runtime.getFuncWrapper(finalizer, 'vii')(ptr, GC.finalizerArgs[ptr]); GC.finalizers[ptr] = 0; } _free(ptr); GC.totalAllocations -= GC.sizes[ptr]; delete GC.sizes[ptr]; }, registerFinalizer: function(ptr, func, arg, oldFunc, oldArg) { var finalizer = GC.finalizers[ptr]; if (finalizer) { if (oldFunc) { {{{ makeSetValue('oldFunc', '0', 'finalizer', 'i32') }}}; } if (oldArg) { {{{ makeSetValue('oldArg', '0', 'GC.finalizerArgs[ptr]', 'i32') }}}; } } GC.finalizers[ptr] = func; GC.finalizerArgs[ptr] = arg; }, getHeapSize: function() { return GC.totalAllocations; }, maybeCollect: function() { if (GC.needCollect()) GC.collect(); }, needCollect: function() { return GC.recentAllocations >= GC.ALLOCATIONS_TO_GC; // TODO: time, etc. }, collect: function() { GC.prep(); GC.mark(); GC.sweep(); GC.recentAllocations = 0; }, scan: function(start, end) { // scans a memory region and adds new reachable objects for (var i = start; i < end; i += {{{ Runtime.getNativeTypeSize('void*') }}}) { var ptr = {{{ makeGetValue('i', '0', 'void*') }}}; if (GC.sizes[ptr] && !GC.reachable[ptr]) { GC.reachable[ptr] = 1; if (GC.scannables[ptr]) { GC.reachableList.push(ptr); } } } }, prep: function() { // Clear reachables and scan for roots GC.reachable = {}; // 1 if reachable. XXX GC.reachableList = []; // each reachable is added once to this. XXX // static data areas var staticStart = STACK_MAX; var staticEnd = _sbrk.DYNAMIC_START || STATICTOP; // after DYNAMIC_START, sbrk manages it (but it might not exist yet) GC.scan(staticStart, staticEnd); // TODO: scan stack and registers. Currently we assume we run from a timeout or such, so no stack/regs // stack: STACK_ROOT to STACKTOP // registers: call scanners }, mark: function() { // mark all reachable from roots for (var i = 0; i < GC.reachableList.length; i++) { // note that the list length changes as we push more var ptr = GC.reachableList[i]; GC.scan(ptr, ptr + GC.sizes[ptr]); } }, sweep: function() { // traverse all objects and free all unreachable var freeList = []; for (var ptr in GC.sizes) { if (!GC.reachable[ptr] && !GC.uncollectables[ptr]) { freeList.push(parseInt(ptr)); } } for (var i = 0; i < freeList.length; i++) { GC.free(freeList[i]); } } }, GC_INIT__deps: ['$GC'], GC_INIT: function() { GC.init(); }, GC_MALLOC__deps: ['$GC'], GC_MALLOC: function(bytes) { return GC.malloc(bytes, true, true, true); }, GC_MALLOC_ATOMIC__deps: ['$GC'], GC_MALLOC_ATOMIC: function(bytes) { return GC.malloc(bytes, false, false, true); }, GC_MALLOC_UNCOLLECTABLE__deps: ['$GC'], GC_MALLOC_UNCOLLECTABLE: function(bytes) { return GC.malloc(bytes, true, true, false); }, GC_REALLOC__deps: ['$GC'], GC_REALLOC: function(ptr, newBytes) { return GC.realloc(ptr, newBytes); }, GC_FREE__deps: ['$GC'], GC_FREE: function(ptr) { GC.free(ptr); }, GC_REGISTER_FINALIZER_NO_ORDER__deps: ['$GC'], GC_REGISTER_FINALIZER_NO_ORDER: function(ptr, func, arg, old_func, old_arg) { GC.registerFinalizer(ptr, func, arg, old_func, old_arg); }, GC_get_heap_size__deps: ['$GC'], GC_get_heap_size: function() { return GC.getHeapSize(); }, GC_MAYBE_COLLECT__deps: ['$GC'], GC_MAYBE_COLLECT: function() { GC.maybeCollect(); }, GC_FORCE_COLLECT__deps: ['$GC'], GC_FORCE_COLLECT: function() { GC.collect(); }, GC_finalize_on_demand: 0, GC_java_finalization: 0, GC_finalizer_notifier: 0, GC_enable_incremental: function(){}, }; mergeInto(LibraryManager.library, LibraryGC); }