1 /**
2  * Fast buffer implementation.
3  *
4  * Authors:
5  *   $(LINK2 mailto:Marco.Leise@gmx.de, Marco Leise)
6  *
7  * Copyright:
8  *   © 2015 $(LINK2 mailto:Marco.Leise@gmx.de, Marco Leise)
9  *
10  * License:
11  *   $(LINK2 http://www.gnu.org/licenses/gpl-3.0, GNU General Public License 3.0)
12  */
13 module fast.buffer; nothrow
14 
15 import core.stdc.stdint;
16 import core.stdc.stdlib;
17 import std.range;
18 import core.exception;
19 
20 
21 enum allocaLimit = 2048;
22 
23 
24 /*******************************************************************************
25  * 
26  * Dynamic array using `malloc`, `realloc` and `free` under the hood. Note that
27  * memory will be released on scope exit.
28  *
29  **************************************/
30 struct RaiiArray(T)
31 {
32 private:
33 
34 	T*     m_ptr;
35 	size_t m_capacity;
36 
37 
38 public:
39 
40 	nothrow
41 	this(size_t capacity)
42 	{
43 		if (capacity)
44 		{
45 			m_ptr = cast(T*) malloc(capacity);
46 			if (m_ptr is null)
47 				onOutOfMemoryError();
48 			m_capacity = capacity;
49 		}
50 	}
51 
52 
53 	nothrow @nogc
54 	~this()
55 	{
56 		if (m_ptr !is null)
57 			free(m_ptr);
58 	}
59 
60 
61 	@safe pure nothrow @nogc
62 	@property inout(T)* ptr() inout
63 	{
64 		return m_ptr;
65 	}
66 
67 
68 	@safe pure nothrow @nogc
69 	@property size_t capacity() const
70 	{
71 		return m_capacity;
72 	}
73 
74 
75 	nothrow
76 	@property void capacity(size_t value)
77 	{
78 		if (value != 0)
79 		{
80 			if (T* ptrNew = cast(T*) realloc(m_ptr, value))
81 				m_ptr = ptrNew;
82 			else onOutOfMemoryError();
83 		}
84 		else if (m_ptr)
85 		{
86 			free(m_ptr);
87 			m_ptr = null;
88 		}
89 		m_capacity = value;
90 	}
91 
92 
93 	alias length = capacity;
94 
95 
96 	mixin Slicing;
97 	mixin CapacityTools;
98 }
99 
100 
101 /*******************************************************************************
102  * 
103  * Fixed maximum number of items on the stack. Memory is a static stack buffer.
104  * This buffer can be filled up and cleared for reuse.
105  *
106  **************************************/
107 struct LimitedScopeBuffer(T, size_t n)
108 {
109 private:
110 
111 	T[n]   m_data;
112 	size_t m_used;
113 
114 
115 public:
116 
117 	@safe pure nothrow @nogc
118 	@property inout(T)* ptr() inout
119 	{
120 		return m_data.ptr;
121 	}
122 
123 
124 	@safe pure nothrow @nogc
125 	@property size_t length() const
126 	{
127 		return m_used;
128 	}
129 
130 	@safe pure nothrow @nogc
131 	@property void length(size_t value)
132 	in
133 	{
134 		assert( value <= n );
135 	}
136 	body
137 	{
138 		m_used = value;
139 	}
140 
141 
142 	@safe pure nothrow @nogc
143 	inout(T)[] opSlice() inout
144 	{
145 		return m_data[0 .. m_used];
146 	}
147 }
148 
149 
150 struct TempBuffer(T)
151 {
152 	T[] slice;
153 	bool callFree;
154 	
155 	@disable this(this);
156 
157 	~this() nothrow
158 	{
159 		if (this.callFree)
160 			free(this.slice.ptr);
161 	}
162 
163 	T[] opSlice() @safe pure nothrow { return this.slice[]; }
164 	T[] opSlice(size_t a, size_t b) @safe pure nothrow { return this.slice[a .. b]; }
165 	T[] opSliceAssign(const(T)[] value, size_t a, size_t b) @safe pure nothrow { return this.slice[a .. b] = value; }
166 	ref T opIndex(size_t idx) @safe pure nothrow { return this.slice[idx]; }
167 	@property size_t size() @safe pure nothrow { return T.sizeof * this.slice.length; }
168 	@property size_t length() @safe pure nothrow { return this.slice.length; }
169 	alias opDollar = length;
170 	@property T* ptr() @trusted pure nothrow { return this.slice.ptr; } // must use .ptr here for zero length strings
171 	alias ptr this;
172 
173 	auto makeOutputRange()
174 	{
175 		struct OutputRange
176 		{
177 			T* ptr;
178 			size_t idx;
179 
180 			void put(T)(auto ref T t) { ptr[idx++] = t; }
181 			T[] opSlice() pure nothrow { return ptr[0 .. idx]; }
182 		}
183 		return OutputRange(this.slice.ptr, 0);
184 	}
185 }
186 
187 
188 TempBuffer!T tempBuffer(T, alias length, size_t allocaLimit = .allocaLimit)
189 	(void* buffer = (T.sizeof * length <= allocaLimit) ? alloca(T.sizeof * length) : null)
190 {
191 	return TempBuffer!T((cast(T*) (
192 		buffer is null
193 		? malloc(T.sizeof * length)
194 		: buffer))[0 .. length],
195 	buffer is null);
196 }
197 
198 
199 /*******************************************************************************
200  * 
201  * Returns a structure to your stack that contains a buffer of $(D bytes) size.
202  * Memory is allocated by calling `.alloc!T(count)` on it in order to get
203  * `count` elements of type `T`. The return value will be a RAII structure
204  * that releases the memory back to the stack buffer upon destruction, so it can
205  * be reused. The pointer within that RAII structure is aligned to
206  * `T.alignof`. If the internal buffer isn't enough to fulfill the request
207  * including padding from alignment, then `malloc()` is used instead.
208  * 
209  * Warning:
210  *   Always keep the return value of `.alloc()` around on your stack until
211  *   you are done with its contents. Never pass it directly into functions as
212  *   arguments!
213  *
214  * Params:
215  *   bytes = The size of the buffer on the stack.
216  *
217  * Returns:
218  *   A stack buffer allocator.
219  *
220  **************************************/
221 auto stackBuffer(size_t bytes)() @trusted pure
222 {
223 	// All that remains of this after inlining is a stack pointer decrement and
224 	// a mov instruction for the `null`.
225 	StackBuffer!bytes result = void;
226 	result.last = cast(StackBufferEntry!void*) &result.last;
227 	result.sentinel = null;
228 	return result;
229 }
230 
231 
232 auto asOutputRange(T)(T* t) @safe pure
233 {
234 	struct PointerRange
235 	{
236 	private:
237 
238 		T* start;
239 		T* ptr;
240 
241 	public:
242 
243 		void put()(auto ref const(T) t) pure
244 		{
245 			*this.ptr++ = t;
246 		}
247 
248 		T[] opSlice() pure
249 		{
250 			return this.start[0 .. this.ptr - this.start];
251 		}
252 	}
253 	static assert(isOutputRange!(PointerRange, T));
254 	return PointerRange(t, t);
255 }
256 
257 
258 enum bufferArg(alias size)()
259 {
260 	return "((size <= allocaLimit) ? alloca(size) : null)";
261 }
262 
263 
264 
265 package:
266 
267 struct StackBuffer(size_t bytes)
268 {
269 private:
270 	
271 	void[bytes] space = void;
272 	StackBufferEntry!void* last;
273 	void* sentinel;
274 	
275 public:
276 	
277 	@disable this(this);
278 	
279 	@trusted
280 	StackBufferEntry!T alloc(T)(size_t howMany)
281 	{
282 		enum max = size_t.max / T.sizeof;
283 		alias SBE = StackBufferEntry!T;
284 		T* target = cast(T*) (cast(uintptr_t) this.last.ptr / T.alignof * T.alignof);
285 		if (target > this.space.ptr && cast(uintptr_t) (target - cast(T*) this.space.ptr) >= howMany)
286 			return SBE(target - howMany, this.last);
287 		else
288 			// TODO: Respect alignment here as well by padding. Optionally also embed a length in the heap block, so we can provide slicing of the whole thing.
289 			return SBE(howMany <= max ? cast(T*) malloc(T.sizeof * howMany) : null);
290 	}
291 }
292 
293 struct StackBufferEntry(T)
294 {
295 private:
296 
297 	StackBufferEntry!void* prev;
298 
299 	this(T* ptr) pure { this.ptr = ptr; }
300 
301 	this(T* ptr, ref StackBufferEntry!void* last) pure
302 	{
303 		this.ptr = ptr;
304 		this.prev = last;
305 		last = cast(StackBufferEntry!void*) &this;
306 	}
307 
308 
309 public:
310 	
311 	T* ptr;
312 	
313 	static if (!is(T == void))
314 	{
315 		@disable this(this);
316 	
317 		~this() @trusted
318 		{
319 			if (this.prev)
320 			{
321 				StackBufferEntry!void* it = this.prev;
322 				while (it.prev) it = it.prev;
323 				auto last = cast(StackBufferEntry!void**) &prev.ptr;
324 				*last = this.prev;
325 			}
326 			else free(this.ptr);
327 		}
328 
329 		@system pure nothrow @nogc
330 		ref inout(T) opIndex(size_t idx) inout
331 		{
332 			return ptr[idx];
333 		}
334 
335 		@system pure nothrow @nogc
336 		inout(T)[] opSlice(size_t a, size_t b) inout
337 		{
338 			return ptr[a .. b];
339 		}
340 
341 		@safe pure nothrow @nogc
342 		@property auto range()
343 		{
344 			return ptr.asOutputRange();
345 		}
346 	}
347 }
348 
349 
350 
351 private:
352 
353 mixin template Slicing()
354 {
355 	public
356 	{
357 		@nogc pure nothrow
358 		ref inout(T) opIndex(size_t idx) inout
359 		in
360 		{
361 			assert(idx < length);
362 		}
363 		body
364 		{
365 			return ptr[idx];
366 		}
367 
368 
369 		@nogc pure nothrow
370 		inout(T)[] opSlice() inout
371 		{
372 			return ptr[0 .. length];
373 		}
374 		
375 		
376 		@nogc pure nothrow
377 		inout(T)[] opSlice(size_t a, size_t b) inout
378 		in
379 		{
380 			assert(a <= b && b <= length);
381 		}
382 		body
383 		{
384 			return ptr[a .. b];
385 		}
386 	}
387 }
388 
389 
390 mixin template CapacityTools()
391 {
392 	public
393 	{
394 		nothrow
395 		void capacityNeeded(size_t c)
396 		{
397 			if (capacity < c)
398 				capacity = c;
399 		}
400 	}
401 }