Merge pull request #20863 from rainers/rainer_aa_fak_entry_ti

Fix issue #17503 - Associative Arrays improperly register a GC-allocated TypeInfo for element cleanup

Signed-off-by: Nicholas Wilson <thewilsonator@users.noreply.github.com>
Signed-off-by: Steven Schveighoffer <schveiguy@users.noreply.github.com>
Merged-on-behalf-of: Steven Schveighoffer <schveiguy@users.noreply.github.com>
This commit is contained in:
The Dlang Bot 2025-02-19 17:59:27 +01:00 committed by GitHub
commit 016c38e276
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 226 additions and 162 deletions

View file

@ -2832,6 +2832,7 @@ Expression castTo(Expression e, Scope* sc, Type t, Type att = null)
(*ae.keys)[i] = ex;
}
ae.type = t;
semanticTypeInfo(sc, ae.type);
return ae;
}
return visit(e);

View file

@ -1549,6 +1549,8 @@ extern (C++) final class TypeInfoStaticArrayDeclaration : TypeInfoDeclaration
*/
extern (C++) final class TypeInfoAssociativeArrayDeclaration : TypeInfoDeclaration
{
Type entry; // type of TypeInfo_AssociativeArray.Entry!(t.index, t.next)
extern (D) this(Type tinfo)
{
super(tinfo);

View file

@ -397,6 +397,8 @@ public:
class TypeInfoAssociativeArrayDeclaration final : public TypeInfoDeclaration
{
public:
Type* entry;
static TypeInfoAssociativeArrayDeclaration *create(Type *tinfo);
void accept(Visitor *v) override { v->visit(this); }

View file

@ -11168,6 +11168,11 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
exp.e2 = e2x;
t1 = e1x.type.toBasetype();
}
else if (t1.ty == Taarray)
{
// when assigning a constant, the need for TypeInfo might change
semanticTypeInfo(sc, t1);
}
/* Check the mutability of e1.
*/
if (auto ale = exp.e1.isArrayLengthExp())
@ -13070,7 +13075,10 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
exp.e1 = exp.e1.implicitCastTo(sc, ta.index);
}
semanticTypeInfo(sc, ta.index);
// even though the glue layer only needs the type info of the index,
// this might be the first time an AA literal is accessed, so check
// the full type info
semanticTypeInfo(sc, ta);
// Return type is pointer to value
exp.type = ta.nextOf().pointerTo();
@ -13307,8 +13315,9 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
}
if (exp.e1.type.toBasetype().ty == Taarray)
{
semanticTypeInfo(sc, exp.e1.type.toBasetype());
}
if (!target.isVectorOpSupported(t1, exp.op, t2))
{
@ -16849,6 +16858,9 @@ void semanticTypeInfo(Scope* sc, Type t)
{
semanticTypeInfo(sc, t.index);
semanticTypeInfo(sc, t.next);
if (global.params.useTypeInfo)
getTypeInfoType(t.loc, t, sc);
}
void visitStruct(TypeStruct t)

View file

@ -6912,6 +6912,7 @@ public:
class TypeInfoAssociativeArrayDeclaration final : public TypeInfoDeclaration
{
public:
Type* entry;
static TypeInfoAssociativeArrayDeclaration* create(Type* tinfo);
void accept(Visitor* v) override;
};
@ -8791,6 +8792,7 @@ struct Id final
static Identifier* xopEquals;
static Identifier* xopCmp;
static Identifier* xtoHash;
static Identifier* Entry;
static Identifier* LINE;
static Identifier* FILE;
static Identifier* MODULE;

View file

@ -166,6 +166,7 @@ immutable Msgtable[] msgtable =
{ "xopCmp", "__xopCmp" },
{ "xtoHash", "__xtoHash" },
{ "__tmpfordtor" },
{ "Entry" },
{ "LINE", "__LINE__" },
{ "FILE", "__FILE__" },

View file

@ -852,6 +852,8 @@ private extern(C++) final class StaticAAVisitor : SemanticTimeTransitiveVisitor
loweredExp = loweredExp.ctfeInterpret();
aaExp.lowering = loweredExp;
semanticTypeInfo(sc, loweredExp.type);
}
// https://issues.dlang.org/show_bug.cgi?id=24602

View file

@ -1348,7 +1348,7 @@ private extern (C++) class TypeInfoDtVisitor : Visitor
override void visit(TypeInfoAssociativeArrayDeclaration d)
{
//printf("TypeInfoAssociativeArrayDeclaration.toDt()\n");
verifyStructSize(Type.typeinfoassociativearray, 4 * target.ptrsize);
verifyStructSize(Type.typeinfoassociativearray, 5 * target.ptrsize);
dtb.xoff(toVtblSymbol(Type.typeinfoassociativearray), 0); // vtbl for TypeInfo_AssociativeArray
if (Type.typeinfoassociativearray.hasMonitor())
@ -1361,6 +1361,9 @@ private extern (C++) class TypeInfoDtVisitor : Visitor
TypeInfo_toObjFile(null, d.loc, tc.index);
dtb.xoff(toSymbol(tc.index.vtinfo), 0); // TypeInfo for array of type
TypeInfo_toObjFile(null, d.loc, d.entry);
dtb.xoff(toSymbol(d.entry.vtinfo), 0); // TypeInfo for key,value-pair
}
override void visit(TypeInfoFunctionDeclaration d)

View file

@ -7023,6 +7023,39 @@ Type sharedWildConstOf(Type type)
return t;
}
Type nakedOf(Type type)
{
//printf("Type::nakedOf() %p, %s\n", type, type.toChars());
if (type.mod == 0)
return type;
if (type.mcache) with(type.mcache)
{
// the cache has the naked type at the "identity" position, try to find it
if (cto && cto.mod == 0)
return cto;
if (ito && ito.mod == 0)
return ito;
if (sto && sto.mod == 0)
return sto;
if (scto && scto.mod == 0)
return scto;
if (wto && wto.mod == 0)
return wto;
if (wcto && wcto.mod == 0)
return wcto;
if (swto && swto.mod == 0)
return swto;
if (swcto && swcto.mod == 0)
return swcto;
}
Type t = type.nullAttributes();
t.mod = 0;
t = t.merge();
t.fixTo(type);
//printf("\t%p %s\n", t, t.toChars());
return t;
}
Type unqualify(Type type, uint m)
{
Type t = type.mutableOf().unSharedOf();

View file

@ -22,6 +22,7 @@ import dmd.expression;
import dmd.globals;
import dmd.location;
import dmd.mtype;
import dmd.typesem;
import core.stdc.stdio;
/****************************************************
@ -67,6 +68,9 @@ bool genTypeInfo(Expression e, Loc loc, Type torig, Scope* sc)
import dmd.typesem : merge2;
Type t = torig.merge2(); // do this since not all Type's are merge'd
if (t.ty == Taarray)
t = makeNakedAssociativeArray(cast(TypeAArray)t);
bool needsCodegen = false;
if (!t.vtinfo)
{
@ -79,7 +83,7 @@ bool genTypeInfo(Expression e, Loc loc, Type torig, Scope* sc)
else if (t.isWild())
t.vtinfo = TypeInfoWildDeclaration.create(t);
else
t.vtinfo = getTypeInfoDeclaration(t);
t.vtinfo = getTypeInfoDeclaration(t, sc);
assert(t.vtinfo);
// ClassInfos are generated as part of ClassDeclaration codegen
@ -115,7 +119,7 @@ extern (C++) Type getTypeInfoType(Loc loc, Type t, Scope* sc)
return t.vtinfo.type;
}
private TypeInfoDeclaration getTypeInfoDeclaration(Type t)
private TypeInfoDeclaration getTypeInfoDeclaration(Type t, Scope* sc)
{
//printf("Type::getTypeInfoDeclaration() %s\n", t.toChars());
switch (t.ty)
@ -127,7 +131,7 @@ private TypeInfoDeclaration getTypeInfoDeclaration(Type t)
case Tsarray:
return TypeInfoStaticArrayDeclaration.create(t);
case Taarray:
return TypeInfoAssociativeArrayDeclaration.create(t);
return getTypeInfoAssocArrayDeclaration(cast(TypeAArray)t, sc);
case Tstruct:
return TypeInfoStructDeclaration.create(t);
case Tvector:
@ -151,6 +155,70 @@ private TypeInfoDeclaration getTypeInfoDeclaration(Type t)
}
}
/******************************************
* Instantiate TypeInfoAssociativeArrayDeclaration and fill
* the entry with TypeInfo_AssociativeArray.Entry!(t.index, t.next)
*
* Params:
* t = TypeAArray to generate TypeInfo_AssociativeArray for
* sc = context
* Returns:
* a TypeInfoAssociativeArrayDeclaration with field entry initialized
*/
TypeInfoDeclaration getTypeInfoAssocArrayDeclaration(TypeAArray t, Scope* sc)
{
import dmd.arraytypes;
import dmd.expressionsem;
import dmd.id;
assert(sc); // must not be called in the code generation phase
auto ti = TypeInfoAssociativeArrayDeclaration.create(t);
t.vtinfo = ti; // assign it early to avoid recursion in expressionSemantic
Loc loc = t.loc;
auto tiargs = new Objects();
tiargs.push(t.index); // always called with naked types
tiargs.push(t.next);
Expression id = new IdentifierExp(loc, Id.empty);
id = new DotIdExp(loc, id, Id.object);
id = new DotIdExp(loc, id, Id.TypeInfo_AssociativeArray);
auto tempinst = new DotTemplateInstanceExp(loc, id, Id.Entry, tiargs);
auto e = expressionSemantic(tempinst, sc);
assert(e.type);
ti.entry = e.type;
if (auto ts = ti.entry.isTypeStruct())
{
ts.sym.requestTypeInfo = true;
if (auto tmpl = ts.sym.isInstantiated())
tmpl.minst = sc._module.importedFrom; // ensure it get's emitted
}
getTypeInfoType(loc, ti.entry, sc);
assert(ti.entry.vtinfo);
return ti;
}
/******************************************
* Find or create a TypeAArray with index and next without
* any head modifiers, tail `inout` is replaced with `const`
*
* Params:
* t = TypeAArray to convert
* Returns:
* t = found type
*/
Type makeNakedAssociativeArray(TypeAArray t)
{
Type tindex = t.index.toBasetype().nakedOf().substWildTo(MODFlags.const_);
Type tnext = t.next.toBasetype().nakedOf().substWildTo(MODFlags.const_);
if (tindex == t.index && tnext == t.next)
return t;
t = new TypeAArray(tnext, tindex);
return t.merge();
}
/**************************************************
* Returns:
* true if any part of type t is speculative.

View file

@ -286,6 +286,27 @@ struct PropertyTable10106
CodepointSet10106[string] table;
}
/************************************************/
// strip inout in key and value types
void testinout()
{
inout(long) func1(inout(long[][int]) aa)
{
return aa[0][0];
}
long[][int] a = [ 0 : [42] ];
long b = func1(a);
assert(b == 42);
}
/************************************************/
// type info generated for enum creation in InExp?
void testinenum()
{
enum string[string] aa = [ "one" : "un", "two" : "deux" ];
assert("one" in aa);
}
/************************************************/
int main()
@ -295,6 +316,8 @@ int main()
test4523();
test3825();
test3825x();
testinout();
testinenum();
printf("Success\n");
return 0;

View file

@ -44,7 +44,7 @@ struct Impl
Bucket[] buckets;
uint used;
uint deleted;
TypeInfo_Struct entryTI;
const(TypeInfo) entryTI;
uint firstUsed;
immutable uint keysz;
immutable uint valsz;

View file

@ -1324,8 +1324,16 @@ class TypeInfo_AssociativeArray : TypeInfo
override @property inout(TypeInfo) next() nothrow pure inout { return value; }
override @property uint flags() nothrow pure const { return 1; }
// TypeInfo entry is generated from the type of this template to help rt/aaA.d
static struct Entry(K, V)
{
K key;
V value;
}
TypeInfo value;
TypeInfo key;
TypeInfo entry;
override @property size_t talign() nothrow pure const
{

View file

@ -66,13 +66,13 @@ private:
if ((ti.key.flags | ti.value.flags) & 1)
flags |= Flags.hasPointers;
entryTI = fakeEntryTI(this, ti.key, ti.value);
entryTI = ti.entry;
}
Bucket[] buckets;
uint used;
uint deleted;
TypeInfo_Struct entryTI;
const(TypeInfo) entryTI;
uint firstUsed;
immutable uint keysz;
immutable uint valsz;
@ -229,15 +229,6 @@ private void* allocEntry(scope const Impl* aa, scope const void* pkey)
return res;
}
package void entryDtor(void* p, const TypeInfo_Struct sti)
{
// key and value type info stored after the TypeInfo_Struct by tiEntry()
auto sizeti = __traits(classInstanceSize, TypeInfo_Struct);
auto extra = cast(const(TypeInfo)*)(cast(void*) sti + sizeti);
extra[0].destroy(p);
extra[1].destroy(p + talign(extra[0].tsize, extra[1].talign));
}
private bool hasDtor(const TypeInfo ti) pure nothrow
{
import rt.lifetime : unqualify;
@ -258,132 +249,6 @@ private immutable(void)* getRTInfo(const TypeInfo ti) pure nothrow
return isNoClass ? ti.rtInfo() : rtinfoHasPointers;
}
// build type info for Entry with additional key and value fields
TypeInfo_Struct fakeEntryTI(ref Impl aa, const TypeInfo keyti, const TypeInfo valti) nothrow
{
import rt.lifetime : unqualify;
auto kti = unqualify(keyti);
auto vti = unqualify(valti);
// figure out whether RTInfo has to be generated (indicated by rtisize > 0)
enum pointersPerWord = 8 * (void*).sizeof * (void*).sizeof;
auto rtinfo = rtinfoNoPointers;
size_t rtisize = 0;
immutable(size_t)* keyinfo = void;
immutable(size_t)* valinfo = void;
if (aa.flags & Impl.Flags.hasPointers)
{
// classes are references
keyinfo = cast(immutable(size_t)*) getRTInfo(keyti);
valinfo = cast(immutable(size_t)*) getRTInfo(valti);
if (keyinfo is rtinfoHasPointers && valinfo is rtinfoHasPointers)
rtinfo = rtinfoHasPointers;
else
rtisize = 1 + (aa.valoff + aa.valsz + pointersPerWord - 1) / pointersPerWord;
}
bool entryHasDtor = hasDtor(kti) || hasDtor(vti);
if (rtisize == 0 && !entryHasDtor)
return null;
// save kti and vti after type info for struct
enum sizeti = __traits(classInstanceSize, TypeInfo_Struct);
void* p = GC.malloc(sizeti + (2 + rtisize) * (void*).sizeof);
import core.stdc.string : memcpy;
memcpy(p, __traits(initSymbol, TypeInfo_Struct).ptr, sizeti);
auto ti = cast(TypeInfo_Struct) p;
auto extra = cast(TypeInfo*)(p + sizeti);
extra[0] = cast() kti;
extra[1] = cast() vti;
static immutable tiMangledName = "S2rt3aaA__T5EntryZ";
ti.mangledName = tiMangledName;
ti.m_RTInfo = rtisize > 0 ? rtinfoEntry(aa, keyinfo, valinfo, cast(size_t*)(extra + 2), rtisize) : rtinfo;
ti.m_flags = ti.m_RTInfo is rtinfoNoPointers ? cast(TypeInfo_Struct.StructFlags)0 : TypeInfo_Struct.StructFlags.hasPointers;
// we don't expect the Entry objects to be used outside of this module, so we have control
// over the non-usage of the callback methods and other entries and can keep these null
// xtoHash, xopEquals, xopCmp, xtoString and xpostblit
immutable entrySize = aa.valoff + aa.valsz;
ti.m_init = (cast(ubyte*) null)[0 .. entrySize]; // init length, but not ptr
if (entryHasDtor)
{
// xdtor needs to be built from the dtors of key and value for the GC
ti.xdtorti = &entryDtor;
ti.m_flags |= TypeInfo_Struct.StructFlags.isDynamicType;
}
ti.m_align = cast(uint) max(kti.talign, vti.talign);
return ti;
}
// build appropriate RTInfo at runtime
immutable(void)* rtinfoEntry(ref Impl aa, immutable(size_t)* keyinfo,
immutable(size_t)* valinfo, size_t* rtinfoData, size_t rtinfoSize) pure nothrow
{
enum bitsPerWord = 8 * size_t.sizeof;
rtinfoData[0] = aa.valoff + aa.valsz;
rtinfoData[1..rtinfoSize] = 0;
void copyKeyInfo(string src)()
{
size_t pos = 1;
size_t keybits = aa.keysz / (void*).sizeof;
while (keybits >= bitsPerWord)
{
rtinfoData[pos] = mixin(src);
keybits -= bitsPerWord;
pos++;
}
if (keybits > 0)
rtinfoData[pos] = mixin(src) & ((size_t(1) << keybits) - 1);
}
if (keyinfo is rtinfoHasPointers)
copyKeyInfo!"~size_t(0)"();
else if (keyinfo !is rtinfoNoPointers)
copyKeyInfo!"keyinfo[pos]"();
void copyValInfo(string src)()
{
size_t bitpos = aa.valoff / (void*).sizeof;
size_t pos = 1;
size_t dstpos = 1 + bitpos / bitsPerWord;
size_t begoff = bitpos % bitsPerWord;
size_t valbits = aa.valsz / (void*).sizeof;
size_t endoff = (bitpos + valbits) % bitsPerWord;
for (;;)
{
const bits = bitsPerWord - begoff;
size_t s = mixin(src);
rtinfoData[dstpos] |= s << begoff;
if (begoff > 0 && valbits > bits)
rtinfoData[dstpos+1] |= s >> bits;
if (valbits < bitsPerWord)
break;
valbits -= bitsPerWord;
dstpos++;
pos++;
}
if (endoff > 0)
rtinfoData[dstpos] &= (size_t(1) << endoff) - 1;
}
if (valinfo is rtinfoHasPointers)
copyValInfo!"~size_t(0)"();
else if (valinfo !is rtinfoNoPointers)
copyValInfo!"valinfo[pos]"();
return cast(immutable(void)*) rtinfoData;
}
unittest
{
void test(K, V)()
@ -402,12 +267,10 @@ unittest
if (valrti is rtinfoNoPointers && keyrti is rtinfoNoPointers)
{
assert(!(impl.flags & Impl.Flags.hasPointers));
assert(impl.entryTI is null);
}
else if (valrti is rtinfoHasPointers && keyrti is rtinfoHasPointers)
{
assert(impl.flags & Impl.Flags.hasPointers);
assert(impl.entryTI is null);
}
else
{
@ -977,7 +840,10 @@ unittest
aa1 = null;
aa2 = null;
aa3 = null;
GC.runFinalizers((cast(char*)&entryDtor)[0 .. 1]);
auto dtor1 = typeid(TypeInfo_AssociativeArray.Entry!(int, T)).xdtor;
GC.runFinalizers((cast(char*)dtor1)[0 .. 1]);
auto dtor2 = typeid(TypeInfo_AssociativeArray.Entry!(T, int)).xdtor;
GC.runFinalizers((cast(char*)dtor2)[0 .. 1]);
assert(T.dtor == 6 && T.postblit == 2);
}

View file

@ -1587,16 +1587,13 @@ deprecated unittest
GC.free(blkinf.base);
// associative arrays
import rt.aaA : entryDtor;
// throw away all existing AA entries with dtor
GC.runFinalizers((cast(char*)&entryDtor)[0..1]);
S1[int] aa1;
aa1[0] = S1(0);
aa1[1] = S1(1);
dtorCount = 0;
aa1 = null;
GC.runFinalizers((cast(char*)&entryDtor)[0..1]);
auto dtor1 = typeid(TypeInfo_AssociativeArray.Entry!(int, S1)).xdtor;
GC.runFinalizers((cast(char*)dtor1)[0..1]);
assert(dtorCount == 2);
int[S1] aa2;
@ -1605,7 +1602,8 @@ deprecated unittest
aa2[S1(2)] = 2;
dtorCount = 0;
aa2 = null;
GC.runFinalizers((cast(char*)&entryDtor)[0..1]);
auto dtor2 = typeid(TypeInfo_AssociativeArray.Entry!(S1, int)).xdtor;
GC.runFinalizers((cast(char*)dtor2)[0..1]);
assert(dtorCount == 3);
S1[2][int] aa3;
@ -1613,7 +1611,8 @@ deprecated unittest
aa3[1] = [S1(1),S1(3)];
dtorCount = 0;
aa3 = null;
GC.runFinalizers((cast(char*)&entryDtor)[0..1]);
auto dtor3 = typeid(TypeInfo_AssociativeArray.Entry!(int, S1[2])).xdtor;
GC.runFinalizers((cast(char*)dtor3)[0..1]);
assert(dtorCount == 4);
}

View file

@ -40,6 +40,7 @@ void main()
testZeroSizedValue();
testTombstonePurging();
testClear();
testTypeInfoCollect();
}
void testKeysValues1()
@ -905,3 +906,44 @@ void testClear()
assert(aa.length == 1);
assert(aa[5] == 6);
}
// https://github.com/dlang/dmd/issues/17503
void testTypeInfoCollect()
{
import core.memory;
static struct S
{
int x;
~this() {}
}
static struct AAHolder
{
S[int] aa;
}
static S* getBadS()
{
auto aaholder = new AAHolder;
aaholder.aa[0] = S();
auto s = 0 in aaholder.aa; // keep a pointer to the entry
GC.free(aaholder); // but not a pointer to the AA.
return s;
}
static void stackStomp()
{
import core.stdc.string : memset;
ubyte[4 * 4096] x;
memset(x.ptr, 0, x.sizeof);
}
auto s = getBadS();
stackStomp(); // destroy any stale references to the AA or s except in the current frame;
GC.collect(); // BUG: this used to invalidate the fake type info, should no longer do this.
foreach(i; 0 .. 1000) // try to reallocate the freed type info
auto p = new void*[1];
s = null; // clear any reference to the entry
GC.collect(); // used to segfault.
}

View file

@ -1,5 +1,5 @@
bytes allocated, allocations, type, function, file:line
256 1 immutable(char)[][int] profilegc.main src/profilegc.d:23
160 1 immutable(char)[][int] profilegc.main src/profilegc.d:23
128 1 float profilegc.main src/profilegc.d:18
128 1 int profilegc.main src/profilegc.d:15
64 1 double[] profilegc.main src/profilegc.d:56

View file

@ -1,5 +1,5 @@
bytes allocated, allocations, type, function, file:line
496 1 immutable(char)[][int] profilegc.main src/profilegc.d:23
320 1 immutable(char)[][int] profilegc.main src/profilegc.d:23
160 1 float profilegc.main src/profilegc.d:18
160 1 int profilegc.main src/profilegc.d:15
64 1 double[] profilegc.main src/profilegc.d:56

View file

@ -1,5 +1,5 @@
bytes allocated, allocations, type, function, file:line
256 1 immutable(char)[][int] profilegc.main src/profilegc.d:23
160 1 immutable(char)[][int] profilegc.main src/profilegc.d:23
128 1 float profilegc.main src/profilegc.d:18
128 1 int profilegc.main src/profilegc.d:15
64 1 double[] profilegc.main src/profilegc.d:56

View file

@ -1,5 +1,5 @@
bytes allocated, allocations, type, function, file:line
496 1 immutable(char)[][int] profilegc.main src/profilegc.d:23
320 1 immutable(char)[][int] profilegc.main src/profilegc.d:23
160 1 float profilegc.main src/profilegc.d:18
160 1 int profilegc.main src/profilegc.d:15
64 1 double[] profilegc.main src/profilegc.d:56

View file

@ -1,5 +1,5 @@
bytes allocated, allocations, type, function, file:line
176 1 immutable(char)[][int] profilegc.main src/profilegc.d:23
160 1 immutable(char)[][int] profilegc.main src/profilegc.d:23
128 1 float profilegc.main src/profilegc.d:18
128 1 int profilegc.main src/profilegc.d:15
64 1 float[] profilegc.main src/profilegc.d:42

View file

@ -1,5 +1,5 @@
bytes allocated, allocations, type, function, file:line
496 1 immutable(char)[][int] profilegc.main src/profilegc.d:23
320 1 immutable(char)[][int] profilegc.main src/profilegc.d:23
160 1 float profilegc.main src/profilegc.d:18
160 1 int profilegc.main src/profilegc.d:15
64 1 double[] profilegc.main src/profilegc.d:56

View file

@ -1,5 +1,5 @@
bytes allocated, allocations, type, function, file:line
256 1 immutable(char)[][int] profilegc.main src\profilegc.d:23
160 1 immutable(char)[][int] profilegc.main src\profilegc.d:23
128 1 float profilegc.main src\profilegc.d:18
128 1 int profilegc.main src\profilegc.d:15
64 1 double[] profilegc.main src\profilegc.d:56

View file

@ -1,5 +1,5 @@
bytes allocated, allocations, type, function, file:line
496 1 immutable(char)[][int] profilegc.main src\profilegc.d:23
320 1 immutable(char)[][int] profilegc.main src\profilegc.d:23
160 1 float profilegc.main src\profilegc.d:18
160 1 int profilegc.main src\profilegc.d:15
64 1 double[] profilegc.main src\profilegc.d:56