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