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 void setArenaSize(size_t totalSize) {
45 	gcaData.arena_size = totalSize;
46 }
47 
48 struct ArenaHandler {
49 	this(this) @disable;
50 	~this() @nogc { stop(); }
51 
52 	void stop() @nogc {
53 		if (stopped) return;
54 		gcaData.clearProxy();
55 		stopped = true;
56 	}
57 
58 	size_t allocated() { return gcaData.arena_pos; }
59 
60 	private bool stopped = false;
61 }
62 
63 auto useCleanArena() {
64 	gcaData.arena_pos = 0;
65 	gcaData.installProxy();
66 	return ArenaHandler();
67 }
68 
69 auto pauseArena() {
70 	gcaData.clearProxy();
71 	struct ArenaPause {
72 		~this() @nogc { gcaData.installProxy(); }
73 		this(this) @disable;
74 	}
75 	return ArenaPause();
76 }
77 
78 private: //////////////////////////////////////////////////////////////
79 alias BlkInfo = GC.BlkInfo;
80 
81 struct Proxy // copied from proxy.d (d runtime)
82 {
83 	extern (C)
84 	{
85 		void function() gc_enable;
86 		void function() gc_disable;
87 		void function() gc_collect;
88 		void function() gc_minimize;
89 
90 		uint function(void*) gc_getAttr;
91 		uint function(void*, uint) gc_setAttr;
92 		uint function(void*, uint) gc_clrAttr;
93 
94 		void*   function(size_t, uint, const TypeInfo) gc_malloc;
95 		BlkInfo function(size_t, uint, const TypeInfo) gc_qalloc;
96 		void*   function(size_t, uint, const TypeInfo) gc_calloc;
97 		void*   function(void*, size_t, uint ba, const TypeInfo) gc_realloc;
98 		size_t  function(void*, size_t, size_t, const TypeInfo) gc_extend;
99 		size_t  function(size_t) gc_reserve;
100 		void	function(void*) gc_free;
101 
102 		void*   function(void*) gc_addrOf;
103 		size_t  function(void*) gc_sizeOf;
104 
105 		BlkInfo function(void*) gc_query;
106 
107 		void function(void*) gc_addRoot;
108 		void function(void*, size_t, const TypeInfo) gc_addRange;
109 
110 		void function(void*) gc_removeRoot;
111 		void function(void*) gc_removeRange;
112 		void function(in void[]) gc_runFinalizers;
113 	}
114 }
115 
116 struct GCAData {
117 	Proxy myProxy;
118 	Proxy *pOrg; // pointer to original Proxy of runtime
119 	Proxy** pproxy;
120 
121 	ubyte[] arena_bytes;
122 	size_t arena_pos = 0;
123 	size_t arena_size = 64*1024*1024;
124 
125 	void initProxy() {
126 		pOrg = gc_getProxy();
127 		pproxy = cast(Proxy**) (cast(byte*)pOrg + Proxy.sizeof);
128 		foreach(funname; __traits(allMembers, Proxy))
129 			__traits(getMember, myProxy, funname) = &genCall!funname;
130 		myProxy.gc_malloc = &gca_malloc;
131 		myProxy.gc_qalloc = &gca_qalloc;
132 		myProxy.gc_calloc = &gca_calloc;
133 	}
134 
135 	void* alloc(size_t size) {
136 		if (arena_bytes.length==0) {
137 			auto oldproxy = *pproxy;
138 			*pproxy = null;
139 			arena_bytes = new ubyte[arena_size];
140 			*pproxy = oldproxy;
141 		}
142 
143 		if (arena_pos + size > arena_bytes.length) {
144 			writeln("Arena too small! arena=", arena_bytes.length, " asked for ", size, " need ", arena_pos + size);
145 			onOutOfMemoryError();
146 		}
147 		auto pos = arena_pos;
148 		arena_pos += size;
149 		arena_pos = (arena_pos + 15) & ~15;
150 		return &arena_bytes[pos];
151 	}
152 
153 	void clearArena() {
154 		writeln("clearArena: allocated ", arena_pos);
155 		arena_pos = 0;
156 	}
157 
158 	void installProxy() @nogc {
159 		// writeln("using arena now");
160 		*pproxy = &myProxy;
161 	}
162 
163 	void clearProxy() @nogc {
164 		// writeln("using GC now");
165 		*pproxy = null;
166 	}
167 }
168 
169 extern(C) {
170 	Proxy* gc_getProxy();
171 
172 	auto genCall(string funname)(FunArgsTypes!funname args) {
173 		*gcaData.pproxy = null;
174 		scope(exit) *gcaData.pproxy = &gcaData.myProxy;
175 		return __traits(getMember, *gcaData.pOrg, funname)(args);
176 	}
177 
178 	void* gca_malloc(size_t sz, uint ba, const TypeInfo ti) {
179 		//writeln("gca_malloc ", sz);
180 		return gcaData.alloc(sz);
181 	}
182 
183 	BlkInfo gca_qalloc(size_t sz, uint ba, const TypeInfo ti) {
184 		//writeln("gca_qalloc ", sz);
185 		auto pos0 = gcaData.arena_pos;
186 		BlkInfo b;
187 		b.base = gcaData.alloc(sz);
188 		b.size = gcaData.arena_pos - pos0;
189 		b.attr = ba;
190 		return b;
191 	}
192 
193 	void* gca_calloc(size_t sz, uint ba, const TypeInfo ti) {
194 		import core.stdc.string : memset;
195 		//writeln("gca_calloc ", sz);
196 		void* p = gcaData.alloc(sz);
197 		memset(p, 0, sz);
198 		return p;
199 	}
200 }
201 
202 template FunArgsTypes(string funname) {
203 	alias FunType = typeof(*__traits(getMember, gcaData.myProxy, funname));
204 	alias FunArgsTypes = ParameterTypeTuple!FunType;
205 }
206 
207 GCAData gcaData; // thread local
208 
209 static this() {
210 	gcaData.initProxy();
211 }