| /* | |
| * 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. | |
| */ | |
| // | |
| // Optimize using the DataFlow SSA IR. | |
| // | |
| // This needs 'flatten' to be run before it, and you should run full | |
| // regular opts afterwards to clean up the flattening. For example, | |
| // you might use it like this: | |
| // | |
| // --flatten --dfo -Os | |
| // | |
| #include "dataflow/graph.h" | |
| #include "dataflow/node.h" | |
| #include "dataflow/users.h" | |
| #include "dataflow/utils.h" | |
| #include "ir/flat.h" | |
| #include "ir/utils.h" | |
| #include "pass.h" | |
| #include "wasm-builder.h" | |
| #include "wasm.h" | |
| namespace wasm { | |
| struct DataFlowOpts : public WalkerPass<PostWalker<DataFlowOpts>> { | |
| bool isFunctionParallel() override { return true; } | |
| std::unique_ptr<Pass> create() override { | |
| return std::make_unique<DataFlowOpts>(); | |
| } | |
| DataFlow::Users nodeUsers; | |
| // The optimization work left to do: nodes that we need to look at. | |
| std::unordered_set<DataFlow::Node*> workLeft; | |
| DataFlow::Graph graph; | |
| void doWalkFunction(Function* func) { | |
| Flat::verifyFlatness(func); | |
| // Build the data-flow IR. | |
| graph.build(func, getModule()); | |
| nodeUsers.build(graph); | |
| // Propagate optimizations through the graph. | |
| std::unordered_set<DataFlow::Node*> optimized; // which nodes we optimized | |
| for (auto& node : graph.nodes) { | |
| workLeft.insert(node.get()); // we should try to optimize each node | |
| } | |
| while (!workLeft.empty()) { | |
| // std::cout << "\n\ndump before work iter\n"; | |
| // dump(graph, std::cout); | |
| auto iter = workLeft.begin(); | |
| auto* node = *iter; | |
| workLeft.erase(iter); | |
| workOn(node); | |
| } | |
| // After updating the DataFlow IR, we can update the sets in | |
| // the wasm. | |
| // TODO: we also need phis, as a phi can flow directly into say | |
| // a return or a call parameter. | |
| for (auto* set : graph.sets) { | |
| auto* node = graph.setNodeMap[set]; | |
| auto iter = optimized.find(node); | |
| if (iter != optimized.end()) { | |
| assert(node->isExpr()); // this is a set, where the node is defined | |
| set->value = node->expr; | |
| } | |
| } | |
| } | |
| void workOn(DataFlow::Node* node) { | |
| if (node->isConst()) { | |
| return; | |
| } | |
| // If there are no uses, there is no point to work. | |
| if (nodeUsers.getNumUses(node) == 0) { | |
| return; | |
| } | |
| // Optimize: Look for nodes that we can easily convert into | |
| // something simpler. | |
| // TODO: we can expressionify and run full normal opts on that, | |
| // then copy the result if it's smaller. | |
| if (node->isPhi() && DataFlow::allInputsIdentical(node)) { | |
| // Note we don't need to check for effects when replacing, as in | |
| // flattened IR expression children are local.gets or consts. | |
| auto* value = node->getValue(1); | |
| if (value->isConst()) { | |
| replaceAllUsesWith(node, value); | |
| } | |
| } else if (node->isExpr() && DataFlow::allInputsConstant(node)) { | |
| assert(!node->isConst()); | |
| // If this is a concrete value (not e.g. an eqz of unreachable), | |
| // it can definitely be precomputed into a constant. | |
| if (node->expr->type.isConcrete()) { | |
| // This can be precomputed. | |
| // TODO not just all-constant inputs? E.g. i32.mul of 0 and X. | |
| optimizeExprToConstant(node); | |
| } | |
| } | |
| } | |
| void optimizeExprToConstant(DataFlow::Node* node) { | |
| assert(node->isExpr()); | |
| assert(!node->isConst()); | |
| // std::cout << "will optimize an Expr of all constant inputs. before" << | |
| // '\n'; | |
| // dump(node, std::cout); | |
| auto* expr = node->expr; | |
| // First, note that some of the expression's children may be | |
| // local.gets that we inferred during SSA analysis as constant. | |
| // We can apply those now. | |
| for (Index i = 0; i < node->values.size(); i++) { | |
| if (node->values[i]->isConst()) { | |
| auto* currp = getIndexPointer(expr, i); | |
| // Directly represent it as a constant. (Note that it may already be | |
| // a constant, but for now to avoid corner cases just replace them | |
| // all here.) | |
| auto* c = node->values[i]->expr->dynCast<Const>(); | |
| *currp = Builder(*getModule()).makeConst(c->value); | |
| } | |
| } | |
| // Now we know that all our DataFlow inputs are constant, and all | |
| // our Binaryen IR representations of them are constant too. RUn | |
| // precompute, which will transform the expression into a constant. | |
| Module temp; | |
| // XXX we should copy expr here, in principle, and definitely will need to | |
| // when we do arbitrarily regenerated expressions | |
| std::unique_ptr<Function> tempFunc(Builder(temp).makeFunction( | |
| "temp", Signature(Type::none, Type::none), {}, expr)); | |
| PassRunner runner(&temp); | |
| runner.setIsNested(true); | |
| runner.add("precompute"); | |
| runner.runOnFunction(tempFunc.get()); | |
| // Get the optimized thing | |
| auto* result = tempFunc->body; | |
| // It may not be a constant, e.g. 0 / 0 does not optimize to 0 | |
| if (!result->is<Const>()) { | |
| return; | |
| } | |
| // All good, copy it. | |
| node->expr = Builder(*getModule()).makeConst(result->cast<Const>()->value); | |
| assert(node->isConst()); | |
| // We no longer have values, and so do not use anything. | |
| nodeUsers.stopUsingValues(node); | |
| node->values.clear(); | |
| // Our contents changed, update our users. | |
| replaceAllUsesWith(node, node); | |
| } | |
| // Replaces all uses of a node with another value. This both modifies | |
| // the DataFlow IR to make the other users point to this one, and | |
| // updates the underlying Binaryen IR as well. | |
| // This can be used to "replace" a node with itself, which makes sense | |
| // when the node contents have changed and so the users must be updated. | |
| void replaceAllUsesWith(DataFlow::Node* node, DataFlow::Node* with) { | |
| // Const nodes are trivial to replace, but other stuff is trickier - | |
| // in particular phis. | |
| assert(with->isConst()); // TODO | |
| // All the users should be worked on later, as we will update them. | |
| auto& users = nodeUsers.getUsers(node); | |
| for (auto* user : users) { | |
| // Add the user to the work left to do, as we are modifying it. | |
| workLeft.insert(user); | |
| // `with` is getting another user. | |
| nodeUsers.addUser(with, user); | |
| // Replacing in the DataFlow IR is simple - just replace it, | |
| // in all the indexes it appears. | |
| std::vector<Index> indexes; | |
| for (Index i = 0; i < user->values.size(); i++) { | |
| if (user->values[i] == node) { | |
| user->values[i] = with; | |
| indexes.push_back(i); | |
| } | |
| } | |
| assert(!indexes.empty()); | |
| // Replacing in the Binaryen IR requires more care | |
| switch (user->type) { | |
| case DataFlow::Node::Type::Expr: { | |
| auto* expr = user->expr; | |
| for (auto index : indexes) { | |
| *(getIndexPointer(expr, index)) = graph.makeUse(with); | |
| } | |
| break; | |
| } | |
| case DataFlow::Node::Type::Phi: { | |
| // Nothing to do: a phi is not in the Binaryen IR. | |
| // If the entire phi can become a constant, that will be | |
| // propagated when we process that phi later. | |
| break; | |
| } | |
| case DataFlow::Node::Type::Cond: { | |
| // Nothing to do: a cond is not in the Binaryen IR. | |
| // If the cond input is a constant, that might indicate | |
| // useful optimizations are possible, which perhaps we | |
| // should look into TODO | |
| break; | |
| } | |
| case DataFlow::Node::Type::Zext: { | |
| // Nothing to do: a zext is not in the Binaryen IR. | |
| // If the cond input is a constant, that might indicate | |
| // useful optimizations are possible, which perhaps we | |
| // should look into TODO | |
| break; | |
| } | |
| default: | |
| WASM_UNREACHABLE("unexpected dataflow node type"); | |
| } | |
| } | |
| // No one is a user of this node after we replaced all the uses. | |
| nodeUsers.removeAllUsesOf(node); | |
| } | |
| // Gets a pointer to the expression pointer in an expression. | |
| // That is, given an index in the values() vector, get an | |
| // Expression** that we can assign to so as to modify it. | |
| Expression** getIndexPointer(Expression* expr, Index index) { | |
| if (auto* unary = expr->dynCast<Unary>()) { | |
| assert(index == 0); | |
| return &unary->value; | |
| } else if (auto* binary = expr->dynCast<Binary>()) { | |
| if (index == 0) { | |
| return &binary->left; | |
| } else if (index == 1) { | |
| return &binary->right; | |
| } | |
| WASM_UNREACHABLE("unexpected index"); | |
| } else if (auto* select = expr->dynCast<Select>()) { | |
| if (index == 0) { | |
| return &select->condition; | |
| } else if (index == 1) { | |
| return &select->ifTrue; | |
| } else if (index == 2) { | |
| return &select->ifFalse; | |
| } | |
| WASM_UNREACHABLE("unexpected index"); | |
| } | |
| WASM_UNREACHABLE("unexpected expression type"); | |
| } | |
| }; | |
| Pass* createDataFlowOptsPass() { return new DataFlowOpts(); } | |
| } // namespace wasm |