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