1 /*
2   A tiny hack to redirect all GC allocations to a fixed size arena.
3 
4   Say, you've got an operation that actively allocates in GC'ed heap but
5   after it's complete you don't need anything it allocated. And you need
6   to do it in a loop many times, potentially allocating lots of memory.
7   Just add one line in the beginning of loop's scope and each iteration
8   will reuse the same fixed size buffer.
9   Like this:
10 
11   foreach(fname; files) {
12     auto ar = useCleanArena();     // (1)
13     auto img = readPng(fname).getAsTrueColorImage();
14     process(img);
15     // (2)
16   }
17 
18   Between points (1) and (2) all GC allocations will happen inside
19   an arena which will be reused on each iteration. No GC will happen,
20   no garbage accumulated.
21 
22   If you need some data created inbetween, you can temporarily pause
23   using the arena and allocate something on main GC heap:
24 
25     void testArena() {
26         auto ar = useCleanArena();
27         auto s = new ubyte[100]; // allocated in arena
28         {
29             auto pause = pauseArena();  // in this scope it's not used
30             auto t = new ubyte[200];    // allocated in main GC heap
31         }
32         auto v = new ubyte[300];   // allocated in arena again
33         writeln("hi");
34         // end of scope, stop using arena
35     }
36 
37   You can set size for arena by calling setArenaSize() before its first use.
38   Default size is 64 MB.
39 
40 */
41 module gcarena;
42 import std.stdio, core.memory, core.exception;
43 
44 static if (__VERSION__ < 2066) {
45     static assert(0, "pre 2.066 versions not supported at this moment");
46 } else {
47     version = D2066;
48 }
49 
50 
51 void setArenaSize(size_t totalSize) {
52     gcaData.arena_size = totalSize;
53 }
54 
55 struct ArenaHandler {
56     @disable this(this);
57     ~this() @nogc { stop(); }
58 
59     void stop() @nogc {
60         if (stopped) return;
61         gcaData.clearProxy();
62         stopped = true;
63     }
64 
65     size_t allocated() { return gcaData.arena_pos; }
66 
67     private bool stopped = false;
68 }
69 
70 auto useCleanArena() {
71     gcaData.arena_pos = 0;
72     gcaData.installProxy();
73     return ArenaHandler();
74 }
75 
76 auto pauseArena() {
77     gcaData.clearProxy();
78     struct ArenaPause {
79         ~this() @nogc { gcaData.installProxy(); }
80         @disable this(this);
81     }
82     return ArenaPause();
83 }
84 
85 private: //////////////////////////////////////////////////////////////
86 alias BlkInfo = GC.BlkInfo;
87 
88 struct Proxy // copied from proxy.d (d runtime)
89 {
90     extern (C)
91     {
92         void function() gc_enable;
93         void function() gc_disable;
94         void function() gc_collect;
95         void function() gc_minimize;
96 
97         uint function(void*) gc_getAttr;
98         uint function(void*, uint) gc_setAttr;
99         uint function(void*, uint) gc_clrAttr;
100 
101         version(D2066) {
102             void*   function(size_t, uint, const TypeInfo) gc_malloc;
103             BlkInfo function(size_t, uint, const TypeInfo) gc_qalloc;
104             void*   function(size_t, uint, const TypeInfo) gc_calloc;
105             void*   function(void*, size_t, uint ba, const TypeInfo) gc_realloc;
106             size_t  function(void*, size_t, size_t, const TypeInfo) gc_extend;
107         } else {
108             void*   function(size_t, uint) gc_malloc;
109             BlkInfo function(size_t, uint) gc_qalloc;
110             void*   function(size_t, uint) gc_calloc;
111             void*   function(void*, size_t, uint ba) gc_realloc;
112             size_t  function(void*, size_t, size_t) gc_extend;
113         }
114         size_t  function(size_t) gc_reserve;
115         void    function(void*) gc_free;
116 
117         void*   function(void*) gc_addrOf;
118         size_t  function(void*) gc_sizeOf;
119 
120         BlkInfo function(void*) gc_query;
121 
122         void function(void*) gc_addRoot;
123         version(D2066) {
124             void function(void*, size_t, const TypeInfo) gc_addRange;
125         } else {
126             void function(void*, size_t) gc_addRange;
127         }
128 
129         void function(void*) gc_removeRoot;
130         void function(void*) gc_removeRange;
131         version(D2066) {
132             void function(in void[]) gc_runFinalizers;
133         }
134     }
135 }
136 
137 struct GCAData {
138     Proxy myProxy;
139     Proxy *pOrg; // pointer to original Proxy of runtime
140     Proxy** pproxy;
141 
142     ubyte[] arena_bytes;
143     size_t arena_pos = 0;
144     size_t arena_size = 64*1024*1024;
145 
146     void initProxy() {
147         pOrg = gc_getProxy();
148         pproxy = cast(Proxy**) (cast(byte*)pOrg + Proxy.sizeof);
149         foreach(funname; __traits(allMembers, Proxy))
150             __traits(getMember, myProxy, funname) = &genCall!funname;
151         myProxy.gc_malloc = &gca_malloc;
152         myProxy.gc_qalloc = &gca_qalloc;
153         myProxy.gc_calloc = &gca_calloc;
154     }
155 
156     void* alloc(size_t size) {
157         if (arena_bytes.length==0) {
158             auto oldproxy = *pproxy;
159             *pproxy = null;
160             arena_bytes = new ubyte[arena_size];
161             *pproxy = oldproxy;
162         }
163 
164         if (arena_pos + size > arena_bytes.length) {
165             writeln("Arena too small! arena=", arena_bytes.length, " asked for ", size, " need ", arena_pos + size);
166             onOutOfMemoryError();
167         }
168         auto pos = arena_pos;
169         arena_pos += size;
170         arena_pos = (arena_pos + 15) & ~15;
171         return &arena_bytes[pos];
172     }
173 
174     void clearArena() {
175         writeln("clearArena: allocated ", arena_pos);
176         arena_pos = 0;
177     }
178 
179     void installProxy() @nogc {
180         // writeln("using arena now");
181         *pproxy = &myProxy;
182     }
183 
184     void clearProxy() @nogc {
185         // writeln("using GC now");
186         *pproxy = null;
187     }
188 }
189 
190 extern(C) {
191     Proxy* gc_getProxy();
192 
193     auto genCall(string funname)(FunArgsTypes!funname args) {
194         *gcaData.pproxy = null;
195         scope(exit) *gcaData.pproxy = &gcaData.myProxy;
196         return __traits(getMember, *gcaData.pOrg, funname)(args);
197     }
198 
199     void* gca_malloc(size_t sz, uint ba, const TypeInfo ti) {
200         //writeln("gca_malloc ", sz);
201         return gcaData.alloc(sz);
202     }
203 
204     BlkInfo gca_qalloc(size_t sz, uint ba, const TypeInfo ti) {
205         //writeln("gca_qalloc ", sz);
206         auto pos0 = gcaData.arena_pos;
207         BlkInfo b;
208         b.base = gcaData.alloc(sz);
209         b.size = gcaData.arena_pos - pos0;
210         b.attr = ba;
211         return b;
212     }
213 
214     void* gca_calloc(size_t sz, uint ba, const TypeInfo ti) {
215         import core.stdc.string : memset;
216         //writeln("gca_calloc ", sz);
217         void* p = gcaData.alloc(sz);
218         memset(p, 0, sz);
219         return p;
220     }
221 }
222 
223 template FunArgsTypes(string funname) {
224     alias FunType = typeof(*__traits(getMember, gcaData.myProxy, funname));
225     alias FunArgsTypes = ParameterTypeTuple!FunType;
226 }
227 
228 GCAData gcaData; // thread local
229 
230 static this() {
231     gcaData.initProxy();
232 }