// 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 ::Entry */> class PortSet { public: static constexpr Dart_Port kFreePort = static_cast(0); static constexpr Dart_Port kDeletedPort = static_cast(3); struct Entry : public MallocAllocated { Entry() : port(kFreePort) {} // Free entries have set this to 0. Dart_Port port; }; class Iterator { public: Iterator(PortSet* 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* ports_; intptr_t index_ = 0; #if defined(DEBUG) intptr_t dirty_counter_ = 0; #endif }; PortSet() { static 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_