diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/passes/ReorderGlobals.cpp | 92 | ||||
-rw-r--r-- | src/support/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/support/topological_orders.cpp | 159 | ||||
-rw-r--r-- | src/support/topological_orders.h | 196 |
4 files changed, 200 insertions, 248 deletions
diff --git a/src/passes/ReorderGlobals.cpp b/src/passes/ReorderGlobals.cpp index dab4625fe..9f934bb17 100644 --- a/src/passes/ReorderGlobals.cpp +++ b/src/passes/ReorderGlobals.cpp @@ -77,7 +77,7 @@ struct ReorderGlobals : public Pass { // use the index of the global in the original ordering to identify each // global. A different ordering is then a vector of old indices, saying where // each element comes from, which is logically a mapping between indices. - using IndexIndexMap = std::vector<Index>; + using IndexIndexMap = std::vector<size_t>; // We will also track counts of uses for each global. We use floating-point // values here since while the initial counts are integers, we will be @@ -93,8 +93,8 @@ struct ReorderGlobals : public Pass { // To do so we construct a map from each global to those it depends on. We // also build the reverse map, of those that it is depended upon by. struct Dependencies { - std::unordered_map<Index, std::unordered_set<Index>> dependsOn; - std::unordered_map<Index, std::unordered_set<Index>> dependedUpon; + TopologicalSort::Graph dependsOn; + TopologicalSort::Graph dependedUpon; }; void run(Module* module) override { @@ -133,17 +133,26 @@ struct ReorderGlobals : public Pass { } // Compute dependencies. - Dependencies deps; + std::vector<std::unordered_set<size_t>> dependsOn(globals.size()), + dependedUpon(globals.size()); for (Index i = 0; i < globals.size(); i++) { auto& global = globals[i]; if (!global->imported()) { for (auto* get : FindAll<GlobalGet>(global->init).list) { auto getIndex = originalIndices[get->name]; - deps.dependsOn[i].insert(getIndex); - deps.dependedUpon[getIndex].insert(i); + dependsOn[i].insert(getIndex); + dependedUpon[getIndex].insert(i); } } } + Dependencies deps; + deps.dependsOn.reserve(globals.size()); + deps.dependedUpon.reserve(globals.size()); + for (size_t i = 0; i < globals.size(); ++i) { + deps.dependsOn.emplace_back(dependsOn[i].begin(), dependsOn[i].end()); + deps.dependedUpon.emplace_back(dependedUpon[i].begin(), + dependedUpon[i].end()); + } // Compute various sorting options. All the options use a variation of the // algorithm in doSort() below, see there for more details; the only @@ -190,16 +199,7 @@ struct ReorderGlobals : public Pass { double const EXPONENTIAL_FACTOR = 0.095; IndexCountMap sumCounts(globals.size()), exponentialCounts(globals.size()); - std::vector<std::vector<size_t>> dependenceGraph(globals.size()); - for (size_t i = 0; i < globals.size(); ++i) { - if (auto it = deps.dependsOn.find(i); it != deps.dependsOn.end()) { - for (auto dep : it->second) { - dependenceGraph[i].push_back(dep); - } - } - } - - for (auto global : TopologicalSort::sort(dependenceGraph)) { + for (auto global : TopologicalSort::sort(deps.dependsOn)) { // We can compute this global's count as in the sorted order all the // values it cares about are resolved. Start with the self-count, then // add the deps. @@ -236,8 +236,6 @@ struct ReorderGlobals : public Pass { IndexIndexMap doSort(const IndexCountMap& counts, const Dependencies& deps, Module* module) { - auto& globals = module->globals; - // To sort the globals we do a simple greedy approach of always picking the // global with the highest count at every point in time, subject to the // constraint that we can only emit globals that have all of their @@ -249,51 +247,31 @@ struct ReorderGlobals : public Pass { // global $c that depends on $b, and $c might have a much higher use count // than $a. For that reason we try several variations of this with different // counts, see earlier. - // - // Sort the globals into the optimal order based on the counts, ignoring - // dependencies for now. - std::vector<Index> sortedGlobals; - sortedGlobals.resize(globals.size()); - for (Index i = 0; i < globals.size(); ++i) { - sortedGlobals[i] = i; - } - std::sort( - sortedGlobals.begin(), sortedGlobals.end(), [&](Index a, Index b) { - // Imports always go first. The binary writer takes care of this itself - // anyhow, but it is better to do it here in the IR so we can actually - // see what the final layout will be. - auto aImported = globals[a]->imported(); - auto bImported = globals[b]->imported(); - if (aImported != bImported) { - return aImported; - } - - // Sort by the counts. Higher counts come first. - auto aCount = counts[a]; - auto bCount = counts[b]; - if (aCount != bCount) { - return aCount > bCount; - } - - // Break ties using the original order, which means just using the - // indices we have. - return a < b; - }); // Now use that optimal order to create an ordered graph that includes the // dependencies. The final order will be the minimum topological sort of // this graph. - std::vector<std::pair<Index, std::vector<Index>>> graph; - graph.reserve(globals.size()); - for (auto i : sortedGlobals) { - std::vector<Index> children; - if (auto it = deps.dependedUpon.find(i); it != deps.dependedUpon.end()) { - children = std::vector<Index>(it->second.begin(), it->second.end()); + return TopologicalSort::minSort(deps.dependedUpon, [&](size_t a, size_t b) { + // Imports always go first. The binary writer takes care of this itself + // anyhow, but it is better to do it here in the IR so we can actually + // see what the final layout will be. + auto aImported = module->globals[a]->imported(); + auto bImported = module->globals[b]->imported(); + if (aImported != bImported) { + return aImported; + } + + // Sort by the counts. Higher counts come first. + auto aCount = counts[a]; + auto bCount = counts[b]; + if (aCount != bCount) { + return aCount > bCount; } - graph.emplace_back(i, std::move(children)); - } - return TopologicalSort::minSortOf(graph.begin(), graph.end()); + // Break ties using the original order, which means just using the + // indices. + return a < b; + }); } // Given an indexing of the globals and the counts of how many times each is diff --git a/src/support/CMakeLists.txt b/src/support/CMakeLists.txt index 20470a3cd..54ee7b206 100644 --- a/src/support/CMakeLists.txt +++ b/src/support/CMakeLists.txt @@ -14,7 +14,6 @@ set(support_SOURCES safe_integer.cpp string.cpp threads.cpp - topological_orders.cpp utilities.cpp ${support_HEADERS} ) diff --git a/src/support/topological_orders.cpp b/src/support/topological_orders.cpp deleted file mode 100644 index aeec95d18..000000000 --- a/src/support/topological_orders.cpp +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2024 WebAssembly Community Group participants - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <algorithm> -#include <cassert> - -#include "topological_orders.h" - -namespace wasm { - -TopologicalOrders::Selector -TopologicalOrders::Selector::select(TopologicalOrders& ctx, - SelectionMethod method = InPlace) { - assert(count >= 1); - assert(start + count <= ctx.buf.size()); - if (method == MinHeap) { - ctx.buf[start] = ctx.popChoice(); - } - auto selection = ctx.buf[start]; - // The next selector will select the next index and will not be able to choose - // the vertex we just selected. - Selector next = {start + 1, count - 1, 0}; - // Append any child that this selection makes available to the choices for the - // next selector. - for (auto child : ctx.graph[selection]) { - assert(ctx.indegrees[child] > 0); - if (--ctx.indegrees[child] == 0) { - if (method == MinHeap) { - ctx.pushChoice(child); - } else { - ctx.buf[next.start + next.count] = child; - } - ++next.count; - } - } - return next; -} - -std::optional<TopologicalOrders::Selector> -TopologicalOrders::Selector::advance(TopologicalOrders& ctx) { - assert(count >= 1); - // Undo the current selection. Backtrack by incrementing the in-degree for - // each child of the vertex we are unselecting. No need to remove the newly - // unavailable children from the buffer; they will be overwritten with valid - // selections. - auto unselected = ctx.buf[start]; - for (auto child : ctx.graph[unselected]) { - ++ctx.indegrees[child]; - } - if (index == count - 1) { - // We are wrapping back to the original configuration. The current selection - // element needs to go back on the end and everything else needs to be - // shifted back to its original location. This ensures that we leave - // everything how we found it so the previous selector can make its next - // selection without observing anything having changed in the meantime. - for (size_t i = 1; i < count; ++i) { - ctx.buf[start + i - 1] = ctx.buf[start + i]; - } - ctx.buf[start + count - 1] = unselected; - return std::nullopt; - } - // Otherwise, just swap the next selection into the first position and - // finalize the selection. - std::swap(ctx.buf[start], ctx.buf[start + ++index]); - return select(ctx); -} - -TopologicalOrders::TopologicalOrders(const Graph& graph, SelectionMethod method) - : graph(graph), indegrees(graph.size()), buf(graph.size()) { - if (graph.size() == 0) { - return; - } - // Find the in-degree of each vertex. - for (const auto& vertex : graph) { - for (auto child : vertex) { - ++indegrees[child]; - } - } - // Set up the first selector with its possible selections. - selectors.reserve(graph.size()); - selectors.push_back({0, 0, 0}); - auto& first = selectors.back(); - for (size_t i = 0; i < graph.size(); ++i) { - if (indegrees[i] == 0) { - if (method == MinHeap) { - pushChoice(i); - } else { - buf[first.count] = i; - } - ++first.count; - } - } - // Initialize the full stack of selectors. - while (selectors.size() < graph.size()) { - selectors.push_back(selectors.back().select(*this, method)); - } - selectors.back().select(*this, method); -} - -TopologicalOrders& TopologicalOrders::operator++() { - // Find the last selector that can be advanced, popping any that cannot. - std::optional<Selector> next; - while (!selectors.empty() && !(next = selectors.back().advance(*this))) { - selectors.pop_back(); - } - if (!next) { - // No selector could be advanced, so we've seen every possible ordering. - assert(selectors.empty()); - return *this; - } - // We've advanced the last selector on the stack, so initialize the - // subsequent selectors. - assert(selectors.size() < graph.size()); - selectors.push_back(*next); - while (selectors.size() < graph.size()) { - selectors.push_back(selectors.back().select(*this)); - } - - return *this; -} - -void TopologicalOrders::pushChoice(size_t choice) { - choiceHeap.push_back(choice); - std::push_heap(choiceHeap.begin(), choiceHeap.end(), std::greater<size_t>{}); -} - -size_t TopologicalOrders::popChoice() { - std::pop_heap(choiceHeap.begin(), choiceHeap.end(), std::greater<size_t>{}); - auto choice = choiceHeap.back(); - choiceHeap.pop_back(); - return choice; -} - -namespace TopologicalSort { - -std::vector<size_t> sort(const Graph& graph) { - return *TopologicalOrders(graph, TopologicalOrders::InPlace); -} - -std::vector<size_t> minSort(const Graph& graph) { - return *TopologicalOrders(graph, TopologicalOrders::MinHeap); -} - -} // namespace TopologicalSort - -} // namespace wasm diff --git a/src/support/topological_orders.h b/src/support/topological_orders.h index 1a5828247..925ad1117 100644 --- a/src/support/topological_orders.h +++ b/src/support/topological_orders.h @@ -17,11 +17,14 @@ #ifndef wasm_support_topological_orders_h #define wasm_support_topological_orders_h +#include <algorithm> #include <cassert> #include <cstddef> +#include <functional> #include <optional> #include <type_traits> #include <unordered_map> +#include <variant> #include <vector> namespace wasm { @@ -32,17 +35,12 @@ namespace TopologicalSort { using Graph = std::vector<std::vector<size_t>>; // Return a topological sort of the vertices in the given adjacency graph. -std::vector<size_t> sort(const Graph& graph); - -// Return the topological sort of the vertices in the given adjacency graph that -// is closest to their original order. Implemented using a min-heap internally. -std::vector<size_t> minSort(const Graph& graph); +inline std::vector<size_t> sort(const Graph& graph); // A utility that finds a topological sort of a graph with arbitrary element // types. The provided iterators must be to pairs of elements and collections of // their children. -template<typename It, std::vector<size_t> (*TopoSort)(const Graph&) = sort> -decltype(auto) sortOf(It begin, It end) { +template<typename It> decltype(auto) sortOf(It begin, It end) { using T = std::remove_cv_t<typename It::value_type::first_type>; std::unordered_map<T, size_t> indices; std::vector<T> elements; @@ -64,26 +62,31 @@ decltype(auto) sortOf(It begin, It end) { // Compute the topological order and convert back to original elements. std::vector<T> order; order.reserve(elements.size()); - auto indexOrder = TopoSort(indexGraph); - for (auto i : indexOrder) { + for (auto i : sort(indexGraph)) { order.emplace_back(std::move(elements[i])); } return order; } -// Find the topological sort of a graph with arbitrary element types that is -// closest to their original order. -template<typename It> decltype(auto) minSortOf(It begin, It end) { - return sortOf<It, minSort>(begin, end); -} +// Return the topological sort of the vertices in the given adjacency graph that +// is lexicographically minimal with respect to the provided comparator on +// vertex indices. Implemented using a min-heap internally. +template<typename F = std::less<size_t>> +std::vector<size_t> minSort(const Graph& graph, F cmp = std::less<size_t>{}); } // namespace TopologicalSort +template<typename F = std::monostate> struct TopologicalOrdersImpl; + // A utility for iterating through all possible topological orders in a graph // using an extension of Kahn's algorithm (see // https://en.wikipedia.org/wiki/Topological_sorting) that iteratively makes all // possible choices for each position of the output order. -struct TopologicalOrders { +using TopologicalOrders = TopologicalOrdersImpl<std::monostate>; + +// The template parameter `Cmp` is only used as in internal implementation +// detail of `minSort`. +template<typename Cmp> struct TopologicalOrdersImpl { using Graph = TopologicalSort::Graph; using value_type = const std::vector<size_t>; using difference_type = std::ptrdiff_t; @@ -93,25 +96,75 @@ struct TopologicalOrders { // Takes an adjacency list, where the list for each vertex is a sorted list of // the indices of its children, which will appear after it in the order. - TopologicalOrders(const Graph& graph) : TopologicalOrders(graph, InPlace) {} + TopologicalOrdersImpl(const Graph& graph) + : TopologicalOrdersImpl(graph, {}) {} - TopologicalOrders begin() { return TopologicalOrders(graph); } - TopologicalOrders end() { return TopologicalOrders({}); } + TopologicalOrdersImpl begin() { return TopologicalOrdersImpl(graph); } + TopologicalOrdersImpl end() { return TopologicalOrdersImpl({}); } - bool operator==(const TopologicalOrders& other) const { + bool operator==(const TopologicalOrdersImpl& other) const { return selectors.empty() == other.selectors.empty(); } - bool operator!=(const TopologicalOrders& other) const { + bool operator!=(const TopologicalOrdersImpl& other) const { return !(*this == other); } const std::vector<size_t>& operator*() const { return buf; } const std::vector<size_t>* operator->() const { return &buf; } - TopologicalOrders& operator++(); - TopologicalOrders operator++(int) { return ++(*this); } + TopologicalOrdersImpl& operator++() { + // Find the last selector that can be advanced, popping any that cannot. + std::optional<Selector> next; + while (!selectors.empty() && !(next = selectors.back().advance(*this))) { + selectors.pop_back(); + } + if (!next) { + // No selector could be advanced, so we've seen every possible ordering. + assert(selectors.empty()); + return *this; + } + // We've advanced the last selector on the stack, so initialize the + // subsequent selectors. + assert(selectors.size() < graph.size()); + selectors.push_back(*next); + while (selectors.size() < graph.size()) { + selectors.push_back(selectors.back().select(*this)); + } + + return *this; + } + TopologicalOrdersImpl operator++(int) { return ++(*this); } private: - enum SelectionMethod { InPlace, MinHeap }; - TopologicalOrders(const Graph& graph, SelectionMethod method); + TopologicalOrdersImpl(const Graph& graph, Cmp cmp) + : graph(graph), indegrees(graph.size()), buf(graph.size()), cmp(cmp) { + if (graph.size() == 0) { + return; + } + // Find the in-degree of each vertex. + for (const auto& vertex : graph) { + for (auto child : vertex) { + ++indegrees[child]; + } + } + // Set up the first selector with its possible selections. + selectors.reserve(graph.size()); + selectors.push_back({0, 0, 0}); + auto& first = selectors.back(); + for (size_t i = 0; i < graph.size(); ++i) { + if (indegrees[i] == 0) { + if constexpr (useMinHeap) { + pushChoice(i); + } else { + buf[first.count] = i; + } + ++first.count; + } + } + // Initialize the full stack of selectors. + while (selectors.size() < graph.size()) { + selectors.push_back(selectors.back().select(*this)); + } + selectors.back().select(*this); + } // The input graph given as an adjacency list with edges from vertices to // their dependent children. @@ -124,9 +177,15 @@ private: // sequence of selected vertices followed by a sequence of possible choices // for the next vertex. std::vector<size_t> buf; - // When we are finding the minimal topological order, store the possible + // When we are finding a minimal topological order, store the possible // choices in this separate min-heap instead of directly in `buf`. std::vector<size_t> choiceHeap; + // When we are finding a minimal topological order, use this function to + // compare possible choices. Empty iff we are not finding a minimal + // topological order. + Cmp cmp; + + static constexpr bool useMinHeap = !std::is_same_v<Cmp, std::monostate>; // The state for tracking the possible choices for a single vertex in the // output order. @@ -141,25 +200,100 @@ private: // Select the next available vertex, decrement in-degrees, and update the // sequence of available vertices. Return the Selector for the next vertex. - Selector select(TopologicalOrders& ctx, SelectionMethod method); + Selector select(TopologicalOrdersImpl& ctx) { + assert(count >= 1); + assert(start + count <= ctx.buf.size()); + if constexpr (TopologicalOrdersImpl::useMinHeap) { + ctx.buf[start] = ctx.popChoice(); + } + auto selection = ctx.buf[start]; + // The next selector will select the next index and will not be able to + // choose the vertex we just selected. + Selector next = {start + 1, count - 1, 0}; + // Append any child that this selection makes available to the choices for + // the next selector. + for (auto child : ctx.graph[selection]) { + assert(ctx.indegrees[child] > 0); + if (--ctx.indegrees[child] == 0) { + if constexpr (TopologicalOrdersImpl::useMinHeap) { + ctx.pushChoice(child); + } else { + ctx.buf[next.start + next.count] = child; + } + ++next.count; + } + } + return next; + } // Undo the current selection, move the next selection into the first // position and return the new selector for the next position. Returns // nullopt if advancing wraps back around to the original configuration. - std::optional<Selector> advance(TopologicalOrders& ctx); + std::optional<Selector> advance(TopologicalOrdersImpl& ctx) { + assert(count >= 1); + // Undo the current selection. Backtrack by incrementing the in-degree for + // each child of the vertex we are unselecting. No need to remove the + // newly unavailable children from the buffer; they will be overwritten + // with valid selections. + auto unselected = ctx.buf[start]; + for (auto child : ctx.graph[unselected]) { + ++ctx.indegrees[child]; + } + if (index == count - 1) { + // We are wrapping back to the original configuration. The current + // selection element needs to go back on the end and everything else + // needs to be shifted back to its original location. This ensures that + // we leave everything how we found it so the previous selector can make + // its next selection without observing anything having changed in the + // meantime. + for (size_t i = 1; i < count; ++i) { + ctx.buf[start + i - 1] = ctx.buf[start + i]; + } + ctx.buf[start + count - 1] = unselected; + return std::nullopt; + } + // Otherwise, just swap the next selection into the first position and + // finalize the selection. + std::swap(ctx.buf[start], ctx.buf[start + ++index]); + return select(ctx); + } }; - void pushChoice(size_t); - size_t popChoice(); + void pushChoice(size_t choice) { + choiceHeap.push_back(choice); + std::push_heap(choiceHeap.begin(), + choiceHeap.end(), + [&](size_t a, size_t b) { return cmp(b, a); }); + } + size_t popChoice() { + std::pop_heap(choiceHeap.begin(), + choiceHeap.end(), + [&](size_t a, size_t b) { return cmp(b, a); }); + auto choice = choiceHeap.back(); + choiceHeap.pop_back(); + return choice; + } // A stack of selectors, one for each vertex in a complete topological order. // Empty if we've already seen every possible ordering. std::vector<Selector> selectors; - friend std::vector<size_t> TopologicalSort::sort(const Graph&); - friend std::vector<size_t> TopologicalSort::minSort(const Graph&); + friend std::vector<size_t> TopologicalSort::minSort<Cmp>(const Graph&, Cmp); }; +namespace TopologicalSort { + +std::vector<size_t> sort(const Graph& graph) { + return *TopologicalOrders(graph); +} + +template<typename Cmp> +std::vector<size_t> minSort(const Graph& graph, Cmp cmp) { + return *TopologicalOrdersImpl<Cmp>(graph, cmp); +} + +} // namespace TopologicalSort + } // namespace wasm #endif // wasm_support_topological_orders_h |