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