1 /**
2  This module contains utility functions to help the implementation of the runtime hook
3 
4   Copyright: Copyright Digital Mars 2000 - 2019.
5   License: Distributed under the
6        $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
7      (See accompanying file LICENSE)
8   Source: $(DRUNTIMESRC core/internal/_array/_utils.d)
9 */
10 module core.internal.array.utils;
11 
12 import core.internal.traits : Parameters;
13 import core.memory : GC;
14 
15 alias BlkInfo = GC.BlkInfo;
16 alias BlkAttr = GC.BlkAttr;
17 
18 private
19 {
20     enum : size_t
21     {
22         PAGESIZE = 4096,
23         BIGLENGTHMASK = ~(PAGESIZE - 1),
24         SMALLPAD = 1,
25         MEDPAD = ushort.sizeof,
26         LARGEPREFIX = 16, // 16 bytes padding at the front of the array
27         LARGEPAD = LARGEPREFIX + 1,
28         MAXSMALLSIZE = 256-SMALLPAD,
29         MAXMEDSIZE = (PAGESIZE / 2) - MEDPAD
30     }
31 }
32 
33 auto gcStatsPure() nothrow pure
34 {
35     import core.memory : GC;
36 
37     auto impureBypass = cast(GC.Stats function() pure nothrow)&GC.stats;
38     return impureBypass();
39 }
40 
41 ulong accumulatePure(string file, int line, string funcname, string name, ulong size) nothrow pure
42 {
43     static ulong impureBypass(string file, int line, string funcname, string name, ulong size) @nogc nothrow
44     {
45         import core.internal.traits : externDFunc;
46 
47         alias accumulate = externDFunc!("rt.profilegc.accumulate", void function(string file, uint line, string funcname, string type, ulong sz) @nogc nothrow);
48         accumulate(file, line, funcname, name, size);
49         return size;
50     }
51 
52     auto func = cast(ulong function(string file, int line, string funcname, string name, ulong size) @nogc nothrow pure)&impureBypass;
53     return func(file, line, funcname, name, size);
54 }
55 
56 version (D_ProfileGC)
57 {
58     /**
59      * TraceGC wrapper generator around the runtime hook `Hook`.
60      * Params:
61      *   Type = The type of hook to report to accumulate
62      *   Hook = The name hook to wrap
63      */
64     template TraceHook(string Type, string Hook)
65     {
66         const char[] TraceHook = q{
67             import core.internal.array.utils : gcStatsPure, accumulatePure;
68 
69             pragma(inline, false);
70             string name = } ~ "`" ~ Type ~ "`;" ~ q{
71 
72             // FIXME: use rt.tracegc.accumulator when it is accessable in the future.
73             version (tracegc)
74         } ~ "{\n" ~ q{
75                 import core.stdc.stdio;
76 
77                 printf("%sTrace file = '%.*s' line = %d function = '%.*s' type = %.*s\n",
78                 } ~ "\"" ~ Hook ~ "\".ptr," ~ q{
79                     file.length, file.ptr,
80                     line,
81                     funcname.length, funcname.ptr,
82                     name.length, name.ptr
83                 );
84             } ~ "}\n" ~ q{
85             ulong currentlyAllocated = gcStatsPure().allocatedInCurrentThread;
86 
87             scope(exit)
88             {
89                 ulong size = gcStatsPure().allocatedInCurrentThread - currentlyAllocated;
90                 if (size > 0)
91                     if (!accumulatePure(file, line, funcname, name, size)) {
92                         // This 'if' and 'assert' is needed to force the compiler to not remove the call to
93                         // `accumulatePure`. It really want to do that while optimizing as the function is
94                         // `pure` and it does not influence the result of this hook.
95 
96                         // `accumulatePure` returns the value of `size`, which can never be zero due to the
97                         // previous 'if'. So this assert will never be triggered.
98                         assert(0);
99                     }
100             }
101         };
102     }
103 
104     /**
105      * TraceGC wrapper around runtime hook `Hook`.
106      * Params:
107      *  T = Type of hook to report to accumulate
108      *  Hook = The hook to wrap
109      *  errorMessage = The error message incase `version != D_TypeInfo`
110      *  file = File that called `_d_HookTraceImpl`
111      *  line = Line inside of `file` that called `_d_HookTraceImpl`
112      *  funcname = Function that called `_d_HookTraceImpl`
113      *  parameters = Parameters that will be used to call `Hook`
114      * Bugs:
115      *  This function template needs be between the compiler and a much older runtime hook that bypassed safety,
116      *  purity, and throwabilty checks. To prevent breaking existing code, this function template
117      *  is temporarily declared `@trusted pure` until the implementation can be brought up to modern D expectations.
118     */
119     auto _d_HookTraceImpl(T, alias Hook, string errorMessage)(string file, int line, string funcname, Parameters!Hook parameters) @trusted pure
120     {
121         version (D_TypeInfo)
122         {
123             mixin(TraceHook!(T.stringof, __traits(identifier, Hook)));
124             return Hook(parameters);
125         }
126         else
127             assert(0, errorMessage);
128     }
129 }
130 
131 /**
132  * Check if the function `F` is calleable in a `nothrow` scope.
133  * Params:
134  *  F = Function that does not take any parameters
135  * Returns:
136  *  if the function is callable in a `nothrow` scope.
137  */
138 enum isNoThrow(alias F) = is(typeof(() nothrow { F(); }));
139 
140 /**
141  * Check if the type `T`'s postblit is called in nothrow, if it exist
142  * Params:
143  *  T = Type to check
144  * Returns:
145  *  if the postblit is callable in a `nothrow` scope, if it exist.
146  *  if it does not exist, return true.
147  */
148 template isPostblitNoThrow(T) {
149     static if (__traits(isStaticArray, T))
150         enum isPostblitNoThrow = isPostblitNoThrow!(typeof(T.init[0]));
151     else static if (__traits(hasMember, T, "__xpostblit") &&
152         // Bugzilla 14746: Check that it's the exact member of S.
153         __traits(isSame, T, __traits(parent, T.init.__xpostblit)))
154         enum isPostblitNoThrow = isNoThrow!(T.init.__xpostblit);
155     else
156         enum isPostblitNoThrow = true;
157 }
158 
159 /**
160  * Clear padding that might not be zeroed by the GC (it assumes it is within the
161  * requested size from the start, but it is actually at the end of the allocated
162  * block).
163  *
164  * Params:
165  *  info = array allocation data
166  *  arrSize = size of the array in bytes
167  *  padSize = size of the padding in bytes
168  */
169 void __arrayClearPad()(ref BlkInfo info, size_t arrSize, size_t padSize) nothrow pure
170 {
171     import core.stdc.string;
172     if (padSize > MEDPAD && !(info.attr & BlkAttr.NO_SCAN) && info.base)
173     {
174         if (info.size < PAGESIZE)
175             memset(info.base + arrSize, 0, padSize);
176         else
177             memset(info.base, 0, LARGEPREFIX);
178     }
179 }
180 
181 /**
182  * Allocate an array memory block by applying the proper padding and assigning
183  * block attributes if not inherited from the existing block.
184  *
185  * Params:
186  *  arrSize = size of the allocated array in bytes
187  * Returns:
188  *  `BlkInfo` with allocation metadata
189  */
190 BlkInfo __arrayAlloc(T)(size_t arrSize) @trusted
191 {
192     import core.checkedint;
193     import core.lifetime : TypeInfoSize;
194     import core.internal.traits : hasIndirections;
195 
196     enum typeInfoSize = TypeInfoSize!T;
197     BlkAttr attr = BlkAttr.APPENDABLE;
198 
199     size_t padSize = arrSize > MAXMEDSIZE ?
200         LARGEPAD :
201         ((arrSize > MAXSMALLSIZE ? MEDPAD : SMALLPAD) + typeInfoSize);
202 
203     bool overflow;
204     auto paddedSize = addu(arrSize, padSize, overflow);
205 
206     if (overflow)
207         return BlkInfo();
208 
209     /* `extern(C++)` classes don't have a classinfo pointer in their vtable,
210      * so the GC can't finalize them.
211      */
212     static if (typeInfoSize)
213         attr |= BlkAttr.STRUCTFINAL | BlkAttr.FINALIZE;
214     static if (!hasIndirections!T)
215         attr |= BlkAttr.NO_SCAN;
216 
217     auto bi = GC.qalloc(paddedSize, attr, typeid(T));
218     __arrayClearPad(bi, arrSize, padSize);
219     return bi;
220 }
221 
222 /**
223  * Get the start of the array for the given block.
224  *
225  * Params:
226  *  info = array metadata
227  * Returns:
228  *  pointer to the start of the array
229  */
230 void *__arrayStart()(return scope BlkInfo info) nothrow pure
231 {
232     return info.base + ((info.size & BIGLENGTHMASK) ? LARGEPREFIX : 0);
233 }
234 
235 /**
236  * Set the allocated length of the array block.  This is called when an array
237  * is appended to or its length is set.
238  *
239  * The allocated block looks like this for blocks < PAGESIZE:
240  * `|elem0|elem1|elem2|...|elemN-1|emptyspace|N*elemsize|`
241  *
242  * The size of the allocated length at the end depends on the block size:
243  *      a block of 16 to 256 bytes has an 8-bit length.
244  *      a block with 512 to pagesize/2 bytes has a 16-bit length.
245  *
246  * For blocks >= pagesize, the length is a size_t and is at the beginning of the
247  * block.  The reason we have to do this is because the block can extend into
248  * more pages, so we cannot trust the block length if it sits at the end of the
249  * block, because it might have just been extended.  If we can prove in the
250  * future that the block is unshared, we may be able to change this, but I'm not
251  * sure it's important.
252  *
253  * In order to do put the length at the front, we have to provide 16 bytes
254  * buffer space in case the block has to be aligned properly.  In x86, certain
255  * SSE instructions will only work if the data is 16-byte aligned.  In addition,
256  * we need the sentinel byte to prevent accidental pointers to the next block.
257  * Because of the extra overhead, we only do this for page size and above, where
258  * the overhead is minimal compared to the block size.
259  *
260  * So for those blocks, it looks like:
261  * `|N*elemsize|padding|elem0|elem1|...|elemN-1|emptyspace|sentinelbyte|``
262  *
263  * where `elem0` starts 16 bytes after the first byte.
264  */
265 bool __setArrayAllocLength(T)(ref BlkInfo info, size_t newLength, bool isShared, size_t oldLength = ~0)
266 {
267     import core.atomic;
268     import core.lifetime : TypeInfoSize;
269 
270     size_t typeInfoSize = TypeInfoSize!T;
271 
272     if (info.size <= 256)
273     {
274         import core.checkedint;
275 
276         bool overflow;
277         auto newLengthPadded = addu(newLength,
278                                      addu(SMALLPAD, typeInfoSize, overflow),
279                                      overflow);
280 
281         if (newLengthPadded > info.size || overflow)
282             // new size does not fit inside block
283             return false;
284 
285         auto length = cast(ubyte *)(info.base + info.size - typeInfoSize - SMALLPAD);
286         if (oldLength != ~0)
287         {
288             if (isShared)
289             {
290                 return cas(cast(shared)length, cast(ubyte)oldLength, cast(ubyte)newLength);
291             }
292             else
293             {
294                 if (*length == cast(ubyte)oldLength)
295                     *length = cast(ubyte)newLength;
296                 else
297                     return false;
298             }
299         }
300         else
301         {
302             // setting the initial length, no cas needed
303             *length = cast(ubyte)newLength;
304         }
305         if (typeInfoSize)
306         {
307             auto typeInfo = cast(TypeInfo*)(info.base + info.size - size_t.sizeof);
308             *typeInfo = cast()typeid(T);
309         }
310     }
311     else if (info.size < PAGESIZE)
312     {
313         if (newLength + MEDPAD + typeInfoSize > info.size)
314             // new size does not fit inside block
315             return false;
316         auto length = cast(ushort *)(info.base + info.size - typeInfoSize - MEDPAD);
317         if (oldLength != ~0)
318         {
319             if (isShared)
320             {
321                 return cas(cast(shared)length, cast(ushort)oldLength, cast(ushort)newLength);
322             }
323             else
324             {
325                 if (*length == oldLength)
326                     *length = cast(ushort)newLength;
327                 else
328                     return false;
329             }
330         }
331         else
332         {
333             // setting the initial length, no cas needed
334             *length = cast(ushort)newLength;
335         }
336         if (typeInfoSize)
337         {
338             auto typeInfo = cast(TypeInfo*)(info.base + info.size - size_t.sizeof);
339             *typeInfo = cast()typeid(T);
340         }
341     }
342     else
343     {
344         if (newLength + LARGEPAD > info.size)
345             // new size does not fit inside block
346             return false;
347         auto length = cast(size_t *)(info.base);
348         if (oldLength != ~0)
349         {
350             if (isShared)
351             {
352                 return cas(cast(shared)length, cast(size_t)oldLength, cast(size_t)newLength);
353             }
354             else
355             {
356                 if (*length == oldLength)
357                     *length = newLength;
358                 else
359                     return false;
360             }
361         }
362         else
363         {
364             // setting the initial length, no cas needed
365             *length = newLength;
366         }
367         if (typeInfoSize)
368         {
369             auto typeInfo = cast(TypeInfo*)(info.base + size_t.sizeof);
370             *typeInfo = cast()typeid(T);
371         }
372     }
373     return true; // resize succeeded
374 }