diff --git a/CMakeLists.txt b/CMakeLists.txt index f95d5ff7..ea4217e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -317,6 +317,7 @@ set(DREAVM_TEST_SOURCES ${DREAVM_SOURCES} test/test_interval_tree.cc test/test_intrusive_list.cc + test/test_minmax_heap.cc test/test_ring_buffer.cc test/test_sh4.cc ${asm_inc}) diff --git a/src/core/minmax_heap.h b/src/core/minmax_heap.h new file mode 100644 index 00000000..3f0424d4 --- /dev/null +++ b/src/core/minmax_heap.h @@ -0,0 +1,288 @@ +#ifndef MINMAX_HEAP_H +#define MINMAX_HEAP_H + +#include +#include +#include "core/assert.h" + +// Min-max heap implementation, based on +// http://www.akira.ruc.dk/~keld/teaching/algoritmedesign_f03/Artikler/02../Atkinson86.pdf + +namespace dvm { + +template +static inline bool mmheap_is_max_level(T index) { + T n = index + 1; + T log2 = 0; + while (n >>= 1) log2++; + return log2 % 2 == 1; +} + +template +static inline T mmheap_parent(T index) { + return (index - 1) / 2; +} + +template +static inline T mmheap_grandparent(T index) { + return mmheap_parent(mmheap_parent(index)); +} + +template +static inline bool mmheap_has_grandparent(T index) { + return mmheap_parent(index) != 0; +} + +template +static inline T mmheap_left_child(T index) { + return 2 * index + 1; +} + +template +static inline T mmheap_left_grandchild(T index) { + return mmheap_left_child(mmheap_left_child(index)); +} + +template +static inline T mmheap_is_child(T parent, T child) { + return parent == ((child - 1) / 2); +} + +template +void mmheap_sift_up( + RandomIt first, RandomIt last, Compare comp, + typename std::iterator_traits::difference_type index) { + using difference_type = + typename std::iterator_traits::difference_type; + + // can't sift up past the root + if (!index) { + return; + } + + difference_type ancestor_index = mmheap_parent(index); + bool max_level = mmheap_is_max_level(ancestor_index); + + // if the node is smaller (greater) than its parent, then it is smaller + // (greater) than all other nodes at max (min) levels up to the root. swap + // the node with its parent and check min (max) levels up to the root until + // the min-max order property is satisfied + if (comp(*(first + index), *(first + ancestor_index)) ^ max_level) { + std::swap(*(first + ancestor_index), *(first + index)); + index = ancestor_index; + } + // if the node is greater (smaller) than its parent, then it is greater + // (smaller) than all other nodes at min (max) levels up to the root. the + // node is in the correct order with regards to its parent, but check max + // (min) levels up to the root until the min-max order property is satisfied + else { + max_level = !max_level; + } + + while (mmheap_has_grandparent(index)) { + ancestor_index = mmheap_grandparent(index); + + // once node is greater (smaller) than parent, the min-max order property + // is satisfied + if (!(comp(*(first + index), *(first + ancestor_index)) ^ max_level)) { + break; + } + + // swap node with parent + std::swap(*(first + ancestor_index), *(first + index)); + index = ancestor_index; + } +} + +template +void mmheap_sift_down( + RandomIt first, RandomIt last, Compare comp, + typename std::iterator_traits::difference_type index) { + using difference_type = + typename std::iterator_traits::difference_type; + + bool max_level = mmheap_is_max_level(index); + difference_type size = last - first; + + while (index < size) { + // get the smallest (largest) child or grandchild + difference_type smallest = index; + + difference_type i = mmheap_left_child(index); + difference_type end = std::min(i + 2, size); + for (; i < end; i++) { + if (comp(*(first + i), *(first + smallest)) ^ max_level) { + smallest = i; + } + } + + i = mmheap_left_grandchild(index); + end = std::min(i + 4, size); + for (; i < end; i++) { + if (comp(*(first + i), *(first + smallest)) ^ max_level) { + smallest = i; + } + } + + // already the smallest (largest) node, nothing to do + if (smallest == index) { + break; + } + + // swap the node with the smallest (largest) descendant + std::swap(*(first + index), *(first + smallest)); + + // if the swapped node was a child, then the current node, its child, and + // its grandchild are all ordered correctly at this point satisfying the + // min-max order property + if (mmheap_is_child(index, smallest)) { + break; + } + + // if the node's new parent is now smaller than it, swap again + if (comp(*(first + mmheap_parent(smallest)), *(first + smallest)) ^ + max_level) { + std::swap(*(first + mmheap_parent(smallest)), *(first + smallest)); + } + + // if the swapped node was a grandchild, iteration must continue to + // ensure it's now ordered with regard to its descendants + index = smallest; + } +} + +template +bool mmheap_validate(RandomIt first, RandomIt last, Comp comp) { + using difference_type = + typename std::iterator_traits::difference_type; + + difference_type size = last - first; + + for (difference_type i = 0; i < size; i++) { + bool flip_compare = mmheap_is_max_level(i); + + // values stored at nodes on even (odd) levels are smaller (greater) than + // or equal to the values stored at their descendants + + // validate children + difference_type j = std::min(mmheap_left_child(i), size); + difference_type end = std::min(j + 2, size); + + for (; j < end; j++) { + if (!(comp(*(first + i), *(first + j)) ^ flip_compare)) { + return false; + } + } + + // validate grandchildren + j = std::min(mmheap_left_grandchild(i), size); + end = std::min(j + 4, size); + + for (; j < end; j++) { + if (!(comp(*(first + i), *(first + j)) ^ flip_compare)) { + return false; + } + } + } + + return true; +} + +template +bool mmheap_validate(RandomIt first, RandomIt last) { + return mmheap_validate( + first, last, + std::less::value_type>()); +} + +template +void mmheap_push(RandomIt first, RandomIt last, Comp comp) { + mmheap_sift_up(first, last, comp, (last - first) - 1); +} + +template +void mmheap_push(RandomIt first, RandomIt last) { + mmheap_push(first, last, + std::less::value_type>()); +} + +template +RandomIt mmheap_find_min(RandomIt first, RandomIt last, Comp comp) { + return first; +} + +template +RandomIt mmheap_find_min(RandomIt first, RandomIt last) { + return mmheap_find_min( + first, last, + std::less::value_type>()); +} + +template +RandomIt mmheap_find_max(RandomIt first, RandomIt last, Comp comp) { + using difference_type = + typename std::iterator_traits::difference_type; + + difference_type size = last - first; + + if (size == 1) { + // root must be the max + return first; + } else if (size == 2) { + // root's child must be the max + return first + 1; + } else { + // must be the larger of the two children + if (comp(*(first + 1), *(first + 2))) { + return first + 2; + } else { + return first + 1; + } + } +} + +template +RandomIt mmheap_find_max(RandomIt first, RandomIt last) { + return mmheap_find_max( + first, last, + std::less::value_type>()); +} + +template +void mmheap_pop_min(RandomIt first, RandomIt last, Comp comp) { + if (first == last) { + return; + } + + RandomIt min = mmheap_find_min(first, last, comp); + std::swap(*min, *--last); + mmheap_sift_down(first, last, comp, std::distance(first, min)); +} + +template +void mmheap_pop_min(RandomIt first, RandomIt last) { + mmheap_pop_min( + first, last, + std::less::value_type>()); +} + +template +void mmheap_pop_max(RandomIt first, RandomIt last, Comp comp) { + if (first == last) { + return; + } + + RandomIt max = mmheap_find_max(first, last, comp); + std::swap(*max, *--last); + mmheap_sift_down(first, last, comp, std::distance(first, max)); +} + +template +void mmheap_pop_max(RandomIt first, RandomIt last) { + mmheap_pop_max( + first, last, + std::less::value_type>()); +} +} + +#endif diff --git a/src/core/ring_buffer.h b/src/core/ring_buffer.h index b49fcab4..0028c752 100644 --- a/src/core/ring_buffer.h +++ b/src/core/ring_buffer.h @@ -122,24 +122,6 @@ class RingBuffer { front_++; } - void Insert(const iterator &it, const T &el) { - size_t end = front_ + max_ - 1; - - // if the buffer isn't full, increase its size - if (back_ < end) { - end = back_++; - } - - // shift old elements over by one - while ((end - front_) != it.index_) { - buffer_[end % max_] = buffer_[(end - 1) % max_]; - end--; - } - - // add new element - buffer_[end % max_] = el; - } - private: T *buffer_; const size_t max_; diff --git a/src/jit/ir/passes/register_allocation_pass.cc b/src/jit/ir/passes/register_allocation_pass.cc index c3e771ce..807ec036 100644 --- a/src/jit/ir/passes/register_allocation_pass.cc +++ b/src/jit/ir/passes/register_allocation_pass.cc @@ -1,4 +1,5 @@ #include "core/core.h" +#include "core/minmax_heap.h" #include "emu/profiler.h" #include "jit/ir/passes/register_allocation_pass.h" @@ -16,15 +17,25 @@ static inline bool RegisterCanStore(const Register &r, ValueTy type) { return r.value_types & (1 << type); } -RegisterSet::RegisterSet(int max_registers) : live_(max_registers) { +struct LiveIntervalSort { + bool operator()(const Interval *lhs, const Interval *rhs) const { + return GetOrdinal(lhs->next->instr()) < GetOrdinal(rhs->next->instr()); + } +}; + +RegisterSet::RegisterSet(int max_registers) { free_ = new int[max_registers]; + live_ = new Interval *[max_registers]; } -RegisterSet::~RegisterSet() { delete[] free_; } +RegisterSet::~RegisterSet() { + delete[] free_; + delete[] live_; +} void RegisterSet::Clear() { num_free_ = 0; - live_.Clear(); + num_live_ = 0; } int RegisterSet::PopRegister() { @@ -37,33 +48,36 @@ int RegisterSet::PopRegister() { void RegisterSet::PushRegister(int reg) { free_[num_free_++] = reg; } Interval *RegisterSet::HeadInterval() { - if (live_.Empty()) { + if (!num_live_) { return nullptr; } - return live_.front(); + auto it = dvm::mmheap_find_min(live_, live_ + num_live_, LiveIntervalSort()); + return *it; } Interval *RegisterSet::TailInterval() { - if (live_.Empty()) { + if (!num_live_) { return nullptr; } - return live_.back(); + auto it = dvm::mmheap_find_max(live_, live_ + num_live_, LiveIntervalSort()); + return *it; } -void RegisterSet::PopHeadInterval() { live_.PopFront(); } +void RegisterSet::PopHeadInterval() { + dvm::mmheap_pop_min(live_, live_ + num_live_, LiveIntervalSort()); + num_live_--; +} -void RegisterSet::PopTailInterval() { live_.PopBack(); } +void RegisterSet::PopTailInterval() { + dvm::mmheap_pop_max(live_, live_ + num_live_, LiveIntervalSort()); + num_live_--; +} void RegisterSet::InsertInterval(Interval *interval) { - auto it = std::lower_bound(live_.begin(), live_.end(), - GetOrdinal(interval->next->instr()), - [](const Interval *lhs, int rhs) { - return GetOrdinal(lhs->next->instr()) < rhs; - }); - - live_.Insert(it, interval); + live_[num_live_++] = interval; + dvm::mmheap_push(live_, live_ + num_live_, LiveIntervalSort()); } RegisterAllocationPass::RegisterAllocationPass(const Backend &backend) diff --git a/src/jit/ir/passes/register_allocation_pass.h b/src/jit/ir/passes/register_allocation_pass.h index 66c2c7b6..2a0aff2f 100644 --- a/src/jit/ir/passes/register_allocation_pass.h +++ b/src/jit/ir/passes/register_allocation_pass.h @@ -1,7 +1,7 @@ #ifndef REGISTER_ALLOCATION_PASS_H #define REGISTER_ALLOCATION_PASS_H -#include "core/ring_buffer.h" +#include #include "jit/backend/backend.h" #include "jit/ir/passes/pass_runner.h" @@ -39,7 +39,8 @@ class RegisterSet { int *free_, num_free_; // intervals used by this register set, sorted in order of next use - RingBuffer live_; + Interval **live_; + int num_live_; }; class RegisterAllocationPass : public Pass { diff --git a/test/test_minmax_heap.cc b/test/test_minmax_heap.cc new file mode 100644 index 00000000..73017065 --- /dev/null +++ b/test/test_minmax_heap.cc @@ -0,0 +1,117 @@ +#include +#include "core/minmax_heap.h" + +TEST(MinMaxHeap, PopEmpty) { + std::vector elements; + + // shouldn't do anything, just sanity checking that it doesn't crash + dvm::mmheap_pop_min(elements.begin(), elements.end()); + ASSERT_TRUE(dvm::mmheap_validate(elements.begin(), elements.end())); + + dvm::mmheap_pop_max(elements.begin(), elements.end()); + ASSERT_TRUE(dvm::mmheap_validate(elements.begin(), elements.end())); +} + +TEST(MinMaxHeap, PopMinRoot) { + std::vector elements = {1}; + + dvm::mmheap_pop_min(elements.begin(), elements.end()); + ASSERT_EQ(elements.back(), 1); + elements.pop_back(); + + ASSERT_TRUE(dvm::mmheap_validate(elements.begin(), elements.end())); +} + +TEST(MinMaxHeap, PopMaxRoot) { + std::vector elements = {1}; + + dvm::mmheap_pop_min(elements.begin(), elements.end()); + ASSERT_EQ(elements.back(), 1); + elements.pop_back(); + + ASSERT_TRUE(dvm::mmheap_validate(elements.begin(), elements.end())); +} + +TEST(MinMaxHeap, PopMax1) { + std::vector elements = {1}; + + dvm::mmheap_pop_max(elements.begin(), elements.end()); + ASSERT_EQ(elements.back(), 1); + elements.pop_back(); + + ASSERT_TRUE(dvm::mmheap_validate(elements.begin(), elements.end())); +} + +TEST(MinMaxHeap, PopMax2) { + std::vector elements = {1, 2}; + + dvm::mmheap_pop_max(elements.begin(), elements.end()); + ASSERT_EQ(elements.back(), 2); + elements.pop_back(); + + ASSERT_TRUE(dvm::mmheap_validate(elements.begin(), elements.end())); +} + +TEST(MinMaxHeap, PopMax3) { + { + std::vector elements = {1, 2, 3}; + + dvm::mmheap_pop_max(elements.begin(), elements.end()); + ASSERT_EQ(elements.back(), 3); + elements.pop_back(); + + ASSERT_TRUE(dvm::mmheap_validate(elements.begin(), elements.end())); + } + + { + std::vector elements = {1, 3, 2}; + + dvm::mmheap_pop_max(elements.begin(), elements.end()); + ASSERT_EQ(elements.back(), 3); + elements.pop_back(); + + ASSERT_TRUE(dvm::mmheap_validate(elements.begin(), elements.end())); + } +} + +TEST(MinMaxHeap, PushPopMinN) { + static const int N = 1337; + + std::vector elements = {}; + + for (int i = 0; i < N; i++) { + elements.push_back(i); + dvm::mmheap_push(elements.begin(), elements.end()); + + ASSERT_TRUE(dvm::mmheap_validate(elements.begin(), elements.end())); + } + + for (int i = 0; i < N; i++) { + dvm::mmheap_pop_min(elements.begin(), elements.end()); + ASSERT_EQ(elements.back(), i); + elements.pop_back(); + + ASSERT_TRUE(dvm::mmheap_validate(elements.begin(), elements.end())); + } +} + +TEST(MinMaxHeap, PushPopMaxN) { + static const int N = 1337; + + std::vector elements = {}; + + for (int i = 0; i < N; i++) { + elements.push_back(i); + dvm::mmheap_push(elements.begin(), elements.end()); + + ASSERT_TRUE(dvm::mmheap_validate(elements.begin(), elements.end())); + } + + for (int i = N - 1; i >= 0; i--) { + dvm::mmheap_pop_max(elements.begin(), elements.end()); + ASSERT_EQ(elements.back(), i); + elements.pop_back(); + + ASSERT_TRUE(dvm::mmheap_validate(elements.begin(), elements.end())); + } +} diff --git a/test/test_ring_buffer.cc b/test/test_ring_buffer.cc index b5dd3189..67f67f1b 100644 --- a/test/test_ring_buffer.cc +++ b/test/test_ring_buffer.cc @@ -47,27 +47,6 @@ TEST_F(RingTest, PushBack) { ASSERT_EQ(2, (int)items.Size()); } -TEST_F(RingTest, Insert) { - items.PushBack(7); - items.PushBack(9); - - // insert at beginning - items.Insert(items.begin(), 3); - ASSERT_EQ(3, items.front()); - ASSERT_EQ(3, *items.begin()); - ASSERT_EQ(7, items.back()); - ASSERT_EQ(7, *(--items.end())); - ASSERT_EQ(2, (int)items.Size()); - - // insert at end - items.Insert(++items.begin(), 5); - ASSERT_EQ(3, items.front()); - ASSERT_EQ(3, *items.begin()); - ASSERT_EQ(5, items.back()); - ASSERT_EQ(5, *(--items.end())); - ASSERT_EQ(2, (int)items.Size()); -} - // remove tests TEST_F(RingTest, PopBack) { items.PushBack(7);