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 }