| /** | |
| * template implementation of associative arrays. | |
| * | |
| * Copyright: Copyright Digital Mars 2000 - 2015, Steven Schveighoffer 2022. | |
| * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). | |
| * Authors: Martin Nowak, Steven Schveighoffer, Rainer Schuetze | |
| * | |
| * Source: $(DRUNTIMESRC core/internal/_newaa.d) | |
| * | |
| * derived from rt/aaA.d | |
| */ | |
| module core.internal.newaa; | |
| /// AA version for debuggers, bump whenever changing the layout | |
| immutable int _aaVersion = 1; | |
| import core.internal.util.math : min, max; | |
| import core.internal.traits : substInout; | |
| // 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; | |
| /// AA wrapper | |
| struct AA(K, V) | |
| { | |
| Impl!(K,V)* impl; | |
| alias impl this; | |
| @property bool empty() const pure nothrow @nogc @safe | |
| { | |
| pragma(inline, true); | |
| return impl is null || !impl.length; | |
| } | |
| @property size_t length() const pure nothrow @nogc @safe | |
| { | |
| pragma(inline, true); | |
| return impl is null ? 0 : impl.length; | |
| } | |
| } | |
| /// like core.internal.traits.Unconst, but stripping inout, too | |
| private template Unconstify(T : const U, U) | |
| { | |
| static if (is(U == inout V, V)) | |
| alias Unconstify = V; | |
| else | |
| alias Unconstify = U; | |
| } | |
| ref _refAA(K, V)(ref V[K] aa) @trusted | |
| { | |
| pragma(inline, true); | |
| return *(cast(AA!(substInout!K, substInout!V)*)&aa); | |
| } | |
| auto _toAA(K, V)(const V[K] aa) @trusted | |
| { | |
| pragma(inline, true); | |
| return *(cast(const(AA!(K, V))*)&aa); | |
| } | |
| auto _toAA(K, V)(inout V[K] aa) @trusted | |
| { | |
| pragma(inline, true); | |
| return *(cast(inout(AA!(K, V))*)&aa); | |
| } | |
| // for backward compatibility, but should be deprecated | |
| auto _toAA(K, V)(shared const V[K] aa) @trusted | |
| { | |
| pragma(inline, true); | |
| return *(cast(AA!(K, V)*)&aa); | |
| } | |
| // for backward compatibility, but should be deprecated | |
| auto _toAA(K, V)(shared V[K] aa) @trusted | |
| { | |
| pragma(inline, true); | |
| return *(cast(AA!(K, V)*)&aa); | |
| } | |
| // resolve ambiguity for immutable converting to const and shared const | |
| auto _toAA(K, V)(immutable V[K] aa) @trusted | |
| { | |
| pragma(inline, true); | |
| return *(cast(AA!(K, V)*)&aa); | |
| } | |
| static struct Entry(K, V) | |
| { | |
| K key; | |
| V value; | |
| } | |
| // backward compatibility conversions | |
| private ref compat_key(K, K2)(ref K2 key) | |
| { | |
| static if(!(is(K2 == struct) && __traits(isNested, K2))) | |
| pragma(inline, true); | |
| static if (is(K2 == const(char)[]) && is(K == string)) | |
| return (ref (ref return K2 k2) @trusted => *cast(string*)&k2)(key); | |
| else | |
| return key; | |
| } | |
| private void _aaMove(V)(ref V src, ref V dst) @trusted | |
| { | |
| import core.stdc.string : memcpy, memset; | |
| // move without postblit!? | |
| memcpy(&dst, &src, V.sizeof); | |
| static if (__traits(isZeroInit, V)) | |
| memset(&src, 0, V.sizeof); | |
| else | |
| memcpy(&src, &V.init, V.sizeof); | |
| } | |
| // mimick behaviour of rt.aaA for initialization | |
| Entry!(K, V)* _newEntry(K, V)(ref K key, auto ref V value) | |
| { | |
| static if (__traits(compiles, new Entry!(K, V)(key, value))) | |
| { | |
| auto entry = new Entry!(K, V)(key, value); | |
| } | |
| else static if (__traits(compiles, { K k; new Entry!(K, V)(k); })) | |
| { | |
| auto entry = new Entry!(K, V)(key); | |
| _aaMove(value, entry.value); | |
| } | |
| else | |
| { | |
| auto entry = new Entry!(K, V); | |
| _aaMove(key, entry.key); | |
| _aaMove(value, entry.value); | |
| } | |
| return entry; | |
| } | |
| // mimick behaviour of rt.aaA for initialization | |
| Entry!(K, V)* _newEntry(K, V, K2)(ref K2 key) | |
| { | |
| static if (__traits(compiles, new Entry!(K, V)(key)) && | |
| !(is(V == struct) && __traits(isNested, V))) // not detected by "compiles" | |
| { | |
| auto entry = new Entry!(K, V)(key); | |
| } | |
| else static if (__traits(compiles, { K2 k; new Entry!(K, V)(k, V.init); })) | |
| { | |
| // with disabled ctor for V | |
| auto entry = new Entry!(K, V)(key, V.init); | |
| } | |
| else | |
| { | |
| // with disabled ctor for K and V | |
| auto entry = new Entry!(K, V); | |
| entry.key = key; | |
| } | |
| static if (!__traits(isZeroInit, V)) | |
| { | |
| () @trusted { (cast(ubyte*)&entry.value)[0..V.sizeof] = 0; }(); | |
| } | |
| return entry; | |
| } | |
| template pure_hashOf(K) | |
| { | |
| static if (!(is(K == struct) && __traits(isNested, K)) && | |
| __traits(compiles, function hash_t(scope const ref K key) pure nothrow @nogc @trusted { return hashOf(cast()key); })) | |
| { | |
| // avoid wrapper call in debug builds if pure nothrow @nogc is inferred | |
| pragma(inline, true) | |
| hash_t pure_hashOf(scope const ref K key) @trusted { return hashOf(cast()key); } | |
| } | |
| else | |
| { | |
| // for backward compatibility, do not require const in hashOf() | |
| hash_t wrap_hashOf(K)(scope const ref K key) @trusted { return hashOf(cast()key); } | |
| enum pure_hashOf = cast(hash_t function(scope ref const K key) pure nothrow @nogc @safe) &wrap_hashOf!K; | |
| } | |
| } | |
| // for backward compatibilty pretend the comparison is @safe, pure, etc | |
| // this also breaks cyclic inference on recursive data types | |
| template pure_keyEqual(K1, K2 = K1) | |
| { | |
| static if (!(is(K1 == struct) && __traits(isNested, K1)) && | |
| !(is(K2 == struct) && __traits(isNested, K2)) && | |
| __traits(compiles, function bool(ref const K1 k1, ref const K2 k2) pure nothrow @nogc @trusted { return cast()k1 == cast()k2; })) | |
| { | |
| // avoid wrapper call in debug builds if pure nothrow @nogc is inferred | |
| pragma(inline, true) | |
| bool pure_keyEqual(ref const K1 k1, ref const K2 k2) @trusted { return cast()k1 == cast()k2; } | |
| } | |
| else | |
| { | |
| bool keyEqual(ref const K1 k1, ref const K2 k2) @trusted { return cast()k1 == cast()k2; } | |
| enum pure_keyEqual = cast(bool function(ref const K1, ref const K2) pure nothrow @nogc @safe) &keyEqual; | |
| } | |
| } | |
| private struct Impl(K, V) | |
| { | |
| private: | |
| alias Bucket = .Bucket!(K, V); | |
| this(size_t sz /* = INIT_NUM_BUCKETS */) nothrow | |
| { | |
| buckets = allocBuckets(sz); | |
| firstUsed = cast(uint) buckets.length; | |
| // only for binary compatibility | |
| version(D_TypeInfo) | |
| entryTI = typeid(Entry!(K, V)); | |
| hashFn = delegate size_t (scope ref const K key) nothrow pure @nogc @safe { | |
| return pure_hashOf!K(key); | |
| }; | |
| keysz = cast(uint) K.sizeof; | |
| valsz = cast(uint) V.sizeof; | |
| valoff = cast(uint) talign(keysz, V.alignof); | |
| enum ctflags = () { | |
| import core.internal.traits; | |
| Impl.Flags flags; | |
| static if (__traits(hasPostblit, K)) | |
| flags |= flags.keyHasPostblit; | |
| static if (hasIndirections!K || hasIndirections!V) | |
| flags |= flags.hasPointers; | |
| return flags; | |
| } (); | |
| flags = ctflags; | |
| } | |
| Bucket[] buckets; | |
| uint used; | |
| uint deleted; | |
| const(TypeInfo) entryTI; // only for binary compatibility | |
| uint firstUsed; | |
| immutable uint keysz; // only for binary compatibility | |
| immutable uint valsz; // only for binary compatibility | |
| immutable uint valoff; // only for binary compatibility | |
| Flags flags; // only for binary compatibility | |
| size_t delegate(scope ref const K) nothrow pure @nogc @safe hashFn; | |
| enum Flags : ubyte | |
| { | |
| none = 0x0, | |
| keyHasPostblit = 0x1, | |
| hasPointers = 0x2, | |
| } | |
| @property size_t length() const pure nothrow @nogc @safe | |
| { | |
| pragma(inline, true); | |
| assert(used >= deleted); | |
| return used - deleted; | |
| } | |
| @property size_t dim() const pure nothrow @nogc @safe | |
| { | |
| pragma(inline, true); | |
| return buckets.length; | |
| } | |
| @property size_t mask() const pure nothrow @nogc @safe | |
| { | |
| pragma(inline, true); | |
| return dim - 1; | |
| } | |
| // find the first slot to insert a value with hash | |
| size_t findSlotInsert(size_t hash) const pure nothrow @nogc @safe | |
| { | |
| for (size_t i = hash & mask, j = 1;; ++j) | |
| { | |
| if (!buckets[i].filled) | |
| return i; | |
| i = (i + j) & mask; | |
| } | |
| } | |
| // lookup a key | |
| inout(Bucket)* findSlotLookup(K2)(size_t hash, scope ref const K2 key) inout pure @safe nothrow | |
| { | |
| for (size_t i = hash & mask, j = 1;; ++j) | |
| { | |
| auto b = &buckets[i]; // avoid multiple bounds checks | |
| if (b.hash == hash && b.entry) | |
| if (pure_keyEqual!(K2, K)(key, b.entry.key)) | |
| return b; | |
| if (b.empty) | |
| return null; | |
| i = (i + j) & mask; | |
| } | |
| } | |
| void grow() pure nothrow @safe | |
| { | |
| // 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() pure nothrow @safe | |
| { | |
| if (dim > INIT_NUM_BUCKETS) | |
| resize(dim / GROW_FAC); | |
| } | |
| void resize(size_t ndim) pure nothrow @safe | |
| { | |
| auto obuckets = buckets; | |
| buckets = allocBuckets(ndim); | |
| foreach (ref b; obuckets[firstUsed .. $]) | |
| if (b.filled) | |
| buckets[findSlotInsert(b.hash)] = b; | |
| firstUsed = 0; | |
| used -= deleted; | |
| deleted = 0; | |
| obuckets.length = 0; // safe to free b/c impossible to reference, but doesn't really free | |
| } | |
| void clear() pure nothrow | |
| { | |
| // clear all data, but don't change bucket array length | |
| buckets[firstUsed .. $] = Bucket.init; | |
| deleted = used = 0; | |
| firstUsed = cast(uint) dim; | |
| } | |
| size_t calcHash(K2)(ref K2 key) const nothrow pure @nogc @safe | |
| { | |
| static if(is(K2* : K*)) // ref compatible? | |
| hash_t hash = pure_hashOf!K(key); | |
| else | |
| hash_t hash = pure_hashOf!K2(key); | |
| // highest bit is set to distinguish empty/deleted from filled buckets | |
| return mix(hash) | HASH_FILLED_MARK; | |
| } | |
| static Bucket[] allocBuckets(size_t dim) pure nothrow @safe | |
| { | |
| // could allocate with BlkAttr.NO_INTERIOR, but that does not combine | |
| // well with arrays and type info for precise scanning | |
| return new Bucket[dim]; | |
| } | |
| } | |
| //============================================================================== | |
| // Bucket | |
| //------------------------------------------------------------------------------ | |
| private struct Bucket(K, V) | |
| { | |
| private pure nothrow @nogc: | |
| size_t hash; | |
| Entry!(K, V)* entry; | |
| @property bool empty() const | |
| { | |
| pragma(inline, true); | |
| return hash == HASH_EMPTY; | |
| } | |
| @property bool deleted() const | |
| { | |
| pragma(inline, true); | |
| return hash == HASH_DELETED; | |
| } | |
| @property bool filled() const @safe | |
| { | |
| pragma(inline, true); | |
| return cast(ptrdiff_t) hash < 0; | |
| } | |
| } | |
| //============================================================================== | |
| // Helper functions | |
| //------------------------------------------------------------------------------ | |
| 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; | |
| } | |
| // 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; | |
| } | |
| private size_t nextpow2(const size_t n) pure nothrow @nogc @safe | |
| { | |
| import core.bitop : bsr; | |
| if (!n) | |
| return 1; | |
| const isPowerOf2 = !((n - 1) & n); | |
| return 1 << (bsr(n) + !isPowerOf2); | |
| } | |
| pure nothrow @nogc unittest | |
| { | |
| // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 | |
| foreach (const n, const pow2; [1, 1, 2, 4, 4, 8, 8, 8, 8, 16]) | |
| assert(nextpow2(n) == pow2); | |
| } | |
| //============================================================================== | |
| // API Implementation | |
| //------------------------------------------------------------------------------ | |
| /** Allocate associative array data. | |
| * Called for `new SomeAA` expression. | |
| * Returns: | |
| * A new associative array. | |
| * Note: | |
| * not supported in CTFE | |
| */ | |
| V[K] _d_aaNew(K, V)() | |
| { | |
| AA!(K, V) aa; | |
| aa.impl = new Impl!(K,V)(INIT_NUM_BUCKETS); | |
| return *cast(V[K]*)&aa; | |
| } | |
| /// Determine number of entries in associative array. | |
| /// Note: | |
| /// emulated by the compiler during CTFE | |
| size_t _d_aaLen(K, V)(inout V[K] a) | |
| { | |
| auto aa = _toAA!(K, V)(a); | |
| return aa ? aa.length : 0; | |
| } | |
| /// ditto | |
| size_t _d_aaLen(K, V)(shared V[K] a) | |
| { | |
| // accept shared for backward compatibility, should be deprecated | |
| return _d_aaLen(cast(V[K]) a); | |
| } | |
| /// ditto | |
| size_t _d_aaLen(K, V)(const shared V[K] a) | |
| { | |
| // accept shared for backward compatibility, should be deprecated | |
| return _d_aaLen(cast(V[K]) a); | |
| } | |
| /****************************** | |
| * Lookup key in aa. | |
| * Called only from implementation of (aa[key]) expressions when value is mutable. | |
| * Params: | |
| * aa = associative array | |
| * key = reference to the key value | |
| * found = returns whether the key was found or a new entry was added | |
| * Returns: | |
| * if key was in the aa, a mutable pointer to the existing value. | |
| * If key was not in the aa, a mutable pointer to newly inserted value which | |
| * is set to zero | |
| */ | |
| V* _d_aaGetY(K, V, T : V1[K1], K1, V1, K2)(auto ref scope T aa, auto ref K2 key, out bool found) | |
| { | |
| ref aax = cast(V[K])cast(V1[K1])aa; // remove outer const from T | |
| return _aaGetX!(K, V, K2)(aax, key, found, _noV2()); | |
| } | |
| private struct _noV2 {} | |
| /****************************** | |
| * Lookup key in aa. | |
| * Called only from implementation of require, update and _d_aaGetY | |
| * Params: | |
| * a = associative array | |
| * key = reference to the key value | |
| * found = true if the value was found | |
| * v2 = if key not found, init new value to this (except if type _noV2) | |
| * Returns: | |
| * if key was in the aa, a mutable pointer to the existing value. | |
| * If key was not in the aa, a mutable pointer to newly inserted value which | |
| * is set to v2 or is zero-initialized | |
| */ | |
| V* _aaGetX(K, V, K2, V2)(auto ref scope V[K] a, auto ref K2 key, out bool found, lazy V2 v2) | |
| { | |
| ref aa = _refAA!(K, V)(a); | |
| // lazily alloc implementation | |
| if (aa is null) | |
| { | |
| aa.impl = new Impl!(K, V)(INIT_NUM_BUCKETS); | |
| } | |
| ref key2 = compat_key!(K)(key); | |
| // get hash and bucket for key | |
| immutable hash = aa.calcHash(key2); | |
| // found a value => return it | |
| if (auto p = aa.findSlotLookup(hash, key2)) | |
| { | |
| found = true; | |
| return &p.entry.value; | |
| } | |
| auto pi = aa.findSlotInsert(hash); | |
| // check load factor and possibly grow | |
| if (!aa.buckets[pi].deleted && (aa.used + 1) * GROW_DEN > aa.dim * GROW_NUM) | |
| { | |
| aa.grow(); | |
| pi = aa.findSlotInsert(hash); | |
| assert(aa.buckets[pi].empty); | |
| } | |
| // allocate entry and update search cache (if not throwing in _newEntry) | |
| ref p = aa.buckets[pi]; | |
| static if (is(V2 == _noV2)) | |
| p.entry = _newEntry!(K, V)(key2); | |
| else | |
| p.entry = _newEntry!(K, V)(key2, v2); | |
| if (p.deleted) | |
| --aa.deleted; | |
| else | |
| aa.used++; | |
| p.hash = hash; | |
| aa.firstUsed = min(aa.firstUsed, cast(uint)pi); | |
| return &p.entry.value; | |
| } | |
| /****************************** | |
| * Lookup key in aa. | |
| * Called only from implementation of (aa[key]) expressions when value is not mutable. | |
| * Params: | |
| * aa = associative array | |
| * key = key value | |
| * Returns: | |
| * pointer to value if present, null otherwise | |
| */ | |
| auto _d_aaGetRvalueX(K, V, K2)(inout V[K] aa, auto ref scope K2 key) | |
| { | |
| return _d_aaIn(aa, key); | |
| } | |
| /// ditto | |
| auto _d_aaGetRvalueX(K, V, K2)(shared(V[K]) aa, auto ref scope K2 key) | |
| { | |
| // accept shared for backward compatibility, should be deprecated | |
| return cast(shared(V)*)_d_aaIn(cast(V[K]) aa, key); | |
| } | |
| /// ditto | |
| auto _d_aaGetRvalueX(K, V, K2)(shared const(V[K]) aa, auto ref scope K2 key) | |
| { | |
| // accept shared for backward compatibility, should be deprecated | |
| return cast(const shared(V)*)_d_aaIn(cast(V[K]) aa, key); | |
| } | |
| /// ditto | |
| auto _d_aaGetRvalueX(K, V, K2)(immutable(V[K]) aa, auto ref scope K2 key) | |
| { | |
| // resolve ambiguity for immutable converting to const and shared const | |
| return _d_aaIn((() @trusted => cast(V[K]) aa) (), key); | |
| } | |
| /*********************************** | |
| * Creates a new associative array of the same size and copies the contents of | |
| * the associative array into it. | |
| * Params: | |
| * a = The associative array. | |
| */ | |
| auto _aaDup(T : V[K], K, V)(T a) | |
| { | |
| auto aa = _toAA!(K, V)(a); | |
| immutable len = aa.length; | |
| if (len == 0) | |
| return null; | |
| auto impl = new Impl!(K, V)(aa.dim); | |
| // copy the entries | |
| bool sameHash = aa.hashFn == impl.hashFn; // can be different if coming from template/rt | |
| foreach (b; aa.buckets[aa.firstUsed .. $]) | |
| { | |
| if (!b.filled) | |
| continue; | |
| hash_t hash = sameHash ? b.hash : impl.calcHash(b.entry.key); | |
| auto pi = impl.findSlotInsert(hash); | |
| auto p = &impl.buckets[pi]; | |
| p.hash = hash; | |
| p.entry = new Entry!(K, V)(b.entry.key, b.entry.value); | |
| impl.firstUsed = min(impl.firstUsed, cast(uint)pi); | |
| } | |
| impl.used = cast(uint) len; | |
| return () @trusted { return *cast(Unconstify!V[K]*)&impl; }(); | |
| } | |
| /****************************** | |
| * Lookup key in aa. | |
| * Called only from implementation of (key in aa) expressions. | |
| * Params: | |
| * a = associative array opaque pointer | |
| * key = reference to the key value | |
| * Returns: | |
| * pointer to value if present, null otherwise | |
| */ | |
| auto _d_aaIn(T : V[K], K, V, K2)(inout T a, auto ref scope K2 key) | |
| { | |
| auto aa = _toAA!(K, V)(a); | |
| if (aa.empty) | |
| return null; | |
| ref key2 = compat_key!(K)(key); | |
| immutable hash = aa.calcHash(key2); | |
| if (auto p = aa.findSlotLookup(hash, key2)) | |
| return &p.entry.value; | |
| return null; | |
| } | |
| /// ditto | |
| auto _d_aaIn(T : V[K], K, V, K2)(shared T a, auto ref scope K2 key) | |
| { | |
| // accept shared for backward compatibility, should be deprecated | |
| return _d_aaIn(cast(V[K]) a, key); | |
| } | |
| /// ditto | |
| auto _d_aaIn(T : V[K], K, V, K2)(const shared T a, auto ref scope K2 key) | |
| { | |
| // accept shared for backward compatibility, should be deprecated | |
| return _d_aaIn(cast(V[K]) a, key); | |
| } | |
| // fake purity for backward compatibility with runtime hooks | |
| private extern(C) bool gc_inFinalizer() pure nothrow @safe; | |
| /// Delete entry scope const AA, return true if it was present | |
| auto _d_aaDel(T : V[K], K, V, K2)(T a, auto ref K2 key) | |
| { | |
| auto aa = _toAA!(K, V)(a); | |
| if (aa.empty) | |
| return false; | |
| ref key2 = compat_key!(K)(key); | |
| immutable hash = aa.calcHash(key2); | |
| if (auto p = aa.findSlotLookup(hash, key2)) | |
| { | |
| // clear entry | |
| p.hash = HASH_DELETED; | |
| p.entry = null; | |
| ++aa.deleted; | |
| // `shrink` reallocates, and allocating from a finalizer leads to | |
| // InvalidMemoryError: https://issues.dlang.org/show_bug.cgi?id=21442 | |
| if (aa.length * SHRINK_DEN < aa.dim * SHRINK_NUM && !__ctfe && !gc_inFinalizer()) | |
| aa.shrink(); | |
| return true; | |
| } | |
| return false; | |
| } | |
| /// Remove all elements from AA. | |
| void _aaClear(K, V)(V[K] a) | |
| { | |
| auto aa = _toAA!(K, V)(a); | |
| if (!aa.empty) | |
| { | |
| aa.clear(); | |
| } | |
| } | |
| /// Rehash AA | |
| V[K] _aaRehash(K, V)(V[K] a) | |
| { | |
| auto aa = _toAA!(K, V)(a); | |
| if (!aa.empty) | |
| aa.resize(nextpow2(INIT_DEN * aa.length / INIT_NUM)); | |
| return a; | |
| } | |
| /// Return a GC allocated array of all values | |
| auto _aaValues(K, V)(inout V[K] a) | |
| { | |
| auto aa = _toAA!(K, V)(a); | |
| if (aa.empty) | |
| return null; | |
| static if (__traits(compiles, { V val = aa.buckets[0].entry.value; } )) | |
| V[] res; // if value has no const indirections | |
| else | |
| typeof([aa.buckets[0].entry.value]) res; // as mutable as it can get | |
| res = new typeof(res[0])[aa.length]; | |
| if (false) // never execute, but infer function attributes from this operation | |
| res ~= aa.buckets[0].entry.value; | |
| size_t i = 0; | |
| foreach (b; aa.buckets[aa.firstUsed .. $]) | |
| { | |
| if (!b.filled) | |
| continue; | |
| import core.lifetime; | |
| () @trusted { copyEmplace(b.entry.value, res[i++]); }(); | |
| } | |
| return res; | |
| } | |
| /// Return a GC allocated array of all keys | |
| auto _aaKeys(K, V)(inout V[K] a) | |
| { | |
| auto aa = _toAA!(K, V)(a); | |
| if (aa.empty) | |
| return null; | |
| static if (__traits(compiles, { K key = aa.buckets[0].entry.key; } )) | |
| K[] res; // if key has no const indirections | |
| else | |
| typeof([aa.buckets[0].entry.key]) res; // as mutable as it can get | |
| res = new typeof(res[0])[aa.length]; | |
| if (false) // never execute, but infer function attributes from this operation | |
| res ~= aa.buckets[0].entry.key; | |
| size_t i = 0; | |
| foreach (b; aa.buckets[aa.firstUsed .. $]) | |
| { | |
| if (!b.filled) | |
| continue; | |
| // res ~= b.entry.key; | |
| import core.lifetime; | |
| () @trusted { copyEmplace(b.entry.key, res[i++]); }(); | |
| } | |
| return res; | |
| } | |
| /// foreach opApply over all values | |
| /// Note: | |
| /// emulated by the compiler during CTFE | |
| int _d_aaApply(K, V, DG)(inout V[K] a, DG dg) | |
| { | |
| auto aa = () @trusted { return cast(AA!(K, V))_toAA!(K, V)(a); }(); | |
| if (aa.empty) | |
| return 0; | |
| foreach (b; aa.buckets) | |
| { | |
| if (!b.filled) | |
| continue; | |
| if (auto res = dg(b.entry.value)) | |
| return res; | |
| } | |
| return 0; | |
| } | |
| int _d_aaApply(K, V, DG)(shared V[K] a, DG dg) | |
| { | |
| return _d_aaApply!(K, V, DG)(cast(V[K]) a, dg); | |
| } | |
| int _d_aaApply(K, V, DG)(shared const V[K] a, DG dg) | |
| { | |
| return _d_aaApply!(K, V, DG)(cast(const V[K]) a, dg); | |
| } | |
| int _d_aaApply(K, V, DG)(immutable V[K] a, DG dg) | |
| { | |
| return _d_aaApply!(K, V, DG)(cast(const V[K]) a, dg); | |
| } | |
| /// foreach opApply over all key/value pairs | |
| /// Note: | |
| /// emulated by the compiler during CTFE | |
| int _d_aaApply2(K, V, DG)(inout V[K] a, DG dg) | |
| { | |
| auto aa = () @trusted { return cast(AA!(K, V))_toAA!(K, V)(a); }(); | |
| if (aa.empty) | |
| return 0; | |
| foreach (b; aa.buckets) | |
| { | |
| if (!b.filled) | |
| continue; | |
| if (auto res = dg(b.entry.key, b.entry.value)) | |
| return res; | |
| } | |
| return 0; | |
| } | |
| int _d_aaApply2(K, V, DG)(shared V[K] a, DG dg) | |
| { | |
| return _d_aaApply2!(K, V, DG)(cast(V[K]) a, dg); | |
| } | |
| int _d_aaApply2(K, V, DG)(shared const V[K] a, DG dg) | |
| { | |
| return _d_aaApply2!(K, V, DG)(cast(const V[K]) a, dg); | |
| } | |
| int _d_aaApply2(K, V, DG)(immutable V[K] a, DG dg) | |
| { | |
| return _d_aaApply2!(K, V, DG)(cast(const V[K]) a, dg); | |
| } | |
| /** Construct an associative array of type ti from corresponding keys and values. | |
| * Called for an AA literal `[k1:v1, k2:v2]`. | |
| * Params: | |
| * keys = array of keys | |
| * vals = array of values | |
| * Returns: | |
| * A new associative array opaque pointer, or null if `keys` is empty. | |
| */ | |
| Impl!(K, V)* _d_assocarrayliteralTX(K, V)(K[] keys, V[] vals) | |
| { | |
| assert(keys.length == vals.length); | |
| immutable length = keys.length; | |
| if (!length) | |
| return null; | |
| auto aa = new Impl!(K, V)(nextpow2(INIT_DEN * length / INIT_NUM)); | |
| size_t duplicates = 0; | |
| foreach (i; 0 .. length) | |
| { | |
| immutable hash = aa.calcHash(keys[i]); | |
| auto p = aa.findSlotLookup!K(hash, keys[i]); | |
| if (p) | |
| { | |
| static if (__traits(compiles, p.entry.value = vals[i])) // immutable? | |
| p.entry.value = vals[i]; | |
| else | |
| p.entry = _newEntry!(K, V)(keys[i], vals[i]); | |
| duplicates++; | |
| continue; | |
| } | |
| auto pi = aa.findSlotInsert(hash); | |
| p = &aa.buckets[pi]; | |
| p.hash = hash; | |
| p.entry = _newEntry!(K, V)(keys[i], vals[i]); // todo: move key and value? | |
| aa.firstUsed = min(aa.firstUsed, cast(uint)pi); | |
| } | |
| aa.used = cast(uint) (length - duplicates); | |
| return aa; | |
| } | |
| /// compares 2 AAs for equality | |
| bool _aaEqual(T : AA!(K, V), K, V)(scope T aa1, scope T aa2) | |
| { | |
| if (aa1 is aa2) | |
| return true; | |
| immutable len = aa1.length; | |
| if (len != aa2.length) | |
| return false; | |
| if (!len) // both empty | |
| return true; | |
| bool sameHash = aa1.hashFn == aa2.hashFn; // can be different if coming from template/rt | |
| // compare the entries | |
| foreach (b1; aa1.buckets[aa1.firstUsed .. $]) | |
| { | |
| if (!b1.filled) | |
| continue; | |
| hash_t hash = sameHash ? b1.hash : aa2.calcHash(b1.entry.key); | |
| auto pb2 = aa2.findSlotLookup!K(hash, b1.entry.key); | |
| if (pb2 is null || !pure_keyEqual!(V, V)(b1.entry.value, pb2.entry.value)) // rarely, inference on opEqual breaks builds here | |
| return false; | |
| } | |
| return true; | |
| } | |
| /// compares 2 AAs for equality (compiler hook) | |
| bool _d_aaEqual(K, V)(scope const V[K] a1, scope const V[K] a2) | |
| { | |
| scope aa1 = _toAA!(K, V)(a1); | |
| scope aa2 = _toAA!(K, V)(a2); | |
| return _aaEqual(aa1, aa2); | |
| } | |
| /// callback from TypeInfo_AssociativeArray.equals (ignore const for now) | |
| bool _aaOpEqual(K, V)(scope /* const */ AA!(K, V)* aa1, scope /* const */ AA!(K, V)* aa2) | |
| { | |
| return _aaEqual(*aa1, *aa2); | |
| } | |
| /// compute a hash callback from TypeInfo_AssociativeArray.xtoHash (ignore scope const for now) | |
| hash_t _aaGetHash(K, V)(/* scope const */ AA!(K, V)* paa) | |
| { | |
| const aa = *paa; | |
| if (aa.empty) | |
| return 0; | |
| size_t h; | |
| foreach (b; aa.buckets) | |
| { | |
| // use addition here, so that hash is independent of element order | |
| if (b.filled) | |
| h += hashOf(pure_hashOf!V(b.entry.value), pure_hashOf!K(b.entry.key)); | |
| } | |
| return h; | |
| } | |
| /** | |
| * _aaRange implements a ForwardRange | |
| */ | |
| struct AARange(K, V) | |
| { | |
| alias Key = substInout!K; | |
| alias Value = substInout!V; | |
| Impl!(Key, Value)* impl; | |
| size_t idx; | |
| alias impl this; | |
| } | |
| AARange!(K, V) _aaRange(K, V)(V[K] a) | |
| { | |
| auto aa = _toAA!(K, V)(a); | |
| if (!aa) | |
| return AARange!(K, V)(); | |
| foreach (i; aa.firstUsed .. aa.dim) | |
| { | |
| if (aa.buckets[i].filled) | |
| return AARange!(K, V)(aa, i); | |
| } | |
| return AARange!(K, V)(aa, aa.dim); | |
| } | |
| bool _aaRangeEmpty(K, V)(AARange!(K, V) r) | |
| { | |
| return r.impl is null || r.idx >= r.dim; | |
| } | |
| K* _aaRangeFrontKey(K, V)(AARange!(K, V) r) | |
| { | |
| assert(!_aaRangeEmpty(r)); | |
| if (r.idx >= r.dim) | |
| return null; | |
| auto entry = r.buckets[r.idx].entry; | |
| return entry is null ? null : &r.buckets[r.idx].entry.key; | |
| } | |
| V* _aaRangeFrontValue(K, V)(AARange!(K, V) r) | |
| { | |
| assert(!_aaRangeEmpty(r)); | |
| if (r.idx >= r.dim) | |
| return null; | |
| auto entry = r.buckets[r.idx].entry; | |
| return entry is null ? null : &r.buckets[r.idx].entry.value; | |
| } | |
| void _aaRangePopFront(K, V)(ref AARange!(K, V) r) | |
| { | |
| if (r.idx >= r.dim) return; | |
| for (++r.idx; r.idx < r.dim; ++r.idx) | |
| { | |
| if (r.buckets[r.idx].filled) | |
| break; | |
| } | |
| } | |
| // test postblit for AA literals | |
| unittest | |
| { | |
| import core.memory; | |
| static struct T | |
| { | |
| ubyte field; | |
| static size_t postblit, dtor; | |
| this(this) | |
| { | |
| ++postblit; | |
| } | |
| ~this() | |
| { | |
| ++dtor; | |
| } | |
| } | |
| T t; | |
| auto aa1 = [0 : t, 1 : t]; | |
| assert(T.dtor == 2 && T.postblit == 4); | |
| aa1[0] = t; | |
| assert(T.dtor == 3 && T.postblit == 5); | |
| T.dtor = 0; | |
| T.postblit = 0; | |
| auto aa2 = [0 : t, 1 : t, 0 : t]; // literal with duplicate key => value overwritten | |
| assert(T.dtor == 4 && T.postblit == 6); | |
| T.dtor = 0; | |
| T.postblit = 0; | |
| auto aa3 = [t : 0]; | |
| assert(T.dtor == 1 && T.postblit == 2); | |
| aa3[t] = 1; | |
| assert(T.dtor == 1 && T.postblit == 2); | |
| aa3.remove(t); | |
| assert(T.dtor == 1 && T.postblit == 2); | |
| aa3[t] = 2; | |
| assert(T.dtor == 1 && T.postblit == 3); | |
| // dtor will be called by GC finalizers | |
| aa1 = null; | |
| aa2 = null; | |
| aa3 = null; | |
| 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 == 7 && T.postblit == 3); | |
| } | |
| // create a binary-compatible AA structure that can be used directly as an | |
| // associative array. | |
| // NOTE: this must only be called during CTFE | |
| AA!(K, V) makeAA(K, V)(V[K] src) @trusted | |
| { | |
| assert(__ctfe, "makeAA Must only be called at compile time"); | |
| // keys and values are cheap operations in CTFE, so just reuse _d_assocarrayliteralTX | |
| auto impl = _d_assocarrayliteralTX!(K, V)(cast(K[])src.keys, cast(V[])src.values); | |
| auto aa = AA!(K, V)(impl); | |
| return aa; | |
| } | |
| unittest | |
| { | |
| static struct Foo | |
| { | |
| ubyte x; | |
| double d; | |
| } | |
| static int[Foo] utaa = [Foo(1, 2.0) : 5]; | |
| auto k = Foo(1, 2.0); | |
| // verify that getHash doesn't match hashOf for Foo | |
| assert(typeid(Foo).getHash(&k) != hashOf(k)); | |
| assert(utaa[Foo(1, 2.0)] == 5); | |
| } |