Translate _d_newarray{U,iT,T} to a single template (#15299)

* druntime: Copy array allocation functions to `core.internal.array.utils`

This copies `__setArrayAllocLength()`, `__arrayAlloc()` and moves
`__arrayStart()` and `__arrayClearPad()` to
`core.internal.array.utils.d`. This is needed because `_d_newarrayT()`
calls these functions from `rt.lifetime.d`, but the file cannot be
imported from `core.internal.array.creation.d`.

Signed-off-by: Teodor Dutu <teodor.dutu@gmail.com>

* Translate `_d_newarray{U,iT,T}` to a single template

This achieves the following:
- Convert `_d_newarray{U,iT,T}` to a single template `_d_newarrayT` that
handles arrays of elements that either have an init symbol or are
zero-initialised.
- Move compiler lowering to the semantic phase
- Store lowered expression in `NewExp.lowering`

Signed-off-by: Teodor Dutu <teodor.dutu@gmail.com>

---------

Signed-off-by: Teodor Dutu <teodor.dutu@gmail.com>
This commit is contained in:
Teodor Dutu 2023-10-24 10:44:55 +03:00 committed by GitHub
parent 8c7c9bead5
commit fcff1b51ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 510 additions and 57 deletions

View file

@ -1251,15 +1251,8 @@ elem* toElem(Expression e, ref IRState irs)
assert(ne.arguments && ne.arguments.length >= 1);
if (ne.arguments.length == 1)
{
// Single dimension array allocations
Expression arg = (*ne.arguments)[0]; // gives array length
e = toElem(arg, irs);
// call _d_newT(ti, arg)
e = el_param(e, getTypeInfo(ne, ne.type, irs));
const rtl = tda.next.isZeroInit(Loc.initial) ? RTLSYM.NEWARRAYT : RTLSYM.NEWARRAYIT;
e = el_bin(OPcall,TYdarray,el_var(getRtlsym(rtl)),e);
toTraceGC(irs, e, ne.loc);
assert(ne.lowering);
e = toElem(ne.lowering, irs);
}
else
{

View file

@ -1205,10 +1205,11 @@ extern (C++) abstract class Expression : ASTNode
return false;
if (sc.flags & (SCOPE.ctfe | SCOPE.debug_))
return false;
/* The original expression (`new S(...)`) will be verified instead. This
* is to keep errors related to the original code and not the lowering.
/* The original expressions (`new S(...)` or `new S[...]``) will be
* verified instead. This is to keep errors related to the original code
* and not the lowering.
*/
if (f.ident == Id._d_newitemT)
if (f.ident == Id._d_newitemT || f.ident == Id._d_newarrayT)
return false;
if (!f.isNogc())

View file

@ -4406,6 +4406,58 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
(*exp.arguments)[i] = arg;
tb = tb.isTypeDArray().next.toBasetype();
}
if (nargs == 1)
{
if (global.params.betterC || !sc.needsCodegen())
goto LskipNewArrayLowering;
/* Class types may inherit base classes that have errors.
* This may leak errors from the base class to the derived one
* and then to the hook. Semantic analysis is performed eagerly
* to a void this.
*/
if (auto tc = exp.type.nextOf.isTypeClass())
{
tc.sym.dsymbolSemantic(sc);
if (tc.sym.errors)
goto LskipNewArrayLowering;
}
auto hook = global.params.tracegc ? Id._d_newarrayTTrace : Id._d_newarrayT;
if (!verifyHookExist(exp.loc, *sc, hook, "new array"))
goto LskipNewArrayLowering;
/* Lower the memory allocation and initialization of `new T[n]`
* to `_d_newarrayT!T(n)`.
*/
Expression lowering = new IdentifierExp(exp.loc, Id.empty);
lowering = new DotIdExp(exp.loc, lowering, Id.object);
auto tiargs = new Objects();
/* Remove `inout`, `const`, `immutable` and `shared` to reduce
* the number of generated `_d_newarrayT` instances.
*/
const isShared = exp.type.nextOf.isShared();
auto t = exp.type.nextOf.unqualify(MODFlags.wild | MODFlags.const_ |
MODFlags.immutable_ | MODFlags.shared_);
tiargs.push(t);
lowering = new DotTemplateInstanceExp(exp.loc, lowering, hook, tiargs);
auto arguments = new Expressions();
if (global.params.tracegc)
{
auto funcname = (sc.callsc && sc.callsc.func) ?
sc.callsc.func.toPrettyChars() : sc.func.toPrettyChars();
arguments.push(new StringExp(exp.loc, exp.loc.filename.toDString()));
arguments.push(new IntegerExp(exp.loc, exp.loc.linnum, Type.tint32));
arguments.push(new StringExp(exp.loc, funcname.toDString()));
}
arguments.push((*exp.arguments)[0]);
arguments.push(new IntegerExp(exp.loc, isShared, Type.tbool));
lowering = new CallExp(exp.loc, lowering, arguments);
exp.lowering = lowering.expressionSemantic(sc);
}
}
else if (tb.isscalar())
{
@ -4447,6 +4499,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
return setError();
}
LskipNewArrayLowering:
//printf("NewExp: '%s'\n", toChars());
//printf("NewExp:type '%s'\n", type.toChars());
semanticTypeInfo(sc, exp.type);

View file

@ -8769,6 +8769,8 @@ struct Id final
static Identifier* _d_newclassTTrace;
static Identifier* _d_newitemT;
static Identifier* _d_newitemTTrace;
static Identifier* _d_newarrayT;
static Identifier* _d_newarrayTTrace;
static Identifier* _d_assert_fail;
static Identifier* dup;
static Identifier* _aaApply;

View file

@ -321,6 +321,8 @@ immutable Msgtable[] msgtable =
{ "_d_newclassTTrace" },
{ "_d_newitemT" },
{ "_d_newitemTTrace" },
{ "_d_newarrayT" },
{ "_d_newarrayTTrace" },
{ "_d_assert_fail" },
{ "dup" },
{ "_aaApply" },

View file

@ -724,9 +724,20 @@ public:
{
//printf("NewExp.doInlineAs!%s(): %s\n", Result.stringof.ptr, e.toChars());
auto ne = e.copy().isNewExp();
auto lowering = ne.lowering;
if (lowering)
if (auto ce = lowering.isCallExp())
if (ce.f.ident == Id._d_newarrayT)
{
ne.lowering = doInlineAs!Expression(lowering, ids);
goto LhasLowering;
}
ne.thisexp = doInlineAs!Expression(e.thisexp, ids);
ne.argprefix = doInlineAs!Expression(e.argprefix, ids);
ne.arguments = arrayExpressionDoInline(e.arguments);
LhasLowering:
result = ne;
semanticTypeInfo(null, e.type);

View file

@ -4,6 +4,8 @@
TEST_OUTPUT:
---
fail_compilation/imports/foo10727a.d(34): Error: undefined identifier `Frop`
fail_compilation/imports/foo10727a.d(26): Error: template instance `foo10727a.CirBuff!(Foo)` error instantiating
fail_compilation/imports/foo10727a.d(31): instantiated from here: `Bar!(Foo)`
---
*/

View file

@ -4,6 +4,8 @@
TEST_OUTPUT:
---
fail_compilation/imports/foo10727b.d(25): Error: undefined identifier `Frop`
fail_compilation/imports/foo10727b.d(17): Error: template instance `foo10727b.CirBuff!(Foo)` error instantiating
fail_compilation/imports/foo10727b.d(22): instantiated from here: `Bar!(Foo)`
---
*/

View file

@ -3,7 +3,7 @@ REQUIRED_ARGS: -profile=gc
RUN_OUTPUT:
---
bytes allocated, allocations, type, function, file:line
96 1 ubyte[] D main runnable/profilegc_stdout.d:17
96 1 ubyte D main runnable/profilegc_stdout.d:17
---
*/

View file

@ -11,6 +11,11 @@ module core.internal.array.construction;
import core.internal.traits : Unqual;
debug(PRINTF)
{
import core.stdc.stdio;
}
/**
* Does array initialization (not assignment) from another array of the same element type.
* Params:
@ -319,3 +324,165 @@ void _d_arraysetctor(Tarr : T[], T)(scope Tarr p, scope ref T value) @trusted
assert(!didThrow);
assert(counter == 4);
}
/**
* Allocate an array with the garbage collector. Also initalize elements if
* their type has an initializer. Otherwise, not zero-initialize the array.
*
* Has three variants:
* `_d_newarrayU` leaves elements uninitialized
* `_d_newarrayT` initializes to 0 or based on initializer
*
* Params:
* length = `.length` of resulting array
*
* Returns:
* newly allocated array
*/
T[] _d_newarrayU(T)(size_t length, bool isShared=false) pure nothrow @nogc @trusted
{
alias PureType = T[] function(size_t length, bool isShared) pure nothrow @nogc @trusted;
return (cast(PureType) &_d_newarrayUImpl!T)(length, isShared);
}
T[] _d_newarrayUImpl(T)(size_t length, bool isShared=false) @trusted
{
import core.exception : onOutOfMemoryError;
import core.internal.array.utils : __arrayStart, __setArrayAllocLength, __arrayAlloc;
size_t elemSize = T.sizeof;
size_t arraySize;
debug(PRINTF) printf("_d_newarrayU(length = x%zu, size = %zu)\n", length, elemSize);
if (length == 0 || elemSize == 0)
return null;
version (D_InlineAsm_X86)
{
asm pure nothrow @nogc
{
mov EAX, elemSize ;
mul EAX, length ;
mov arraySize, EAX ;
jnc Lcontinue ;
}
}
else version (D_InlineAsm_X86_64)
{
asm pure nothrow @nogc
{
mov RAX, elemSize ;
mul RAX, length ;
mov arraySize, RAX ;
jnc Lcontinue ;
}
}
else
{
import core.checkedint : mulu;
bool overflow = false;
arraySize = mulu(elemSize, length, overflow);
if (!overflow)
goto Lcontinue;
}
Loverflow:
onOutOfMemoryError();
assert(0);
Lcontinue:
auto info = __arrayAlloc!T(arraySize);
if (!info.base)
goto Loverflow;
debug(PRINTF) printf("p = %p\n", info.base);
auto arrstart = __arrayStart(info);
__setArrayAllocLength!T(info, arraySize, isShared);
return (cast(T*) arrstart)[0 .. length];
}
/// ditto
T[] _d_newarrayT(T)(size_t length, bool isShared=false) @trusted
{
T[] result = _d_newarrayU!T(length, isShared);
static if (__traits(isZeroInit, T))
{
import core.stdc.string : memset;
memset(result.ptr, 0, length * T.sizeof);
}
else
{
import core.internal.lifetime : emplaceInitializer;
foreach (ref elem; result)
emplaceInitializer(elem);
}
return result;
}
unittest
{
{
// zero-initialization
struct S { int x, y; }
S[] s = _d_newarrayT!S(10);
assert(s !is null);
assert(s.length == 10);
foreach (ref elem; s)
{
assert(elem.x == 0);
assert(elem.y == 0);
}
}
{
// S.init
struct S { int x = 2, y = 3; }
S[] s = _d_newarrayT!S(10);
assert(s.length == 10);
foreach (ref elem; s)
{
assert(elem.x == 2);
assert(elem.y == 3);
}
}
}
unittest
{
int pblits;
struct S
{
this(this) { pblits++; }
}
S[] s = _d_newarrayT!S(2);
assert(s.length == 2);
assert(pblits == 0);
}
version (D_ProfileGC)
{
/**
* TraceGC wrapper around $(REF _d_newitemT, core,lifetime).
*/
T[] _d_newarrayTTrace(T)(string file, int line, string funcname, size_t length, bool isShared) @trusted
{
version (D_TypeInfo)
{
import core.internal.array.utils : TraceHook, gcStatsPure, accumulatePure;
mixin(TraceHook!(T.stringof, "_d_newarrayT"));
return _d_newarrayT!T(length, isShared);
}
else
assert(0, "Cannot create new array if compiling without support for runtime type information!");
}
}

View file

@ -10,6 +10,25 @@
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
{
@ -136,3 +155,220 @@ template isPostblitNoThrow(T) {
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
}

View file

@ -2731,7 +2731,8 @@ if (is(T == class))
{
import core.internal.traits : hasIndirections;
import core.exception : onOutOfMemoryError;
import core.memory : GC, pureMalloc;
import core.memory : pureMalloc;
import core.memory : GC;
alias BlkAttr = GC.BlkAttr;
@ -2820,11 +2821,11 @@ T _d_newclassTTrace(T)(string file, int line, string funcname) @trusted
T* _d_newitemT(T)() @trusted
{
import core.internal.lifetime : emplaceInitializer;
import core.internal.traits : hasElaborateDestructor, hasIndirections;
import core.internal.traits : hasIndirections;
import core.memory : GC;
auto flags = !hasIndirections!T ? GC.BlkAttr.NO_SCAN : GC.BlkAttr.NONE;
immutable tiSize = hasElaborateDestructor!T ? size_t.sizeof : 0;
immutable tiSize = TypeInfoSize!T;
immutable itemSize = T.sizeof;
immutable totalSize = itemSize + tiSize;
if (tiSize)
@ -3004,3 +3005,9 @@ version (D_ProfileGC)
assert(0, "Cannot create new `struct` if compiling without support for runtime type information!");
}
}
template TypeInfoSize(T)
{
import core.internal.traits : hasElaborateDestructor;
enum TypeInfoSize = hasElaborateDestructor!T ? size_t.sizeof : 0;
}

View file

@ -4668,6 +4668,7 @@ version (D_ProfileGC)
public import core.internal.array.appending : _d_arrayappendTTrace;
public import core.internal.array.concatenation : _d_arraycatnTXTrace;
public import core.lifetime : _d_newitemTTrace;
public import core.internal.array.construction : _d_newarrayTTrace;
}
public import core.internal.array.appending : _d_arrayappendcTXImpl;
public import core.internal.array.comparison : __cmp;
@ -4676,6 +4677,7 @@ public import core.internal.array.casting: __ArrayCast;
public import core.internal.array.concatenation : _d_arraycatnTX;
public import core.internal.array.construction : _d_arrayctor;
public import core.internal.array.construction : _d_arraysetctor;
public import core.internal.array.construction : _d_newarrayT;
public import core.internal.array.arrayassign : _d_arrayassign_l;
public import core.internal.array.arrayassign : _d_arrayassign_r;
public import core.internal.array.arrayassign : _d_arraysetassign;

View file

@ -13,6 +13,7 @@
module rt.lifetime;
import core.attribute : weak;
import core.internal.array.utils : __arrayStart, __arrayClearPad;
import core.memory;
debug(PRINTF) import core.stdc.stdio;
static import rt.tlsgc;
@ -226,7 +227,6 @@ size_t structTypeInfoSize(const TypeInfo ti) pure nothrow @nogc
private class ArrayAllocLengthLock
{}
/**
Set the allocated length of the array block. This is called
any time an array is appended to or its length is set.
@ -386,14 +386,6 @@ private size_t __arrayAllocLength(ref BlkInfo info, const TypeInfo tinext) pure
return *cast(size_t *)(info.base);
}
/**
get the start of the array for the given block
*/
private void *__arrayStart(return scope BlkInfo info) nothrow pure
{
return info.base + ((info.size & BIGLENGTHMASK) ? LARGEPREFIX : 0);
}
/**
get the padding required to allocate size bytes. Note that the padding is
NOT included in the passed in size. Therefore, do NOT call this function
@ -404,22 +396,6 @@ private size_t __arrayPad(size_t size, const TypeInfo tinext) nothrow pure @trus
return size > MAXMEDSIZE ? LARGEPAD : ((size > MAXSMALLSIZE ? MEDPAD : SMALLPAD) + structTypeInfoSize(tinext));
}
/**
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)
*/
private 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

View file

@ -17,9 +17,6 @@ module rt.tracegc;
// version = tracegc;
extern (C) void[] _d_newarrayT(const TypeInfo ti, size_t length);
extern (C) void[] _d_newarrayU(const scope TypeInfo ti, size_t length);
extern (C) void[] _d_newarrayiT(const TypeInfo ti, size_t length);
extern (C) void[] _d_newarraymTX(const TypeInfo ti, size_t[] dims);
extern (C) void[] _d_newarraymiTX(const TypeInfo ti, size_t[] dims);
extern (C) void _d_callfinalizer(void* p);
@ -59,6 +56,7 @@ extern (C) size_t gc_extend(void* p, size_t mx, size_t sz, const TypeInfo ti = n
enum accumulator = q{
import rt.profilegc : accumulate;
import core.memory : GC;
import core.stdc.string : strstr;
static if (is(typeof(ci)))
string name = ci.name;
@ -91,7 +89,8 @@ enum accumulator = q{
scope(exit)
{
ulong size = GC.allocatedInCurrentThread - currentlyAllocated;
if (size > 0)
// Skip internal functions.
if (size > 0 && strstr(funcname.ptr, "core.internal") is null)
accumulate(file, line, funcname, name, size);
}
};

View file

@ -11,9 +11,9 @@ bytes allocated, allocations, type, function, file:line
16 1 char[] D main src/profilegc.d:36
16 1 closure profilegc.main.foo src/profilegc.d:45
16 1 float D main src/profilegc.d:16
16 1 float[] D main src/profilegc.d:17
16 1 float D main src/profilegc.d:17
16 1 int D main src/profilegc.d:13
16 1 int[] D main src/profilegc.d:14
16 1 int D main src/profilegc.d:14
16 1 int[] D main src/profilegc.d:22
16 1 int[] D main src/profilegc.d:37
16 1 wchar[] D main src/profilegc.d:35

View file

@ -11,9 +11,9 @@ bytes allocated, allocations, type, function, file:line
16 1 char[] D main src/profilegc.d:36
16 1 closure profilegc.main.foo src/profilegc.d:45
16 1 float D main src/profilegc.d:16
16 1 float[] D main src/profilegc.d:17
16 1 float D main src/profilegc.d:17
16 1 int D main src/profilegc.d:13
16 1 int[] D main src/profilegc.d:14
16 1 int D main src/profilegc.d:14
16 1 int[] D main src/profilegc.d:22
16 1 int[] D main src/profilegc.d:37
16 1 wchar[] D main src/profilegc.d:35

View file

@ -11,9 +11,9 @@ bytes allocated, allocations, type, function, file:line
16 1 char[] D main src/profilegc.d:36
16 1 closure profilegc.main.foo src/profilegc.d:45
16 1 float D main src/profilegc.d:16
16 1 float[] D main src/profilegc.d:17
16 1 float D main src/profilegc.d:17
16 1 int D main src/profilegc.d:13
16 1 int[] D main src/profilegc.d:14
16 1 int D main src/profilegc.d:14
16 1 int[] D main src/profilegc.d:22
16 1 int[] D main src/profilegc.d:37
16 1 wchar[] D main src/profilegc.d:35

View file

@ -11,9 +11,9 @@ bytes allocated, allocations, type, function, file:line
16 1 char[] D main src/profilegc.d:36
16 1 closure profilegc.main.foo src/profilegc.d:45
16 1 float D main src/profilegc.d:16
16 1 float[] D main src/profilegc.d:17
16 1 float D main src/profilegc.d:17
16 1 int D main src/profilegc.d:13
16 1 int[] D main src/profilegc.d:14
16 1 int D main src/profilegc.d:14
16 1 int[] D main src/profilegc.d:22
16 1 int[] D main src/profilegc.d:37
16 1 wchar[] D main src/profilegc.d:35

View file

@ -11,9 +11,9 @@ bytes allocated, allocations, type, function, file:line
16 1 char[] D main src/profilegc.d:36
16 1 closure profilegc.main.foo src/profilegc.d:45
16 1 float D main src/profilegc.d:16
16 1 float[] D main src/profilegc.d:17
16 1 float D main src/profilegc.d:17
16 1 int D main src/profilegc.d:13
16 1 int[] D main src/profilegc.d:14
16 1 int D main src/profilegc.d:14
16 1 int[] D main src/profilegc.d:22
16 1 int[] D main src/profilegc.d:37
16 1 wchar[] D main src/profilegc.d:35

View file

@ -11,9 +11,9 @@ bytes allocated, allocations, type, function, file:line
16 1 char[] D main src/profilegc.d:36
16 1 closure profilegc.main.foo src/profilegc.d:45
16 1 float D main src/profilegc.d:16
16 1 float[] D main src/profilegc.d:17
16 1 float D main src/profilegc.d:17
16 1 int D main src/profilegc.d:13
16 1 int[] D main src/profilegc.d:14
16 1 int D main src/profilegc.d:14
16 1 int[] D main src/profilegc.d:22
16 1 int[] D main src/profilegc.d:37
16 1 wchar[] D main src/profilegc.d:35