summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/CMakeLists.txt1
-rw-r--r--src/passes/FlattenControlFlow.cpp471
-rw-r--r--src/passes/pass.cpp1
-rw-r--r--src/passes/passes.h1
-rw-r--r--src/wasm-builder.h5
-rw-r--r--test/passes/flatten-control-flow.txt1177
-rw-r--r--test/passes/flatten-control-flow.wast776
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)
+ )
+ )
+)