xref: /netbsd-src/external/gpl3/gcc/dist/libphobos/src/std/internal/scopebuffer.d (revision ccd9df534e375a4366c5b55f23782053c7a98d82)
1 /*
2  * Copyright: 2014 by Digital Mars
3  * License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0).
4  * Authors: Walter Bright
5  * Source: $(PHOBOSSRC std/internal/scopebuffer.d)
6  */
7 
8 module std.internal.scopebuffer;
9 
10 
11 //debug=ScopeBuffer;
12 
13 import core.stdc.stdlib : realloc;
14 import std.traits;
15 import std.internal.attributes : betterC;
16 
17 /**************************************
18  * ScopeBuffer encapsulates using a local array as a temporary buffer.
19  * It is initialized with a local array that should be large enough for
20  * most uses. If the need exceeds that size, ScopeBuffer will reallocate
21  * the data using its `realloc` function.
22  *
23  * ScopeBuffer cannot contain more than `(uint.max-16)/2` elements.
24  *
25  * ScopeBuffer is an Output Range.
26  *
27  * Since ScopeBuffer may store elements of type `T` in `malloc`'d memory,
28  * those elements are not scanned when the GC collects. This can cause
29  * memory corruption. Do not use ScopeBuffer when elements of type `T` point
30  * to the GC heap, except when a `realloc` function is provided which supports this.
31  *
32  * Example:
33 ---
34 import core.stdc.stdio;
35 import std.internal.scopebuffer;
36 void main()
37 {
38     char[2] buf = void;
39     auto textbuf = ScopeBuffer!char(buf);
40     scope(exit) textbuf.free(); // necessary for cleanup
41 
42     // Put characters and strings into textbuf, verify they got there
43     textbuf.put('a');
44     textbuf.put('x');
45     textbuf.put("abc");
46     assert(textbuf.length == 5);
47     assert(textbuf[1 .. 3] == "xa");
48     assert(textbuf[3] == 'b');
49 
50     // Can shrink it
51     textbuf.length = 3;
52     assert(textbuf[0 .. textbuf.length] == "axa");
53     assert(textbuf[textbuf.length - 1] == 'a');
54     assert(textbuf[1 .. 3] == "xa");
55 
56     textbuf.put('z');
57     assert(textbuf[] == "axaz");
58 
59     // Can shrink it to 0 size, and reuse same memory
60     textbuf.length = 0;
61 }
62 ---
63  * It is invalid to access ScopeBuffer's contents when ScopeBuffer goes out of scope.
64  * Hence, copying the contents are necessary to keep them around:
65 ---
66 import std.internal.scopebuffer;
67 string cat(string s1, string s2)
68 {
69     char[10] tmpbuf = void;
70     auto textbuf = ScopeBuffer!char(tmpbuf);
71     scope(exit) textbuf.free();
72     textbuf.put(s1);
73     textbuf.put(s2);
74     textbuf.put("even more");
75     return textbuf[].idup;
76 }
77 ---
78  * ScopeBuffer is intended for high performance usages in `@system` and `@trusted` code.
79  * It is designed to fit into two 64 bit registers, again for high performance use.
80  * If used incorrectly, memory leaks and corruption can result. Be sure to use
81  * $(D scope(exit) textbuf.free();) for proper cleanup, and do not refer to a ScopeBuffer
82  * instance's contents after `ScopeBuffer.free()` has been called.
83  *
84  * The `realloc` parameter defaults to C's `realloc()`. Another can be supplied to override it.
85  *
86  * ScopeBuffer instances may be copied, as in:
87 ---
88 textbuf = doSomething(textbuf, args);
89 ---
90  * which can be very efficent, but these must be regarded as a move rather than a copy.
91  * Additionally, the code between passing and returning the instance must not throw
92  * exceptions, otherwise when `ScopeBuffer.free()` is called, memory may get corrupted.
93  */
94 
95 @system
96 struct ScopeBuffer(T, alias realloc = /*core.stdc.stdlib*/.realloc)
97 if (isAssignable!T &&
98     !hasElaborateDestructor!T &&
99     !hasElaborateCopyConstructor!T &&
100     !hasElaborateAssign!T)
101 {
102     import core.exception : onOutOfMemoryError;
103     import core.stdc.string : memcpy;
104 
105 
106     /**************************
107      * Initialize with buf to use as scratch buffer space.
108      * Params:
109      *  buf = Scratch buffer space, must have length that is even
110      * Example:
111      * ---
112      * ubyte[10] tmpbuf = void;
113      * auto sbuf = ScopeBuffer!ubyte(tmpbuf);
114      * ---
115      * Note:
116      * If buf was created by the same `realloc` passed as a parameter
117      * to `ScopeBuffer`, then the contents of `ScopeBuffer` can be extracted without needing
118      * to copy them, and `ScopeBuffer.free()` will not need to be called.
119      */
120     this(T[] buf)
121         in
122         {
123             assert(!(buf.length & wasResized));    // assure even length of scratch buffer space
124             assert(buf.length <= uint.max);     // because we cast to uint later
125         }
126     do
127     {
128         this.buf = buf.ptr;
129         this.bufLen = cast(uint) buf.length;
130     }
131 
132     @system @betterC unittest
133     {
134         ubyte[10] tmpbuf = void;
135         auto sbuf = ScopeBuffer!ubyte(tmpbuf);
136     }
137 
138     /**************************
139      * Releases any memory used.
140      * This will invalidate any references returned by the `[]` operator.
141      * A destructor is not used, because that would make it not POD
142      * (Plain Old Data) and it could not be placed in registers.
143      */
144     void free()
145     {
146         debug(ScopeBuffer) buf[0 .. bufLen] = 0;
147         if (bufLen & wasResized)
148             realloc(buf, 0);
149         buf = null;
150         bufLen = 0;
151         used = 0;
152     }
153 
154     /************************
155      * Append element c to the buffer.
156      * This member function makes `ScopeBuffer` an Output Range.
157      */
158     void put(T c)
159     {
160         /* j will get enregistered, while used will not because resize() may change used
161          */
162         const j = used;
163         if (j == bufLen)
164         {
165             assert(j <= (uint.max - 16) / 2);
166             resize(j * 2 + 16);
167         }
168         buf[j] = c;
169         used = j + 1;
170     }
171 
172     /************************
173      * Append array s to the buffer.
174      *
175      * If `const(T)` can be converted to `T`, then put will accept
176      * `const(T)[]` as input. It will accept a `T[]` otherwise.
177      */
178     package alias CT = Select!(is(const(T) : T), const(T), T);
179     /// ditto
180     void put(CT[] s)
181     {
182         const newlen = used + s.length;
183         assert((cast(ulong) used + s.length) <= uint.max);
184         const len = bufLen;
185         if (newlen > len)
186         {
187             assert(len <= uint.max / 2);
188             resize(newlen <= len * 2 ? len * 2 : newlen);
189         }
190         buf[used .. newlen] = s[];
191         used = cast(uint) newlen;
192     }
193 
194     /******
195      * Returns:
196      *  A slice into the temporary buffer.
197      * Warning:
198      *  The result is only valid until the next `put()` or `ScopeBuffer` goes out of scope.
199      */
200     @system inout(T)[] opSlice(size_t lower, size_t upper) inout
201         in
202         {
203             assert(lower <= bufLen);
204             assert(upper <= bufLen);
205             assert(lower <= upper);
206         }
207     do
208     {
209         return buf[lower .. upper];
210     }
211 
212     /// ditto
213     @system inout(T)[] opSlice() inout
214     {
215         assert(used <= bufLen);
216         return buf[0 .. used];
217     }
218 
219     /*******
220      * Returns:
221      *  The element at index i.
222      */
223     ref inout(T) opIndex(size_t i) inout
224     {
225         assert(i < bufLen);
226         return buf[i];
227     }
228 
229     /***
230      * Returns:
231      *  The number of elements in the `ScopeBuffer`.
232      */
233     @property size_t length() const
234     {
235         return used;
236     }
237 
238     /***
239      * Used to shrink the length of the buffer,
240      * typically to `0` so the buffer can be reused.
241      * Cannot be used to extend the length of the buffer.
242      */
243     @property void length(size_t i)
244         in
245         {
246             assert(i <= this.used);
247         }
248     do
249     {
250         this.used = cast(uint) i;
251     }
252 
253     alias opDollar = length;
254 
255   private:
256     T* buf;
257     // Using uint instead of size_t so the struct fits in 2 registers in 64 bit code
258     uint bufLen;
259     enum wasResized = 1;         // this bit is set in bufLen if we control the memory
260     uint used;
261 
262     void resize(size_t newsize)
263         in
264         {
265             assert(newsize <= uint.max);
266         }
267     do
268     {
269         //writefln("%s: oldsize %s newsize %s", id, buf.length, newsize);
270         newsize |= wasResized;
271         void *newBuf = realloc((bufLen & wasResized) ? buf : null, newsize * T.sizeof);
272         if (!newBuf)
273             onOutOfMemoryError();
274         if (!(bufLen & wasResized))
275         {
276             memcpy(newBuf, buf, used * T.sizeof);
277             debug(ScopeBuffer) buf[0 .. bufLen] = 0;
278         }
279         buf = cast(T*) newBuf;
280         bufLen = cast(uint) newsize;
281 
282         /* This function is called only rarely,
283          * inlining results in poorer register allocation.
284          */
285         version (DigitalMars)
286             /* With dmd, a fake loop will prevent inlining.
287              * Using a hack until a language enhancement is implemented.
288              */
289             while (1) { break; }
290     }
291 }
292 
293 @system @betterC unittest
294 {
295     import core.stdc.stdio;
296     import std.range;
297 
298     char[2] tmpbuf = void;
299     {
300     // Exercise all the lines of code except for assert(0)'s
301     auto textbuf = ScopeBuffer!char(tmpbuf);
302     scope(exit) textbuf.free();
303 
304     static assert(isOutputRange!(ScopeBuffer!char, char));
305 
306     textbuf.put('a');
307     textbuf.put('x');
308     textbuf.put("abc");         // tickle put([])'s resize
309     assert(textbuf.length == 5);
310     assert(textbuf[1 .. 3] == "xa");
311     assert(textbuf[3] == 'b');
312 
313     textbuf.length = textbuf.length - 1;
314     assert(textbuf[0 .. textbuf.length] == "axab");
315 
316     textbuf.length = 3;
317     assert(textbuf[0 .. textbuf.length] == "axa");
318     assert(textbuf[textbuf.length - 1] == 'a');
319     assert(textbuf[1 .. 3] == "xa");
320 
321     textbuf.put(cast(dchar)'z');
322     assert(textbuf[] == "axaz");
323 
324     textbuf.length = 0;                 // reset for reuse
325     assert(textbuf.length == 0);
326 
327     foreach (char c; "asdf;lasdlfaklsdjfalksdjfa;lksdjflkajsfdasdfkja;sdlfj")
328     {
329         textbuf.put(c); // tickle put(c)'s resize
330     }
331     assert(textbuf[] == "asdf;lasdlfaklsdjfalksdjfa;lksdjflkajsfdasdfkja;sdlfj");
332     } // run destructor on textbuf here
333 
334 }
335 
336 @system unittest
337 {
338     string cat(string s1, string s2)
339     {
340         char[10] tmpbuf = void;
341         auto textbuf = ScopeBuffer!char(tmpbuf);
342         scope(exit) textbuf.free();
343         textbuf.put(s1);
344         textbuf.put(s2);
345         textbuf.put("even more");
346         return textbuf[].idup;
347     }
348 
349     auto s = cat("hello", "betty");
350     assert(s == "hellobettyeven more");
351 }
352 
353 // const
354 @system @betterC unittest
355 {
356     char[10] tmpbuf = void;
357     auto textbuf = ScopeBuffer!char(tmpbuf);
358     scope(exit) textbuf.free();
359     foreach (i; 0 .. 10) textbuf.put('w');
360     const csb = textbuf;
361     const elem = csb[3];
362     const slice0 = csb[0 .. 5];
363     const slice1 = csb[];
364 }
365 
366 /*********************************
367  * Creates a `ScopeBuffer` instance using type deduction - see
368  * $(LREF .ScopeBuffer.this) for details.
369  * Params:
370  *      tmpbuf = the initial buffer to use
371  * Returns:
372  *      An instance of `ScopeBuffer`.
373  */
374 
375 auto scopeBuffer(T)(T[] tmpbuf)
376 {
377     return ScopeBuffer!T(tmpbuf);
378 }
379 
380 ///
381 @system @betterC unittest
382 {
383     ubyte[10] tmpbuf = void;
384     auto sb = scopeBuffer(tmpbuf);
385     scope(exit) sb.free();
386 }
387 
388 @system @betterC unittest
389 {
390     ScopeBuffer!(int*) b;
391     int*[] s;
392     b.put(s);
393 
394     ScopeBuffer!char c;
395     string s1;
396     char[] s2;
397     c.put(s1);
398     c.put(s2);
399 }
400