Ignore unreachable code in wasm binaries (#1122)

Ignoring unreachable code in wasm binaries lets us avoid corner cases with unstructured code in wasm binaries that is a poor fit for Binaryen's structured IR.
diff --git a/README.md b/README.md
index c8c22e3..c67dc01 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,10 @@
 
  * Binaryen IR [is an AST](https://github.com/WebAssembly/binaryen/issues/663), for convenience of optimization. This differs from the WebAssembly binary format which is a stack machine.
  * WebAssembly limits block/if/loop types to none and the concrete value types (i32, i64, f32, f64). Binaryen IR has an unreachable type, and it allows block/if/loop to take it, allowing [local transforms that don't need to know the global context](https://github.com/WebAssembly/binaryen/issues/903).
- * Binaryen IR's text format requires the names of blocks and loops to be unique. This differs from the WebAssembly s-expression format which allows duplicate names (and depends on scoping to disambiguate).
+ * Binaryen IR requires the names of blocks and loops to be unique. (Reading wast files with duplicate names is supported, by disambiguating them).
+ * Binaryen IR has only one node with a list: blocks. WebAssembly on the other hand allows lists in loops and ifs (Binaryen would represent those with additional blocks as necessary). The motivation here is that many passes need special code for iterating on lists, so having a single IR node with a list simplifies things.
+ * Binaryen's text format allows only s-expressions. WebAssembly's official text format is a stack machine with s-expression extensions. Binaryen can't read stack machine code, but it can read a wast if it contains only s-expressions.
+ * Binaryen ignores unreachable code when reading WebAssembly binaries. That means that if you read a wasm file with unreachable code, that code will be discarded as if it were optimized out (often this is what you want anyhow, and optimized programs have no unreachable code anyway, but if you write an unoptimized file and then read it, it may look different). The reason for this behavior is that unreachable code in WebAssembly has corner cases that are tricky to handle in Binaryen IR (it can be very unstructured, and Binaryen IR is more structured than WebAssembly as noted earlier). Note that Binaryen does support unreachable code in wast text files, since as we saw Binaryen only supports s-expressions there, which are structured.
 
 As a result, you might notice that round-trip conversions (wasm => Binaryen IR => wasm) change code a little in some corner cases.
 
diff --git a/src/wasm-binary.h b/src/wasm-binary.h
index 55a9774..9724bc9 100644
--- a/src/wasm-binary.h
+++ b/src/wasm-binary.h
@@ -855,9 +855,15 @@
 
   std::vector<Expression*> expressionStack;
 
+  bool definitelyUnreachable; // set when we know code is definitely unreachable. this helps parse
+                              // stacky wasm code, which can be unsuitable for our IR when unreachable
+
   BinaryConsts::ASTNodes lastSeparator = BinaryConsts::End;
 
+  // process a block-type scope, until an end or else marker, or the end of the function
   void processExpressions();
+  void skipUnreachableCode();
+
   Expression* popExpression();
   Expression* popNonVoidExpression();
 
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index eb41ae8..b114b81 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -1807,12 +1807,61 @@
   }
 }
 
-void WasmBinaryBuilder::processExpressions() { // until an end or else marker, or the end of the function
+void WasmBinaryBuilder::processExpressions() {
+  if (debug) std::cerr << "== processExpressions" << std::endl;
+  definitelyUnreachable = false;
   while (1) {
     Expression* curr;
     auto ret = readExpression(curr);
     if (!curr) {
       lastSeparator = ret;
+      if (debug) std::cerr << "== processExpressions finished" << std::endl;
+      return;
+    }
+    expressionStack.push_back(curr);
+    if (curr->type == unreachable) {
+      // once we see something unreachable, we don't want to add anything else
+      // to the stack, as it could be stacky code that is non-representable in
+      // our AST. but we do need to skip it
+      // if there is nothing else here, just stop. otherwise, go into unreachable
+      // mode. peek to see what to do
+      if (pos == endOfFunction) {
+        throw ParseException("Reached function end without seeing End opcode");
+      }
+      auto peek = input[pos];
+      if (peek == BinaryConsts::End || peek == BinaryConsts::Else) {
+        if (debug) std::cerr << "== processExpressions finished with unreachable" << std::endl;
+        lastSeparator = BinaryConsts::ASTNodes(peek);
+        pos++;
+        return;
+      } else {
+        skipUnreachableCode();
+        return;
+      }
+    }
+  }
+}
+
+void WasmBinaryBuilder::skipUnreachableCode() {
+  if (debug) std::cerr << "== skipUnreachableCode" << std::endl;
+  // preserve the stack, and restore it. it contains the instruction that made us
+  // unreachable, and we can ignore anything after it. things after it may pop,
+  // we want to undo that
+  auto savedStack = expressionStack;
+  // clear the stack. nothing should be popped from there anyhow, just stuff
+  // can be pushed and then popped. Popping past the top of the stack will
+  // result in uneachables being returned
+  expressionStack.clear();
+  while (1) {
+    // set the definitelyUnreachable flag each time, as sub-blocks may set and unset it
+    definitelyUnreachable = true;
+    Expression* curr;
+    auto ret = readExpression(curr);
+    if (!curr) {
+      if (debug) std::cerr << "== skipUnreachableCode finished" << std::endl;
+      lastSeparator = ret;
+      definitelyUnreachable = false;
+      expressionStack = savedStack;
       return;
     }
     expressionStack.push_back(curr);
@@ -1820,15 +1869,19 @@
 }
 
 Expression* WasmBinaryBuilder::popExpression() {
+  if (debug) std::cerr << "== popExpression" << std::endl;
   if (expressionStack.empty()) {
-    throw ParseException("attempted pop from empty stack at " + std::to_string(pos));
+    if (definitelyUnreachable) {
+      // in unreachable code, trying to pop past the polymorphic stack
+      // area results in receiving unreachables
+      if (debug) std::cerr << "== popping unreachable from polymorphic stack" << std::endl;
+      return allocator.alloc<Unreachable>();
+    }
+    throw ParseException("attempted pop from empty stack / beyond block start boundary at " + std::to_string(pos));
   }
+  // the stack is not empty, and we would not be going out of the current block
   auto ret = expressionStack.back();
-  // to simulate the wasm polymorphic stack mode, leave a final
-  // unreachable, don't empty the stack in that case
-  if (!(expressionStack.size() == 1 && ret->type == unreachable)) {
-    expressionStack.pop_back();
-  }
+  expressionStack.pop_back();
   return ret;
 }
 
@@ -2222,7 +2275,6 @@
 void WasmBinaryBuilder::visitSwitch(Switch *curr) {
   if (debug) std::cerr << "zz node: Switch" << std::endl;
   curr->condition = popNonVoidExpression();
-
   auto numTargets = getU32LEB();
   if (debug) std::cerr << "targets: "<< numTargets<<std::endl;
   for (size_t i = 0; i < numTargets; i++) {
diff --git a/test/example/c-api-unused-mem.txt b/test/example/c-api-unused-mem.txt
index c6ac482..60bdbe5 100644
--- a/test/example/c-api-unused-mem.txt
+++ b/test/example/c-api-unused-mem.txt
@@ -67,9 +67,7 @@
     )
     (block $label$2
      (br $label$1)
-     (unreachable)
     )
-    (unreachable)
    )
    (block $label$3
     (block $label$4
@@ -77,9 +75,7 @@
      )
      (block $label$6
       (br $label$4)
-      (unreachable)
      )
-     (unreachable)
     )
     (block $label$7
      (block $label$8
@@ -88,18 +84,11 @@
        (get_local $var$0)
       )
       (return)
-      (unreachable)
      )
      (unreachable)
-     (unreachable)
     )
-    (unreachable)
-    (unreachable)
    )
-   (unreachable)
-   (unreachable)
   )
-  (unreachable)
  )
  (func $__wasm_start (type $1)
   (block $label$0
diff --git a/test/fib-dbg.wasm.fromBinary b/test/fib-dbg.wasm.fromBinary
index 3f940fa..8f7193f 100644
--- a/test/fib-dbg.wasm.fromBinary
+++ b/test/fib-dbg.wasm.fromBinary
@@ -72,9 +72,7 @@
    (return
     (get_local $var$1)
    )
-   (unreachable)
   )
-  (unreachable)
  )
  (func $stackSave (type $2) (result i32)
   (return
@@ -214,10 +212,7 @@
    (return
     (get_local $var$4)
    )
-   (unreachable)
-   (unreachable)
   )
-  (unreachable)
  )
  (func $runPostSets (type $4)
   (local $var$0 i32)
diff --git a/test/min.wast.fromBinary b/test/min.wast.fromBinary
index b39388c..f25c510 100644
--- a/test/min.wast.fromBinary
+++ b/test/min.wast.fromBinary
@@ -46,7 +46,6 @@
    (br $label$0
     (i32.const 2)
    )
-   (i32.const 0)
   )
  )
  (func $f1 (type $3) (param $var$0 i32) (param $var$1 i32) (param $var$2 i32) (result i32)
diff --git a/test/min.wast.fromBinary.noDebugInfo b/test/min.wast.fromBinary.noDebugInfo
index d6ad4be..b740758 100644
--- a/test/min.wast.fromBinary.noDebugInfo
+++ b/test/min.wast.fromBinary.noDebugInfo
@@ -46,7 +46,6 @@
    (br $label$0
     (i32.const 2)
    )
-   (i32.const 0)
   )
  )
  (func $3 (type $3) (param $var$0 i32) (param $var$1 i32) (param $var$2 i32) (result i32)
diff --git a/test/passes/flatten-control-flow.bin.txt b/test/passes/flatten-control-flow.bin.txt
index 9b33f97..5de52bb 100644
--- a/test/passes/flatten-control-flow.bin.txt
+++ b/test/passes/flatten-control-flow.bin.txt
@@ -119,51 +119,7 @@
   (local $var$8 f64)
   (block $label$0
    (nop)
-   (block
-    (block
-     (unreachable)
-    )
-   )
-   (drop
-    (f32.neg
-     (get_local $var$1)
-    )
-   )
-   (drop
-    (f64.neg
-     (get_local $var$2)
-    )
-   )
-   (drop
-    (i32.eqz
-     (get_local $var$3)
-    )
-   )
-   (drop
-    (i32.eqz
-     (get_local $var$4)
-    )
-   )
-   (drop
-    (f32.neg
-     (get_local $var$7)
-    )
-   )
-   (drop
-    (i64.eqz
-     (get_local $var$5)
-    )
-   )
-   (drop
-    (i64.eqz
-     (get_local $var$6)
-    )
-   )
-   (drop
-    (f64.neg
-     (get_local $var$8)
-    )
-   )
+   (unreachable)
   )
  )
  (func $9 (type $9) (param $var$0 i64) (param $var$1 f32) (param $var$2 f64) (param $var$3 i32) (param $var$4 i32) (result f64)
diff --git a/test/polymorphic_stack.wast b/test/polymorphic_stack.wast
index b618fbb..1b24591 100644
--- a/test/polymorphic_stack.wast
+++ b/test/polymorphic_stack.wast
@@ -86,6 +86,21 @@
       )
     )
   )
+  (func $unreachable-in-block-but-code-before (param $0 i32) (result i32)
+   (if
+    (get_local $0)
+    (return
+     (i32.const 127)
+    )
+   )
+   (block $label$0 (result i32)
+    (br_if $label$0
+     (return
+      (i32.const -32)
+     )
+    )
+   )
+  )
   (func $br_table_unreachable_to_also_unreachable (result i32)
     (block $a (result i32)
       (block $b
diff --git a/test/polymorphic_stack.wast.from-wast b/test/polymorphic_stack.wast.from-wast
index c20f921..82f88f4 100644
--- a/test/polymorphic_stack.wast.from-wast
+++ b/test/polymorphic_stack.wast.from-wast
@@ -90,6 +90,21 @@
    )
   )
  )
+ (func $unreachable-in-block-but-code-before (type $FUNCSIG$ii) (param $0 i32) (result i32)
+  (if
+   (get_local $0)
+   (return
+    (i32.const 127)
+   )
+  )
+  (block $label$0 (result i32)
+   (br_if $label$0
+    (return
+     (i32.const -32)
+    )
+   )
+  )
+ )
  (func $br_table_unreachable_to_also_unreachable (type $1) (result i32)
   (block $a (result i32)
    (block $b
diff --git a/test/polymorphic_stack.wast.fromBinary b/test/polymorphic_stack.wast.fromBinary
index b139c9c..68bb7ec 100644
--- a/test/polymorphic_stack.wast.fromBinary
+++ b/test/polymorphic_stack.wast.fromBinary
@@ -8,81 +8,18 @@
  (func $break-and-binary (type $1) (result i32)
   (block $label$0 (result i32)
    (unreachable)
-   (i32.trunc_u/f64
-    (unreachable)
-   )
-   (unreachable)
-   (br_if $label$0
-    (i32.trunc_u/f64
-     (unreachable)
-    )
-    (unreachable)
-   )
-   (f32.add
-    (unreachable)
-    (f32.const 1)
-   )
-   (unreachable)
   )
  )
  (func $call-and-unary (type $0) (param $var$0 i32) (result i32)
   (block $label$0 (result i32)
    (unreachable)
-   (call $call-and-unary
-    (unreachable)
-   )
-   (i64.eqz
-    (unreachable)
-   )
-   (drop
-    (unreachable)
-   )
-   (i32.eqz
-    (unreachable)
-   )
-   (i64.eqz
-    (unreachable)
-   )
-   (drop
-    (unreachable)
-   )
-   (call_indirect $0
-    (unreachable)
-    (unreachable)
-   )
-   (i64.eqz
-    (unreachable)
-   )
-   (drop
-    (unreachable)
-   )
   )
  )
  (func $tee (type $2) (param $var$0 i32)
   (local $var$1 f32)
   (block $label$0
    (unreachable)
-   (tee_local $var$0
-    (unreachable)
-   )
-   (i64.eqz
-    (unreachable)
-   )
-   (drop
-    (unreachable)
-   )
-   (i64.eqz
-    (unreachable)
-   )
-   (tee_local $var$1
-    (unreachable)
-   )
-   (drop
-    (unreachable)
-   )
-   (unreachable)
   )
-  (unreachable)
  )
  (func $tee2 (type $3)
   (local $var$0 f32)
@@ -90,48 +27,41 @@
    (i32.const 259)
    (block $label$0
     (unreachable)
-    (tee_local $var$0
-     (unreachable)
-    )
-    (unreachable)
    )
   )
  )
  (func $select (type $3)
   (unreachable)
-  (select
-   (unreachable)
-   (i32.const 1)
-   (i32.const 2)
-  )
-  (i64.eqz
-   (unreachable)
-  )
-  (drop
-   (unreachable)
-  )
  )
  (func $untaken-break-should-have-value (type $1) (result i32)
   (block $label$0 (result i32)
    (block $label$1
     (unreachable)
-    (br_if $label$0
-     (unreachable)
-     (unreachable)
-    )
-    (unreachable)
-    (unreachable)
    )
    (unreachable)
   )
  )
+ (func $unreachable-in-block-but-code-before (type $0) (param $var$0 i32) (result i32)
+  (block $label$0 (result i32)
+   (if
+    (get_local $var$0)
+    (block $label$1
+     (return
+      (i32.const 127)
+     )
+    )
+   )
+   (block $label$2 (result i32)
+    (return
+     (i32.const -32)
+    )
+   )
+  )
+ )
  (func $br_table_unreachable_to_also_unreachable (type $1) (result i32)
   (block $label$0 (result i32)
    (block $label$1
     (unreachable)
-    (unreachable)
-    (unreachable)
-    (unreachable)
    )
    (unreachable)
   )
diff --git a/test/polymorphic_stack.wast.fromBinary.noDebugInfo b/test/polymorphic_stack.wast.fromBinary.noDebugInfo
index e707d50..6929295 100644
--- a/test/polymorphic_stack.wast.fromBinary.noDebugInfo
+++ b/test/polymorphic_stack.wast.fromBinary.noDebugInfo
@@ -8,81 +8,18 @@
  (func $0 (type $1) (result i32)
   (block $label$0 (result i32)
    (unreachable)
-   (i32.trunc_u/f64
-    (unreachable)
-   )
-   (unreachable)
-   (br_if $label$0
-    (i32.trunc_u/f64
-     (unreachable)
-    )
-    (unreachable)
-   )
-   (f32.add
-    (unreachable)
-    (f32.const 1)
-   )
-   (unreachable)
   )
  )
  (func $1 (type $0) (param $var$0 i32) (result i32)
   (block $label$0 (result i32)
    (unreachable)
-   (call $1
-    (unreachable)
-   )
-   (i64.eqz
-    (unreachable)
-   )
-   (drop
-    (unreachable)
-   )
-   (i32.eqz
-    (unreachable)
-   )
-   (i64.eqz
-    (unreachable)
-   )
-   (drop
-    (unreachable)
-   )
-   (call_indirect $0
-    (unreachable)
-    (unreachable)
-   )
-   (i64.eqz
-    (unreachable)
-   )
-   (drop
-    (unreachable)
-   )
   )
  )
  (func $2 (type $2) (param $var$0 i32)
   (local $var$1 f32)
   (block $label$0
    (unreachable)
-   (tee_local $var$0
-    (unreachable)
-   )
-   (i64.eqz
-    (unreachable)
-   )
-   (drop
-    (unreachable)
-   )
-   (i64.eqz
-    (unreachable)
-   )
-   (tee_local $var$1
-    (unreachable)
-   )
-   (drop
-    (unreachable)
-   )
-   (unreachable)
   )
-  (unreachable)
  )
  (func $3 (type $3)
   (local $var$0 f32)
@@ -90,48 +27,41 @@
    (i32.const 259)
    (block $label$0
     (unreachable)
-    (tee_local $var$0
-     (unreachable)
-    )
-    (unreachable)
    )
   )
  )
  (func $4 (type $3)
   (unreachable)
-  (select
-   (unreachable)
-   (i32.const 1)
-   (i32.const 2)
-  )
-  (i64.eqz
-   (unreachable)
-  )
-  (drop
-   (unreachable)
-  )
  )
  (func $5 (type $1) (result i32)
   (block $label$0 (result i32)
    (block $label$1
     (unreachable)
-    (br_if $label$0
-     (unreachable)
-     (unreachable)
-    )
-    (unreachable)
-    (unreachable)
    )
    (unreachable)
   )
  )
- (func $6 (type $1) (result i32)
+ (func $6 (type $0) (param $var$0 i32) (result i32)
+  (block $label$0 (result i32)
+   (if
+    (get_local $var$0)
+    (block $label$1
+     (return
+      (i32.const 127)
+     )
+    )
+   )
+   (block $label$2 (result i32)
+    (return
+     (i32.const -32)
+    )
+   )
+  )
+ )
+ (func $7 (type $1) (result i32)
   (block $label$0 (result i32)
    (block $label$1
     (unreachable)
-    (unreachable)
-    (unreachable)
-    (unreachable)
    )
    (unreachable)
   )
diff --git a/test/unit.wast.fromBinary b/test/unit.wast.fromBinary
index 8bb80bc..817cc3c 100644
--- a/test/unit.wast.fromBinary
+++ b/test/unit.wast.fromBinary
@@ -270,22 +270,14 @@
       (block $label$15
        (loop $label$16
         (br $label$15)
-        (br $label$16)
        )
-       (unreachable)
-       (br $label$9)
       )
      )
      (block $label$17
       (loop $label$18
        (br $label$9)
-       (br $label$18)
       )
-      (unreachable)
-      (br $label$9)
-      (unreachable)
      )
-     (unreachable)
     )
     (nop)
    )
@@ -474,12 +466,7 @@
    (return
     (i32.const 2)
    )
-   (unreachable)
   )
-  (f64.abs
-   (unreachable)
-  )
-  (unreachable)
  )
  (func $unreachable-block-toplevel (type $5) (result i32)
   (block $label$0
@@ -489,30 +476,21 @@
    (return
     (i32.const 2)
    )
-   (unreachable)
   )
-  (unreachable)
  )
  (func $unreachable-block0 (type $5) (result i32)
   (block $label$0
    (return
     (i32.const 2)
    )
-   (unreachable)
   )
-  (f64.abs
-   (unreachable)
-  )
-  (unreachable)
  )
  (func $unreachable-block0-toplevel (type $5) (result i32)
   (block $label$0
    (return
     (i32.const 2)
    )
-   (unreachable)
   )
-  (unreachable)
  )
  (func $unreachable-block-with-br (type $5) (result i32)
   (block $label$0 (result i32)
@@ -539,10 +517,6 @@
     )
    )
   )
-  (f64.abs
-   (unreachable)
-  )
-  (unreachable)
  )
  (func $unreachable-if-toplevel (type $5) (result i32)
   (if
@@ -558,7 +532,6 @@
     )
    )
   )
-  (unreachable)
  )
  (func $unreachable-loop (type $5) (result i32)
   (loop $label$0
@@ -567,10 +540,6 @@
     (i32.const 1)
    )
   )
-  (f64.abs
-   (unreachable)
-  )
-  (unreachable)
  )
  (func $unreachable-loop0 (type $5) (result i32)
   (loop $label$0
@@ -578,10 +547,6 @@
     (i32.const 1)
    )
   )
-  (f64.abs
-   (unreachable)
-  )
-  (unreachable)
  )
  (func $unreachable-loop-toplevel (type $5) (result i32)
   (loop $label$0
@@ -590,7 +555,6 @@
     (i32.const 1)
    )
   )
-  (unreachable)
  )
  (func $unreachable-loop0-toplevel (type $5) (result i32)
   (loop $label$0
@@ -598,53 +562,11 @@
     (i32.const 1)
    )
   )
-  (unreachable)
  )
  (func $unreachable-ifs (type $1)
   (block $label$0
    (unreachable)
-   (unreachable)
-   (unreachable)
-   (unreachable)
-   (unreachable)
-   (unreachable)
-   (unreachable)
-   (unreachable)
-   (unreachable)
-   (unreachable)
-   (unreachable)
-   (unreachable)
-   (if
-    (i32.const 1)
-    (block $label$1
-     (unreachable)
-    )
-    (block $label$2
-     (nop)
-    )
-   )
-   (if
-    (i32.const 1)
-    (block $label$3
-     (nop)
-    )
-    (block $label$4
-     (unreachable)
-    )
-   )
-   (if
-    (i32.const 1)
-    (block $label$5
-     (unreachable)
-    )
-    (block $label$6
-     (unreachable)
-    )
-   )
-   (unreachable)
-   (unreachable)
   )
-  (unreachable)
  )
  (func $unreachable-if-arm (type $1)
   (if
@@ -654,10 +576,6 @@
    )
    (block $label$1
     (unreachable)
-    (drop
-     (i32.const 1)
-    )
-    (unreachable)
    )
   )
  )
diff --git a/test/unit.wast.fromBinary.noDebugInfo b/test/unit.wast.fromBinary.noDebugInfo
index 09da9a1..46b86e8 100644
--- a/test/unit.wast.fromBinary.noDebugInfo
+++ b/test/unit.wast.fromBinary.noDebugInfo
@@ -270,22 +270,14 @@
       (block $label$15
        (loop $label$16
         (br $label$15)
-        (br $label$16)
        )
-       (unreachable)
-       (br $label$9)
       )
      )
      (block $label$17
       (loop $label$18
        (br $label$9)
-       (br $label$18)
       )
-      (unreachable)
-      (br $label$9)
-      (unreachable)
      )
-     (unreachable)
     )
     (nop)
    )
@@ -474,12 +466,7 @@
    (return
     (i32.const 2)
    )
-   (unreachable)
   )
-  (f64.abs
-   (unreachable)
-  )
-  (unreachable)
  )
  (func $25 (type $5) (result i32)
   (block $label$0
@@ -489,30 +476,21 @@
    (return
     (i32.const 2)
    )
-   (unreachable)
   )
-  (unreachable)
  )
  (func $26 (type $5) (result i32)
   (block $label$0
    (return
     (i32.const 2)
    )
-   (unreachable)
   )
-  (f64.abs
-   (unreachable)
-  )
-  (unreachable)
  )
  (func $27 (type $5) (result i32)
   (block $label$0
    (return
     (i32.const 2)
    )
-   (unreachable)
   )
-  (unreachable)
  )
  (func $28 (type $5) (result i32)
   (block $label$0 (result i32)
@@ -539,10 +517,6 @@
     )
    )
   )
-  (f64.abs
-   (unreachable)
-  )
-  (unreachable)
  )
  (func $30 (type $5) (result i32)
   (if
@@ -558,7 +532,6 @@
     )
    )
   )
-  (unreachable)
  )
  (func $31 (type $5) (result i32)
   (loop $label$0
@@ -567,10 +540,6 @@
     (i32.const 1)
    )
   )
-  (f64.abs
-   (unreachable)
-  )
-  (unreachable)
  )
  (func $32 (type $5) (result i32)
   (loop $label$0
@@ -578,10 +547,6 @@
     (i32.const 1)
    )
   )
-  (f64.abs
-   (unreachable)
-  )
-  (unreachable)
  )
  (func $33 (type $5) (result i32)
   (loop $label$0
@@ -590,7 +555,6 @@
     (i32.const 1)
    )
   )
-  (unreachable)
  )
  (func $34 (type $5) (result i32)
   (loop $label$0
@@ -598,53 +562,11 @@
     (i32.const 1)
    )
   )
-  (unreachable)
  )
  (func $35 (type $1)
   (block $label$0
    (unreachable)
-   (unreachable)
-   (unreachable)
-   (unreachable)
-   (unreachable)
-   (unreachable)
-   (unreachable)
-   (unreachable)
-   (unreachable)
-   (unreachable)
-   (unreachable)
-   (unreachable)
-   (if
-    (i32.const 1)
-    (block $label$1
-     (unreachable)
-    )
-    (block $label$2
-     (nop)
-    )
-   )
-   (if
-    (i32.const 1)
-    (block $label$3
-     (nop)
-    )
-    (block $label$4
-     (unreachable)
-    )
-   )
-   (if
-    (i32.const 1)
-    (block $label$5
-     (unreachable)
-    )
-    (block $label$6
-     (unreachable)
-    )
-   )
-   (unreachable)
-   (unreachable)
   )
-  (unreachable)
  )
  (func $36 (type $1)
   (if
@@ -654,10 +576,6 @@
    )
    (block $label$1
     (unreachable)
-    (drop
-     (i32.const 1)
-    )
-    (unreachable)
    )
   )
  )
diff --git a/test/unreachable-code.wast b/test/unreachable-code.wast
new file mode 100644
index 0000000..f67d10e
--- /dev/null
+++ b/test/unreachable-code.wast
@@ -0,0 +1,85 @@
+(module
+  (func $a
+    (if (i32.const 1)
+      (unreachable)
+    )
+  )
+  (func $b
+    (if (i32.const 1)
+      (unreachable)
+      (unreachable)
+    )
+  )
+  (func $a-block
+    (block
+      (if (i32.const 1)
+        (unreachable)
+      )
+    )
+  )
+  (func $b-block
+    (block
+      (if (i32.const 1)
+        (unreachable)
+        (unreachable)
+      )
+    )
+  )
+  (func $a-prepost
+    (nop)
+    (if (i32.const 1)
+      (unreachable)
+    )
+    (nop)
+  )
+  (func $b-prepost
+    (nop)
+    (if (i32.const 1)
+      (unreachable)
+      (unreachable)
+    )
+    (nop)
+  )
+  (func $a-block-prepost
+    (nop)
+    (block
+      (if (i32.const 1)
+        (unreachable)
+      )
+    )
+    (nop)
+  )
+  (func $b-block-prepost
+    (nop)
+    (block
+      (if (i32.const 1)
+        (unreachable)
+        (unreachable)
+      )
+    )
+    (nop)
+  )
+  (func $recurse
+    (block $a
+      (nop)
+      (block $b
+        (nop)
+        (br $b)
+        (nop)
+      )
+      (nop)
+    )
+  )
+  (func $recurse-b
+    (block $a
+      (nop)
+      (block $b
+        (nop)
+        (br $a)
+        (nop)
+      )
+      (nop)
+    )
+  )
+)
+
diff --git a/test/unreachable-code.wast.from-wast b/test/unreachable-code.wast.from-wast
new file mode 100644
index 0000000..2196056
--- /dev/null
+++ b/test/unreachable-code.wast.from-wast
@@ -0,0 +1,94 @@
+(module
+ (type $0 (func))
+ (memory $0 0)
+ (func $a (type $0)
+  (if
+   (i32.const 1)
+   (unreachable)
+  )
+ )
+ (func $b (type $0)
+  (if
+   (i32.const 1)
+   (unreachable)
+   (unreachable)
+  )
+ )
+ (func $a-block (type $0)
+  (block $block
+   (if
+    (i32.const 1)
+    (unreachable)
+   )
+  )
+ )
+ (func $b-block (type $0)
+  (block $block
+   (if
+    (i32.const 1)
+    (unreachable)
+    (unreachable)
+   )
+  )
+ )
+ (func $a-prepost (type $0)
+  (nop)
+  (if
+   (i32.const 1)
+   (unreachable)
+  )
+  (nop)
+ )
+ (func $b-prepost (type $0)
+  (nop)
+  (if
+   (i32.const 1)
+   (unreachable)
+   (unreachable)
+  )
+  (nop)
+ )
+ (func $a-block-prepost (type $0)
+  (nop)
+  (block $block
+   (if
+    (i32.const 1)
+    (unreachable)
+   )
+  )
+  (nop)
+ )
+ (func $b-block-prepost (type $0)
+  (nop)
+  (block $block
+   (if
+    (i32.const 1)
+    (unreachable)
+    (unreachable)
+   )
+  )
+  (nop)
+ )
+ (func $recurse (type $0)
+  (block $a
+   (nop)
+   (block $b
+    (nop)
+    (br $b)
+    (nop)
+   )
+   (nop)
+  )
+ )
+ (func $recurse-b (type $0)
+  (block $a
+   (nop)
+   (block $b
+    (nop)
+    (br $a)
+    (nop)
+   )
+   (nop)
+  )
+ )
+)
diff --git a/test/unreachable-code.wast.fromBinary b/test/unreachable-code.wast.fromBinary
new file mode 100644
index 0000000..d27da22
--- /dev/null
+++ b/test/unreachable-code.wast.fromBinary
@@ -0,0 +1,122 @@
+(module
+ (type $0 (func))
+ (memory $0 0)
+ (func $a (type $0)
+  (if
+   (i32.const 1)
+   (block $label$0
+    (unreachable)
+   )
+  )
+ )
+ (func $b (type $0)
+  (if
+   (i32.const 1)
+   (block $label$0
+    (unreachable)
+   )
+   (block $label$1
+    (unreachable)
+   )
+  )
+ )
+ (func $a-block (type $0)
+  (block $label$0
+   (if
+    (i32.const 1)
+    (block $label$1
+     (unreachable)
+    )
+   )
+  )
+ )
+ (func $b-block (type $0)
+  (block $label$0
+   (if
+    (i32.const 1)
+    (block $label$1
+     (unreachable)
+    )
+    (block $label$2
+     (unreachable)
+    )
+   )
+  )
+ )
+ (func $a-prepost (type $0)
+  (block $label$0
+   (nop)
+   (if
+    (i32.const 1)
+    (block $label$1
+     (unreachable)
+    )
+   )
+   (nop)
+  )
+ )
+ (func $b-prepost (type $0)
+  (block $label$0
+   (nop)
+   (if
+    (i32.const 1)
+    (block $label$1
+     (unreachable)
+    )
+    (block $label$2
+     (unreachable)
+    )
+   )
+  )
+ )
+ (func $a-block-prepost (type $0)
+  (block $label$0
+   (nop)
+   (block $label$1
+    (if
+     (i32.const 1)
+     (block $label$2
+      (unreachable)
+     )
+    )
+   )
+   (nop)
+  )
+ )
+ (func $b-block-prepost (type $0)
+  (block $label$0
+   (nop)
+   (block $label$1
+    (if
+     (i32.const 1)
+     (block $label$2
+      (unreachable)
+     )
+     (block $label$3
+      (unreachable)
+     )
+    )
+   )
+  )
+ )
+ (func $recurse (type $0)
+  (block $label$0
+   (nop)
+   (block $label$1
+    (nop)
+    (br $label$1)
+   )
+   (nop)
+  )
+ )
+ (func $recurse-b (type $0)
+  (block $label$0
+   (nop)
+   (block $label$1
+    (nop)
+    (br $label$0)
+   )
+  )
+ )
+)
+
diff --git a/test/unreachable-code.wast.fromBinary.noDebugInfo b/test/unreachable-code.wast.fromBinary.noDebugInfo
new file mode 100644
index 0000000..288a24f
--- /dev/null
+++ b/test/unreachable-code.wast.fromBinary.noDebugInfo
@@ -0,0 +1,122 @@
+(module
+ (type $0 (func))
+ (memory $0 0)
+ (func $0 (type $0)
+  (if
+   (i32.const 1)
+   (block $label$0
+    (unreachable)
+   )
+  )
+ )
+ (func $1 (type $0)
+  (if
+   (i32.const 1)
+   (block $label$0
+    (unreachable)
+   )
+   (block $label$1
+    (unreachable)
+   )
+  )
+ )
+ (func $2 (type $0)
+  (block $label$0
+   (if
+    (i32.const 1)
+    (block $label$1
+     (unreachable)
+    )
+   )
+  )
+ )
+ (func $3 (type $0)
+  (block $label$0
+   (if
+    (i32.const 1)
+    (block $label$1
+     (unreachable)
+    )
+    (block $label$2
+     (unreachable)
+    )
+   )
+  )
+ )
+ (func $4 (type $0)
+  (block $label$0
+   (nop)
+   (if
+    (i32.const 1)
+    (block $label$1
+     (unreachable)
+    )
+   )
+   (nop)
+  )
+ )
+ (func $5 (type $0)
+  (block $label$0
+   (nop)
+   (if
+    (i32.const 1)
+    (block $label$1
+     (unreachable)
+    )
+    (block $label$2
+     (unreachable)
+    )
+   )
+  )
+ )
+ (func $6 (type $0)
+  (block $label$0
+   (nop)
+   (block $label$1
+    (if
+     (i32.const 1)
+     (block $label$2
+      (unreachable)
+     )
+    )
+   )
+   (nop)
+  )
+ )
+ (func $7 (type $0)
+  (block $label$0
+   (nop)
+   (block $label$1
+    (if
+     (i32.const 1)
+     (block $label$2
+      (unreachable)
+     )
+     (block $label$3
+      (unreachable)
+     )
+    )
+   )
+  )
+ )
+ (func $8 (type $0)
+  (block $label$0
+   (nop)
+   (block $label$1
+    (nop)
+    (br $label$1)
+   )
+   (nop)
+  )
+ )
+ (func $9 (type $0)
+  (block $label$0
+   (nop)
+   (block $label$1
+    (nop)
+    (br $label$0)
+   )
+  )
+ )
+)
+
diff --git a/test/unreachable-pops.wasm b/test/unreachable-pops.wasm
new file mode 100644
index 0000000..f3e0ae1
--- /dev/null
+++ b/test/unreachable-pops.wasm
Binary files differ
diff --git a/test/unreachable-pops.wasm.fromBinary b/test/unreachable-pops.wasm.fromBinary
new file mode 100644
index 0000000..4e14995
--- /dev/null
+++ b/test/unreachable-pops.wasm.fromBinary
@@ -0,0 +1,10 @@
+(module
+ (type $0 (func (result i32)))
+ (memory $0 0)
+ (func $0 (type $0) (result i32)
+  (block $label$0 (result i32)
+   (unreachable)
+  )
+ )
+)
+
diff --git a/test/untaken-br_if.wast.fromBinary b/test/untaken-br_if.wast.fromBinary
index 87dd9c7..815789c 100644
--- a/test/untaken-br_if.wast.fromBinary
+++ b/test/untaken-br_if.wast.fromBinary
@@ -12,16 +12,10 @@
      (drop
       (i32.const 1)
      )
-     (br_if $label$2
-      (unreachable)
-     )
-     (unreachable)
      (unreachable)
     )
-    (unreachable)
    )
   )
-  (unreachable)
  )
 )
 
diff --git a/test/untaken-br_if.wast.fromBinary.noDebugInfo b/test/untaken-br_if.wast.fromBinary.noDebugInfo
index ad71cd8..9b31417 100644
--- a/test/untaken-br_if.wast.fromBinary.noDebugInfo
+++ b/test/untaken-br_if.wast.fromBinary.noDebugInfo
@@ -12,16 +12,10 @@
      (drop
       (i32.const 1)
      )
-     (br_if $label$2
-      (unreachable)
-     )
-     (unreachable)
      (unreachable)
     )
-    (unreachable)
    )
   )
-  (unreachable)
  )
 )