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 }