2 * Implementation of associative arrays.
4 * Copyright: Copyright Digital Mars 2000 - 2015.
5 * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
6 * Authors: Martin Nowak
7 * Source: $(DRUNTIMESRC rt/_aaA.d)
11 /// AA version for debuggers, bump whenever changing the layout
12 extern (C
) immutable int _aaVersion
= 1;
14 import core
.memory
: GC
;
15 import core
.internal
.util
.math
: min
, max
;
18 private enum GROW_NUM
= 4;
19 private enum GROW_DEN
= 5;
21 private enum SHRINK_NUM
= 1;
22 private enum SHRINK_DEN
= 8;
24 private enum GROW_FAC
= 4;
25 // growing the AA doubles it's size, so the shrink threshold must be
26 // smaller than half the grow threshold to have a hysteresis
27 static assert(GROW_FAC
* SHRINK_NUM
* GROW_DEN
< GROW_NUM
* SHRINK_DEN
);
28 // initial load factor (for literals), mean of both thresholds
29 private enum INIT_NUM
= (GROW_DEN
* SHRINK_NUM
+ GROW_NUM
* SHRINK_DEN
) / 2;
30 private enum INIT_DEN
= SHRINK_DEN
* GROW_DEN
;
32 private enum INIT_NUM_BUCKETS
= 8;
33 // magic hash constants to distinguish empty, deleted, and filled buckets
34 private enum HASH_EMPTY
= 0;
35 private enum HASH_DELETED
= 0x1;
36 private enum HASH_FILLED_MARK
= size_t(1) << 8 * size_t
.sizeof
- 1;
44 private @property bool empty() const pure nothrow @nogc @safe
46 return impl
is null ||
!impl
.length
;
53 this(scope const TypeInfo_AssociativeArray ti
, size_t sz
= INIT_NUM_BUCKETS
) nothrow
55 keysz
= cast(uint) ti
.key
.tsize
;
56 valsz
= cast(uint) ti
.value
.tsize
;
57 buckets
= allocBuckets(sz
);
58 firstUsed
= cast(uint) buckets
.length
;
59 valoff
= cast(uint) talign(keysz
, ti
.value
.talign
);
60 hashFn
= &ti
.key
.getHash
;
62 import rt
.lifetime
: hasPostblit
, unqualify
;
64 if (hasPostblit(unqualify(ti
.key
)))
65 flags |
= Flags
.keyHasPostblit
;
66 if ((ti
.key
.flags | ti
.value
.flags
) & 1)
67 flags |
= Flags
.hasPointers
;
69 entryTI
= fakeEntryTI(this, ti
.key
, ti
.value
);
75 TypeInfo_Struct entryTI
;
79 immutable uint valoff
;
82 // function that calculates hash of a key. Set on creation
83 // the parameter is a pointer to the key.
84 size_t
delegate(scope const void*) nothrow hashFn
;
93 @property size_t
length() const pure nothrow @nogc @safe
95 assert(used
>= deleted
);
96 return used
- deleted
;
99 @property size_t
dim() const pure nothrow @nogc @safe
101 return buckets
.length
;
104 @property size_t
mask() const pure nothrow @nogc
109 // find the first slot to insert a value with hash
110 inout(Bucket
)* findSlotInsert(size_t hash
) inout pure nothrow @nogc
112 for (size_t i
= hash
& mask
, j
= 1;; ++j
)
114 if (!buckets
[i
].filled
)
121 inout(Bucket
)* findSlotLookup(size_t hash
, scope const void* pkey
, scope const TypeInfo keyti
) inout
123 for (size_t i
= hash
& mask
, j
= 1;; ++j
)
125 if (buckets
[i
].hash
== hash
&& keyti
.equals(pkey
, buckets
[i
].entry
))
127 else if (buckets
[i
].empty
)
133 void grow(scope const TypeInfo keyti
) pure nothrow
135 // If there are so many deleted entries, that growing would push us
136 // below the shrink threshold, we just purge deleted entries instead.
137 if (length
* SHRINK_DEN
< GROW_FAC
* dim
* SHRINK_NUM
)
140 resize(GROW_FAC
* dim
);
143 void shrink(scope const TypeInfo keyti
) pure nothrow
145 if (dim
> INIT_NUM_BUCKETS
)
146 resize(dim
/ GROW_FAC
);
149 void resize(size_t ndim
) pure nothrow
151 auto obuckets
= buckets
;
152 buckets
= allocBuckets(ndim
);
154 foreach (ref b
; obuckets
[firstUsed
.. $])
156 *findSlotInsert(b
.hash
) = b
;
161 GC
.free(obuckets
.ptr
); // safe to free b/c impossible to reference
164 void clear() pure nothrow @trusted
166 import core
.stdc
.string
: memset
;
167 // clear all data, but don't change bucket array length
168 memset(&buckets
[firstUsed
], 0, (buckets
.length
- firstUsed
) * Bucket
.sizeof
);
170 firstUsed
= cast(uint) dim
;
174 //==============================================================================
176 //------------------------------------------------------------------------------
178 private struct Bucket
180 private pure nothrow @nogc:
184 @property bool empty() const
186 return hash
== HASH_EMPTY
;
189 @property bool deleted() const
191 return hash
== HASH_DELETED
;
194 @property bool filled() const @safe
196 return cast(ptrdiff_t
) hash
< 0;
200 Bucket
[] allocBuckets(size_t dim
) @trusted pure nothrow
202 enum attr
= GC
.BlkAttr
.NO_INTERIOR
;
203 immutable sz
= dim
* Bucket
.sizeof
;
204 return (cast(Bucket
*) GC
.calloc(sz
, attr
))[0 .. dim
];
207 //==============================================================================
209 //------------------------------------------------------------------------------
211 private void* allocEntry(scope const Impl
* aa
, scope const void* pkey
)
213 import rt
.lifetime
: _d_newitemU
;
214 import core
.stdc
.string
: memcpy
, memset
;
216 immutable akeysz
= aa
.valoff
;
219 res
= _d_newitemU(aa
.entryTI
);
222 auto flags
= (aa
.flags
& Impl
.Flags
.hasPointers
) ?
0 : GC
.BlkAttr
.NO_SCAN
;
223 res
= GC
.malloc(akeysz
+ aa
.valsz
, flags
);
226 memcpy(res
, pkey
, aa
.keysz
); // copy key
227 memset(res
+ akeysz
, 0, aa
.valsz
); // zero value
232 package void entryDtor(void* p
, const TypeInfo_Struct
sti)
234 // key and value type info stored after the TypeInfo_Struct by tiEntry()
235 auto sizeti
= __traits(classInstanceSize
, TypeInfo_Struct
);
236 auto extra
= cast(const(TypeInfo
)*)(cast(void*) sti + sizeti
);
238 extra
[1].destroy(p
+ talign(extra
[0].tsize
, extra
[1].talign
));
241 private bool hasDtor(const TypeInfo ti
) pure nothrow
243 import rt
.lifetime
: unqualify
;
245 if (typeid(ti
) is typeid(TypeInfo_Struct
))
246 if ((cast(TypeInfo_Struct
) cast(void*) ti
).xdtor
)
248 if (typeid(ti
) is typeid(TypeInfo_StaticArray
))
249 return hasDtor(unqualify(ti
.next
));
254 private immutable(void)* getRTInfo(const TypeInfo ti
) pure nothrow
256 // classes are references
257 const isNoClass
= ti
&& typeid(ti
) !is typeid(TypeInfo_Class
);
258 return isNoClass ? ti
.rtInfo() : rtinfoHasPointers
;
261 // build type info for Entry with additional key and value fields
262 TypeInfo_Struct
fakeEntryTI(ref Impl aa
, const TypeInfo keyti
, const TypeInfo valti
) nothrow
264 import rt
.lifetime
: unqualify
;
266 auto kti
= unqualify(keyti
);
267 auto vti
= unqualify(valti
);
269 // figure out whether RTInfo has to be generated (indicated by rtisize > 0)
270 enum pointersPerWord
= 8 * (void*).sizeof
* (void*).sizeof
;
271 auto rtinfo
= rtinfoNoPointers
;
273 immutable(size_t
)* keyinfo
= void;
274 immutable(size_t
)* valinfo
= void;
275 if (aa
.flags
& Impl
.Flags
.hasPointers
)
277 // classes are references
278 keyinfo
= cast(immutable(size_t
)*) getRTInfo(keyti
);
279 valinfo
= cast(immutable(size_t
)*) getRTInfo(valti
);
281 if (keyinfo
is rtinfoHasPointers
&& valinfo
is rtinfoHasPointers
)
282 rtinfo
= rtinfoHasPointers
;
284 rtisize
= 1 + (aa
.valoff
+ aa
.valsz
+ pointersPerWord
- 1) / pointersPerWord
;
286 bool entryHasDtor
= hasDtor(kti
) ||
hasDtor(vti
);
287 if (rtisize
== 0 && !entryHasDtor
)
290 // save kti and vti after type info for struct
291 enum sizeti
= __traits(classInstanceSize
, TypeInfo_Struct
);
292 void* p
= GC
.malloc(sizeti
+ (2 + rtisize
) * (void*).sizeof
);
293 import core
.stdc
.string
: memcpy
;
295 memcpy(p
, __traits(initSymbol
, TypeInfo_Struct
).ptr
, sizeti
);
297 auto ti
= cast(TypeInfo_Struct
) p
;
298 auto extra
= cast(TypeInfo
*)(p
+ sizeti
);
299 extra
[0] = cast() kti
;
300 extra
[1] = cast() vti
;
302 static immutable tiMangledName
= "S2rt3aaA__T5EntryZ";
303 ti
.mangledName
= tiMangledName
;
305 ti
.m_RTInfo
= rtisize
> 0 ?
rtinfoEntry(aa
, keyinfo
, valinfo
, cast(size_t
*)(extra
+ 2), rtisize
) : rtinfo
;
306 ti
.m_flags
= ti
.m_RTInfo
is rtinfoNoPointers ?
cast(TypeInfo_Struct
.StructFlags
)0 : TypeInfo_Struct
.StructFlags
.hasPointers
;
308 // we don't expect the Entry objects to be used outside of this module, so we have control
309 // over the non-usage of the callback methods and other entries and can keep these null
310 // xtoHash, xopEquals, xopCmp, xtoString and xpostblit
311 immutable entrySize
= aa
.valoff
+ aa
.valsz
;
312 ti
.m_init
= (cast(ubyte*) null)[0 .. entrySize
]; // init length, but not ptr
316 // xdtor needs to be built from the dtors of key and value for the GC
317 ti
.xdtorti
= &entryDtor
;
318 ti
.m_flags |
= TypeInfo_Struct
.StructFlags
.isDynamicType
;
321 ti
.m_align
= cast(uint) max(kti
.talign
, vti
.talign
);
326 // build appropriate RTInfo at runtime
327 immutable(void)* rtinfoEntry(ref Impl aa
, immutable(size_t
)* keyinfo
,
328 immutable(size_t
)* valinfo
, size_t
* rtinfoData
, size_t rtinfoSize
) pure nothrow
330 enum bitsPerWord
= 8 * size_t
.sizeof
;
332 rtinfoData
[0] = aa
.valoff
+ aa
.valsz
;
333 rtinfoData
[1..rtinfoSize
] = 0;
335 void copyKeyInfo(string src
)()
338 size_t keybits
= aa
.keysz
/ (void*).sizeof
;
339 while (keybits
>= bitsPerWord
)
341 rtinfoData
[pos
] = mixin(src
);
342 keybits
-= bitsPerWord
;
346 rtinfoData
[pos
] = mixin(src
) & ((cast(size_t
) 1 << keybits
) - 1);
349 if (keyinfo
is rtinfoHasPointers
)
350 copyKeyInfo
!"~cast(size_t) 0"();
351 else if (keyinfo
!is rtinfoNoPointers
)
352 copyKeyInfo
!"keyinfo[pos]"();
354 void copyValInfo(string src
)()
356 size_t bitpos
= aa
.valoff
/ (void*).sizeof
;
358 size_t dstpos
= 1 + bitpos
/ bitsPerWord
;
359 size_t begoff
= bitpos
% bitsPerWord
;
360 size_t valbits
= aa
.valsz
/ (void*).sizeof
;
361 size_t endoff
= (bitpos
+ valbits
) % bitsPerWord
;
364 const bits
= bitsPerWord
- begoff
;
365 size_t s
= mixin(src
);
366 rtinfoData
[dstpos
] |
= s
<< begoff
;
367 if (begoff
> 0 && valbits
> bits
)
368 rtinfoData
[dstpos
+1] |
= s
>> bits
;
369 if (valbits
< bitsPerWord
)
371 valbits
-= bitsPerWord
;
376 rtinfoData
[dstpos
] &= ((cast(size_t
) 1 << endoff
) - 1);
379 if (valinfo
is rtinfoHasPointers
)
380 copyValInfo
!"~cast(size_t) 0"();
381 else if (valinfo
!is rtinfoNoPointers
)
382 copyValInfo
!"valinfo[pos]"();
384 return cast(immutable(void)*) rtinfoData
;
396 auto keyti
= typeid(K
);
397 auto valti
= typeid(V
);
398 auto valrti
= getRTInfo(valti
);
399 auto keyrti
= getRTInfo(keyti
);
401 auto impl
= new Impl(typeid(V
[K
]));
402 if (valrti
is rtinfoNoPointers
&& keyrti
is rtinfoNoPointers
)
404 assert(!(impl
.flags
& Impl
.Flags
.hasPointers
));
405 assert(impl
.entryTI
is null);
407 else if (valrti
is rtinfoHasPointers
&& keyrti
is rtinfoHasPointers
)
409 assert(impl
.flags
& Impl
.Flags
.hasPointers
);
410 assert(impl
.entryTI
is null);
414 auto rtInfo
= cast(size_t
*) impl
.entryTI
.rtInfo();
415 auto refInfo
= cast(size_t
*) typeid(Entry
).rtInfo();
416 assert(rtInfo
[0] == refInfo
[0]); // size
417 enum bytesPerWord
= 8 * size_t
.sizeof
* (void*).sizeof
;
418 size_t words
= (rtInfo
[0] + bytesPerWord
- 1) / bytesPerWord
;
419 foreach (i
; 0 .. words
)
420 assert(rtInfo
[1 + i
] == refInfo
[i
+ 1]);
424 test!(string
, string
);
425 test!(ubyte[16], Object
);
432 test!(string
, Small
);
438 ubyte[1024] moredata
;
443 //==============================================================================
445 //------------------------------------------------------------------------------
447 private size_t
talign(size_t tsize
, size_t algn
) @safe pure nothrow @nogc
449 immutable mask
= algn
- 1;
450 assert(!(mask
& algn
));
451 return (tsize
+ mask
) & ~mask
;
454 // mix hash to "fix" bad hash functions
455 private size_t
mix(size_t h
) @safe pure nothrow @nogc
457 // final mix function of MurmurHash2
465 private size_t
calcHash(scope const void *pkey
, scope const Impl
* impl
) nothrow
467 immutable hash
= impl
.hashFn(pkey
);
468 // highest bit is set to distinguish empty/deleted from filled buckets
469 return mix(hash
) | HASH_FILLED_MARK
;
472 private size_t
nextpow2(const size_t n
) pure nothrow @nogc
474 import core
.bitop
: bsr;
479 const isPowerOf2
= !((n
- 1) & n
);
480 return 1 << (bsr(n
) + !isPowerOf2
);
483 pure nothrow @nogc unittest
485 // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
486 foreach (const n
, const pow2
; [1, 1, 2, 4, 4, 8, 8, 8, 8, 16])
487 assert(nextpow2(n
) == pow2
);
490 //==============================================================================
491 // API Implementation
492 //------------------------------------------------------------------------------
494 /** Allocate associative array data.
495 * Called for `new SomeAA` expression.
497 * ti = TypeInfo for the associative array
499 * A new associative array.
501 extern (C
) Impl
* _aaNew(const TypeInfo_AssociativeArray ti
)
506 /// Determine number of entries in associative array.
507 extern (C
) size_t
_aaLen(scope const AA aa
) pure nothrow @nogc
509 return aa ? aa
.length
: 0;
512 /******************************
513 * Lookup *pkey in aa.
514 * Called only from implementation of (aa[key]) expressions when value is mutable.
516 * paa = associative array opaque pointer
517 * ti = TypeInfo for the associative array
519 * pkey = pointer to the key value
521 * if key was in the aa, a mutable pointer to the existing value.
522 * If key was not in the aa, a mutable pointer to newly inserted value which
523 * is set to all zeros
525 extern (C
) void* _aaGetY(scope AA
* paa
, const TypeInfo_AssociativeArray ti
,
526 const size_t valsz
, scope const void* pkey
)
529 return _aaGetX(paa
, ti
, valsz
, pkey
, found
);
532 /******************************
533 * Lookup *pkey in aa.
534 * Called only from implementation of require
536 * paa = associative array opaque pointer
537 * ti = TypeInfo for the associative array
539 * pkey = pointer to the key value
540 * found = true if the value was found
542 * if key was in the aa, a mutable pointer to the existing value.
543 * If key was not in the aa, a mutable pointer to newly inserted value which
544 * is set to all zeros
546 extern (C
) void* _aaGetX(scope AA
* paa
, const TypeInfo_AssociativeArray ti
,
547 const size_t valsz
, scope const void* pkey
, out bool found
)
549 // lazily alloc implementation
557 // get hash and bucket for key
558 immutable hash
= calcHash(pkey
, aa
);
560 // found a value => return it
561 if (auto p
= aa
.findSlotLookup(hash
, pkey
, ti
.key
))
564 return p
.entry
+ aa
.valoff
;
567 auto p
= aa
.findSlotInsert(hash
);
570 // check load factor and possibly grow
571 else if (++aa
.used
* GROW_DEN
> aa
.dim
* GROW_NUM
)
574 p
= aa
.findSlotInsert(hash
);
578 // update search cache and allocate entry
579 aa
.firstUsed
= min(aa
.firstUsed
, cast(uint)(p
- aa
.buckets
.ptr
));
581 p
.entry
= allocEntry(aa
, pkey
);
583 if (aa
.flags
& Impl
.Flags
.keyHasPostblit
)
585 import rt
.lifetime
: __doPostblit
, unqualify
;
587 __doPostblit(p
.entry
, aa
.keysz
, unqualify(ti
.key
));
589 // return pointer to value
590 return p
.entry
+ aa
.valoff
;
593 /******************************
594 * Lookup *pkey in aa.
595 * Called only from implementation of (aa[key]) expressions when value is not mutable.
597 * aa = associative array opaque pointer
598 * keyti = TypeInfo for the key
600 * pkey = pointer to the key value
602 * pointer to value if present, null otherwise
604 extern (C
) inout(void)* _aaGetRvalueX(inout AA aa
, scope const TypeInfo keyti
, const size_t valsz
,
605 scope const void* pkey
)
607 return _aaInX(aa
, keyti
, pkey
);
610 /******************************
611 * Lookup *pkey in aa.
612 * Called only from implementation of (key in aa) expressions.
614 * aa = associative array opaque pointer
615 * keyti = TypeInfo for the key
616 * pkey = pointer to the key value
618 * pointer to value if present, null otherwise
620 extern (C
) inout(void)* _aaInX(inout AA aa
, scope const TypeInfo keyti
, scope const void* pkey
)
625 immutable hash
= calcHash(pkey
, aa
);
626 if (auto p
= aa
.findSlotLookup(hash
, pkey
, keyti
))
627 return p
.entry
+ aa
.valoff
;
631 /// Delete entry scope const AA, return true if it was present
632 extern (C
) bool _aaDelX(AA aa
, scope const TypeInfo keyti
, scope const void* pkey
)
637 immutable hash
= calcHash(pkey
, aa
);
638 if (auto p
= aa
.findSlotLookup(hash
, pkey
, keyti
))
641 p
.hash
= HASH_DELETED
;
645 // `shrink` reallocates, and allocating from a finalizer leads to
646 // InvalidMemoryError: https://issues.dlang.org/show_bug.cgi?id=21442
647 if (aa
.length
* SHRINK_DEN
< aa
.dim
* SHRINK_NUM
&& !GC
.inFinalizer())
655 /// Remove all elements from AA.
656 extern (C
) void _aaClear(AA aa
) pure nothrow @safe
665 extern (C
) void* _aaRehash(AA
* paa
, scope const TypeInfo keyti
) pure nothrow
669 aa
.resize(nextpow2(INIT_DEN
* aa
.length
/ INIT_NUM
));
673 /// Return a GC allocated array of all values
674 extern (C
) inout(void[]) _aaValues(inout AA aa
, const size_t keysz
, const size_t valsz
,
675 const TypeInfo tiValueArray
) pure nothrow
680 import rt
.lifetime
: _d_newarrayU
;
682 auto res
= _d_newarrayU(tiValueArray
, aa
.length
).ptr
;
685 immutable off
= aa
.valoff
;
686 foreach (b
; aa
.buckets
[aa
.firstUsed
.. $])
690 pval
[0 .. valsz
] = b
.entry
[off
.. valsz
+ off
];
693 // postblit is done in object.values
694 return (cast(inout(void)*) res
)[0 .. aa
.length
]; // fake length, return number of elements
697 /// Return a GC allocated array of all keys
698 extern (C
) inout(void[]) _aaKeys(inout AA aa
, const size_t keysz
, const TypeInfo tiKeyArray
) pure nothrow
703 import rt
.lifetime
: _d_newarrayU
;
705 auto res
= _d_newarrayU(tiKeyArray
, aa
.length
).ptr
;
708 foreach (b
; aa
.buckets
[aa
.firstUsed
.. $])
712 pkey
[0 .. keysz
] = b
.entry
[0 .. keysz
];
715 // postblit is done in object.keys
716 return (cast(inout(void)*) res
)[0 .. aa
.length
]; // fake length, return number of elements
719 // opApply callbacks are extern(D)
720 extern (D
) alias dg_t
= int delegate(void*);
721 extern (D
) alias dg2_t
= int delegate(void*, void*);
723 /// foreach opApply over all values
724 extern (C
) int _aaApply(AA aa
, const size_t keysz
, dg_t dg
)
729 immutable off
= aa
.valoff
;
730 foreach (b
; aa
.buckets
)
734 if (auto res
= dg(b
.entry
+ off
))
740 /// foreach opApply over all key/value pairs
741 extern (C
) int _aaApply2(AA aa
, const size_t keysz
, dg2_t dg
)
746 immutable off
= aa
.valoff
;
747 foreach (b
; aa
.buckets
)
751 if (auto res
= dg(b
.entry
, b
.entry
+ off
))
757 /** Construct an associative array of type ti from corresponding keys and values.
758 * Called for an AA literal `[k1:v1, k2:v2]`.
760 * ti = TypeInfo for the associative array
761 * keys = array of keys
762 * vals = array of values
764 * A new associative array opaque pointer, or null if `keys` is empty.
766 extern (C
) Impl
* _d_assocarrayliteralTX(const TypeInfo_AssociativeArray ti
, void[] keys
,
769 assert(keys
.length
== vals
.length
);
771 immutable keysz
= ti
.key
.tsize
;
772 immutable valsz
= ti
.value
.tsize
;
773 immutable length
= keys
.length
;
778 auto aa
= new Impl(ti
, nextpow2(INIT_DEN
* length
/ INIT_NUM
));
780 void* pkey
= keys
.ptr
;
781 void* pval
= vals
.ptr
;
782 immutable off
= aa
.valoff
;
783 uint actualLength
= 0;
784 foreach (_
; 0 .. length
)
786 immutable hash
= calcHash(pkey
, aa
);
788 auto p
= aa
.findSlotLookup(hash
, pkey
, ti
.key
);
791 p
= aa
.findSlotInsert(hash
);
793 p
.entry
= allocEntry(aa
, pkey
); // move key, no postblit
794 aa
.firstUsed
= min(aa
.firstUsed
, cast(uint)(p
- aa
.buckets
.ptr
));
797 else if (aa
.entryTI
&& hasDtor(ti
.value
))
799 // destroy existing value before overwriting it
800 ti
.value
.destroy(p
.entry
+ off
);
802 // set hash and blit value
803 auto pdst
= p
.entry
+ off
;
804 pdst
[0 .. valsz
] = pval
[0 .. valsz
]; // move value, no postblit
809 aa
.used
= actualLength
;
813 /// compares 2 AAs for equality
814 extern (C
) int _aaEqual(scope const TypeInfo tiRaw
, scope const AA aa1
, scope const AA aa2
)
819 immutable len
= _aaLen(aa1
);
820 if (len
!= _aaLen(aa2
))
823 if (!len
) // both empty
826 import rt
.lifetime
: unqualify
;
828 auto uti
= unqualify(tiRaw
);
829 auto ti
= *cast(TypeInfo_AssociativeArray
*)&uti
;
830 // compare the entries
831 immutable off
= aa1
.valoff
;
832 foreach (b1
; aa1
.buckets
)
836 auto pb2
= aa2
.findSlotLookup(b1
.hash
, b1
.entry
, ti
.key
);
837 if (pb2
is null ||
!ti
.value
.equals(b1
.entry
+ off
, pb2
.entry
+ off
))
844 extern (C
) hash_t
_aaGetHash(scope const AA
* paa
, scope const TypeInfo tiRaw
) nothrow
851 import rt
.lifetime
: unqualify
;
853 auto uti
= unqualify(tiRaw
);
854 auto ti
= *cast(TypeInfo_AssociativeArray
*)&uti
;
855 immutable off
= aa
.valoff
;
856 auto keyHash
= &ti
.key
.getHash
;
857 auto valHash
= &ti
.value
.getHash
;
860 foreach (b
; aa
.buckets
)
862 // use addition here, so that hash is independent of element order
864 h
+= hashOf(valHash(b
.entry
+ off
), keyHash(b
.entry
));
871 * _aaRange implements a ForwardRange
880 extern (C
) pure nothrow @nogc @safe
882 Range
_aaRange(return scope AA aa
)
887 foreach (i
; aa
.firstUsed
.. aa
.dim
)
889 if (aa
.buckets
[i
].filled
)
892 return Range(aa
, aa
.dim
);
895 bool _aaRangeEmpty(Range r
)
897 return r
.impl
is null || r
.idx
>= r
.dim
;
900 void* _aaRangeFrontKey(Range r
)
902 assert(!_aaRangeEmpty(r
));
905 return r
.buckets
[r
.idx
].entry
;
908 void* _aaRangeFrontValue(Range r
)
910 assert(!_aaRangeEmpty(r
));
914 auto entry
= r
.buckets
[r
.idx
].entry
;
915 return entry
is null ?
917 (() @trusted { return entry
+ r
.valoff
; } ());
920 void _aaRangePopFront(ref Range r
)
922 if (r
.idx
>= r
.dim
) return;
923 for (++r
.idx
; r
.idx
< r
.dim
; ++r
.idx
)
925 if (r
.buckets
[r
.idx
].filled
)
931 // Most tests are now in test_aa.d
933 // test postblit for AA literals
939 static size_t postblit
, dtor
;
952 auto aa1
= [0 : t
, 1 : t
];
953 assert(T
.dtor
== 0 && T
.postblit
== 2);
955 assert(T
.dtor
== 1 && T
.postblit
== 3);
960 auto aa2
= [0 : t
, 1 : t
, 0 : t
]; // literal with duplicate key => value overwritten
961 assert(T
.dtor
== 1 && T
.postblit
== 3);
967 assert(T
.dtor
== 0 && T
.postblit
== 1);
969 assert(T
.dtor
== 0 && T
.postblit
== 1);
971 assert(T
.dtor
== 0 && T
.postblit
== 1);
973 assert(T
.dtor
== 0 && T
.postblit
== 2);
975 // dtor will be called by GC finalizers
979 GC
.runFinalizers((cast(char*)(&entryDtor
))[0 .. 1]);
980 assert(T
.dtor
== 6 && T
.postblit
== 2);
983 // Ensure the newaa struct layout (used for static initialization) is in sync
986 import newaa
= core
.internal
.newaa
;
987 static assert(newaa
.Impl
.sizeof
== Impl
.sizeof
);
988 // ensure compatible types and offsets
989 static foreach (i
; 0 .. Impl
.tupleof
.length
)
991 // for bucket array and Flags, we "compatible" types, not exactly the same types.
992 static if (__traits(identifier
, Impl
.tupleof
[i
]) == "buckets"
993 ||
__traits(identifier
, Impl
.tupleof
[i
]) == "flags")
994 static assert(Impl
.tupleof
[i
].sizeof
== newaa
.Impl
.tupleof
[i
].sizeof
);
996 static assert(is(typeof(Impl
.tupleof
[i
]) == typeof(newaa
.Impl
.tupleof
[i
])));
998 static assert(Impl
.tupleof
[i
].offsetof
== newaa
.Impl
.tupleof
[i
].offsetof
);