1 module spasm.rt.gc; 2 3 import std.traits : isPointer, isBasicType, isCallable, PointerTarget; 4 import std.meta : staticMap, Filter; 5 import spasm.types : assumeUsed; 6 import spasm.rt.allocator : poolAllocatorIndex, MarkResult; 7 8 nothrow: 9 10 auto mark(T)(T arr) if (is(T : A[], A)) { 11 return mark(arr.ptr); 12 } 13 14 auto mark(T)(T* ptr) { 15 return poolAllocatorIndex.mark(cast(void*)ptr); 16 } 17 18 alias MarkFunction = void function(void*); 19 20 struct Node { 21 nothrow: 22 MarkFunction markFn; 23 void* root; 24 void markIt() { 25 mark(root); 26 markFn(root); 27 } 28 } 29 30 __gshared Node singleNode = void; 31 32 extern(C) export @assumeUsed void markMemory() { 33 singleNode.markIt(); 34 } 35 36 extern(C) export @assumeUsed void freeUnmarked() { 37 import spasm.rt.allocator : poolAllocatorIndex; 38 poolAllocatorIndex.freeUnmarked(); 39 } 40 41 void markMemory(T)(T* root) { 42 static if (is(T : A[], A)) { 43 if (root is null) 44 return; 45 // NOTE: it is actually a little faster not to take this information into account (but maybe not with bigger sized objects. For now we assume that is true.) 46 if (mark(root) == MarkResult.AlreadyMarked) 47 return; 48 static if (isPointer!A) { 49 foreach(item; (*root)) { 50 // NOTE: it is actually a little faster not to take this information into account (but maybe not with bigger sized objects. For now we assume that is true.) 51 if (mark(item) == MarkResult.AlreadyMarked) 52 continue; 53 alias Target = PointerTarget!A; 54 static if (!isBasicType!(Target) && !is(Target == enum)) 55 markMemory(item); 56 } 57 } 58 } else { 59 static foreach(idx, member; T.tupleof) {{ 60 static if (!isCallable!(member)) { 61 static if (isPointer!(typeof(member))) { 62 // NOTE: it is actually a little faster not to take this information into account (but maybe not with bigger sized objects. For now we assume that is true.) 63 if (root.tupleof[idx] !is null && mark(root.tupleof[idx]) != MarkResult.AlreadyMarked) { 64 alias Target = PointerTarget!(typeof(member)); 65 static if (!isBasicType!(Target) && !is(Target == enum)) 66 markMemory(root.tupleof[idx]); 67 } 68 } else static if (!isBasicType!(typeof(member)) && !is(typeof(member) == enum)) { 69 alias Type = typeof(member); 70 // when we are dealing with an array field and the root defines an opSlice 71 // we skip the array and use the opSlice (this is often needed since the 72 // object can define its own count of valid items in the array, and we don't 73 // want to scan into void or old memory) 74 static if (is(Type : Item[], Item)) { 75 // NOTE: it is actually a little faster not to take this information into account (but maybe not with bigger sized objects. For now we assume that is true.) 76 if (mark(root.tupleof[idx].ptr) != MarkResult.AlreadyMarked) { 77 static if (__traits(compiles, (*root)[])) { 78 auto arr = (*root)[]; 79 markMemory(&arr); 80 } else { 81 markMemory(&root.tupleof[idx]); 82 } 83 } 84 } else { 85 markMemory(&root.tupleof[idx]); 86 } 87 } 88 } 89 }} 90 } 91 } 92 93 void addRoot(T)(T* root) { 94 addNode(Node(cast(MarkFunction)&markMemory!(T), cast(void*)root)); 95 } 96 97 void addNode(Node node) { 98 // TODO: for now we simply have only one root 99 singleNode = node; 100 } 101 102 import spasm.rt.allocator : PoolAllocatorList, WasmAllocator; 103 import stdx.allocator.building_blocks.segregator : Segregator; 104 105 // TODO: currently everything above 1024 bytes, doesn't get GC'ed 106 alias SpasmGCAllocator = Segregator!(8, PoolAllocatorList!8, 107 16, PoolAllocatorList!16, 108 24, PoolAllocatorList!24, 109 32, PoolAllocatorList!32, 110 48, PoolAllocatorList!48, 111 64, PoolAllocatorList!64, 112 96, PoolAllocatorList!96, 113 128, PoolAllocatorList!128, 114 192, PoolAllocatorList!192, 115 256, PoolAllocatorList!256, 116 384, PoolAllocatorList!384, 117 512, PoolAllocatorList!512, 118 768, PoolAllocatorList!768, 119 1024, PoolAllocatorList!1024, 120 WasmAllocator); 121 122 version (SPASM_GC_DEBUG) 123 extern(C) export @assumeUsed void renderGCStats() { 124 import spasm.rt.allocator; 125 import spasm.dom : document, addCss; 126 import spasm.types : as, invalidHandle; 127 import spasm.bindings.html : HTMLElement; 128 import spasm.rt.array : text; 129 import std.algorithm : each, sum, map; 130 import mir.bitop : ctpop; 131 import stdx.allocator.building_blocks.region : InSituRegion; 132 import optional; 133 auto stackAllocator = InSituRegion!(2048)(); 134 static __gshared HTMLElement gcRootDomNode = HTMLElement(invalidHandle); 135 if (gcRootDomNode.handle == invalidHandle) { 136 gcRootDomNode = document.createElement("div").as!HTMLElement; 137 gcRootDomNode.classList.add("spasm_gc_debug"); 138 document.body_.front.appendChild(gcRootDomNode); 139 addCss(".spasm_gc_debug{position:absolute;width:50%;left:25%}.spasm_gc_debug .spasm_gc_debug_garbage{background:#03A9F4;height:20px;display:inline-block}.spasm_gc_debug .spasm_gc_debug_marked{background:yellow;height:20px;display:inline-block}.spasm_gc_debug_container{display:flex;border:1px solid #e2e2e2;background:white;margin:2px 0px;}.spasm_gc_debug_legend{position:absolute;left:5px;text-shadow:1px 1px 1px white;}"); 140 } 141 gcRootDomNode.innerHTML = ""; 142 foreach(stats; poolAllocatorIndex.getStats()) { 143 auto blocksUsed = stats.control.map!(ctpop!ulong).sum(); 144 auto blocksMarked = stats.markers.map!(ctpop!ulong).sum(); 145 ulong cumUsedPer = (blocksMarked*100) / stats.blockCount; 146 ulong cumGarbagePer = ((blocksUsed-blocksMarked)*100) / stats.blockCount; 147 string line = stackAllocator.text("<div class='spasm_gc_debug_legend'>", stats.blockSize ,": ", blocksUsed, " / ", stats.blockCount, "</div><div style='width:", cumUsedPer, "%' class='spasm_gc_debug_marked'></div>", "<div style='width:", cumGarbagePer, "%' class='spasm_gc_debug_garbage'></div>"); 148 auto lineNode = document.createElement("div").as!HTMLElement; 149 lineNode.classList.add("spasm_gc_debug_container"); 150 lineNode.innerHTML = line; 151 gcRootDomNode.appendChild(lineNode); 152 stackAllocator.deallocateAll(); 153 } 154 }