module clox.gc; import core.stdc.stdio : printf; import std.stdio : writeln; import std.typecons : Tuple, tuple; import clox.compiler; import clox.memory; import clox.obj; import clox.util; import clox.value; import clox.vm; import clox.container.dynarray; import clox.container.table; import conf = clox.conf; void collectGarbage(){ debug(disableGC) return; debug(stressGC) {} else { debug(logGC) writeln("bytesAllocated ", vm.bytesAllocated, ", nextGC ", vm.nextGC); if(vm.bytesAllocated < vm.nextGC) return; } debug(logGC) printf(colour!(" -- GC begin\n", Colour.Yellow).ptr); debug { static bool isAlreadyRunning = false; assert(!isAlreadyRunning); isAlreadyRunning = true; scope(exit) isAlreadyRunning = false; } markRoots(); traceReferences(); vm.strings.removeWhite(); debug(memTrace){ import clox.memorydbg : totalGCScanned, totalGCollected, totalGCollections; auto sweepStats = sweep(); totalGCollections++; totalGCollected += sweepStats.collected; totalGCScanned += sweepStats.total; debug(logGC) printf(colour!(" -- GC end %zu/%zu\n", Colour.Yellow).ptr, sweepStats.collected, sweepStats.total); } else { sweep(); } debug(stressGC) {} else { vm.nextGC = conf.nextGCGrowthFunc(vm.bytesAllocated); } } void mark(O)(O* o) if(is(O == Obj) || __traits(getMember, O, "obj").offsetof == 0){ Obj* object = cast(Obj*)o; if(object is null || object.isMarked) return; debug(logGC) writeln("\t", object.prettyPtr, colour!(" (-) ", "200", "200", "240"), Obj.toString(object)); object.isMarked = true; vm.greyStack ~= object; } void mark(Value value){ if(value.isType(Value.Type.Obj)) mark(value.asObj); } void mark(K, V)(ref Table!(K, V) table){ foreach(ref entry; table.pool){ mark(entry.key); mark(entry.val); } } void mark(T)(ref DynArray!T darray){ foreach(ref item; darray[]){ mark(item); } } private void markRoots(){ for(Value* slot = vm.stack.ptr; slot < vm.stackTop; slot++){ mark(*slot); } foreach(frame; vm.frames[0 .. vm.frameCount]){ mark(frame.closure); } for(Obj.Upvalue* upvalue = vm.openUpvalues; upvalue !is null; upvalue = upvalue.next){ mark(upvalue); } mark(vm.globals); mark(vm.initString); markCompilerRoots(); } private void blacken(Obj* object){ debug(logGC) writeln("\t", object.prettyPtr, colour!(" (#) ", Colour.Black), Obj.toString(object)); final switch(object.type){ case Obj.Type.NativeFunction: case Obj.Type.String: break; case Obj.Type.Upvalue: mark(object.asUpvalue.closed); break; case Obj.Type.Function: Obj.Function* func = object.asFunc; mark(func.name); mark(func.chunk.constants); break; case Obj.Type.Closure: Obj.Closure* closure = object.asClosure; mark(closure.func); foreach(upvalue; closure.upvalues) mark(upvalue); break; case Obj.Type.Class: Obj.Class* cls = object.asClass; mark(cls.methods); mark(cls.name); break; case Obj.Type.Instance: Obj.Instance* ins = object.asInstance; mark(ins.cls); mark(ins.fields); break; case Obj.Type.BoundMethod: Obj.BoundMethod* bm = object.asBoundMethod; mark(bm.receiver); mark(bm.method); break; case Obj.Type.None: assert(0); } } private void traceReferences(){ while(vm.greyStack.count > 0){ Obj* object = vm.greyStack.pop(); blacken(object); } } private auto sweep(){ debug(memTrace) size_t nCollected, total; Obj* previous = null; Obj* object = vm.objects; while(object !is null){ debug(memTrace) total++; if(object.isMarked){ object.isMarked = false; previous = object; object = object.next; } else { Obj* unreached = object; object = object.next; if(previous !is null){ previous.next = object; } else { vm.objects = object; } freeObject(unreached); debug(memTrace) nCollected++; } } debug(memTrace) return tuple!("collected", "total")(nCollected, total); }