diff options
-rw-r--r-- | src/tools/wasm-reduce.cpp | 120 |
1 files changed, 75 insertions, 45 deletions
diff --git a/src/tools/wasm-reduce.cpp b/src/tools/wasm-reduce.cpp index ed2b9b564..747b93b95 100644 --- a/src/tools/wasm-reduce.cpp +++ b/src/tools/wasm-reduce.cpp @@ -330,8 +330,8 @@ struct Reducer loadWorking(); reduced = 0; funcsSeen = 0; - // before we do any changes, it should be valid to write out the module: - // size should be as expected, and output should be as expected + // Before we do any changes, it should be valid to write out the module: + // size should be as expected, and output should be as expected. ProgramResult result; if (!writeAndTestReduction(result)) { std::cerr << "\n|! WARNING: writing before destructive reduction fails, " @@ -475,35 +475,6 @@ struct Reducer // since we don't need to duplicate work that they do void visitExpression(Expression* curr) { - if (getFunction() && curr == getFunction()->body) { - // At the top level, we can try to reduce anything to an unreachable or a - // nop, and it is useful to do so when possible. - if (!curr->is<Unreachable>() && !curr->is<Nop>() && - shouldTryToReduce(1000)) { - auto* save = curr; - Unreachable un; - Nop nop; - bool useUnreachable = getFunction()->getResults() != Type::none; - if (useUnreachable) { - replaceCurrent(&un); - } else { - replaceCurrent(&nop); - } - if (writeAndTestReduction()) { - if (useUnreachable) { - replaceCurrent(builder->makeUnreachable()); - } else { - replaceCurrent(builder->makeNop()); - } - std::cerr << "| body emptied (" << getFunction()->name - << ")\n"; - noteReduction(); - return; - } else { - replaceCurrent(save); - } - } - } // type-based reductions if (curr->type == Type::none) { if (tryToReduceCurrentToNop()) { @@ -842,7 +813,7 @@ struct Reducer return shrank; } - void shrinkElementSegments(Module* module) { + void shrinkElementSegments() { std::cerr << "| try to simplify elem segments\n"; Expression* first = nullptr; auto it = @@ -887,11 +858,9 @@ struct Reducer } } - void visitModule(Module* curr) { - assert(curr == module.get()); - - shrinkElementSegments(curr); - + // Reduces entire functions at a time. Returns whether we did a significant + // amount of reduction that justifies doing even more. + bool reduceFunctions() { // try to remove functions std::cerr << "| try to remove functions\n"; std::vector<Name> functionNames; @@ -899,13 +868,14 @@ struct Reducer functionNames.push_back(func->name); } size_t skip = 1; + size_t maxSkip = 1; // If we just removed some functions in the previous iteration, keep trying // to remove more as this is one of the most efficient ways to reduce. - bool justRemoved = false; + bool justReduced = true; for (size_t i = 0; i < functionNames.size(); i++) { - if (!justRemoved && + if (!justReduced && functionsWeTriedToRemove.count(functionNames[i]) == 1 && - !shouldTryToReduce(std::max((factor / 100) + 1, 1000))) { + !shouldTryToReduce(std::max((factor / 5) + 1, 20000))) { continue; } std::vector<Name> names; @@ -920,25 +890,48 @@ struct Reducer if (names.size() == 0) { continue; } - std::cout << "| try to remove " << names.size() - << " functions (skip: " << skip << ")\n"; - justRemoved = tryToRemoveFunctions(names); - if (justRemoved) { + // Try to remove functions, and if that fails, try to at least empty out + // their bodies. + justReduced = tryToRemoveFunctions(names) || tryToEmptyFunctions(names); + if (justReduced) { noteReduction(names.size()); i += skip; skip = std::min(size_t(factor), 2 * skip); + maxSkip = std::max(skip, maxSkip); } else { skip = std::max(skip / 2, size_t(1)); // or 1? i += factor / 100; } } + // If maxSkip is 1 then we never reduced at all. If it is 2 then we did + // manage to reduce individual functions, but all our attempts at + // exponential growth failed. Only suggest doing a new iteration of this + // function if we did in fact manage to grow, which indicated there are lots + // of opportunities here, and it is worth focusing on this. + return maxSkip > 2; + } + + void visitModule(Module* curr) { + // The initial module given to us is our global object. As we continue to + // process things here, we may replace the module, so we should never again + // refer to curr. + assert(curr == module.get()); + curr = nullptr; + + // Reduction of entire functions at a time is very effective, and we do it + // with exponential growth and backoff, so keep doing it while it works. + while (reduceFunctions()) { + } + + shrinkElementSegments(); + // try to remove exports std::cerr << "| try to remove exports (with factor " << factor << ")\n"; std::vector<Export> exports; for (auto& exp : module->exports) { exports.push_back(*exp); } - skip = 1; + size_t skip = 1; for (size_t i = 0; i < exports.size(); i++) { if (!shouldTryToReduce(std::max((factor / 100) + 1, 1000))) { continue; @@ -1001,6 +994,43 @@ struct Reducer } } + // Try to empty out the bodies of some functions. + bool tryToEmptyFunctions(std::vector<Name> names) { + std::vector<Expression*> oldBodies; + size_t actuallyEmptied = 0; + for (auto name : names) { + auto* func = module->getFunction(name); + auto* oldBody = func->body; + oldBodies.push_back(oldBody); + // Nothing to do for imported functions (body is nullptr) or for bodies + // that have already been as reduced as we can make them. + if (func->imported() || oldBody->is<Unreachable>() || + oldBody->is<Nop>()) { + continue; + } + actuallyEmptied++; + bool useUnreachable = func->getResults() != Type::none; + if (useUnreachable) { + func->body = builder->makeUnreachable(); + } else { + func->body = builder->makeNop(); + } + } + if (actuallyEmptied > 0 && writeAndTestReduction()) { + std::cerr << "| emptied " << actuallyEmptied << " / " + << names.size() << " functions\n"; + return true; + } else { + // Restore the bodies. + for (size_t i = 0; i < names.size(); i++) { + module->getFunction(names[i])->body = oldBodies[i]; + } + return false; + } + } + + // Try to actually remove functions. If they are somehow referred to, we will + // get a validation error and undo it. bool tryToRemoveFunctions(std::vector<Name> names) { for (auto name : names) { module->removeFunction(name); |