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 }