1 module cachetools.containers.hashmap;
2 
3 import std.traits;
4 import std.experimental.logger;
5 import std.format;
6 import std.typecons;
7 
8 import core.memory;
9 import core.bitop;
10 
11 private import std.experimental.allocator;
12 private import std.experimental.allocator.mallocator: Mallocator;
13 
14 private import cachetools.internal;
15 private import cachetools.hash;
16 private import cachetools.containers.lists;
17 
18 class KeyNotFound: Exception
19 {
20     this(string msg = "key not found") @safe
21     {
22         super(msg);
23     }
24 }
25 ///
26 /// Return true if it is worth to store values inline in hash table
27 /// V footprint should be small enough
28 ///
29 package bool SmallValueFootprint(V)() {
30     import std.traits;
31     static if (
32            isNumeric!V
33         || isSomeString!V
34         || isSomeChar!V
35         || isPointer!V )
36     {
37             return true;
38     }
39     else static if (
40            is(V == struct) && V.sizeof <= (void*).sizeof )
41     {
42             return true;
43     }
44     else static if (
45             is(V == class ) && __traits(classInstanceSize, V) <= (void*).sizeof)
46     {
47         return true;
48     }
49     else
50         return false;
51 }
52 
53 private bool keyEquals(K)(const K a, const K b)
54 {
55     static if ( is(K==class) )
56     {
57         if (a is b)
58         {
59             return true;
60         }
61         if (a is null || b is null)
62         {
63             return false;
64         }
65         return a.opEquals(b);
66     }
67     else
68     {
69         return a == b;
70     }
71 }
72 /*
73 dub run -b release --compiler ldc2
74 Running ./cachetools 
75 AA!(int,int)    [137 ms, 348 μs, and 6 hnsecs]
76 OA!(int,int)    [77 ms, 913 μs, and 1 hnsec]
77 OA!(int,int) GC [80 ms, 571 μs, and 3 hnsecs]
78 ---
79 AA large        [122 ms and 157 μs]
80 OA large        [177 ms, 167 μs, and 4 hnsecs]
81 OA large GC     [125 ms, 771 μs, and 8 hnsecs]
82 ---
83 AA largeClass   [183 ms, 366 μs, and 3 hnsecs]
84 OA largeClass   [124 ms, 617 μs, and 3 hnsecs]
85 OA largeClassGC [106 ms, 407 μs, and 7 hnsecs]
86 ---
87 */
88 
89 struct HashMap(K, V, Allocator = Mallocator) {
90 
91     enum initial_buckets_num = 32;
92     enum grow_factor = 2;
93     enum inlineValues = true;//SmallValueFootprint!V()|| is(V==class);
94 
95     //static if ( is(K==class) && (is(K==immutable) || is(K==const)) )
96     //{
97     //    alias StoredKeyType = Rebindable!K;
98     //}
99     //else static if (is(K==struct) && (is(K==immutable) || is(K==const)) )
100     //{
101     //    alias StoredKeyType = Unqual!K;
102     //}
103     //else
104     //{
105     //    alias StoredKeyType = K;
106     //}
107 
108     alias StoredKeyType   = StoredType!K;
109     alias StoredValueType = StoredType!V;
110 
111     private {
112         alias   allocator = Allocator.instance;
113         static if (hash_t.sizeof == 8)
114         {
115             enum    EMPTY_HASH =     0x00_00_00_00_00_00_00_00;
116             enum    DELETED_HASH =   0x10_00_00_00_00_00_00_00;
117             enum    ALLOCATED_HASH = 0x20_00_00_00_00_00_00_00;
118             enum    TYPE_MASK =      0xF0_00_00_00_00_00_00_00;
119             enum    HASH_MASK =      0x0F_FF_FF_FF_FF_FF_FF_FF;
120         }
121         else static if (hash_t.sizeof == 4)
122         {
123             enum    EMPTY_HASH =     0x00_00_00_00;
124             enum    DELETED_HASH =   0x10_00_00_00;
125             enum    ALLOCATED_HASH = 0x20_00_00_00;
126             enum    TYPE_MASK =      0xF0_00_00_00;
127             enum    HASH_MASK =      0x0F_FF_FF_FF;
128         }
129 
130         struct _Bucket {
131             hash_t          hash;
132             StoredKeyType   key;
133             static if (inlineValues)
134             {
135                 StoredValueType   value;
136             }
137             else
138             {
139                 V*  value_ptr;
140             }
141             string toString() const {
142                 import std.format;
143                 static if (inlineValues) {
144                     return "%s, hash: %0x,key: %s, value: %s".format(
145                         [EMPTY_HASH:"free", DELETED_HASH:"deleted", ALLOCATED_HASH:"allocated"][cast(long   )(hash & TYPE_MASK)],
146                         hash, key, value);
147                 } else {
148                     return "%s, hash: %0x, key: %s, value: %s".format(
149                         [EMPTY_HASH:"free", DELETED_HASH:"deleted", ALLOCATED_HASH:"allocated"][cast(long)(hash & TYPE_MASK)],
150                         hash, key,
151                         value_ptr !is null?  format("%s", *value_ptr) : "-");
152                 }
153             }
154         }
155 
156         _Bucket[]   _buckets;
157         int         _buckets_num;
158         int         _mask;
159         int         _allocated;
160         int         _deleted;
161         int         _empty;
162     }
163 
164     ~this() @safe {
165         if ( _buckets_num > 0 )
166         {
167             static if ( !inlineValues )
168             {
169                 for(int i=0;i<_buckets_num;i++)
170                 {
171                     auto t = _buckets[i].hash;
172                     if ( t <= DELETED_HASH )
173                     {
174                         continue;
175                     }
176                     (() @trusted {dispose(allocator, _buckets[i].value_ptr);})();
177                 }
178             }
179             (() @trusted {
180                 GC.removeRange(_buckets.ptr);
181                 dispose(allocator, _buckets);
182             })();
183         }
184     }
185     invariant {
186         assert(_allocated>=0 && _deleted>=0 && _empty >= 0);
187         assert(_allocated + _deleted + _empty == _buckets_num, "a:%s + d:%s + e:%s != total: %s".format(_allocated, _deleted,  _empty, _buckets_num));
188     }
189     ///
190     /// Find any unallocated bucket starting from start_index (inclusive)
191     /// Returns index on success or hash_t.max on fail
192     ///
193     private hash_t findEmptyIndex(const hash_t start_index) pure const @safe @nogc
194     in
195     {
196         assert(start_index < _buckets_num);
197     }
198     do {
199         hash_t index = start_index;
200 
201         do {
202             () @nogc {debug(cachetools) tracef("test index %d for non-ALLOCATED", index);}();
203             if ( _buckets[index].hash < ALLOCATED_HASH )
204             {
205                 return index;
206             }
207             index = (index + 1) & _mask;
208         } while(index != start_index);
209         return hash_t.max;
210     }
211     ///
212     /// Find allocated bucket for given key and computed hash starting from start_index
213     /// Returns: index if bucket found or hash_t.max otherwise
214     ///
215     /// Inherits @nogc and from K opEquals()
216     ///
217     private hash_t findEntryIndex(const hash_t start_index, const hash_t hash, in K key) pure const @safe
218     in
219     {
220         assert(hash < DELETED_HASH);        // we look for real hash
221         assert(start_index < _buckets_num); // start position inside array
222     }
223     do {
224         hash_t index = start_index;
225 
226         do {
227             immutable h = _buckets[index].hash;
228 
229             () @nogc @trusted {debug(cachetools) tracef("test entry index %d (%s) for key %s", index, _buckets[index].toString, key);}();
230 
231             if ( h == EMPTY_HASH ) {
232                 break;
233             }
234 
235             if ( h >= ALLOCATED_HASH && (h & HASH_MASK) == hash && keyEquals(_buckets[index].key, key) ) {
236                 //() @nogc @trusted {debug(cachetools) tracef("test entry index %d for key %s - success", index, key);}();
237                 return index;
238             }
239             index = (index + 1) & _mask;
240         } while(index != start_index);
241         return hash_t.max;
242     }
243 
244     ///
245     /// Find place where we can insert(DELETED or EMPTY bucket) or update existent (ALLOCATED)
246     /// bucket for key k and precomputed hash starting from start_index
247     ///
248     ///
249     /// Inherits @nogc from K opEquals()
250     ///
251     private hash_t findUpdateIndex(const hash_t start_index, const hash_t computed_hash, in K key) pure const @safe
252     in 
253     {
254         assert(computed_hash < DELETED_HASH);
255         assert(start_index < _buckets_num);
256     }
257     do {
258         hash_t index = start_index;
259 
260         do {
261             immutable h = _buckets[index].hash;
262 
263             () @nogc @trusted {debug(cachetools) tracef("test update index %d (%s) for key %s", index, _buckets[index], key);}();
264 
265             if ( h <= DELETED_HASH ) // empty or deleted
266             {
267                 () @nogc @trusted {debug(cachetools) tracef("test update index %d (%s) for key %s - success", index, _buckets[index], key);}();
268                 return index;
269             }
270             assert((h & TYPE_MASK) == ALLOCATED_HASH);
271             if ( (h & HASH_MASK) == computed_hash && keyEquals(_buckets[index].key, key) ) 
272             {
273                 () @nogc @trusted {debug(cachetools) tracef("test update index %d (%s) for key %s - success", index, _buckets[index], key);}();
274                 return index;
275             }
276             index = (index + 1) & _mask;
277         } while(index != start_index);
278         return hash_t.max;
279     }
280     ///
281     /// Find unallocated entry in the buckets slice
282     /// We use this function during resize() only.
283     ///
284     private long findEmptyIndexExtended(const hash_t start_index, in ref _Bucket[] buckets, int new_mask) pure const @safe @nogc
285     in
286     {
287         assert(start_index < buckets.length);
288     }
289     do
290     {
291         hash_t index = start_index;
292 
293         do {
294             immutable t = buckets[index].hash;
295 
296             () @nogc @trusted {debug(cachetools) tracef("test empty index %d (%s)", 
297                 index, buckets[index]);}();
298             
299             if ( t <= DELETED_HASH ) // empty or deleted
300             {
301                 return index;
302             }
303 
304             index = (index + 1) & new_mask;
305         } while(index != start_index);
306         return hash_t.max;
307     }
308 
309     private bool tooMuchDeleted() pure const @safe @nogc {
310         //
311         // _deleted > _buckets_num / 8
312         //
313         return _deleted << 3 > _buckets_num;
314     }
315 
316     private bool tooHighLoad() pure const @safe @nogc {
317         //
318         // _allocated/_buckets_num > 0.8
319         // 5 * allocated > 4 * buckets_num
320         //
321         return _allocated + (_allocated << 2) > _buckets_num << 2;
322     }
323 
324     private void doResize(int dest) @safe {
325         immutable _new_buckets_num = dest;
326         immutable _new_mask = dest - 1;
327         _Bucket[] _new_buckets = makeArray!(_Bucket)(allocator, _new_buckets_num);
328 
329         () @trusted {GC.addRange(_new_buckets.ptr, _new_buckets_num * _Bucket.sizeof);}();
330 
331         // iterate over entries
332 
333         debug(cachetools)
334         {
335             () @nogc {tracef("start resizing: old loadfactor: %s", (1.0*_allocated) / _buckets_num);}();
336         }
337 
338         for(int i=0;i<_buckets_num;i++) {
339             immutable hash_t h = _buckets[i].hash;
340             if ( h < ALLOCATED_HASH ) { // empty or deleted
341                 continue;
342             }
343 
344             immutable hash_t start_index = h & _new_mask;
345             immutable new_position = findEmptyIndexExtended(start_index, _new_buckets, _new_mask);
346 
347             debug(cachetools) () @nogc {tracef("old hash: %0x, old pos: %d, new_pos: %d", h, i, new_position);}();
348 
349             assert( new_position >= 0 );
350             assert( _new_buckets[cast(hash_t)new_position].hash  == EMPTY_HASH );
351 
352             _new_buckets[cast(hash_t)new_position] = _buckets[i];
353         }
354         (() @trusted {
355             GC.removeRange(_buckets.ptr);
356             dispose(allocator, _buckets);
357         })();
358         _buckets = _new_buckets;
359         _buckets_num = _new_buckets_num;
360         assert(_popcnt(_buckets_num) == 1, "Buckets number must be power of 2");
361         _mask = _buckets_num - 1;
362         _deleted = 0;
363         _empty = _buckets_num - _allocated;
364 
365         debug(cachetools)
366         {
367             () @nogc {tracef("resizing done: new loadfactor: %s", (1.0*_allocated) / _buckets_num);}();
368         }
369     }
370 
371     ///
372     /// Lookup methods
373     ///
374     V* opBinaryRight(string op)(in K k) @safe if (op == "in")
375     {
376 
377         if ( _buckets_num == 0 ) return null;
378 
379         immutable computed_hash = hash_function(k) & HASH_MASK;
380         immutable start_index = computed_hash & _mask;
381         immutable lookup_index = findEntryIndex(start_index, computed_hash, k);
382         if ( lookup_index == hash_t.max) {
383             return null;
384         }
385         static if ( inlineValues )
386         {
387             static if ( is(V==StoredValueType) )
388             {
389                 return &_buckets[lookup_index].value;
390             }
391             else
392             {
393                 V* r = () @trusted {return cast(V*)&_buckets[lookup_index].value;}();
394                 return r;
395             }
396         }
397         else
398         {
399             return _buckets[lookup_index].value_ptr;
400         }
401     }
402 
403     ref V getOrAdd(T)(K k, T defaultValue) @safe
404     {
405         V* v = k in this;
406         if ( v )
407         {
408             return *v;
409         }
410         static if (isAssignable!(V, T))
411         {
412             return *put(k, defaultValue);
413         }
414         else static if ( isCallable!T && isAssignable!(V, ReturnType!T))
415         {
416             return *put(k, defaultValue());
417         }
418         else
419         {
420             static assert(0, "what?");
421         }
422     }
423 
424     alias require = getOrAdd;
425 
426     /// Attention: this can't return ref as default value can be rvalue
427     V get(T)(K k, T defaultValue) @safe
428     {
429         V* v = k in this;
430         if ( v )
431         {
432             return *v;
433         }
434         static if (isAssignable!(V, T))
435         {
436             return defaultValue;
437         }
438         else static if ( isCallable!T && isAssignable!(V, ReturnType!T))
439         {
440             return defaultValue();
441         }
442         else
443         {
444             static assert(0, "You must call 'get' with default value of HashMap 'value' type, or with callable, returning HashMap 'value'");
445         }
446     }
447 
448     ///
449     /// Attention: you can't use this method in @nogc code.
450     /// Usual aa[key] method.
451     /// Throws exception if key not found
452     ///
453     ref V opIndex(in K k) @safe
454     {
455         V* v = k in this;
456         if ( v !is null )
457         {
458             return *v;
459         }
460         throw new KeyNotFound();
461     }
462 
463     ///
464     /// Modifiers
465     ///
466     void opIndexAssign(V v, K k) @safe
467     {
468         put(k, v);
469     }
470     ///
471     /// put pair (k,v) into hash.
472     /// it must be @safe, it inherits @nogc properties from K and V
473     /// It can resize hashtable it is overloaded or has too much deleted entries
474     ///
475     V* put(K k, V v) @safe
476     out
477     {
478         assert(__result !is null);
479     }
480     do {
481         if ( !_buckets_num ) {
482             _buckets_num = _empty = initial_buckets_num;
483             assert(_popcnt(_buckets_num) == 1, "Buckets number must be power of 2");
484             _mask = _buckets_num - 1;
485             _buckets = makeArray!(_Bucket)(allocator, _buckets_num);
486             () @trusted {GC.addRange(_buckets.ptr, _buckets_num * _Bucket.sizeof);}();
487         }
488 
489         () @nogc @trusted {debug(cachetools) tracef("put k: %s, v: %s", k,v);}();
490 
491         if ( tooHighLoad ) {
492             doResize(grow_factor * _buckets_num);
493         }
494 
495         V* r; //result
496         immutable computed_hash = hash_function(k) & HASH_MASK;
497         immutable start_index = computed_hash & _mask;
498         immutable placement_index = findUpdateIndex(start_index, computed_hash, k);
499         assert(placement_index >= 0);
500         _Bucket* bucket = &_buckets[placement_index];
501         immutable h = bucket.hash;
502 
503         debug(cachetools) () @nogc @trusted {tracef("start_index: %d, placement_index: %d", start_index, placement_index);}();
504 
505         if ( h < ALLOCATED_HASH )
506         {
507             _allocated++;
508             _empty--;
509         }
510         static if ( inlineValues )
511         {
512             debug(cachetools) () @nogc @trusted {tracef("place inline buckets[%d] '%s'='%s'", placement_index, k, v);}();
513             bucket.value = v;
514             static if ( is(V==StoredValueType) )
515             {
516                 r = &bucket.value;
517             }
518             else
519             {
520                 () @trusted {r = cast(V*)&bucket.value;}();
521             }
522         }
523         else
524         {
525             debug(cachetools) () @nogc @trusted {tracef("place with allocation buckets[%d] '%s'='%s'", placement_index, k, v);}();
526             if ( (bucket.hash & TYPE_MASK) == ALLOCATED_HASH )
527             {
528                 // we just replace what we already allocated
529                 r = (bucket.value_ptr);
530                 *r = v;
531             }
532             else
533             {
534                 r = bucket.value_ptr = make!(V)(allocator);
535                 *r = v;
536             }
537         }
538         bucket.hash = computed_hash | ALLOCATED_HASH;
539         bucket.key = k;
540         return r;
541     }
542 
543     bool remove(K k) @safe {
544 
545         if ( tooMuchDeleted ) {
546             // do not shrink, just compact table
547             doResize(_buckets_num);
548         }
549 
550 
551         debug(cachetools) () @nogc @trusted {tracef("remove k: %s", k);}();
552 
553         immutable computed_hash = hash_function(k) & HASH_MASK;
554         immutable start_index = computed_hash & _mask;
555         immutable lookup_index = findEntryIndex(start_index, computed_hash, k);
556         if ( lookup_index == hash_t.max )
557         {
558             // nothing to remove
559             return false;
560         }
561 
562         assert((_buckets[lookup_index].hash & TYPE_MASK) == ALLOCATED_HASH, "tried to remove non allocated bucket");
563 
564         static if ( inlineValues )
565         {
566             // what we have to do with removed values XXX?
567         }
568         else
569         {
570             // what we have to do with removed values XXX?
571             // free space
572             (() @trusted {dispose(allocator, _buckets[lookup_index].value_ptr);})();
573             _buckets[lookup_index].value_ptr = null;
574         }
575 
576         _allocated--;
577         immutable next_index = (lookup_index + 1) & _mask;
578         // if next bucket is EMPTY, then we can convert all DELETED buckets down staring from current to EMPTY buckets
579         if ( _buckets[next_index].hash == EMPTY_HASH )
580         {
581             _empty++;
582             _buckets[lookup_index].hash = EMPTY_HASH;
583             auto free_index = (lookup_index - 1) & _mask;
584             while (free_index != lookup_index) {
585                 if ( _buckets[free_index].hash != DELETED_HASH ) {
586                     break;
587                 }
588                 _buckets[free_index].hash = EMPTY_HASH;
589                 _deleted--;
590                 _empty++;
591                 free_index = (free_index - 1) & _mask;
592             }
593             assert(free_index != lookup_index, "table full of deleted buckets?");
594         }
595         else
596         {
597             _buckets[lookup_index].hash = DELETED_HASH;
598             _deleted++;
599         }
600         return true;
601     }
602     void clear() @safe 
603     {
604         static if ( !inlineValues )
605         {
606            // dispose every element
607            foreach(k; byKey)
608            {
609                remove(k);
610            }
611         }
612         (() @trusted {
613             GC.removeRange(_buckets.ptr);
614             dispose(allocator, _buckets);
615         })();
616         _buckets = makeArray!(_Bucket)(allocator, _buckets_num);
617         _allocated = _deleted = 0;
618         _empty = _buckets_num;
619     }
620     auto length() const pure nothrow @nogc @safe
621     {
622         return _allocated;
623     }
624 
625     auto size() const pure nothrow @nogc @safe
626     {
627         return _buckets_num;
628     }
629 
630     auto byKey() pure @safe @nogc
631     {
632         struct _kvRange {
633             int         _pos;
634             ulong       _buckets_num;
635             _Bucket[]   _buckets;
636             this(_Bucket[] _b)
637             {
638                 _buckets = _b;
639                 _buckets_num = _b.length;
640                 _pos = 0;
641                 while( _pos < _buckets_num  && _buckets[_pos].hash < ALLOCATED_HASH )
642                 {
643                     _pos++;
644                 }
645             }
646             bool empty() const pure nothrow @safe @nogc
647             {
648                 return _pos == _buckets_num;
649             }
650             K front()
651             {
652                 return _buckets[_pos].key;
653             }
654             void popFront() pure nothrow @safe @nogc
655             {
656                 _pos++;
657                 while( _pos < _buckets_num && _buckets[_pos].hash <  ALLOCATED_HASH )
658                 {
659                     _pos++;
660                 }
661             }
662         }
663         return _kvRange(_buckets);
664     }
665 
666     auto byValue() pure @safe
667     {
668         struct _kvRange {
669             int         _pos;
670             ulong       _buckets_num;
671             _Bucket[]   _buckets;
672             this(_Bucket[] _b)
673             {
674                 _buckets = _b;
675                 _buckets_num = _b.length;
676                 _pos = 0;
677                 while( _pos < _buckets_num  && _buckets[_pos].hash < ALLOCATED_HASH )
678                 {
679                     _pos++;
680                 }
681             }
682             bool empty() const pure nothrow @safe @nogc
683             {
684                 return _pos == _buckets_num;
685             }
686             V front()
687             {
688                 static if (inlineValues)
689                 {
690                     return _buckets[_pos].value;
691                 }
692                 else
693                 {
694                     return *(_buckets[_pos].value_ptr);
695                 }
696             }
697             void popFront() pure nothrow @safe @nogc
698             {
699                 _pos++;
700                 while( _pos < _buckets_num && _buckets[_pos].hash < ALLOCATED_HASH )
701                 {
702                     _pos++;
703                 }
704             }
705         }
706         return _kvRange(_buckets);
707     }
708 
709     auto byPair() pure @safe
710     {
711         import std.typecons;
712 
713         struct _kvRange {
714             int         _pos;
715             ulong       _buckets_num;
716             _Bucket[]   _buckets;
717             this(_Bucket[] _b)
718             {
719                 _buckets = _b;
720                 _buckets_num = _b.length;
721                 _pos = 0;
722                 while( _pos < _buckets_num  && _buckets[_pos].hash < ALLOCATED_HASH )
723                 {
724                     _pos++;
725                 }
726             }
727             bool empty() const pure nothrow @safe @nogc
728             {
729                 return _pos == _buckets_num;
730             }
731             auto front() @safe
732             {
733                 static if (inlineValues)
734                 {
735                     return Tuple!(K, "key", V, "value")(_buckets[_pos].key, _buckets[_pos].value);
736                 }
737                 else
738                 {
739                     return Tuple!(K, "key", V, "value")(_buckets[_pos].key, *(_buckets[_pos].value_ptr));
740                 }
741             }
742             void popFront() pure nothrow @safe @nogc
743             {
744                 _pos++;
745                 while( _pos < _buckets_num && _buckets[_pos].hash < ALLOCATED_HASH )
746                 {
747                     _pos++;
748                 }
749             }
750         }
751         return _kvRange(_buckets);
752     }
753 }
754 
755 /// Tests
756 
757 /// test immutable struct and class as Key type
758 @safe unittest
759 {
760     () @nogc
761     {
762         struct S
763         {
764             int s;
765         }
766         HashMap!(immutable S, int) hs1;
767         immutable ss = S(1);
768         hs1[ss] = 1;
769         assert(ss in hs1 && *(ss in hs1) == 1);
770         HashMap!(int, immutable S) hs2;
771         hs2[1] = ss;
772         assert(1 in hs2 && *(1 in hs2) == ss);
773         assert(!(2 in hs2));
774     }();
775 
776     // class
777     class C
778     {
779         int v;
780         this(int _v) pure inout
781         {
782             v = _v;
783         }
784         bool opEquals(const C o) pure const @safe @nogc nothrow {
785             return v == o.v;
786         }
787         override hash_t toHash() const @safe @nogc {
788             return hash_function(v);
789         }
790     }
791     HashMap!(immutable C, int) hc1;
792     immutable cc = new immutable C(1);
793     hc1[cc] = 1;
794     assert(hc1[cc] == 1);
795     HashMap!(int, immutable C) hc2;
796     hc2[1] = cc;
797     assert(hc2[1] is cc);
798 }
799 @safe unittest {
800     // test class as key
801     globalLogLevel = LogLevel.info;
802     class A {
803         int v;
804 
805         bool opEquals(const A o) pure const @safe @nogc nothrow {
806             return v == o.v;
807         }
808         override hash_t toHash() const @safe @nogc {
809             return hash_function(v);
810         }
811         this(int v)
812         {
813             this.v = v;
814         }
815         override string toString() const
816         {
817             import std.format;
818             return "A(%d)".format(v);
819         }
820     }
821 
822     globalLogLevel = LogLevel.info;
823     auto x = new A(1);
824     auto y = new A(2);
825     HashMap!(A, string) dict;
826     dict.put(x, "x");
827     dict.put(y, "y");
828 }
829 
830 @safe unittest {
831     globalLogLevel = LogLevel.info;
832     () @nogc {
833         HashMap!(int, int) int2int;
834         foreach(i; 1..5) {
835             int2int.put(i,i);
836         }
837         int2int.put(33,33); // <- follow key 1, move key 2 on pos 3
838         assert(1 in int2int, "1 not in hash");
839         assert(2 in int2int, "2 not in hash");
840         assert(1 in int2int, "3 not in hash");
841         assert(4 in int2int, "4 not in hash");
842         assert(33 in int2int, "33 not in hash");
843         int2int.remove(33);
844         int2int.put(2,2); // <- must replace key 2 on pos 3
845         assert(2 in int2int, "2 not in hash");
846     }();
847     () @nogc {
848         struct LargeStruct {
849             ulong a;
850             ulong b;
851         }
852         HashMap!(int, LargeStruct) int2ls;
853         foreach(i; 1..5) {
854             int2ls.put(i,LargeStruct(i,i));
855         }
856         int2ls.put(33,LargeStruct(33,33)); // <- follow key 1, move key 2 on pos 3
857         assert(1 in int2ls, "1 not in hash");
858         assert(2 in int2ls, "2 not in hash");
859         assert(3 in int2ls, "3 not in hash");
860         assert(4 in int2ls, "4 not in hash");
861         assert(33 in int2ls, "33 not in hash");
862         int2ls.remove(33);
863         int2ls.put(2,LargeStruct(2,2)); // <- must replace key 2 on pos 3
864         assert(2 in int2ls, "2 not in hash");
865     }();
866 }
867 
868 @safe unittest {
869     globalLogLevel = LogLevel.info;
870     () @nogc {
871         assert(SmallValueFootprint!int());
872         assert(SmallValueFootprint!double());
873         struct SmallStruct {
874             ulong a;
875         }
876         //assert(SmallValueFootprint!SmallStruct);
877         struct LargeStruct {
878             ulong a;
879             ulong b;
880         }
881         assert(!SmallValueFootprint!LargeStruct);
882         class SmallClass {
883             ulong a;
884         }
885         //assert(!SmallValueFootprint!SmallClass);
886 
887         HashMap!(int, string) int2string;
888         auto u = int2string.put(1, "one");
889         {
890             auto v = 1 in int2string;
891             assert(v !is null);
892             assert(*v == "one");
893         }
894         assert(2 !in int2string);
895         u = int2string.put(32+1, "33");
896         assert(33 in int2string);
897         assert(int2string.remove(33));
898         assert(!int2string.remove(33));
899         
900         HashMap!(int, LargeStruct) int2LagreStruct;
901         int2LagreStruct.put(1, LargeStruct(1,2));
902         {
903             auto v = 1 in int2LagreStruct;
904             assert(v !is null);
905             assert(*v == LargeStruct(1, 2));
906         }
907     }();
908 
909     globalLogLevel = LogLevel.info;
910 }
911 
912 @safe unittest {
913     import std.experimental.allocator.gc_allocator;
914     globalLogLevel = LogLevel.info;
915     static int i;
916     () @safe @nogc {
917         struct LargeStruct {
918             ulong a;
919             ulong b;
920             ~this() @safe @nogc {
921                 i++;
922             }
923         }
924         HashMap!(int, LargeStruct) int2LagreStruct;
925         int2LagreStruct.put(1, LargeStruct(1,2));
926     }();
927     globalLogLevel = LogLevel.info;
928 }
929 
930 @safe unittest
931 {
932     import std.typecons;
933     alias K = Tuple!(int, int);
934     alias V = int;
935     HashMap!(K,V) h;
936     K k0 = K(0,1);
937     V v0 = 1;
938     h.put(k0, v0);
939     int *v = k0 in h;
940     assert(v);
941     assert(*v == 1);
942     h[k0] = v0;
943     assert(h[k0] == v0);
944 }
945 
946 @safe unittest
947 {
948     class c {
949         int a;
950         this(int a)
951         {
952             this.a = a;
953         }
954         override hash_t toHash() const pure @nogc @safe
955         {
956             return hash_function(a);
957         }
958         bool opEquals(const c other) pure const @safe @nogc
959         {
960             return this is other || this.a == other.a;
961         }
962     }
963     alias K = c;
964     alias V = int;
965     K k0 = new c(0);
966     V v0 = 1;
967     () @nogc {
968         HashMap!(K,V) h;
969         h.put(k0, v0);
970         int *v = k0 in h;
971         assert(v);
972         assert(*v == 1);
973         h[k0] = 2;
974         v = k0 in h;
975         assert(*v == 2);
976     }();
977 }
978 
979 /// Test if we can work with non-@nogc opEquals for class-key.
980 /// opEquals anyway must be non-@system.
981 @safe unittest
982 {
983     class c {
984         int a;
985         this(int a)
986         {
987             this.a = a;
988         }
989         override hash_t toHash() const pure @safe
990         {
991             int[] _ = [1, 2, 3]; // this cause GC
992             return hash_function(a);
993         }
994 
995         bool opEquals(const c other) const pure @safe
996         {
997             auto _ = [1,2,3]; // this cause GC
998             return this is other || this.a == other.a;
999         }
1000     }
1001     alias K = c;
1002     alias V = int;
1003     HashMap!(K,V) h;
1004     K k0 = new c(0);
1005     V v0 = 1;
1006     h.put(k0, v0);
1007     int *v = k0 in h;
1008     assert(v);
1009     assert(*v == 1);
1010 
1011 }
1012 
1013 @safe unittest
1014 {
1015     import std.algorithm;
1016     import std.array;
1017     import std.stdio;
1018 
1019     HashMap!(int, string) m;
1020     m[1] = "one";
1021     m[2] = "two";
1022     m[10] = "ten";
1023     assert(equal(m.byKey.array.sort, [1,2,10]));
1024     assert(equal(m.byValue.array.sort, ["one", "ten", "two"]));
1025     assert(equal(
1026         m.byPair.map!"tuple(a.key, a.value)".array.sort, 
1027         [tuple(1, "one"), tuple(2, "two"), tuple(10, "ten")]
1028     ));
1029     m.remove(1);
1030     m.remove(10);
1031     assert(equal(
1032     m.byPair.map!"tuple(a.key, a.value)".array.sort,
1033         [tuple(2, "two")]
1034     ));
1035     m.remove(2);
1036     assert(m.byPair.map!"tuple(a.key, a.value)".array.sort.length() == 0);
1037     m.remove(2);
1038     assert(m.byPair.map!"tuple(a.key, a.value)".array.sort.length() == 0);
1039 }
1040 
1041 unittest {
1042     import std.random;
1043     import std.array;
1044     import std.algorithm;
1045     import std.stdio;
1046 
1047     enum iterations = 400_000;
1048 
1049     globalLogLevel = LogLevel.info;
1050 
1051     HashMap!(int, int) hashMap;
1052     int[int]             AA;
1053 
1054     auto rnd = Random(unpredictableSeed);
1055 
1056     foreach(i;0..iterations) {
1057         int k = uniform(0, iterations, rnd);
1058         hashMap.put(k, i);
1059         AA[k] = i;
1060     }
1061     assert(equal(AA.keys().sort(), hashMap.byKey().array.sort()));
1062     assert(equal(AA.values().sort(), hashMap.byValue().array.sort()));
1063     assert(AA.length == hashMap.length);
1064 }
1065 
1066 @safe unittest
1067 {
1068     // test removal while iterating
1069     import std.random;
1070     import std.array;
1071     import std.algorithm;
1072     import std.stdio;
1073 
1074     enum iterations = 400_000;
1075 
1076     globalLogLevel = LogLevel.info;
1077 
1078     HashMap!(int, int) hashMap;
1079 
1080     auto rnd = Random(unpredictableSeed);
1081 
1082     foreach(i;0..iterations) {
1083         int k = uniform(0, iterations, rnd);
1084         hashMap[k] = i;
1085     }
1086     foreach(k; hashMap.byKey)
1087     {
1088         hashMap.remove(k);
1089     }
1090     assert(hashMap.length == 0);
1091 }
1092 
1093 @safe @nogc unittest
1094 {
1095     // test clear
1096 
1097     HashMap!(int, int) hashMap;
1098 
1099     foreach(i;0..100) {
1100         hashMap[i] = i;
1101     }
1102     hashMap.clear();
1103     assert(hashMap.length == 0);
1104     hashMap[1] = 1;
1105     assert(1 in hashMap && hashMap.length == 1);
1106 }
1107 
1108 @safe @nogc unittest
1109 {
1110     // test of nogc getOrAdd
1111 
1112     HashMap!(int, int) hashMap;
1113 
1114     foreach(i;0..100) {
1115         hashMap[i] = i;
1116     }
1117     auto v = hashMap.getOrAdd(-1, -1);
1118     assert(-1 in hashMap && v == -1);
1119 }
1120 
1121 @safe @nogc unittest
1122 {
1123     // test of nogc getOrAdd with lazy default value
1124 
1125     HashMap!(int, int) hashMap;
1126 
1127     foreach(i;0..100) {
1128         hashMap[i] = i;
1129     }
1130     int v = hashMap.getOrAdd(-1, () => -1);
1131     assert(-1 in hashMap && v == -1);
1132     assert(hashMap.get(-1, 0) == -1); // key -1 is in hash, return value
1133     assert(hashMap.get(-2, 0) == 0);  // key -2 not in map, return default value
1134     assert(hashMap.get(-3, () => 0) == 0);  // ditto
1135 }
1136 
1137 @safe unittest
1138 {
1139     import std.socket;
1140     HashMap!(string, Socket) socketPool;
1141     Socket s0 = socketPool.getOrAdd("http://example.com", () => new Socket(AddressFamily.INET, SocketType.STREAM));
1142     assert(s0 !is null);
1143     assert(s0.addressFamily == AddressFamily.INET);
1144     Socket s1 = socketPool.getOrAdd("http://example.com", () => new Socket(AddressFamily.INET, SocketType.STREAM));
1145     assert(s1 !is null);
1146     assert(s1 is s0);
1147 }
1148 
1149 @safe unittest
1150 {
1151     import std.socket;
1152     class Connection {
1153         Socket s;
1154         bool opEquals(const Connection other) const pure @safe
1155         {
1156             return s is other.s;
1157         }
1158         override hash_t toHash() const @safe
1159         {
1160             return hash_function(s.handle);
1161         }
1162     }
1163     HashMap!(Connection, string) socketPool;
1164 }
1165 
1166 @safe unittest
1167 {
1168     // test of non-@nogc getOrAdd with lazy default value
1169     import std.conv;
1170     import std.exception;
1171     class C {
1172         string v;
1173         this(int _v) @safe
1174         {
1175             v = to!string(_v);
1176         }
1177     }
1178 
1179     HashMap!(int, C) hashMap;
1180 
1181     foreach(i;0..100) {
1182         hashMap[i] = new C(i);
1183     }
1184     C v = hashMap.getOrAdd(-1, () => new C(-1));
1185     assert(-1 in hashMap && v.v == "-1");
1186     assert(hashMap[-1].v == "-1");
1187     hashMap[-1].v ~= "1";
1188     assert(hashMap[-1].v == "-11");
1189     assertThrown!KeyNotFound(hashMap[-2]);
1190     // check lazyness
1191     bool called;
1192     v = hashMap.getOrAdd(-1, delegate C() {called = true; return new C(0);});
1193     assert(!called);
1194     v = hashMap.getOrAdd(-2, delegate C() {called = true; return new C(0);});
1195     assert(called);
1196 }
1197 
1198 @safe @nogc unittest
1199 {
1200     // test of nogc getOrAdd with lazy default value
1201     // corner case when V is callable
1202 
1203     alias F = int function() @safe @nogc;
1204 
1205     F one = function()
1206     {
1207         return 1;
1208     };
1209     F two = function()
1210     {
1211         return 2;
1212     };
1213     F three = function()
1214     {
1215         return 3;
1216     };
1217     F four = function()
1218     {
1219         return 4;
1220     };
1221     HashMap!(int, F) hashMap;
1222     hashMap.put(1, one);
1223     hashMap.put(2, two);
1224     auto p = 1 in hashMap;
1225     assert(p);
1226     assert((*p)() == 1);
1227     p = 2 in hashMap;
1228     assert(p);
1229     assert((*p)() == 2);
1230     auto f3 = hashMap.getOrAdd(3, () => function int() {return 3;}); // used as default()
1231     assert(f3() == 3);
1232     auto f4 = hashMap.getOrAdd(4, four);
1233     assert(f4() == 4);
1234 }
1235 
1236 // test get()
1237 @safe @nogc unittest
1238 {
1239     HashMap!(int, int) hashMap;
1240     int i = hashMap.get(1, 55);
1241     assert(i == 55);
1242     i = hashMap.get(1, () => 66);
1243     assert(i == 66);
1244     hashMap[1] = 1;
1245     i = hashMap.get(1, () => 66);
1246     assert(i == 1);
1247 }