summaryrefslogtreecommitdiff
path: root/src/tools/wasm-reduce.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/wasm-reduce.cpp')
-rw-r--r--src/tools/wasm-reduce.cpp562
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