1 /**
2  * Fast buffer implementation.
3  *
4  * Authors:
5  *   $(LINK2 mailto:Marco.Leise@gmx.de, Marco Leise)
6  *
7  * Copyright:
8  *   © 2015-2023 $(LINK2 mailto:Marco.Leise@gmx.de, Marco Leise), $(LINK2 mailto:etienne@cimons.com, Etienne Cimon)
9  *
10  * License:
11  *   $(LINK2 https://mit-license.org/, The MIT License (MIT))
12  */
13 module fast.buffer;
14 nothrow
15 
16 //import core.stdc.stdint;
17 //import core.stdc.stdlib;
18 import std.range;
19 
20 enum allocaLimit = 2048;
21 
22 /*******************************************************************************
23  * 
24  * Fixed maximum number of items on the stack. Memory is a static stack buffer.
25  * This buffer can be filled up and cleared for reuse.
26  *
27  **************************************/
28 struct LimitedScopeBuffer(T, size_t n)
29 {
30 private:
31 
32 	T[n] m_data;
33 	size_t m_used;
34 
35 public:
36 
37 	@safe pure nothrow @nogc
38 	@property inout(T)* ptr() inout
39 	{
40 		return m_data.ptr;
41 	}
42 
43 	@safe pure nothrow @nogc
44 	@property size_t length() const
45 	{
46 		return m_used;
47 	}
48 
49 	@safe pure nothrow @nogc
50 	@property void length(size_t value)
51 	in
52 	{
53 		assert(value <= n);
54 	}
55 	body
56 	{
57 		m_used = value;
58 	}
59 
60 	@safe pure nothrow @nogc
61 	inout(T)[] opSlice() inout
62 	{
63 		return m_data[0 .. m_used];
64 	}
65 }
66 
67 struct TempBuffer(T)
68 {
69 	T[] slice;
70 	bool callFree;
71 
72 	@disable this(this);
73 
74 	~this() nothrow
75 	{
76 		if (this.callFree)
77 			free(this.slice.ptr);
78 	}
79 
80 	T[] opSlice() @safe pure nothrow
81 	{
82 		return this.slice[];
83 	}
84 
85 	T[] opSlice(size_t a, size_t b) @safe pure nothrow
86 	{
87 		return this.slice[a .. b];
88 	}
89 
90 	T[] opSliceAssign(const(T)[] value, size_t a, size_t b) @safe pure nothrow
91 	{
92 		return this.slice[a .. b] = value;
93 	}
94 
95 	ref T opIndex(size_t idx) @safe pure nothrow
96 	{
97 		return this.slice[idx];
98 	}
99 
100 	@property size_t size() @safe pure nothrow
101 	{
102 		return T.sizeof * this.slice.length;
103 	}
104 
105 	@property size_t length() @safe pure nothrow
106 	{
107 		return this.slice.length;
108 	}
109 
110 	alias opDollar = length;
111 	@property T* ptr() @trusted pure nothrow
112 	{
113 		return this.slice.ptr;
114 	} // must use .ptr here for zero length strings
115 	alias ptr this;
116 
117 	auto makeOutputRange()
118 	{
119 		struct OutputRange
120 		{
121 			T* ptr;
122 			size_t idx;
123 
124 			void put(T)(auto ref T t)
125 			{
126 				ptr[idx++] = t;
127 			}
128 
129 			T[] opSlice() pure nothrow
130 			{
131 				return ptr[0 .. idx];
132 			}
133 		}
134 
135 		return OutputRange(this.slice.ptr, 0);
136 	}
137 }
138 
139 TempBuffer!T tempBuffer(T, alias length, size_t allocaLimit = .allocaLimit)(
140 	void* buffer = (T.sizeof * length <= allocaLimit) ? alloca(T.sizeof * length) : null)
141 {
142 	return TempBuffer!T((cast(T*)(
143 			buffer is null
144 			? malloc(T.sizeof * length) : buffer))[0 .. length],
145 		buffer is null);
146 }
147 
148 /*******************************************************************************
149  * 
150  * Returns a structure to your stack that contains a buffer of $(D bytes) size.
151  * Memory is allocated by calling `.alloc!T(count)` on it in order to get
152  * `count` elements of type `T`. The return value will be a RAII structure
153  * that releases the memory back to the stack buffer upon destruction, so it can
154  * be reused. The pointer within that RAII structure is aligned to
155  * `T.alignof`. If the internal buffer isn't enough to fulfill the request
156  * including padding from alignment, then `malloc()` is used instead.
157  * 
158  * Warning:
159  *   Always keep the return value of `.alloc()` around on your stack until
160  *   you are done with its contents. Never pass it directly into functions as
161  *   arguments!
162  *
163  * Params:
164  *   bytes = The size of the buffer on the stack.
165  *
166  * Returns:
167  *   A stack buffer allocator.
168  *
169  **************************************/
170 auto stackBuffer(size_t bytes)() @trusted pure
171 {
172 	// All that remains of this after inlining is a stack pointer decrement and
173 	// a mov instruction for the `null`.
174 	StackBuffer!bytes result = void;
175 	result.last = cast(StackBufferEntry!void*)&result.last;
176 	result.sentinel = null;
177 	return result;
178 }
179 
180 auto asOutputRange(T)(T* t) @safe pure
181 {
182 	struct PointerRange
183 	{
184 	private:
185 
186 		T* start;
187 		T* ptr;
188 
189 	public:
190 
191 		void put()(auto ref const(T) t) pure
192 		{
193 			*this.ptr++ = t;
194 		}
195 
196 		T[] opSlice() pure
197 		{
198 			return this.start[0 .. this.ptr - this.start];
199 		}
200 	}
201 
202 	static assert(isOutputRange!(PointerRange, T));
203 	return PointerRange(t, t);
204 }
205 
206 enum bufferArg(alias size)()
207 {
208 	return "((size <= allocaLimit) ? alloca(size) : null)";
209 }
210 
211 package:
212 
213 struct StackBuffer(size_t bytes)
214 {
215 private:
216 
217 	void[bytes] space = void;
218 	StackBufferEntry!void* last;
219 	void* sentinel;
220 
221 public:
222 
223 	@disable this(this);
224 
225 	@trusted
226 	StackBufferEntry!T alloc(T)(size_t howMany)
227 	{
228 		enum max = size_t.max / T.sizeof;
229 		alias SBE = StackBufferEntry!T;
230 		T* target = cast(T*)(cast(uintptr_t) this.last.ptr / T.alignof * T.alignof);
231 		if (target > this.space.ptr && cast(uintptr_t)(target - cast(T*) this.space.ptr) >= howMany)
232 			return SBE(target - howMany, this.last);
233 		else // 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.
234 			return SBE(howMany <= max ? cast(T*) malloc(T.sizeof * howMany) : null);
235 	}
236 }
237 
238 struct StackBufferEntry(T)
239 {
240 private:
241 
242 	StackBufferEntry!void* prev;
243 
244 	this(T* ptr) pure
245 	{
246 		this.ptr = ptr;
247 	}
248 
249 	this(T* ptr, ref StackBufferEntry!void* last) pure
250 	{
251 		this.ptr = ptr;
252 		this.prev = last;
253 		last = cast(StackBufferEntry!void*)&this;
254 	}
255 
256 public:
257 
258 	T* ptr;
259 
260 	static if (!is(T == void))
261 	{
262 		@disable this(this);
263 
264 		~this() @trusted
265 		{
266 			if (this.prev)
267 			{
268 				StackBufferEntry!void* it = this.prev;
269 				while (it.prev)
270 					it = it.prev;
271 				auto last = cast(StackBufferEntry!void**)&prev.ptr;
272 				*last = this.prev;
273 			}
274 			else
275 				free(this.ptr);
276 		}
277 
278 		@system pure nothrow @nogc
279 		ref inout(T) opIndex(size_t idx) inout
280 		{
281 			return ptr[idx];
282 		}
283 
284 		@system pure nothrow @nogc
285 		inout(T)[] opSlice(size_t a, size_t b) inout
286 		{
287 			return ptr[a .. b];
288 		}
289 
290 		@safe pure nothrow @nogc
291 		@property auto range()
292 		{
293 			return ptr.asOutputRange();
294 		}
295 	}
296 }