| // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| #ifndef RUNTIME_VM_PORT_SET_H_ | |
| #define RUNTIME_VM_PORT_SET_H_ | |
| #include "include/dart_api.h" | |
| #include "platform/allocation.h" | |
| #include "platform/globals.h" | |
| #include "platform/utils.h" | |
| namespace dart { | |
| template <typename T /* :public PortSet<T>::Entry */> | |
| class PortSet { | |
| public: | |
| static constexpr Dart_Port kFreePort = static_cast<Dart_Port>(0); | |
| static constexpr Dart_Port kDeletedPort = static_cast<Dart_Port>(3); | |
| struct Entry : public MallocAllocated { | |
| Entry() : port(kFreePort) {} | |
| explicit Entry(Dart_Port port) : port(port) {} | |
| // Free entries have set this to 0. | |
| Dart_Port port; | |
| }; | |
| class Iterator { | |
| public: | |
| Iterator(PortSet<T>* ports, intptr_t index) : ports_(ports), index_(index) { | |
| #if defined(DEBUG) | |
| dirty_counter_ = ports_->dirty_counter_; | |
| #endif | |
| } | |
| DART_FORCE_INLINE T& operator->() const { | |
| ASSERT(index_ >= 0 && index_ < ports_->capacity_); | |
| DEBUG_ASSERT(!WasModified()); | |
| return ports_->map_[index_]; | |
| } | |
| DART_FORCE_INLINE T& operator*() const { | |
| ASSERT(index_ >= 0 && index_ < ports_->capacity_); | |
| DEBUG_ASSERT(!WasModified()); | |
| return ports_->map_[index_]; | |
| } | |
| DART_FORCE_INLINE bool operator==(const Iterator& other) const { | |
| DEBUG_ASSERT(!WasModified()); | |
| return ports_ == other.ports_ && index_ == other.index_; | |
| } | |
| DART_FORCE_INLINE bool operator!=(const Iterator& other) const { | |
| DEBUG_ASSERT(!WasModified()); | |
| return !(*this == other); | |
| } | |
| DART_FORCE_INLINE Iterator& operator++() { | |
| DEBUG_ASSERT(!WasModified()); | |
| index_++; | |
| while (index_ < ports_->capacity_) { | |
| const Dart_Port port = ports_->map_[index_].port; | |
| if (port == kFreePort || port == kDeletedPort) { | |
| index_++; | |
| continue; | |
| } else { | |
| break; | |
| } | |
| } | |
| return *this; | |
| } | |
| // The caller must ensure to call [PortSet::Rebalance] once the iterator is | |
| // not used anymore. | |
| DART_FORCE_INLINE void Delete() { | |
| DEBUG_ASSERT(!WasModified()); | |
| ports_->map_[index_] = T(); | |
| ports_->map_[index_].port = kDeletedPort; | |
| ports_->used_--; | |
| ports_->deleted_++; | |
| } | |
| private: | |
| friend class PortSet; | |
| #if defined(DEBUG) | |
| // Whether the underlying [PortSet] was modified in a way that would render | |
| // the iterator unusable. | |
| bool WasModified() const { | |
| return dirty_counter_ != ports_->dirty_counter_; | |
| } | |
| #endif | |
| PortSet<T>* ports_; | |
| intptr_t index_ = 0; | |
| #if defined(DEBUG) | |
| intptr_t dirty_counter_ = 0; | |
| #endif | |
| }; | |
| PortSet() { | |
| const intptr_t kInitialCapacity = 8; | |
| ASSERT(Utils::IsPowerOfTwo(kInitialCapacity)); | |
| map_ = new T[kInitialCapacity]; | |
| capacity_ = kInitialCapacity; | |
| } | |
| ~PortSet() { | |
| delete[] map_; | |
| map_ = nullptr; | |
| } | |
| bool IsEmpty() const { return used_ == 0; } | |
| DART_FORCE_INLINE Iterator begin() { | |
| for (intptr_t i = 0; i < capacity_; ++i) { | |
| auto& entry = map_[i]; | |
| if (entry.port != kFreePort && entry.port != kDeletedPort) { | |
| return Iterator(this, i); | |
| } | |
| } | |
| return end(); | |
| } | |
| DART_FORCE_INLINE Iterator end() { return Iterator(this, capacity_); } | |
| void Insert(const T& entry) { | |
| // Search for the first unused slot. Make use of the knowledge that here is | |
| // currently no port with this id in the port map. | |
| ASSERT(FindIndexOfPort(entry.port) < 0); | |
| intptr_t index = entry.port % capacity_; | |
| T cur = map_[index]; | |
| // Stop the search at the first found unused (free or deleted) slot. | |
| while (cur.port != kFreePort && cur.port != kDeletedPort) { | |
| index = (index + 1) % capacity_; | |
| cur = map_[index]; | |
| } | |
| // Insert the newly created port at the index. | |
| ASSERT(map_[index].port == kFreePort || map_[index].port == kDeletedPort); | |
| if (map_[index].port == kDeletedPort) { | |
| deleted_--; | |
| } | |
| map_[index] = entry; | |
| ASSERT(FindIndexOfPort(entry.port) >= 0); | |
| // Increment number of used slots and grow if necessary. | |
| used_++; | |
| MaintainInvariants(); | |
| #if defined(DEBUG) | |
| dirty_counter_++; | |
| #endif | |
| } | |
| Iterator TryLookup(Dart_Port port) { | |
| const intptr_t index = FindIndexOfPort(port); | |
| if (index >= 0) return Iterator(this, index); | |
| return Iterator(this, capacity_); | |
| } | |
| bool Contains(Dart_Port port) { return FindIndexOfPort(port) >= 0; } | |
| // To be called if an iterator was used to delete an entry. | |
| void Rebalance() { MaintainInvariants(); } | |
| private: | |
| intptr_t FindIndexOfPort(Dart_Port port) { | |
| // ILLEGAL_PORT (0) is used as a sentinel value in Entry.port. The loop | |
| // below could return the index to a deleted port when we are searching for | |
| // port id ILLEGAL_PORT. Return -1 immediately to indicate the port | |
| // does not exist. | |
| if (port == ILLEGAL_PORT) { | |
| return -1; | |
| } | |
| ASSERT(port != ILLEGAL_PORT); | |
| intptr_t index = port % capacity_; | |
| intptr_t start_index = index; | |
| T entry = map_[index]; | |
| while (entry.port != kFreePort) { | |
| if (entry.port == port) { | |
| return index; | |
| } | |
| index = (index + 1) % capacity_; | |
| // Prevent endless loops. | |
| ASSERT(index != start_index); | |
| entry = map_[index]; | |
| } | |
| return -1; | |
| } | |
| void MaintainInvariants() { | |
| const intptr_t empty = capacity_ - used_ - deleted_; | |
| if (used_ > ((capacity_ / 4) * 3)) { | |
| // Grow the port map. | |
| Rehash(capacity_ * 2); | |
| } else if (empty < deleted_) { | |
| // Rehash without growing the table to flush the deleted slots out of the | |
| // map. | |
| Rehash(capacity_); | |
| } | |
| } | |
| void Rehash(intptr_t new_capacity) { | |
| T* new_ports = new T[new_capacity]; | |
| for (auto entry : *this) { | |
| intptr_t new_index = entry.port % new_capacity; | |
| while (new_ports[new_index].port != 0) { | |
| new_index = (new_index + 1) % new_capacity; | |
| } | |
| new_ports[new_index] = entry; | |
| } | |
| delete[] map_; | |
| map_ = new_ports; | |
| capacity_ = new_capacity; | |
| deleted_ = 0; | |
| #if defined(DEBUG) | |
| dirty_counter_++; | |
| #endif | |
| } | |
| T* map_ = nullptr; | |
| intptr_t capacity_ = 0; | |
| intptr_t used_ = 0; | |
| intptr_t deleted_ = 0; | |
| #if defined(DEBUG) | |
| intptr_t dirty_counter_ = 0; | |
| #endif | |
| }; | |
| } // namespace dart | |
| #endif // RUNTIME_VM_PORT_SET_H_ |