|  | // 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_PLATFORM_PRIORITY_QUEUE_H_ | 
|  | #define RUNTIME_PLATFORM_PRIORITY_QUEUE_H_ | 
|  |  | 
|  | #include "platform/assert.h" | 
|  | #include "platform/globals.h" | 
|  | #include "platform/hashmap.h" | 
|  | #include "platform/utils.h" | 
|  |  | 
|  | namespace dart { | 
|  |  | 
|  | // A min-priority queue with deletion support. | 
|  | // | 
|  | // The [PriorityQueue] allows insertion of entries with a priority [P] and a | 
|  | // value [V]. The minimum element can be queried in O(1) time. | 
|  | // Insertion/Deletion operations have O(N) time. | 
|  | // | 
|  | // In addition to the normal insert/minimum/remove-minimum operations this | 
|  | // priority queue allows deletion-by-value. We have therefore an invariant | 
|  | // is that the value must be unique amongst all entries. | 
|  | template <typename P, typename V> | 
|  | class PriorityQueue { | 
|  | public: | 
|  | static constexpr intptr_t kMinimumSize = 16; | 
|  |  | 
|  | struct Entry { | 
|  | P priority; | 
|  | V value; | 
|  | }; | 
|  |  | 
|  | PriorityQueue() : hashmap_(&MatchFun, kMinimumSize) { | 
|  | min_heap_size_ = kMinimumSize; | 
|  | min_heap_ = | 
|  | reinterpret_cast<Entry*>(malloc(sizeof(Entry) * min_heap_size_)); | 
|  | if (min_heap_ == nullptr) FATAL("Cannot allocate memory."); | 
|  | size_ = 0; | 
|  | } | 
|  |  | 
|  | ~PriorityQueue() { free(min_heap_); } | 
|  |  | 
|  | // Whether the queue is empty. | 
|  | bool IsEmpty() const { return size_ == 0; } | 
|  |  | 
|  | // Inserts a new entry with [priority] and [value], requires there to be no | 
|  | // existing entry with given [value]. | 
|  | void Insert(const P& priority, const V& value) { | 
|  | ASSERT(!ContainsValue(value)); | 
|  |  | 
|  | if (size_ == min_heap_size_) { | 
|  | Resize(min_heap_size_ << 1); | 
|  | } | 
|  |  | 
|  | Set(size_, {priority, value}); | 
|  | BubbleUp(size_); | 
|  |  | 
|  | size_++; | 
|  | } | 
|  |  | 
|  | // Returns a reference to the minimum entry. | 
|  | // | 
|  | // The caller can access it's priority and value in read-only mode only. | 
|  | const Entry& Minimum() const { | 
|  | ASSERT(!IsEmpty()); | 
|  | return min_heap_[0]; | 
|  | } | 
|  |  | 
|  | // Removes the minimum entry. | 
|  | void RemoveMinimum() { | 
|  | ASSERT(!IsEmpty()); | 
|  | RemoveAt(0); | 
|  | } | 
|  |  | 
|  | // Removes an existing entry with the given [value]. | 
|  | // | 
|  | // Returns true if such an entry was removed. | 
|  | bool RemoveByValue(const V& value) { | 
|  | auto entry = FindMapEntry(value); | 
|  | if (entry != nullptr) { | 
|  | const intptr_t offset = ValueOfMapEntry(entry); | 
|  | RemoveAt(offset); | 
|  |  | 
|  | ASSERT(hashmap_.size() == size_); | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Whether the priority queue contains an entry with the given [value]. | 
|  | bool ContainsValue(const V& value) { return FindMapEntry(value) != nullptr; } | 
|  |  | 
|  | // Changes the priority of an existing entry with given [value] or adds a | 
|  | // new entry. | 
|  | bool InsertOrChangePriority(const P& priority, const V& value) { | 
|  | auto map_entry = FindMapEntry(value); | 
|  | if (map_entry == nullptr) { | 
|  | Insert(priority, value); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | const intptr_t offset = ValueOfMapEntry(map_entry); | 
|  | ASSERT(offset < size_); | 
|  |  | 
|  | Entry& entry = min_heap_[offset]; | 
|  | entry.priority = priority; | 
|  | if (offset == 0) { | 
|  | BubbleDown(offset); | 
|  | } else { | 
|  | intptr_t parent = (offset - 1) / 2; | 
|  | intptr_t diff = entry.priority - min_heap_[parent].priority; | 
|  | if (diff < 0) { | 
|  | BubbleUp(offset); | 
|  | } else if (diff > 0) { | 
|  | BubbleDown(offset); | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | #ifdef TESTING | 
|  | intptr_t min_heap_size() { return min_heap_size_; } | 
|  | #endif  // TESTING | 
|  |  | 
|  | private: | 
|  | // Utility functions dealing with the SimpleHashMap interface. | 
|  | static bool MatchFun(void* key1, void* key2) { return key1 == key2; } | 
|  |  | 
|  | SimpleHashMap::Entry* FindMapEntry(const V& key, bool insert = false) { | 
|  | return hashmap_.Lookup(CastKey(key), HashKey(key), insert); | 
|  | } | 
|  | void RemoveMapEntry(const V& key) { | 
|  | ASSERT(FindMapEntry(key) != nullptr); | 
|  | hashmap_.Remove(CastKey(key), HashKey(key)); | 
|  | } | 
|  | void SetMapEntry(const V& key, intptr_t value) { | 
|  | FindMapEntry(key, /*insert=*/true)->value = reinterpret_cast<void*>(value); | 
|  | } | 
|  | static uint32_t HashKey(const V& key) { | 
|  | return static_cast<uint32_t>(reinterpret_cast<intptr_t>(CastKey(key))); | 
|  | } | 
|  | static intptr_t ValueOfMapEntry(SimpleHashMap::Entry* entry) { | 
|  | return reinterpret_cast<intptr_t>(entry->value); | 
|  | } | 
|  | static void* CastKey(const V& key) { | 
|  | return reinterpret_cast<void*>((const_cast<V&>(key))); | 
|  | } | 
|  |  | 
|  | void RemoveAt(intptr_t offset) { | 
|  | ASSERT(offset < size_); | 
|  |  | 
|  | size_--; | 
|  |  | 
|  | if (offset == size_) { | 
|  | RemoveMapEntry(min_heap_[offset].value); | 
|  | } else { | 
|  | Replace(offset, size_); | 
|  | BubbleDown(offset); | 
|  | } | 
|  |  | 
|  | if (size_ <= (min_heap_size_ >> 2) && | 
|  | kMinimumSize <= (min_heap_size_ >> 1)) { | 
|  | Resize(min_heap_size_ >> 1); | 
|  | } | 
|  | } | 
|  |  | 
|  | void BubbleUp(intptr_t offset) { | 
|  | while (true) { | 
|  | if (offset == 0) return; | 
|  |  | 
|  | intptr_t parent = (offset - 1) / 2; | 
|  | if (min_heap_[parent].priority > min_heap_[offset].priority) { | 
|  | Swap(parent, offset); | 
|  | } | 
|  | offset = parent; | 
|  | } | 
|  | } | 
|  |  | 
|  | void BubbleDown(intptr_t offset) { | 
|  | while (true) { | 
|  | intptr_t left_child_index = 2 * offset + 1; | 
|  | bool has_left_child = left_child_index < size_; | 
|  |  | 
|  | if (!has_left_child) return; | 
|  |  | 
|  | intptr_t smallest_index = offset; | 
|  |  | 
|  | if (min_heap_[left_child_index].priority < min_heap_[offset].priority) { | 
|  | smallest_index = left_child_index; | 
|  | } | 
|  |  | 
|  | intptr_t right_child_index = left_child_index + 1; | 
|  | bool has_right_child = right_child_index < size_; | 
|  | if (has_right_child) { | 
|  | if (min_heap_[right_child_index].priority < | 
|  | min_heap_[smallest_index].priority) { | 
|  | smallest_index = right_child_index; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (offset == smallest_index) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | Swap(offset, smallest_index); | 
|  | offset = smallest_index; | 
|  | } | 
|  | } | 
|  |  | 
|  | void Set(intptr_t offset1, const Entry& entry) { | 
|  | min_heap_[offset1] = entry; | 
|  | SetMapEntry(entry.value, offset1); | 
|  | } | 
|  |  | 
|  | void Swap(intptr_t offset1, intptr_t offset2) { | 
|  | Entry temp = min_heap_[offset1]; | 
|  | min_heap_[offset1] = min_heap_[offset2]; | 
|  | min_heap_[offset2] = temp; | 
|  |  | 
|  | SetMapEntry(min_heap_[offset1].value, offset1); | 
|  | SetMapEntry(min_heap_[offset2].value, offset2); | 
|  | } | 
|  |  | 
|  | void Replace(intptr_t index, intptr_t with_other) { | 
|  | RemoveMapEntry(min_heap_[index].value); | 
|  |  | 
|  | const Entry& entry = min_heap_[with_other]; | 
|  | SetMapEntry(entry.value, index); | 
|  | min_heap_[index] = entry; | 
|  | } | 
|  |  | 
|  | void Resize(intptr_t new_min_heap_size) { | 
|  | ASSERT(size_ < new_min_heap_size); | 
|  | ASSERT(new_min_heap_size != min_heap_size_); | 
|  |  | 
|  | Entry* new_backing = reinterpret_cast<Entry*>( | 
|  | realloc(min_heap_, sizeof(Entry) * new_min_heap_size)); | 
|  |  | 
|  | if (new_backing == nullptr) FATAL("Cannot allocate memory."); | 
|  |  | 
|  | min_heap_ = new_backing; | 
|  | min_heap_size_ = new_min_heap_size; | 
|  | } | 
|  |  | 
|  | // The array is representing a tree structure with guaranteed log(n) height. | 
|  | // It has the property that the value of node N is always equal or smaller | 
|  | // than the value of N's children. Furthermore it is a "dense" tree in the | 
|  | // sense that all rows/layers of the tree are fully occupied except the last | 
|  | // one. The way to represent such "dense" trees is via an array that allows | 
|  | // finding left/right children by <2*index+1><2*index+2> and the parent by | 
|  | // <(index-1)/2>. | 
|  | // | 
|  | // Insertion operations can be performed by adding one more entry at the end | 
|  | // (bottom right) and bubbling it up until the tree invariant is satisfied | 
|  | // again. | 
|  | // | 
|  | // Deletion operations can be performed by replacing the minimum element | 
|  | // (first entry) by the last entry (bottom right) and bubbling it down until | 
|  | // the tree invariant is satisfied again. | 
|  | Entry* min_heap_; | 
|  | intptr_t min_heap_size_; | 
|  | intptr_t size_; | 
|  | SimpleHashMap hashmap_; | 
|  | }; | 
|  |  | 
|  | }  // namespace dart | 
|  |  | 
|  | #endif  // RUNTIME_PLATFORM_PRIORITY_QUEUE_H_ |