| /** | |
| Turn an Associative Array into a binary compatible struct for static initialization. | |
| This does not implement all the pieces of | |
| the associative array type in druntime, just enough to create an AA from an | |
| existing range of key/value pairs. | |
| 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 | |
| */ | |
| module core.internal.newaa; | |
| import core.memory; | |
| // 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_FILLED_MARK = size_t(1) << 8 * size_t.sizeof - 1; | |
| private struct Bucket | |
| { | |
| size_t hash; | |
| void *entry; | |
| } | |
| struct Impl | |
| { | |
| Bucket[] buckets; | |
| uint used; | |
| uint deleted; | |
| const(TypeInfo) entryTI; | |
| uint firstUsed; | |
| immutable uint keysz; | |
| immutable uint valsz; | |
| immutable uint valoff; | |
| Flags flags; | |
| size_t delegate(scope const void*) nothrow hashFn; | |
| enum Flags : ubyte | |
| { | |
| none = 0x0, | |
| keyHasPostblit = 0x1, | |
| hasPointers = 0x2, | |
| } | |
| } | |
| private struct AAShell | |
| { | |
| Impl *impl; | |
| } | |
| 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; | |
| } | |
| // create a binary-compatible AA structure that can be used directly as an | |
| // associative array. | |
| // NOTE: this must only be called during CTFE | |
| AAShell makeAA(K, V)(V[K] src) @trusted | |
| { | |
| assert(__ctfe, "makeAA Must only be called at compile time"); | |
| immutable srclen = src.length; | |
| assert(srclen <= uint.max); | |
| alias E = TypeInfo_AssociativeArray.Entry!(K, V); | |
| if (srclen == 0) | |
| return AAShell.init; | |
| // first, determine the size that would be used if we grew the bucket list | |
| // one element at a time using the standard AA algorithm. | |
| size_t dim = INIT_NUM_BUCKETS; | |
| while (srclen * GROW_DEN > dim * GROW_NUM) | |
| dim = dim * GROW_FAC; | |
| // used during runtime. | |
| typeof(Impl.hashFn) hashFn = (scope const void* val) { | |
| auto x = cast(K*)val; | |
| return hashOf(*x); | |
| }; | |
| Bucket[] buckets; | |
| // Allocate and fill the buckets | |
| if (__ctfe) | |
| buckets = new Bucket[dim]; | |
| else | |
| assert(0); | |
| assert(buckets.length >= dim); | |
| immutable mask = dim - 1; | |
| assert((dim & mask) == 0); // must be a power of 2 | |
| Bucket* findSlotInsert(immutable size_t hash) | |
| { | |
| for (size_t i = hash & mask, j = 1;; ++j) | |
| { | |
| if (buckets[i].hash == HASH_EMPTY) | |
| return &buckets[i]; | |
| i = (i + j) & mask; | |
| } | |
| } | |
| uint firstUsed = cast(uint) buckets.length; | |
| foreach (k, v; src) | |
| { | |
| immutable h = hashOf(k).mix | HASH_FILLED_MARK; | |
| auto location = findSlotInsert(h); | |
| immutable nfu = cast(uint) (location - &buckets[0]); | |
| if (nfu < firstUsed) | |
| firstUsed = nfu; | |
| *location = Bucket(h, new E(k, v)); | |
| } | |
| enum flags = () { | |
| import core.internal.traits; | |
| Impl.Flags flags; | |
| static if (__traits(hasPostblit, K)) | |
| flags |= flags.keyHasPostblit; | |
| static if (hasIndirections!E) | |
| flags |= flags.hasPointers; | |
| return flags; | |
| } (); | |
| // return the new implementation | |
| return AAShell(new Impl(buckets, cast(uint)srclen, 0, typeid(E), firstUsed, | |
| K.sizeof, V.sizeof, E.value.offsetof, flags, hashFn)); | |
| } | |
| 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); | |
| } |