/* * Copyright 2022 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. */ // // Sorts globals by their static use count. This helps reduce the size of wasm // binaries because fewer bytes are needed to encode references to frequently // used globals. // #include "memory" #include "ir/find_all.h" #include "pass.h" #include "support/topological_sort.h" #include "wasm.h" namespace wasm { using NameCountMap = std::unordered_map>; struct UseCountScanner : public WalkerPass> { bool isFunctionParallel() override { return true; } bool modifiesBinaryenIR() override { return false; } UseCountScanner(NameCountMap& counts) : counts(counts) {} std::unique_ptr create() override { return std::make_unique(counts); } void visitGlobalGet(GlobalGet* curr) { // We can't add a new element to the map in parallel. assert(counts.count(curr->name) > 0); counts[curr->name]++; } void visitGlobalSet(GlobalSet* curr) { assert(counts.count(curr->name) > 0); counts[curr->name]++; } private: NameCountMap& counts; }; struct ReorderGlobals : public Pass { // Whether to always reorder globals, even if there are very few and the // benefit is minor. That is useful for testing. bool always; ReorderGlobals(bool always) : always(always) {} void run(Module* module) override { if (module->globals.size() < 128 && !always) { // The module has so few globals that they all fit in a single-byte U32LEB // value, so no reordering we can do can actually decrease code size. Note // that this is the common case with wasm MVP modules where the only // globals are typically the stack pointer and perhaps a handful of others // (however, features like wasm GC there may be a great many globals). return; } NameCountMap counts; // Fill in info, as we'll operate on it in parallel. for (auto& global : module->globals) { counts[global->name]; } // Count uses. UseCountScanner scanner(counts); scanner.run(getPassRunner(), module); scanner.runOnModuleCode(getPassRunner(), module); // Do a toplogical sort to ensure we keep dependencies before the things // that need them. For example, if $b's definition depends on $a then $b // must appear later: // // (global $a i32 (i32.const 10)) // (global $b i32 (global.get $a)) ;; $b depends on $a // struct DependencySort : TopologicalSort { Module& wasm; const NameCountMap& counts; std::unordered_map> deps; DependencySort(Module& wasm, const NameCountMap& counts) : wasm(wasm), counts(counts) { // Sort a list of global names by their counts. auto sort = [&](std::vector& globals) { std::stable_sort( globals.begin(), globals.end(), [&](const Name& a, const Name& b) { return counts.at(a) < counts.at(b); }); }; // Sort the globals. std::vector sortedNames; for (auto& global : wasm.globals) { sortedNames.push_back(global->name); } sort(sortedNames); // Everything is a root (we need to emit all globals). for (auto global : sortedNames) { push(global); } // The dependencies are the globals referred to. for (auto& global : wasm.globals) { if (global->imported()) { continue; } std::vector vec; for (auto* get : FindAll(global->init).list) { vec.push_back(get->name); } sort(vec); deps[global->name] = std::move(vec); } } void pushPredecessors(Name global) { for (auto pred : deps[global]) { push(pred); } } }; std::unordered_map sortedIndexes; for (auto global : DependencySort(*module, counts)) { auto index = sortedIndexes.size(); sortedIndexes[global] = index; } std::sort( module->globals.begin(), module->globals.end(), [&](const std::unique_ptr& a, const std::unique_ptr& b) { return sortedIndexes[a->name] < sortedIndexes[b->name]; }); module->updateMaps(); } }; Pass* createReorderGlobalsPass() { return new ReorderGlobals(false); } Pass* createReorderGlobalsAlwaysPass() { return new ReorderGlobals(true); } } // namespace wasm