From f215193ec12a45fdd893ea8a8cec1353aa3b529e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 27 Aug 2018 10:47:07 -0700 Subject: Souper integration + DataFlow optimizations (#1638) Background: google/souper#323 This adds a --souperify pass, which emits Souper IR in text format. That can then be read by Souper which can emit superoptimization rules. We hope that eventually we can integrate those rules into Binaryen. How this works is we emit an internal "DataFlow IR", which is an SSA-based IR, and then write that out into Souper text. This also adds a --dfo pass, which stands for data-flow optimizations. A DataFlow IR is generated, like in souperify, and then performs some trivial optimizations using it. There are very few things that can do that our other optimizations can't already, but this is also good testing for the DataFlow IR, plus it is good preparation for using Souper's superoptimization output (which would also construct DataFlow IR, like here, but then do some matching on the Souper rules). --- src/dataflow/graph.h | 755 +++++++++++++++++++++++++++++++++++++ src/dataflow/node.h | 210 +++++++++++ src/dataflow/users.h | 106 ++++++ src/dataflow/utils.h | 145 +++++++ src/ir/utils.h | 14 + src/passes/CMakeLists.txt | 2 + src/passes/DataFlowOpts.cpp | 250 ++++++++++++ src/passes/DeadCodeElimination.cpp | 2 +- src/passes/Souperify.cpp | 691 +++++++++++++++++++++++++++++++++ src/passes/pass.cpp | 9 +- src/passes/passes.h | 3 + src/wasm-traversal.h | 2 +- 12 files changed, 2184 insertions(+), 5 deletions(-) create mode 100644 src/dataflow/graph.h create mode 100644 src/dataflow/node.h create mode 100644 src/dataflow/users.h create mode 100644 src/dataflow/utils.h create mode 100644 src/passes/DataFlowOpts.cpp create mode 100644 src/passes/Souperify.cpp (limited to 'src') diff --git a/src/dataflow/graph.h b/src/dataflow/graph.h new file mode 100644 index 000000000..f81b4f989 --- /dev/null +++ b/src/dataflow/graph.h @@ -0,0 +1,755 @@ +/* + * Copyright 2018 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. + */ + +// +// DataFlow IR is an SSA representation. It can be built from the main +// Binaryen IR. +// +// THe main initial use case was an IR that could easily be converted to +// Souper IR, and the design favors that. +// + +#ifndef wasm_dataflow_graph_h +#define wasm_dataflow_graph_h + +#include "wasm.h" +#include "ir/abstract.h" +#include "ir/iteration.h" +#include "ir/literal-utils.h" +#include "dataflow/node.h" + +namespace wasm { + +namespace DataFlow { + +// Main logic to generate IR for a function. This is implemented as a +// visitor on the wasm, where visitors return a Node* that either +// contains the DataFlow IR for that expression, which can be a +// Bad node if not supported, or nullptr if not relevant (we only +// use the return value for internal expressions, that is, the +// value of a set_local or the condition of an if etc). +struct Graph : public UnifiedExpressionVisitor { + // We only need one canonical bad node. It is never modified. + Node bad = Node(Node::Type::Bad); + + // Connects a specific set to the data in its value. + std::unordered_map setNodeMap; + + // Maps a control-flow expression to the conditions for it. Currently, + // this maps an if to the conditions for its arms + std::unordered_map> expressionConditionMap; + + // Maps each expression to its control-flow parent (or null if + // there is none). We only map expressions we need to know about, + // which are sets, set values, and control-flow constructs. + std::unordered_map expressionParentMap; + + // The same, for nodes. Note that we currently don't know the parents + // of nodes like phis that don't exist in wasm - we need to add more + // stuff to handle that. But we will know the parent of anything using + // that phi and storing to a local, which is probably enough anyhow + // for pc generation. + std::unordered_map nodeParentMap; + + // All the sets, in order of appearance. + std::vector sets; + + // The function being processed. + Function* func; + + // The module we are working in. + Module* module; + + // All of our nodes + std::vector> nodes; + + // Tracking state during building + + // We need to track the parents of control flow nodes. + Expression* parent = nullptr; + + // Tracks the state of locals in a control flow path: + // locals[i] = the node whose value it contains + // When we are in unreachable code (i.e., a path that does not + // need to be merged in anywhere), we set the length of this + // vector to 0 to indicate that. + typedef std::vector Locals; + + // The current local state in the control flow path being emitted. + Locals locals; + + // The local states on branches to a specific target. + std::unordered_map> breakStates; + + // The local state in a control flow path, including a possible + // condition as well. + struct FlowState { + Locals locals; + Node* condition; + FlowState(Locals locals, Node* condition) : locals(locals), condition(condition) {} + }; + + // API + + void build(Function* funcInit, Module* moduleInit) { + func = funcInit; + module = moduleInit; + + auto numLocals = func->getNumLocals(); + if (numLocals == 0) return; // nothing to do + // Set up initial local state IR. + setInReachable(); + for (Index i = 0; i < numLocals; i++) { + if (!isRelevantType(func->getLocalType(i))) continue; + Node* node; + auto type = func->getLocalType(i); + if (func->isParam(i)) { + node = makeVar(type); + } else { + node = makeZero(type); + } + locals[i] = node; + } + // Process the function body, generating the rest of the IR. + visit(func->body); + } + + // Makes a Var node, representing a value that could be anything. + Node* makeVar(wasm::Type type) { + if (isRelevantType(type)) { + return addNode(Node::makeVar(type)); + } else { + return &bad; + } + } + + // We create one node per constant value + std::unordered_map constantNodes; + + Node* makeConst(Literal value) { + auto iter = constantNodes.find(value); + if (iter!= constantNodes.end()) { + return iter->second; + } + // Create one for this literal. + Builder builder(*module); + auto* c = builder.makeConst(value); + auto* ret = addNode(Node::makeExpr(c, c)); + constantNodes[value] = ret; + return ret; + } + + Node* makeZero(wasm::Type type) { + return makeConst(LiteralUtils::makeLiteralZero(type)); + } + + // Add a new node to our list of owned nodes. + Node* addNode(Node* node) { + nodes.push_back(std::unique_ptr(node)); + return node; + } + + Node* makeZeroComp(Node* node, bool equal, Expression* origin) { + assert(!node->isBad()); + Builder builder(*module); + auto type = node->getWasmType(); + if (!isConcreteType(type)) return &bad; + auto* zero = makeZero(type); + auto* expr = builder.makeBinary( + Abstract::getBinary(type, equal ? Abstract::Eq : Abstract::Ne), + makeUse(node), + makeUse(zero) + ); + auto* check = addNode(Node::makeExpr(expr, origin)); + check->addValue(expandFromI1(node, origin)); + check->addValue(zero); + return check; + } + + void setInUnreachable() { + locals.clear(); + } + + void setInReachable() { + locals.resize(func->getNumLocals()); + } + + bool isInUnreachable() { + return isInUnreachable(locals); + } + + bool isInUnreachable(const Locals& state) { + return state.empty(); + } + + bool isInUnreachable(const FlowState& state) { + return isInUnreachable(state.locals); + } + + // Visiting. + + Node* visitExpression(Expression* curr) { + // Control flow and get/set etc. are special. Aside from them, we just need + // to do something very generic. + if (auto* block = curr->dynCast()) { + return doVisitBlock(block); + } else if (auto* iff = curr->dynCast()) { + return doVisitIf(iff); + } else if (auto* loop = curr->dynCast()) { + return doVisitLoop(loop); + } else if (auto* get = curr->dynCast()) { + return doVisitGetLocal(get); + } else if (auto* set = curr->dynCast()) { + return doVisitSetLocal(set); + } else if (auto* br = curr->dynCast()) { + return doVisitBreak(br); + } else if (auto* sw = curr->dynCast()) { + return doVisitSwitch(sw); + } else if (auto* c = curr->dynCast()) { + return doVisitConst(c); + } else if (auto* unary = curr->dynCast()) { + return doVisitUnary(unary); + } else if (auto* binary = curr->dynCast()) { + return doVisitBinary(binary); + } else if (auto* select = curr->dynCast()) { + return *(node->getValue(1)) == *(node->getValue(2)); + } + break; + } + case Node::Type::Phi: { + auto* first = node->getValue(1); + // Check if any of the others are not equal + for (Index i = 2; i < node->values.size(); i++) { + auto* curr = node->getValue(i); + if (*first != *curr) { + return false; + } + } + return true; + } + default: {} + } + return false; +} + +// Checks if the inputs are all constant - something we could +// probably optimize. Returns false if irrelevant. +inline bool allInputsConstant(Node* node) { + switch (node->type) { + case Node::Type::Expr: { + if (node->expr->is()) { + return node->getValue(0)->isConst(); + } else if (node->expr->is()) { + return node->getValue(0)->isConst() && + node->getValue(1)->isConst(); + } else if (node->expr->is()) { + if (index == 0) { + return &select->condition; + } else if (index == 1) { + return &select->ifTrue; + } else if (index == 2) { + return &select->ifFalse; + } else { + WASM_UNREACHABLE(); + } + } else { + WASM_UNREACHABLE(); + } + } +}; + +Pass *createDataFlowOptsPass() { + return new DataFlowOpts(); +} + +} // namespace wasm + diff --git a/src/passes/DeadCodeElimination.cpp b/src/passes/DeadCodeElimination.cpp index a2f3b895c..1879b1fc8 100644 --- a/src/passes/DeadCodeElimination.cpp +++ b/src/passes/DeadCodeElimination.cpp @@ -68,7 +68,7 @@ struct DeadCodeElimination : public WalkerPass> void addBreak(Name name) { // we normally have already reduced unreachable code into (unreachable) - // nodes, so we would not get to this function at all anyhow, the breaking + // nodes, so we would not get to this place at all anyhow, the breaking // instruction itself would be removed. However, an exception are things // like (block (result i32) (call $x) (unreachable)) , which has type i32 // despite not being exited. diff --git a/src/passes/Souperify.cpp b/src/passes/Souperify.cpp new file mode 100644 index 000000000..a54146d38 --- /dev/null +++ b/src/passes/Souperify.cpp @@ -0,0 +1,691 @@ +/* + * Copyright 2018 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. + */ + +// +// Souperify - convert to Souper IR in text form. +// +// This needs 'flatten' to be run before it, as it assumes the IR is in +// flat form. You may also want to optimize a little, e.g. +// --flatten --simplify-locals-nonesting --reorder-locals +// (as otherwise flattening introduces many copies; we do ignore boring +// copies here, but they end up as identical LHSes). +// +// See https://github.com/google/souper/issues/323 +// +// TODO: +// * pcs and blockpcs for things other than ifs +// * Investigate 'inlining', adding in nodes through calls +// * Consider generalizing DataFlow IR for internal Binaryen use. +// * Automatic conversion of Binaryen IR opts to run on the DataFlow IR. +// This would subsume precompute-propagate, for example. Using DFIR we +// can "expand" the BIR into expressions that BIR opts can handle +// directly, without the need for *-propagate techniques. +// + +#include "wasm.h" +#include "pass.h" +#include "wasm-builder.h" +#include "ir/local-graph.h" +#include "ir/utils.h" +#include "dataflow/node.h" +#include "dataflow/graph.h" +#include "dataflow/utils.h" + +namespace wasm { + +static int debug() { + static char* str = getenv("BINARYEN_DEBUG_SOUPERIFY"); + static int ret = str ? atoi(str) : 0; + return ret; +} + +namespace DataFlow { + +// Internal helper to find all the uses of a set. +struct UseFinder { + // Gets a list of all the uses of an expression. As we are in flat IR, + // the expression must be the value of a set, and we seek the other sets + // (or rather, their values) that contain a get that uses that value. + // There may also be non-set uses of the value, for example in a drop + // or a return. We represent those with a nullptr, meaning "other". + std::vector getUses(Expression* origin, Graph& graph, LocalGraph& localGraph) { + if (debug() >= 2) { + std::cout << "getUses\n" << origin << '\n'; + } + std::vector ret; + auto* set = graph.getSet(origin); + if (!set) { + // If the parent is not a set (a drop, call, return, etc.) then + // it is not something we need to track. + return ret; + } + addSetUses(set, graph, localGraph, ret); + return ret; + } + + // There may be loops of sets with copies between them. + std::unordered_set seenSets; + + void addSetUses(SetLocal* set, Graph& graph, LocalGraph& localGraph, std::vector& ret) { + // If already handled, nothing to do here. + if (seenSets.count(set)) return; + seenSets.insert(set); + // Find all the uses of that set. + auto& gets = localGraph.setInfluences[set]; + if (debug() >= 2) { + std::cout << "addSetUses for " << set << ", " << gets.size() << " gets\n"; + } + for (auto* get : gets) { + // Each of these relevant gets is either + // (1) a child of a set, which we can track, or + // (2) not a child of a set, e.g., a call argument or such + auto& sets = localGraph.getInfluences[get]; // TODO: iterator + // In flat IR, each get can influence at most 1 set. + assert(sets.size() <= 1); + if (sets.size() == 0) { + // This get is not the child of a set. Check if it is a drop, + // otherwise it is an actual use, and so an external use. + auto* parent = graph.getParent(get); + if (parent && parent->is()) { + // Just ignore it. + } else { + ret.push_back(nullptr); + if (debug() >= 2) { + std::cout << "add nullptr\n"; + } + } + } else { + // This get is the child of a set. + auto* subSet = *sets.begin(); + // If this is a copy, we need to look through it: data-flow IR + // counts actual values, not copies, and in particular we need + // to look through the copies that implement a phi. + if (subSet->value == get) { + // Indeed a copy. + // TODO: this could be optimized and done all at once beforehand. + addSetUses(subSet, graph, localGraph, ret); + } else { + // Not a copy. + auto* value = subSet->value; + ret.push_back(value); + if (debug() >= 2) { + std::cout << "add a value\n" << value << '\n'; + } + } + } + } + } +}; + +// Generates a trace: all the information to generate a Souper LHS +// for a specific set_local whose value we want to infer. +struct Trace { + Graph& graph; + Node* toInfer; + // Nodes we should exclude from being children of traces (but they + // may be the root we try to infer. + std::unordered_set& excludeAsChildren; + + // A limit on how deep we go - we don't want to create arbitrarily + // large traces. + size_t depthLimit = 10; + size_t totalLimit = 30; + + bool bad = false; + std::vector nodes; + std::unordered_set addedNodes; + std::vector pathConditions; + // When we need to (like when the depth is too deep), we replace + // expressions with other expressions, and track them here. + std::unordered_map> replacements; + // The nodes that have additional external uses (only computed + // for the "work" nodes, not the descriptive nodes arriving for + // path conditions). + std::unordered_set hasExternalUses; + // We add path conditions after the "work". We collect them here + // and then go through them at the proper time. + std::vector conditionsToAdd; + // Whether we are at the adding-conditions stage (i.e., post + // adding the "work"). + bool addingConditions = false; + // The local information graph. Used to check if a node has external uses. + LocalGraph& localGraph; + + Trace(Graph& graph, Node* toInfer, std::unordered_set& excludeAsChildren, LocalGraph& localGraph) : graph(graph), toInfer(toInfer), excludeAsChildren(excludeAsChildren), localGraph(localGraph) { + if (debug() >= 2) { + std::cout << "\nstart a trace (in " << graph.func->name << ")\n"; + } + // Check if there is a depth limit override + auto* depthLimitStr = getenv("BINARYEN_SOUPERIFY_DEPTH_LIMIT"); + if (depthLimitStr) { + depthLimit = atoi(depthLimitStr); + } + auto* totalLimitStr = getenv("BINARYEN_SOUPERIFY_TOTAL_LIMIT"); + if (totalLimitStr) { + totalLimit = atoi(totalLimitStr); + } + // Pull in all the dependencies, starting from the value itself. + add(toInfer, 0); + if (bad) return; + // If we are trivial before adding pcs, we are still trivial, and + // can ignore this. + auto sizeBeforePathConditions = nodes.size(); + // No input is uninteresting + if (sizeBeforePathConditions == 0) { + bad = true; + return; + } + // Just a var is uninteresting. TODO: others too? + if (sizeBeforePathConditions == 1 && nodes[0]->isVar()) { + bad = true; + return; + } + // Before adding the path conditions, we can now compute the + // actual number of uses of "work" nodes, the real computation done + // here and that we hope to replace, as opposed to path condition + // computation which is only descriptive and helps optimization of + // the work. + findExternalUses(); + // We can now add conditions. + addingConditions = true; + for (auto* condition : conditionsToAdd) { + add(condition, 0); + } + // Add in path conditions based on the location of this node: e.g. + // if it is inside an if's true branch, we can add a path-condition + // for that. + auto iter = graph.nodeParentMap.find(toInfer); + if (iter != graph.nodeParentMap.end()) { + addPath(toInfer, iter->second); + } + } + + Node* add(Node* node, size_t depth) { + depth++; + // If replaced, return the replacement. + auto iter = replacements.find(node); + if (iter != replacements.end()) { + return iter->second.get(); + } + // If already added, nothing more to do. + if (addedNodes.find(node) != addedNodes.end()) { + return node; + } + switch (node->type) { + case Node::Type::Var: { + break; // nothing more to add + } + case Node::Type::Expr: { + // If this is a Const, it's not an instruction - nothing to add, + // it's just a value. + if (node->expr->is()) { + return node; + } + // If we've gone too deep, emit a var instead. + // Do the same if this is a node we should exclude from traces. + if (depth >= depthLimit || nodes.size() >= totalLimit || + (node != toInfer && excludeAsChildren.find(node) != excludeAsChildren.end())) { + auto type = node->getWasmType(); + assert(isConcreteType(type)); + auto* var = Node::makeVar(type); + replacements[node] = std::unique_ptr(var); + node = var; + break; + } + // Add the dependencies. + assert(!node->expr->is()); + for (Index i = 0; i < node->values.size(); i++) { + add(node->getValue(i), depth); + } + break; + } + case Node::Type::Phi: { + auto* block = add(node->getValue(0), depth); + assert(block); + auto size = block->values.size(); + // First, add the conditions for the block + for (Index i = 0; i < size; i++) { + // a condition may be bad, but conditions are not necessary - + // we can proceed without the extra condition information + auto* condition = block->getValue(i); + if (!condition->isBad()) { + if (!addingConditions) { + // Too early, queue it for later. + conditionsToAdd.push_back(condition); + } else { + add(condition, depth); + } + } + } + // Then, add the phi values + for (Index i = 1; i < size + 1; i++) { + add(node->getValue(i), depth); + } + break; + } + case Node::Type::Cond: { + add(node->getValue(0), depth); // add the block + add(node->getValue(1), depth); // add the node + break; + } + case Node::Type::Block: { + break; // nothing more to add + } + case Node::Type::Zext: { + add(node->getValue(0), depth); + break; + } + case Node::Type::Bad: { + bad = true; + return nullptr; + } + default: WASM_UNREACHABLE(); + } + // Assert on no cycles + assert(addedNodes.find(node) == addedNodes.end()); + nodes.push_back(node); + addedNodes.insert(node); + return node; + } + + void addPath(Node* node, Expression* curr) { + // We track curr and parent, which are always in the state of parent + // being the parent of curr. + auto* parent = graph.expressionParentMap.at(curr); + while (parent) { + auto iter = graph.expressionConditionMap.find(parent); + if (iter != graph.expressionConditionMap.end()) { + // Given the block, add a proper path-condition + addPathTo(parent, curr, iter->second); + } + curr = parent; + parent = graph.expressionParentMap.at(parent); + } + } + + // curr is a child of parent, and parent has a Block which we are + // give as 'node'. Add a path condition for reaching the child. + void addPathTo(Expression* parent, Expression* curr, std::vector conditions) { + if (auto* iff = parent->dynCast()) { + Index index; + if (curr == iff->ifTrue) { + index = 0; + } else if (curr == iff->ifFalse) { + index = 1; + } else { + WASM_UNREACHABLE(); + } + auto* condition = conditions[index]; + // Add the condition itself as an instruction in the trace - + // the pc uses it as its input. + add(condition, 0); + // Add it as a pc, which we will emit directly. + pathConditions.push_back(condition); + } else { + WASM_UNREACHABLE(); + } + } + + bool isBad() { + return bad; + } + + static bool isTraceable(Node* node) { + if (!node->origin) { + // Ignore artificial etc. nodes. + // TODO: perhaps require all the nodes for an origin appear, so we + // don't try to compute an internal part of one, like the + // extra artificial != 0 of a select? + return false; + } + if (node->isExpr()) { + // Consider only the simple computational nodes. + auto* expr = node->expr; + return expr->is() || expr->is() || expr->is()) { + std::cout << "select "; + printInternal(node->getValue(0)); + std::cout << ", "; + printInternal(node->getValue(1)); + std::cout << ", "; + printInternal(node->getValue(2)); + } else { + WASM_UNREACHABLE(); + } + } + + void printPathCondition(Node* condition) { + std::cout << "pc "; + printInternal(condition); + std::cout << " 1:i1\n"; + } + + // Checks if a value looks suspiciously optimizable. + void warnOnSuspiciousValues(Node* node) { + assert(debug()); + // If the node has no uses, it's not interesting enough to be + // suspicious. TODO + // If an input was replaced with a var, then we should not + // look into it, it's not suspiciously trivial. + for (auto* value : node->values) { + if (value != getMaybeReplaced(value)) { + return; + } + } + if (allInputsIdentical(node)) { + std::cout << "^^ suspicious identical inputs! missing optimization in " << graph.func->name << "? ^^\n"; + return; + } + if (!node->isPhi() && allInputsConstant(node)) { + std::cout << "^^ suspicious constant inputs! missing optimization in " << graph.func->name << "? ^^\n"; + return; + } + } +}; + +} // namespace DataFlow + +struct Souperify : public WalkerPass> { + // Not parallel, for now - could parallelize and combine outputs at the end. + // If Souper is thread-safe, we could also run it in parallel. + + bool singleUseOnly; + + Souperify(bool singleUseOnly) : singleUseOnly(singleUseOnly) {} + + void doWalkFunction(Function* func) { + std::cout << "\n; function: " << func->name << '\n'; + // Build the data-flow IR. + DataFlow::Graph graph; + graph.build(func, getModule()); + if (debug() >= 2) dump(graph, std::cout); + // Build the local graph data structure. + LocalGraph localGraph(func); + localGraph.computeInfluences(); + // If we only want single-use nodes, exclude all the others. + std::unordered_set excludeAsChildren; + if (singleUseOnly) { + for (auto& nodePtr : graph.nodes) { + auto* node = nodePtr.get(); + if (node->origin) { + // TODO: work for identical origins could be saved + auto uses = DataFlow::UseFinder().getUses(node->origin, graph, localGraph); + if (debug() >= 2) { + std::cout << "following node has " << uses.size() << " uses\n"; + dump(node, std::cout); + } + if (uses.size() > 1) { + excludeAsChildren.insert(node); + } + } + } + } + // Emit possible traces. + for (auto& nodePtr : graph.nodes) { + auto* node = nodePtr.get(); + // Trace + if (DataFlow::Trace::isTraceable(node)) { + DataFlow::Trace trace(graph, node, excludeAsChildren, localGraph); + if (!trace.isBad()) { + DataFlow::Printer printer(graph, trace); + if (singleUseOnly) { + assert(!printer.printedHasExternalUses); + } + } + } + } + } +}; + +Pass *createSouperifyPass() { + return new Souperify(false); +} + +Pass *createSouperifySingleUsePass() { + return new Souperify(true); +} + +} // namespace wasm + diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 97151f847..c8d02f445 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -72,6 +72,7 @@ void PassRegistry::registerPasses() { registerPass("code-folding", "fold code, merging duplicates", createCodeFoldingPass); registerPass("const-hoisting", "hoist repeated constants to a local", createConstHoistingPass); registerPass("dce", "removes unreachable code", createDeadCodeEliminationPass); + registerPass("dfo", "optimizes using the DataFlow SSA IR", createDataFlowOptsPass); registerPass("duplicate-function-elimination", "removes duplicate functions", createDuplicateFunctionEliminationPass); registerPass("extract-function", "leaves just one function (useful for debugging)", createExtractFunctionPass); registerPass("flatten", "flattens out code, removing nesting", createFlattenPass); @@ -117,9 +118,11 @@ void PassRegistry::registerPasses() { registerPass("safe-heap", "instrument loads and stores to check for invalid behavior", createSafeHeapPass); registerPass("simplify-locals", "miscellaneous locals-related optimizations", createSimplifyLocalsPass); registerPass("simplify-locals-nonesting", "miscellaneous locals-related optimizations (no nesting at all; preserves flatness)", createSimplifyLocalsNoNestingPass); - registerPass("simplify-locals-notee", "miscellaneous locals-related optimizations", createSimplifyLocalsNoTeePass); - registerPass("simplify-locals-nostructure", "miscellaneous locals-related optimizations", createSimplifyLocalsNoStructurePass); - registerPass("simplify-locals-notee-nostructure", "miscellaneous locals-related optimizations", createSimplifyLocalsNoTeeNoStructurePass); + registerPass("simplify-locals-notee", "miscellaneous locals-related optimizations (no tees)", createSimplifyLocalsNoTeePass); + registerPass("simplify-locals-nostructure", "miscellaneous locals-related optimizations (no structure)", createSimplifyLocalsNoStructurePass); + registerPass("simplify-locals-notee-nostructure", "miscellaneous locals-related optimizations (no tees or structure)", createSimplifyLocalsNoTeeNoStructurePass); + registerPass("souperify", "emit Souper IR in text form", createSouperifyPass); + registerPass("souperify-single-use", "emit Souper IR in text form (single-use nodes only)", createSouperifySingleUsePass); registerPass("spill-pointers", "spill pointers to the C stack (useful for Boehm-style GC)", createSpillPointersPass); registerPass("ssa", "ssa-ify variables so that they have a single assignment", createSSAifyPass); registerPass("trap-mode-clamp", "replace trapping operations with clamping semantics", createTrapModeClamp); diff --git a/src/passes/passes.h b/src/passes/passes.h index 1e26dc777..e853c040a 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -27,6 +27,7 @@ Pass* createCoalesceLocalsWithLearningPass(); Pass* createCodeFoldingPass(); Pass* createCodePushingPass(); Pass* createConstHoistingPass(); +Pass* createDataFlowOptsPass(); Pass* createDeadCodeEliminationPass(); Pass* createDuplicateFunctionEliminationPass(); Pass* createExtractFunctionPass(); @@ -76,6 +77,8 @@ Pass* createSimplifyLocalsNoNestingPass(); Pass* createSimplifyLocalsNoTeePass(); Pass* createSimplifyLocalsNoStructurePass(); Pass* createSimplifyLocalsNoTeeNoStructurePass(); +Pass* createSouperifyPass(); +Pass* createSouperifySingleUsePass(); Pass* createSpillPointersPass(); Pass* createSSAifyPass(); Pass* createTrapModeClamp(); diff --git a/src/wasm-traversal.h b/src/wasm-traversal.h index 0c5088917..0c775e872 100644 --- a/src/wasm-traversal.h +++ b/src/wasm-traversal.h @@ -213,7 +213,7 @@ struct OverriddenVisitor { // separate visit* per node template -struct UnifiedExpressionVisitor : public Visitor { +struct UnifiedExpressionVisitor : public Visitor { // called on each node ReturnType visitExpression(Expression* curr) { return ReturnType(); } -- cgit v1.2.3