diff options
Diffstat (limited to 'src/tools/wasm-reduce.cpp')
-rw-r--r-- | src/tools/wasm-reduce.cpp | 562 |
1 files changed, 345 insertions, 217 deletions
diff --git a/src/tools/wasm-reduce.cpp b/src/tools/wasm-reduce.cpp index e064abbcb..87f64c1ae 100644 --- a/src/tools/wasm-reduce.cpp +++ b/src/tools/wasm-reduce.cpp @@ -23,22 +23,22 @@ // much more debuggable manner). // -#include <memory> #include <cstdio> #include <cstdlib> +#include <memory> +#include "ir/branch-utils.h" +#include "ir/iteration.h" +#include "ir/literal-utils.h" +#include "ir/properties.h" #include "pass.h" -#include "support/command-line.h" #include "support/colors.h" +#include "support/command-line.h" #include "support/file.h" #include "support/path.h" #include "support/timing.h" -#include "wasm-io.h" #include "wasm-builder.h" -#include "ir/branch-utils.h" -#include "ir/iteration.h" -#include "ir/literal-utils.h" -#include "ir/properties.h" +#include "wasm-io.h" #include "wasm-validator.h" #ifdef _WIN32 #ifndef NOMINMAX @@ -50,18 +50,18 @@ std::string GetLastErrorStdStr() { DWORD error = GetLastError(); if (error) { LPVOID lpMsgBuf; - DWORD bufLen = FormatMessage( - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - error, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR) &lpMsgBuf, - 0, NULL ); + DWORD bufLen = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&lpMsgBuf, + 0, + NULL); if (bufLen) { LPCSTR lpMsgStr = (LPCSTR)lpMsgBuf; - std::string result(lpMsgStr, lpMsgStr+bufLen); + std::string result(lpMsgStr, lpMsgStr + bufLen); LocalFree(lpMsgBuf); return result; } @@ -80,9 +80,7 @@ struct ProgramResult { double time; ProgramResult() = default; - ProgramResult(std::string command) { - getFromExecution(command); - } + ProgramResult(std::string command) { getFromExecution(command); } #ifdef _WIN32 void getFromExecution(std::string command) { @@ -100,9 +98,9 @@ struct ProgramResult { // Create a pipe for the child process's STDOUT. !CreatePipe(&hChildStd_OUT_Rd, &hChildStd_OUT_Wr, &saAttr, 0) || // Ensure the read handle to the pipe for STDOUT is not inherited. - !SetHandleInformation(hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0) - ) { - Fatal() << "CreatePipe \"" << command << "\" failed: " << GetLastErrorStdStr() << ".\n"; + !SetHandleInformation(hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0)) { + Fatal() << "CreatePipe \"" << command + << "\" failed: " << GetLastErrorStdStr() << ".\n"; } STARTUPINFO si; @@ -116,18 +114,19 @@ struct ProgramResult { ZeroMemory(&pi, sizeof(pi)); // Start the child process. - if (!CreateProcess(NULL, // No module name (use command line) - (LPSTR)command.c_str(),// Command line - NULL, // Process handle not inheritable - NULL, // Thread handle not inheritable - TRUE, // Set handle inheritance to TRUE - 0, // No creation flags - NULL, // Use parent's environment block - NULL, // Use parent's starting directory - &si, // Pointer to STARTUPINFO structure - &pi ) // Pointer to PROCESS_INFORMATION structure + if (!CreateProcess(NULL, // No module name (use command line) + (LPSTR)command.c_str(), // Command line + NULL, // Process handle not inheritable + NULL, // Thread handle not inheritable + TRUE, // Set handle inheritance to TRUE + 0, // No creation flags + NULL, // Use parent's environment block + NULL, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi) // Pointer to PROCESS_INFORMATION structure ) { - Fatal() << "CreateProcess \"" << command << "\" failed: " << GetLastErrorStdStr() << ".\n"; + Fatal() << "CreateProcess \"" << command + << "\" failed: " << GetLastErrorStdStr() << ".\n"; } // Wait until child process exits. @@ -156,8 +155,10 @@ struct ProgramResult { PeekNamedPipe(hChildStd_OUT_Rd, NULL, 0, NULL, &dwTotal, NULL); while (dwTotalRead < dwTotal) { - bSuccess = ReadFile(hChildStd_OUT_Rd, chBuf, BUFSIZE - 1, &dwRead, NULL); - if (!bSuccess || dwRead == 0) break; + bSuccess = + ReadFile(hChildStd_OUT_Rd, chBuf, BUFSIZE - 1, &dwRead, NULL); + if (!bSuccess || dwRead == 0) + break; chBuf[dwRead] = 0; dwTotalRead += dwRead; output.append(chBuf); @@ -166,7 +167,7 @@ struct ProgramResult { timer.stop(); time = timer.getTotal(); } -#else // POSIX +#else // POSIX // runs the command and notes the output // TODO: also stderr, not just stdout? void getFromExecution(std::string command) { @@ -174,10 +175,15 @@ struct ProgramResult { timer.start(); // do this using just core stdio.h and stdlib.h, for portability // sadly this requires two invokes - code = system(("timeout " + std::to_string(timeout) + "s " + command + " > /dev/null 2> /dev/null").c_str()); + code = system(("timeout " + std::to_string(timeout) + "s " + command + + " > /dev/null 2> /dev/null") + .c_str()); const int MAX_BUFFER = 1024; char buffer[MAX_BUFFER]; - FILE *stream = popen(("timeout " + std::to_string(timeout) + "s " + command + " 2> /dev/null").c_str(), "r"); + FILE* stream = popen( + ("timeout " + std::to_string(timeout) + "s " + command + " 2> /dev/null") + .c_str(), + "r"); while (fgets(buffer, MAX_BUFFER, stream) != NULL) { output.append(buffer); } @@ -190,16 +196,13 @@ struct ProgramResult { bool operator==(ProgramResult& other) { return code == other.code && output == other.output; } - bool operator!=(ProgramResult& other) { - return !(*this == other); - } + bool operator!=(ProgramResult& other) { return !(*this == other); } - bool failed() { - return code != 0; - } + bool failed() { return code != 0; } void dump(std::ostream& o) { - o << "[ProgramResult] code: " << code << " stdout: \n" << output << "[====]\nin " << time << " seconds\n[/ProgramResult]\n"; + o << "[ProgramResult] code: " << code << " stdout: \n" + << output << "[====]\nin " << time << " seconds\n[/ProgramResult]\n"; } }; @@ -210,7 +213,7 @@ inline std::ostream& operator<<(std::ostream& o, ProgramResult& result) { return o; } -} +} // namespace std ProgramResult expected; @@ -219,14 +222,22 @@ ProgramResult expected; // case we may try again but much later. static std::unordered_set<Name> functionsWeTriedToRemove; -struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor<Reducer>>> { +struct Reducer + : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor<Reducer>>> { std::string command, test, working; bool binary, deNan, verbose, debugInfo; // test is the file we write to that the command will operate on // working is the current temporary state, the reduction so far - Reducer(std::string command, std::string test, std::string working, bool binary, bool deNan, bool verbose, bool debugInfo) : - command(command), test(test), working(working), binary(binary), deNan(deNan), verbose(verbose), debugInfo(debugInfo) {} + Reducer(std::string command, + std::string test, + std::string working, + bool binary, + bool deNan, + bool verbose, + bool debugInfo) + : command(command), test(test), working(working), binary(binary), + deNan(deNan), verbose(verbose), debugInfo(debugInfo) {} // runs passes in order to reduce, until we can't reduce any more // the criterion here is wasm binary size @@ -261,28 +272,32 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< "--reorder-locals", "--simplify-locals --vacuum", "--strip", - "--vacuum" - }; + "--vacuum"}; auto oldSize = file_size(working); bool more = true; while (more) { - //std::cerr << "| starting passes loop iteration\n"; + // std::cerr << "| starting passes loop iteration\n"; more = false; - // try both combining with a generic shrink (so minor pass overhead is compensated for), and without + // try both combining with a generic shrink (so minor pass overhead is + // compensated for), and without for (auto pass : passes) { std::string currCommand = Path::getBinaryenBinaryTool("wasm-opt") + " "; // TODO(tlively): -all should be replaced with an option to use the // existing feature set, once implemented. currCommand += working + " -all -o " + test + " " + pass; - if (debugInfo) currCommand += " -g "; - if (verbose) std::cerr << "| trying pass command: " << currCommand << "\n"; + if (debugInfo) + currCommand += " -g "; + if (verbose) + std::cerr << "| trying pass command: " << currCommand << "\n"; if (!ProgramResult(currCommand).failed()) { auto newSize = file_size(test); if (newSize < oldSize) { // the pass didn't fail, and the size looks smaller, so promising // see if it is still has the property we are preserving if (ProgramResult(command) == expected) { - std::cerr << "| command \"" << currCommand << "\" succeeded, reduced size to " << newSize << ", and preserved the property\n"; + std::cerr << "| command \"" << currCommand + << "\" succeeded, reduced size to " << newSize + << ", and preserved the property\n"; copy_file(test, working); more = true; oldSize = newSize; @@ -291,7 +306,8 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< } } } - if (verbose) std::cerr << "| done with passes for now\n"; + if (verbose) + std::cerr << "| done with passes for now\n"; } // does one pass of slow and destructive reduction. returns whether it @@ -311,7 +327,9 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< // 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, very unlikely reduction can work\n" << result << '\n'; + std::cerr << "\n|! WARNING: writing before destructive reduction fails, " + "very unlikely reduction can work\n" + << result << '\n'; } // destroy! walkModule(getModule()); @@ -381,15 +399,18 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< return false; } auto* curr = getCurrent(); - //std::cerr << "try " << curr << " => " << with << '\n'; - if (curr->type != with->type) return false; - if (!shouldTryToReduce()) return false; + // std::cerr << "try " << curr << " => " << with << '\n'; + if (curr->type != with->type) + return false; + if (!shouldTryToReduce()) + return false; replaceCurrent(with); if (!writeAndTestReduction()) { replaceCurrent(curr); return false; } - std::cerr << "| tryToReplaceCurrent succeeded (in " << getLocation() << ")\n"; + std::cerr << "| tryToReplaceCurrent succeeded (in " << getLocation() + << ")\n"; noteReduction(); return true; } @@ -404,38 +425,45 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< if (!isOkReplacement(with)) { return false; } - if (child->type != with->type) return false; - if (!shouldTryToReduce()) return false; + if (child->type != with->type) + return false; + if (!shouldTryToReduce()) + return false; auto* before = child; child = with; if (!writeAndTestReduction()) { child = before; return false; } - std::cerr << "| tryToReplaceChild succeeded (in " << getLocation() << ")\n"; - //std::cerr << "| " << before << " => " << with << '\n'; + std::cerr << "| tryToReplaceChild succeeded (in " << getLocation() + << ")\n"; + // std::cerr << "| " << before << " => " << with << '\n'; noteReduction(); return true; } std::string getLocation() { - if (getFunction()) return getFunction()->name.str; + if (getFunction()) + return getFunction()->name.str; return "(non-function context)"; } - // visitors. in each we try to remove code in a destructive and nontrivial way. - // "nontrivial" means something that optimization passes can't achieve, since we - // don't need to duplicate work that they do + // visitors. in each we try to remove code in a destructive and nontrivial + // way. "nontrivial" means something that optimization passes can't achieve, + // since we don't need to duplicate work that they do void visitExpression(Expression* curr) { // type-based reductions if (curr->type == none) { - if (tryToReduceCurrentToNop()) return; + if (tryToReduceCurrentToNop()) + return; } else if (isConcreteType(curr->type)) { - if (tryToReduceCurrentToConst()) return; + if (tryToReduceCurrentToConst()) + return; } else { assert(curr->type == unreachable); - if (tryToReduceCurrentToUnreachable()) return; + if (tryToReduceCurrentToUnreachable()) + return; } // specific reductions if (auto* iff = curr->dynCast<If>()) { @@ -472,11 +500,14 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< } } } else if (auto* block = curr->dynCast<Block>()) { - if (!shouldTryToReduce()) return; + if (!shouldTryToReduce()) + return; // replace a singleton auto& list = block->list; - if (list.size() == 1 && !BranchUtils::BranchSeeker::hasNamed(block, block->name)) { - if (tryToReplaceCurrent(block->list[0])) return; + if (list.size() == 1 && + !BranchUtils::BranchSeeker::hasNamed(block, block->name)) { + if (tryToReplaceCurrent(block->list[0])) + return; } // try to get rid of nops Index i = 0; @@ -504,7 +535,8 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< } return; // nothing more to do } else if (auto* loop = curr->dynCast<Loop>()) { - if (shouldTryToReduce() && !BranchUtils::BranchSeeker::hasNamed(loop, loop->name)) { + if (shouldTryToReduce() && + !BranchUtils::BranchSeeker::hasNamed(loop, loop->name)) { tryToReplaceCurrent(loop->body); } return; // nothing more to do @@ -512,73 +544,116 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< // Finally, try to replace with a child. for (auto* child : ChildIterator(curr)) { if (isConcreteType(child->type) && curr->type == none) { - if (tryToReplaceCurrent(builder->makeDrop(child))) return; + if (tryToReplaceCurrent(builder->makeDrop(child))) + return; } else { - if (tryToReplaceCurrent(child)) return; + if (tryToReplaceCurrent(child)) + return; } } // If that didn't work, try to replace with a child + a unary conversion if (isConcreteType(curr->type) && !curr->is<Unary>()) { // but not if it's already unary for (auto* child : ChildIterator(curr)) { - if (child->type == curr->type) continue; // already tried - if (!isConcreteType(child->type)) continue; // no conversion + if (child->type == curr->type) + continue; // already tried + if (!isConcreteType(child->type)) + continue; // no conversion Expression* fixed = nullptr; switch (curr->type) { case i32: { switch (child->type) { - case i32: WASM_UNREACHABLE(); - case i64: fixed = builder->makeUnary(WrapInt64, child); break; - case f32: fixed = builder->makeUnary(TruncSFloat32ToInt32, child); break; - case f64: fixed = builder->makeUnary(TruncSFloat64ToInt32, child); break; - case v128: continue; // v128 not implemented yet + case i32: + WASM_UNREACHABLE(); + case i64: + fixed = builder->makeUnary(WrapInt64, child); + break; + case f32: + fixed = builder->makeUnary(TruncSFloat32ToInt32, child); + break; + case f64: + fixed = builder->makeUnary(TruncSFloat64ToInt32, child); + break; + case v128: + continue; // v128 not implemented yet case none: - case unreachable: WASM_UNREACHABLE(); + case unreachable: + WASM_UNREACHABLE(); } break; } case i64: { switch (child->type) { - case i32: fixed = builder->makeUnary(ExtendSInt32, child); break; - case i64: WASM_UNREACHABLE(); - case f32: fixed = builder->makeUnary(TruncSFloat32ToInt64, child); break; - case f64: fixed = builder->makeUnary(TruncSFloat64ToInt64, child); break; - case v128: continue; // v128 not implemented yet + case i32: + fixed = builder->makeUnary(ExtendSInt32, child); + break; + case i64: + WASM_UNREACHABLE(); + case f32: + fixed = builder->makeUnary(TruncSFloat32ToInt64, child); + break; + case f64: + fixed = builder->makeUnary(TruncSFloat64ToInt64, child); + break; + case v128: + continue; // v128 not implemented yet case none: - case unreachable: WASM_UNREACHABLE(); + case unreachable: + WASM_UNREACHABLE(); } break; } case f32: { switch (child->type) { - case i32: fixed = builder->makeUnary(ConvertSInt32ToFloat32, child); break; - case i64: fixed = builder->makeUnary(ConvertSInt64ToFloat32, child); break; - case f32: WASM_UNREACHABLE(); - case f64: fixed = builder->makeUnary(DemoteFloat64, child); break; - case v128: continue; // v128 not implemented yet + case i32: + fixed = builder->makeUnary(ConvertSInt32ToFloat32, child); + break; + case i64: + fixed = builder->makeUnary(ConvertSInt64ToFloat32, child); + break; + case f32: + WASM_UNREACHABLE(); + case f64: + fixed = builder->makeUnary(DemoteFloat64, child); + break; + case v128: + continue; // v128 not implemented yet case none: - case unreachable: WASM_UNREACHABLE(); + case unreachable: + WASM_UNREACHABLE(); } break; } case f64: { switch (child->type) { - case i32: fixed = builder->makeUnary(ConvertSInt32ToFloat64, child); break; - case i64: fixed = builder->makeUnary(ConvertSInt64ToFloat64, child); break; - case f32: fixed = builder->makeUnary(PromoteFloat32, child); break; - case f64: WASM_UNREACHABLE(); - case v128: continue; // v128 not implemented yet + case i32: + fixed = builder->makeUnary(ConvertSInt32ToFloat64, child); + break; + case i64: + fixed = builder->makeUnary(ConvertSInt64ToFloat64, child); + break; + case f32: + fixed = builder->makeUnary(PromoteFloat32, child); + break; + case f64: + WASM_UNREACHABLE(); + case v128: + continue; // v128 not implemented yet case none: - case unreachable: WASM_UNREACHABLE(); + case unreachable: + WASM_UNREACHABLE(); } break; } - case v128: continue; // v128 not implemented yet + case v128: + continue; // v128 not implemented yet case none: - case unreachable: WASM_UNREACHABLE(); + case unreachable: + WASM_UNREACHABLE(); } assert(fixed->type == curr->type); - if (tryToReplaceCurrent(fixed)) return; + if (tryToReplaceCurrent(fixed)) + return; } } } @@ -609,7 +684,8 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< first = item; break; } - if (!first.isNull()) break; + if (!first.isNull()) + break; } visitSegmented(curr, first, 100); } @@ -627,12 +703,15 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< bool shrank = false; for (auto& segment : curr->segments) { auto& data = segment.data; - size_t skip = 1; // when we succeed, try to shrink by more and more, similar to bisection + // when we succeed, try to shrink by more and more, similar to bisection + size_t skip = 1; for (size_t i = 0; i < data.size() && !data.empty(); i++) { - if (!justShrank && !shouldTryToReduce(bonus)) continue; + if (!justShrank && !shouldTryToReduce(bonus)) + continue; auto save = data; for (size_t j = 0; j < skip; j++) { - if (!data.empty()) data.pop_back(); + if (!data.empty()) + data.pop_back(); } auto justShrank = writeAndTestReduction(); if (justShrank) { @@ -648,10 +727,13 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< } // the "opposite" of shrinking: copy a 'zero' element for (auto& segment : curr->segments) { - if (segment.data.empty()) continue; + if (segment.data.empty()) + continue; for (auto& item : segment.data) { - if (!shouldTryToReduce(bonus)) continue; - if (item == zero) continue; + if (!shouldTryToReduce(bonus)) + continue; + if (item == zero) + continue; auto save = item; item = zero; if (writeAndTestReduction()) { @@ -678,23 +760,27 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< functionNames.push_back(func->name); } size_t skip = 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. + // 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; for (size_t i = 0; i < functionNames.size(); i++) { if (!justRemoved && functionsWeTriedToRemove.count(functionNames[i]) == 1 && - !shouldTryToReduce(std::max((factor / 100) + 1, 1000))) continue; + !shouldTryToReduce(std::max((factor / 100) + 1, 1000))) + continue; std::vector<Name> names; - for (size_t j = 0; names.size() < skip && i + j < functionNames.size(); j++) { + for (size_t j = 0; names.size() < skip && i + j < functionNames.size(); + j++) { auto name = functionNames[i + j]; if (module->getFunctionOrNull(name)) { names.push_back(name); functionsWeTriedToRemove.insert(name); } } - if (names.size() == 0) continue; - std::cout << "| try to remove " << names.size() << " functions (skip: " << skip << ")\n"; + if (names.size() == 0) + continue; + std::cout << "| try to remove " << names.size() + << " functions (skip: " << skip << ")\n"; justRemoved = tryToRemoveFunctions(names); if (justRemoved) { noteReduction(names.size()); @@ -712,9 +798,11 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< } skip = 1; for (size_t i = 0; i < exports.size(); i++) { - if (!shouldTryToReduce(std::max((factor / 100) + 1, 1000))) continue; + if (!shouldTryToReduce(std::max((factor / 100) + 1, 1000))) + continue; std::vector<Export> currExports; - for (size_t j = 0; currExports.size() < skip && i + j < exports.size(); j++) { + for (size_t j = 0; currExports.size() < skip && i + j < exports.size(); + j++) { auto exp = exports[i + j]; if (module->getExportOrNull(exp.name)) { currExports.push_back(exp); @@ -736,7 +824,8 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< } // If we are left with a single function that is not exported or used in // a table, that is useful as then we can change the return type. - if (module->functions.size() == 1 && module->exports.empty() && module->table.segments.empty()) { + if (module->functions.size() == 1 && module->exports.empty() && + module->table.segments.empty()) { auto* func = module->functions[0].get(); // We can't remove something that might have breaks to it. if (!func->imported() && !Properties::isNamedControlFlow(func->body)) { @@ -773,7 +862,8 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< } // remove all references to them - struct FunctionReferenceRemover : public PostWalker<FunctionReferenceRemover> { + struct FunctionReferenceRemover + : public PostWalker<FunctionReferenceRemover> { std::unordered_set<Name> names; std::vector<Name> exportsToRemove; @@ -801,9 +891,11 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< break; } } - if (!other.isNull()) break; + if (!other.isNull()) + break; } - if (other.isNull()) return; // we failed to find a replacement + if (other.isNull()) + return; // we failed to find a replacement for (auto& segment : curr->segments) { for (auto& name : segment.data) { if (names.count(name)) { @@ -822,7 +914,8 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< FunctionReferenceRemover referenceRemover(names); referenceRemover.walkModule(module.get()); - if (WasmValidator().validate(*module, WasmValidator::Globally | WasmValidator::Quiet) && + if (WasmValidator().validate( + *module, WasmValidator::Globally | WasmValidator::Quiet) && writeAndTestReduction()) { std::cerr << "| removed " << names.size() << " functions\n"; return true; @@ -836,8 +929,10 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< // try to replace condition with always true and always false void handleCondition(Expression*& condition) { - if (!condition) return; - if (condition->is<Const>()) return; + if (!condition) + return; + if (condition->is<Const>()) + return; auto* c = builder->makeConst(Literal(int32_t(0))); if (!tryToReplaceChild(condition, c)) { c->value = Literal(int32_t(1)); @@ -847,7 +942,8 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< bool tryToReduceCurrentToNop() { auto* curr = getCurrent(); - if (curr->is<Nop>()) return false; + if (curr->is<Nop>()) + return false; // try to replace with a trivial value Nop nop; if (tryToReplaceCurrent(&nop)) { @@ -860,10 +956,12 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< // try to replace a concrete value with a trivial constant bool tryToReduceCurrentToConst() { auto* curr = getCurrent(); - if (curr->is<Const>()) return false; + if (curr->is<Const>()) + return false; // try to replace with a trivial value Const* c = builder->makeConst(Literal(int32_t(0))); - if (tryToReplaceCurrent(c)) return true; + if (tryToReplaceCurrent(c)) + return true; c->value = Literal::makeFromInt32(1, curr->type); c->type = curr->type; return tryToReplaceCurrent(c); @@ -871,7 +969,8 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< bool tryToReduceCurrentToUnreachable() { auto* curr = getCurrent(); - if (curr->is<Unreachable>()) return false; + if (curr->is<Unreachable>()) + return false; // try to replace with a trivial value Unreachable un; if (tryToReplaceCurrent(&un)) { @@ -889,76 +988,87 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< int main(int argc, const char* argv[]) { std::string input, test, working, command; - bool binary = true, - deNan = false, - verbose = false, - debugInfo = false, + bool binary = true, deNan = false, verbose = false, debugInfo = false, force = false; - Options options("wasm-reduce", "Reduce a wasm file to a smaller one that has the same behavior on a given command"); + Options options("wasm-reduce", + "Reduce a wasm file to a smaller one that has the same " + "behavior on a given command"); options - .add("--command", "-cmd", "The command to run on the test, that we want to reduce while keeping the command's output identical. " - "We look at the command's return code and stdout here (TODO: stderr), " - "and we reduce while keeping those unchanged.", - Options::Arguments::One, - [&](Options* o, const std::string& argument) { - command = argument; - }) - .add("--test", "-t", "Test file (this will be written to to test, the given command should read it when we call it)", - Options::Arguments::One, - [&](Options* o, const std::string& argument) { - test = argument; - }) - .add("--working", "-w", "Working file (this will contain the current good state while doing temporary computations, " - "and will contain the final best result at the end)", - Options::Arguments::One, - [&](Options* o, const std::string& argument) { - working = argument; - }) - .add("--binaries", "-b", "binaryen binaries location (bin/ directory)", - Options::Arguments::One, - [&](Options* o, const std::string& argument) { - // Add separator just in case - Path::setBinaryenBinDir(argument + Path::getPathSeparator()); - }) - .add("--text", "-S", "Emit intermediate files as text, instead of binary (also make sure the test and working files have a .wat or .wast suffix)", - Options::Arguments::Zero, - [&](Options* o, const std::string& argument) { - binary = false; - }) - .add("--denan", "", "Avoid nans when reducing", - Options::Arguments::Zero, - [&](Options* o, const std::string& argument) { - deNan = true; - }) - .add("--verbose", "-v", "Verbose output mode", - Options::Arguments::Zero, - [&](Options* o, const std::string& argument) { - verbose = true; - }) - .add("--debugInfo", "-g", "Keep debug info in binaries", - Options::Arguments::Zero, - [&](Options* o, const std::string& argument) { - debugInfo = true; - }) - .add("--force", "-f", "Force the reduction attempt, ignoring problems that imply it is unlikely to succeed", - Options::Arguments::Zero, - [&](Options* o, const std::string& argument) { - force = true; - }) - .add("--timeout", "-to", "A timeout to apply to each execution of the command, in seconds (default: 2)", - Options::Arguments::One, - [&](Options* o, const std::string& argument) { - timeout = atoi(argument.c_str()); - std::cout << "|applying timeout: " << timeout << "\n"; - }) - .add_positional("INFILE", Options::Arguments::One, - [&](Options* o, const std::string& argument) { - input = argument; - }); + .add("--command", + "-cmd", + "The command to run on the test, that we want to reduce while keeping " + "the command's output identical. " + "We look at the command's return code and stdout here (TODO: stderr), " + "and we reduce while keeping those unchanged.", + Options::Arguments::One, + [&](Options* o, const std::string& argument) { command = argument; }) + .add("--test", + "-t", + "Test file (this will be written to to test, the given command should " + "read it when we call it)", + Options::Arguments::One, + [&](Options* o, const std::string& argument) { test = argument; }) + .add("--working", + "-w", + "Working file (this will contain the current good state while doing " + "temporary computations, " + "and will contain the final best result at the end)", + Options::Arguments::One, + [&](Options* o, const std::string& argument) { working = argument; }) + .add("--binaries", + "-b", + "binaryen binaries location (bin/ directory)", + Options::Arguments::One, + [&](Options* o, const std::string& argument) { + // Add separator just in case + Path::setBinaryenBinDir(argument + Path::getPathSeparator()); + }) + .add("--text", + "-S", + "Emit intermediate files as text, instead of binary (also make sure " + "the test and working files have a .wat or .wast suffix)", + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { binary = false; }) + .add("--denan", + "", + "Avoid nans when reducing", + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { deNan = true; }) + .add("--verbose", + "-v", + "Verbose output mode", + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { verbose = true; }) + .add("--debugInfo", + "-g", + "Keep debug info in binaries", + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { debugInfo = true; }) + .add("--force", + "-f", + "Force the reduction attempt, ignoring problems that imply it is " + "unlikely to succeed", + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { force = true; }) + .add("--timeout", + "-to", + "A timeout to apply to each execution of the command, in seconds " + "(default: 2)", + Options::Arguments::One, + [&](Options* o, const std::string& argument) { + timeout = atoi(argument.c_str()); + std::cout << "|applying timeout: " << timeout << "\n"; + }) + .add_positional( + "INFILE", + Options::Arguments::One, + [&](Options* o, const std::string& argument) { input = argument; }); options.parse(argc, argv); - if (test.size() == 0) Fatal() << "test file not provided\n"; - if (working.size() == 0) Fatal() << "working file not provided\n"; + if (test.size() == 0) + Fatal() << "test file not provided\n"; + if (working.size() == 0) + Fatal() << "working file not provided\n"; if (!binary) { Colors::disable(); @@ -979,15 +1089,20 @@ int main(int argc, const char* argv[]) { auto stopIfNotForced = [&](std::string message, ProgramResult& result) { std::cerr << "|! " << message << '\n' << result << '\n'; if (!force) { - Fatal() << "|! stopping, as it is very unlikely reduction can succeed (use -f to ignore this check)"; + Fatal() << "|! stopping, as it is very unlikely reduction can succeed " + "(use -f to ignore this check)"; } }; if (expected.time + 1 >= timeout) { - stopIfNotForced("execution time is dangerously close to the timeout - you should probably increase the timeout", expected); + stopIfNotForced("execution time is dangerously close to the timeout - you " + "should probably increase the timeout", + expected); } - std::cerr << "|checking that command has different behavior on invalid binary (this verifies that the test file is used by the command)\n"; + std::cerr + << "|checking that command has different behavior on invalid binary (this " + "verifies that the test file is used by the command)\n"; { { std::ofstream dst(test, std::ios::binary); @@ -995,24 +1110,31 @@ int main(int argc, const char* argv[]) { } ProgramResult result(command); if (result == expected) { - stopIfNotForced("running command on an invalid module should give different results", result); + stopIfNotForced( + "running command on an invalid module should give different results", + result); } } - std::cerr << "|checking that command has expected behavior on canonicalized (read-written) binary\n"; + std::cerr << "|checking that command has expected behavior on canonicalized " + "(read-written) binary\n"; { // read and write it // TODO(tlively): -all should be replaced with an option to use the existing // feature set, once implemented. - auto cmd = Path::getBinaryenBinaryTool("wasm-opt") + " " + input + " -all -o " + test; - if (!binary) cmd += " -S"; + auto cmd = Path::getBinaryenBinaryTool("wasm-opt") + " " + input + + " -all -o " + test; + if (!binary) + cmd += " -S"; ProgramResult readWrite(cmd); if (readWrite.failed()) { stopIfNotForced("failed to read and write the binary", readWrite); } else { ProgramResult result(command); if (result != expected) { - stopIfNotForced("running command on the canonicalized module should give the same results", result); + stopIfNotForced("running command on the canonicalized module should " + "give the same results", + result); } } } @@ -1044,13 +1166,15 @@ int main(int argc, const char* argv[]) { std::cerr << "| after pass reduction: " << newSize << "\n"; // always stop after a pass reduction attempt, for final cleanup - if (stopping) break; + if (stopping) + break; // check if the full cycle (destructive/passes) has helped or not if (lastPostPassesSize && newSize >= lastPostPassesSize) { std::cerr << "| progress has stopped, skipping to the end\n"; if (factor == 1) { - // this is after doing work with factor 1, so after the remaining work, stop + // this is after doing work with factor 1, so after the remaining work, + // stop stopping = true; } else { // just try to remove all we can and finish up @@ -1059,9 +1183,10 @@ int main(int argc, const char* argv[]) { } lastPostPassesSize = newSize; - // if destructive reductions lead to useful proportionate pass reductions, keep - // going at the same factor, as pass reductions are far faster - std::cerr << "| pass progress: " << passProgress << ", last destructive: " << lastDestructiveReductions << '\n'; + // if destructive reductions lead to useful proportionate pass reductions, + // keep going at the same factor, as pass reductions are far faster + std::cerr << "| pass progress: " << passProgress + << ", last destructive: " << lastDestructiveReductions << '\n'; if (passProgress >= 4 * lastDestructiveReductions) { // don't change std::cerr << "| progress is good, do not quickly decrease factor\n"; @@ -1083,16 +1208,19 @@ int main(int argc, const char* argv[]) { while (1) { std::cerr << "| reduce destructively... (factor: " << factor << ")\n"; lastDestructiveReductions = reducer.reduceDestructively(factor); - if (lastDestructiveReductions > 0) break; + if (lastDestructiveReductions > 0) + break; // we failed to reduce destructively if (factor == 1) { stopping = true; break; } - factor = std::max(1, factor / 4); // quickly now, try to find *something* we can reduce + factor = std::max( + 1, factor / 4); // quickly now, try to find *something* we can reduce } - std::cerr << "| destructive reduction led to size: " << file_size(working) << '\n'; + std::cerr << "| destructive reduction led to size: " << file_size(working) + << '\n'; } std::cerr << "|finished, final size: " << file_size(working) << "\n"; copy_file(working, test); // just to avoid confusion |