diff options
-rw-r--r-- | src/passes/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/passes/FlattenControlFlow.cpp | 471 | ||||
-rw-r--r-- | src/passes/pass.cpp | 1 | ||||
-rw-r--r-- | src/passes/passes.h | 1 | ||||
-rw-r--r-- | src/wasm-builder.h | 5 | ||||
-rw-r--r-- | test/passes/flatten-control-flow.txt | 1177 | ||||
-rw-r--r-- | test/passes/flatten-control-flow.wast | 776 |
7 files changed, 2432 insertions, 0 deletions
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index 637f40d94..d7f17a3ca 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -5,6 +5,7 @@ SET(passes_SOURCES DeadCodeElimination.cpp DuplicateFunctionElimination.cpp ExtractFunction.cpp + FlattenControlFlow.cpp Inlining.cpp LegalizeJSInterface.cpp LocalCSE.cpp diff --git a/src/passes/FlattenControlFlow.cpp b/src/passes/FlattenControlFlow.cpp new file mode 100644 index 000000000..3da5809c3 --- /dev/null +++ b/src/passes/FlattenControlFlow.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. + */ + +// +// Flattens control flow, e.g. +// +// (i32.add +// (if (..condition..) +// (..if true..) +// (..if false..) +// ) +// (i32.const 1) +// ) +// => +// (if (..condition..) +// (set_local $temp +// (..if true..) +// ) +// (set_local $temp +// (..if false..) +// ) +// ) +// (i32.add +// (get_local $temp) +// (i32.const 1) +// ) +// +// Formally, this pass flattens control flow in the precise sense of +// making the AST have these properties: +// +// 1. Control flow structures (block, loop, if) and control flow +// operations (br, br_if, br_table, return, unreachable) may +// only be block children, a loop body, or an if-true or if-false. +// (I.e., they cannot be nested inside an i32.add, a drop, a +// call, an if-condition, etc.) +// 2. Disallow block, loop, and if return values, i.e., do not use +// control flow to pass around values. +// +// Note that we do still allow normal arbitrary nesting of expressions +// *without* control flow (i.e., this is not a reduction to 3-address +// code form). We also allow nesting of control flow, but just nested +// in other control flow, like an if in the true arm of an if, and +// so forth. What we achieve here is that when you see an expression, +// you know it has no control flow inside it, it will be fully +// executed. +// + +#include <wasm.h> +#include <pass.h> +#include <wasm-builder.h> + + +namespace wasm { + +// Looks for control flow changes and structures, excluding blocks (as we +// want to put all control flow on them) +struct ControlFlowChecker : public Visitor<ControlFlowChecker> { + static bool is(Expression* node) { + ControlFlowChecker finder; + finder.visit(node); + return finder.hasControlFlow; + } + + bool hasControlFlow = false; + + void visitBreak(Break *curr) { hasControlFlow = true; } + void visitSwitch(Switch *curr) { hasControlFlow = true; } + void visitBlock(Block *curr) { hasControlFlow = true; } + void visitLoop(Loop* curr) { hasControlFlow = true; } + void visitIf(If* curr) { hasControlFlow = true; } + void visitReturn(Return *curr) { hasControlFlow = true; } + void visitUnreachable(Unreachable *curr) { hasControlFlow = true; } +}; + +struct FlattenControlFlow : public WalkerPass<PostWalker<FlattenControlFlow>> { + bool isFunctionParallel() override { return true; } + + Pass* create() override { return new FlattenControlFlow; } + + std::unique_ptr<Builder> builder; + // we get rid of block/if/loop values. this map tells us for + // each break target what local index to use. + // if this is a flowing value, there might not be a name assigned + // (block ending, block with no name; or if value), so we use + // the expr (and there will be exactly one set and get of it, + // so we don't need a name) + std::map<Name, Index> breakNameIndexes; + std::map<Expression*, Index> breakExprIndexes; + + void doWalkFunction(Function* func) { + builder = make_unique<Builder>(*getModule()); + walk(func->body); + if (func->result != none) { + // if the body had a fallthrough, receive it and return it + auto iter = breakExprIndexes.find(func->body); + if (iter != breakExprIndexes.end()) { + func->body = builder->makeSequence( + func->body, + builder->makeReturn( + builder->makeGetLocal(iter->second, func->result) + ) + ); + } + } + } + + // returns the index to assign values to for a break target. allocates + // the local if this is the first time we see it. + // expr is used if this is a flowing value. + Index getBreakTargetIndex(Name name, WasmType type, Expression* expr = nullptr, Index index = -1) { + assert(isConcreteWasmType(type)); // we shouldn't get here if the value ins't actually set + if (name.is()) { + auto iter = breakNameIndexes.find(name); + if (iter == breakNameIndexes.end()) { + if (index == Index(-1)) { + index = builder->addVar(getFunction(), type); + } + breakNameIndexes[name] = index; + if (expr) { + breakExprIndexes[expr] = index; + } + return index; + } + if (expr) { + breakExprIndexes[expr] = iter->second; + } + return iter->second; + } else { + assert(expr); + auto iter = breakExprIndexes.find(expr); + if (iter == breakExprIndexes.end()) { + if (index == Index(-1)) { + index = builder->addVar(getFunction(), type); + } + return breakExprIndexes[expr] = index; + } + return iter->second; + } + } + + // When we reach a fallthrough value, it has already been flattened, and its value + // assigned to the proper local. Or, it may not have needed to be flattened, + // and we can just assign to a local. This method simply returns the fallthrough + // replacement code. + Expression* getFallthroughReplacement(Expression* child, Index myIndex) { + auto iter = breakExprIndexes.find(child); + if (iter != breakExprIndexes.end()) { + // it was flattened and saved to a local + return builder->makeSequence( + child, // which no longer flows a value, now it sets the child index + builder->makeSetLocal( + myIndex, + builder->makeGetLocal(iter->second, getFunction()->getLocalType(iter->second)) + ) + ); + } + // a simple expression + if (child->type == unreachable) { + // no need to even set the local + return child; + } else { + assert(!ControlFlowChecker::is(child)); + return builder->makeSetLocal( + myIndex, + child + ); + } + } + + // flattening fallthroughs makes them have type none. this gets their true type + WasmType getFallthroughType(Expression* child) { + auto iter = breakExprIndexes.find(child); + if (iter != breakExprIndexes.end()) { + // it was flattened and saved to a local + return getFunction()->getLocalType(iter->second); + } + assert(child->type != none); + return child->type; + } + + // Splitter helper + struct Splitter { + Splitter(FlattenControlFlow& parent, Expression* node) : parent(parent), node(node) {} + + ~Splitter() { + finish(); + } + + FlattenControlFlow& parent; + Expression* node; + + std::vector<Expression**> children; // TODO: reuse in parent, avoiding mallocing on each node + + void note(Expression*& child) { + // we accept nullptr inputs, for a non-existing child + if (!child) return; + children.push_back(&child); + } + + Expression* replacement; // the final replacement for the current node + bool stop = false; // if a child is unreachable, we can stop + + void finish() { + if (children.empty()) return; + // first, scan the list + bool hasControlFlowChild = false; + bool hasUnreachableChild = false; + for (auto** childp : children) { + // it's enough to look at the child, ignoring the contents, as the contents + // have already been processed before we got here, so they must have been + // flattened if necessary. + auto* child = *childp; + if (ControlFlowChecker::is(child)) { + hasControlFlowChild = true; + } + if (child->type == unreachable) { + hasUnreachableChild = true; + } + } + if (!hasControlFlowChild) { + // nothing to do here. + assert(!hasUnreachableChild); // all of them should be executed + return; + } + // we have at least one child we need to split out, so to preserve the order of operations, + // split them all out + Builder* builder = parent.builder.get(); + std::vector<Index> tempIndexes; + for (auto** childp : children) { + auto* child = *childp; + if (isConcreteWasmType(child->type)) { + tempIndexes.push_back(builder->addVar(parent.getFunction(), child->type)); + } else { + tempIndexes.push_back(-1); + } + } + // create a new replacement block + auto* block = builder->makeBlock(); + for (Index i = 0; i < children.size(); i++) { + auto* child = *children[i]; + auto type = child->type; + if (isConcreteWasmType(type)) { + // set the child to a local, and use it later + block->list.push_back(builder->makeSetLocal(tempIndexes[i], child)); + *children[i] = builder->makeGetLocal(tempIndexes[i], type); + } else if (type == none) { + // a nested none can not happen normally, here it occurs after we flattened a nested + // we can use the local it already assigned to. TODO: don't even allocate one here + block->list.push_back(child); + assert(parent.breakExprIndexes.count(child) > 0); + auto index = parent.breakExprIndexes[child]; + *children[i] = builder->makeGetLocal( + index, + parent.getFunction()->getLocalType(index) + ); + } else if (type == unreachable) { + block->list.push_back(child); + break; // no need to push any more + } else { + WASM_UNREACHABLE(); + } + } + if (!hasUnreachableChild) { + // we reached the end, so we need to emit the expression itself + // (which has been modified to replace children usages with get_locals) + block->list.push_back(node); + } + block->finalize(); + // finally, we just created a new block, ending in node. If node is e.g. + // i32.add, then our block would return a value. so we must convert + // this new block to return a value through a local + parent.visitBlock(block); + // the block is now done + parent.replaceCurrent(block); + // if the node was potentially a flowthrough value, then it has an entry + // in breakExprIndexes, and since we are replacing it with this block, + // we must note it's index as the same, so it is found by the parent. + if (parent.breakExprIndexes.find(node) != parent.breakExprIndexes.end()) { + parent.breakExprIndexes[block] = parent.breakExprIndexes[node]; + } + } + }; + + void visitBlock(Block* curr) { + if (isConcreteWasmType(curr->type)) { + curr->list.back() = getFallthroughReplacement(curr->list.back(), getBreakTargetIndex(curr->name, curr->type, curr)); + curr->finalize(); + } + } + void visitLoop(Loop* curr) { + if (isConcreteWasmType(curr->type)) { + curr->body = getFallthroughReplacement(curr->body, getBreakTargetIndex(Name(), curr->type, curr)); + curr->finalize(); + } + } + void visitIf(If* curr) { + if (isConcreteWasmType(curr->type)) { + auto targetIndex = getBreakTargetIndex(Name(), curr->type, curr); + curr->ifTrue = getFallthroughReplacement(curr->ifTrue, targetIndex); + curr->ifFalse = getFallthroughReplacement(curr->ifFalse, targetIndex); + curr->finalize(); + } + Splitter splitter(*this, curr); + splitter.note(curr->condition); + } + void visitBreak(Break* curr) { + Expression* processed = curr; + // first of all, get rid of the value if there is one + if (curr->value) { + if (curr->value->type != unreachable) { + auto type = getFallthroughType(curr->value); + auto index = getBreakTargetIndex(curr->name, type); + auto* value = getFallthroughReplacement(curr->value, index); + curr->value = nullptr; + curr->finalize(); + processed = builder->makeSequence( + value, + curr + ); + replaceCurrent(processed); + if (curr->condition) { + // we already called getBreakTargetIndex for the value we send to our + // break target if we break. as this is a br_if with a value, it also + // flows out that value, so our parent needs to know how to receive it. + // we note the already-existing index we prepared before, for that value. + getBreakTargetIndex(Name(), type, processed, index); + } + } else { + // we have a value, but it has unreachable type. we can just replace + // ourselves with it, we won't reach a condition (if there is one) or the br + // itself + replaceCurrent(curr->value); + return; + } + } + Splitter splitter(*this, processed); + splitter.note(curr->condition); + } + void visitSwitch(Switch* curr) { + Expression* processed = curr; + + // first of all, get rid of the value if there is one + if (curr->value) { + if (curr->value->type != unreachable) { + auto type = getFallthroughType(curr->value); + // we must assign the value to *all* the targets + auto temp = builder->addVar(getFunction(), type); + auto* value = getFallthroughReplacement(curr->value, temp); + curr->value = nullptr; + auto* block = builder->makeBlock(); + block->list.push_back(value); + std::set<Name> names; + for (auto target : curr->targets) { + if (names.insert(target).second) { + block->list.push_back( + builder->makeSetLocal( + getBreakTargetIndex(target, type), + builder->makeGetLocal(temp, type) + ) + ); + } + } + if (names.insert(curr->default_).second) { + block->list.push_back( + builder->makeSetLocal( + getBreakTargetIndex(curr->default_, type), + builder->makeGetLocal(temp, type) + ) + ); + } + block->list.push_back(curr); + block->finalize(); + replaceCurrent(block); + } else { + // we have a value, but it has unreachable type. we can just replace + // ourselves with it, we won't reach a condition (if there is one) or the br + // itself + replaceCurrent(curr->value); + return; + } + } + Splitter splitter(*this, processed); + splitter.note(curr->value); + splitter.note(curr->condition); + } + void visitCall(Call* curr) { + Splitter splitter(*this, curr); + for (auto*& operand : curr->operands) { + splitter.note(operand); + } + } + void visitCallImport(CallImport* curr) { + Splitter splitter(*this, curr); + for (auto*& operand : curr->operands) { + splitter.note(operand); + } + } + void visitCallIndirect(CallIndirect* curr) { + Splitter splitter(*this, curr); + for (auto*& operand : curr->operands) { + splitter.note(operand); + } + splitter.note(curr->target); + } + void visitSetLocal(SetLocal* curr) { + Splitter splitter(*this, curr); + splitter.note(curr->value); + } + void visitSetGlobal(SetGlobal* curr) { + Splitter splitter(*this, curr); + splitter.note(curr->value); + } + void visitLoad(Load* curr) { + Splitter splitter(*this, curr); + splitter.note(curr->ptr); + } + void visitStore(Store* curr) { + Splitter splitter(*this, curr); + splitter.note(curr->ptr); + splitter.note(curr->value); + } + void visitUnary(Unary* curr) { + Splitter splitter(*this, curr); + splitter.note(curr->value); + } + void visitBinary(Binary* curr) { + Splitter splitter(*this, curr); + splitter.note(curr->left); + splitter.note(curr->right); + } + void visitSelect(Select* curr) { + Splitter splitter(*this, curr); + splitter.note(curr->ifTrue); + splitter.note(curr->ifFalse); + splitter.note(curr->condition); + } + void visitDrop(Drop* curr) { + Splitter splitter(*this, curr); + splitter.note(curr->value); + } + void visitReturn(Return* curr) { + Splitter splitter(*this, curr); + splitter.note(curr->value); + } + void visitHost(Host* curr) { + Splitter splitter(*this, curr); + for (auto*& operand : curr->operands) { + splitter.note(operand); + } + } +}; + +Pass *createFlattenControlFlowPass() { + return new FlattenControlFlow(); +} + +} // namespace wasm + diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 51e76cdf7..50c8da46b 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -70,6 +70,7 @@ void PassRegistry::registerPasses() { registerPass("dce", "removes unreachable code", createDeadCodeEliminationPass); registerPass("duplicate-function-elimination", "removes duplicate functions", createDuplicateFunctionEliminationPass); registerPass("extract-function", "leaves just one function (useful for debugging)", createExtractFunctionPass); + registerPass("flatten-control-flow", "flattens out control flow to be only on blocks, not nested as expressions", createFlattenControlFlowPass); registerPass("inlining", "inlines functions (currently only ones with a single use)", createInliningPass); registerPass("legalize-js-interface", "legalizes i64 types on the import/export boundary", createLegalizeJSInterfacePass); registerPass("local-cse", "common subexpression elimination inside basic blocks", createLocalCSEPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index 709a903f1..302d82b9f 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -28,6 +28,7 @@ Pass *createCodePushingPass(); Pass *createDeadCodeEliminationPass(); Pass *createDuplicateFunctionEliminationPass(); Pass *createExtractFunctionPass(); +Pass *createFlattenControlFlowPass(); Pass *createFullPrinterPass(); Pass *createInliningPass(); Pass *createLegalizeJSInterfacePass(); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 61db3c9e8..04e0aa4de 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -76,6 +76,11 @@ public: } return ret; } + Block* makeBlock(Name name, Expression* first = nullptr) { + auto* ret = makeBlock(first); + ret->name = name; + return ret; + } If* makeIf(Expression* condition, Expression* ifTrue, Expression* ifFalse = nullptr) { auto* ret = allocator.alloc<If>(); ret->condition = condition; ret->ifTrue = ifTrue; ret->ifFalse = ifFalse; diff --git a/test/passes/flatten-control-flow.txt b/test/passes/flatten-control-flow.txt new file mode 100644 index 000000000..d94536712 --- /dev/null +++ b/test/passes/flatten-control-flow.txt @@ -0,0 +1,1177 @@ +(module + (type $ii (func (param i32 i32))) + (type $1 (func)) + (type $2 (func (result i32))) + (type $3 (func (param i32) (result i32))) + (type $4 (func (param i64 i64) (result i64))) + (global $x (mut i32) (i32.const 0)) + (table 1 1 anyfunc) + (elem (i32.const 0) $call-me) + (memory $0 10) + (func $call-me (type $ii) (param $0 i32) (param $1 i32) + (nop) + ) + (func $code-to-kill (type $1) + (local $x i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (local $6 i32) + (local $7 i32) + (local $8 i32) + (local $9 i32) + (local $10 i32) + (local $11 i32) + (local $12 i32) + (local $13 i32) + (local $14 i32) + (local $15 i32) + (local $16 i32) + (local $17 i32) + (local $18 i32) + (local $19 i32) + (local $20 i32) + (block $out + (br $out) + (drop + (i32.const 0) + ) + (if + (i32.const 1) + (drop + (i32.const 2) + ) + ) + (br_table $out $out $out $out + (i32.const 3) + ) + (call $code-to-kill) + ) + (if + (i32.const 0) + (block $out1 + (unreachable) + (drop + (i32.const 0) + ) + ) + ) + (if + (i32.const 0) + (block $out3 + (return) + (drop + (i32.const 0) + ) + ) + ) + (block $out4 + (br_table $out4 $out4 $out4 $out4 + (i32.const 4) + ) + (drop + (i32.const 0) + ) + ) + (block $out5 + (br_if $out5 + (i32.const 3) + ) + (drop + (i32.const 0) + ) + ) + (if + (i32.const 0) + (block $block4 + (if + (i32.const 0) + (block $out8 + (unreachable) + (drop + (i32.const 0) + ) + ) + (block $out9 + (unreachable) + (drop + (i32.const 0) + ) + ) + ) + (drop + (i32.const 0) + ) + ) + ) + (if + (i32.const 0) + (block $out11 + (unreachable) + (drop + (i32.const 0) + ) + (unreachable) + ) + ) + (if + (i32.const 0) + (block $out13 + (unreachable) + (drop + (i32.const 0) + ) + (unreachable) + ) + ) + (if + (i32.const 0) + (block $out15 + (unreachable) + (drop + (i32.const 0) + ) + (unreachable) + ) + ) + (block $out16 + (block $in + (br_if $out16 + (i32.const 1) + ) + ) + (unreachable) + ) + (if + (i32.const 0) + (block $block11 + (block $out18 + (block $in19 + (br_if $in19 + (i32.const 1) + ) + ) + (unreachable) + ) + (drop + (i32.const 10) + ) + ) + ) + (block $out20 + (block $in21 + (br_table $out20 $in21 + (i32.const 1) + ) + ) + (unreachable) + ) + (block $out22 + (block $in23 + (br_table $in23 $out22 + (i32.const 1) + ) + ) + (unreachable) + ) + (if + (i32.const 0) + (block $block13 + (block $out25 + (block $in26 + (br_table $in26 $in26 + (i32.const 1) + ) + ) + (unreachable) + ) + (drop + (i32.const 10) + ) + ) + ) + (if + (i32.const 0) + (block $block15 + (drop + (i32.const 10) + ) + (drop + (i32.const 42) + ) + (unreachable) + (block + (unreachable) + ) + (unreachable) + (return) + ) + ) + (if + (i32.const 0) + (loop $loop-in18 + (unreachable) + ) + ) + (block $out29 + (loop $in30 + (br_if $out29 + (i32.const 1) + ) + (unreachable) + ) + ) + (if + (i32.const 0) + (block $block20 + (loop $in32 + (br_if $in32 + (i32.const 1) + ) + (unreachable) + ) + (drop + (i32.const 10) + ) + ) + ) + (if + (i32.const 1) + (block + (set_local $4 + (i32.const 123) + ) + (unreachable) + ) + ) + (if + (i32.const 2) + (unreachable) + ) + (if + (i32.const 3) + (unreachable) + ) + (if + (i32.const -1) + (block + (set_local $6 + (i32.const 123) + ) + (set_local $7 + (i32.const 456) + ) + (unreachable) + ) + ) + (if + (i32.const -2) + (block + (set_local $8 + (i32.const 139) + ) + (unreachable) + ) + ) + (if + (i32.const -3) + (block + (set_local $10 + (i32.const 246) + ) + (unreachable) + ) + ) + (if + (i32.const -4) + (unreachable) + ) + (if + (i32.const 11) + (unreachable) + ) + (if + (i32.const 22) + (block + (unreachable) + ) + ) + (if + (i32.const 33) + (block + (set_local $11 + (i32.const 0) + ) + (unreachable) + ) + ) + (if + (i32.const 44) + (unreachable) + ) + (if + (i32.const 55) + (unreachable) + ) + (if + (i32.const 66) + (block + (unreachable) + ) + ) + (if + (i32.const 77) + (block + (unreachable) + ) + ) + (if + (i32.const 88) + (block + (set_local $14 + (i32.const 0) + ) + (unreachable) + ) + ) + (if + (i32.const 99) + (unreachable) + ) + (if + (i32.const 100) + (block + (set_local $15 + (i32.const 123) + ) + (set_local $16 + (i32.const 456) + ) + (unreachable) + ) + ) + (if + (i32.const 101) + (block + (set_local $17 + (i32.const 123) + ) + (unreachable) + ) + ) + (if + (i32.const 102) + (block + (unreachable) + ) + ) + (drop + (i32.const 1337) + ) + ) + (func $killer (type $1) + (unreachable) + (drop + (i32.const 1000) + ) + ) + (func $target (type $1) + (drop + (i32.const 2000) + ) + ) + (func $typed-block-none-then-unreachable (type $2) (result i32) + (local $0 i32) + (block $top-typed + (block $switch$0 + (return + (i32.const 0) + ) + (br $switch$0) + ) + (return + (i32.const 1) + ) + ) + (return + (get_local $0) + ) + ) + (func $typed-block-remove-br-changes-type (type $3) (param $$$0 i32) (result i32) + (local $1 i32) + (block + (block $switch$7 + (block $switch-default$10 + (block $switch-case$9 + (block $switch-case$8 + (br_table $switch-case$9 $switch-case$8 $switch-default$10 + (i32.const -1) + ) + ) + ) + (return + (get_local $$$0) + ) + (br $switch$7) + ) + (return + (get_local $$$0) + ) + ) + (return + (i32.const 0) + ) + ) + (return + (get_local $1) + ) + ) + (func $global (type $1) + (unreachable) + (drop + (get_global $x) + ) + (set_global $x + (i32.const 1) + ) + ) + (func $ret (type $2) (result i32) + (local $0 i32) + (block + (return + (i32.const 0) + ) + (nop) + (set_local $0 + (i32.const 0) + ) + ) + (return + (get_local $0) + ) + ) + (func $unreachable-br (type $2) (result i32) + (local $0 i32) + (block $out + (block + (set_local $0 + (i32.const 0) + ) + (br $out) + ) + ) + (return + (get_local $0) + ) + ) + (func $unreachable-br-loop (type $2) (result i32) + (loop $out + (br $out) + ) + ) + (func $unreachable-block-ends-switch (type $2) (result i32) + (local $0 i32) + (block $label$0 + (block $label$3 + (nop) + (block + (unreachable) + ) + (unreachable) + ) + (set_local $0 + (i32.const 19) + ) + ) + (return + (get_local $0) + ) + ) + (func $unreachable-block-ends-br_if (type $2) (result i32) + (local $0 i32) + (block $label$0 + (block $label$2 + (nop) + (block + (unreachable) + ) + (unreachable) + ) + (set_local $0 + (i32.const 19) + ) + ) + (return + (get_local $0) + ) + ) + (func $unreachable-brs-3 (type $2) (result i32) + (local $0 i32) + (block $label$0 + (block + (block + (set_local $0 + (i32.const 18) + ) + (br $label$0) + ) + ) + (set_local $0 + (i32.const 21) + ) + ) + (return + (get_local $0) + ) + ) + (func $unreachable-brs-4 (type $3) (param $var$0 i32) (result i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (set_local $3 + (i32.const 1) + ) + (block $label$0 + (block $label$1 + (block + (block + (unreachable) + ) + ) + (set_local $2 + (i32.const 4) + ) + ) + (set_local $1 + (i32.const 16) + ) + ) + ) + (func $call-unreach (type $4) (param $var$0 i64) (param $var$1 i64) (result i64) + (local $2 i64) + (local $3 i64) + (local $4 i64) + (local $5 i64) + (local $6 i64) + (local $7 i64) + (if + (i64.eqz + (get_local $var$0) + ) + (block + (block $label$0 + (set_local $3 + (get_local $var$1) + ) + ) + (set_local $7 + (get_local $3) + ) + ) + (block + (block $label$1 + (block + (set_local $5 + (i64.sub + (get_local $var$0) + (i64.const 1) + ) + ) + (block + (block $block + (set_local $2 + (get_local $var$0) + ) + (nop) + (set_local $4 + (get_local $2) + ) + ) + (unreachable) + ) + ) + ) + (set_local $7 + (get_local $6) + ) + ) + ) + (return + (get_local $7) + ) + ) + (func $test-flatten (type $1) + (local $0 i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (local $6 i32) + (local $7 i32) + (local $8 i32) + (local $9 i32) + (local $10 i32) + (local $11 i32) + (local $12 i32) + (local $13 i32) + (local $14 i32) + (local $15 i32) + (local $16 i32) + (local $17 i32) + (local $18 i32) + (local $19 i32) + (local $20 i32) + (local $21 i32) + (local $22 i32) + (local $23 i32) + (local $24 i32) + (local $25 i32) + (local $26 i32) + (local $27 i32) + (local $28 i32) + (local $29 i32) + (local $30 i32) + (local $31 i32) + (local $32 i32) + (local $33 i32) + (local $34 i32) + (local $35 i32) + (local $36 i32) + (local $37 i32) + (local $38 i32) + (local $39 i32) + (local $40 i32) + (local $41 i32) + (local $42 i32) + (local $43 i32) + (local $44 i32) + (block $out + (drop + (i32.add + (i32.const 1) + (i32.const 2) + ) + ) + (block + (block + (set_local $0 + (i32.const 1) + ) + (br $out) + ) + ) + (block + (block + (br $out) + ) + ) + (block + (block + (set_local $2 + (i32.const 1) + ) + (br_table $out $out $out $out + (i32.const 3) + ) + ) + ) + (block + (block + (set_local $4 + (i32.const 1) + ) + (block $block + (drop + (i32.const 2) + ) + (drop + (i32.const 3) + ) + (set_local $3 + (i32.const 4) + ) + ) + (set_local $5 + (i32.add + (get_local $4) + (get_local $3) + ) + ) + ) + (drop + (get_local $5) + ) + ) + (block + (block + (set_local $9 + (i32.const 1) + ) + (block $in + (block + (block $switch-in + (block + (set_local $6 + (i32.const 2) + ) + (set_local $7 + (get_local $6) + ) + (set_local $8 + (get_local $6) + ) + (br_table $in $switch-in $in + (i32.const 777) + ) + ) + ) + (drop + (get_local $8) + ) + ) + (block + (set_local $7 + (i32.const 3) + ) + (br $in) + ) + (set_local $7 + (i32.const 4) + ) + ) + (set_local $10 + (i32.add + (get_local $9) + (get_local $7) + ) + ) + ) + (drop + (get_local $10) + ) + ) + (block + (block + (set_local $12 + (i32.const 1) + ) + (loop $loop-in + (set_local $11 + (i32.const 5) + ) + ) + (set_local $13 + (i32.add + (get_local $12) + (get_local $11) + ) + ) + ) + (drop + (get_local $13) + ) + ) + (block + (block + (set_local $15 + (i32.const 1) + ) + (if + (i32.const 6) + (set_local $14 + (i32.const 7) + ) + (set_local $14 + (i32.const 8) + ) + ) + (set_local $16 + (i32.add + (get_local $15) + (get_local $14) + ) + ) + ) + (drop + (get_local $16) + ) + ) + (drop + (select + (i32.const 9) + (i32.const 10) + (i32.const 11) + ) + ) + (block + (block + (br $out) + ) + ) + (block + (block + (set_local $19 + (i32.const 9) + ) + (br $out) + ) + ) + (block + (block + (set_local $21 + (i32.const 9) + ) + (set_local $22 + (i32.const 10) + ) + (br $out) + ) + ) + (block + (block + (if + (i32.const 11) + (set_local $23 + (i32.const 12) + ) + (set_local $23 + (i32.const 13) + ) + ) + (set_local $24 + (i32.const 9) + ) + (set_local $25 + (i32.const 10) + ) + (set_local $26 + (select + (get_local $23) + (get_local $24) + (get_local $25) + ) + ) + ) + (drop + (get_local $26) + ) + ) + (block + (block + (set_local $28 + (i32.const 9) + ) + (if + (i32.const 11) + (set_local $27 + (i32.const 12) + ) + (set_local $27 + (i32.const 13) + ) + ) + (set_local $29 + (i32.const 10) + ) + (set_local $30 + (select + (get_local $28) + (get_local $27) + (get_local $29) + ) + ) + ) + (drop + (get_local $30) + ) + ) + (block + (block + (set_local $32 + (i32.const 9) + ) + (set_local $33 + (i32.const 10) + ) + (if + (i32.const 11) + (set_local $31 + (i32.const 12) + ) + (set_local $31 + (i32.const 13) + ) + ) + (set_local $34 + (select + (get_local $32) + (get_local $33) + (get_local $31) + ) + ) + ) + (drop + (get_local $34) + ) + ) + (block + (block + (if + (i32.const 11) + (set_local $35 + (i32.const 12) + ) + (set_local $35 + (i32.const 13) + ) + ) + (set_local $37 + (i32.const 14) + ) + (if + (i32.const 15) + (set_local $36 + (i32.const 16) + ) + (set_local $36 + (i32.const 17) + ) + ) + (set_local $38 + (select + (get_local $35) + (get_local $37) + (get_local $36) + ) + ) + ) + (drop + (get_local $38) + ) + ) + (block + (block + (set_local $39 + (i32.const 1) + ) + (return) + ) + ) + (block + (block + (set_local $40 + (i32.const 1) + ) + (unreachable) + ) + ) + (block + (block + (if + (i32.const 5) + (set_local $41 + (i32.const 6) + ) + (set_local $41 + (i32.const 7) + ) + ) + (if + (get_local $41) + (set_local $43 + (i32.const 8) + ) + (block + (if + (i32.const 9) + (set_local $42 + (i32.const 10) + ) + (set_local $42 + (i32.const 11) + ) + ) + (set_local $43 + (get_local $42) + ) + ) + ) + ) + (drop + (get_local $43) + ) + ) + (block + (block $temp + (block + (block + (set_local $44 + (i32.const 1) + ) + (br_if $temp + (i32.const 2) + ) + ) + (set_local $44 + (get_local $44) + ) + ) + ) + (drop + (get_local $44) + ) + ) + ) + ) + (func $flatten-return-value (type $2) (result i32) + (local $0 i32) + (local $1 i32) + (block + (block + (block + (set_local $0 + (i32.const 1) + ) + (return + (i32.const 2) + ) + ) + ) + (set_local $1 + (i32.const 3) + ) + ) + (return + (get_local $1) + ) + ) + (func $unbug (type $1) + (local $12 i32) + (local $432 i32) + (local $430 i32) + (local $431 i32) + (local $9 i32) + (local $5 i32) + (local $433 i32) + (local $7 i32) + (block $block + (if + (i32.eq + (get_local $12) + (i32.const 65535) + ) + (block $block39 + (block $label$78 + (set_local $430 + (i32.const 0) + ) + ) + (set_local $432 + (get_local $430) + ) + ) + (block $block40 + (block $label$79 + (set_local $431 + (i32.lt_u + (get_local $9) + (i32.load16_u offset=2 + (i32.add + (get_local $5) + (i32.mul + (get_local $12) + (i32.const 12) + ) + ) + ) + ) + ) + ) + (set_local $432 + (get_local $431) + ) + ) + ) + (set_local $433 + (i32.const 1) + ) + (set_local $7 + (i32.xor + (get_local $432) + (get_local $433) + ) + ) + ) + (drop + (get_local $7) + ) + ) + (func $outer-block-typed (type $3) (param $var$0 i32) (result i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (block $block + (block + (block + (set_local $2 + (i32.const 1) + ) + (block $label$0 + (set_local $1 + (i32.const 16) + ) + ) + (set_local $3 + (i32.add + (get_local $2) + (get_local $1) + ) + ) + ) + (set_local $4 + (get_local $3) + ) + ) + ) + (return + (get_local $4) + ) + ) + (func $nested-br_if-with-value (type $2) (result i32) + (local $0 i32) + (local $1 i32) + (local $2 i32) + (block $label$0 + (block + (block + (block $block + (set_local $1 + (get_local $0) + ) + ) + (block + (set_local $2 + (i32.const 0) + ) + (br_if $label$0 + (get_local $1) + ) + ) + ) + (drop + (get_local $2) + ) + ) + (set_local $2 + (i32.const 1) + ) + ) + (return + (get_local $2) + ) + ) +) diff --git a/test/passes/flatten-control-flow.wast b/test/passes/flatten-control-flow.wast new file mode 100644 index 000000000..30965c1bf --- /dev/null +++ b/test/passes/flatten-control-flow.wast @@ -0,0 +1,776 @@ +(module + (type $ii (func (param i32 i32))) + (type $1 (func)) + (type $2 (func (result i32))) + (type $3 (func (param i32) (result i32))) + (type $4 (func (param i64 i64) (result i64))) + (global $x (mut i32) (i32.const 0)) + (table 1 1 anyfunc) + (elem (i32.const 0) $call-me) + (memory $0 10) + (func $call-me (param $0 i32) (param $1 i32) + (nop) + ) + (func $code-to-kill + (local $x i32) + (block $out + (br $out) + (drop + (i32.const 0) + ) + (if + (i32.const 1) + (drop + (i32.const 2) + ) + ) + (br_table $out $out $out $out + (i32.const 3) + ) + (call $code-to-kill) + ) + (if + (i32.const 0) + (block $out1 + (unreachable) + (drop + (i32.const 0) + ) + ) + ) + (if + (i32.const 0) + (block $out3 + (return) + (drop + (i32.const 0) + ) + ) + ) + (block $out4 + (br_table $out4 $out4 $out4 $out4 + (i32.const 4) + ) + (drop + (i32.const 0) + ) + ) + (block $out5 + (br_if $out5 + (i32.const 3) + ) + (drop + (i32.const 0) + ) + ) + (if + (i32.const 0) + (block $block4 + (if + (i32.const 0) + (block $out8 + (unreachable) + (drop + (i32.const 0) + ) + ) + (block $out9 + (unreachable) + (drop + (i32.const 0) + ) + ) + ) + (drop + (i32.const 0) + ) + ) + ) + (if + (i32.const 0) + (drop + (block $out11 i32 + (br $out11 + (unreachable) + ) + (drop + (i32.const 0) + ) + (unreachable) + ) + ) + ) + (if + (i32.const 0) + (drop + (block $out13 i32 + (br_if $out13 + (unreachable) + (i32.const 0) + ) + (drop + (i32.const 0) + ) + (unreachable) + ) + ) + ) + (if + (i32.const 0) + (drop + (block $out15 i32 + (br_if $out15 + (unreachable) + (unreachable) + ) + (drop + (i32.const 0) + ) + (unreachable) + ) + ) + ) + (block $out16 + (block $in + (br_if $out16 + (i32.const 1) + ) + ) + (unreachable) + ) + (if + (i32.const 0) + (block $block11 + (block $out18 + (block $in19 + (br_if $in19 + (i32.const 1) + ) + ) + (unreachable) + ) + (drop + (i32.const 10) + ) + ) + ) + (block $out20 + (block $in21 + (br_table $out20 $in21 + (i32.const 1) + ) + ) + (unreachable) + ) + (block $out22 + (block $in23 + (br_table $in23 $out22 + (i32.const 1) + ) + ) + (unreachable) + ) + (if + (i32.const 0) + (block $block13 + (block $out25 + (block $in26 + (br_table $in26 $in26 + (i32.const 1) + ) + ) + (unreachable) + ) + (drop + (i32.const 10) + ) + ) + ) + (if + (i32.const 0) + (block $block15 + (drop + (i32.const 10) + ) + (drop + (i32.const 42) + ) + (unreachable) + (return + (unreachable) + ) + (unreachable) + (return) + ) + ) + (if + (i32.const 0) + (loop $loop-in18 + (unreachable) + ) + ) + (block $out29 + (loop $in30 + (br_if $out29 + (i32.const 1) + ) + (unreachable) + ) + ) + (if + (i32.const 0) + (block $block20 + (loop $in32 + (br_if $in32 + (i32.const 1) + ) + (unreachable) + ) + (drop + (i32.const 10) + ) + ) + ) + (if + (i32.const 1) + (call $call-me + (i32.const 123) + (unreachable) + ) + ) + (if + (i32.const 2) + (call $call-me + (unreachable) + (i32.const 0) + ) + ) + (if + (i32.const 3) + (call $call-me + (unreachable) + (unreachable) + ) + ) + (if + (i32.const -1) + (call_indirect $ii + (i32.const 123) + (i32.const 456) + (unreachable) + ) + ) + (if + (i32.const -2) + (call_indirect $ii + (i32.const 139) + (unreachable) + (i32.const 0) + ) + ) + (if + (i32.const -3) + (call_indirect $ii + (i32.const 246) + (unreachable) + (unreachable) + ) + ) + (if + (i32.const -4) + (call_indirect $ii + (unreachable) + (unreachable) + (unreachable) + ) + ) + (if + (i32.const 11) + (set_local $x + (unreachable) + ) + ) + (if + (i32.const 22) + (drop + (i32.load + (unreachable) + ) + ) + ) + (if + (i32.const 33) + (i32.store + (i32.const 0) + (unreachable) + ) + ) + (if + (i32.const 44) + (i32.store + (unreachable) + (i32.const 0) + ) + ) + (if + (i32.const 55) + (i32.store + (unreachable) + (unreachable) + ) + ) + (if + (i32.const 66) + (drop + (i32.eqz + (unreachable) + ) + ) + ) + (if + (i32.const 77) + (drop + (i32.add + (unreachable) + (i32.const 0) + ) + ) + ) + (if + (i32.const 88) + (drop + (i32.add + (i32.const 0) + (unreachable) + ) + ) + ) + (if + (i32.const 99) + (i32.add + (unreachable) + (unreachable) + ) + ) + (if + (i32.const 100) + (drop + (select + (i32.const 123) + (i32.const 456) + (unreachable) + ) + ) + ) + (if + (i32.const 101) + (drop + (select + (i32.const 123) + (unreachable) + (i32.const 456) + ) + ) + ) + (if + (i32.const 102) + (drop + (select + (unreachable) + (i32.const 123) + (i32.const 456) + ) + ) + ) + (drop + (i32.const 1337) + ) + ) + (func $killer (type $1) + (unreachable) + (drop + (i32.const 1000) + ) + ) + (func $target (type $1) + (drop + (i32.const 2000) + ) + ) + (func $typed-block-none-then-unreachable (type $2) (result i32) + (block $top-typed i32 + (block $switch$0 + (return + (i32.const 0) + ) + (br $switch$0) + ) + (return + (i32.const 1) + ) + ) + ) + (func $typed-block-remove-br-changes-type (type $3) (param $$$0 i32) (result i32) + (block $switch$7 + (block $switch-default$10 + (block $switch-case$9 + (block $switch-case$8 + (br_table $switch-case$9 $switch-case$8 $switch-default$10 + (i32.const -1) + ) + ) + ) + (return + (get_local $$$0) + ) + (br $switch$7) + ) + (return + (get_local $$$0) + ) + ) + (return + (i32.const 0) + ) + ) + (func $global (type $1) + (unreachable) + (drop + (get_global $x) + ) + (set_global $x + (i32.const 1) + ) + ) + (func $ret (type $2) (result i32) + (return + (i32.const 0) + ) + (nop) + (i32.const 0) + ) + (func $unreachable-br (type $2) (result i32) + (block $out i32 + (br $out + (br $out + (i32.const 0) + ) + ) + ) + ) + (func $unreachable-br-loop (type $2) (result i32) + (loop $out + (br $out) + ) + ) + (func $unreachable-block-ends-switch (type $2) (result i32) + (block $label$0 i32 + (block $label$3 + (nop) + (br_table $label$3 + (unreachable) + ) + (unreachable) + ) + (i32.const 19) + ) + ) + (func $unreachable-block-ends-br_if (result i32) + (block $label$0 i32 + (block $label$2 + (nop) + (br_if $label$2 + (unreachable) + ) + (unreachable) + ) + (i32.const 19) + ) + ) + (func $unreachable-brs-3 (result i32) + (block $label$0 i32 + (br $label$0 + (grow_memory + (br $label$0 + (i32.const 18) + ) + ) + ) + (i32.const 21) + ) + ) + (func $unreachable-brs-4 (param $var$0 i32) (result i32) + (i32.add + (i32.const 1) + (block $label$0 i32 + (br $label$0 + (block $label$1 i32 + (drop + (br_if $label$0 + (i32.const 4104) + (unreachable) + ) + ) + (i32.const 4) + ) + ) + (i32.const 16) + ) + ) + ) + (func $call-unreach (param $var$0 i64) (param $var$1 i64) (result i64) + (local $2 i64) + (if i64 + (i64.eqz + (get_local $var$0) + ) + (block $label$0 i64 + (get_local $var$1) + ) + (block $label$1 i64 + (call $call-unreach + (i64.sub + (get_local $var$0) + (i64.const 1) + ) + (i64.mul + (block $block i64 + (set_local $2 + (get_local $var$0) + ) + (nop) + (get_local $2) + ) + (unreachable) + ) + ) + ) + ) + ) + + ;; flatten-specific + (func $test-flatten + (block $out + (drop (i32.add (i32.const 1) (i32.const 2))) + (drop (i32.add (i32.const 1) (br $out))) + (drop (i32.add (br $out) (i32.const 1))) + (drop (i32.add (i32.const 1) (br_table $out $out $out $out (i32.const 3)))) + (drop (i32.add (i32.const 1) + (block i32 + (drop (i32.const 2)) + (drop (i32.const 3)) + (i32.const 4) + ) + )) + (drop (i32.add (i32.const 1) + (block $in i32 + (drop + (block $switch-in i32 + (br_table $in $switch-in $in (i32.const 2) (i32.const 777)) + ) + ) + (br $in (i32.const 3)) + (i32.const 4) + ) + )) + (drop (i32.add (i32.const 1) + (loop i32 + (i32.const 5) + ) + )) + (drop (i32.add (i32.const 1) + (if i32 + (i32.const 6) + (i32.const 7) + (i32.const 8) + ) + )) + (drop + (select + (i32.const 9) + (i32.const 10) + (i32.const 11) + ) + ) + (drop + (select + (br $out) + (i32.const 10) + (i32.const 11) + ) + ) + (drop + (select + (i32.const 9) + (br $out) + (i32.const 11) + ) + ) + (drop + (select + (i32.const 9) + (i32.const 10) + (br $out) + ) + ) + (drop + (select + (if i32 + (i32.const 11) + (i32.const 12) + (i32.const 13) + ) + (i32.const 9) + (i32.const 10) + ) + ) + (drop + (select + (i32.const 9) + (if i32 + (i32.const 11) + (i32.const 12) + (i32.const 13) + ) + (i32.const 10) + ) + ) + (drop + (select + (i32.const 9) + (i32.const 10) + (if i32 + (i32.const 11) + (i32.const 12) + (i32.const 13) + ) + ) + ) + (drop + (select + (if i32 + (i32.const 11) + (i32.const 12) + (i32.const 13) + ) + (i32.const 14) + (if i32 + (i32.const 15) + (i32.const 16) + (i32.const 17) + ) + ) + ) + (drop (i32.add (i32.const 1) (return))) + (drop (i32.add (i32.const 1) (unreachable))) + (drop + (if i32 + (if i32 + (i32.const 5) + (i32.const 6) + (i32.const 7) + ) + (i32.const 8) + (if i32 + (i32.const 9) + (i32.const 10) + (i32.const 11) + ) + ) + ) + (drop + (block $temp i32 + (br_if $temp + (i32.const 1) + (i32.const 2) + ) + ) + ) + ) + ) + (func $flatten-return-value (result i32) + (drop (i32.add (i32.const 1) (return (i32.const 2)))) + (i32.const 3) + ) + (func $unbug + (local $12 i32) + (local $432 i32) + (local $430 i32) + (local $431 i32) + (local $9 i32) + (local $5 i32) + (local $433 i32) + (drop + (block i32 + (if + (i32.eq + (get_local $12) + (i32.const 65535) + ) + (block + (block $label$78 + (set_local $430 + (i32.const 0) + ) + ) + (set_local $432 + (get_local $430) + ) + ) + (block + (block $label$79 + (set_local $431 + (i32.lt_u + (get_local $9) + (i32.load16_u offset=2 + (i32.add + (get_local $5) + (i32.mul + (get_local $12) + (i32.const 12) + ) + ) + ) + ) + ) + ) + (set_local $432 + (get_local $431) + ) + ) + ) + (set_local $433 + (i32.const 1) + ) + (i32.xor + (get_local $432) + (get_local $433) + ) + ) + ) + ) + (func $outer-block-typed (type $3) (param $var$0 i32) (result i32) + (block i32 + (i32.add + (i32.const 1) + (block $label$0 i32 + (i32.const 16) + ) + ) + ) + ) + (func $nested-br_if-with-value (result i32) + (local $0 i32) + (block $label$0 i32 + (drop + (br_if $label$0 + (i32.const 0) + (block i32 + (get_local $0) + ) + ) + ) + (i32.const 1) + ) + ) +) |