1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- 2 * vim: set ts=8 sts=4 et sw=4 tw=99: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef ds_InlineTable_h 8 #define ds_InlineTable_h 9 10 #include "mozilla/Move.h" 11 12 #include "jsalloc.h" 13 14 #include "js/HashTable.h" 15 16 namespace js { 17 18 namespace detail { 19 20 template <typename InlineEntry, 21 typename Entry, 22 typename Table, 23 typename HashPolicy, 24 typename AllocPolicy, 25 size_t InlineEntries> 26 class InlineTable 27 { 28 private: 29 using TablePtr = typename Table::Ptr; 30 using TableAddPtr = typename Table::AddPtr; 31 using TableRange = typename Table::Range; 32 using Lookup = typename HashPolicy::Lookup; 33 34 size_t inlNext_; 35 size_t inlCount_; 36 InlineEntry inl_[InlineEntries]; 37 Table table_; 38 39 #ifdef DEBUG 40 template <typename Key> keyNonZero(const Key & key)41 static bool keyNonZero(const Key& key) { 42 // Zero as tombstone means zero keys are invalid. 43 return !!key; 44 } 45 #endif 46 inlineStart()47 InlineEntry* inlineStart() { 48 MOZ_ASSERT(!usingTable()); 49 return inl_; 50 } 51 inlineStart()52 const InlineEntry* inlineStart() const { 53 MOZ_ASSERT(!usingTable()); 54 return inl_; 55 } 56 inlineEnd()57 InlineEntry* inlineEnd() { 58 MOZ_ASSERT(!usingTable()); 59 return inl_ + inlNext_; 60 } 61 inlineEnd()62 const InlineEntry* inlineEnd() const { 63 MOZ_ASSERT(!usingTable()); 64 return inl_ + inlNext_; 65 } 66 usingTable()67 bool usingTable() const { 68 return inlNext_ > InlineEntries; 69 } 70 switchToTable()71 MOZ_MUST_USE bool switchToTable() { 72 MOZ_ASSERT(inlNext_ == InlineEntries); 73 74 if (table_.initialized()) { 75 table_.clear(); 76 } else { 77 if (!table_.init(count())) 78 return false; 79 MOZ_ASSERT(table_.initialized()); 80 } 81 82 InlineEntry* end = inlineEnd(); 83 for (InlineEntry* it = inlineStart(); it != end; ++it) { 84 if (it->key && !it->moveTo(table_)) 85 return false; 86 } 87 88 inlNext_ = InlineEntries + 1; 89 MOZ_ASSERT(table_.count() == inlCount_); 90 MOZ_ASSERT(usingTable()); 91 return true; 92 } 93 94 MOZ_NEVER_INLINE switchAndAdd(const InlineEntry & entry)95 MOZ_MUST_USE bool switchAndAdd(const InlineEntry& entry) { 96 if (!switchToTable()) 97 return false; 98 99 return entry.putNew(table_); 100 } 101 102 public: 103 static const size_t SizeOfInlineEntries = sizeof(InlineEntry) * InlineEntries; 104 105 explicit InlineTable(AllocPolicy a = AllocPolicy()) 106 : inlNext_(0), 107 inlCount_(0), 108 table_(a) 109 { } 110 111 class Ptr 112 { 113 friend class InlineTable; 114 115 protected: 116 Entry entry_; 117 TablePtr tablePtr_; 118 InlineEntry* inlPtr_; 119 bool isInlinePtr_; 120 Ptr(TablePtr p)121 explicit Ptr(TablePtr p) 122 : entry_(p.found() ? &*p : nullptr), 123 tablePtr_(p), 124 isInlinePtr_(false) 125 { } 126 Ptr(InlineEntry * inlineEntry)127 explicit Ptr(InlineEntry* inlineEntry) 128 : entry_(inlineEntry), 129 inlPtr_(inlineEntry), 130 isInlinePtr_(true) 131 { } 132 133 void operator==(const Ptr& other); 134 135 public: 136 // Leaves Ptr uninitialized. Ptr()137 Ptr() { 138 #ifdef DEBUG 139 inlPtr_ = (InlineEntry*) 0xbad; 140 isInlinePtr_ = true; 141 #endif 142 } 143 144 // Default copy constructor works for this structure. 145 found()146 bool found() const { 147 return isInlinePtr_ ? bool(inlPtr_) : tablePtr_.found(); 148 } 149 150 explicit operator bool() const { 151 return found(); 152 } 153 154 bool operator==(const Ptr& other) const { 155 MOZ_ASSERT(found() && other.found()); 156 if (isInlinePtr_ != other.isInlinePtr_) 157 return false; 158 if (isInlinePtr_) 159 return inlPtr_ == other.inlPtr_; 160 return tablePtr_ == other.tablePtr_; 161 } 162 163 bool operator!=(const Ptr& other) const { 164 return !(*this == other); 165 } 166 167 Entry& operator*() { 168 MOZ_ASSERT(found()); 169 return entry_; 170 } 171 172 Entry* operator->() { 173 MOZ_ASSERT(found()); 174 return &entry_; 175 } 176 }; 177 178 class AddPtr 179 { 180 friend class InlineTable; 181 182 protected: 183 Entry entry_; 184 TableAddPtr tableAddPtr_; 185 InlineEntry* inlAddPtr_; 186 bool isInlinePtr_; 187 // Indicates whether inlAddPtr is a found result or an add pointer. 188 bool inlPtrFound_; 189 AddPtr(InlineEntry * ptr,bool found)190 AddPtr(InlineEntry* ptr, bool found) 191 : entry_(ptr), 192 inlAddPtr_(ptr), 193 isInlinePtr_(true), 194 inlPtrFound_(found) 195 {} 196 AddPtr(const TableAddPtr & p)197 explicit AddPtr(const TableAddPtr& p) 198 : entry_(p.found() ? &*p : nullptr), 199 tableAddPtr_(p), 200 isInlinePtr_(false) 201 { } 202 203 public: AddPtr()204 AddPtr() {} 205 found()206 bool found() const { 207 return isInlinePtr_ ? inlPtrFound_ : tableAddPtr_.found(); 208 } 209 210 explicit operator bool() const { 211 return found(); 212 } 213 214 bool operator==(const AddPtr& other) const { 215 MOZ_ASSERT(found() && other.found()); 216 if (isInlinePtr_ != other.isInlinePtr_) 217 return false; 218 if (isInlinePtr_) 219 return inlAddPtr_ == other.inlAddPtr_; 220 return tableAddPtr_ == other.tableAddPtr_; 221 } 222 223 bool operator!=(const AddPtr& other) const { 224 return !(*this == other); 225 } 226 227 Entry& operator*() { 228 MOZ_ASSERT(found()); 229 return entry_; 230 } 231 232 Entry* operator->() { 233 MOZ_ASSERT(found()); 234 return &entry_; 235 } 236 }; 237 count()238 size_t count() const { 239 return usingTable() ? table_.count() : inlCount_; 240 } 241 empty()242 bool empty() const { 243 return usingTable() ? table_.empty() : !inlCount_; 244 } 245 clear()246 void clear() { 247 inlNext_ = 0; 248 inlCount_ = 0; 249 } 250 251 MOZ_ALWAYS_INLINE lookup(const Lookup & l)252 Ptr lookup(const Lookup& l) { 253 MOZ_ASSERT(keyNonZero(l)); 254 255 if (usingTable()) 256 return Ptr(table_.lookup(l)); 257 258 InlineEntry* end = inlineEnd(); 259 for (InlineEntry* it = inlineStart(); it != end; ++it) { 260 if (it->key && HashPolicy::match(it->key, l)) 261 return Ptr(it); 262 } 263 264 return Ptr(nullptr); 265 } 266 267 MOZ_ALWAYS_INLINE lookupForAdd(const Lookup & l)268 AddPtr lookupForAdd(const Lookup& l) { 269 MOZ_ASSERT(keyNonZero(l)); 270 271 if (usingTable()) 272 return AddPtr(table_.lookupForAdd(l)); 273 274 InlineEntry* end = inlineEnd(); 275 for (InlineEntry* it = inlineStart(); it != end; ++it) { 276 if (it->key && HashPolicy::match(it->key, l)) 277 return AddPtr(it, true); 278 } 279 280 // The add pointer that's returned here may indicate the limit entry of 281 // the linear space, in which case the |add| operation will initialize 282 // the table if necessary and add the entry there. 283 return AddPtr(inlineEnd(), false); 284 } 285 286 template <typename KeyInput, 287 typename... Args> 288 MOZ_ALWAYS_INLINE add(AddPtr & p,KeyInput && key,Args &&...args)289 MOZ_MUST_USE bool add(AddPtr& p, KeyInput&& key, Args&&... args) { 290 MOZ_ASSERT(!p); 291 MOZ_ASSERT(keyNonZero(key)); 292 293 if (p.isInlinePtr_) { 294 InlineEntry* addPtr = p.inlAddPtr_; 295 MOZ_ASSERT(addPtr == inlineEnd()); 296 297 // Switching to table mode before we add this pointer. 298 if (addPtr == inlineStart() + InlineEntries) { 299 if (!switchToTable()) 300 return false; 301 return table_.putNew(mozilla::Forward<KeyInput>(key), 302 mozilla::Forward<Args>(args)...); 303 } 304 305 MOZ_ASSERT(!p.found()); 306 MOZ_ASSERT(uintptr_t(inlineEnd()) == uintptr_t(p.inlAddPtr_)); 307 addPtr->update(mozilla::Forward<KeyInput>(key), 308 mozilla::Forward<Args>(args)...); 309 ++inlCount_; 310 ++inlNext_; 311 return true; 312 } 313 314 return table_.add(p.tableAddPtr_, 315 mozilla::Forward<KeyInput>(key), 316 mozilla::Forward<Args>(args)...); 317 } 318 remove(Ptr & p)319 void remove(Ptr& p) { 320 MOZ_ASSERT(p); 321 if (p.isInlinePtr_) { 322 MOZ_ASSERT(inlCount_ > 0); 323 MOZ_ASSERT(p.inlPtr_->key != nullptr); 324 p.inlPtr_->key = nullptr; 325 --inlCount_; 326 return; 327 } 328 MOZ_ASSERT(table_.initialized() && usingTable()); 329 table_.remove(p.tablePtr_); 330 } 331 remove(const Lookup & l)332 void remove(const Lookup& l) { 333 if (Ptr p = lookup(l)) 334 remove(p); 335 } 336 337 class Range 338 { 339 friend class InlineTable; 340 341 protected: 342 TableRange tableRange_; 343 InlineEntry* cur_; 344 InlineEntry* end_; 345 bool isInline_; 346 Range(TableRange r)347 explicit Range(TableRange r) 348 : cur_(nullptr), 349 end_(nullptr), 350 isInline_(false) 351 { 352 tableRange_ = r; 353 MOZ_ASSERT(!isInlineRange()); 354 } 355 Range(const InlineEntry * begin,const InlineEntry * end)356 Range(const InlineEntry* begin, const InlineEntry* end) 357 : cur_(const_cast<InlineEntry*>(begin)), 358 end_(const_cast<InlineEntry*>(end)), 359 isInline_(true) 360 { 361 advancePastNulls(cur_); 362 MOZ_ASSERT(isInlineRange()); 363 } 364 assertInlineRangeInvariants()365 bool assertInlineRangeInvariants() const { 366 MOZ_ASSERT(uintptr_t(cur_) <= uintptr_t(end_)); 367 MOZ_ASSERT_IF(cur_ != end_, cur_->key != nullptr); 368 return true; 369 } 370 isInlineRange()371 bool isInlineRange() const { 372 MOZ_ASSERT_IF(isInline_, assertInlineRangeInvariants()); 373 return isInline_; 374 } 375 advancePastNulls(InlineEntry * begin)376 void advancePastNulls(InlineEntry* begin) { 377 InlineEntry* newCur = begin; 378 while (newCur < end_ && nullptr == newCur->key) 379 ++newCur; 380 MOZ_ASSERT(uintptr_t(newCur) <= uintptr_t(end_)); 381 cur_ = newCur; 382 } 383 bumpCurPtr()384 void bumpCurPtr() { 385 MOZ_ASSERT(isInlineRange()); 386 advancePastNulls(cur_ + 1); 387 } 388 389 public: empty()390 bool empty() const { 391 return isInlineRange() ? cur_ == end_ : tableRange_.empty(); 392 } 393 front()394 Entry front() { 395 MOZ_ASSERT(!empty()); 396 if (isInlineRange()) 397 return Entry(cur_); 398 return Entry(&tableRange_.front()); 399 } 400 popFront()401 void popFront() { 402 MOZ_ASSERT(!empty()); 403 if (isInlineRange()) 404 bumpCurPtr(); 405 else 406 tableRange_.popFront(); 407 } 408 }; 409 all()410 Range all() const { 411 return usingTable() ? Range(table_.all()) : Range(inlineStart(), inlineEnd()); 412 } 413 }; 414 415 } // namespace detail 416 417 // A map with InlineEntries number of entries kept inline in an array. 418 // 419 // The Key type must be zeroable as zeros are used as tombstone keys. 420 // The Value type must have a default constructor. 421 // 422 // The API is very much like HashMap's. 423 template <typename Key, 424 typename Value, 425 size_t InlineEntries, 426 typename HashPolicy = DefaultHasher<Key>, 427 typename AllocPolicy = TempAllocPolicy> 428 class InlineMap 429 { 430 using Map = HashMap<Key, Value, HashPolicy, AllocPolicy>; 431 432 struct InlineEntry 433 { 434 Key key; 435 Value value; 436 437 template <typename KeyInput, typename ValueInput> updateInlineEntry438 void update(KeyInput&& key, ValueInput&& value) { 439 this->key = mozilla::Forward<KeyInput>(key); 440 this->value = mozilla::Forward<ValueInput>(value); 441 } 442 moveToInlineEntry443 MOZ_MUST_USE bool moveTo(Map& map) { 444 return map.putNew(mozilla::Move(key), mozilla::Move(value)); 445 } 446 }; 447 448 class Entry 449 { 450 using MapEntry = typename Map::Entry; 451 452 MapEntry* mapEntry_; 453 InlineEntry* inlineEntry_; 454 455 public: 456 Entry() = default; 457 Entry(MapEntry * mapEntry)458 explicit Entry(MapEntry* mapEntry) 459 : mapEntry_(mapEntry), 460 inlineEntry_(nullptr) 461 { } 462 Entry(InlineEntry * inlineEntry)463 explicit Entry(InlineEntry* inlineEntry) 464 : mapEntry_(nullptr), 465 inlineEntry_(inlineEntry) 466 { } 467 key()468 const Key& key() const { 469 MOZ_ASSERT(!!mapEntry_ != !!inlineEntry_); 470 if (mapEntry_) 471 return mapEntry_->key(); 472 return inlineEntry_->key; 473 } 474 value()475 Value& value() { 476 MOZ_ASSERT(!!mapEntry_ != !!inlineEntry_); 477 if (mapEntry_) 478 return mapEntry_->value(); 479 return inlineEntry_->value; 480 } 481 }; 482 483 using Impl = detail::InlineTable<InlineEntry, Entry, 484 Map, HashPolicy, AllocPolicy, 485 InlineEntries>; 486 487 Impl impl_; 488 489 public: 490 using Table = Map; 491 using Ptr = typename Impl::Ptr; 492 using AddPtr = typename Impl::AddPtr; 493 using Range = typename Impl::Range; 494 using Lookup = typename HashPolicy::Lookup; 495 496 static const size_t SizeOfInlineEntries = Impl::SizeOfInlineEntries; 497 498 explicit InlineMap(AllocPolicy a = AllocPolicy()) impl_(a)499 : impl_(a) 500 { } 501 count()502 size_t count() const { 503 return impl_.count(); 504 } 505 empty()506 bool empty() const { 507 return impl_.empty(); 508 } 509 clear()510 void clear() { 511 impl_.clear(); 512 } 513 all()514 Range all() const { 515 return impl_.all(); 516 } 517 518 MOZ_ALWAYS_INLINE lookup(const Lookup & l)519 Ptr lookup(const Lookup& l) { 520 return impl_.lookup(l); 521 } 522 523 MOZ_ALWAYS_INLINE has(const Lookup & l)524 bool has(const Lookup& l) const { 525 return const_cast<InlineMap*>(this)->lookup(l).found(); 526 } 527 528 MOZ_ALWAYS_INLINE lookupForAdd(const Lookup & l)529 AddPtr lookupForAdd(const Lookup& l) { 530 return impl_.lookupForAdd(l); 531 } 532 533 template <typename KeyInput, typename ValueInput> 534 MOZ_ALWAYS_INLINE add(AddPtr & p,KeyInput && key,ValueInput && value)535 MOZ_MUST_USE bool add(AddPtr& p, KeyInput&& key, ValueInput&& value) { 536 return impl_.add(p, mozilla::Forward<KeyInput>(key), mozilla::Forward<ValueInput>(value)); 537 } 538 539 template <typename KeyInput, typename ValueInput> put(KeyInput && key,ValueInput && value)540 MOZ_MUST_USE bool put(KeyInput&& key, ValueInput&& value) { 541 AddPtr p = lookupForAdd(key); 542 if (p) { 543 p->value() = mozilla::Forward<ValueInput>(value); 544 return true; 545 } 546 return add(p, mozilla::Forward<KeyInput>(key), mozilla::Forward<ValueInput>(value)); 547 } 548 remove(Ptr & p)549 void remove(Ptr& p) { 550 impl_.remove(p); 551 } 552 remove(const Lookup & l)553 void remove(const Lookup& l) { 554 impl_.remove(l); 555 } 556 }; 557 558 // A set with InlineEntries number of entries kept inline in an array. 559 // 560 // The T type must be zeroable as zeros are used as tombstone keys. 561 // The T type must have a default constructor. 562 // 563 // The API is very much like HashMap's. 564 template <typename T, 565 size_t InlineEntries, 566 typename HashPolicy = DefaultHasher<T>, 567 typename AllocPolicy = TempAllocPolicy> 568 class InlineSet 569 { 570 using Set = HashSet<T, HashPolicy, AllocPolicy>; 571 572 struct InlineEntry 573 { 574 T key; 575 576 template <typename TInput> updateInlineEntry577 void update(TInput&& key) { 578 this->key = mozilla::Forward<TInput>(key); 579 } 580 moveToInlineEntry581 MOZ_MUST_USE bool moveTo(Set& set) { 582 return set.putNew(mozilla::Move(key)); 583 } 584 }; 585 586 class Entry 587 { 588 using SetEntry = typename Set::Entry; 589 590 SetEntry* setEntry_; 591 InlineEntry* inlineEntry_; 592 593 public: 594 Entry() = default; 595 Entry(const SetEntry * setEntry)596 explicit Entry(const SetEntry* setEntry) 597 : setEntry_(const_cast<SetEntry*>(setEntry)), 598 inlineEntry_(nullptr) 599 { } 600 Entry(InlineEntry * inlineEntry)601 explicit Entry(InlineEntry* inlineEntry) 602 : setEntry_(nullptr), 603 inlineEntry_(inlineEntry) 604 { } 605 T()606 operator T() const { 607 MOZ_ASSERT(!!setEntry_ != !!inlineEntry_); 608 if (setEntry_) 609 return *setEntry_; 610 return inlineEntry_->key; 611 } 612 }; 613 614 using Impl = detail::InlineTable<InlineEntry, Entry, 615 Set, HashPolicy, AllocPolicy, 616 InlineEntries>; 617 618 Impl impl_; 619 620 public: 621 using Table = Set; 622 using Ptr = typename Impl::Ptr; 623 using AddPtr = typename Impl::AddPtr; 624 using Range = typename Impl::Range; 625 using Lookup = typename HashPolicy::Lookup; 626 627 static const size_t SizeOfInlineEntries = Impl::SizeOfInlineEntries; 628 629 explicit InlineSet(AllocPolicy a = AllocPolicy()) impl_(a)630 : impl_(a) 631 { } 632 count()633 size_t count() const { 634 return impl_.count(); 635 } 636 empty()637 bool empty() const { 638 return impl_.empty(); 639 } 640 clear()641 void clear() { 642 impl_.clear(); 643 } 644 all()645 Range all() const { 646 return impl_.all(); 647 } 648 649 MOZ_ALWAYS_INLINE lookup(const Lookup & l)650 Ptr lookup(const Lookup& l) { 651 return impl_.lookup(l); 652 } 653 654 MOZ_ALWAYS_INLINE has(const Lookup & l)655 bool has(const Lookup& l) const { 656 return const_cast<InlineSet*>(this)->lookup(l).found(); 657 } 658 659 MOZ_ALWAYS_INLINE lookupForAdd(const Lookup & l)660 AddPtr lookupForAdd(const Lookup& l) { 661 return impl_.lookupForAdd(l); 662 } 663 664 template <typename TInput> 665 MOZ_ALWAYS_INLINE add(AddPtr & p,TInput && key)666 MOZ_MUST_USE bool add(AddPtr& p, TInput&& key) { 667 return impl_.add(p, mozilla::Forward<TInput>(key)); 668 } 669 670 template <typename TInput> put(TInput && key)671 MOZ_MUST_USE bool put(TInput&& key) { 672 AddPtr p = lookupForAdd(key); 673 return p ? true : add(p, mozilla::Forward<TInput>(key)); 674 } 675 remove(Ptr & p)676 void remove(Ptr& p) { 677 impl_.remove(p); 678 } 679 remove(const Lookup & l)680 void remove(const Lookup& l) { 681 impl_.remove(l); 682 } 683 }; 684 685 } // namespace js 686 687 #endif // ds_InlineTable_h 688