| /* |
| * 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. |
| */ |
| |
| // |
| // Flattens code into "Flat IR" form. See ir/flat.h. |
| // |
| // TODO: handle non-nullability. for example: |
| // |
| // (module |
| // (type $none (func)) |
| // (func $foo |
| // (drop |
| // (block (result funcref (ref $none)) |
| // (tuple.make 2 |
| // (ref.null func) |
| // (ref.func $foo) |
| // ) |
| // ) |
| // ) |
| // ) |
| // ) |
| // |
| // The tuple has a non-nullable type, and so it cannot currently be set to a |
| // local, but in principle there's no reason it couldn't be. For now, error on |
| // this. |
| |
| #include <ir/branch-utils.h> |
| #include <ir/effects.h> |
| #include <ir/eh-utils.h> |
| #include <ir/flat.h> |
| #include <ir/properties.h> |
| #include <ir/utils.h> |
| #include <pass.h> |
| #include <wasm-builder.h> |
| #include <wasm.h> |
| |
| namespace wasm { |
| |
| // We use the following algorithm: we maintain a list of "preludes", code |
| // that runs right before an expression. When we visit an expression we |
| // must handle it and its preludes. If the expression has side effects, |
| // we reduce it to a local.get and add a prelude for that. We then handle |
| // the preludes, by moving them to the parent or handling them directly. |
| // we can move them to the parent if the parent is not a control flow |
| // structure. Otherwise, if the parent is a control flow structure, it |
| // will incorporate the preludes of its children accordingly. |
| // As a result, when we reach a node, we know its children have no |
| // side effects (they have been moved to a prelude), or we are a |
| // control flow structure (which allows children with side effects, |
| // e.g. a return as a block element). |
| // Once exception is that we allow an (unreachable) node, which is used |
| // when we move something unreachable to another place, and need a |
| // placeholder. We will never reach that (unreachable) anyhow |
| struct Flatten |
| : public WalkerPass< |
| ExpressionStackWalker<Flatten, UnifiedExpressionVisitor<Flatten>>> { |
| bool isFunctionParallel() override { return true; } |
| |
| // Flattening splits the original locals into a great many other ones, losing |
| // track of the originals that DWARF refers to. |
| // FIXME DWARF updating does not handle local changes yet. |
| bool invalidatesDWARF() override { return true; } |
| |
| std::unique_ptr<Pass> create() override { |
| return std::make_unique<Flatten>(); |
| } |
| |
| // For each expression, a bunch of expressions that should execute right |
| // before it |
| std::unordered_map<Expression*, std::vector<Expression*>> preludes; |
| |
| // Break values are sent through a temp local |
| std::unordered_map<Name, Index> breakTemps; |
| |
| void visitExpression(Expression* curr) { |
| std::vector<Expression*> ourPreludes; |
| Builder builder(*getModule()); |
| |
| // Nothing to do for constants and nop. |
| if (Properties::isConstantExpression(curr) || curr->is<Nop>()) { |
| return; |
| } |
| |
| if (Properties::isControlFlowStructure(curr)) { |
| // handle control flow explicitly. our children do not have control flow, |
| // but they do have preludes which we need to set up in the right place |
| |
| // no one should have given us preludes, they are on the children |
| assert(preludes.find(curr) == preludes.end()); |
| |
| if (auto* block = curr->dynCast<Block>()) { |
| // make a new list, where each item's preludes are added before it |
| ExpressionList newList(getModule()->allocator); |
| for (auto* item : block->list) { |
| auto iter = preludes.find(item); |
| if (iter != preludes.end()) { |
| auto& itemPreludes = iter->second; |
| for (auto* prelude : itemPreludes) { |
| newList.push_back(prelude); |
| } |
| itemPreludes.clear(); |
| } |
| newList.push_back(item); |
| } |
| block->list.swap(newList); |
| // remove a block return value |
| auto type = block->type; |
| if (type.isConcrete()) { |
| // if there is a temp index for breaking to the block, use that |
| Index temp; |
| auto iter = breakTemps.find(block->name); |
| if (iter != breakTemps.end()) { |
| temp = iter->second; |
| } else { |
| temp = builder.addVar(getFunction(), type); |
| } |
| auto*& last = block->list.back(); |
| if (last->type.isConcrete()) { |
| last = builder.makeLocalSet(temp, last); |
| } |
| block->finalize(Type::none); |
| // and we leave just a get of the value |
| auto* rep = builder.makeLocalGet(temp, type); |
| replaceCurrent(rep); |
| // the whole block is now a prelude |
| ourPreludes.push_back(block); |
| } |
| // the block now has no return value, and may have become unreachable |
| block->finalize(Type::none); |
| |
| } else if (auto* iff = curr->dynCast<If>()) { |
| // condition preludes go before the entire if |
| auto* rep = getPreludesWithExpression(iff->condition, iff); |
| // arm preludes go in the arms. we must also remove an if value |
| auto* originalIfTrue = iff->ifTrue; |
| auto* originalIfFalse = iff->ifFalse; |
| auto type = iff->ifFalse ? Type::getLeastUpperBound(iff->ifTrue->type, |
| iff->ifFalse->type) |
| : Type::none; |
| Expression* prelude = nullptr; |
| if (type.isConcrete()) { |
| Index temp = builder.addVar(getFunction(), type); |
| if (iff->ifTrue->type.isConcrete()) { |
| iff->ifTrue = builder.makeLocalSet(temp, iff->ifTrue); |
| } |
| if (iff->ifFalse->type.isConcrete()) { |
| iff->ifFalse = builder.makeLocalSet(temp, iff->ifFalse); |
| } |
| if (curr->type.isConcrete()) { |
| // the whole if (+any preludes from the condition) is now a prelude |
| prelude = rep; |
| // and we leave just a get of the value |
| rep = builder.makeLocalGet(temp, type); |
| } |
| } |
| iff->ifTrue = getPreludesWithExpression(originalIfTrue, iff->ifTrue); |
| if (iff->ifFalse) { |
| iff->ifFalse = |
| getPreludesWithExpression(originalIfFalse, iff->ifFalse); |
| } |
| iff->finalize(); |
| if (prelude) { |
| ReFinalizeNode().visit(prelude); |
| ourPreludes.push_back(prelude); |
| } |
| replaceCurrent(rep); |
| |
| } else if (auto* loop = curr->dynCast<Loop>()) { |
| // remove a loop value |
| Expression* rep = loop; |
| auto* originalBody = loop->body; |
| auto type = loop->type; |
| if (type.isConcrete()) { |
| Index temp = builder.addVar(getFunction(), type); |
| loop->body = builder.makeLocalSet(temp, loop->body); |
| // and we leave just a get of the value |
| rep = builder.makeLocalGet(temp, type); |
| // the whole if is now a prelude |
| ourPreludes.push_back(loop); |
| loop->type = Type::none; |
| } |
| loop->body = getPreludesWithExpression(originalBody, loop->body); |
| loop->finalize(); |
| replaceCurrent(rep); |
| |
| } else if (auto* tryy = curr->dynCast<Try>()) { |
| // remove a try value |
| Expression* rep = tryy; |
| auto* originalBody = tryy->body; |
| std::vector<Expression*> originalCatchBodies(tryy->catchBodies.begin(), |
| tryy->catchBodies.end()); |
| auto type = tryy->type; |
| if (type.isConcrete()) { |
| Index temp = builder.addVar(getFunction(), type); |
| if (tryy->body->type.isConcrete()) { |
| tryy->body = builder.makeLocalSet(temp, tryy->body); |
| } |
| for (Index i = 0; i < tryy->catchBodies.size(); i++) { |
| if (tryy->catchBodies[i]->type.isConcrete()) { |
| tryy->catchBodies[i] = |
| builder.makeLocalSet(temp, tryy->catchBodies[i]); |
| } |
| } |
| // and we leave just a get of the value |
| rep = builder.makeLocalGet(temp, type); |
| // the whole try is now a prelude |
| ourPreludes.push_back(tryy); |
| } |
| tryy->body = getPreludesWithExpression(originalBody, tryy->body); |
| for (Index i = 0; i < tryy->catchBodies.size(); i++) { |
| tryy->catchBodies[i] = getPreludesWithExpression( |
| originalCatchBodies[i], tryy->catchBodies[i]); |
| } |
| tryy->finalize(); |
| replaceCurrent(rep); |
| |
| } else { |
| WASM_UNREACHABLE("unexpected expr type"); |
| } |
| |
| } else { |
| // for anything else, there may be existing preludes |
| auto iter = preludes.find(curr); |
| if (iter != preludes.end()) { |
| ourPreludes.swap(iter->second); |
| } |
| |
| // special handling |
| if (auto* set = curr->dynCast<LocalSet>()) { |
| if (set->isTee()) { |
| // we disallow local.tee |
| if (set->value->type == Type::unreachable) { |
| replaceCurrent(set->value); // trivial, no set happens |
| } else { |
| // use a set in a prelude + a get |
| set->makeSet(); |
| ourPreludes.push_back(set); |
| Type localType = getFunction()->getLocalType(set->index); |
| replaceCurrent(builder.makeLocalGet(set->index, localType)); |
| } |
| } |
| |
| } else if (auto* br = curr->dynCast<Break>()) { |
| if (br->value) { |
| auto type = br->value->type; |
| if (type.isConcrete()) { |
| // we are sending a value. use a local instead |
| Type blockType = findBreakTarget(br->name)->type; |
| Index temp = getTempForBreakTarget(br->name, blockType); |
| ourPreludes.push_back(builder.makeLocalSet(temp, br->value)); |
| |
| // br_if leaves a value on the stack if not taken, which later can |
| // be the last element of the enclosing innermost block and flow |
| // out. The local we created using 'getTempForBreakTarget' returns |
| // the return type of the block this branch is targetting, which may |
| // not be the same with the innermost block's return type. For |
| // example, |
| // (block $any (result anyref) |
| // (block (result funcref) |
| // (local.tee $0 |
| // (br_if $any |
| // (ref.null func) |
| // (i32.const 0) |
| // ) |
| // ) |
| // ) |
| // ) |
| // In this case we need two locals to store (ref.null); one with |
| // funcref type that's for the target block ($label0) and one more |
| // with anyref type in case for flowing out. Here we create the |
| // second 'flowing out' local in case two block's types are |
| // different. |
| if (type != blockType) { |
| temp = builder.addVar(getFunction(), type); |
| ourPreludes.push_back(builder.makeLocalSet( |
| temp, ExpressionManipulator::copy(br->value, *getModule()))); |
| } |
| |
| if (br->condition) { |
| // the value must also flow out |
| ourPreludes.push_back(br); |
| if (br->type.isConcrete()) { |
| replaceCurrent(builder.makeLocalGet(temp, type)); |
| } else { |
| assert(br->type == Type::unreachable); |
| replaceCurrent(builder.makeUnreachable()); |
| } |
| } |
| br->value = nullptr; |
| br->finalize(); |
| } else { |
| assert(type == Type::unreachable); |
| // we don't need the br at all |
| replaceCurrent(br->value); |
| } |
| } |
| |
| } else if (auto* sw = curr->dynCast<Switch>()) { |
| if (sw->value) { |
| auto type = sw->value->type; |
| if (type.isConcrete()) { |
| // we are sending a value. use a local instead |
| Index temp = builder.addVar(getFunction(), type); |
| ourPreludes.push_back(builder.makeLocalSet(temp, sw->value)); |
| // we don't know which break target will be hit - assign to them all |
| auto names = BranchUtils::getUniqueTargets(sw); |
| for (auto name : names) { |
| ourPreludes.push_back( |
| builder.makeLocalSet(getTempForBreakTarget(name, type), |
| builder.makeLocalGet(temp, type))); |
| } |
| sw->value = nullptr; |
| sw->finalize(); |
| } else { |
| assert(type == Type::unreachable); |
| // we don't need the br at all |
| replaceCurrent(sw->value); |
| } |
| } |
| } |
| } |
| |
| if (curr->is<BrOn>() || curr->is<TryTable>()) { |
| Fatal() << "Unsupported instruction for Flatten: " |
| << getExpressionName(curr); |
| } |
| |
| // continue for general handling of everything, control flow or otherwise |
| curr = getCurrent(); // we may have replaced it |
| // we have changed children |
| ReFinalizeNode().visit(curr); |
| if (curr->type == Type::unreachable) { |
| ourPreludes.push_back(curr); |
| replaceCurrent(builder.makeUnreachable()); |
| } else if (curr->type.isConcrete()) { |
| // use a local |
| auto type = curr->type; |
| Index temp = builder.addVar(getFunction(), type); |
| ourPreludes.push_back(builder.makeLocalSet(temp, curr)); |
| replaceCurrent(builder.makeLocalGet(temp, type)); |
| } |
| |
| // next, finish up: migrate our preludes if we can |
| if (!ourPreludes.empty()) { |
| auto* parent = getParent(); |
| if (parent && !Properties::isControlFlowStructure(parent)) { |
| auto& parentPreludes = preludes[parent]; |
| for (auto* prelude : ourPreludes) { |
| parentPreludes.push_back(prelude); |
| } |
| } else { |
| // keep our preludes, parent will handle them |
| preludes[getCurrent()].swap(ourPreludes); |
| } |
| } |
| } |
| |
| void visitFunction(Function* curr) { |
| auto* originalBody = curr->body; |
| // if the body is a block with a result, turn that into a return |
| if (curr->body->type.isConcrete()) { |
| curr->body = Builder(*getModule()).makeReturn(curr->body); |
| } |
| // the body may have preludes |
| curr->body = getPreludesWithExpression(originalBody, curr->body); |
| |
| // Flatten can generate blocks within 'catch', making pops invalid. Fix them |
| // up. |
| EHUtils::handleBlockNestedPops(curr, *getModule()); |
| } |
| |
| private: |
| // gets an expression, either by itself, or in a block with its |
| // preludes (which we use up) before it |
| Expression* getPreludesWithExpression(Expression* curr) { |
| return getPreludesWithExpression(curr, curr); |
| } |
| |
| // gets an expression, either by itself, or in a block with some |
| // preludes (which we use up) for another expression before it |
| Expression* getPreludesWithExpression(Expression* preluder, |
| Expression* after) { |
| auto iter = preludes.find(preluder); |
| if (iter == preludes.end()) { |
| return after; |
| } |
| // we have preludes |
| auto& thePreludes = iter->second; |
| auto* ret = Builder(*getModule()).makeBlock(thePreludes); |
| thePreludes.clear(); |
| ret->list.push_back(after); |
| ret->finalize(); |
| return ret; |
| } |
| |
| // get the temp local to be used for breaks to that target. allocates |
| // one if there isn't one yet |
| Index getTempForBreakTarget(Name name, Type type) { |
| auto iter = breakTemps.find(name); |
| if (iter != breakTemps.end()) { |
| return iter->second; |
| } else { |
| return breakTemps[name] = |
| Builder(*getModule()).addVar(getFunction(), type); |
| } |
| } |
| }; |
| |
| Pass* createFlattenPass() { return new Flatten(); } |
| |
| } // namespace wasm |