1 /** 2 * Implementation of associative arrays. 3 * 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) 8 */ 9 module rt.aaA; 10 11 // version (CRuntime_LIBWASM) {} else 12 /// This file has interactions with the GC removed in favor of a _d_allocmemory 13 /// that relies on PoolStack from libwasm 14 15 /// AA version for debuggers, bump whenever changing the layout 16 extern (C) immutable int _aaVersion = 1; 17 extern (C) void* _d_allocmemory(size_t sz) pure nothrow; 18 19 import core.internal.util.math : min, max; 20 // grow threshold 21 private enum GROW_NUM = 4; 22 private enum GROW_DEN = 5; 23 // shrink threshold 24 private enum SHRINK_NUM = 1; 25 private enum SHRINK_DEN = 8; 26 // grow factor 27 private enum GROW_FAC = 4; 28 // growing the AA doubles it's size, so the shrink threshold must be 29 // smaller than half the grow threshold to have a hysteresis 30 static assert(GROW_FAC * SHRINK_NUM * GROW_DEN < GROW_NUM * SHRINK_DEN); 31 // initial load factor (for literals), mean of both thresholds 32 private enum INIT_NUM = (GROW_DEN * SHRINK_NUM + GROW_NUM * SHRINK_DEN) / 2; 33 private enum INIT_DEN = SHRINK_DEN * GROW_DEN; 34 35 private enum INIT_NUM_BUCKETS = 8; 36 // magic hash constants to distinguish empty, deleted, and filled buckets 37 private enum HASH_EMPTY = 0; 38 private enum HASH_DELETED = 0x1; 39 private enum HASH_FILLED_MARK = size_t(1) << 8 * size_t.sizeof - 1; 40 41 version (LDC) 42 { 43 // The compiler uses `void*` for its prototypes. 44 // Don't wrap in a struct to maintain ABI compatibility. 45 alias AA = Impl*; 46 47 private bool empty(scope const AA impl) pure nothrow @nogc 48 { 49 return impl is null || !impl.length; 50 } 51 } 52 else 53 { 54 /// Opaque AA wrapper 55 struct AA 56 { 57 Impl* impl; 58 alias impl this; 59 60 private @property bool empty() const pure nothrow @nogc 61 { 62 return impl is null || !impl.length; 63 } 64 } 65 } 66 67 private struct Impl 68 { 69 private: 70 this(scope const TypeInfo_AssociativeArray ti, size_t sz = INIT_NUM_BUCKETS) nothrow 71 { 72 initialize(ti, sz); 73 } 74 void initialize(scope const TypeInfo_AssociativeArray ti, size_t sz = INIT_NUM_BUCKETS) nothrow 75 { 76 keysz = cast(uint) ti.key.tsize; 77 valsz = cast(uint) ti.value.tsize; 78 buckets = allocBuckets(sz); 79 firstUsed = cast(uint) buckets.length; 80 valoff = cast(uint) talign(keysz, ti.value.talign); 81 hashFn = &ti.key.getHash; 82 83 import rt.lifetime : hasPostblit, unqualify; 84 85 if (hasPostblit(unqualify(ti.key))) 86 flags |= Flags.keyHasPostblit; 87 if ((ti.key.flags | ti.value.flags) & 1) 88 flags |= Flags.hasPointers; 89 90 entryTI = fakeEntryTI(this, ti.key, ti.value); 91 } 92 Bucket[] buckets; 93 uint used; 94 uint deleted; 95 TypeInfo_Struct entryTI; 96 uint firstUsed; 97 /*immutable*/uint keysz; 98 /*immutable*/uint valsz; 99 /*immutable*/uint valoff; 100 Flags flags; 101 102 // function that calculates hash of a key. Set on creation 103 // the parameter is a pointer to the key. 104 size_t delegate(scope const void*) nothrow hashFn; 105 106 enum Flags : ubyte 107 { 108 none = 0x0, 109 keyHasPostblit = 0x1, 110 hasPointers = 0x2, 111 } 112 113 @property size_t length() const pure nothrow @nogc 114 { 115 assert(used >= deleted); 116 return used - deleted; 117 } 118 119 @property size_t dim() const pure nothrow @nogc @safe 120 { 121 return buckets.length; 122 } 123 124 @property size_t mask() const pure nothrow @nogc 125 { 126 return dim - 1; 127 } 128 129 // find the first slot to insert a value with hash 130 inout(Bucket)* findSlotInsert(size_t hash) inout pure nothrow @nogc 131 { 132 for (size_t i = hash & mask, j = 1;; ++j) 133 { 134 if (!buckets[i].filled) 135 return &buckets[i]; 136 i = (i + j) & mask; 137 } 138 } 139 140 // lookup a key 141 inout(Bucket)* findSlotLookup(size_t hash, scope const void* pkey, scope const TypeInfo keyti) inout 142 { 143 for (size_t i = hash & mask, j = 1;; ++j) 144 { 145 if (buckets[i].hash == hash && keyti.equals(pkey, buckets[i].entry)) 146 return &buckets[i]; 147 else if (buckets[i].empty) 148 return null; 149 i = (i + j) & mask; 150 } 151 } 152 153 void grow(scope const TypeInfo keyti) pure nothrow 154 { 155 // If there are so many deleted entries, that growing would push us 156 // below the shrink threshold, we just purge deleted entries instead. 157 if (length * SHRINK_DEN < GROW_FAC * dim * SHRINK_NUM) 158 resize(dim); 159 else 160 resize(GROW_FAC * dim); 161 } 162 163 void shrink(scope const TypeInfo keyti) pure nothrow 164 { 165 if (dim > INIT_NUM_BUCKETS) 166 resize(dim / GROW_FAC); 167 } 168 169 void resize(size_t ndim) pure nothrow 170 { 171 auto obuckets = buckets; 172 buckets = allocBuckets(ndim); 173 174 foreach (ref b; obuckets[firstUsed .. $]) 175 if (b.filled) 176 *findSlotInsert(b.hash) = b; 177 178 firstUsed = 0; 179 used -= deleted; 180 deleted = 0; 181 //GC.free(obuckets.ptr); // safe to free b/c impossible to reference 182 } 183 184 void clear() pure nothrow 185 { 186 import core.stdc.string : memset; 187 // clear all data, but don't change bucket array length 188 memset(&buckets[firstUsed], 0, (buckets.length - firstUsed) * Bucket.sizeof); 189 deleted = used = 0; 190 firstUsed = cast(uint) dim; 191 } 192 } 193 194 //============================================================================== 195 // Bucket 196 //------------------------------------------------------------------------------ 197 198 private struct Bucket 199 { 200 private pure nothrow @nogc: 201 size_t hash; 202 void* entry; 203 204 @property bool empty() const 205 { 206 return hash == HASH_EMPTY; 207 } 208 209 @property bool deleted() const 210 { 211 return hash == HASH_DELETED; 212 } 213 214 @property bool filled() const @safe 215 { 216 return cast(ptrdiff_t) hash < 0; 217 } 218 } 219 220 Bucket[] allocBuckets(size_t dim) @trusted pure nothrow 221 { 222 immutable sz = dim * Bucket.sizeof; 223 return (cast(Bucket*) _d_allocmemory(sz))[0 .. dim]; 224 } 225 226 //============================================================================== 227 // Entry 228 //------------------------------------------------------------------------------ 229 230 private void* allocEntry(scope const Impl* aa, scope const void* pkey) 231 { 232 import rt.lifetime : _d_newitemU; 233 import core.stdc.string : memcpy, memset; 234 235 immutable akeysz = aa.valoff; 236 void* res = void; 237 if (aa.entryTI) 238 res = _d_newitemU(aa.entryTI); 239 else 240 { 241 //auto flags = (aa.flags & Impl.Flags.hasPointers) ? 0 : GC.BlkAttr.NO_SCAN; 242 res = _d_allocmemory(akeysz + aa.valsz); 243 } 244 245 memcpy(res, pkey, aa.keysz); // copy key 246 memset(res + akeysz, 0, aa.valsz); // zero value 247 248 return res; 249 } 250 251 package void entryDtor(void* p, const TypeInfo_Struct sti) 252 { 253 // key and value type info stored after the TypeInfo_Struct by tiEntry() 254 auto sizeti = __traits(classInstanceSize, TypeInfo_Struct); 255 auto extra = cast(const(TypeInfo)*)(cast(void*) sti + sizeti); 256 extra[0].destroy(p); 257 extra[1].destroy(p + talign(extra[0].tsize, extra[1].talign)); 258 } 259 260 private bool hasDtor(const TypeInfo ti) pure nothrow 261 { 262 import rt.lifetime : unqualify; 263 264 if (typeid(ti) is typeid(TypeInfo_Struct)) 265 if ((cast(TypeInfo_Struct) cast(void*) ti).xdtor) 266 return true; 267 if (typeid(ti) is typeid(TypeInfo_StaticArray)) 268 return hasDtor(unqualify(ti.next)); 269 270 return false; 271 } 272 273 private immutable(void)* getRTInfo(const TypeInfo ti) pure nothrow 274 { 275 // classes are references 276 const isNoClass = ti && typeid(ti) !is typeid(TypeInfo_Class); 277 return isNoClass ? ti.rtInfo() : rtinfoHasPointers; 278 } 279 280 // build type info for Entry with additional key and value fields 281 TypeInfo_Struct fakeEntryTI(ref Impl aa, const TypeInfo keyti, const TypeInfo valti) nothrow 282 { 283 import rt.lifetime : unqualify; 284 285 auto kti = unqualify(keyti); 286 auto vti = unqualify(valti); 287 288 // figure out whether RTInfo has to be generated (indicated by rtisize > 0) 289 enum pointersPerWord = 8 * (void*).sizeof * (void*).sizeof; 290 auto rtinfo = rtinfoNoPointers; 291 size_t rtisize = 0; 292 immutable(size_t)* keyinfo = void; 293 immutable(size_t)* valinfo = void; 294 if (aa.flags & Impl.Flags.hasPointers) 295 { 296 // classes are references 297 keyinfo = cast(immutable(size_t)*) getRTInfo(keyti); 298 valinfo = cast(immutable(size_t)*) getRTInfo(valti); 299 300 if (keyinfo is rtinfoHasPointers && valinfo is rtinfoHasPointers) 301 rtinfo = rtinfoHasPointers; 302 else 303 rtisize = 1 + (aa.valoff + aa.valsz + pointersPerWord - 1) / pointersPerWord; 304 } 305 bool entryHasDtor = hasDtor(kti) || hasDtor(vti); 306 if (rtisize == 0 && !entryHasDtor) 307 return null; 308 309 // save kti and vti after type info for struct 310 enum sizeti = __traits(classInstanceSize, TypeInfo_Struct); 311 void* p = _d_allocmemory(sizeti + (2 + rtisize) * (void*).sizeof); 312 import core.stdc.string : memcpy; 313 314 memcpy(p, __traits(initSymbol, TypeInfo_Struct).ptr, sizeti); 315 316 auto ti = cast(TypeInfo_Struct) p; 317 auto extra = cast(TypeInfo*)(p + sizeti); 318 extra[0] = cast() kti; 319 extra[1] = cast() vti; 320 321 static immutable tiMangledName = "S2rt3aaA__T5EntryZ"; 322 ti.mangledName = tiMangledName; 323 324 ti.m_RTInfo = rtisize > 0 ? rtinfoEntry(aa, keyinfo, valinfo, cast(size_t*)(extra + 2), rtisize) : rtinfo; 325 ti.m_flags = ti.m_RTInfo is rtinfoNoPointers ? cast(TypeInfo_Struct.StructFlags)0 : TypeInfo_Struct.StructFlags.hasPointers; 326 327 // we don't expect the Entry objects to be used outside of this module, so we have control 328 // over the non-usage of the callback methods and other entries and can keep these null 329 // xtoHash, xopEquals, xopCmp, xtoString and xpostblit 330 immutable entrySize = aa.valoff + aa.valsz; 331 ti.m_init = (cast(ubyte*) null)[0 .. entrySize]; // init length, but not ptr 332 333 if (entryHasDtor) 334 { 335 // xdtor needs to be built from the dtors of key and value for the GC 336 ti.xdtorti = &entryDtor; 337 ti.m_flags |= TypeInfo_Struct.StructFlags.isDynamicType; 338 } 339 340 ti.m_align = cast(uint) max(kti.talign, vti.talign); 341 342 return ti; 343 } 344 345 // build appropriate RTInfo at runtime 346 immutable(void)* rtinfoEntry(ref Impl aa, immutable(size_t)* keyinfo, 347 immutable(size_t)* valinfo, size_t* rtinfoData, size_t rtinfoSize) pure nothrow 348 { 349 enum bitsPerWord = 8 * size_t.sizeof; 350 351 rtinfoData[0] = aa.valoff + aa.valsz; 352 rtinfoData[1..rtinfoSize] = 0; 353 354 void copyKeyInfo(string src)() 355 { 356 size_t pos = 1; 357 size_t keybits = aa.keysz / (void*).sizeof; 358 while (keybits >= bitsPerWord) 359 { 360 rtinfoData[pos] = mixin(src); 361 keybits -= bitsPerWord; 362 pos++; 363 } 364 if (keybits > 0) 365 rtinfoData[pos] = mixin(src) & ((cast(size_t) 1 << keybits) - 1); 366 } 367 368 if (keyinfo is rtinfoHasPointers) 369 copyKeyInfo!"~cast(size_t) 0"(); 370 else if (keyinfo !is rtinfoNoPointers) 371 copyKeyInfo!"keyinfo[pos]"(); 372 373 void copyValInfo(string src)() 374 { 375 size_t bitpos = aa.valoff / (void*).sizeof; 376 size_t pos = 1; 377 size_t dstpos = 1 + bitpos / bitsPerWord; 378 size_t begoff = bitpos % bitsPerWord; 379 size_t valbits = aa.valsz / (void*).sizeof; 380 size_t endoff = (bitpos + valbits) % bitsPerWord; 381 for (;;) 382 { 383 const bits = bitsPerWord - begoff; 384 size_t s = mixin(src); 385 rtinfoData[dstpos] |= s << begoff; 386 if (begoff > 0 && valbits > bits) 387 rtinfoData[dstpos+1] |= s >> bits; 388 if (valbits < bitsPerWord) 389 break; 390 valbits -= bitsPerWord; 391 dstpos++; 392 pos++; 393 } 394 if (endoff > 0) 395 rtinfoData[dstpos] &= ((cast(size_t) 1 << endoff) - 1); 396 } 397 398 if (valinfo is rtinfoHasPointers) 399 copyValInfo!"~cast(size_t) 0"(); 400 else if (valinfo !is rtinfoNoPointers) 401 copyValInfo!"valinfo[pos]"(); 402 403 return cast(immutable(void)*) rtinfoData; 404 } 405 406 unittest 407 { 408 void test(K, V)() 409 { 410 static struct Entry 411 { 412 K key; 413 V val; 414 } 415 auto keyti = typeid(K); 416 auto valti = typeid(V); 417 auto valrti = getRTInfo(valti); 418 auto keyrti = getRTInfo(keyti); 419 420 auto impl = new Impl(typeid(V[K])); 421 if (valrti is rtinfoNoPointers && keyrti is rtinfoNoPointers) 422 { 423 assert(!(impl.flags & Impl.Flags.hasPointers)); 424 assert(impl.entryTI is null); 425 } 426 else if (valrti is rtinfoHasPointers && keyrti is rtinfoHasPointers) 427 { 428 assert(impl.flags & Impl.Flags.hasPointers); 429 assert(impl.entryTI is null); 430 } 431 else 432 { 433 auto rtInfo = cast(size_t*) impl.entryTI.rtInfo(); 434 auto refInfo = cast(size_t*) typeid(Entry).rtInfo(); 435 assert(rtInfo[0] == refInfo[0]); // size 436 enum bytesPerWord = 8 * size_t.sizeof * (void*).sizeof; 437 size_t words = (rtInfo[0] + bytesPerWord - 1) / bytesPerWord; 438 foreach (i; 0 .. words) 439 assert(rtInfo[1 + i] == refInfo[i + 1]); 440 } 441 } 442 test!(long, int)(); 443 test!(string, string); 444 test!(ubyte[16], Object); 445 446 static struct Small 447 { 448 ubyte[16] guid; 449 string name; 450 } 451 test!(string, Small); 452 453 static struct Large 454 { 455 ubyte[1024] data; 456 string[412] names; 457 ubyte[1024] moredata; 458 } 459 test!(Large, Large); 460 } 461 462 //============================================================================== 463 // Helper functions 464 //------------------------------------------------------------------------------ 465 466 private size_t talign(size_t tsize, size_t algn) @safe pure nothrow @nogc 467 { 468 immutable mask = algn - 1; 469 assert(!(mask & algn)); 470 return (tsize + mask) & ~mask; 471 } 472 473 // mix hash to "fix" bad hash functions 474 private size_t mix(size_t h) @safe pure nothrow @nogc 475 { 476 // final mix function of MurmurHash2 477 enum m = 0x5bd1e995; 478 h ^= h >> 13; 479 h *= m; 480 h ^= h >> 15; 481 return h; 482 } 483 484 private size_t calcHash(scope const void *pkey, scope const Impl* impl) nothrow 485 { 486 immutable hash = impl.hashFn(pkey); 487 // highest bit is set to distinguish empty/deleted from filled buckets 488 return mix(hash) | HASH_FILLED_MARK; 489 } 490 491 private size_t nextpow2(const size_t n) pure nothrow @nogc 492 { 493 import core.bitop : bsr; 494 495 if (!n) 496 return 1; 497 498 const isPowerOf2 = !((n - 1) & n); 499 return 1 << (bsr(n) + !isPowerOf2); 500 } 501 502 pure nothrow @nogc unittest 503 { 504 // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 505 foreach (const n, const pow2; [1, 1, 2, 4, 4, 8, 8, 8, 8, 16]) 506 assert(nextpow2(n) == pow2); 507 } 508 509 //============================================================================== 510 // API Implementation 511 //------------------------------------------------------------------------------ 512 export: 513 /** Allocate associative array data. 514 * Called for `new SomeAA` expression. 515 * Params: 516 * ti = TypeInfo for the associative array 517 * Returns: 518 * A new associative array. 519 */ 520 extern (C) Impl* _aaNew(const TypeInfo_AssociativeArray ti) 521 { 522 523 auto init = ti.initializer; 524 void* p = _d_allocmemory(ti.tsize()); 525 p[0 .. init.length] = init[]; 526 (cast(Impl*) p).initialize(ti); 527 return cast(Impl*)p; 528 529 } 530 531 /// Determine number of entries in associative array. 532 extern (C) size_t _aaLen(scope const AA aa) pure nothrow @nogc 533 { 534 return aa ? aa.length : 0; 535 } 536 537 /****************************** 538 * Lookup *pkey in aa. 539 * Called only from implementation of (aa[key]) expressions when value is mutable. 540 * Params: 541 * paa = associative array opaque pointer 542 * ti = TypeInfo for the associative array 543 * valsz = ignored 544 * pkey = pointer to the key value 545 * Returns: 546 * if key was in the aa, a mutable pointer to the existing value. 547 * If key was not in the aa, a mutable pointer to newly inserted value which 548 * is set to all zeros 549 */ 550 extern (C) void* _aaGetY(scope AA* paa, const TypeInfo_AssociativeArray ti, 551 const size_t valsz, scope const void* pkey) 552 { 553 bool found; 554 return _aaGetX(paa, ti, valsz, pkey, found); 555 } 556 557 /****************************** 558 * Lookup *pkey in aa. 559 * Called only from implementation of require 560 * Params: 561 * paa = associative array opaque pointer 562 * ti = TypeInfo for the associative array 563 * valsz = ignored 564 * pkey = pointer to the key value 565 * found = true if the value was found 566 * Returns: 567 * if key was in the aa, a mutable pointer to the existing value. 568 * If key was not in the aa, a mutable pointer to newly inserted value which 569 * is set to all zeros 570 */ 571 extern (C) void* _aaGetX(scope AA* paa, const TypeInfo_AssociativeArray ti, 572 const size_t valsz, scope const void* pkey, out bool found) 573 { 574 // lazily alloc implementation 575 AA aa = *paa; 576 if (aa is null) 577 { 578 aa = _aaNew(ti); 579 *paa = aa; 580 } 581 582 // get hash and bucket for key 583 immutable hash = calcHash(pkey, aa); 584 585 // found a value => return it 586 if (auto p = aa.findSlotLookup(hash, pkey, ti.key)) 587 { 588 found = true; 589 return p.entry + aa.valoff; 590 } 591 592 auto p = aa.findSlotInsert(hash); 593 if (p.deleted) 594 --aa.deleted; 595 // check load factor and possibly grow 596 else if (++aa.used * GROW_DEN > aa.dim * GROW_NUM) 597 { 598 aa.grow(ti.key); 599 p = aa.findSlotInsert(hash); 600 assert(p.empty); 601 } 602 603 // update search cache and allocate entry 604 aa.firstUsed = min(aa.firstUsed, cast(uint)(p - aa.buckets.ptr)); 605 p.hash = hash; 606 p.entry = allocEntry(aa, pkey); 607 // postblit for key 608 if (aa.flags & Impl.Flags.keyHasPostblit) 609 { 610 import rt.lifetime : __doPostblit, unqualify; 611 612 __doPostblit(p.entry, aa.keysz, unqualify(ti.key)); 613 } 614 // return pointer to value 615 return p.entry + aa.valoff; 616 } 617 618 /****************************** 619 * Lookup *pkey in aa. 620 * Called only from implementation of (aa[key]) expressions when value is not mutable. 621 * Params: 622 * aa = associative array opaque pointer 623 * keyti = TypeInfo for the key 624 * valsz = ignored 625 * pkey = pointer to the key value 626 * Returns: 627 * pointer to value if present, null otherwise 628 */ 629 extern (C) inout(void)* _aaGetRvalueX(inout AA aa, scope const TypeInfo keyti, const size_t valsz, 630 scope const void* pkey) 631 { 632 return _aaInX(aa, keyti, pkey); 633 } 634 635 /****************************** 636 * Lookup *pkey in aa. 637 * Called only from implementation of (key in aa) expressions. 638 * Params: 639 * aa = associative array opaque pointer 640 * keyti = TypeInfo for the key 641 * pkey = pointer to the key value 642 * Returns: 643 * pointer to value if present, null otherwise 644 */ 645 extern (C) inout(void)* _aaInX(inout AA aa, scope const TypeInfo keyti, scope const void* pkey) 646 { 647 if (aa.empty) 648 return null; 649 650 immutable hash = calcHash(pkey, aa); 651 if (auto p = aa.findSlotLookup(hash, pkey, keyti)) 652 return p.entry + aa.valoff; 653 return null; 654 } 655 656 /// Delete entry scope const AA, return true if it was present 657 extern (C) bool _aaDelX(AA aa, scope const TypeInfo keyti, scope const void* pkey) 658 { 659 if (aa.empty) 660 return false; 661 662 immutable hash = calcHash(pkey, aa); 663 if (auto p = aa.findSlotLookup(hash, pkey, keyti)) 664 { 665 // clear entry 666 p.hash = HASH_DELETED; 667 p.entry = null; 668 669 ++aa.deleted; 670 // `shrink` reallocates, and allocating from a finalizer leads to 671 // InvalidMemoryError: https://issues.dlang.org/show_bug.cgi?id=21442 672 if (aa.length * SHRINK_DEN < aa.dim * SHRINK_NUM) 673 aa.shrink(keyti); 674 675 return true; 676 } 677 return false; 678 } 679 680 /// Remove all elements from AA. 681 extern (C) void _aaClear(AA aa) pure nothrow 682 { 683 if (!aa.empty) 684 { 685 aa.clear(); 686 } 687 } 688 689 /// Rehash AA 690 extern (C) void* _aaRehash(AA* paa, scope const TypeInfo keyti) pure nothrow 691 { 692 AA aa = *paa; 693 if (!aa.empty) 694 aa.resize(nextpow2(INIT_DEN * aa.length / INIT_NUM)); 695 return aa; 696 } 697 698 /// Return a GC allocated array of all values 699 extern (C) inout(void[]) _aaValues(inout AA aa, const size_t keysz, const size_t valsz, 700 const TypeInfo tiValueArray) pure nothrow 701 { 702 if (aa.empty) 703 return null; 704 705 import rt.lifetime : _d_newarrayU; 706 707 auto res = _d_newarrayU(tiValueArray, aa.length).ptr; 708 auto pval = res; 709 710 immutable off = aa.valoff; 711 foreach (b; aa.buckets[aa.firstUsed .. $]) 712 { 713 if (!b.filled) 714 continue; 715 pval[0 .. valsz] = b.entry[off .. valsz + off]; 716 pval += valsz; 717 } 718 // postblit is done in object.values 719 return (cast(inout(void)*) res)[0 .. aa.length]; // fake length, return number of elements 720 } 721 722 /// Return a GC allocated array of all keys 723 extern (C) inout(void[]) _aaKeys(inout AA aa, const size_t keysz, const TypeInfo tiKeyArray) pure nothrow 724 { 725 if (aa.empty) 726 return null; 727 728 import rt.lifetime : _d_newarrayU; 729 730 auto res = _d_newarrayU(tiKeyArray, aa.length).ptr; 731 auto pkey = res; 732 733 foreach (b; aa.buckets[aa.firstUsed .. $]) 734 { 735 if (!b.filled) 736 continue; 737 pkey[0 .. keysz] = b.entry[0 .. keysz]; 738 pkey += keysz; 739 } 740 // postblit is done in object.keys 741 return (cast(inout(void)*) res)[0 .. aa.length]; // fake length, return number of elements 742 } 743 744 // opApply callbacks are extern(D) 745 extern (D) alias dg_t = int delegate(void*); 746 extern (D) alias dg2_t = int delegate(void*, void*); 747 748 /// foreach opApply over all values 749 extern (C) int _aaApply(AA aa, const size_t keysz, dg_t dg) 750 { 751 if (aa.empty) 752 return 0; 753 754 immutable off = aa.valoff; 755 foreach (b; aa.buckets) 756 { 757 if (!b.filled) 758 continue; 759 if (auto res = dg(b.entry + off)) 760 return res; 761 } 762 return 0; 763 } 764 765 /// foreach opApply over all key/value pairs 766 extern (C) int _aaApply2(AA aa, const size_t keysz, dg2_t dg) 767 { 768 if (aa.empty) 769 return 0; 770 771 immutable off = aa.valoff; 772 foreach (b; aa.buckets) 773 { 774 if (!b.filled) 775 continue; 776 if (auto res = dg(b.entry, b.entry + off)) 777 return res; 778 } 779 return 0; 780 } 781 782 /** Construct an associative array of type ti from corresponding keys and values. 783 * Called for an AA literal `[k1:v1, k2:v2]`. 784 * Params: 785 * ti = TypeInfo for the associative array 786 * keys = array of keys 787 * vals = array of values 788 * Returns: 789 * A new associative array opaque pointer, or null if `keys` is empty. 790 */ 791 extern (C) Impl* _d_assocarrayliteralTX(const TypeInfo_AssociativeArray ti, void[] keys, 792 void[] vals) 793 { 794 assert(keys.length == vals.length); 795 796 immutable keysz = ti.key.tsize; 797 immutable valsz = ti.value.tsize; 798 immutable length = keys.length; 799 800 if (!length) 801 return null; 802 803 auto init = ti.initializer; 804 void* aap = _d_allocmemory(ti.tsize()); 805 aap[0 .. init.length] = init[]; 806 (cast(Impl*) aap).initialize(ti, nextpow2(INIT_DEN * length / INIT_NUM)); 807 Impl* aa = cast(Impl*)aap; 808 809 void* pkey = keys.ptr; 810 void* pval = vals.ptr; 811 immutable off = aa.valoff; 812 uint actualLength = 0; 813 foreach (_; 0 .. length) 814 { 815 immutable hash = calcHash(pkey, aa); 816 817 auto p = aa.findSlotLookup(hash, pkey, ti.key); 818 if (p is null) 819 { 820 p = aa.findSlotInsert(hash); 821 p.hash = hash; 822 p.entry = allocEntry(aa, pkey); // move key, no postblit 823 aa.firstUsed = min(aa.firstUsed, cast(uint)(p - aa.buckets.ptr)); 824 actualLength++; 825 } 826 else if (aa.entryTI && hasDtor(ti.value)) 827 { 828 // destroy existing value before overwriting it 829 ti.value.destroy(p.entry + off); 830 } 831 // set hash and blit value 832 auto pdst = p.entry + off; 833 pdst[0 .. valsz] = pval[0 .. valsz]; // move value, no postblit 834 835 pkey += keysz; 836 pval += valsz; 837 } 838 aa.used = actualLength; 839 return aa; 840 } 841 842 /// compares 2 AAs for equality 843 extern (C) int _aaEqual(scope const TypeInfo tiRaw, scope const AA aa1, scope const AA aa2) 844 { 845 if (aa1 is aa2) 846 return true; 847 848 immutable len = _aaLen(aa1); 849 if (len != _aaLen(aa2)) 850 return false; 851 852 if (!len) // both empty 853 return true; 854 855 import rt.lifetime : unqualify; 856 857 auto uti = unqualify(tiRaw); 858 auto ti = *cast(TypeInfo_AssociativeArray*)&uti; 859 // compare the entries 860 immutable off = aa1.valoff; 861 foreach (b1; aa1.buckets) 862 { 863 if (!b1.filled) 864 continue; 865 auto pb2 = aa2.findSlotLookup(b1.hash, b1.entry, ti.key); 866 if (pb2 is null || !ti.value.equals(b1.entry + off, pb2.entry + off)) 867 return false; 868 } 869 return true; 870 } 871 872 /// compute a hash 873 extern (C) hash_t _aaGetHash(scope const AA* paa, scope const TypeInfo tiRaw) nothrow 874 { 875 const AA aa = *paa; 876 877 if (aa.empty) 878 return 0; 879 880 import rt.lifetime : unqualify; 881 882 auto uti = unqualify(tiRaw); 883 auto ti = *cast(TypeInfo_AssociativeArray*)&uti; 884 immutable off = aa.valoff; 885 auto keyHash = &ti.key.getHash; 886 auto valHash = &ti.value.getHash; 887 888 size_t h; 889 foreach (b; aa.buckets) 890 { 891 // use addition here, so that hash is independent of element order 892 if (b.filled) 893 h += hashOf(valHash(b.entry + off), keyHash(b.entry)); 894 } 895 896 return h; 897 } 898 899 /** 900 * _aaRange implements a ForwardRange 901 */ 902 struct Range 903 { 904 Impl* impl; 905 size_t idx; 906 alias impl this; 907 } 908 909 extern (C) pure nothrow @nogc @safe 910 { 911 Range _aaRange(return scope AA aa) 912 { 913 if (!aa) 914 return Range(); 915 916 foreach (i; aa.firstUsed .. aa.dim) 917 { 918 if (aa.buckets[i].filled) 919 return Range(aa, i); 920 } 921 return Range(aa, aa.dim); 922 } 923 924 bool _aaRangeEmpty(Range r) 925 { 926 return r.impl is null || r.idx >= r.dim; 927 } 928 929 void* _aaRangeFrontKey(Range r) 930 { 931 assert(!_aaRangeEmpty(r)); 932 if (r.idx >= r.dim) 933 return null; 934 return r.buckets[r.idx].entry; 935 } 936 937 void* _aaRangeFrontValue(Range r) 938 { 939 assert(!_aaRangeEmpty(r)); 940 if (r.idx >= r.dim) 941 return null; 942 943 auto entry = r.buckets[r.idx].entry; 944 return entry is null ? 945 null : 946 (() @trusted { return entry + r.valoff; } ()); 947 } 948 949 void _aaRangePopFront(ref Range r) 950 { 951 if (r.idx >= r.dim) return; 952 for (++r.idx; r.idx < r.dim; ++r.idx) 953 { 954 if (r.buckets[r.idx].filled) 955 break; 956 } 957 } 958 } 959 960 // Most tests are now in test_aa.d 961 962 // LDC_FIXME: Cannot compile these tests in this module (and this module only) 963 // because the public signatures of the various functions are different from 964 // the ones used here (AA vs. void*). 965 version (LDC) {} else: 966 967 // test postblit for AA literals 968 unittest 969 { 970 static struct T 971 { 972 ubyte field; 973 static size_t postblit, dtor; 974 this(this) 975 { 976 ++postblit; 977 } 978 979 ~this() 980 { 981 ++dtor; 982 } 983 } 984 985 T t; 986 auto aa1 = [0 : t, 1 : t]; 987 assert(T.dtor == 0 && T.postblit == 2); 988 aa1[0] = t; 989 assert(T.dtor == 1 && T.postblit == 3); 990 991 T.dtor = 0; 992 T.postblit = 0; 993 994 auto aa2 = [0 : t, 1 : t, 0 : t]; // literal with duplicate key => value overwritten 995 assert(T.dtor == 1 && T.postblit == 3); 996 997 T.dtor = 0; 998 T.postblit = 0; 999 1000 auto aa3 = [t : 0]; 1001 assert(T.dtor == 0 && T.postblit == 1); 1002 aa3[t] = 1; 1003 assert(T.dtor == 0 && T.postblit == 1); 1004 aa3.remove(t); 1005 assert(T.dtor == 0 && T.postblit == 1); 1006 aa3[t] = 2; 1007 assert(T.dtor == 0 && T.postblit == 2); 1008 1009 // dtor will be called by GC finalizers 1010 aa1 = null; 1011 aa2 = null; 1012 aa3 = null; 1013 GC.runFinalizers((cast(char*)(&entryDtor))[0 .. 1]); 1014 assert(T.dtor == 6 && T.postblit == 2); 1015 } 1016 1017 // Ensure the newaa struct layout (used for static initialization) is in sync 1018 unittest 1019 { 1020 import newaa = core.internal.newaa; 1021 static assert(newaa.Impl.sizeof == Impl.sizeof); 1022 // ensure compatible types and offsets 1023 static foreach (i; 0 .. Impl.tupleof.length) 1024 { 1025 // for bucket array and Flags, we "compatible" types, not exactly the same types. 1026 static if (__traits(identifier, Impl.tupleof[i]) == "buckets" 1027 || __traits(identifier, Impl.tupleof[i]) == "flags") 1028 static assert(Impl.tupleof[i].sizeof == newaa.Impl.tupleof[i].sizeof); 1029 else 1030 static assert(is(typeof(Impl.tupleof[i]) == typeof(newaa.Impl.tupleof[i]))); 1031 1032 static assert(Impl.tupleof[i].offsetof == newaa.Impl.tupleof[i].offsetof); 1033 } 1034 }