diff --git a/src/object.di b/src/object.di index 1daabf71ece..1aabd754ad2 100644 --- a/src/object.di +++ b/src/object.di @@ -411,7 +411,7 @@ extern (C) // alias _dg2_t = extern(D) int delegate(void*, void*); // int _aaApply2(void* aa, size_t keysize, _dg2_t dg); - private struct AARange { void* impl, current; } + private struct AARange { void* impl; size_t idx; } AARange _aaRange(void* aa) pure nothrow @nogc; bool _aaRangeEmpty(AARange r) pure nothrow @nogc; void* _aaRangeFrontKey(AARange r) pure nothrow @nogc; @@ -542,7 +542,9 @@ auto byValue(T : Value[Key], Value, Key)(T *aa) pure nothrow @nogc Key[] keys(T : Value[Key], Value, Key)(T aa) @property { auto a = cast(void[])_aaKeys(cast(inout(void)*)aa, Key.sizeof, typeid(Key[])); - return *cast(Key[]*)&a; + auto res = *cast(Key[]*)&a; + _doPostblit(res); + return res; } Key[] keys(T : Value[Key], Value, Key)(T *aa) @property @@ -553,7 +555,9 @@ Key[] keys(T : Value[Key], Value, Key)(T *aa) @property Value[] values(T : Value[Key], Value, Key)(T aa) @property { auto a = cast(void[])_aaValues(cast(inout(void)*)aa, Key.sizeof, Value.sizeof, typeid(Value[])); - return *cast(Value[]*)&a; + auto res = *cast(Value[]*)&a; + _doPostblit(res); + return res; } Value[] values(T : Value[Key], Value, Key)(T *aa) @property diff --git a/src/object_.d b/src/object_.d index e2007953bfc..0c98d27a1a7 100644 --- a/src/object_.d +++ b/src/object_.d @@ -2003,7 +2003,7 @@ extern (C) // alias _dg2_t = extern(D) int delegate(void*, void*); // int _aaApply2(void* aa, size_t keysize, _dg2_t dg); - private struct AARange { void* impl, current; } + private struct AARange { void* impl; size_t idx; } AARange _aaRange(void* aa) pure nothrow @nogc; bool _aaRangeEmpty(AARange r) pure nothrow @nogc; void* _aaRangeFrontKey(AARange r) pure nothrow @nogc; @@ -2139,7 +2139,9 @@ auto byValue(T : Value[Key], Value, Key)(T *aa) pure nothrow @nogc Key[] keys(T : Value[Key], Value, Key)(T aa) @property { auto a = cast(void[])_aaKeys(cast(inout(void)*)aa, Key.sizeof, typeid(Key[])); - return *cast(Key[]*)&a; + auto res = *cast(Key[]*)&a; + _doPostblit(res); + return res; } Key[] keys(T : Value[Key], Value, Key)(T *aa) @property @@ -2150,7 +2152,9 @@ Key[] keys(T : Value[Key], Value, Key)(T *aa) @property Value[] values(T : Value[Key], Value, Key)(T aa) @property { auto a = cast(void[])_aaValues(cast(inout(void)*)aa, Key.sizeof, Value.sizeof, typeid(Value[])); - return *cast(Value[]*)&a; + auto res = *cast(Value[]*)&a; + _doPostblit(res); + return res; } Value[] values(T : Value[Key], Value, Key)(T *aa) @property @@ -2158,6 +2162,33 @@ Value[] values(T : Value[Key], Value, Key)(T *aa) @property return (*aa).values; } +unittest +{ + static struct T + { + static size_t count; + this(this) { ++count; } + } + T[int] aa; + T t; + aa[0] = t; + aa[1] = t; + assert(T.count == 2); + auto vals = aa.values; + assert(vals.length == 2); + assert(T.count == 4); + + T.count = 0; + int[T] aa2; + aa2[t] = 0; + assert(T.count == 1); + aa2[t] = 1; + assert(T.count == 1); + auto keys = aa2.keys; + assert(keys.length == 1); + assert(T.count == 2); +} + auto byKeyValue(T : Value[Key], Value, Key)(T aa) pure nothrow @nogc @property { static struct Result diff --git a/src/rt/aaA.d b/src/rt/aaA.d index 1a466727ffa..b7bcd0d8b5a 100644 --- a/src/rt/aaA.d +++ b/src/rt/aaA.d @@ -1,86 +1,235 @@ /** * Implementation of associative arrays. * - * Copyright: Copyright Digital Mars 2000 - 2010. + * Copyright: Copyright Digital Mars 2000 - 2015. * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors: Walter Bright, Sean Kelly - */ - -/* Copyright Digital Mars 2000 - 2010. - * Distributed under the Boost Software License, Version 1.0. - * (See accompanying file LICENSE or copy at - * http://www.boost.org/LICENSE_1_0.txt) + * Authors: Martin Nowak */ module rt.aaA; -private +/// AA version for debuggers, bump whenever changing the layout +extern (C) immutable int _aaVersion = 1; + +import core.memory : GC; + +// grow threshold +private enum GROW_NUM = 4; +private enum GROW_DEN = 5; +// shrink threshold +private enum SHRINK_NUM = 1; +private enum SHRINK_DEN = 8; +// grow factor +private enum GROW_FAC = 4; +// growing the AA doubles it's size, so the shrink threshold must be +// smaller than half the grow threshold to have a hysteresis +static assert(GROW_FAC * SHRINK_NUM * GROW_DEN < GROW_NUM * SHRINK_DEN); +// initial load factor (for literals), mean of both thresholds +private enum INIT_NUM = (GROW_DEN * SHRINK_NUM + GROW_NUM * SHRINK_DEN) / 2; +private enum INIT_DEN = SHRINK_DEN * GROW_DEN; + +private enum INIT_NUM_BUCKETS = 8; +// magic hash constants to distinguish empty, deleted, and filled buckets +private enum HASH_EMPTY = 0; +private enum HASH_DELETED = 0x1; +private enum HASH_FILLED_MARK = size_t(1) << 8 * size_t.sizeof - 1; + +/// Opaque AA wrapper +struct AA { - import core.stdc.stdarg; - import core.stdc.string; - import core.stdc.stdio; - import core.memory; - import rt.lifetime : _d_newarrayU, _d_newitemT, unqualify, __doPostblit; + Impl* impl; + alias impl this; - // Convenience function to make sure the NO_INTERIOR gets set on the - // bucket array. - Entry*[] newBuckets(in size_t len) @trusted pure nothrow + private @property bool empty() const pure nothrow @nogc { - auto ptr = cast(Entry**) GC.calloc( - len * (Entry*).sizeof, GC.BlkAttr.NO_INTERIOR); - return ptr[0..len]; + return impl is null || !impl.length; } } -// Auto-rehash and pre-allocate - Dave Fladebo - -static immutable size_t[] prime_list = [ - 31UL, - 97UL, 389UL, - 1_543UL, 6_151UL, - 24_593UL, 98_317UL, - 393_241UL, 1_572_869UL, - 6_291_469UL, 25_165_843UL, - 100_663_319UL, 402_653_189UL, - 1_610_612_741UL, 4_294_967_291UL, -// 8_589_934_513UL, 17_179_869_143UL -]; - -/* This is the type of the return value for dynamic arrays. - * It should be a type that is returned in registers. - * Although DMD will return types of Array in registers, - * gcc will not, so we instead use a 'long'. - */ -alias void[] ArrayRet_t; - -struct Array +private struct Impl { - size_t length; - void* ptr; +private: + this(in TypeInfo_AssociativeArray ti, size_t sz = INIT_NUM_BUCKETS) + { + keysz = cast(uint) ti.key.tsize; + valsz = cast(uint) ti.value.tsize; + buckets = allocBuckets(sz); + firstUsed = cast(uint) buckets.length; + entryTI = fakeEntryTI(ti.key, ti.value); + immutable pad = talign(keysz, ti.value.talign) - keysz; + if (pad >= ushort.max) + assert(0, "value alignment too big"); + keypad = cast(ushort) pad; + + import rt.lifetime : hasPostblit, unqualify; + + if (hasPostblit(unqualify(ti.key))) + flags |= Flags.keyHasPostblit; + if ((ti.key.flags | ti.value.flags) & 1) + flags |= Flags.hasPointers; + } + + Bucket[] buckets; + uint used; + uint deleted; + TypeInfo_Struct entryTI; + uint firstUsed; + immutable uint keysz; + immutable uint valsz; + ushort keypad; + Flags flags; + + enum Flags : ubyte + { + none = 0x0, + keyHasPostblit = 0x1, + hasPointers = 0x2, + } + + @property size_t length() const pure nothrow @nogc + { + assert(used >= deleted); + return used - deleted; + } + + @property size_t dim() const pure nothrow @nogc + { + return buckets.length; + } + + @property size_t mask() const pure nothrow @nogc + { + return dim - 1; + } + + // find the first slot to insert a value with hash + inout(Bucket)* findSlotInsert(size_t hash) inout pure nothrow @nogc + { + for (size_t i = hash & mask, j = 1;; ++j) + { + if (buckets[i].empty || buckets[i].deleted) + return &buckets[i]; + i = (i + j) & mask; + } + } + + // lookup a key + inout(Bucket)* findSlotLookup(size_t hash, in void* pkey, in TypeInfo keyti) inout + { + for (size_t i = hash & mask, j = 1;; ++j) + { + if (buckets[i].hash == hash && keyti.equals(pkey, buckets[i].entry)) + return &buckets[i]; + else if (buckets[i].empty) + return null; + i = (i + j) & mask; + } + } + + void grow(in TypeInfo keyti) + { + // If there are so many deleted entries, that growing would push us + // below the shrink threshold, we just purge deleted entries instead. + if (length * SHRINK_DEN < GROW_FAC * dim * SHRINK_NUM) + resize(dim); + else + resize(GROW_FAC * dim); + } + + void shrink(in TypeInfo keyti) + { + if (dim > INIT_NUM_BUCKETS) + resize(dim / GROW_FAC); + } + + void resize(size_t ndim) pure nothrow + { + auto obuckets = buckets; + buckets = allocBuckets(ndim); + + foreach (ref b; obuckets) + if (b.filled) + *findSlotInsert(b.hash) = b; + + firstUsed = 0; + used -= deleted; + deleted = 0; + GC.free(obuckets.ptr); // safe to free b/c impossible to reference + } } -struct Entry +//============================================================================== +// Bucket +//------------------------------------------------------------------------------ + +private struct Bucket { - Entry *next; +private pure nothrow @nogc: size_t hash; - /* key */ - /* value */ + void* entry; - static void Dtor(void*p, const TypeInfo_Struct sti) + @property bool empty() const { - // 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 + Entry.sizeof); - extra[1].destroy(p + Entry.sizeof + aligntsize(extra[0].tsize)); + return hash == HASH_EMPTY; } + + @property bool deleted() const + { + return hash == HASH_DELETED; + } + + @property bool filled() const + { + return !!(hash & HASH_FILLED_MARK); + } +} + +Bucket[] allocBuckets(size_t dim) @trusted pure nothrow +{ + enum attr = GC.BlkAttr.NO_INTERIOR; + immutable sz = dim * Bucket.sizeof; + return (cast(Bucket*) GC.calloc(sz, attr))[0 .. dim]; +} + +//============================================================================== +// Entry +//------------------------------------------------------------------------------ + +private void* allocEntry(in Impl* aa, in void* pkey) +{ + import rt.lifetime : _d_newitemU; + + immutable akeysz = aa.keysz + aa.keypad; + void* res = void; + if (aa.entryTI) + res = _d_newitemU(aa.entryTI); + else + { + auto flags = (aa.flags & Impl.Flags.hasPointers) ? 0 : GC.BlkAttr.NO_SCAN; + res = GC.malloc(akeysz + aa.valsz, flags); + } + + res[0 .. aa.keysz] = pkey[0 .. aa.keysz]; // copy key + import core.stdc.string : memset; // explicitly use memset (Issue 14458) + memset(res + akeysz, 0, aa.valsz); // zero value + + 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) { - if (!ti) - return false; + import rt.lifetime : unqualify; + if (typeid(ti) is typeid(TypeInfo_Struct)) - if ((cast(TypeInfo_Struct)cast(void*)ti).xdtor) + if ((cast(TypeInfo_Struct) cast(void*) ti).xdtor) return true; if (typeid(ti) is typeid(TypeInfo_StaticArray)) return hasDtor(unqualify(ti.next())); @@ -89,21 +238,24 @@ private bool hasDtor(const TypeInfo ti) } // build type info for Entry with additional key and value fields -TypeInfo_Struct tiEntry(const TypeInfo keyti, const TypeInfo valti) +TypeInfo_Struct fakeEntryTI(const TypeInfo keyti, const TypeInfo valti) { + import rt.lifetime : unqualify; + auto kti = unqualify(keyti); auto vti = unqualify(valti); if (!hasDtor(kti) && !hasDtor(vti)) return null; // save kti and vti after type info for struct - auto sizeti = __traits(classInstanceSize, TypeInfo_Struct); - auto sizeall = sizeti + 2 * (void*).sizeof; - void* p = GC.malloc(sizeall); + enum sizeti = __traits(classInstanceSize, TypeInfo_Struct); + void* p = GC.malloc(sizeti + 2 * (void*).sizeof); + import core.stdc.string : memcpy; + memcpy(p, typeid(TypeInfo_Struct).init().ptr, sizeti); - auto ti = cast(TypeInfo_Struct)p; - auto extra = cast(TypeInfo*) (p + sizeti); + auto ti = cast(TypeInfo_Struct) p; + auto extra = cast(TypeInfo*)(p + sizeti); extra[0] = cast() kti; extra[1] = cast() vti; @@ -114,442 +266,412 @@ TypeInfo_Struct tiEntry(const TypeInfo keyti, const TypeInfo valti) // over the non-usage of the callback methods and other entries and can keep these null // xtoHash, xopEquals, xopCmp, xtoString and xpostblit ti.m_RTInfo = null; - auto sizeEntry = Entry.sizeof + aligntsize(kti.tsize()) + valti.tsize(); - ti.m_init = (cast(char*)null)[0..sizeEntry]; // init length, but not ptr + immutable entrySize = talign(kti.tsize, vti.talign) + vti.tsize; + ti.m_init = (cast(ubyte*) null)[0 .. entrySize]; // init length, but not ptr // xdtor needs to be built from the dtors of key and value for the GC - ti.xdtorti = &Entry.Dtor; + ti.xdtorti = &entryDtor; - ti.m_flags = TypeInfo_Struct.StructFlags.hasPointers | TypeInfo_Struct.StructFlags.isDynamicType; - ti.m_align = cast(uint) aligntsize(1); + ti.m_flags = TypeInfo_Struct.StructFlags.isDynamicType; + ti.m_flags |= (keyti.flags | valti.flags) & TypeInfo_Struct.StructFlags.hasPointers; + ti.m_align = cast(uint) max(kti.talign, vti.talign); return ti; } -struct Impl -{ - this(const TypeInfo_AssociativeArray ti) - { - _keyti = cast() ti.key; - _entryti = cast() tiEntry(_keyti, ti.next); - } +//============================================================================== +// Helper functions +//------------------------------------------------------------------------------ - Entry*[] buckets; - size_t nodes; // total number of entries - size_t firstUsedBucket; // starting index for first used bucket. - TypeInfo _keyti; // these should be const, but missing tail const for classes make this ugly - TypeInfo_Struct _entryti; +private size_t talign(size_t tsize, size_t algn) @safe pure nothrow @nogc +{ + immutable mask = algn - 1; + assert(!(mask & algn)); + return (tsize + mask) & ~mask; +} - Entry*[4] binit; // initial value of buckets[] +// mix hash to "fix" bad hash functions +private size_t mix(size_t h) @safe pure nothrow @nogc +{ + // final mix function of MurmurHash2 + enum m = 0x5bd1e995; + h ^= h >> 13; + h *= m; + h ^= h >> 15; + return h; +} - @property const(TypeInfo) keyti() const @safe pure nothrow @nogc - { return _keyti; } +private size_t calcHash(in void* pkey, in TypeInfo keyti) +{ + immutable hash = keyti.getHash(pkey); + // highest bit is set to distinguish empty/deleted from filled buckets + return mix(hash) | HASH_FILLED_MARK; +} - // helper function to determine first used bucket, and update implementation's cache for it - // NOTE: will not work with immutable AA in ROM, but that doesn't exist yet. - size_t firstUsedBucketCache() @safe pure nothrow @nogc - in - { - assert(firstUsedBucket <= buckets.length); - foreach(i; 0 .. firstUsedBucket) - assert(buckets[i] is null); - } - body - { - size_t i; - for(i = firstUsedBucket; i < buckets.length; ++i) - if(buckets[i] !is null) - break; - return firstUsedBucket = i; - } +private size_t nextpow2(size_t val) pure nothrow @nogc +{ + size_t res = 1; + while (res < val) + res <<= 1; + return res; } -/* This is the type actually seen by the programmer, although - * it is completely opaque. - */ -struct AA +private T min(T)(T a, T b) pure nothrow @nogc { - Impl* impl; + return a < b ? a : b; } -/********************************** - * Align to next pointer boundary, so that - * GC won't be faced with misaligned pointers - * in value. - */ -size_t aligntsize(in size_t tsize) @safe pure nothrow @nogc +private T max(T)(T a, T b) pure nothrow @nogc { - version (D_LP64) { - // align to 16 bytes on 64-bit - return (tsize + 15) & ~(15); - } - else { - return (tsize + size_t.sizeof - 1) & ~(size_t.sizeof - 1); - } + return b < a ? a : b; } -extern (C): +//============================================================================== +// API Implementation +//------------------------------------------------------------------------------ -/**************************************************** - * Determine number of entries in associative array. - */ -size_t _aaLen(in AA aa) pure nothrow @nogc -in +/// Determine number of entries in associative array. +extern (C) size_t _aaLen(in AA aa) pure nothrow @nogc { - //printf("_aaLen()+\n"); - //_aaInv(aa); + return aa ? aa.length : 0; } -out (result) + +/// Get LValue for key +extern (C) void* _aaGetY(AA* aa, const TypeInfo_AssociativeArray ti, in size_t valsz, + in void* pkey) { - size_t len = 0; + // lazily alloc implementation + if (aa.impl is null) + aa.impl = new Impl(ti); + + // get hash and bucket for key + immutable hash = calcHash(pkey, ti.key); - if (aa.impl) + // found a value => return it + if (auto p = aa.findSlotLookup(hash, pkey, ti.key)) + return p.entry + aa.keysz + aa.keypad; + + auto p = aa.findSlotInsert(hash); + if (p.deleted) + --aa.deleted; + // check load factor and possibly grow + else if (++aa.used * GROW_DEN > aa.dim * GROW_NUM) { - foreach (const(Entry)* e; aa.impl.buckets) - { - while (e) - { - len++; - e = e.next; - } - } + aa.grow(ti.key); + p = aa.findSlotInsert(hash); + assert(p.empty); } - assert(len == result); - //printf("_aaLen()-\n"); + // update search cache and allocate entry + aa.firstUsed = min(aa.firstUsed, cast(uint)(p - aa.buckets.ptr)); + p.hash = hash; + p.entry = allocEntry(aa.impl, pkey); + // postblit for key + if (aa.flags & Impl.Flags.keyHasPostblit) + { + import rt.lifetime : __doPostblit, unqualify; + + __doPostblit(p.entry, aa.keysz, unqualify(ti.key)); + } + // return pointer to value + return p.entry + aa.keysz + aa.keypad; } -body + +/// Get RValue for key, returns null if not present +extern (C) inout(void)* _aaGetRvalueX(inout AA aa, in TypeInfo keyti, in size_t valsz, + in void* pkey) { - return aa.impl ? aa.impl.nodes : 0; + return _aaInX(aa, keyti, pkey); } +/// Return pointer to value if present, null otherwise +extern (C) inout(void)* _aaInX(inout AA aa, in TypeInfo keyti, in void* pkey) +{ + if (aa.empty) + return null; -/************************************************* - * Get pointer to value in associative array indexed by key. - * Add entry for key if it is not already there. - */ -void* _aaGetY(AA* aa, const TypeInfo_AssociativeArray ti, in size_t valuesize, in void* pkey) + immutable hash = calcHash(pkey, keyti); + if (auto p = aa.findSlotLookup(hash, pkey, keyti)) + return p.entry + aa.keysz + aa.keypad; + return null; +} + +/// Delete entry in AA, return true if it was present +extern (C) bool _aaDelX(AA aa, in TypeInfo keyti, in void* pkey) { - if (aa.impl is null) + if (aa.empty) + return false; + + immutable hash = calcHash(pkey, keyti); + if (auto p = aa.findSlotLookup(hash, pkey, keyti)) { - aa.impl = new Impl(ti); - aa.impl.buckets = aa.impl.binit[]; - aa.impl.firstUsedBucket = aa.impl.buckets.length; + // clear entry + p.hash = HASH_DELETED; + p.entry = null; + + ++aa.deleted; + if (aa.length * SHRINK_DEN < aa.dim * SHRINK_NUM) + aa.shrink(keyti); + + return true; } - return _aaGetImpl(aa, ti.key, valuesize, pkey); + return false; } -void* _aaGetImpl(AA* aa, const TypeInfo keyti, in size_t valuesize, in void* pkey) -out (result) +/// Rehash AA +extern (C) void* _aaRehash(AA* paa, in TypeInfo keyti) pure nothrow { - assert(result); - assert(aa.impl !is null); - assert(aa.impl.buckets.length); - //assert(_aaInAh(*aa.a, key)); + if (!paa.empty) + paa.resize(nextpow2(INIT_DEN * paa.length / INIT_NUM)); + return *paa; } -body -{ - //printf("keyti = %p\n", keyti); - //printf("aa = %p\n", aa); - //printf("aa = %p\n", aa); - //printf("aa.a = %p\n", aa.a); +/// Return a GC allocated array of all values +extern (C) inout(void[]) _aaValues(inout AA aa, in size_t keysz, in size_t valsz, + const TypeInfo tiValueArray) pure nothrow +{ + if (aa.empty) + return null; - immutable keytitsize = keyti.tsize; + import rt.lifetime : _d_newarrayU; - immutable key_hash = keyti.getHash(pkey); - immutable i = key_hash % aa.impl.buckets.length; - //printf("hash = %d\n", key_hash); + auto res = _d_newarrayU(tiValueArray, aa.length).ptr; + auto pval = res; - Entry** pe = &aa.impl.buckets[i]; - Entry* e; - while ((e = *pe) !is null) + immutable off = aa.keysz + aa.keypad; + foreach (b; aa.buckets[aa.firstUsed .. $]) { - if (key_hash == e.hash) - { - if (keyti.equals(pkey, e + 1)) - goto Lret; - } - pe = &e.next; + if (!b.filled) + continue; + pval[0 .. valsz] = b.entry[off .. valsz + off]; + pval += valsz; } + // postblit is done in object.values + return (cast(inout(void)*) res)[0 .. aa.length]; // fake length, return number of elements +} +/// Return a GC allocated array of all keys +extern (C) inout(void[]) _aaKeys(inout AA aa, in size_t keysz, const TypeInfo tiKeyArray) pure nothrow +{ + if (aa.empty) + return null; + + import rt.lifetime : _d_newarrayU; + + auto res = _d_newarrayU(tiKeyArray, aa.length).ptr; + auto pkey = res; + + foreach (b; aa.buckets[aa.firstUsed .. $]) { - // Not found, create new elem - //printf("create new one\n"); - size_t size = Entry.sizeof + aligntsize(keytitsize) + valuesize; - if (aa.impl._entryti) - e = cast(Entry *) _d_newitemT(aa.impl._entryti); - else - e = cast(Entry *) GC.malloc(size, 0); // TODO: needs typeid(Entry+) - e.next = null; - e.hash = key_hash; - ubyte* ptail = cast(ubyte*)(e + 1); - memcpy(ptail, pkey, keytitsize); - __doPostblit(ptail, keytitsize, unqualify(keyti)); - memset(ptail + aligntsize(keytitsize), 0, valuesize); // zero value - *pe = e; - - auto nodes = ++aa.impl.nodes; - //printf("length = %d, nodes = %d\n", aa.a.buckets.length, nodes); - - // update cache if necessary - if (i < aa.impl.firstUsedBucket) - aa.impl.firstUsedBucket = i; - if (nodes > aa.impl.buckets.length * 4) - { - //printf("rehash\n"); - _aaRehash(aa,keyti); - } + if (!b.filled) + continue; + pkey[0 .. keysz] = b.entry[0 .. keysz]; + pkey += keysz; } - -Lret: - return cast(void*)(e + 1) + aligntsize(keytitsize); + // postblit is done in object.keys + return (cast(inout(void)*) res)[0 .. aa.length]; // fake length, return number of elements } +// opApply callbacks are extern(D) +extern (D) alias dg_t = int delegate(void*); +extern (D) alias dg2_t = int delegate(void*, void*); -/// Same as above but with a function pointer to aaLiteral!(Key, Value) for creating a typed AA instance. -void* _aaGetZ(AA* aa, const TypeInfo keyti, in size_t valuesize, in void* pkey, - void *function(void[], void[]) @trusted pure aaLiteral) +/// foreach opApply over all values +extern (C) int _aaApply(AA aa, in size_t keysz, dg_t dg) { - assert(false, "_aaGetZ not implemented"); - //return _aaGetX(aa, keyti, valuesize, pkey); + if (aa.empty) + return 0; + + immutable off = aa.keysz + aa.keypad; + foreach (b; aa.buckets) + { + if (!b.filled) + continue; + if (auto res = dg(b.entry + off)) + return res; + } + return 0; } -// bug 13748 -pure nothrow unittest +/// foreach opApply over all key/value pairs +extern (C) int _aaApply2(AA aa, in size_t keysz, dg2_t dg) { - int[int] aa; - // make all values go into the last bucket (int hash is simply the int) - foreach(i; 0..16) + if (aa.empty) + return 0; + + immutable off = aa.keysz + aa.keypad; + foreach (b; aa.buckets) { - aa[3 + i * 4] = 1; - assert(aa.keys.length == i+1); + if (!b.filled) + continue; + if (auto res = dg(b.entry, b.entry + off)) + return res; } - - // now force a rehash, but with a different value - aa[0] = 1; - assert(aa.keys.length == 17); + return 0; } - -/************************************************* - * Get pointer to value in associative array indexed by key. - * Returns null if it is not already there. - */ -inout(void)* _aaGetRvalueX(inout AA aa, in TypeInfo keyti, in size_t valuesize, in void* pkey) +/// Construct an associative array of type ti from keys and value +extern (C) Impl* _d_assocarrayliteralTX(const TypeInfo_AssociativeArray ti, void[] keys, + void[] vals) { - return _aaInX(aa, keyti, pkey); -} + assert(keys.length == vals.length); + immutable keysz = ti.key.tsize; + immutable valsz = ti.value.tsize; + immutable length = keys.length; -/************************************************* - * Determine if key is in aa. - * Returns: - * null not in aa - * !=null in aa, return pointer to value - */ -inout(void)* _aaInX(inout AA aa, in TypeInfo keyti, in void* pkey) -in -{ -} -out (result) -{ - //assert(result == 0 || result == 1); -} -body -{ - if (aa.impl is null) + if (!length) return null; - //printf("_aaIn(), .length = %d, .ptr = %x\n", aa.a.length, cast(uint)aa.a.ptr); - if (immutable len = aa.impl.buckets.length) + auto aa = new Impl(ti, nextpow2(INIT_DEN * length / INIT_NUM)); + + void* pkey = keys.ptr; + void* pval = vals.ptr; + immutable off = aa.keysz + aa.keypad; + foreach (_; 0 .. length) { - immutable key_hash = keyti.getHash(pkey); - immutable i = key_hash % len; - //printf("hash = %d\n", key_hash); + immutable hash = calcHash(pkey, ti.key); - inout(Entry)* e = aa.impl.buckets[i]; - while (e !is null) + auto p = aa.findSlotLookup(hash, pkey, ti.key); + if (p is null) { - if (key_hash == e.hash) - { - if (keyti.equals(pkey, e + 1)) - return cast(inout void*)(e + 1) + aligntsize(keyti.tsize); - } - e = e.next; + p = aa.findSlotInsert(hash); + p.hash = hash; + p.entry = allocEntry(aa, pkey); // move key, no postblit + aa.firstUsed = min(aa.firstUsed, cast(uint)(p - aa.buckets.ptr)); } - } + else if (aa.entryTI && hasDtor(ti.value)) + { + // destroy existing value before overwriting it + ti.value.destroy(p.entry + off); + } + // set hash and blit value + auto pdst = p.entry + off; + pdst[0 .. valsz] = pval[0 .. valsz]; // move value, no postblit - // Not found - return null; + pkey += keysz; + pval += valsz; + } + aa.used = cast(uint) length; + return aa; } -/************************************************* - * Delete key entry in aa[]. - * If key is not in aa[], do nothing. - */ -bool _aaDelX(AA aa, in TypeInfo keyti, in void* pkey) +/// compares 2 AAs for equality +extern (C) int _aaEqual(in TypeInfo tiRaw, in AA aa1, in AA aa2) { - if (!aa.impl || !aa.impl.buckets.length) + if (aa1.impl is aa2.impl) + return true; + + immutable len = _aaLen(aa1); + if (len != _aaLen(aa2)) return false; - auto key_hash = keyti.getHash(pkey); - //printf("hash = %d\n", key_hash); - immutable size_t i = key_hash % aa.impl.buckets.length; - auto pe = &aa.impl.buckets[i]; - for (Entry *e = void; (e = *pe) !is null; pe = &e.next) + + if (!len) // both empty + return true; + + import rt.lifetime : unqualify; + + auto uti = unqualify(tiRaw); + auto ti = *cast(TypeInfo_AssociativeArray*)&uti; + // compare the entries + immutable off = aa1.keysz + aa1.keypad; + foreach (b1; aa1.buckets) { - if (key_hash != e.hash || !keyti.equals(pkey, e + 1)) + if (!b1.filled) continue; - *pe = e.next; - if (!(--aa.impl.nodes)) - // reset cache, we know there are no nodes in the aa. - aa.impl.firstUsedBucket = aa.impl.buckets.length; - // ee could be freed here, but user code may - // hold pointers to it - return true; + auto pb2 = aa2.findSlotLookup(b1.hash, b1.entry, ti.key); + if (pb2 is null || !ti.value.equals(b1.entry + off, pb2.entry + off)) + return false; } - return false; + return true; } - -/******************************************** - * Produce array of values from aa. - */ -inout(ArrayRet_t) _aaValues(inout AA aa, in size_t keysize, in size_t valuesize, const TypeInfo tiValueArray) pure nothrow +/// compute a hash +extern (C) hash_t _aaGetHash(in AA* aa, in TypeInfo tiRaw) nothrow { - size_t resi; - Array a; + if (aa.empty) + return 0; + + import rt.lifetime : unqualify; - auto alignsize = aligntsize(keysize); + auto uti = unqualify(tiRaw); + auto ti = *cast(TypeInfo_AssociativeArray*)&uti; + immutable off = aa.keysz + aa.keypad; + auto valHash = &ti.value.getHash; - if (aa.impl !is null) + size_t h; + foreach (b; aa.buckets) { - a.length = _aaLen(aa); - a.ptr = cast(byte*) _d_newarrayU(tiValueArray, a.length).ptr; - resi = 0; - foreach (inout(Entry)* e; aa.impl.buckets[aa.impl.firstUsedBucket..$]) - { - while (e) - { - memcpy(a.ptr + resi * valuesize, - cast(byte*)e + Entry.sizeof + alignsize, - valuesize); - resi++; - e = e.next; - } - } - assert(resi == a.length); - // cannot postblit, it might not be pure - //__doPostblit(a.ptr, alignsize, unqualify(tiValueArray.next)); + if (!b.filled) + continue; + size_t[2] h2 = [b.hash, valHash(b.entry + off)]; + // use XOR here, so that hash is independent of element order + h ^= hashOf(h2.ptr, h2.length * h2[0].sizeof); } - return *cast(inout ArrayRet_t*)(&a); + return h; } - -/******************************************** - * Rehash an array. +/** + * _aaRange implements a ForwardRange */ -void* _aaRehash(AA* paa, in TypeInfo keyti) pure nothrow -in -{ - //_aaInvAh(paa); -} -out (result) +struct Range { - //_aaInvAh(result); + Impl* impl; + size_t idx; + alias impl this; } -body + +extern (C) pure nothrow @nogc { - //printf("Rehash\n"); - if (paa.impl !is null) + Range _aaRange(AA aa) { - auto len = _aaLen(*paa); - if (len) - { - Impl newImpl; - Impl* oldImpl = paa.impl; - - size_t i; - for (i = 0; i < prime_list.length - 1; i++) - { - if (len <= prime_list[i]) - break; - } - len = prime_list[i]; - newImpl.buckets = newBuckets(len); - newImpl.firstUsedBucket = newImpl.buckets.length; - - foreach (e; oldImpl.buckets[oldImpl.firstUsedBucket..$]) - { - while (e) - { - auto enext = e.next; - const j = e.hash % len; - e.next = newImpl.buckets[j]; - newImpl.buckets[j] = e; - e = enext; - if(j < newImpl.firstUsedBucket) - newImpl.firstUsedBucket = j; - } - } - if (oldImpl.buckets.ptr == oldImpl.binit.ptr) - oldImpl.binit[] = null; - else - GC.free(oldImpl.buckets.ptr); - - newImpl.nodes = oldImpl.nodes; - newImpl._keyti = oldImpl._keyti; - newImpl._entryti = oldImpl._entryti; - - *paa.impl = newImpl; - } - else + if (!aa) + return Range(); + + foreach (i; aa.firstUsed .. aa.dim) { - if (paa.impl.buckets.ptr != paa.impl.binit.ptr) - GC.free(paa.impl.buckets.ptr); - paa.impl.buckets = paa.impl.binit[]; - paa.impl.firstUsedBucket = paa.impl.buckets.length; // start out with the cache at the end + if (aa.buckets[i].filled) + return Range(aa.impl, i); } + return Range(aa, aa.dim); } - return paa.impl; -} -/******************************************** - * Produce array of N byte keys from aa. - */ -inout(ArrayRet_t) _aaKeys(inout AA aa, in size_t keysize, const TypeInfo tiKeyArray) pure nothrow -{ - auto len = _aaLen(aa); - if (!len) - return null; + bool _aaRangeEmpty(Range r) + { + return r.impl is null || r.idx == r.dim; + } + + void* _aaRangeFrontKey(Range r) + { + return r.buckets[r.idx].entry; + } - void* res = _d_newarrayU(tiKeyArray, len).ptr; + void* _aaRangeFrontValue(Range r) + { + return r.buckets[r.idx].entry + r.keysz + r.keypad; + } - size_t resi = 0; - // note, can't use firstUsedBucketCache here, aa is inout - foreach (inout(Entry)* e; aa.impl.buckets[aa.impl.firstUsedBucket..$]) + void _aaRangePopFront(ref Range r) { - while (e) + for (++r.idx; r.idx < r.dim; ++r.idx) { - memcpy(&res[resi * keysize], cast(byte*)(e + 1), keysize); - resi++; - e = e.next; + if (r.buckets[r.idx].filled) + break; } } - assert(resi == len); - // cannot postblit, it might not be pure - //__doPostblit(res, len * kisize, unqualify(tiKeyArray.next)); - - Array a; - a.length = len; - a.ptr = res; - return *cast(inout ArrayRet_t*)(&a); } +//============================================================================== +// Unittests +//------------------------------------------------------------------------------ + pure nothrow unittest { int[string] aa; + assert(aa.keys.length == 0); + assert(aa.values.length == 0); + aa["hello"] = 3; assert(aa["hello"] == 3); aa["hello"]++; @@ -559,7 +681,7 @@ pure nothrow unittest string[] keys = aa.keys; assert(keys.length == 1); - assert(memcmp(keys[0].ptr, cast(char*)"hello", 5) == 0); + assert(keys[0] == "hello"); int[] values = aa.values; assert(values.length == 1); @@ -576,350 +698,37 @@ pure nothrow unittest assert(aa.keys.length == 4); assert(aa.values.length == 4); - foreach(a; aa.keys) + foreach (a; aa.keys) { assert(a.length != 0); assert(a.ptr != null); - //printf("key: %.*s -> value: %d\n", a.length, a.ptr, aa[a]); } - foreach(v; aa.values) + foreach (v; aa.values) { assert(v != 0); - //printf("value: %d\n", v); } } -unittest // Test for Issue 10381 +unittest // Test for Issue 10381 { alias II = int[int]; - II aa1 = [0: 1]; - II aa2 = [0: 1]; - II aa3 = [0: 2]; + II aa1 = [0 : 1]; + II aa2 = [0 : 1]; + II aa3 = [0 : 2]; assert(aa1 == aa2); // Passes - assert( typeid(II).equals(&aa1, &aa2)); + assert(typeid(II).equals(&aa1, &aa2)); assert(!typeid(II).equals(&aa1, &aa3)); } - -/********************************************** - * 'apply' for associative arrays - to support foreach - */ -// dg is D, but _aaApply() is C -extern (D) alias int delegate(void *) dg_t; - -int _aaApply(AA aa, in size_t keysize, dg_t dg) -{ - if (aa.impl is null) - { - return 0; - } - - immutable alignsize = aligntsize(keysize); - //printf("_aaApply(aa = x%llx, keysize = %d, dg = x%llx)\n", aa.impl, keysize, dg); - - foreach (e; aa.impl.buckets[aa.impl.firstUsedBucketCache .. $]) - { - while (e) - { - auto result = dg(cast(void *)(e + 1) + alignsize); - if (result) - return result; - e = e.next; - } - } - return 0; -} - -// dg is D, but _aaApply2() is C -extern (D) alias int delegate(void *, void *) dg2_t; - -int _aaApply2(AA aa, in size_t keysize, dg2_t dg) -{ - if (aa.impl is null) - { - return 0; - } - - //printf("_aaApply(aa = x%llx, keysize = %d, dg = x%llx)\n", aa.impl, keysize, dg); - - immutable alignsize = aligntsize(keysize); - - foreach (e; aa.impl.buckets[aa.impl.firstUsedBucketCache..$]) - { - while (e) - { - auto result = dg(e + 1, cast(void *)(e + 1) + alignsize); - if (result) - return result; - e = e.next; - } - } - - return 0; -} - - -/*********************************** - * Construct an associative array of type ti from - * length pairs of key/value pairs. - */ -Impl* _d_assocarrayliteralTX(const TypeInfo_AssociativeArray ti, void[] keys, void[] values) -{ - const valuesize = ti.next.tsize; // value size - const keyti = ti.key; - const keysize = keyti.tsize; // key size - const length = keys.length; - Impl* result; - - //printf("_d_assocarrayliteralT(keysize = %d, valuesize = %d, length = %d)\n", keysize, valuesize, length); - //printf("tivalue = %.*s\n", typeid(ti.next).name); - assert(length == values.length); - if (length == 0 || valuesize == 0 || keysize == 0) - { - } - else - { - result = new Impl(ti); - - size_t i; - for (i = 0; i < prime_list.length - 1; i++) - { - if (length <= prime_list[i]) - break; - } - auto len = prime_list[i]; - result.buckets = newBuckets(len); - result.firstUsedBucket = result.buckets.length; - - size_t keytsize = aligntsize(keysize); - - for (size_t j = 0; j < length; j++) - { - auto pkey = keys.ptr + j * keysize; - auto pvalue = values.ptr + j * valuesize; - Entry* e; - - auto key_hash = keyti.getHash(pkey); - //printf("hash = %d\n", key_hash); - i = key_hash % len; - if (i < result.firstUsedBucket) result.firstUsedBucket = i; - auto pe = &result.buckets[i]; - while (1) - { - e = *pe; - if (!e) - { - // Not found, create new elem - //printf("create new one\n"); - if (result._entryti) - e = cast(Entry *) _d_newitemT(result._entryti); - else - e = cast(Entry *) GC.malloc(Entry.sizeof + keytsize + valuesize); // TODO: needs typeid(Entry+) - memcpy(e + 1, pkey, keysize); - e.next = null; - e.hash = key_hash; - *pe = e; - result.nodes++; - break; - } - if (key_hash == e.hash) - { - if (keyti.equals(pkey, e + 1)) - break; - } - pe = &e.next; - } - memcpy(cast(void *)(e + 1) + keytsize, pvalue, valuesize); - } - } - return result; -} - - -const(TypeInfo_AssociativeArray) _aaUnwrapTypeInfo(const(TypeInfo) tiRaw) pure nothrow @nogc -{ - const(TypeInfo)* p = &tiRaw; - TypeInfo_AssociativeArray ti; - while (true) - { - if ((ti = cast(TypeInfo_AssociativeArray)*p) !is null) - break; - - if (auto tiConst = cast(TypeInfo_Const)*p) { - // The member in object_.d and object.di differ. This is to ensure - // the file can be compiled both independently in unittest and - // collectively in generating the library. Fixing object.di - // requires changes to std.format in Phobos, fixing object_.d - // makes Phobos's unittest fail, so this hack is employed here to - // avoid irrelevant changes. - static if (is(typeof(&tiConst.base) == TypeInfo*)) - p = &tiConst.base; - else - p = &tiConst.next; - } else - assert(0); // ??? - } - - return ti; -} - - -/*********************************** - * Compare AA contents for equality. - * Returns: - * 1 equal - * 0 not equal - */ -int _aaEqual(in TypeInfo tiRaw, in AA e1, in AA e2) -{ - //printf("_aaEqual()\n"); - //printf("keyti = %.*s\n", typeid(ti.key).name); - //printf("valueti = %.*s\n", typeid(ti.next).name); - - if (e1.impl is e2.impl) - return 1; - - size_t len = _aaLen(e1); - if (len != _aaLen(e2)) - return 0; - - // Bug 9852: at this point, e1 and e2 have the same length, so if one is - // null, the other must either also be null or have zero entries, so they - // must be equal. We check this here to avoid dereferencing null later on. - if (e1.impl is null || e2.impl is null) - return 1; - - // Check for Bug 5925. ti_raw could be a TypeInfo_Const, we need to unwrap - // it until reaching a real TypeInfo_AssociativeArray. - const TypeInfo_AssociativeArray ti = _aaUnwrapTypeInfo(tiRaw); - - /* Algorithm: Visit each key/value pair in e1. If that key doesn't exist - * in e2, or if the value in e1 doesn't match the one in e2, the arrays - * are not equal, and exit early. - * After all pairs are checked, the arrays must be equal. - */ - - const keyti = ti.key; - const valueti = ti.next; - const keysize = aligntsize(keyti.tsize); - - assert(e2.impl !is null); - const len2 = e2.impl.buckets.length; - - int _aaKeys_x(const(Entry)* e) - { - do - { - auto pkey = cast(void*)(e + 1); - auto pvalue = pkey + keysize; - //printf("key = %d, value = %g\n", *cast(int*)pkey, *cast(double*)pvalue); - - // We have key/value for e1. See if they exist in e2 - - auto key_hash = keyti.getHash(pkey); - //printf("hash = %d\n", key_hash); - const i = key_hash % len2; - const(Entry)* f = e2.impl.buckets[i]; - while (1) - { - //printf("f is %p\n", f); - if (f is null) - return 0; // key not found, so AA's are not equal - if (key_hash == f.hash) - { - //printf("hash equals\n"); - if (keyti.equals(pkey, f + 1)) - { - // Found key in e2. Compare values - //printf("key equals\n"); - auto pvalue2 = cast(void *)(f + 1) + keysize; - if (valueti.equals(pvalue, pvalue2)) - { - //printf("value equals\n"); - break; - } - else - return 0; // values don't match, so AA's are not equal - } - } - f = f.next; - } - - // Look at next entry in e1 - e = e.next; - } while (e !is null); - return 1; // this subtree matches - } - - // note, cannot use firstUsedBucketCache here, e1 is const - foreach (e; e1.impl.buckets[e1.impl.firstUsedBucket..$]) - { - if (e) - { - if (_aaKeys_x(e) == 0) - return 0; - } - } - - return 1; // equal -} - - -/***************************************** - * Computes a hash value for the entire AA - * Returns: - * Hash value - */ -hash_t _aaGetHash(in AA* aa, in TypeInfo tiRaw) nothrow -{ - import rt.util.hash; - - if (aa.impl is null) - return 0; - - hash_t h = 0; - const TypeInfo_AssociativeArray ti = _aaUnwrapTypeInfo(tiRaw); - const keyti = ti.key; - const valueti = ti.next; - const keysize = aligntsize(keyti.tsize); - - // note, can't use firstUsedBucketCache here, aa is const - foreach (const(Entry)* e; aa.impl.buckets[aa.impl.firstUsedBucket..$]) - { - while (e) - { - auto pkey = cast(void*)(e + 1); - auto pvalue = pkey + keysize; - - // Compute a hash for the key/value pair by hashing their - // respective hash values. - hash_t[2] hpair; - hpair[0] = e.hash; - hpair[1] = valueti.getHash(pvalue); - - // Combine the hash of the key/value pair with the running hash - // value using an associative operator (+) so that the resulting - // hash value is independent of the actual order the pairs are - // stored in (important to ensure equality of hash value for two - // AA's containing identical pairs but with different hashtable - // sizes). - h += hashOf(hpair.ptr, hpair.length * hash_t.sizeof); - - e = e.next; - } - } - - return h; -} - pure nothrow unittest { - string[int] key1 = [1: "true", 2: "false"]; - string[int] key2 = [1: "false", 2: "true"]; + string[int] key1 = [1 : "true", 2 : "false"]; + string[int] key2 = [1 : "false", 2 : "true"]; + string[int] key3; // AA lits create a larger hashtable - int[string[int]] aa1 = [key1: 100, key2: 200]; + int[string[int]] aa1 = [key1 : 100, key2 : 200, key3 : 300]; // Ensure consistent hash values are computed for key1 assert((key1 in aa1) !is null); @@ -928,6 +737,7 @@ pure nothrow unittest int[string[int]] aa2; aa2[key1] = 100; aa2[key2] = 200; + aa2[key3] = 300; assert(aa1 == aa2); @@ -946,108 +756,167 @@ pure nothrow unittest int[string] a; a["foo"] = 0; a.remove("foo"); - assert(a == null); // should not crash + assert(a == null); // should not crash int[string] b; assert(b is null); - assert(a == b); // should not deref null - assert(b == a); // ditto + assert(a == b); // should not deref null + assert(b == a); // ditto int[string] c; c["a"] = 1; - assert(a != c); // comparison with empty non-null AA + assert(a != c); // comparison with empty non-null AA assert(c != a); - assert(b != c); // comparison with null AA + assert(b != c); // comparison with null AA assert(c != b); } - -/** - * _aaRange implements a ForwardRange - */ -struct Range +// Bugzilla 14104 +unittest { - Impl* impl; - Entry* current; -} + import core.stdc.stdio; + alias K = const(ubyte)*; + size_t[K] aa; + immutable key = cast(K)(cast(size_t) uint.max + 1); + aa[key] = 12; + assert(key in aa); +} -Range _aaRange(AA aa) pure nothrow @nogc +unittest { - typeof(return) res; - if (aa.impl is null) - return res; + int[int] aa; + foreach (k, v; aa) + assert(false); + foreach (v; aa) + assert(false); + assert(aa.byKey.empty); + assert(aa.byValue.empty); + assert(aa.byKeyValue.empty); + + size_t n; + aa = [0 : 3, 1 : 4, 2 : 5]; + foreach (k, v; aa) + { + n += k; + assert(k >= 0 && k < 3); + assert(v >= 3 && v < 6); + } + assert(n == 3); + n = 0; - res.impl = aa.impl; - foreach (entry; aa.impl.buckets[aa.impl.firstUsedBucketCache .. $] ) + foreach (v; aa) { - if (entry !is null) - { - res.current = entry; - break; - } + n += v; + assert(v >= 3 && v < 6); } - return res; -} + assert(n == 12); + n = 0; + foreach (k, v; aa) + { + ++n; + break; + } + assert(n == 1); -bool _aaRangeEmpty(Range r) pure nothrow @nogc -{ - return r.current is null; + n = 0; + foreach (v; aa) + { + ++n; + break; + } + assert(n == 1); } - -void* _aaRangeFrontKey(Range r) pure nothrow @nogc -in -{ - assert(r.current !is null); -} -body +unittest { - return cast(void*)r.current + Entry.sizeof; -} - + int[int] aa; + assert(!aa.remove(0)); + aa = [0 : 1]; + assert(aa.remove(0)); + assert(!aa.remove(0)); + aa[1] = 2; + assert(!aa.remove(0)); + assert(aa.remove(1)); -void* _aaRangeFrontValue(Range r) pure nothrow @nogc -in -{ - assert(r.current !is null); - assert(r.impl.keyti !is null); // set on first insert + assert(aa.length == 0); + assert(aa.byKey.empty); } -body + +unittest { - return cast(void*)r.current + Entry.sizeof + aligntsize(r.impl.keyti.tsize); + alias E = void[0]; + auto aa = [E.init : E.init]; + assert(aa.length == 1); + assert(aa.byKey.front == E.init); + assert(aa.byValue.front == E.init); + aa[E.init] = E.init; + assert(aa.length == 1); + assert(aa.remove(E.init)); + assert(aa.length == 0); } - -void _aaRangePopFront(ref Range r) pure nothrow @nogc +// test tombstone purging +unittest { - if (r.current.next !is null) - { - r.current = r.current.next; - } - else + int[int] aa; + foreach (i; 0 .. 6) + aa[i] = i; + foreach (i; 0 .. 6) + assert(aa.remove(i)); + foreach (i; 6 .. 10) + aa[i] = i; + assert(aa.length == 4); + foreach (i; 6 .. 10) + assert(i in aa); +} + +// test postblit for AA literals +unittest +{ + static struct T { - immutable idx = r.current.hash % r.impl.buckets.length; - r.current = null; - foreach (entry; r.impl.buckets[idx + 1 .. $]) + static size_t postblit, dtor; + this(this) { - if (entry !is null) - { - r.current = entry; - break; - } + ++postblit; + } + + ~this() + { + ++dtor; } } -} -// Bugzilla 14104 -unittest -{ - import core.stdc.stdio; - alias K = const(ubyte)*; - size_t[K] aa; - immutable key = cast(K)(cast(size_t)uint.max + 1); - aa[key] = 12; - assert(key in aa); + T t; + auto aa1 = [0 : t, 1 : t]; + assert(T.dtor == 0 && T.postblit == 2); + aa1[0] = t; + assert(T.dtor == 1 && T.postblit == 3); + + T.dtor = 0; + T.postblit = 0; + + auto aa2 = [0 : t, 1 : t, 0 : t]; // literal with duplicate key => value overwritten + assert(T.dtor == 1 && T.postblit == 3); + + T.dtor = 0; + T.postblit = 0; + + auto aa3 = [t : 0]; + assert(T.dtor == 0 && T.postblit == 1); + aa3[t] = 1; + assert(T.dtor == 0 && T.postblit == 1); + aa3.remove(t); + assert(T.dtor == 0 && T.postblit == 1); + aa3[t] = 2; + assert(T.dtor == 0 && T.postblit == 2); + + // dtor will be called by GC finalizers + aa1 = null; + aa2 = null; + aa3 = null; + GC.runFinalizers((cast(char*)(&entryDtor))[0 .. 1]); + assert(T.dtor == 6 && T.postblit == 2); } diff --git a/src/rt/lifetime.d b/src/rt/lifetime.d index ee4a4c21ac4..b3b0293afb4 100644 --- a/src/rt/lifetime.d +++ b/src/rt/lifetime.d @@ -667,13 +667,14 @@ extern(C) void _d_arrayshrinkfit(const TypeInfo ti, void[] arr) /+nothrow+/ } } +package bool hasPostblit(in TypeInfo ti) +{ + return (&ti.postblit).funcptr !is &TypeInfo.postblit; +} + void __doPostblit(void *ptr, size_t len, const TypeInfo ti) { - // optimize out any type info that does not need postblit. - //if((&ti.postblit).funcptr is &TypeInfo.postblit) // compiler doesn't like this - auto fptr = &ti.postblit; - if(fptr.funcptr is &TypeInfo.postblit) - // postblit has not been overridden, no point in looping. + if (!hasPostblit(ti)) return; if(auto tis = cast(TypeInfo_Struct)ti) @@ -1051,83 +1052,43 @@ extern (C) void[] _d_newarraymiTX(const TypeInfo ti, size_t[] dims) } /** - * Allocate a non-array item. + * Allocate an uninitialized non-array item. * This is an optimization to avoid things needed for arrays like the __arrayPad(size). */ - -extern (C) void* _d_newitemT(TypeInfo _ti) +extern (C) void* _d_newitemU(in TypeInfo _ti) { auto ti = unqualify(_ti); - auto size = ti.tsize; // array element size - auto baseFlags = !(ti.flags & 1) ? BlkAttr.NO_SCAN : 0; - size_t typeInfoSize = structTypeInfoSize(ti); - size += typeInfoSize; // Need space for the type info - if (typeInfoSize) - baseFlags |= BlkAttr.STRUCTFINAL | BlkAttr.FINALIZE; + auto flags = !(ti.flags & 1) ? BlkAttr.NO_SCAN : 0; + immutable tiSize = structTypeInfoSize(ti); + immutable size = ti.tsize + tiSize; + if (tiSize) + flags |= BlkAttr.STRUCTFINAL | BlkAttr.FINALIZE; - debug(PRINTF) printf("_d_newitemT(size = %d)\n", size); - /* not sure if we need this... - * if (length == 0 || size == 0) - result = null; - else - {*/ - // allocate a block to hold this item - auto blkInf = GC.qalloc(size, baseFlags, ti); - auto ptr = blkInf.base; - debug(PRINTF) printf(" p = %p\n", ptr); - if(size == ubyte.sizeof) - *cast(ubyte*)ptr = 0; - else if(size == ushort.sizeof) - *cast(ushort*)ptr = 0; - else if(size == uint.sizeof) - *cast(uint*)ptr = 0; - else - memset(ptr, 0, size); - - if (typeInfoSize) - *cast(TypeInfo*)(ptr + blkInf.size - typeInfoSize) = ti; + auto blkInf = GC.qalloc(size, flags, ti); + auto p = blkInf.base; - return ptr; - //} + if (tiSize) + *cast(TypeInfo*)(p + blkInf.size - tiSize) = cast() ti; + return p; } -extern (C) void* _d_newitemiT(TypeInfo _ti) +/// Same as above, zero initializes the item. +extern (C) void* _d_newitemT(in TypeInfo _ti) { - auto ti = unqualify(_ti); - auto size = ti.tsize; // array element size - auto baseFlags = !(ti.flags & 1) ? BlkAttr.NO_SCAN : 0; - size_t typeInfoSize = structTypeInfoSize(ti); - size += typeInfoSize; // Need space for the type info - if (typeInfoSize) - baseFlags |= BlkAttr.STRUCTFINAL | BlkAttr.FINALIZE; - - debug(PRINTF) printf("_d_newitemiT(size = %d)\n", size); - - /*if (length == 0 || size == 0) - result = null; - else - {*/ - auto initializer = ti.init(); - auto isize = initializer.length; - auto q = initializer.ptr; - - auto blkInf = GC.qalloc(size, baseFlags, ti); - auto ptr = blkInf.base; - debug(PRINTF) printf(" p = %p\n", ptr); - if (isize == 1) - *cast(ubyte*)ptr = *cast(ubyte*)q; - else if (isize == ushort.sizeof) - *cast(ushort*)ptr = *cast(ushort*)q; - else if (isize == uint.sizeof) - *cast(uint*)ptr = *cast(uint*)q; - else - memcpy(ptr, q, isize); + auto p = _d_newitemU(_ti); + memset(p, 0, _ti.tsize); + return p; +} - if (typeInfoSize) - *cast(TypeInfo*)(ptr + blkInf.size - typeInfoSize) = ti; - return ptr; - //} +/// Same as above, for item with non-zero initializer. +extern (C) void* _d_newitemiT(in TypeInfo _ti) +{ + auto p = _d_newitemU(_ti); + auto init = _ti.init(); + assert(init.length <= _ti.tsize); + memcpy(p, init.ptr, init.length); + return p; } /** @@ -2116,8 +2077,7 @@ out (result) // If a postblit is involved, the contents of result might rightly differ // from the bitwise concatenation of x and y. - auto pb = &tinext.postblit; - if (pb.funcptr is &TypeInfo.postblit) + if (!hasPostblit(tinext)) { for (size_t i = 0; i < x.length * sizeelem; i++) assert((cast(byte*)result)[i] == (cast(byte*)x)[i]); @@ -2558,9 +2518,9 @@ unittest } // associative arrays - import rt.aaA; + import rt.aaA : entryDtor; // throw away all existing AA entries with dtor - GC.runFinalizers((cast(char*)(&Entry.Dtor))[0..1]); + GC.runFinalizers((cast(char*)(&entryDtor))[0..1]); S1[int] aa1; aa1[0] = S1(0); @@ -2569,7 +2529,7 @@ unittest { dtorCount = 0; aa1 = null; - GC.runFinalizers((cast(char*)(&Entry.Dtor))[0..1]); + GC.runFinalizers((cast(char*)(&entryDtor))[0..1]); assert(dtorCount == 2); } @@ -2581,7 +2541,7 @@ unittest { dtorCount = 0; aa2 = null; - GC.runFinalizers((cast(char*)(&Entry.Dtor))[0..1]); + GC.runFinalizers((cast(char*)(&entryDtor))[0..1]); assert(dtorCount == 3); } @@ -2592,7 +2552,7 @@ unittest { dtorCount = 0; aa3 = null; - GC.runFinalizers((cast(char*)(&Entry.Dtor))[0..1]); + GC.runFinalizers((cast(char*)(&entryDtor))[0..1]); assert(dtorCount == 4); } }