summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/support/command-line.cpp6
-rw-r--r--src/support/json.h398
-rw-r--r--src/tools/wasm-metadce.cpp471
3 files changed, 874 insertions, 1 deletions
diff --git a/src/support/command-line.cpp b/src/support/command-line.cpp
index e415715c3..384dc5dcd 100644
--- a/src/support/command-line.cpp
+++ b/src/support/command-line.cpp
@@ -28,7 +28,7 @@ void printWrap(std::ostream& os, int leftPad, const std::string& content) {
std::string nextWord;
std::string pad(leftPad, ' ');
for (int i = 0; i <= len; ++i) {
- if (i != len && content[i] != ' ') {
+ if (i != len && content[i] != ' ' && content[i] != '\n') {
nextWord += content[i];
} else {
if (static_cast<int>(nextWord.size()) > space) {
@@ -39,6 +39,10 @@ void printWrap(std::ostream& os, int leftPad, const std::string& content) {
space -= nextWord.size() + 1;
if (space > 0) os << ' ';
nextWord.clear();
+ if (content[i] == '\n') {
+ os << '\n';
+ space = SCREEN_WIDTH - leftPad;
+ }
}
}
}
diff --git a/src/support/json.h b/src/support/json.h
new file mode 100644
index 000000000..034cdc9b9
--- /dev/null
+++ b/src/support/json.h
@@ -0,0 +1,398 @@
+/*
+ * Copyright 2017 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.
+ */
+
+// An arena-free version of emscripten-optimizer/simple_ast.h's JSON
+// class TODO: use this instead of that
+
+#ifndef wasm_support_json_h
+#define wasm_support_json_h
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <functional>
+#include <iomanip>
+#include <iostream>
+#include <limits>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "emscripten-optimizer/istring.h"
+#include "support/safe_integer.h"
+
+namespace json {
+
+typedef cashew::IString IString;
+
+// Main value type
+struct Value {
+ struct Ref : public std::shared_ptr<Value> {
+ Ref() : std::shared_ptr<Value>() {}
+ Ref(Value* value) : std::shared_ptr<Value>(value) {}
+
+ Ref& operator[](size_t x) {
+ return (*this->get())[x];
+ }
+ Ref& operator[](IString x) {
+ return (*this->get())[x];
+ }
+ };
+
+ enum Type {
+ String = 0,
+ Number = 1,
+ Array = 2,
+ Null = 3,
+ Bool = 4,
+ Object = 5,
+ };
+
+ Type type;
+
+ typedef std::vector<Ref> ArrayStorage;
+ typedef std::unordered_map<IString, Ref> ObjectStorage;
+
+#ifdef _MSC_VER // MSVC does not allow unrestricted unions: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2544.pdf
+ IString str;
+#endif
+ union { // TODO: optimize
+#ifndef _MSC_VER
+ IString str;
+#endif
+ double num;
+ ArrayStorage *arr; // manually allocated/freed
+ bool boo;
+ ObjectStorage *obj; // manually allocated/freed
+ Ref ref;
+ };
+
+ // constructors all copy their input
+ Value() : type(Null), num(0) {}
+ explicit Value(const char *s) : type(Null) {
+ setString(s);
+ }
+ explicit Value(double n) : type(Null) {
+ setNumber(n);
+ }
+ explicit Value(ArrayStorage &a) : type(Null) {
+ setArray();
+ *arr = a;
+ }
+ // no bool constructor - would endanger the double one (int might convert the wrong way)
+
+ ~Value() {
+ free();
+ }
+
+ void free() {
+ if (type == Array) {
+ delete arr;
+ arr = nullptr;
+ } else if (type == Object) {
+ delete obj;
+ obj = nullptr;
+ }
+ type = Null;
+ num = 0;
+ }
+
+ Value& setString(const char *s) {
+ free();
+ type = String;
+ str.set(s);
+ return *this;
+ }
+ Value& setString(const IString &s) {
+ free();
+ type = String;
+ str.set(s);
+ return *this;
+ }
+ Value& setNumber(double n) {
+ free();
+ type = Number;
+ num = n;
+ return *this;
+ }
+ Value& setArray(ArrayStorage &a) {
+ free();
+ type = Array;
+ arr = new ArrayStorage;
+ *arr = a;
+ return *this;
+ }
+ Value& setArray(size_t size_hint=0) {
+ free();
+ type = Array;
+ arr = new ArrayStorage;
+ arr->reserve(size_hint);
+ return *this;
+ }
+ Value& setNull() {
+ free();
+ type = Null;
+ return *this;
+ }
+ Value& setBool(bool b) { // Bool in the name, as otherwise might overload over int
+ free();
+ type = Bool;
+ boo = b;
+ return *this;
+ }
+ Value& setObject() {
+ free();
+ type = Object;
+ obj = new ObjectStorage();
+ return *this;
+ }
+
+ bool isString() { return type == String; }
+ bool isNumber() { return type == Number; }
+ bool isArray() { return type == Array; }
+ bool isNull() { return type == Null; }
+ bool isBool() { return type == Bool; }
+ bool isObject() { return type == Object; }
+
+ bool isBool(bool b) { return type == Bool && b == boo; } // avoid overloading == as it might overload over int
+
+ const char* getCString() {
+ assert(isString());
+ return str.str;
+ }
+ IString& getIString() {
+ assert(isString());
+ return str;
+ }
+ double& getNumber() {
+ assert(isNumber());
+ return num;
+ }
+ ArrayStorage& getArray() {
+ assert(isArray());
+ return *arr;
+ }
+ bool& getBool() {
+ assert(isBool());
+ return boo;
+ }
+
+ int32_t getInteger() { // convenience function to get a known integer
+ assert(fmod(getNumber(), 1) == 0);
+ int32_t ret = getNumber();
+ assert(double(ret) == getNumber()); // no loss in conversion
+ return ret;
+ }
+
+ Value& operator=(const Value& other) {
+ free();
+ switch (other.type) {
+ case String:
+ setString(other.str);
+ break;
+ case Number:
+ setNumber(other.num);
+ break;
+ case Array:
+ setArray(*other.arr);
+ break;
+ case Null:
+ setNull();
+ break;
+ case Bool:
+ setBool(other.boo);
+ break;
+ default:
+ abort(); // TODO
+ }
+ return *this;
+ }
+
+ bool operator==(const Value& other) {
+ if (type != other.type) return false;
+ switch (other.type) {
+ case String:
+ return str == other.str;
+ case Number:
+ return num == other.num;
+ case Array:
+ return this == &other; // if you want a deep compare, use deepCompare
+ case Null:
+ break;
+ case Bool:
+ return boo == other.boo;
+ case Object:
+ return this == &other; // if you want a deep compare, use deepCompare
+ default:
+ abort();
+ }
+ return true;
+ }
+
+ char* parse(char* curr) {
+ #define is_json_space(x) (x == 32 || x == 9 || x == 10 || x == 13) /* space, tab, linefeed/newline, or return */
+ #define skip() { while (*curr && is_json_space(*curr)) curr++; }
+ skip();
+ if (*curr == '"') {
+ // String
+ curr++;
+ char *close = strchr(curr, '"');
+ assert(close);
+ *close = 0; // end this string, and reuse it straight from the input
+ setString(curr);
+ curr = close+1;
+ } else if (*curr == '[') {
+ // Array
+ curr++;
+ skip();
+ setArray();
+ while (*curr != ']') {
+ Ref temp = Ref(new Value());
+ arr->push_back(temp);
+ curr = temp->parse(curr);
+ skip();
+ if (*curr == ']') break;
+ assert(*curr == ',');
+ curr++;
+ skip();
+ }
+ curr++;
+ } else if (*curr == 'n') {
+ // Null
+ assert(strncmp(curr, "null", 4) == 0);
+ setNull();
+ curr += 4;
+ } else if (*curr == 't') {
+ // Bool true
+ assert(strncmp(curr, "true", 4) == 0);
+ setBool(true);
+ curr += 4;
+ } else if (*curr == 'f') {
+ // Bool false
+ assert(strncmp(curr, "false", 5) == 0);
+ setBool(false);
+ curr += 5;
+ } else if (*curr == '{') {
+ // Object
+ curr++;
+ skip();
+ setObject();
+ while (*curr != '}') {
+ assert(*curr == '"');
+ curr++;
+ char *close = strchr(curr, '"');
+ assert(close);
+ *close = 0; // end this string, and reuse it straight from the input
+ IString key(curr);
+ curr = close+1;
+ skip();
+ assert(*curr == ':');
+ curr++;
+ skip();
+ Ref value = Ref(new Value());
+ curr = value->parse(curr);
+ (*obj)[key] = value;
+ skip();
+ if (*curr == '}') break;
+ assert(*curr == ',');
+ curr++;
+ skip();
+ }
+ curr++;
+ } else {
+ // Number
+ char *after;
+ setNumber(strtod(curr, &after));
+ curr = after;
+ }
+ return curr;
+ }
+
+ void stringify(std::ostream &os, bool pretty=false);
+
+ // String operations
+
+ // Number operations
+
+ // Array operations
+
+ size_t size() {
+ assert(isArray());
+ return arr->size();
+ }
+
+ void setSize(size_t size) {
+ assert(isArray());
+ auto old = arr->size();
+ if (old != size) arr->resize(size);
+ if (old < size) {
+ for (auto i = old; i < size; i++) {
+ (*arr)[i] = Ref(new Value());
+ }
+ }
+ }
+
+ Ref& operator[](unsigned x) {
+ assert(isArray());
+ return (*arr)[x];
+ }
+
+ Value& push_back(Ref r) {
+ assert(isArray());
+ arr->push_back(r);
+ return *this;
+ }
+ Ref pop_back() {
+ assert(isArray());
+ Ref ret = arr->back();
+ arr->pop_back();
+ return ret;
+ }
+
+ Ref back() {
+ assert(isArray());
+ if (arr->size() == 0) return nullptr;
+ return arr->back();
+ }
+
+ // Null operations
+
+ // Bool operations
+
+ // Object operations
+
+ Ref& operator[](IString x) {
+ assert(isObject());
+ return (*obj)[x];
+ }
+
+ bool has(IString x) {
+ assert(isObject());
+ return obj->count(x) > 0;
+ }
+};
+
+typedef Value::Ref Ref;
+
+} // namespace json
+
+#endif // wasm_support_json_h
diff --git a/src/tools/wasm-metadce.cpp b/src/tools/wasm-metadce.cpp
new file mode 100644
index 000000000..b6447f58a
--- /dev/null
+++ b/src/tools/wasm-metadce.cpp
@@ -0,0 +1,471 @@
+/*
+ * Copyright 2017 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.
+ */
+
+//
+// Performs DCE on a graph containing a wasm module. The caller provides
+// the graph, this tool fills in the wasm module's parts. It can then
+// remove exports that are unused, for example, which is impossible
+// otherwise (since we wouldn't know if the outside needs them).
+//
+// TODO: Currently all functions in the table are considered roots,
+// as the outside may call them. In the future we probably want
+// to refine that.
+
+#include <memory>
+
+#include "pass.h"
+#include "support/command-line.h"
+#include "support/file.h"
+#include "support/json.h"
+#include "support/colors.h"
+#include "wasm-io.h"
+#include "wasm-builder.h"
+#include "ir/import-utils.h"
+
+using namespace wasm;
+
+// Generic reachability graph of abstract nodes
+
+struct DCENode {
+ Name name;
+ std::vector<Name> reaches; // the other nodes this one can reach
+ DCENode() {}
+ DCENode(Name name) : name(name) {}
+};
+
+// A meta DCE graph with wasm integration
+struct MetaDCEGraph {
+ std::unordered_map<Name, DCENode> nodes;
+ std::unordered_set<Name> roots;
+
+ std::unordered_map<Name, Name> importToDCENode; // import internal name => DCE name
+ std::unordered_map<Name, Name> exportToDCENode; // export exported name => DCE name
+ std::unordered_map<Name, Name> functionToDCENode; // function name => DCE name
+ std::unordered_map<Name, Name> globalToDCENode; // global name => DCE name
+
+ std::unordered_map<Name, Name> DCENodeToImport; // reverse maps
+ std::unordered_map<Name, Name> DCENodeToExport;
+ std::unordered_map<Name, Name> DCENodeToFunction;
+ std::unordered_map<Name, Name> DCENodeToGlobal;
+
+ Module& wasm;
+
+ MetaDCEGraph(Module& wasm) : wasm(wasm) {}
+
+ // populate the graph with info from the wasm, integrating with potentially-existing
+ // nodes for imports and exports that the graph may already contain.
+ void scanWebAssembly() {
+ // Add an entry for everything we might need ahead of time, so parallel work
+ // does not alter parent state, just adds to things pointed by it, independently
+ // (each thread will add for one function, etc.)
+ for (auto& func : wasm.functions) {
+ auto dceName = getName("func", func->name.str);
+ DCENodeToFunction[dceName] = func->name;
+ functionToDCENode[func->name] = dceName;
+ nodes[dceName] = DCENode(dceName);
+ }
+ for (auto& global : wasm.globals) {
+ auto dceName = getName("global", global->name.str);
+ DCENodeToGlobal[dceName] = global->name;
+ globalToDCENode[global->name] = dceName;
+ nodes[dceName] = DCENode(dceName);
+ }
+ for (auto& imp : wasm.imports) {
+ if (importToDCENode.find(imp->name) == importToDCENode.end()) {
+ auto dceName = getName("import", imp->name.str);
+ DCENodeToImport[dceName] = imp->name;
+ importToDCENode[imp->name] = dceName;
+ nodes[dceName] = DCENode(dceName);
+ }
+ }
+ for (auto& exp : wasm.exports) {
+ if (exportToDCENode.find(exp->name) == exportToDCENode.end()) {
+ auto dceName = getName("export", exp->name.str);
+ DCENodeToExport[dceName] = exp->name;
+ exportToDCENode[exp->name] = dceName;
+ nodes[dceName] = DCENode(dceName);
+ }
+ // we can also link the export to the thing being exported
+ auto& node = nodes[exportToDCENode[exp->name]];
+ if (exp->kind == ExternalKind::Function) {
+ if (wasm.getFunctionOrNull(exp->value)) {
+ node.reaches.push_back(functionToDCENode[exp->value]);
+ } else {
+ node.reaches.push_back(importToDCENode[exp->value]);
+ }
+ } else if (exp->kind == ExternalKind::Global) {
+ if (wasm.getGlobalOrNull(exp->value)) {
+ node.reaches.push_back(globalToDCENode[exp->value]);
+ } else {
+ node.reaches.push_back(importToDCENode[exp->value]);
+ }
+ }
+ }
+
+ // A parallel scanner for function bodies
+ struct Scanner : public WalkerPass<PostWalker<Scanner>> {
+ bool isFunctionParallel() override { return true; }
+
+ Scanner(MetaDCEGraph* parent) : parent(parent) {}
+
+ Scanner* create() override {
+ return new Scanner(parent);
+ }
+
+ void visitCall(Call* curr) {
+ parent->nodes[parent->functionToDCENode[getFunction()->name]].reaches.push_back(
+ parent->functionToDCENode[curr->target]
+ );
+ }
+ void visitCallImport(CallImport* curr) {
+ assert(parent->functionToDCENode.count(getFunction()->name) > 0);
+ parent->nodes[parent->functionToDCENode[getFunction()->name]].reaches.push_back(
+ parent->importToDCENode[curr->target]
+ );
+ }
+ void visitGetGlobal(GetGlobal* curr) {
+ handleGlobal(curr->name);
+ }
+ void visitSetGlobal(SetGlobal* curr) {
+ handleGlobal(curr->name);
+ }
+
+ private:
+ MetaDCEGraph* parent;
+
+ void handleGlobal(Name name) {
+ if (getModule()->getGlobalOrNull(name)) return;
+ // it's an import
+ parent->nodes[parent->functionToDCENode[getFunction()->name]].reaches.push_back(
+ parent->importToDCENode[name]
+ );
+ }
+ };
+
+ PassRunner runner(&wasm);
+ runner.setIsNested(true);
+ runner.add<Scanner>(this);
+ runner.run();
+
+ // also scan segment offsets
+ Scanner scanner(this);
+ scanner.setModule(&wasm);
+ for (auto& segment : wasm.table.segments) {
+ scanner.walk(segment.offset);
+ // TODO: currently, all functions in the table are roots, but we
+ // should add an option to refine that
+ for (auto& name : segment.data) {
+ roots.insert(functionToDCENode[name]);
+ }
+ }
+ for (auto& segment : wasm.memory.segments) {
+ scanner.walk(segment.offset);
+ }
+ }
+
+private:
+ // gets a unique name for the graph
+ Name getName(std::string prefix1, std::string prefix2) {
+ while (1) {
+ auto curr = Name(prefix1 + '$' + prefix2 + '$' + std::to_string(nameIndex++));
+ if (nodes.find(curr) == nodes.end()) {
+ return curr;
+ }
+ }
+ }
+
+ Index nameIndex = 0;
+
+ std::unordered_set<Name> reached;
+
+public:
+ // Perform the DCE: simple marking from the roots
+ void deadCodeElimination() {
+ std::vector<Name> queue;
+ for (auto root : roots) {
+ reached.insert(root);
+ queue.push_back(root);
+ }
+ while (queue.size() > 0) {
+ auto name = queue.back();
+ queue.pop_back();
+ auto& node = nodes[name];
+ for (auto target : node.reaches) {
+ if (reached.find(target) == reached.end()) {
+ reached.insert(target);
+ queue.push_back(target);
+ }
+ }
+ }
+ }
+
+ // Apply to the wasm
+ void apply() {
+ // Remove the unused exports
+ std::vector<Name> toRemove;
+ for (auto& exp : wasm.exports) {
+ auto name = exp->name;
+ auto dceName = exportToDCENode[name];
+ if (reached.find(dceName) == reached.end()) {
+ toRemove.push_back(name);
+ }
+ }
+ for (auto name : toRemove) {
+ wasm.removeExport(name);
+ }
+ // Now they are gone, standard optimization passes can do the rest!
+ PassRunner passRunner(&wasm);
+ passRunner.add("remove-unused-module-elements");
+ passRunner.run();
+ }
+
+ // Print out everything we found is not used, and so can be
+ // removed, including on the outside
+ void printAllUnused() {
+ std::set<std::string> unused;
+ for (auto& pair : nodes) {
+ auto name = pair.first;
+ if (reached.find(name) == reached.end()) {
+ unused.insert(name.str);
+ }
+ }
+ for (auto& name : unused) {
+ std::cout << "unused: " << name << '\n';
+ }
+ }
+
+ // A debug utility, prints out the graph
+ void dump() {
+ std::cout << "=== graph ===\n";
+ for (auto root : roots) {
+ std::cout << "root: " << root.str << '\n';
+ }
+ for (auto& pair : nodes) {
+ auto name = pair.first;
+ auto& node = pair.second;
+ std::cout << "node: " << name.str << '\n';
+ if (DCENodeToImport.find(name) != DCENodeToImport.end()) {
+ auto* imp = wasm.getImport(DCENodeToImport[name]);
+ std::cout << " is import " << DCENodeToImport[name] << ", " << imp->module.str << '.' << imp->base.str << '\n';
+ }
+ if (DCENodeToExport.find(name) != DCENodeToExport.end()) {
+ std::cout << " is export " << DCENodeToExport[name].str << ", " << wasm.getExport(DCENodeToExport[name])->value << '\n';
+ }
+ if (DCENodeToFunction.find(name) != DCENodeToFunction.end()) {
+ std::cout << " is function " << DCENodeToFunction[name] << '\n';
+ }
+ if (DCENodeToGlobal.find(name) != DCENodeToGlobal.end()) {
+ std::cout << " is function " << DCENodeToGlobal[name] << '\n';
+ }
+ for (auto target : node.reaches) {
+ std::cout << " reaches: " << target.str << '\n';
+ }
+ }
+ std::cout << "=============\n";
+ }
+};
+
+//
+// main
+//
+
+int main(int argc, const char* argv[]) {
+ Name entry;
+ std::vector<std::string> passes;
+ bool emitBinary = true;
+ bool debugInfo = false;
+ std::string graphFile;
+
+ Options options("wasm-metadce", "This tool performs dead code elimination (DCE) on a larger space "
+ "that the wasm module is just a part of. For example, if you have "
+ "JS and wasm that are connected, this can DCE the combined graph. "
+ "By doing so, it is able to eliminate wasm module exports, which "
+ "otherwise regular optimizations cannot.\n\n"
+ "This tool receives a representation of the reachability graph "
+ "that the wasm module resides in, which contains abstract nodes "
+ "and connections showing what they reach. Some of those nodes "
+ "can represent the wasm module's imports and exports. The tool "
+ "then completes the graph by adding the internal parts of the "
+ "module, and does DCE on the entire thing.\n\n"
+ "This tool will output a wasm module with dead code eliminated, "
+ "and metadata describing the things in the rest of the graph "
+ "that can be eliminated as well.\n\n"
+ "The graph description file should represent the graph in the following "
+ "JSON-like notation (note, this is not true JSON, things like "
+ "comments, escaping, single-quotes, etc. are not supported):\n\n"
+ " [\n"
+ " {\n"
+ " \"name\": \"entity1\",\n"
+ " \"reaches\": [\"entity2, \"entity3\"],\n"
+ " \"root\": true\n"
+ " },\n"
+ " {\n"
+ " \"name\": \"entity2\",\n"
+ " \"reaches\": [\"entity1, \"entity4\"]\n"
+ " },\n"
+ " {\n"
+ " \"name\": \"entity3\",\n"
+ " \"reaches\": [\"entity1\"],\n"
+ " \"export\": \"export1\"\n"
+ " },\n"
+ " {\n"
+ " \"name\": \"entity4\",\n"
+ " \"import\": [\"module\", \"import1\"]\n"
+ " },\n"
+ " ]\n\n"
+ "Each entity has a name and an optional list of the other "
+ "entities it reaches. It can also be marked as a root, "
+ "export (with the export string), or import (with the "
+ "module and import strings). DCE then computes what is "
+ "reachable from the roots.");
+
+ options
+ .add("--output", "-o", "Output file (stdout if not specified)",
+ Options::Arguments::One,
+ [](Options* o, const std::string& argument) {
+ o->extra["output"] = argument;
+ Colors::disable();
+ })
+ .add("--emit-text", "-S", "Emit text instead of binary for the output file",
+ Options::Arguments::Zero,
+ [&](Options *o, const std::string &argument) { emitBinary = false; })
+ .add("--debuginfo", "-g", "Emit names section and debug info",
+ Options::Arguments::Zero,
+ [&](Options *o, const std::string &arguments) { debugInfo = true; })
+ .add("--graph-file", "-f", "Filename of the graph description file",
+ Options::Arguments::One,
+ [&](Options* o, const std::string& argument) {
+ graphFile = argument;
+ })
+ .add_positional("INFILE", Options::Arguments::One,
+ [](Options* o, const std::string& argument) {
+ o->extra["infile"] = argument;
+ });
+ options.parse(argc, argv);
+
+ if (graphFile.size() == 0) {
+ Fatal() << "no graph file provided.";
+ }
+
+ auto input(read_file<std::string>(options.extra["infile"], Flags::Text, Flags::Release));
+
+ Module wasm;
+
+ {
+ if (options.debug) std::cerr << "reading...\n";
+ ModuleReader reader;
+ reader.setDebug(options.debug);
+
+ try {
+ reader.read(options.extra["infile"], wasm);
+ } catch (ParseException& p) {
+ p.dump(std::cerr);
+ Fatal() << "error in parsing wasm input";
+ }
+ }
+
+ auto graphInput(read_file<std::string>(graphFile, Flags::Text, Flags::Release));
+ auto* copy = strdup(graphInput.c_str());
+ json::Value outside;
+ outside.parse(copy);
+
+ // parse the JSON into our graph, doing all the JSON parsing here, leaving
+ // the abstract computation for the class itself
+ const json::IString NAME("name"),
+ REACHES("reaches"),
+ ROOT("root"),
+ EXPORT("export"),
+ IMPORT("import");
+
+ MetaDCEGraph graph(wasm);
+
+ if (!outside.isArray()) {
+ Fatal() << "input graph must be a JSON array of nodes. see --help for the form";
+ }
+ auto size = outside.size();
+ for (size_t i = 0; i < size; i++) {
+ json::Ref ref = outside[i];
+ if (!ref->isObject()) {
+ Fatal() << "nodes in input graph must be JSON objects. see --help for the form";
+ }
+ if (!ref->has(NAME)) {
+ Fatal() << "nodes in input graph must have a name. see --help for the form";
+ }
+ DCENode node(ref[NAME]->getIString());
+ if (ref->has(REACHES)) {
+ json::Ref reaches = ref[REACHES];
+ if (!reaches->isArray()) {
+ Fatal() << "node.reaches must be an array. see --help for the form";
+ }
+ auto size = reaches->size();
+ for (size_t j = 0; j < size; j++) {
+ json::Ref name = reaches[j];
+ if (!name->isString()) {
+ Fatal() << "node.reaches items must be strings. see --help for the form";
+ }
+ node.reaches.push_back(name->getIString());
+ }
+ }
+ if (ref->has(ROOT)) {
+ json::Ref root = ref[ROOT];
+ if (!root->isBool() || !root->getBool()) {
+ Fatal() << "node.root, if it exists, must be true. see --help for the form";
+ }
+ graph.roots.insert(node.name);
+ }
+ if (ref->has(EXPORT)) {
+ json::Ref exp = ref[EXPORT];
+ if (!exp->isString()) {
+ Fatal() << "node.export, if it exists, must be a string. see --help for the form";
+ }
+ graph.exportToDCENode[exp->getIString()] = node.name;
+ graph.DCENodeToExport[node.name] = exp->getIString();
+ }
+ if (ref->has(IMPORT)) {
+ json::Ref imp = ref[IMPORT];
+ if (!imp->isArray() || imp->size() != 2 || !imp[0]->isString() || !imp[1]->isString()) {
+ Fatal() << "node.import, if it exists, must be an array of two strings. see --help for the form";
+ }
+ auto importName = ImportUtils::getImport(wasm, imp[0]->getIString(), imp[1]->getIString())->name;
+ graph.importToDCENode[importName] = node.name;
+ graph.DCENodeToImport[node.name] = importName;
+ }
+ // TODO: optimize this copy with a clever move
+ graph.nodes[node.name] = node;
+ }
+
+ // The external graph is now populated. Scan the module
+ graph.scanWebAssembly();
+
+ // Perform the DCE
+ graph.deadCodeElimination();
+
+ // Apply to the wasm
+ graph.apply();
+
+ if (options.extra.count("output") > 0) {
+ ModuleWriter writer;
+ writer.setBinary(emitBinary);
+ writer.setDebugInfo(debugInfo);
+ writer.write(wasm, options.extra["output"]);
+ }
+
+ // Print out everything that we found is removable, the outside might use that
+ graph.printAllUnused();
+
+ // Clean up
+ free(copy);
+}