Bug 1338002 - Baldr: temporarily accept both 0xd and 0x1. r=sunfish a=gchang
authorLuke Wagner <luke@mozilla.com>
Thu, 09 Feb 2017 18:32:39 -0600
changeset 378600 cc472627593465885a69a4c1c7e2fd8ad3787c48
parent 378599 c0053a0cf40f389068d7fdf443d721df8259cdaf
child 378601 0ef6ce3951925a1972396f61db87aedfb3c9004a
push id1419
push userjlund@mozilla.com
push dateMon, 10 Apr 2017 20:44:07 +0000
treeherdermozilla-release@5e6801b73ef6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssunfish, gchang
bugs1338002, 1324032
milestone53.0a2
Bug 1338002 - Baldr: temporarily accept both 0xd and 0x1. r=sunfish a=gchang MozReview-Commit-ID: FZyBAnADg7 * * * Bug 1338002 - Baldr: backout bug 1324032 (r=sunfish) MozReview-Commit-ID: DATV6VP9672 * * * Bug 1338002 - Baldr: validate unreachable code with polymorphic type checking (r=sunfish) MozReview-Commit-ID: 71NtyT7Khl8
js/src/jit-test/lib/wasm-binary.js
js/src/jit-test/lib/wasm.js
js/src/jit-test/tests/wasm/basic.js
js/src/jit-test/tests/wasm/binary.js
js/src/jit-test/tests/wasm/control-flow.js
js/src/jit-test/tests/wasm/full-cycle.js
js/src/jit-test/tests/wasm/regress/baseline-joinreg.js
js/src/jit-test/tests/wasm/regress/misc-control-flow.js
js/src/jit-test/tests/wasm/regress/movable-traps.js
js/src/jit-test/tests/wasm/regress/select-any.js
js/src/jit-test/tests/wasm/spec/block.wast.js
js/src/jit-test/tests/wasm/spec/br.wast.js
js/src/jit-test/tests/wasm/spec/br_if.wast.js
js/src/jit-test/tests/wasm/spec/br_table.wast.js
js/src/jit-test/tests/wasm/spec/func.wast.js
js/src/jit-test/tests/wasm/spec/labels.wast.js
js/src/jit-test/tests/wasm/spec/loop.wast.js
js/src/jit-test/tests/wasm/spec/return.wast.js
js/src/jit-test/tests/wasm/spec/select.wast.js
js/src/jit-test/tests/wasm/spec/unreachable.wast.js
js/src/jit-test/tests/wasm/spec/unwind.wast.js
js/src/jit-test/tests/wasm/to-text-experimental.js
js/src/jit-test/tests/wasm/unreachable.js
js/src/jit-test/tests/wasm/wast.js
js/src/wasm/AsmJS.cpp
js/src/wasm/WasmBaselineCompile.cpp
js/src/wasm/WasmBinaryConstants.h
js/src/wasm/WasmBinaryIterator.h
js/src/wasm/WasmBinaryToAST.cpp
js/src/wasm/WasmIonCompile.cpp
js/src/wasm/WasmValidate.cpp
--- a/js/src/jit-test/lib/wasm-binary.js
+++ b/js/src/jit-test/lib/wasm-binary.js
@@ -1,20 +1,20 @@
 // MagicNumber = 0x6d736100;
 const magic0 = 0x00;  // '\0'
 const magic1 = 0x61;  // 'a'
 const magic2 = 0x73;  // 's'
 const magic3 = 0x6d;  // 'm'
 
-// EncodingVersion (temporary; to be set to 1 at some point before release)
-const experimentalVersion = 0xd;
-const ver0 = (experimentalVersion >>>  0) & 0xff;
-const ver1 = (experimentalVersion >>>  8) & 0xff;
-const ver2 = (experimentalVersion >>> 16) & 0xff;
-const ver3 = (experimentalVersion >>> 24) & 0xff;
+// EncodingVersion
+const encodingVersion = 0x1;
+const ver0 = (encodingVersion >>>  0) & 0xff;
+const ver1 = (encodingVersion >>>  8) & 0xff;
+const ver2 = (encodingVersion >>> 16) & 0xff;
+const ver3 = (encodingVersion >>> 24) & 0xff;
 
 // Section opcodes
 const userDefinedId    = 0;
 const typeId           = 1;
 const importId         = 2;
 const functionId       = 3;
 const tableId          = 4;
 const memoryId         = 5;
--- a/js/src/jit-test/lib/wasm.js
+++ b/js/src/jit-test/lib/wasm.js
@@ -30,16 +30,19 @@ function wasmFailValidateText(str, patte
     assertErrorMessage(() => new WebAssembly.Module(binary), WebAssembly.CompileError, pattern);
 }
 
 function mismatchError(actual, expect) {
     var str = `type mismatch: expression has type ${actual} but expected ${expect}`;
     return RegExp(str);
 }
 
+const emptyStackError = /from empty stack/;
+const unusedValuesError = /unused values not explicitly dropped by end of block/;
+
 function jsify(wasmVal) {
     if (wasmVal === 'nan')
         return NaN;
     if (wasmVal === 'infinity')
         return Infinity;
     if (wasmVal === '-infinity')
         return Infinity;
     if (wasmVal === '-0')
--- a/js/src/jit-test/tests/wasm/basic.js
+++ b/js/src/jit-test/tests/wasm/basic.js
@@ -53,18 +53,18 @@ assertEq(o.a(), 2);
 assertEq(o.b(), 1);
 
 wasmFailValidateText('(module (func) (export "a" 0) (export "a" 0))', /duplicate export/);
 wasmFailValidateText('(module (func) (func) (export "a" 0) (export "a" 1))', /duplicate export/);
 
 // ----------------------------------------------------------------------------
 // signatures
 
-wasmFailValidateText('(module (func (result i32)))', mismatchError("void", "i32"));
-wasmFailValidateText('(module (func (result i32) (nop)))', mismatchError("void", "i32"));
+wasmFailValidateText('(module (func (result i32)))', emptyStackError);
+wasmFailValidateText('(module (func (result i32) (nop)))', emptyStackError);
 
 wasmValidateText('(module (func (nop)))');
 wasmValidateText('(module (func (result i32) (i32.const 42)))');
 wasmValidateText('(module (func (param i32)))');
 wasmValidateText('(module (func (param i32) (result i32) (i32.const 42)))');
 wasmValidateText('(module (func (result i32) (param i32) (i32.const 42)))');
 wasmValidateText('(module (func (param f32)))');
 wasmValidateText('(module (func (param f64)))');
@@ -191,17 +191,17 @@ wasmValidateText('(module (func (local i
 wasmValidateText('(module (func (local i32) (local f32)))');
 
 wasmFullPass('(module (func (result i32) (local i32) (get_local 0)) (export "run" 0))', 0);
 wasmFullPass('(module (func (result i32) (param i32) (local f32) (get_local 0)) (export "run" 0))', 0);
 wasmFullPass('(module (func (result f32) (param i32) (local f32) (get_local 1)) (export "run" 0))', 0);
 
 wasmFailValidateText('(module (func (set_local 0 (i32.const 0))))', /set_local index out of range/);
 wasmFailValidateText('(module (func (local f32) (set_local 0 (i32.const 0))))', mismatchError("i32", "f32"));
-wasmFailValidateText('(module (func (local f32) (set_local 0 (nop))))', /popping value from empty stack/);
+wasmFailValidateText('(module (func (local f32) (set_local 0 (nop))))', emptyStackError);
 wasmFailValidateText('(module (func (local i32) (local f32) (set_local 0 (get_local 1))))', mismatchError("f32", "i32"));
 wasmFailValidateText('(module (func (local i32) (local f32) (set_local 1 (get_local 0))))', mismatchError("i32", "f32"));
 
 wasmValidateText('(module (func (local i32) (set_local 0 (i32.const 0))))');
 wasmValidateText('(module (func (local i32) (local f32) (set_local 0 (get_local 0))))');
 wasmValidateText('(module (func (local i32) (local f32) (set_local 1 (get_local 1))))');
 
 wasmFullPass('(module (func (result i32) (local i32) (tee_local 0 (i32.const 42))) (export "run" 0))', 42);
@@ -212,51 +212,51 @@ wasmFullPass('(module (func (param $a i3
 
 wasmValidateText('(module (func (local i32) (local $a f32) (set_local 0 (i32.const 1)) (set_local $a (f32.const nan))))');
 
 // ----------------------------------------------------------------------------
 // blocks
 
 wasmFullPass('(module (func (block )) (export "run" 0))', undefined);
 
-wasmFailValidateText('(module (func (result i32) (block )))', mismatchError("void", "i32"));
-wasmFailValidateText('(module (func (result i32) (block (block ))))', mismatchError("void", "i32"));
-wasmFailValidateText('(module (func (local i32) (set_local 0 (block ))))', /popping value from empty stack/);
+wasmFailValidateText('(module (func (result i32) (block )))', emptyStackError);
+wasmFailValidateText('(module (func (result i32) (block (block ))))', emptyStackError);
+wasmFailValidateText('(module (func (local i32) (set_local 0 (block ))))', emptyStackError);
 
 wasmFullPass('(module (func (block (block ))) (export "run" 0))', undefined);
 wasmFullPass('(module (func (result i32) (block i32 (i32.const 42))) (export "run" 0))', 42);
 wasmFullPass('(module (func (result i32) (block i32 (block i32 (i32.const 42)))) (export "run" 0))', 42);
 wasmFailValidateText('(module (func (result f32) (block i32 (i32.const 0))))', mismatchError("i32", "f32"));
 
 wasmFullPass('(module (func (result i32) (block i32 (drop (i32.const 13)) (block i32 (i32.const 42)))) (export "run" 0))', 42);
 wasmFailValidateText('(module (func (result f32) (param f32) (block i32 (drop (get_local 0)) (i32.const 0))))', mismatchError("i32", "f32"));
 
 wasmFullPass('(module (func (result i32) (local i32) (set_local 0 (i32.const 42)) (get_local 0)) (export "run" 0))', 42);
 
 // ----------------------------------------------------------------------------
 // calls
 
-wasmFailValidateText('(module (func (nop)) (func (call 0 (i32.const 0))))', /unused values not explicitly dropped by end of block/);
+wasmFailValidateText('(module (func (nop)) (func (call 0 (i32.const 0))))', unusedValuesError);
 
-wasmFailValidateText('(module (func (param i32) (nop)) (func (call 0)))', /peeking at value from outside block/);
+wasmFailValidateText('(module (func (param i32) (nop)) (func (call 0)))', emptyStackError);
 wasmFailValidateText('(module (func (param f32) (nop)) (func (call 0 (i32.const 0))))', mismatchError("i32", "f32"));
 wasmFailValidateText('(module (func (nop)) (func (call 3)))', /callee index out of range/);
 
 wasmValidateText('(module (func (nop)) (func (call 0)))');
 wasmValidateText('(module (func (param i32) (nop)) (func (call 0 (i32.const 0))))');
 
 wasmFullPass('(module (func (result i32) (i32.const 42)) (func (result i32) (call 0)) (export "run" 1))', 42);
 assertThrowsInstanceOf(() => wasmEvalText('(module (func (call 0)) (export "" 0))').exports[""](), InternalError);
 assertThrowsInstanceOf(() => wasmEvalText('(module (func (call 1)) (func (call 0)) (export "" 0))').exports[""](), InternalError);
 
 wasmValidateText('(module (func (param i32 f32)) (func (call 0 (i32.const 0) (f32.const nan))))');
 wasmFailValidateText('(module (func (param i32 f32)) (func (call 0 (i32.const 0) (i32.const 0))))', mismatchError("i32", "f32"));
 
-wasmFailValidateText('(module (import "a" "") (func (call 0 (i32.const 0))))', /unused values not explicitly dropped by end of block/);
-wasmFailValidateText('(module (import "a" "" (param i32)) (func (call 0)))', /peeking at value from outside block/);
+wasmFailValidateText('(module (import "a" "") (func (call 0 (i32.const 0))))', unusedValuesError);
+wasmFailValidateText('(module (import "a" "" (param i32)) (func (call 0)))', emptyStackError);
 wasmFailValidateText('(module (import "a" "" (param f32)) (func (call 0 (i32.const 0))))', mismatchError("i32", "f32"));
 
 assertErrorMessage(() => wasmEvalText('(module (import "a" "") (func (call 1)))'), TypeError, noImportObj);
 wasmEvalText('(module (import "" "a") (func (call 0)))', {"":{a:()=>{}}});
 wasmEvalText('(module (import "" "a" (param i32)) (func (call 0 (i32.const 0))))', {"":{a:()=>{}}});
 
 function checkF32CallImport(v) {
     wasmFullPass('(module (import "" "a" (result f32)) (func (result f32) (call 0)) (export "run" 1))',
@@ -457,23 +457,22 @@ wasmValidateText('(module (func (call $f
 wasmValidateText('(module (import $bar "" "a") (func (call $bar)) (func $foo (nop)))');
 
 // ----------------------------------------------------------------------------
 // select
 
 wasmFailValidateText('(module (func (select (i32.const 0) (i32.const 0) (f32.const 0))))', mismatchError("f32", "i32"));
 
 wasmFailValidateText('(module (func (select (i32.const 0) (f32.const 0) (i32.const 0))) (export "" 0))', /select operand types must match/);
-wasmFailValidateText('(module (func (select (block ) (i32.const 0) (i32.const 0))) (export "" 0))', /popping value from empty stack/);
-wasmFailValidateText('(module (func (select (return) (i32.const 0) (i32.const 0))) (export "" 0))', /non-fallthrough instruction must be followed by end or else/);
-wasmFailValidateText('(module (func (i32.add (i32.const 0) (select (return) (i32.const 0) (i32.const 0)))) (export "" 0))', /non-fallthrough instruction must be followed by end or else/);
-assertEq(wasmEvalText('(module (func (select (block (return)) (i32.const 0) (i32.const 0))) (export "" 0))').exports[""](), undefined);
-assertEq(wasmEvalText('(module (func (i32.add (i32.const 0) (select (block (return)) (i32.const 0) (i32.const 0)))) (export "" 0))').exports[""](), undefined);
+wasmFailValidateText('(module (func (select (block ) (i32.const 0) (i32.const 0))) (export "" 0))', emptyStackError);
+wasmFailValidateText('(module (func (select (return) (i32.const 0) (i32.const 0))) (export "" 0))', unusedValuesError);
+assertEq(wasmEvalText('(module (func (drop (select (return) (i32.const 0) (i32.const 0)))) (export "" 0))').exports[""](), undefined);
+assertEq(wasmEvalText('(module (func (result i32) (i32.add (i32.const 0) (select (return (i32.const 42)) (i32.const 0) (i32.const 0)))) (export "" 0))').exports[""](), 42);
 wasmFailValidateText('(module (func (select (if i32 (i32.const 1) (i32.const 0) (f32.const 0)) (i32.const 0) (i32.const 0))) (export "" 0))', mismatchError("f32", "i32"));
-wasmFailValidateText('(module (func) (func (select (call 0) (call 0) (i32.const 0))) (export "" 0))', /popping value from empty stack/);
+wasmFailValidateText('(module (func) (func (select (call 0) (call 0) (i32.const 0))) (export "" 0))', emptyStackError);
 
 (function testSideEffects() {
 
 var numT = 0;
 var numF = 0;
 
 var imports = {"": {
     ifTrue: () => 1 + numT++,
--- a/js/src/jit-test/tests/wasm/binary.js
+++ b/js/src/jit-test/tests/wasm/binary.js
@@ -7,17 +7,17 @@ const CompileError = WebAssembly.Compile
 const magicError = /failed to match magic number/;
 const unknownSection = /expected custom section/;
 
 function sectionError(section) {
     return RegExp(`failed to start ${section} section`);
 }
 
 function versionError(actual) {
-    var expect = experimentalVersion;
+    var expect = encodingVersion;
     var str = `binary version 0x${actual.toString(16)} does not match expected version 0x${expect.toString(16)}`;
     return RegExp(str);
 }
 
 function toU8(array) {
     for (let b of array)
         assertEq(b < 256, true);
     return Uint8Array.from(array);
@@ -59,16 +59,19 @@ assertErrorMessage(() => wasmEval(toU8([
 assertErrorMessage(() => wasmEval(toU8([42])), CompileError, magicError);
 assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2])), CompileError, magicError);
 assertErrorMessage(() => wasmEval(toU8([1,2,3,4])), CompileError, magicError);
 assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2, magic3])), CompileError, versionError(0x6d736100));
 assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2, magic3, 1])), CompileError, versionError(0x6d736100));
 assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2, magic3, ver0])), CompileError, versionError(0x6d736100));
 assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2, magic3, ver0, ver1, ver2])), CompileError, versionError(0x6d736100));
 
+// This test should be removed shortly.
+assertEq(WebAssembly.validate(toU8([magic0, magic1, magic2, magic3, 0xd, 0x0, 0x0, 0x0])), true);
+
 function moduleHeaderThen(...rest) {
     return [magic0, magic1, magic2, magic3, ver0, ver1, ver2, ver3, ...rest];
 }
 
 var o = wasmEval(toU8(moduleHeaderThen()));
 assertEq(Object.getOwnPropertyNames(o).length, 0);
 
 // unfinished known sections
--- a/js/src/jit-test/tests/wasm/control-flow.js
+++ b/js/src/jit-test/tests/wasm/control-flow.js
@@ -152,200 +152,136 @@ wasmFullPass(`(module
         )
     )
     (export "run" 1)
 )`, 42, imports);
 assertEq(counter, 0);
 
 // "if" doesn't return an expression value
 wasmFailValidateText('(module (func (result i32) (if i32 (i32.const 42) (i32.const 0))))', /if without else with a result value/);
-wasmFailValidateText('(module (func (result i32) (if i32 (i32.const 42) (drop (i32.const 0)))))', mismatchError("void", "i32"));
+wasmFailValidateText('(module (func (result i32) (if i32 (i32.const 42) (drop (i32.const 0)))))', emptyStackError);
 wasmFailValidateText('(module (func (result i32) (if i32 (i32.const 1) (i32.const 0) (if i32 (i32.const 1) (i32.const 1)))))', /if without else with a result value/);
-wasmFailValidateText('(module (func (result i32) (if i32 (i32.const 1) (drop (i32.const 0)) (if (i32.const 1) (drop (i32.const 1))))))', mismatchError("void", "i32"));
+wasmFailValidateText('(module (func (result i32) (if i32 (i32.const 1) (drop (i32.const 0)) (if (i32.const 1) (drop (i32.const 1))))))', emptyStackError);
 wasmFailValidateText('(module (func (if i32 (i32.const 1) (i32.const 0) (if i32 (i32.const 1) (i32.const 1)))))', /if without else with a result value/);
-wasmFailValidateText('(module (func (if i32 (i32.const 1) (i32.const 0) (if (i32.const 1) (drop (i32.const 1))))))', mismatchError("void", "i32"));
+wasmFailValidateText('(module (func (if i32 (i32.const 1) (i32.const 0) (if (i32.const 1) (drop (i32.const 1))))))', emptyStackError);
 wasmFailValidateText('(module (func (if (i32.const 1) (drop (i32.const 0)) (if i32 (i32.const 1) (i32.const 1)))))', /if without else with a result value/);
 wasmEvalText('(module (func (if (i32.const 1) (drop (i32.const 0)) (if (i32.const 1) (drop (i32.const 1))))))');
 
 // ----------------------------------------------------------------------------
 // return
 
 wasmFullPass('(module (func (return)) (export "run" 0))', undefined);
 wasmFullPass('(module (func (result i32) (return (i32.const 1))) (export "run" 0))', 1);
-wasmFailValidateText('(module (func (if (return) (i32.const 0))) (export "run" 0))', /non-fallthrough instruction must be followed by end or else/);
-wasmFullPass('(module (func (if (block (return)) (i32.const 0))) (export "run" 0))', undefined);
-wasmFailValidateText('(module (func (result i32) (return)) (export "" 0))', /popping value from empty stack/);
+wasmFailValidateText('(module (func (if (return) (i32.const 0))) (export "run" 0))', unusedValuesError);
+wasmFailValidateText('(module (func (result i32) (return)) (export "" 0))', emptyStackError);
 wasmFullPass('(module (func (return (i32.const 1))) (export "run" 0))', undefined);
 wasmFailValidateText('(module (func (result f32) (return (i32.const 1))) (export "" 0))', mismatchError("i32", "f32"));
-wasmFailValidateText('(module (func (result i32) (return)) (export "" 0))', /popping value from empty stack/);
+wasmFailValidateText('(module (func (result i32) (return)) (export "" 0))', emptyStackError);
 
 // ----------------------------------------------------------------------------
 // br / br_if
 
-wasmFailValidateText('(module (func (result i32) (block (br 0))) (export "" 0))', mismatchError("void", "i32"));
-wasmFailValidateText('(module (func (result i32) (br 0)) (export "" 0))', /popping value from empty stack/);
-wasmFailValidateText('(module (func (result i32) (block (br_if 0 (i32.const 0)))) (export "" 0))', mismatchError("void", "i32"));
+wasmFailValidateText('(module (func (result i32) (block (br 0))) (export "" 0))', emptyStackError);
+wasmFailValidateText('(module (func (result i32) (br 0)) (export "" 0))', emptyStackError);
+wasmFailValidateText('(module (func (result i32) (block (br_if 0 (i32.const 0)))) (export "" 0))', emptyStackError);
 
 const DEPTH_OUT_OF_BOUNDS = /branch depth exceeds current nesting level/;
 
 wasmFailValidateText('(module (func (br 1)))', DEPTH_OUT_OF_BOUNDS);
 wasmFailValidateText('(module (func (block (br 2))))', DEPTH_OUT_OF_BOUNDS);
 wasmFailValidateText('(module (func (loop (br 2))))', DEPTH_OUT_OF_BOUNDS);
 wasmFailValidateText('(module (func (if (i32.const 0) (br 2))))', DEPTH_OUT_OF_BOUNDS);
 wasmFailValidateText('(module (func (if (i32.const 0) (br 1) (br 2))))', DEPTH_OUT_OF_BOUNDS);
 wasmFailValidateText('(module (func (if (i32.const 0) (br 2) (br 1))))', DEPTH_OUT_OF_BOUNDS);
 
 wasmFailValidateText('(module (func (br_if 1 (i32.const 0))))', DEPTH_OUT_OF_BOUNDS);
 wasmFailValidateText('(module (func (block (br_if 2 (i32.const 0)))))', DEPTH_OUT_OF_BOUNDS);
 wasmFailValidateText('(module (func (loop (br_if 2 (i32.const 0)))))', DEPTH_OUT_OF_BOUNDS);
 
 wasmFailValidateText(`(module (func (result i32)
-  (block
-    (if
-      (br 0)
-      (i32.const 0)
-      (i32.const 2)
-    )
-  )
-) (export "" 0))`, /non-fallthrough instruction must be followed by end or else/);
+  block
+    br 0
+    if
+      i32.const 0
+      i32.const 2
+    end
+  end
+) (export "" 0))`, unusedValuesError);
 
-wasmFailValidateText(`(module (func (result i32)
-  (block
-    (if
-      (block i32 (br 1))
-      (i32.const 0)
-      (i32.const 2)
-    )
-  )
-) (export "" 0))`, mismatchError("void", "i32"));
-
-wasmFailValidateText(`(module (func (block $out (br_if $out (br 0)))) (export "run" 0))`, /non-fallthrough instruction must be followed by end or else/);
-wasmFullPass(`(module (func (block $out (br_if $out (block i32 (br 1))))) (export "run" 0))`, undefined);
+wasmFullPass(`(module (func (block $out (br_if $out (br 0)))) (export "run" 0))`, undefined);
 
 wasmFullPass('(module (func (br 0)) (export "run" 0))', undefined);
 wasmFullPass('(module (func (block (br 0))) (export "run" 0))', undefined);
 wasmFullPass('(module (func (block $l (br $l))) (export "run" 0))', undefined);
 
 wasmFullPass('(module (func (block (block (br 1)))) (export "run" 0))', undefined);
 wasmFullPass('(module (func (block $l (block (br $l)))) (export "run" 0))', undefined);
 
 wasmFullPass('(module (func (block $l (block $m (br $l)))) (export "run" 0))', undefined);
 wasmFullPass('(module (func (block $l (block $m (br $m)))) (export "run" 0))', undefined);
 
-wasmFailValidateText(`(module (func (result i32)
+wasmFullPass(`(module (func (result i32)
   (block
     (br 0)
     (return (i32.const 0))
   )
   (return (i32.const 1))
-) (export "run" 0))`, /non-fallthrough instruction must be followed by end or else/);
+) (export "run" 0))`, 1);
 
 wasmFullPass(`(module (func (result i32)
   (block
-    (block (br 1))
-    (return (i32.const 0))
-  )
-  (return (i32.const 1))
-) (export "run" 0))`, 1);
-
-wasmFailValidateText(`(module (func (result i32)
-  (block
     (block
       (br 0)
       (return (i32.const 0))
     )
     (return (i32.const 1))
   )
   (return (i32.const 2))
-) (export "run" 0))`, /non-fallthrough instruction must be followed by end or else/);
-
-wasmFullPass(`(module (func (result i32)
-  (block
-    (block
-      (block (br 1))
-      (return (i32.const 0))
-    )
-    (return (i32.const 1))
-  )
-  (return (i32.const 2))
 ) (export "run" 0))`, 1);
 
-wasmFailValidateText(`(module (func (result i32)
-  (block $outer
-    (block $inner
-      (br $inner)
-      (return (i32.const 0))
-    )
-    (return (i32.const 1))
-  )
-  (return (i32.const 2))
-) (export "run" 0))`, /non-fallthrough instruction must be followed by end or else/);
-
 wasmFullPass(`(module (func (result i32)
   (block $outer
     (block $inner
-      (block (br $inner))
+      (br $inner)
       (return (i32.const 0))
     )
     (return (i32.const 1))
   )
   (return (i32.const 2))
 ) (export "run" 0))`, 1);
 
 var notcalled = false;
 var called = false;
 var imports = {"": {
     notcalled() {notcalled = true},
     called() {called = true}
 }};
-wasmFailValidateText(`(module
+wasmFullPass(`(module
 (import "" "notcalled")
 (import "" "called")
 (func
   (block
     (return (br 0))
     (call 0)
   )
   (call 1)
-) (export "run" 2))`, /non-fallthrough instruction must be followed by end or else/);
-wasmFullPass(`(module
-(import "" "notcalled")
-(import "" "called")
-(func
-  (block
-    (block (return (block (br 2))))
-    (call 0)
-  )
-  (call 1)
 ) (export "run" 2))`, undefined, imports);
 assertEq(notcalled, false);
 assertEq(called, true);
 
-wasmFailValidateText(`(module (func
+wasmFullPass(`(module (func
   (block
     (i32.add
       (i32.const 0)
       (return (br 0))
     )
-  )
-  (return)
-) (export "run" 0))`, /non-fallthrough instruction must be followed by end or else/);
-
-/* TODO: This triggers a bug in BinaryToAST. */
-/*
-wasmFullPass(`(module (func
-  (block
-    (i32.add
-      (i32.const 0)
-      (block i32 (return (block (br 2))))
-    )
+    drop
   )
   (return)
 ) (export "run" 0))`, undefined);
-*/
 
 wasmFullPass(`(module (func (result i32)
   (block
     (if
       (i32.const 1)
       (br 0)
       (return (i32.const 0))
     )
@@ -368,31 +304,29 @@ var isNonZero = wasmEvalText(`(module (f
 ) (export "" 0))`).exports[""];
 
 assertEq(isNonZero(0), 0);
 assertEq(isNonZero(1), 1);
 assertEq(isNonZero(-1), 1);
 
 // branches with values
 // br/br_if and block
-wasmFailValidateText('(module (func (result i32) (br 0)))', /popping value from empty stack/);
+wasmFailValidateText('(module (func (result i32) (br 0)))', emptyStackError);
 wasmFailValidateText('(module (func (result i32) (br 0 (f32.const 42))))', mismatchError("f32", "i32"));
-wasmFailValidateText('(module (func (result i32) (block (br 0))))', mismatchError("void", "i32"));
+wasmFailValidateText('(module (func (result i32) (block (br 0))))', emptyStackError);
 wasmFailValidateText('(module (func (result i32) (block f32 (br 0 (f32.const 42)))))', mismatchError("f32", "i32"));
 
 wasmFailValidateText(`(module (func (result i32) (param i32) (block (if i32 (get_local 0) (br 0 (i32.const 42))))) (export "" 0))`, /if without else with a result value/);
 wasmFailValidateText(`(module (func (result i32) (param i32) (block i32 (if (get_local 0) (drop (i32.const 42))) (br 0 (f32.const 42)))) (export "" 0))`, mismatchError("f32", "i32"));
 
-wasmFailValidateText('(module (func (result i32) (br 0 (i32.const 42)) (i32.const 13)) (export "run" 0))', /non-fallthrough instruction must be followed by end or else/);
-wasmFailValidateText('(module (func (result i32) (block i32 (br 0 (i32.const 42)) (i32.const 13))) (export "run" 0))', /non-fallthrough instruction must be followed by end or else/);
-wasmFullPass('(module (func (result i32) (block (br 1 (i32.const 42))) (i32.const 13)) (export "run" 0))', 42);
-wasmFullPass('(module (func (result i32) (block i32 (block (br 1 (i32.const 42))) (i32.const 13))) (export "run" 0))', 42);
+wasmFullPass('(module (func (result i32) (br 0 (i32.const 42)) (i32.const 13)) (export "run" 0))', 42);
+wasmFullPass('(module (func (result i32) (block i32 (br 0 (i32.const 42)) (i32.const 13))) (export "run" 0))', 42);
 
-wasmFailValidateText('(module (func) (func (block i32 (br 0 (call 0)) (i32.const 13))) (export "" 0))', /popping value from empty stack/);
-wasmFailValidateText('(module (func) (func (block i32 (br_if 0 (call 0) (i32.const 1)) (i32.const 13))) (export "" 0))', /popping value from empty stack/);
+wasmFailValidateText('(module (func) (func (block i32 (br 0 (call 0)) (i32.const 13))) (export "" 0))', emptyStackError);
+wasmFailValidateText('(module (func) (func (block i32 (br_if 0 (call 0) (i32.const 1)) (i32.const 13))) (export "" 0))', emptyStackError);
 
 var f = wasmEvalText(`(module (func (result i32) (param i32) (block i32 (if (get_local 0) (drop (i32.const 42))) (i32.const 43))) (export "" 0))`).exports[""];
 assertEq(f(0), 43);
 assertEq(f(1), 43);
 
 wasmFailValidateText(`(module (func (result i32) (param i32) (block i32 (if i32 (get_local 0) (br 0 (i32.const 42))) (i32.const 43))) (export "" 0))`, /if without else with a result value/);
 
 var f = wasmEvalText(`(module (func (result i32) (param i32) (block i32 (if (get_local 0) (br 1 (i32.const 42))) (i32.const 43))) (export "" 0))`).exports[""];
@@ -438,22 +372,19 @@ assertEq(f(0), 0);
 assertEq(f(1), 100);
 
 wasmFailValidateText(`(module (func (param i32) (result i32) (i32.add (i32.const 1) (block (br_if 0 (i32.const 99) (get_local 0)) (i32.const -1)))) (export "" 0))`, /unused values not explicitly dropped by end of block/);
 
 var f = wasmEvalText(`(module (func (param i32) (result i32) (i32.add (i32.const 1) (block i32 (drop (br_if 0 (i32.const 99) (get_local 0))) (i32.const -1)))) (export "" 0))`).exports[""];
 assertEq(f(0), 0);
 assertEq(f(1), 100);
 
-wasmFailValidateText(`(module (func (result i32) (block i32 (br 0 (return (i32.const 42))) (i32.const 0))) (export "run" 0))`, /non-fallthrough instruction must be followed by end or else/);
-wasmFailValidateText(`(module (func (result i32) (block i32 (return (br 0 (i32.const 42))))) (export "run" 0))`, /non-fallthrough instruction must be followed by end or else/);
-wasmFailValidateText(`(module (func (result i32) (block i32 (return (br 0 (i32.const 42))) (i32.const 0))) (export "run" 0))`, /non-fallthrough instruction must be followed by end or else/);
-wasmFullPass(`(module (func (result i32) (block i32 (block (br 1 (block (return (i32.const 42))))) (i32.const 0))) (export "run" 0))`, 42);
-wasmFullPass(`(module (func (result i32) (block i32 (return (block (br 1 (i32.const 42)))))) (export "run" 0))`, 42);
-wasmFullPass(`(module (func (result i32) (block i32 (block (return (block (br 2 (i32.const 42))))) (i32.const 0))) (export "run" 0))`, 42);
+wasmFullPass(`(module (func (result i32) (block i32 (br 0 (return (i32.const 42))) (i32.const 0))) (export "run" 0))`, 42);
+wasmFullPass(`(module (func (result i32) (block i32 (return (br 0 (i32.const 42))))) (export "run" 0))`, 42);
+wasmFullPass(`(module (func (result i32) (block i32 (return (br 0 (i32.const 42))) (i32.const 0))) (export "run" 0))`, 42);
 
 wasmFullPass(`(module (func (result f32) (drop (block i32 (br 0 (i32.const 0)))) (block f32 (br 0 (f32.const 42)))) (export "run" 0))`, 42);
 
 var called = 0;
 var imports = {
     sideEffects: {
         ifTrue(x) {assertEq(x, 13); called++;},
         ifFalse(x) {assertEq(x, 37); called--;}
@@ -479,17 +410,17 @@ var f = wasmEvalText(`(module
 (export "" 2))`, imports).exports[""];
 assertEq(f(0), 42);
 assertEq(called, -1);
 assertEq(f(1), 42);
 assertEq(called, 0);
 
 // br/br_if and loop
 wasmFullPass(`(module (func (param i32) (result i32) (loop $out $in i32 (br $out (get_local 0)))) (export "run" 0))`, 1, {}, 1);
-wasmFullPass(`(module (func (param i32) (result i32) (loop $in (br 1 (get_local 0)))) (export "run" 0))`, 1, {}, 1);
+wasmFullPass(`(module (func (param i32) (result i32) (loop $in i32 (br 1 (get_local 0)))) (export "run" 0))`, 1, {}, 1);
 wasmFullPass(`(module (func (param i32) (result i32) (block $out i32 (loop $in i32 (br $out (get_local 0))))) (export "run" 0))`, 1, {}, 1);
 
 wasmFailValidateText(`(module (func (param i32) (result i32)
   (loop $out $in
    (if (get_local 0) (br $in (i32.const 1)))
    (if (get_local 0) (br $in (f32.const 2)))
    (if (get_local 0) (br $in (f64.const 3)))
    (if (get_local 0) (br $in))
@@ -497,31 +428,34 @@ wasmFailValidateText(`(module (func (par
   )
 ) (export "" 0))`, /unused values not explicitly dropped by end of block/);
 
 wasmFullPass(`(module
  (func
   (result i32)
   (local i32)
   (block $out i32
-  (loop $in
-   (set_local 0 (i32.add (get_local 0) (i32.const 1)))
-   (if (i32.ge_s (get_local 0) (i32.const 7)) (br $out (get_local 0)))
-   (br $in)
-  )
+    (loop $in i32
+     (set_local 0 (i32.add (get_local 0) (i32.const 1)))
+     (if
+        (i32.ge_s (get_local 0) (i32.const 7))
+        (br $out (get_local 0))
+     )
+     (br $in)
+    )
   )
  )
 (export "run" 0))`, 7);
 
 wasmFullPass(`(module
  (func
   (result i32)
   (local i32)
   (block $out i32
-   (loop $in
+   (loop $in i32
     (set_local 0 (i32.add (get_local 0) (i32.const 1)))
     (br_if $out (get_local 0) (i32.ge_s (get_local 0) (i32.const 7)))
     (br $in)
    )
   )
  )
 (export "run" 0))`, 7);
 
@@ -702,63 +636,36 @@ wasmFailValidateText('(module (func (br_
 wasmFailValidateText('(module (func (br_table 0 1 (i32.const 0))))', DEPTH_OUT_OF_BOUNDS);
 wasmFailValidateText('(module (func (block (br_table 2 0 (i32.const 0)))))', DEPTH_OUT_OF_BOUNDS);
 wasmFailValidateText('(module (func (block (br_table 0 2 (i32.const 0)))))', DEPTH_OUT_OF_BOUNDS);
 wasmFailValidateText('(module (func (block (br_table 0 (f32.const 0)))))', mismatchError("f32", "i32"));
 wasmFailValidateText('(module (func (loop (br_table 2 0 (i32.const 0)))))', DEPTH_OUT_OF_BOUNDS);
 wasmFailValidateText('(module (func (loop (br_table 0 2 (i32.const 0)))))', DEPTH_OUT_OF_BOUNDS);
 wasmFailValidateText('(module (func (loop (br_table 0 (f32.const 0)))))', mismatchError("f32", "i32"));
 
-wasmFailValidateText(`(module (func (result i32) (param i32)
+wasmFullPass(`(module (func (result i32) (param i32)
   (block $default
    (br_table $default (get_local 0))
    (return (i32.const 0))
   )
   (return (i32.const 1))
-) (export "run" 0))`, /non-fallthrough instruction must be followed by end or else/);
+) (export "run" 0))`, 1);
 
 wasmFullPass(`(module (func (result i32) (param i32)
   (block $default
-   (block (br_table $default (get_local 0)))
-   (return (i32.const 0))
-  )
-  (return (i32.const 1))
-) (export "run" 0))`, 1);
-
-wasmFailValidateText(`(module (func (result i32) (param i32)
-  (block $default
    (br_table $default (return (i32.const 1)))
    (return (i32.const 0))
   )
   (return (i32.const 2))
-) (export "run" 0))`, /non-fallthrough instruction must be followed by end or else/);
-
-wasmFullPass(`(module (func (result i32) (param i32)
-  (block $default
-   (block (br_table $default (block i32 (return (i32.const 1)))))
-   (return (i32.const 0))
-  )
-  (return (i32.const 2))
 ) (export "run" 0))`, 1);
 
-wasmFailValidateText(`(module (func (result i32) (param i32)
-  (block $outer
-   (block $inner
-    (br_table $inner (get_local 0))
-    (return (i32.const 0))
-   )
-   (return (i32.const 1))
-  )
-  (return (i32.const 2))
-) (export "run" 0))`, /non-fallthrough instruction must be followed by end or else/);
-
 wasmFullPass(`(module (func (result i32) (param i32)
   (block $outer
    (block $inner
-    (block (br_table $inner (get_local 0)))
+    (br_table $inner (get_local 0))
     (return (i32.const 0))
    )
    (return (i32.const 1))
   )
   (return (i32.const 2))
 ) (export "run" 0))`, 1);
 
 var f = wasmEvalText(`(module (func (result i32) (param i32)
@@ -779,17 +686,17 @@ var f = wasmEvalText(`(module (func (res
 assertEq(f(-2), -1);
 assertEq(f(-1), -1);
 assertEq(f(0), 0);
 assertEq(f(1), 0);
 assertEq(f(2), 2);
 assertEq(f(3), -1);
 
 // br_table with values
-wasmFailValidateText('(module (func (result i32) (block (br_table 0 (i32.const 0)))))', mismatchError("void", "i32"));
+wasmFailValidateText('(module (func (result i32) (block i32 (br_table 0 (i32.const 0)))))', emptyStackError);
 wasmFailValidateText('(module (func (result i32) (block i32 (br_table 0 (f32.const 0) (i32.const 0)))))', mismatchError("f32", "i32"));
 
 wasmFailValidateText(`(module
  (func
   (result i32)
   (block $outer f32
    (block $inner f32
     (br_table $outer $inner (f32.const 13.37) (i32.const 1))
@@ -822,16 +729,12 @@ assertEq(f(2), 9);
 assertEq(f(3), 11);
 assertEq(f(4), 13);
 
 // ----------------------------------------------------------------------------
 // unreachable
 
 const UNREACHABLE = /unreachable/;
 assertErrorMessage(wasmEvalText(`(module (func (unreachable)) (export "" 0))`).exports[""], RuntimeError, UNREACHABLE);
-wasmFailValidateText('(module (func (if (unreachable) (i32.const 0))) (export "" 0))', /non-fallthrough instruction must be followed by end or else/);
-wasmFailValidateText('(module (func (block (br_if 0 (unreachable)))) (export "" 0))', /non-fallthrough instruction must be followed by end or else/);
-wasmFailValidateText('(module (func (block (br_table 0 (unreachable)))) (export "" 0))', /non-fallthrough instruction must be followed by end or else/);
-wasmFailValidateText('(module (func (result i32) (i32.add (i32.const 0) (unreachable))) (export "" 0))', /non-fallthrough instruction must be followed by end or else/);
-assertErrorMessage(wasmEvalText(`(module (func (if (block i32 (unreachable)) (i32.const 0))) (export "" 0))`).exports[""], RuntimeError, UNREACHABLE);
-assertErrorMessage(wasmEvalText(`(module (func (block (br_if 0 (block i32 (unreachable))))) (export "" 0))`).exports[""], RuntimeError, UNREACHABLE);
-assertErrorMessage(wasmEvalText(`(module (func (block (br_table 0 (block i32 (unreachable))))) (export "" 0))`).exports[""], RuntimeError, UNREACHABLE);
-assertErrorMessage(wasmEvalText(`(module (func (result i32) (i32.add (i32.const 0) (block i32 (unreachable)))) (export "" 0))`).exports[""], RuntimeError, UNREACHABLE);
+assertErrorMessage(wasmEvalText(`(module (func (if (unreachable) (nop))) (export "" 0))`).exports[""], RuntimeError, UNREACHABLE);
+assertErrorMessage(wasmEvalText(`(module (func (block (br_if 0 (unreachable)))) (export "" 0))`).exports[""], RuntimeError, UNREACHABLE);
+assertErrorMessage(wasmEvalText(`(module (func (block (br_table 0 (unreachable)))) (export "" 0))`).exports[""], RuntimeError, UNREACHABLE);
+assertErrorMessage(wasmEvalText(`(module (func (result i32) (i32.add (i32.const 0) (unreachable))) (export "" 0))`).exports[""], RuntimeError, UNREACHABLE);
--- a/js/src/jit-test/tests/wasm/full-cycle.js
+++ b/js/src/jit-test/tests/wasm/full-cycle.js
@@ -1,46 +1,27 @@
 load(libdir + "wasm.js");
 
 wasmFullPass(`(module
     (func $test (result i32) (param i32) (param i32) (i32.add (get_local 0) (get_local 1)))
     (func $run (result i32) (call $test (i32.const 1) (i32.const ${Math.pow(2, 31) - 1})))
     (export "run" $run)
 )`, -Math.pow(2, 31));
 
-wasmFailValidateText(`(module
+wasmFullPass(`(module
     (func (result i32)
         i32.const 1
         i32.const 42
         i32.add
         return
         unreachable
         i32.const 0
         call 3
         i32.const 42
-        f32.add
-    )
-    (func) (func) (func)
-(export "run" 0))`, /non-fallthrough instruction must be followed by end or else/);
-
-wasmFullPass(`(module
-    (func (result i32)
-        block
-        i32.const 1
-        i32.const 42
         i32.add
-        return
-        end
-        block
-        unreachable
-        end
-        i32.const 0
-        call 3
-        i32.const 42
-        f32.add
     )
     (func) (func) (func)
 (export "run" 0))`, 43);
 
 // Global section.
 wasmFullPass(`(module
  (import $imported "globals" "x" (global i32))
  (global $mut_local (mut i32) (i32.const 0))
--- a/js/src/jit-test/tests/wasm/regress/baseline-joinreg.js
+++ b/js/src/jit-test/tests/wasm/regress/baseline-joinreg.js
@@ -9,10 +9,10 @@ load(libdir + "wasm.js");
 // This test is white-box: it depends on the floating join reg being among the first
 // floating registers to be allocated.
 
 wasmEvalText(`
 (module
  (func $run
   (drop (block f64
    (drop (br_if 0 (f64.const 1) (f64.eq (f64.const 1) (f64.const 0))))
-   (br 0 (f64.const 2)))))
+   (drop (br 0 (f64.const 2))))))
  (export "run" $run))`);
--- a/js/src/jit-test/tests/wasm/regress/misc-control-flow.js
+++ b/js/src/jit-test/tests/wasm/regress/misc-control-flow.js
@@ -11,32 +11,27 @@ wasmFailValidateText(`(module
      (loop (if (i32.const 0) (br 0)) (get_local 0)))
    (export "" 0)
 )`, /unused values not explicitly dropped by end of block/);
 
 wasmFailValidateText(`(module
    (func (result i32) (param i32)
      (loop (if (i32.const 0) (br 0)) (drop (get_local 0))))
    (export "" 0)
-)`, mismatchError("void", "i32"));
+)`, emptyStackError);
 
 assertEq(wasmEvalText(`(module
    (func (result i32) (param i32)
      (loop (if (i32.const 0) (br 0))) (get_local 0))
    (export "" 0)
 )`).exports[""](42), 42);
 
-wasmFailValidateText(`(module (func $func$0
+wasmEvalText(`(module (func $func$0
       (block (if (i32.const 1) (loop (br_table 0 (br 0)))))
   )
-)`, /non-fallthrough instruction must be followed by end or else/);
-
-wasmEvalText(`(module (func $func$0
-      (block (if (i32.const 1) (loop (br_table 0 (block i32 (br 1))))))
-  )
 )`);
 
 wasmEvalText(`(module (func
       (loop $out $in (br_table $out $out $in (i32.const 0)))
   )
 )`);
 
 wasmEvalText(`(module (func (result i32)
@@ -53,69 +48,39 @@ wasmEvalText(`(module (func (result i32)
       (i32.const 2)
     )
     (i32.const 3)
     (i32.const 4)
   )
 ))
 `);
 
-wasmFailValidateText(`(module
+wasmEvalText(`(module
   (func (result i32) (param i32) (param i32) (i32.const 0))
   (func (result i32)
    (call 0 (i32.const 1) (call 0 (i32.const 2) (i32.const 3)))
    (call 0 (unreachable) (i32.const 4))
   )
-)`, /non-fallthrough instruction must be followed by end or else/);
-
-wasmEvalText(`(module
-  (func (result i32) (param i32) (param i32) (i32.const 0))
-  (func (result i32)
-   (call 0 (i32.const 1) (call 0 (i32.const 2) (i32.const 3)))
-   (call 0 (block i32 (unreachable)) (i32.const 4))
-  )
 )`);
 
-wasmFailValidateText(`
-(module
-
- (func
-  (param i32) (param i32) (param i32) (param i32)
-  (result i32)
-  (i32.const 0)
- )
-
- (func (result i32)
-  (call 0
-   (i32.const 42)
-   (i32.const 53)
-   (call 0 (i32.const 100) (i32.const 13) (i32.const 37) (i32.const 128))
-   (return (i32.const 42))
-  )
- )
-
- (export "" 1)
-)
-`, /non-fallthrough instruction must be followed by end or else/);
-
 wasmEvalText(`
 (module
 
  (func
   (param i32) (param i32) (param i32) (param i32)
   (result i32)
   (i32.const 0)
  )
 
  (func (result i32)
   (call 0
    (i32.const 42)
    (i32.const 53)
    (call 0 (i32.const 100) (i32.const 13) (i32.const 37) (i32.const 128))
-   (block i32 (return (i32.const 42)))
+   (return (i32.const 42))
   )
  )
 
  (export "" 1)
 )
 `).exports[""]();
 
 wasmEvalText(`
@@ -142,48 +107,31 @@ wasmEvalText(`
         },
         two(x, y) {
             assertEq(x, 43);
             assertEq(y, 10);
         }
     }
 }).exports.foo();
 
-wasmFailValidateText(`(module (func
- (return)
+assertEq(wasmEvalText(`(module (func
+ return
  (select
-  (loop (i32.const 1))
-  (loop (i32.const 2))
-  (i32.const 3)
- )
-) (export "" 0))`, /non-fallthrough instruction must be followed by end or else/);
-
-assertEq(wasmEvalText(`(module (func
- (block (return))
- (select
-  (loop (i32.const 1))
-  (loop (i32.const 2))
+  (loop i32 (i32.const 1))
+  (loop i32 (i32.const 2))
   (i32.const 3)
  )
+ drop
 ) (export "" 0))`).exports[""](), undefined);
 
-wasmFailValidateText(`(module (func (result i32)
+wasmEvalText(`(module (func (result i32)
  (return (i32.const 0))
  (select
-  (loop (i32.const 1))
-  (loop (i32.const 2))
-  (i32.const 3)
- )
-))`, /non-fallthrough instruction must be followed by end or else/);
-
-wasmEvalText(`(module (func (result i32)
- (block (return (i32.const 0)))
- (select
-  (loop (i32.const 1))
-  (loop (i32.const 2))
+  (loop i32 (i32.const 1))
+  (loop i32 (i32.const 2))
   (i32.const 3)
  )
 ))`);
 
 wasmEvalText(`(module (func
  (block $return
   (block $beforeReturn
    (loop $out $in
@@ -251,27 +199,27 @@ wasmEvalText(`
 wasmFailValidateText(`
 (module
     (func (result i32)
       (loop
         (i32.const 0)
         (br_table 1 0 (i32.const 15))
       )
     )
-)`, mismatchError("i32", "void"));
+)`, /br_table targets must all have the same value type/);
 
 wasmFailValidateText(`
 (module
   (func (result i32)
     (loop i32
       (i32.const 0)
       (br_table 1 0 (i32.const 15))
     )
   )
-)`, mismatchError("i32", "void"));
+)`, /br_table targets must all have the same value type/);
 
 wasmValidateText(`
 (module
     (func
         (loop
           (i32.const 0)
           (br_table 1 0 (i32.const 15))
         )
--- a/js/src/jit-test/tests/wasm/regress/movable-traps.js
+++ b/js/src/jit-test/tests/wasm/regress/movable-traps.js
@@ -20,17 +20,17 @@ let bodies = [
     i32.trunc_s/f32
     `
 ];
 
 for (let body of bodies) {
     wasmFullPass(`
     (module
         (func $f (param $x i32) (result i32)
-            loop $top
+            loop $top i32
                 get_local $x
                 if
                     get_local $x
                     br 2
                 end
                 ${body}
                 br $top
             end
--- a/js/src/jit-test/tests/wasm/regress/select-any.js
+++ b/js/src/jit-test/tests/wasm/regress/select-any.js
@@ -1,45 +1,43 @@
 load(libdir + "wasm.js");
 
 // Bug 1280921
 
-wasmFailValidateText(
-`(module
-  (type $type0 (func))
-  (func $func0
-   (select (unreachable) (return (nop)) (loop (i32.const 1))))
-  (export "" 0))`, /non-fallthrough instruction must be followed by end or else/);
-
-wasmFailValidateText(
+var m1 = wasmEvalText(
 `(module
   (type $type0 (func))
   (func $func0
-   (select (i32.const 26) (unreachable) (i32.const 3)))
-  (export "" 0))`, /non-fallthrough instruction must be followed by end or else/);
-
-var m3 = wasmEvalText(
-`(module
-  (type $type0 (func))
-  (func $func0
-   (select (block i32 (unreachable)) (block i32 (return (nop))) (loop (i32.const 1))))
+   (select
+     (unreachable)
+     (return (nop))
+     (loop i32 (i32.const 1))
+   )
+   drop
+  )
   (export "" 0))`).exports[""];
 
 try {
-    m3();
+    m1();
 } catch (e) {
     if (!(e instanceof Error && e.message.match(/unreachable executed/)))
 	throw e;
 }
 
-var m4 = wasmEvalText(
+var m2 = wasmEvalText(
 `(module
   (type $type0 (func))
   (func $func0
-   (select (i32.const 26) (block i32 (unreachable)) (i32.const 3)))
+   (select
+    (i32.const 26)
+    (unreachable)
+    (i32.const 3)
+   )
+   drop
+  )
   (export "" 0))`).exports[""];
 
 try {
-    m4();
+    m2();
 } catch (e) {
     if (!(e instanceof Error && e.message.match(/unreachable executed/)))
 	throw e;
 }
--- a/js/src/jit-test/tests/wasm/spec/block.wast.js
+++ b/js/src/jit-test/tests/wasm/spec/block.wast.js
@@ -1,3 +1,1 @@
-// TODO: This spec test has instructions between br/etc. and end/etc.
-quit();
 var importedArgs = ['block.wast']; load(scriptdir + '../wast.js');
--- a/js/src/jit-test/tests/wasm/spec/br.wast.js
+++ b/js/src/jit-test/tests/wasm/spec/br.wast.js
@@ -1,3 +1,1 @@
-// TODO: This spec test has instructions between br/etc. and end/etc.
-quit();
 var importedArgs = ['br.wast']; load(scriptdir + '../wast.js');
--- a/js/src/jit-test/tests/wasm/spec/br_if.wast.js
+++ b/js/src/jit-test/tests/wasm/spec/br_if.wast.js
@@ -1,3 +1,1 @@
-// TODO: This spec test has instructions between br/etc. and end/etc.
-quit();
 var importedArgs = ['br_if.wast']; load(scriptdir + '../wast.js');
--- a/js/src/jit-test/tests/wasm/spec/br_table.wast.js
+++ b/js/src/jit-test/tests/wasm/spec/br_table.wast.js
@@ -1,3 +1,1 @@
-// TODO: This spec test has instructions between br/etc. and end/etc.
-quit();
 var importedArgs = ['br_table.wast']; load(scriptdir + '../wast.js');
--- a/js/src/jit-test/tests/wasm/spec/func.wast.js
+++ b/js/src/jit-test/tests/wasm/spec/func.wast.js
@@ -1,3 +1,1 @@
-// TODO: This spec test has instructions between br/etc. and end/etc.
-quit();
 var importedArgs = ['func.wast']; load(scriptdir + '../wast.js');
--- a/js/src/jit-test/tests/wasm/spec/labels.wast.js
+++ b/js/src/jit-test/tests/wasm/spec/labels.wast.js
@@ -1,3 +1,1 @@
-// TODO: This spec test has instructions between br/etc. and end/etc.
-quit();
 var importedArgs = ['labels.wast']; load(scriptdir + '../wast.js');
--- a/js/src/jit-test/tests/wasm/spec/loop.wast.js
+++ b/js/src/jit-test/tests/wasm/spec/loop.wast.js
@@ -1,3 +1,1 @@
-// TODO: This spec test has instructions between br/etc. and end/etc.
-quit();
 var importedArgs = ['loop.wast']; load(scriptdir + '../wast.js');
--- a/js/src/jit-test/tests/wasm/spec/return.wast.js
+++ b/js/src/jit-test/tests/wasm/spec/return.wast.js
@@ -1,3 +1,1 @@
-// TODO: This spec test has instructions between br/etc. and end/etc.
-quit();
 var importedArgs = ['return.wast']; load(scriptdir + '../wast.js');
--- a/js/src/jit-test/tests/wasm/spec/select.wast.js
+++ b/js/src/jit-test/tests/wasm/spec/select.wast.js
@@ -1,3 +1,1 @@
-// TODO: This spec test has instructions between br/etc. and end/etc.
-quit();
 var importedArgs = ['select.wast']; load(scriptdir + '../wast.js');
--- a/js/src/jit-test/tests/wasm/spec/unreachable.wast.js
+++ b/js/src/jit-test/tests/wasm/spec/unreachable.wast.js
@@ -1,3 +1,1 @@
-// TODO: This spec test has instructions between br/etc. and end/etc.
-quit();
 var importedArgs = ['unreachable.wast']; load(scriptdir + '../wast.js');
--- a/js/src/jit-test/tests/wasm/spec/unwind.wast.js
+++ b/js/src/jit-test/tests/wasm/spec/unwind.wast.js
@@ -1,3 +1,1 @@
-// TODO: This spec test has instructions between br/etc. and end/etc.
-quit();
 var importedArgs = ['unwind.wast']; load(scriptdir + '../wast.js');
--- a/js/src/jit-test/tests/wasm/to-text-experimental.js
+++ b/js/src/jit-test/tests/wasm/to-text-experimental.js
@@ -57,17 +57,17 @@ wasmFailValidateText(
         ))
      )
      (i32.store16 (i32.const 8) (i32.const 128))
 
      (return (f64.const 0))
   )
   (export "test" 0)
   (memory 1 10)
-)`, /popping value from empty stack/);
+)`, emptyStackError);
 
 // function calls
 runTest(`
 (module
   (type $type1 (func (param i32) (result i32)))
   (import $import1 "mod" "test" (param f32) (result f32))
   (table anyfunc (elem $func1 $func2))
   (func $func1 (param i32) (param f32) (nop))
deleted file mode 100644
--- a/js/src/jit-test/tests/wasm/unreachable.js
+++ /dev/null
@@ -1,55 +0,0 @@
-load(libdir + "wasm.js");
-
-// In unreachable code, the current design is that validation is disabled,
-// meaning we have to have a special mode in the decoder for decoding code
-// that won't actually run.
-
-wasmFailValidateText(`(module
-   (func (result i32)
-     (return (i32.const 42))
-     (i32.add (f64.const 1.0) (f32.const 0.0))
-     (return (f64.const 2.0))
-     (if (f32.const 3.0) (i64.const 2) (i32.const 1))
-     (select (f64.const -5.0) (f32.const 2.3) (f64.const 8.9))
-   )
-   (export "run" 0)
-)`, /non-fallthrough instruction must be followed by end or else/);
-
-wasmFailValidateText(`(module
-   (func (result i32) (param i32)
-     (block
-        (br_if 1 (i32.const 41) (get_local 0))
-        (br 1 (i32.const 42))
-     )
-     (i32.add (f32.const 0.0) (f64.const 1.0))
-     (return (f64.const 2.0))
-     (if (f32.const 3.0) (i64.const 2) (i32.const 1))
-     (select (f64.const -5.0) (f32.const 2.3) (f64.const 8.9))
-   )
-   (export "run" 0)
-)`, /non-fallthrough instruction must be followed by end or else/);
-
-wasmFullPass(`(module
-   (func (result i32)
-     (block (return (i32.const 42)))
-     (i32.add (f64.const 1.0) (f32.const 0.0))
-     (block (return (f64.const 2.0)))
-     (if (f32.const 3.0) (i64.const 2) (i32.const 1))
-     (select (f64.const -5.0) (f32.const 2.3) (f64.const 8.9))
-   )
-   (export "run" 0)
-)`, 42);
-
-wasmFullPass(`(module
-   (func (result i32) (param i32)
-     (block
-        (br_if 1 (i32.const 41) (get_local 0))
-        (br 1 (i32.const 42))
-     )
-     (i32.add (f32.const 0.0) (f64.const 1.0))
-     (block (return (f64.const 2.0)))
-     (if (f32.const 3.0) (i64.const 2) (i32.const 1))
-     (select (f64.const -5.0) (f32.const 2.3) (f64.const 8.9))
-   )
-   (export "run" 0)
-)`, 42, {}, 0);
--- a/js/src/jit-test/tests/wasm/wast.js
+++ b/js/src/jit-test/tests/wasm/wast.js
@@ -392,17 +392,17 @@ function exec(e) {
 
             i32[0] = res.nan_low;
             i32[1] = res.nan_high;
             assert(Number.isNaN(f64[0]) || Number.isNaN(f32[0]), "assert_return_nan test failed.");
         }
         return;
     }
 
-    if (exprName === "assert_invalid" || exprName === "assert_malformed") {
+    if (exprName === "assert_invalid" || exprName === "assert_malformed" || exprName == "assert_soft_invalid") {
         let moduleText = e.list[1].toString();
         let errMsg = e.list[2];
         if (errMsg) {
             assert(errMsg.quoted, "assert_invalid/malformed second argument must be a string");
             errMsg.quoted = false;
         }
 
         // assert_invalid tests both the decoder *and* the parser itself.
@@ -422,33 +422,16 @@ function exec(e) {
         } catch (e) {
             caught = true;
             debug("Caught", e.toString(), ", expected:", errMsg);
         }
         assertEq(caught, true);
         return;
     }
 
-    if (exprName === "assert_soft_invalid") {
-        let moduleText = e.list[1].toString();
-        let errMsg = e.list[2];
-        if (errMsg) {
-            assert(errMsg.quoted, "assert_soft_invalid second argument must be a string");
-            errMsg.quoted = false;
-        }
-
-        try {
-            new WebAssembly.Module(wasmTextToBinary(moduleText));
-        } catch(e) {
-            debug('assert_soft_invalid caught:\nExpected:', errMsg, '\nActual:', e.toString());
-        }
-
-        return;
-    }
-
     if (exprName === 'assert_trap') {
         let caught = false;
         let errMsg = e.list[2];
         assert(errMsg.quoted, "assert_trap second argument must be a string");
         errMsg.quoted = false;
         try {
             exec(e.list[1]);
         } catch(err) {
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -2331,25 +2331,25 @@ class MOZ_STACK_CLASS ModuleValidator
         if (SimdOperationNameMap::Ptr p = standardLibrarySimdOpNames_.lookup(name)) {
             *op = p->value();
             return true;
         }
         return false;
     }
 
     bool startFunctionBodies() {
+        if (!arrayViews_.empty())
+            mg_.initMemoryUsage(atomicsPresent_ ? MemoryUsage::Shared : MemoryUsage::Unshared);
+
         return mg_.startFuncDefs();
     }
     bool finishFunctionBodies() {
         return mg_.finishFuncDefs();
     }
     SharedModule finish() {
-        if (!arrayViews_.empty())
-            mg_.initMemoryUsage(atomicsPresent_ ? MemoryUsage::Shared : MemoryUsage::Unshared);
-
         asmJSMetadata_->usesSimd = simdPresent_;
 
         MOZ_ASSERT(asmJSMetadata_->asmJSFuncNames.empty());
         for (const Func* func : functions_) {
             CacheableChars funcName = StringToNewUTF8CharsZ(cx_, *func->name());
             if (!funcName || !asmJSMetadata_->asmJSFuncNames.emplaceBack(Move(funcName)))
                 return nullptr;
         }
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -5627,24 +5627,22 @@ BaseCompiler::endIfThenElse(ExprType typ
 }
 
 bool
 BaseCompiler::emitEnd()
 {
     LabelKind kind;
     ExprType type;
     Nothing unused_value;
-
     if (!iter_.readEnd(&kind, &type, &unused_value))
         return false;
 
     switch (kind) {
       case LabelKind::Block: endBlock(type); break;
       case LabelKind::Loop:  endLoop(type); break;
-      case LabelKind::UnreachableThen:
       case LabelKind::Then:  endIfThen(); break;
       case LabelKind::Else:  endIfThenElse(type); break;
     }
 
     iter_.popEnd();
 
     return true;
 }
@@ -5702,76 +5700,61 @@ BaseCompiler::emitBrIf()
     emitBranchPerform(&b);
 
     return true;
 }
 
 bool
 BaseCompiler::emitBrTable()
 {
-    uint32_t tableLength;
-    ExprType type;
+    Uint32Vector depths;
+    uint32_t defaultDepth;
+    ExprType branchValueType;
     Nothing unused_value, unused_index;
-    if (!iter_.readBrTable(&tableLength, &type, &unused_value, &unused_index))
-        return false;
-
-    LabelVector stubs;
-    if (!stubs.reserve(tableLength+1))
-        return false;
-
-    Uint32Vector depths;
-    if (!depths.reserve(tableLength))
-        return false;
-
-    for (size_t i = 0; i < tableLength; ++i) {
-        uint32_t depth;
-        if (!iter_.readBrTableEntry(&type, &unused_value, &depth))
-            return false;
-        depths.infallibleAppend(depth);
-    }
-
-    uint32_t defaultDepth;
-    if (!iter_.readBrTableDefault(&type, &unused_value, &defaultDepth))
+    if (!iter_.readBrTable(&depths, &defaultDepth, &branchValueType, &unused_value, &unused_index))
         return false;
 
     if (deadCode_)
         return true;
 
     // Don't use joinReg for rc
-    maybeReserveJoinRegI(type);
+    maybeReserveJoinRegI(branchValueType);
 
     // Table switch value always on top.
     RegI32 rc = popI32();
 
-    maybeUnreserveJoinRegI(type);
-
-    AnyReg r = popJoinRegUnlessVoid(type);
+    maybeUnreserveJoinRegI(branchValueType);
+
+    AnyReg r = popJoinRegUnlessVoid(branchValueType);
 
     Label dispatchCode;
-    masm.branch32(Assembler::Below, rc, Imm32(tableLength), &dispatchCode);
+    masm.branch32(Assembler::Below, rc, Imm32(depths.length()), &dispatchCode);
 
     // This is the out-of-range stub.  rc is dead here but we don't need it.
 
     popStackBeforeBranch(controlItem(defaultDepth).framePushed);
     masm.jump(&controlItem(defaultDepth).label);
 
     // Emit stubs.  rc is dead in all of these but we don't need it.
     //
     // The labels in the vector are in the TempAllocator and will
     // be freed by and by.
     //
     // TODO / OPTIMIZE (Bug 1316804): Branch directly to the case code if we
     // can, don't emit an intermediate stub.
 
-    for (uint32_t i = 0; i < tableLength; i++) {
+    LabelVector stubs;
+    if (!stubs.reserve(depths.length()))
+        return false;
+
+    for (uint32_t depth : depths) {
         stubs.infallibleEmplaceBack(NonAssertingLabel());
         masm.bind(&stubs.back());
-        uint32_t k = depths[i];
-        popStackBeforeBranch(controlItem(k).framePushed);
-        masm.jump(&controlItem(k).label);
+        popStackBeforeBranch(controlItem(depth).framePushed);
+        masm.jump(&controlItem(depth).label);
     }
 
     // Emit table.
 
     Label theTable;
     jumpTable(stubs, &theTable);
 
     // Emit indirect jump.  rc is live here.
@@ -5852,41 +5835,32 @@ BaseCompiler::emitReturn()
 
     doReturn(func_.sig().ret(), PopStack(true));
     deadCode_ = true;
 
     return true;
 }
 
 bool
-BaseCompiler::emitCallArgs(const ValTypeVector& args, FunctionCall& baselineCall)
+BaseCompiler::emitCallArgs(const ValTypeVector& argTypes, FunctionCall& baselineCall)
 {
     MOZ_ASSERT(!deadCode_);
 
-    startCallArgs(baselineCall, stackArgAreaSize(args));
-
-    uint32_t numArgs = args.length();
-    for (size_t i = 0; i < numArgs; ++i) {
-        ValType argType = args[i];
-        Nothing arg_;
-        if (!iter_.readCallArg(argType, numArgs, i, &arg_))
-            return false;
-        Stk& arg = peek(numArgs - 1 - i);
-        passArg(baselineCall, argType, arg);
-    }
+    startCallArgs(baselineCall, stackArgAreaSize(argTypes));
+
+    uint32_t numArgs = argTypes.length();
+    for (size_t i = 0; i < numArgs; ++i)
+        passArg(baselineCall, argTypes[i], peek(numArgs - 1 - i));
 
     // Pass the TLS pointer as a hidden argument in WasmTlsReg.  Load
     // it directly out if its stack slot so we don't interfere with
     // the stk_.
     if (baselineCall.loadTlsBefore)
         loadFromFramePtr(WasmTlsReg, frameOffsetFromSlot(tlsSlot_, MIRType::Pointer));
 
-    if (!iter_.readCallArgsEnd(numArgs))
-        return false;
-
     return true;
 }
 
 void
 BaseCompiler::pushReturned(const FunctionCall& call, ExprType type)
 {
     switch (type) {
       case ExprType::Void:
@@ -5931,17 +5905,18 @@ BaseCompiler::pushReturned(const Functio
 // simpler.
 
 bool
 BaseCompiler::emitCall()
 {
     uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
 
     uint32_t funcIndex;
-    if (!iter_.readCall(&funcIndex))
+    BaseOpIter::ValueVector args_;
+    if (!iter_.readCall(&funcIndex, &args_))
         return false;
 
     if (deadCode_)
         return true;
 
     sync();
 
     const Sig& sig = *env_.funcSigs[funcIndex];
@@ -5951,19 +5926,16 @@ BaseCompiler::emitCall()
     size_t stackSpace = stackConsumed(numArgs);
 
     FunctionCall baselineCall(lineOrBytecode);
     beginCall(baselineCall, UseABI::Wasm, import ? InterModule::True : InterModule::False);
 
     if (!emitCallArgs(sig.args(), baselineCall))
         return false;
 
-    if (!iter_.readCallReturn(sig.ret()))
-        return false;
-
     if (import)
         callImport(env_.funcImportGlobalDataOffsets[funcIndex], baselineCall);
     else
         callDefinition(funcIndex, baselineCall);
 
     endCall(baselineCall, stackSpace);
 
     popValueStackBy(numArgs);
@@ -5976,21 +5948,22 @@ BaseCompiler::emitCall()
 
 bool
 BaseCompiler::emitCallIndirect(bool oldStyle)
 {
     uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
 
     uint32_t sigIndex;
     Nothing callee_;
+    BaseOpIter::ValueVector args_;
     if (oldStyle) {
-        if (!iter_.readOldCallIndirect(&sigIndex))
+        if (!iter_.readOldCallIndirect(&sigIndex, &callee_, &args_))
             return false;
     } else {
-        if (!iter_.readCallIndirect(&sigIndex, &callee_))
+        if (!iter_.readCallIndirect(&sigIndex, &callee_, &args_))
             return false;
     }
 
     if (deadCode_)
         return true;
 
     sync();
 
@@ -6009,22 +5982,16 @@ BaseCompiler::emitCallIndirect(bool oldS
     Stk callee = oldStyle ? peek(numArgs) : stk_.popCopy();
 
     FunctionCall baselineCall(lineOrBytecode);
     beginCall(baselineCall, UseABI::Wasm, InterModule::True);
 
     if (!emitCallArgs(sig.args(), baselineCall))
         return false;
 
-    if (oldStyle && !iter_.readOldCallIndirectCallee(&callee_))
-        return false;
-
-    if (!iter_.readCallReturn(sig.ret()))
-        return false;
-
     callIndirect(sigIndex, callee, baselineCall);
 
     endCall(baselineCall, stackSpace);
 
     // For new style calls, the callee was popped off the compiler's
     // stack above.
 
     popValueStackBy(oldStyle ? numArgs + 1 : numArgs);
@@ -6045,48 +6012,53 @@ BaseCompiler::emitCommonMathCall(uint32_
     size_t stackSpace = stackConsumed(numArgs);
 
     FunctionCall baselineCall(lineOrBytecode);
     beginCall(baselineCall, UseABI::System, InterModule::False);
 
     if (!emitCallArgs(signature, baselineCall))
         return false;
 
-    if (!iter_.readCallReturn(retType))
-      return false;
-
     builtinCall(callee, baselineCall);
 
     endCall(baselineCall, stackSpace);
 
     popValueStackBy(numArgs);
 
     pushReturned(baselineCall, retType);
 
     return true;
 }
 
 bool
 BaseCompiler::emitUnaryMathBuiltinCall(SymbolicAddress callee, ValType operandType)
 {
     uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
 
+    Nothing operand_;
+    if (!iter_.readUnary(operandType, &operand_))
+        return false;
+
     if (deadCode_)
         return true;
 
     return emitCommonMathCall(lineOrBytecode, callee,
                               operandType == ValType::F32 ? SigF_ : SigD_,
                               operandType == ValType::F32 ? ExprType::F32 : ExprType::F64);
 }
 
 bool
 BaseCompiler::emitBinaryMathBuiltinCall(SymbolicAddress callee, ValType operandType)
 {
     MOZ_ASSERT(operandType == ValType::F64);
 
+    Nothing lhs_, rhs_;
+    if (!iter_.readBinary(operandType, &lhs_, &rhs_))
+        return false;
+
     uint32_t lineOrBytecode = 0;
     if (callee == SymbolicAddress::ModD) {
         // Not actually a call in the binary representation
     } else {
         lineOrBytecode = readCallSiteLineOrBytecode();
     }
 
     if (deadCode_)
@@ -6326,17 +6298,17 @@ BaseCompiler::emitTeeLocal()
         return false;
     return emitSetOrTeeLocal<false>(slot);
 }
 
 bool
 BaseCompiler::emitGetGlobal()
 {
     uint32_t id;
-    if (!iter_.readGetGlobal(env_.globals, &id))
+    if (!iter_.readGetGlobal(&id))
         return false;
 
     if (deadCode_)
         return true;
 
     const GlobalDesc& global = env_.globals[id];
 
     if (global.isConstant()) {
@@ -6433,28 +6405,28 @@ BaseCompiler::emitSetOrTeeGlobal(uint32_
     return true;
 }
 
 bool
 BaseCompiler::emitSetGlobal()
 {
     uint32_t id;
     Nothing unused_value;
-    if (!iter_.readSetGlobal(env_.globals, &id, &unused_value))
+    if (!iter_.readSetGlobal(&id, &unused_value))
         return false;
 
     return emitSetOrTeeGlobal<true>(id);
 }
 
 bool
 BaseCompiler::emitTeeGlobal()
 {
     uint32_t id;
     Nothing unused_value;
-    if (!iter_.readTeeGlobal(env_.globals, &id, &unused_value))
+    if (!iter_.readTeeGlobal(&id, &unused_value))
         return false;
 
     return emitSetOrTeeGlobal<false>(id);
 }
 
 // See EffectiveAddressAnalysis::analyzeAsmJSHeapAccess() for comparable Ion code.
 //
 // TODO / OPTIMIZE (bug 1329576): There are opportunities to generate better
@@ -6663,17 +6635,17 @@ BaseCompiler::emitTeeStore(ValType resul
         return false;
 
     return emitStoreOrTeeStore<false>(resultType, viewType, addr);
 }
 
 bool
 BaseCompiler::emitSelect()
 {
-    ValType type;
+    StackType type;
     Nothing unused_trueValue;
     Nothing unused_falseValue;
     Nothing unused_condition;
     if (!iter_.readSelect(&type, &unused_trueValue, &unused_falseValue, &unused_condition))
         return false;
 
     if (deadCode_) {
         resetLatentOp();
@@ -6681,17 +6653,17 @@ BaseCompiler::emitSelect()
     }
 
     // I32 condition on top, then false, then true.
 
     Label done;
     BranchState b(&done);
     emitBranchSetup(&b);
 
-    switch (type) {
+    switch (NonAnyToValType(type)) {
       case ValType::I32: {
         RegI32 r0, r1;
         pop2xI32(&r0, &r1);
         emitBranchPerform(&b);
         moveI32(r1, r0);
         masm.bind(&done);
         freeI32(r1);
         pushI32(r0);
@@ -7627,17 +7599,17 @@ BaseCompiler::emitFunction()
 BaseCompiler::BaseCompiler(const ModuleEnvironment& env,
                            Decoder& decoder,
                            const FuncBytes& func,
                            const ValTypeVector& locals,
                            bool debugEnabled,
                            TempAllocator* alloc,
                            MacroAssembler* masm)
     : env_(env),
-      iter_(decoder, func.lineOrBytecode()),
+      iter_(env, decoder, func.lineOrBytecode()),
       func_(func),
       lastReadCallSite_(0),
       alloc_(*alloc),
       locals_(locals),
       localSize_(0),
       varLow_(0),
       varHigh_(0),
       maxFramePushed_(0),
--- a/js/src/wasm/WasmBinaryConstants.h
+++ b/js/src/wasm/WasmBinaryConstants.h
@@ -20,17 +20,21 @@
 #define wasm_binary_h
 
 #include "builtin/SIMD.h"
 
 namespace js {
 namespace wasm {
 
 static const uint32_t MagicNumber        = 0x6d736100; // "\0asm"
-static const uint32_t EncodingVersion    = 0x0d;
+static const uint32_t EncodingVersion    = 0x01;
+
+// 0xd is equivalent to 0x1 modulo unreachability validation rules, so to aid
+// transition of toolchain, accept both for a short period of time.
+static const uint32_t PrevEncodingVersion = 0x0d;
 
 static const char NameSectionName[]      = "name";
 
 enum class SectionId
 {
     Custom                               = 0,
     Type                                 = 1,
     Import                               = 2,
--- a/js/src/wasm/WasmBinaryIterator.h
+++ b/js/src/wasm/WasmBinaryIterator.h
@@ -25,24 +25,78 @@
 
 #include "jit/AtomicOp.h"
 #include "wasm/WasmValidate.h"
 
 namespace js {
 namespace wasm {
 
 // The kind of a control-flow stack item.
-enum class LabelKind : uint8_t {
+enum class LabelKind : uint8_t
+{
     Block,
     Loop,
     Then,
-    UnreachableThen, // like Then, but not reachable
     Else
 };
 
+// The type of values on the operand stack during validation. The Any type
+// represents the type of a value produced by an unconditional branch.
+enum class StackType
+{
+    I32   = uint8_t(ValType::I32),
+    I64   = uint8_t(ValType::I64),
+    F32   = uint8_t(ValType::F32),
+    F64   = uint8_t(ValType::F64),
+
+    I8x16 = uint8_t(ValType::I8x16),
+    I16x8 = uint8_t(ValType::I16x8),
+    I32x4 = uint8_t(ValType::I32x4),
+    F32x4 = uint8_t(ValType::F32x4),
+    B8x16 = uint8_t(ValType::B8x16),
+    B16x8 = uint8_t(ValType::B16x8),
+    B32x4 = uint8_t(ValType::B32x4),
+
+    Any   = uint8_t(TypeCode::Limit)
+};
+
+static inline StackType
+ToStackType(ValType type)
+{
+    return StackType(type);
+}
+
+static inline ValType
+NonAnyToValType(StackType type)
+{
+    MOZ_ASSERT(type != StackType::Any);
+    return ValType(type);
+}
+
+static inline bool
+Unify(StackType one, StackType two, StackType* result)
+{
+    if (MOZ_LIKELY(one == two)) {
+        *result = one;
+        return true;
+    }
+
+    if (one == StackType::Any) {
+        *result = two;
+        return true;
+    }
+
+    if (two == StackType::Any) {
+        *result = one;
+        return true;
+    }
+
+    return false;
+}
+
 #ifdef DEBUG
 // Families of opcodes that share a signature and validation logic.
 enum class OpKind {
     Block,
     Loop,
     Unreachable,
     Drop,
     I32,
@@ -121,115 +175,124 @@ struct LinearMemoryAddress
       : base(base), offset(offset), align(align)
     {}
 };
 
 template <typename ControlItem>
 class ControlStackEntry
 {
     LabelKind kind_;
-    bool reachable_;
+    bool polymorphicBase_;
     ExprType type_;
     size_t valueStackStart_;
     ControlItem controlItem_;
 
   public:
-    ControlStackEntry(LabelKind kind, ExprType type, bool reachable, size_t valueStackStart)
-      : kind_(kind), reachable_(reachable), type_(type), valueStackStart_(valueStackStart),
+    ControlStackEntry(LabelKind kind, ExprType type, size_t valueStackStart)
+      : kind_(kind), polymorphicBase_(false), type_(type), valueStackStart_(valueStackStart),
         controlItem_()
     {
         MOZ_ASSERT(type != ExprType::Limit);
     }
 
     LabelKind kind() const { return kind_; }
-    ExprType type() const { return type_; }
-    bool reachable() const { return reachable_; }
+    ExprType resultType() const { return type_; }
+    ExprType branchTargetType() const { return kind_ == LabelKind::Loop ? ExprType::Void : type_; }
     size_t valueStackStart() const { return valueStackStart_; }
     ControlItem& controlItem() { return controlItem_; }
-
-    void setReachable() { reachable_ = true; }
-
-    void switchToElse(bool reachable) {
-        MOZ_ASSERT(kind_ == LabelKind::Then || kind_ == LabelKind::UnreachableThen);
-        reachable_ = reachable;
+    void setPolymorphicBase() { polymorphicBase_ = true; }
+    bool polymorphicBase() const { return polymorphicBase_; }
+
+    void switchToElse() {
+        MOZ_ASSERT(kind_ == LabelKind::Then);
         kind_ = LabelKind::Else;
+        polymorphicBase_ = false;
     }
 };
 
 // Specialization for when there is no additional data needed.
 template <>
 class ControlStackEntry<Nothing>
 {
     LabelKind kind_;
-    bool reachable_;
+    bool polymorphicBase_;
     ExprType type_;
     size_t valueStackStart_;
 
   public:
-    ControlStackEntry(LabelKind kind, ExprType type, bool reachable, size_t valueStackStart)
-      : kind_(kind), reachable_(reachable), type_(type), valueStackStart_(valueStackStart)
+    ControlStackEntry(LabelKind kind, ExprType type, size_t valueStackStart)
+      : kind_(kind), polymorphicBase_(false), type_(type), valueStackStart_(valueStackStart)
     {
         MOZ_ASSERT(type != ExprType::Limit);
     }
 
     LabelKind kind() const { return kind_; }
-    ExprType type() const { return type_; }
-    bool reachable() const { return reachable_; }
+    ExprType resultType() const { return type_; }
+    ExprType branchTargetType() const { return kind_ == LabelKind::Loop ? ExprType::Void : type_; }
     size_t valueStackStart() const { return valueStackStart_; }
     Nothing controlItem() { return Nothing(); }
-
-    void setReachable() { reachable_ = true; }
-
-    void switchToElse(bool reachable) {
-        MOZ_ASSERT(kind_ == LabelKind::Then || kind_ == LabelKind::UnreachableThen);
-        reachable_ = reachable;
+    void setPolymorphicBase() { polymorphicBase_ = true; }
+    bool polymorphicBase() const { return polymorphicBase_; }
+
+    void switchToElse() {
+        MOZ_ASSERT(kind_ == LabelKind::Then);
         kind_ = LabelKind::Else;
+        polymorphicBase_ = false;
     }
 };
 
 template <typename Value>
 class TypeAndValue
 {
-    ValType type_;
+    StackType type_;
     Value value_;
 
   public:
-    TypeAndValue() : type_(ValType(TypeCode::Limit)), value_() {}
-    explicit TypeAndValue(ValType type)
+    TypeAndValue() : type_(StackType(TypeCode::Limit)), value_() {}
+    explicit TypeAndValue(StackType type)
       : type_(type), value_()
     {}
-    TypeAndValue(ValType type, Value value)
+    explicit TypeAndValue(ValType type)
+      : type_(ToStackType(type)), value_()
+    {}
+    TypeAndValue(StackType type, Value value)
       : type_(type), value_(value)
     {}
-    ValType type() const {
+    TypeAndValue(ValType type, Value value)
+      : type_(ToStackType(type)), value_(value)
+    {}
+    StackType type() const {
+        return type_;
+    }
+    StackType& typeRef() {
         return type_;
     }
     Value value() const {
         return value_;
     }
     void setValue(Value value) {
         value_ = value;
     }
 };
 
 // Specialization for when there is no additional data needed.
 template <>
 class TypeAndValue<Nothing>
 {
-    ValType type_;
+    StackType type_;
 
   public:
-    TypeAndValue() : type_(ValType(TypeCode::Limit)) {}
-    explicit TypeAndValue(ValType type) : type_(type) {}
-
-    TypeAndValue(ValType type, Nothing value)
-      : type_(type)
-    {}
-
-    ValType type() const { return type_; }
+    TypeAndValue() : type_(StackType(TypeCode::Limit)) {}
+    explicit TypeAndValue(StackType type) : type_(type) {}
+    explicit TypeAndValue(ValType type) : type_(ToStackType(type)) {}
+    TypeAndValue(StackType type, Nothing value) : type_(type) {}
+    TypeAndValue(ValType type, Nothing value) : type_(ToStackType(type)) {}
+
+    StackType type() const { return type_; }
+    StackType& typeRef() { return type_; }
     Nothing value() const { return Nothing(); }
     void setValue(Nothing value) {}
 };
 
 // A policy class for configuring OpIter. Clients can use this as a
 // base class, and override the behavior as needed.
 struct OpIterPolicy
 {
@@ -257,21 +320,21 @@ template <typename Policy>
 class MOZ_STACK_CLASS OpIter : private Policy
 {
     static const bool Validate = Policy::Validate;
     static const bool Output = Policy::Output;
     typedef typename Policy::Value Value;
     typedef typename Policy::ControlItem ControlItem;
 
     Decoder& d_;
+    const ModuleEnvironment& env_;
     const size_t offsetInModule_;
 
     Vector<TypeAndValue<Value>, 8, SystemAllocPolicy> valueStack_;
     Vector<ControlStackEntry<ControlItem>, 8, SystemAllocPolicy> controlStack_;
-    bool reachable_;
 
     DebugOnly<Op> op_;
     size_t offsetOfExpr_;
 
     MOZ_MUST_USE bool readFixedU8(uint8_t* out) {
         if (Validate)
             return d_.readFixedU8(out);
         *out = d_.uncheckedReadFixedU8();
@@ -371,176 +434,92 @@ class MOZ_STACK_CLASS OpIter : private P
             }
         }
         *op = jit::AtomicOp(x);
         return true;
     }
 
     MOZ_MUST_USE bool readLinearMemoryAddress(uint32_t byteSize, LinearMemoryAddress<Value>* addr);
     MOZ_MUST_USE bool readBlockType(ExprType* expr);
-
-    MOZ_MUST_USE bool typeMismatch(ExprType actual, ExprType expected) MOZ_COLD;
-    MOZ_MUST_USE bool checkType(ValType actual, ValType expected);
-    MOZ_MUST_USE bool checkType(ExprType actual, ExprType expected);
-
-    MOZ_MUST_USE bool pushControl(LabelKind kind, ExprType type, bool reachable);
-    MOZ_MUST_USE bool checkControlAtEndOfBlock(LabelKind* kind, ExprType* type, Value* value);
-    MOZ_MUST_USE bool finishControl(LabelKind* kind, ExprType* type, Value* value);
-
-    MOZ_MUST_USE bool push(ValType t) {
-        if (MOZ_UNLIKELY(!reachable_))
-            return true;
+    MOZ_MUST_USE bool popCallArgs(const ValTypeVector& expectedTypes, Vector<Value, 8, SystemAllocPolicy>* values);
+
+    MOZ_MUST_USE bool popAnyType(StackType* type, Value* value);
+    MOZ_MUST_USE bool typeMismatch(StackType actual, StackType expected);
+    MOZ_MUST_USE bool popWithType(StackType expectedType, Value* value);
+    MOZ_MUST_USE bool popWithType(ValType valType, Value* value) { return popWithType(ToStackType(valType), value); }
+    MOZ_MUST_USE bool popWithType(ExprType expectedType, Value* value);
+    MOZ_MUST_USE bool topWithType(ExprType expectedType, Value* value);
+    MOZ_MUST_USE bool topWithType(ValType valType, Value* value);
+
+    MOZ_MUST_USE bool pushControl(LabelKind kind, ExprType type);
+    MOZ_MUST_USE bool checkStackAtEndOfBlock(ExprType* type, Value* value);
+    MOZ_MUST_USE bool getControl(uint32_t relativeDepth, ControlStackEntry<ControlItem>** controlEntry);
+    MOZ_MUST_USE bool checkBranchValue(uint32_t relativeDepth, ExprType* type, Value* value);
+    MOZ_MUST_USE bool checkBrTableEntry(uint32_t* relativeDepth, ExprType* branchValueType, Value* branchValue);
+
+    MOZ_MUST_USE bool push(StackType t) {
         return valueStack_.emplaceBack(t);
     }
+    MOZ_MUST_USE bool push(ValType t) {
+        return valueStack_.emplaceBack(t);
+    }
+    MOZ_MUST_USE bool push(ExprType t) {
+        return IsVoid(t) || push(NonVoidToValType(t));
+    }
     MOZ_MUST_USE bool push(TypeAndValue<Value> tv) {
-        if (MOZ_UNLIKELY(!reachable_))
-            return true;
         return valueStack_.append(tv);
     }
+    void infalliblePush(StackType t) {
+        valueStack_.infallibleEmplaceBack(t);
+    }
     void infalliblePush(ValType t) {
-        if (MOZ_UNLIKELY(!reachable_))
-            return;
-        valueStack_.infallibleEmplaceBack(t);
+        valueStack_.infallibleEmplaceBack(ToStackType(t));
     }
     void infalliblePush(TypeAndValue<Value> tv) {
-        if (MOZ_UNLIKELY(!reachable_))
-            return;
         valueStack_.infallibleAppend(tv);
     }
 
-    // Test whether reading the top of the value stack is currently valid.
-    MOZ_MUST_USE bool checkTop() {
-        MOZ_ASSERT(reachable_);
-        if (Validate && valueStack_.length() <= controlStack_.back().valueStackStart()) {
-            if (valueStack_.empty())
-                return fail("popping value from empty stack");
-            return fail("popping value from outside block");
-        }
-        return true;
-    }
-
-    // Pop the top of the value stack.
-    MOZ_MUST_USE bool pop(TypeAndValue<Value>* tv) {
-        if (MOZ_UNLIKELY(!reachable_))
-            return true;
-        if (!checkTop())
-            return false;
-        *tv = valueStack_.popCopy();
-        return true;
-    }
-
-    // Pop the top of the value stack and check that it has the given type.
-    MOZ_MUST_USE bool popWithType(ValType expectedType, Value* value) {
-        if (MOZ_UNLIKELY(!reachable_))
-            return true;
-        if (!checkTop())
-            return false;
-        TypeAndValue<Value> tv = valueStack_.popCopy();
-        if (!checkType(tv.type(), expectedType))
-            return false;
-        if (Output)
-            *value = tv.value();
-        return true;
-    }
-
-    // Pop the top of the value stack and discard the result.
-    MOZ_MUST_USE bool pop() {
-        if (MOZ_UNLIKELY(!reachable_))
-            return true;
-        if (!checkTop())
-            return false;
-        valueStack_.popBack();
-        return true;
+    void afterUnconditionalBranch() {
+        valueStack_.shrinkTo(controlStack_.back().valueStackStart());
+        controlStack_.back().setPolymorphicBase();
     }
 
-    // Read the top of the value stack (without popping it).
-    MOZ_MUST_USE bool top(TypeAndValue<Value>* tv) {
-        if (MOZ_UNLIKELY(!reachable_))
-            return true;
-        if (!checkTop())
-            return false;
-        *tv = valueStack_.back();
-        return true;
-    }
-
-    // Read the top of the value stack (without popping it) and check that it
-    // has the given type.
-    MOZ_MUST_USE bool topWithType(ValType expectedType, Value* value) {
-        if (MOZ_UNLIKELY(!reachable_))
-            return true;
-        if (!checkTop())
-            return false;
-        TypeAndValue<Value>& tv = valueStack_.back();
-        if (!checkType(tv.type(), expectedType))
-            return false;
-        if (Output)
-            *value = tv.value();
-        return true;
-    }
-
-    // Read the value stack entry at depth |index|.
-    MOZ_MUST_USE bool peek(uint32_t index, TypeAndValue<Value>* tv) {
-        MOZ_ASSERT(reachable_);
-        if (Validate && valueStack_.length() - controlStack_.back().valueStackStart() < index)
-            return fail("peeking at value from outside block");
-        *tv = valueStack_[valueStack_.length() - index];
-        return true;
-    }
-
-    bool getControl(uint32_t relativeDepth, ControlStackEntry<ControlItem>** controlEntry) {
-        if (Validate && relativeDepth >= controlStack_.length())
-            return fail("branch depth exceeds current nesting level");
-
-        *controlEntry = &controlStack_[controlStack_.length() - 1 - relativeDepth];
-        return true;
-    }
-
-    MOZ_MUST_USE bool enterUnreachableCode() {
-        if (Validate) {
-            uint16_t op = peekOp();
-            if (op != uint16_t(Op::End) && op != uint16_t(Op::Else))
-                return fail("non-fallthrough instruction must be followed by end or else");
-        }
-
-        valueStack_.shrinkTo(controlStack_.back().valueStackStart());
-        reachable_ = false;
-        return true;
-    }
-
-    bool checkBrValue(uint32_t relativeDepth, ExprType* type, Value* value);
-    bool checkBrIfValues(uint32_t relativeDepth, Value* condition, ExprType* type, Value* value);
-
   public:
-    explicit OpIter(Decoder& decoder, uint32_t offsetInModule = 0)
-      : d_(decoder), offsetInModule_(offsetInModule), reachable_(true),
-        op_(Op::Limit), offsetOfExpr_(0)
+    typedef Vector<Value, 8, SystemAllocPolicy> ValueVector;
+
+    explicit OpIter(const ModuleEnvironment& env, Decoder& decoder, uint32_t offsetInModule = 0)
+      : d_(decoder), env_(env), offsetInModule_(offsetInModule), op_(Op::Limit), offsetOfExpr_(0)
     {}
 
     // Return the decoding byte offset.
-    uint32_t currentOffset() const { return d_.currentOffset(); }
+    uint32_t currentOffset() const {
+        return d_.currentOffset();
+    }
 
     // Returning the offset within the entire module of the last-read Op.
     TrapOffset trapOffset() const {
         return TrapOffset(offsetInModule_ + offsetOfExpr_);
     }
 
     // Test whether the iterator has reached the end of the buffer.
-    bool done() const { return d_.done(); }
+    bool done() const {
+        return d_.done();
+    }
 
     // Report a general failure.
     MOZ_MUST_USE bool fail(const char* msg) MOZ_COLD;
 
-    // Report an unimplemented feature.
-    MOZ_MUST_USE bool notYetImplemented(const char* what) MOZ_COLD;
-
     // Report an unrecognized opcode.
     MOZ_MUST_USE bool unrecognizedOpcode(uint32_t expr) MOZ_COLD;
 
-    // Test whether the iterator is currently in "reachable" code.
-    bool inReachableCode() const { return reachable_; }
+    // Return whether the innermost block has a polymorphic base of its stack.
+    // Ideally this accessor would be removed; consider using something else.
+    bool currentBlockHasPolymorphicBase() const {
+        return !controlStack_.empty() && controlStack_.back().polymorphicBase();
+    }
 
     // ------------------------------------------------------------------------
     // Decoding and validation interface.
 
     MOZ_MUST_USE bool readOp(uint16_t* op);
     MOZ_MUST_USE bool readFunctionStart(ExprType ret);
     MOZ_MUST_USE bool readFunctionEnd();
     MOZ_MUST_USE bool readReturn(Value* value);
@@ -548,61 +527,55 @@ class MOZ_STACK_CLASS OpIter : private P
     MOZ_MUST_USE bool readLoop();
     MOZ_MUST_USE bool readIf(Value* condition);
     MOZ_MUST_USE bool readElse(ExprType* thenType, Value* thenValue);
     MOZ_MUST_USE bool readEnd(LabelKind* kind, ExprType* type, Value* value);
     void popEnd();
     MOZ_MUST_USE bool readBr(uint32_t* relativeDepth, ExprType* type, Value* value);
     MOZ_MUST_USE bool readBrIf(uint32_t* relativeDepth, ExprType* type,
                                Value* value, Value* condition);
-    MOZ_MUST_USE bool readBrTable(uint32_t* tableLength, ExprType* type,
-                                  Value* value, Value* index);
-    MOZ_MUST_USE bool readBrTableEntry(ExprType* type, Value* value, uint32_t* depth);
-    MOZ_MUST_USE bool readBrTableDefault(ExprType* type, Value* value, uint32_t* depth);
+    MOZ_MUST_USE bool readBrTable(Uint32Vector* depths, uint32_t* defaultDepth,
+                                  ExprType* branchValueType, Value* branchValue, Value* index);
     MOZ_MUST_USE bool readUnreachable();
     MOZ_MUST_USE bool readDrop();
     MOZ_MUST_USE bool readUnary(ValType operandType, Value* input);
     MOZ_MUST_USE bool readConversion(ValType operandType, ValType resultType, Value* input);
     MOZ_MUST_USE bool readBinary(ValType operandType, Value* lhs, Value* rhs);
     MOZ_MUST_USE bool readComparison(ValType operandType, Value* lhs, Value* rhs);
     MOZ_MUST_USE bool readLoad(ValType resultType, uint32_t byteSize,
                                LinearMemoryAddress<Value>* addr);
     MOZ_MUST_USE bool readStore(ValType resultType, uint32_t byteSize,
                                 LinearMemoryAddress<Value>* addr, Value* value);
     MOZ_MUST_USE bool readTeeStore(ValType resultType, uint32_t byteSize,
                                    LinearMemoryAddress<Value>* addr, Value* value);
     MOZ_MUST_USE bool readNop();
     MOZ_MUST_USE bool readCurrentMemory();
     MOZ_MUST_USE bool readGrowMemory(Value* input);
-    MOZ_MUST_USE bool readSelect(ValType* type,
+    MOZ_MUST_USE bool readSelect(StackType* type,
                                  Value* trueValue, Value* falseValue, Value* condition);
     MOZ_MUST_USE bool readGetLocal(const ValTypeVector& locals, uint32_t* id);
     MOZ_MUST_USE bool readSetLocal(const ValTypeVector& locals, uint32_t* id, Value* value);
     MOZ_MUST_USE bool readTeeLocal(const ValTypeVector& locals, uint32_t* id, Value* value);
-    MOZ_MUST_USE bool readGetGlobal(const GlobalDescVector& globals, uint32_t* id);
-    MOZ_MUST_USE bool readSetGlobal(const GlobalDescVector& globals, uint32_t* id, Value* value);
-    MOZ_MUST_USE bool readTeeGlobal(const GlobalDescVector& globals, uint32_t* id, Value* value);
+    MOZ_MUST_USE bool readGetGlobal(uint32_t* id);
+    MOZ_MUST_USE bool readSetGlobal(uint32_t* id, Value* value);
+    MOZ_MUST_USE bool readTeeGlobal(uint32_t* id, Value* value);
     MOZ_MUST_USE bool readI32Const(int32_t* i32);
     MOZ_MUST_USE bool readI64Const(int64_t* i64);
     MOZ_MUST_USE bool readF32Const(float* f32);
     MOZ_MUST_USE bool readF64Const(double* f64);
     MOZ_MUST_USE bool readI8x16Const(I8x16* i8x16);
     MOZ_MUST_USE bool readI16x8Const(I16x8* i16x8);
     MOZ_MUST_USE bool readI32x4Const(I32x4* i32x4);
     MOZ_MUST_USE bool readF32x4Const(F32x4* f32x4);
     MOZ_MUST_USE bool readB8x16Const(I8x16* i8x16);
     MOZ_MUST_USE bool readB16x8Const(I16x8* i16x8);
     MOZ_MUST_USE bool readB32x4Const(I32x4* i32x4);
-    MOZ_MUST_USE bool readCall(uint32_t* calleeIndex);
-    MOZ_MUST_USE bool readCallIndirect(uint32_t* sigIndex, Value* callee);
-    MOZ_MUST_USE bool readOldCallIndirect(uint32_t* sigIndex);
-    MOZ_MUST_USE bool readCallArg(ValType type, uint32_t numArgs, uint32_t argIndex, Value* arg);
-    MOZ_MUST_USE bool readCallArgsEnd(uint32_t numArgs);
-    MOZ_MUST_USE bool readOldCallIndirectCallee(Value* callee);
-    MOZ_MUST_USE bool readCallReturn(ExprType ret);
+    MOZ_MUST_USE bool readCall(uint32_t* calleeIndex, ValueVector* argValues);
+    MOZ_MUST_USE bool readCallIndirect(uint32_t* sigIndex, Value* callee, ValueVector* argValues);
+    MOZ_MUST_USE bool readOldCallIndirect(uint32_t* sigIndex, Value* callee, ValueVector* argValues);
     MOZ_MUST_USE bool readAtomicLoad(LinearMemoryAddress<Value>* addr,
                                      Scalar::Type* viewType);
     MOZ_MUST_USE bool readAtomicStore(LinearMemoryAddress<Value>* addr,
                                       Scalar::Type* viewType,
                                       Value* value);
     MOZ_MUST_USE bool readAtomicBinOp(LinearMemoryAddress<Value>* addr,
                                       Scalar::Type* viewType,
                                       jit::AtomicOp* op,
@@ -625,39 +598,34 @@ class MOZ_STACK_CLASS OpIter : private P
                                       Value* vector, Value* scalar);
     MOZ_MUST_USE bool readSplat(ValType simdType, Value* scalar);
     MOZ_MUST_USE bool readSwizzle(ValType simdType, uint8_t (* lanes)[16], Value* vector);
     MOZ_MUST_USE bool readShuffle(ValType simdType, uint8_t (* lanes)[16],
                                   Value* lhs, Value* rhs);
     MOZ_MUST_USE bool readSimdSelect(ValType simdType, Value* trueValue,
                                      Value* falseValue,
                                      Value* condition);
-    MOZ_MUST_USE bool readSimdCtor();
-    MOZ_MUST_USE bool readSimdCtorArg(ValType elementType, uint32_t numElements, uint32_t argIndex,
-                                      Value* arg);
-    MOZ_MUST_USE bool readSimdCtorArgsEnd(uint32_t numElements);
-    MOZ_MUST_USE bool readSimdCtorReturn(ValType simdType);
+    MOZ_MUST_USE bool readSimdCtor(ValType elementType, uint32_t numElements, ValType simdType,
+                                   ValueVector* argValues);
 
     // At a location where readOp is allowed, peek at the next opcode
     // without consuming it or updating any internal state.
     // Never fails: returns uint16_t(Op::Limit) if it can't read.
     uint16_t peekOp();
 
     // ------------------------------------------------------------------------
     // Stack management.
 
     // Set the result value of the current top-of-value-stack expression.
     void setResult(Value value) {
-        if (MOZ_LIKELY(reachable_))
-            valueStack_.back().setValue(value);
+        valueStack_.back().setValue(value);
     }
 
     // Return the result value of the current top-of-value-stack expression.
     Value getResult() {
-        MOZ_ASSERT(reachable_);
         return valueStack_.back().value();
     }
 
     // Return a reference to the top of the control stack.
     ControlItem& controlItem() {
         return controlStack_.back().controlItem();
     }
 
@@ -666,167 +634,240 @@ class MOZ_STACK_CLASS OpIter : private P
         return controlStack_[controlStack_.length() - 1 - relativeDepth].controlItem();
     }
 
     // Return a reference to the outermost element on the control stack.
     ControlItem& controlOutermost() {
         return controlStack_[0].controlItem();
     }
 
-    // Return the signature of the top of the control stack.
-    ExprType controlType() {
-        return controlStack_.back().type();
-    }
-
     // Test whether the control-stack is empty, meaning we've consumed the final
     // end of the function body.
     bool controlStackEmpty() const {
         return controlStack_.empty();
     }
 };
 
 template <typename Policy>
-bool
-OpIter<Policy>::typeMismatch(ExprType actual, ExprType expected)
-{
-    MOZ_ASSERT(Validate);
-    MOZ_ASSERT(reachable_);
-
-    UniqueChars error(JS_smprintf("type mismatch: expression has type %s but expected %s",
-                                  ToCString(actual), ToCString(expected)));
-    if (!error)
-        return false;
-
-    return fail(error.get());
-}
-
-template <typename Policy>
 inline bool
-OpIter<Policy>::checkType(ValType actual, ValType expected)
-{
-    return checkType(ToExprType(actual), ToExprType(expected));
-}
-
-template <typename Policy>
-inline bool
-OpIter<Policy>::checkType(ExprType actual, ExprType expected)
-{
-    MOZ_ASSERT(reachable_);
-
-    if (!Validate) {
-        MOZ_ASSERT(actual == expected, "type mismatch");
-        return true;
-    }
-
-    if (MOZ_LIKELY(actual == expected))
-        return true;
-
-    return typeMismatch(actual, expected);
-}
-
-template <typename Policy>
-bool
-OpIter<Policy>::notYetImplemented(const char* what)
-{
-    UniqueChars error(JS_smprintf("not yet implemented: %s", what));
-    if (!error)
-        return false;
-
-    return fail(error.get());
-}
-
-template <typename Policy>
-bool
 OpIter<Policy>::unrecognizedOpcode(uint32_t expr)
 {
     UniqueChars error(JS_smprintf("unrecognized opcode: %x", expr));
     if (!error)
         return false;
 
     return fail(error.get());
 }
 
 template <typename Policy>
-bool
+inline bool
 OpIter<Policy>::fail(const char* msg)
 {
     return d_.fail("%s", msg);
 }
 
-template <typename Policy>
-inline bool
-OpIter<Policy>::pushControl(LabelKind kind, ExprType type, bool reachable)
-{
-    return controlStack_.emplaceBack(kind, type, reachable, valueStack_.length());
-}
-
+// This function pops exactly one value from the stack, yielding Any types in
+// various cases and therefore making it the caller's responsibility to do the
+// right thing for StackType::Any. Prefer (pop|top)WithType.
 template <typename Policy>
 inline bool
-OpIter<Policy>::checkControlAtEndOfBlock(LabelKind* kind, ExprType* type, Value* value)
+OpIter<Policy>::popAnyType(StackType* type, Value* value)
 {
-    MOZ_ASSERT(!controlStack_.empty());
-
-    ControlStackEntry<ControlItem>& controlItem = controlStack_.back();
-    *kind = controlItem.kind();
-
-    if (reachable_) {
-        // Unlike branching, exiting a scope via fallthrough does not implicitly
-        // pop excess items on the stack.
-        size_t valueStackStart = controlItem.valueStackStart();
-        size_t valueStackLength = valueStack_.length();
-        MOZ_ASSERT(valueStackLength >= valueStackStart);
-        if (valueStackLength == valueStackStart) {
-            *type = ExprType::Void;
-            if (!checkType(ExprType::Void, controlItem.type()))
-                return false;
-        } else {
-            *type = controlItem.type();
-            if (Validate && valueStackLength - valueStackStart > (IsVoid(*type) ? 0u : 1u))
-                return fail("unused values not explicitly dropped by end of block");
-            if (!topWithType(NonVoidToValType(*type), value))
-                return false;
+    ControlStackEntry<ControlItem>& block = controlStack_.back();
+
+    MOZ_ASSERT(valueStack_.length() >= block.valueStackStart());
+    if (MOZ_UNLIKELY(valueStack_.length() == block.valueStackStart())) {
+        // If the base of this block's stack is polymorphic, then we can pop a
+        // dummy value of any type; it won't be used since we're in unreachable code.
+        if (block.polymorphicBase()) {
+            *type = StackType::Any;
+            if (Output)
+                *value = Value();
+
+            // Maintain the invariant that, after a pop, there is always memory
+            // reserved to push a value infallibly.
+            return valueStack_.reserve(valueStack_.length() + 1);
         }
-    } else {
-        if (*kind != LabelKind::Loop && controlItem.reachable()) {
-            // There was no fallthrough path, but there was some other reachable
-            // branch to the end.
-            reachable_ = true;
-            *type = controlItem.type();
-            if (!IsVoid(*type)) {
-                if (!push(NonVoidToValType(*type)))
-                    return false;
-            }
-        } else {
-            // No fallthrough and no branch to the end either; we remain
-            // unreachable.
-            *type = ExprType::Void;
-        }
-        if (Output)
-            *value = Value();
+
+        if (valueStack_.empty())
+            return fail("popping value from empty stack");
+        return fail("popping value from outside block");
     }
 
+    TypeAndValue<Value>& tv = valueStack_.back();
+
+    *type = tv.type();
+    if (Output)
+        *value = tv.value();
+
+    valueStack_.popBack();
+
     return true;
 }
 
 template <typename Policy>
 inline bool
-OpIter<Policy>::finishControl(LabelKind* kind, ExprType* type, Value* value)
+OpIter<Policy>::typeMismatch(StackType actual, StackType expected)
 {
-    if (!checkControlAtEndOfBlock(kind, type, value))
+    UniqueChars error(JS_smprintf("type mismatch: expression has type %s but expected %s",
+                                  ToCString(NonAnyToValType(actual)),
+                                  ToCString(NonAnyToValType(expected))));
+    if (!error)
         return false;
 
-    if (*kind == LabelKind::Then) {
-        // A reachable If without an Else. Forbid a result value.
-        if (reachable_) {
-            if (Validate && !IsVoid(*type))
-                return fail("if without else with a result value");
+    return fail(error.get());
+}
+
+// This function pops exactly one value from the stack, checking that it has the
+// expected type which can either be a specific value type or a type variable.
+template <typename Policy>
+inline bool
+OpIter<Policy>::popWithType(StackType expectedType, Value* value)
+{
+    ControlStackEntry<ControlItem>& block = controlStack_.back();
+
+    MOZ_ASSERT(valueStack_.length() >= block.valueStackStart());
+    if (MOZ_UNLIKELY(valueStack_.length() == block.valueStackStart())) {
+        // If the base of this block's stack is polymorphic, then we can pop a
+        // dummy value of any expected type; it won't be used since we're in
+        // unreachable code.
+        if (block.polymorphicBase()) {
+            if (Output)
+                *value = Value();
+
+            // Maintain the invariant that, after a pop, there is always memory
+            // reserved to push a value infallibly.
+            return valueStack_.reserve(valueStack_.length() + 1);
         }
-        reachable_ = true;
+
+        if (valueStack_.empty())
+            return fail("popping value from empty stack");
+        return fail("popping value from outside block");
+    }
+
+    TypeAndValue<Value> tv = valueStack_.popCopy();
+
+    if (Validate) {
+        StackType _;
+        if (MOZ_UNLIKELY(!Unify(tv.type(), expectedType, &_)))
+            return typeMismatch(tv.type(), expectedType);
+    } else {
+        DebugOnly<StackType> result;
+        MOZ_ASSERT(Unify(tv.type(), expectedType, &result));
+    }
+
+    if (Output)
+        *value = tv.value();
+
+    return true;
+}
+
+// This function pops as many types from the stack as determined by the given
+// signature. Currently, all signatures are limited to 0 or 1 types, with
+// ExprType::Void meaning 0 and all other ValTypes meaning 1, but this will be
+// generalized in the future.
+template <typename Policy>
+inline bool
+OpIter<Policy>::popWithType(ExprType expectedType, Value* value)
+{
+    if (IsVoid(expectedType)) {
+        if (Output)
+            *value = Value();
+        return true;
     }
 
+    return popWithType(NonVoidToValType(expectedType), value);
+}
+
+// This function is just an optimization of popWithType + push.
+template <typename Policy>
+inline bool
+OpIter<Policy>::topWithType(ValType expectedType, Value* value)
+{
+    ControlStackEntry<ControlItem>& block = controlStack_.back();
+
+    MOZ_ASSERT(valueStack_.length() >= block.valueStackStart());
+    if (MOZ_UNLIKELY(valueStack_.length() == block.valueStackStart())) {
+        // If the base of this block's stack is polymorphic, then we can just
+        // pull out a dummy value of the expected type; it won't be used since
+        // we're in unreachable code. We must however push this value onto the
+        // stack since it is now fixed to a specific type by this type
+        // constraint.
+        if (block.polymorphicBase()) {
+            if (!valueStack_.emplaceBack(expectedType, Value()))
+                return false;
+            if (Output)
+                *value = Value();
+            return true;
+        }
+
+        if (valueStack_.empty())
+            return fail("reading value from empty stack");
+        return fail("reading value from outside block");
+    }
+
+    TypeAndValue<Value>& tv = valueStack_.back();
+
+    if (MOZ_UNLIKELY(!Unify(tv.type(), ToStackType(expectedType), &tv.typeRef())))
+        return typeMismatch(tv.type(), ToStackType(expectedType));
+
+    if (Output)
+        *value = tv.value();
+
+    return true;
+}
+
+template <typename Policy>
+inline bool
+OpIter<Policy>::topWithType(ExprType expectedType, Value* value)
+{
+    if (IsVoid(expectedType)) {
+        if (Output)
+            *value = Value();
+        return true;
+    }
+
+    return topWithType(NonVoidToValType(expectedType), value);
+}
+
+template <typename Policy>
+inline bool
+OpIter<Policy>::pushControl(LabelKind kind, ExprType type)
+{
+    return controlStack_.emplaceBack(kind, type, valueStack_.length());
+}
+
+template <typename Policy>
+inline bool
+OpIter<Policy>::checkStackAtEndOfBlock(ExprType* type, Value* value)
+{
+    ControlStackEntry<ControlItem>& block = controlStack_.back();
+
+    MOZ_ASSERT(valueStack_.length() >= block.valueStackStart());
+    size_t pushed = valueStack_.length() - block.valueStackStart();
+    if (Validate && pushed > (IsVoid(block.resultType()) ? 0u : 1u))
+        return fail("unused values not explicitly dropped by end of block");
+
+    if (!topWithType(block.resultType(), value))
+        return false;
+
+    if (Output)
+        *type = block.resultType();
+
+    return true;
+}
+
+template <typename Policy>
+inline bool
+OpIter<Policy>::getControl(uint32_t relativeDepth, ControlStackEntry<ControlItem>** controlEntry)
+{
+    if (Validate && relativeDepth >= controlStack_.length())
+        return fail("branch depth exceeds current nesting level");
+
+    *controlEntry = &controlStack_[controlStack_.length() - 1 - relativeDepth];
     return true;
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::readBlockType(ExprType* type)
 {
     uint8_t unchecked;
@@ -856,16 +897,18 @@ OpIter<Policy>::readBlockType(ExprType* 
     *type = ExprType(unchecked);
     return true;
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::readOp(uint16_t* op)
 {
+    MOZ_ASSERT(!controlStack_.empty());
+
     offsetOfExpr_ = d_.currentOffset();
 
     if (Validate) {
         if (MOZ_UNLIKELY(!d_.readOp(op)))
             return fail("unable to read opcode");
     } else {
         *op = uint16_t(d_.uncheckedReadOp());
     }
@@ -896,19 +939,18 @@ OpIter<Policy>::peekOp()
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::readFunctionStart(ExprType ret)
 {
     MOZ_ASSERT(valueStack_.empty());
     MOZ_ASSERT(controlStack_.empty());
     MOZ_ASSERT(Op(op_) == Op::Limit);
-    MOZ_ASSERT(reachable_);
-
-    return pushControl(LabelKind::Block, ret, false);
+
+    return pushControl(LabelKind::Block, ret);
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::readFunctionEnd()
 {
     if (Validate) {
         if (!controlStack_.empty())
@@ -923,336 +965,276 @@ OpIter<Policy>::readFunctionEnd()
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::readReturn(Value* value)
 {
     MOZ_ASSERT(Classify(op_) == OpKind::Return);
 
-    if (MOZ_LIKELY(reachable_)) {
-        ControlStackEntry<ControlItem>& controlItem = controlStack_[0];
-        MOZ_ASSERT(controlItem.kind() == LabelKind::Block);
-
-        controlItem.setReachable();
-
-        if (!IsVoid(controlItem.type())) {
-            if (!popWithType(NonVoidToValType(controlItem.type()), value))
-                return false;
-        }
-    }
-
-    return enterUnreachableCode();
+    ControlStackEntry<ControlItem>& body = controlStack_[0];
+    MOZ_ASSERT(body.kind() == LabelKind::Block);
+
+    if (!popWithType(body.resultType(), value))
+        return false;
+
+    afterUnconditionalBranch();
+    return true;
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::readBlock()
 {
     MOZ_ASSERT(Classify(op_) == OpKind::Block);
 
     ExprType type = ExprType::Limit;
     if (!readBlockType(&type))
         return false;
 
-    return pushControl(LabelKind::Block, type, false);
+    return pushControl(LabelKind::Block, type);
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::readLoop()
 {
     MOZ_ASSERT(Classify(op_) == OpKind::Loop);
 
     ExprType type = ExprType::Limit;
     if (!readBlockType(&type))
         return false;
 
-    return pushControl(LabelKind::Loop, type, reachable_);
+    return pushControl(LabelKind::Loop, type);
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::readIf(Value* condition)
 {
     MOZ_ASSERT(Classify(op_) == OpKind::If);
 
     ExprType type = ExprType::Limit;
     if (!readBlockType(&type))
         return false;
 
-    if (MOZ_LIKELY(reachable_)) {
-        if (!popWithType(ValType::I32, condition))
-            return false;
-
-        return pushControl(LabelKind::Then, type, false);
-    }
-
-    return pushControl(LabelKind::UnreachableThen, type, false);
+    if (!popWithType(ValType::I32, condition))
+        return false;
+
+    return pushControl(LabelKind::Then, type);
 }
 
 template <typename Policy>
 inline bool
-OpIter<Policy>::readElse(ExprType* thenType, Value* thenValue)
+OpIter<Policy>::readElse(ExprType* type, Value* value)
 {
     MOZ_ASSERT(Classify(op_) == OpKind::Else);
 
-    // Finish up the then arm.
-    ExprType type = ExprType::Limit;
-    LabelKind kind;
-    if (!checkControlAtEndOfBlock(&kind, &type, thenValue))
+    // Finish checking the then-block.
+
+    if (!checkStackAtEndOfBlock(type, value))
         return false;
 
-    if (Output)
-        *thenType = type;
-
-    // Pop the old then value from the stack.
-    if (!IsVoid(type))
+    ControlStackEntry<ControlItem>& block = controlStack_.back();
+
+    if (Validate && block.kind() != LabelKind::Then)
+        return fail("else can only be used within an if");
+
+    // Switch to the else-block.
+
+    if (!IsVoid(block.resultType()))
         valueStack_.popBack();
 
-    if (Validate && kind != LabelKind::Then && kind != LabelKind::UnreachableThen)
-        return fail("else can only be used within an if");
-
-    // Switch to the else arm.
-    controlStack_.back().switchToElse(reachable_);
-
-    reachable_ = kind != LabelKind::UnreachableThen;
-
-    MOZ_ASSERT(valueStack_.length() == controlStack_.back().valueStackStart());
-
+    MOZ_ASSERT(valueStack_.length() == block.valueStackStart());
+
+    block.switchToElse();
     return true;
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::readEnd(LabelKind* kind, ExprType* type, Value* value)
 {
     MOZ_ASSERT(Classify(op_) == OpKind::End);
 
-    LabelKind validateKind = static_cast<LabelKind>(-1);
-    ExprType validateType = ExprType::Limit;
-    if (!finishControl(&validateKind, &validateType, value))
+    if (!checkStackAtEndOfBlock(type, value))
         return false;
 
-    if (Output) {
-        *kind = validateKind;
-        *type = validateType;
-    }
+    ControlStackEntry<ControlItem>& block = controlStack_.back();
+
+    // If an `if` block ends with `end` instead of `else`, then we must
+    // additionally validate that the then-block doesn't push anything.
+    if (Validate && block.kind() == LabelKind::Then && !IsVoid(block.resultType()))
+        return fail("if without else with a result value");
+
+    if (Output)
+        *kind = block.kind();
 
     return true;
 }
 
 template <typename Policy>
 inline void
 OpIter<Policy>::popEnd()
 {
     MOZ_ASSERT(Classify(op_) == OpKind::End);
 
     controlStack_.popBack();
-
-    if (!reachable_ && !controlStack_.empty())
-        valueStack_.shrinkTo(controlStack_.back().valueStackStart());
+}
+
+template <typename Policy>
+inline bool
+OpIter<Policy>::checkBranchValue(uint32_t relativeDepth, ExprType* type, Value* value)
+{
+    ControlStackEntry<ControlItem>* block = nullptr;
+    if (!getControl(relativeDepth, &block))
+        return false;
+
+    *type = block->branchTargetType();
+    return topWithType(*type, value);
 }
 
 template <typename Policy>
 inline bool
-OpIter<Policy>::checkBrValue(uint32_t relativeDepth, ExprType* type, Value* value)
+OpIter<Policy>::readBr(uint32_t* relativeDepthOut, ExprType* typeOut, Value* value)
 {
-    if (MOZ_LIKELY(reachable_)) {
-        ControlStackEntry<ControlItem>* controlItem = nullptr;
-        if (!getControl(relativeDepth, &controlItem))
-            return false;
-
-        if (controlItem->kind() != LabelKind::Loop) {
-            controlItem->setReachable();
-
-            ExprType expectedType = controlItem->type();
-            if (Output)
-                *type = expectedType;
-
-            if (!IsVoid(expectedType))
-                return topWithType(NonVoidToValType(expectedType), value);
-        }
-    }
-
-    if (Output) {
-        *type = ExprType::Void;
-        *value = Value();
-    }
+    MOZ_ASSERT(Classify(op_) == OpKind::Br);
+
+    uint32_t relativeDepth;
+    if (!readVarU32(&relativeDepth))
+        return fail("unable to read br depth");
+
+    if (Output)
+        *relativeDepthOut = relativeDepth;
+
+    ExprType type;
+    if (!checkBranchValue(relativeDepth, &type, value))
+        return false;
+
+    if (Output)
+        *typeOut = type;
+
+    afterUnconditionalBranch();
+    return true;
+}
+
+template <typename Policy>
+inline bool
+OpIter<Policy>::readBrIf(uint32_t* relativeDepthOut, ExprType* typeOut, Value* value, Value* condition)
+{
+    MOZ_ASSERT(Classify(op_) == OpKind::BrIf);
+
+    uint32_t relativeDepth;
+    if (!readVarU32(&relativeDepth))
+        return fail("unable to read br_if depth");
+
+    if (Output)
+        *relativeDepthOut = relativeDepth;
+
+    if (!popWithType(ValType::I32, condition))
+        return false;
+
+    ExprType type;
+    if (!checkBranchValue(relativeDepth, &type, value))
+        return false;
+
+    if (Output)
+        *typeOut = type;
 
     return true;
 }
 
 template <typename Policy>
 inline bool
-OpIter<Policy>::readBr(uint32_t* relativeDepth, ExprType* type, Value* value)
-{
-    MOZ_ASSERT(Classify(op_) == OpKind::Br);
-
-    uint32_t validateRelativeDepth;
-    if (!readVarU32(&validateRelativeDepth))
-        return fail("unable to read br depth");
-
-    if (!checkBrValue(validateRelativeDepth, type, value))
-        return false;
-
-    if (Output)
-        *relativeDepth = validateRelativeDepth;
-
-    return enterUnreachableCode();
-}
-
-template <typename Policy>
-inline bool
-OpIter<Policy>::checkBrIfValues(uint32_t relativeDepth, Value* condition,
-                                  ExprType* type, Value* value)
+OpIter<Policy>::checkBrTableEntry(uint32_t* relativeDepth, ExprType* branchValueType,
+                                  Value* branchValue)
 {
-    if (MOZ_LIKELY(reachable_)) {
-        if (!popWithType(ValType::I32, condition))
-            return false;
-
-        ControlStackEntry<ControlItem>* controlItem = nullptr;
-        if (!getControl(relativeDepth, &controlItem))
+    if (!readVarU32(relativeDepth))
+        return false;
+
+    // For the first encountered branch target, do a normal branch value type
+    // check which will change *branchValueType to a non-sentinel value. For all
+    // subsequent branch targets, check that the branch target matches the
+    // now-known branch value type.
+
+    if (*branchValueType == ExprType::Limit) {
+        if (!checkBranchValue(*relativeDepth, branchValueType, branchValue))
             return false;
-
-        if (controlItem->kind() != LabelKind::Loop) {
-            controlItem->setReachable();
-
-            ExprType expectedType = controlItem->type();
-            if (Output)
-                *type = expectedType;
-
-            if (!IsVoid(expectedType))
-                return topWithType(NonVoidToValType(expectedType), value);
-        }
-    }
-
-    if (Output) {
-        *type = ExprType::Void;
-        *value = Value();
+    } else {
+        ControlStackEntry<ControlItem>* block = nullptr;
+        if (!getControl(*relativeDepth, &block))
+            return false;
+
+        if (*branchValueType != block->branchTargetType())
+            return fail("br_table targets must all have the same value type");
     }
 
     return true;
 }
 
 template <typename Policy>
 inline bool
-OpIter<Policy>::readBrIf(uint32_t* relativeDepth, ExprType* type, Value* value, Value* condition)
-{
-    MOZ_ASSERT(Classify(op_) == OpKind::BrIf);
-
-    uint32_t validateRelativeDepth;
-    if (!readVarU32(&validateRelativeDepth))
-        return fail("unable to read br_if depth");
-
-    if (!checkBrIfValues(validateRelativeDepth, condition, type, value))
-        return false;
-
-    if (Output)
-        *relativeDepth = validateRelativeDepth;
-
-    return true;
-}
-
-template <typename Policy>
-inline bool
-OpIter<Policy>::readBrTable(uint32_t* tableLength, ExprType* type,
-                              Value* value, Value* index)
-{
-    MOZ_ASSERT(Classify(op_) == OpKind::BrTable);
-
-    if (!readVarU32(tableLength))
-        return fail("unable to read br_table table length");
-
-    if (MOZ_LIKELY(reachable_)) {
-        if (!popWithType(ValType::I32, index))
-            return false;
-    }
-
-    // Set *type to indicate that we don't know the type yet.
-    *type = ExprType::Limit;
-    if (Output)
-        *value = Value();
-
-    return true;
-}
-
-template <typename Policy>
-inline bool
-OpIter<Policy>::readBrTableEntry(ExprType* type, Value* value, uint32_t* depth)
+OpIter<Policy>::readBrTable(Uint32Vector* depthsOut, uint32_t* defaultDepthOut,
+                            ExprType* branchValueType, Value* branchValue, Value* index)
 {
     MOZ_ASSERT(Classify(op_) == OpKind::BrTable);
 
-    if (!readVarU32(depth))
+    uint32_t tableLength;
+    if (!readVarU32(&tableLength))
+        return fail("unable to read br_table table length");
+
+    if (!popWithType(ValType::I32, index))
         return false;
 
-    ExprType knownType = *type;
-
-    if (MOZ_LIKELY(reachable_)) {
-        ControlStackEntry<ControlItem>* controlItem = nullptr;
-        if (!getControl(*depth, &controlItem))
+    Uint32Vector depths;
+    if (!depths.resize(tableLength))
+        return false;
+
+    ExprType type = ExprType::Limit;
+
+    for (uint32_t i = 0; i < tableLength; i++) {
+        if (!checkBrTableEntry(&depths[i], &type, branchValue))
             return false;
-
-        if (controlItem->kind() != LabelKind::Loop) {
-            controlItem->setReachable();
-
-            // If we've already seen one label, we know the type and can check
-            // that the type for the current label matches it.
-            if (knownType != ExprType::Limit)
-                return checkType(knownType, controlItem->type());
-
-            // This is the first label; record the type and the value now.
-            ExprType expectedType = controlItem->type();
-            if (!IsVoid(expectedType)) {
-                *type = expectedType;
-                return popWithType(NonVoidToValType(expectedType), value);
-            }
-        }
-
-        if (knownType != ExprType::Limit && knownType != ExprType::Void)
-            return typeMismatch(knownType, ExprType::Void);
     }
 
-    *type = ExprType::Void;
-    if (Output)
-        *value = Value();
+    uint32_t defaultDepth;
+    if (!checkBrTableEntry(&defaultDepth, &type, branchValue))
+        return false;
+
+    MOZ_ASSERT(type != ExprType::Limit);
+
+    afterUnconditionalBranch();
+
+    if (Output) {
+        *depthsOut = Move(depths);
+        *defaultDepthOut = defaultDepth;
+        *branchValueType = type;
+    }
+
     return true;
 }
 
 template <typename Policy>
 inline bool
-OpIter<Policy>::readBrTableDefault(ExprType* type, Value* value, uint32_t* depth)
-{
-    if (!readBrTableEntry(type, value, depth))
-        return false;
-
-    MOZ_ASSERT(!reachable_ || *type != ExprType::Limit);
-
-    return enterUnreachableCode();
-}
-
-template <typename Policy>
-inline bool
 OpIter<Policy>::readUnreachable()
 {
     MOZ_ASSERT(Classify(op_) == OpKind::Unreachable);
 
-    return enterUnreachableCode();
+    afterUnconditionalBranch();
+    return true;
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::readDrop()
 {
     MOZ_ASSERT(Classify(op_) == OpKind::Drop);
-
-    if (!pop())
-        return false;
-
-    return true;
+    StackType type;
+    Value value;
+    return popAnyType(&type, &value);
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::readUnary(ValType operandType, Value* input)
 {
     MOZ_ASSERT(Classify(op_) == OpKind::Unary);
 
@@ -1311,16 +1293,19 @@ OpIter<Policy>::readComparison(ValType o
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::readLinearMemoryAddress(uint32_t byteSize, LinearMemoryAddress<Value>* addr)
 {
+    if (!env_.usesMemory())
+        return fail("can't touch memory without memory");
+
     uint8_t alignLog2;
     if (!readFixedU8(&alignLog2))
         return fail("unable to read load alignment");
 
     uint32_t unusedOffset;
     if (!readVarU32(Output ? &addr->offset : &unusedOffset))
         return fail("unable to read load offset");
 
@@ -1395,35 +1380,38 @@ OpIter<Policy>::readNop()
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::readCurrentMemory()
 {
     MOZ_ASSERT(Classify(op_) == OpKind::CurrentMemory);
 
+    if (!env_.usesMemory())
+        return fail("can't touch memory without memory");
+
     uint32_t flags;
     if (!readVarU32(&flags))
         return false;
 
     if (Validate && flags != uint32_t(MemoryTableFlags::Default))
         return fail("unexpected flags");
 
-    if (!push(ValType::I32))
-        return false;
-
-    return true;
+    return push(ValType::I32);
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::readGrowMemory(Value* input)
 {
     MOZ_ASSERT(Classify(op_) == OpKind::GrowMemory);
 
+    if (!env_.usesMemory())
+        return fail("can't touch memory without memory");
+
     uint32_t flags;
     if (!readVarU32(&flags))
         return false;
 
     if (Validate && flags != uint32_t(MemoryTableFlags::Default))
         return fail("unexpected flags");
 
     if (!popWithType(ValType::I32, input))
@@ -1431,42 +1419,39 @@ OpIter<Policy>::readGrowMemory(Value* in
 
     infalliblePush(ValType::I32);
 
     return true;
 }
 
 template <typename Policy>
 inline bool
-OpIter<Policy>::readSelect(ValType* type, Value* trueValue, Value* falseValue, Value* condition)
+OpIter<Policy>::readSelect(StackType* type, Value* trueValue, Value* falseValue, Value* condition)
 {
     MOZ_ASSERT(Classify(op_) == OpKind::Select);
 
     if (!popWithType(ValType::I32, condition))
         return false;
 
-    TypeAndValue<Value> false_;
-    if (!pop(&false_))
+    StackType falseType;
+    if (!popAnyType(&falseType, falseValue))
         return false;
 
-    TypeAndValue<Value> true_;
-    if (!pop(&true_))
+    StackType trueType;
+    if (!popAnyType(&trueType, trueValue))
         return false;
 
-    ValType resultType = true_.type();
-    if (Validate && resultType != false_.type())
+    StackType resultType;
+    if (!Unify(falseType, trueType, &resultType))
         return fail("select operand types must match");
 
     infalliblePush(resultType);
 
-    if (Output) {
+    if (Output)
         *type = resultType;
-        *trueValue = true_.value();
-        *falseValue = false_.value();
-    }
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::readGetLocal(const ValTypeVector& locals, uint32_t* id)
 {
@@ -1529,78 +1514,78 @@ OpIter<Policy>::readTeeLocal(const ValTy
     if (Output)
         *id = validateId;
 
     return true;
 }
 
 template <typename Policy>
 inline bool
-OpIter<Policy>::readGetGlobal(const GlobalDescVector& globals, uint32_t* id)
+OpIter<Policy>::readGetGlobal(uint32_t* id)
 {
     MOZ_ASSERT(Classify(op_) == OpKind::GetGlobal);
 
     uint32_t validateId;
     if (!readVarU32(&validateId))
         return false;
 
-    if (Validate && validateId >= globals.length())
+    if (Validate && validateId >= env_.globals.length())
         return fail("get_global index out of range");
 
-    if (!push(globals[validateId].type()))
+    if (!push(env_.globals[validateId].type()))
         return false;
 
     if (Output)
         *id = validateId;
 
     return true;
 }
 
 template <typename Policy>
 inline bool
-OpIter<Policy>::readSetGlobal(const GlobalDescVector& globals, uint32_t* id, Value* value)
+OpIter<Policy>::readSetGlobal(uint32_t* id, Value* value)
 {
     MOZ_ASSERT(Classify(op_) == OpKind::SetGlobal);
 
     uint32_t validateId;
     if (!readVarU32(&validateId))
         return false;
 
-    if (Validate && validateId >= globals.length())
+    if (Validate && validateId >= env_.globals.length())
         return fail("set_global index out of range");
 
-    if (Validate && !globals[validateId].isMutable())
+    if (Validate && !env_.globals[validateId].isMutable())
         return fail("can't write an immutable global");
 
-    if (!popWithType(globals[validateId].type(), value))
+    if (!popWithType(env_.globals[validateId].type(), value))
         return false;
 
     if (Output)
         *id = validateId;
 
     return true;
 }
 
 template <typename Policy>
 inline bool
-OpIter<Policy>::readTeeGlobal(const GlobalDescVector& globals, uint32_t* id, Value* value)
+OpIter<Policy>::readTeeGlobal(uint32_t* id, Value* value)
 {
     MOZ_ASSERT(Classify(op_) == OpKind::TeeGlobal);
 
     uint32_t validateId;
     if (!readVarU32(&validateId))
         return false;
 
-    if (Validate && validateId >= globals.length())
+    if (Validate && validateId >= env_.globals.length())
         return fail("set_global index out of range");
 
-    if (Validate && !globals[validateId].isMutable())
+    if (Validate && !env_.globals[validateId].isMutable())
         return fail("can't write an immutable global");
 
-    if (!topWithType(globals[validateId].type(), value))
+    if (!topWithType(env_.globals[validateId].type(), value))
         return false;
 
     if (Output)
         *id = validateId;
 
     return true;
 }
 
@@ -1609,20 +1594,17 @@ inline bool
 OpIter<Policy>::readI32Const(int32_t* i32)
 {
     MOZ_ASSERT(Classify(op_) == OpKind::I32);
 
     int32_t unused;
     if (!readVarS32(Output ? i32 : &unused))
         return false;
 
-    if (!push(ValType::I32))
-       return false;
-
-    return true;
+    return push(ValType::I32);
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::readI64Const(int64_t* i64)
 {
     MOZ_ASSERT(Classify(op_) == OpKind::I64);
 
@@ -1777,116 +1759,122 @@ OpIter<Policy>::readB32x4Const(I32x4* i3
     if (!push(ValType::B32x4))
         return false;
 
     return true;
 }
 
 template <typename Policy>
 inline bool
-OpIter<Policy>::readCall(uint32_t* calleeIndex)
-{
-    MOZ_ASSERT(Classify(op_) == OpKind::Call);
-
-    if (!readVarU32(calleeIndex))
-        return fail("unable to read call function index");
-
-    return true;
-}
-
-template <typename Policy>
-inline bool
-OpIter<Policy>::readCallIndirect(uint32_t* sigIndex, Value* callee)
+OpIter<Policy>::popCallArgs(const ValTypeVector& expectedTypes, ValueVector* values)
 {
-    MOZ_ASSERT(Classify(op_) == OpKind::CallIndirect);
-
-    if (!readVarU32(sigIndex))
-        return fail("unable to read call_indirect signature index");
-
-    uint32_t flags;
-    if (!readVarU32(&flags))
+    // Iterate through the argument types backward so that pops occur in the
+    // right order.
+
+    if (Output && !values->resize(expectedTypes.length()))
         return false;
 
-    if (Validate && flags != uint32_t(MemoryTableFlags::Default))
-        return fail("unexpected flags");
-
-    if (reachable_) {
-        if (!popWithType(ValType::I32, callee))
+    for (int32_t i = expectedTypes.length() - 1; i >= 0; i--) {
+        Value* argValue = Output ? &(*values)[i] : nullptr;
+        if (!popWithType(expectedTypes[i], argValue))
             return false;
     }
 
     return true;
 }
 
 template <typename Policy>
 inline bool
-OpIter<Policy>::readOldCallIndirect(uint32_t* sigIndex)
+OpIter<Policy>::readCall(uint32_t* funcIndexOut, ValueVector* argValues)
 {
-    MOZ_ASSERT(Classify(op_) == OpKind::OldCallIndirect);
-
-    if (!readVarU32(sigIndex))
-        return fail("unable to read call_indirect signature index");
-
-    return true;
-}
-
-template <typename Policy>
-inline bool
-OpIter<Policy>::readCallArg(ValType type, uint32_t numArgs, uint32_t argIndex, Value* arg)
-{
-    MOZ_ASSERT(reachable_);
-
-    TypeAndValue<Value> tv;
-
-    if (!peek(numArgs - argIndex, &tv))
+    MOZ_ASSERT(Classify(op_) == OpKind::Call);
+
+    uint32_t funcIndex;
+    if (!readVarU32(&funcIndex))
+        return fail("unable to read call function index");
+
+    if (funcIndex >= env_.funcSigs.length())
+        return fail("callee index out of range");
+
+    const Sig& sig = *env_.funcSigs[funcIndex];
+
+    if (!popCallArgs(sig.args(), argValues))
         return false;
-    if (!checkType(tv.type(), type))
+
+    if (!push(sig.ret()))
         return false;
 
     if (Output)
-        *arg = tv.value();
+        *funcIndexOut = funcIndex;
 
     return true;
 }
 
 template <typename Policy>
 inline bool
-OpIter<Policy>::readCallArgsEnd(uint32_t numArgs)
+OpIter<Policy>::readCallIndirect(uint32_t* sigIndexOut, Value* callee, ValueVector* argValues)
 {
-    MOZ_ASSERT(reachable_);
-    MOZ_ASSERT(numArgs <= valueStack_.length());
-
-    valueStack_.shrinkBy(numArgs);
+    MOZ_ASSERT(Classify(op_) == OpKind::CallIndirect);
+
+    if (!env_.tables.length())
+        return fail("can't call_indirect without a table");
+
+    uint32_t sigIndex;
+    if (!readVarU32(&sigIndex))
+        return fail("unable to read call_indirect signature index");
+
+    if (sigIndex >= env_.numSigs())
+        return fail("signature index out of range");
+
+    uint32_t flags;
+    if (!readVarU32(&flags))
+        return false;
+
+    if (Validate && flags != uint32_t(MemoryTableFlags::Default))
+        return fail("unexpected flags");
+
+    if (!popWithType(ValType::I32, callee))
+        return false;
+
+    const Sig& sig = env_.sigs[sigIndex];
+
+    if (!popCallArgs(sig.args(), argValues))
+        return false;
+
+    if (!push(sig.ret()))
+        return false;
+
+    if (Output)
+        *sigIndexOut = sigIndex;
 
     return true;
 }
 
 template <typename Policy>
 inline bool
-OpIter<Policy>::readOldCallIndirectCallee(Value* callee)
+OpIter<Policy>::readOldCallIndirect(uint32_t* sigIndex, Value* callee, ValueVector* argValues)
 {
     MOZ_ASSERT(Classify(op_) == OpKind::OldCallIndirect);
-    MOZ_ASSERT(reachable_);
+
+    if (!readVarU32(sigIndex))
+        return fail("unable to read call_indirect signature index");
+
+    if (*sigIndex >= env_.numSigs())
+        return fail("signature index out of range");
+
+    const Sig& sig = env_.sigs[*sigIndex];
+
+    if (!popCallArgs(sig.args(), argValues))
+        return false;
 
     if (!popWithType(ValType::I32, callee))
         return false;
 
-    return true;
-}
-
-template <typename Policy>
-inline bool
-OpIter<Policy>::readCallReturn(ExprType ret)
-{
-    MOZ_ASSERT(reachable_);
-
-    if (!IsVoid(ret)) {
-        if (!push(NonVoidToValType(ret)))
-            return false;
-    }
+    if (!push(sig.ret()))
+        return false;
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::readAtomicLoad(LinearMemoryAddress<Value>* addr, Scalar::Type* viewType)
 {
@@ -2198,59 +2186,29 @@ OpIter<Policy>::readSimdSelect(ValType s
 
     infalliblePush(simdType);
 
     return true;
 }
 
 template <typename Policy>
 inline bool
-OpIter<Policy>::readSimdCtor()
-{
-    MOZ_ASSERT(Classify(op_) == OpKind::SimdCtor);
-
-    return true;
-}
-
-template <typename Policy>
-inline bool
-OpIter<Policy>::readSimdCtorArg(ValType elementType, uint32_t numElements, uint32_t index,
-                                Value* arg)
+OpIter<Policy>::readSimdCtor(ValType elementType, uint32_t numElements, ValType simdType,
+                             ValueVector* argValues)
 {
     MOZ_ASSERT(Classify(op_) == OpKind::SimdCtor);
-    MOZ_ASSERT(numElements > 0);
-
-    TypeAndValue<Value> tv;
-
-    if (!peek(numElements - index, &tv))
-        return false;
-    if (!checkType(tv.type(), elementType))
+
+    if (Output && !argValues->resize(numElements))
         return false;
 
-    *arg = tv.value();
-    return true;
-}
-
-template <typename Policy>
-inline bool
-OpIter<Policy>::readSimdCtorArgsEnd(uint32_t numElements)
-{
-    MOZ_ASSERT(Classify(op_) == OpKind::SimdCtor);
-    MOZ_ASSERT(numElements <= valueStack_.length());
-
-    valueStack_.shrinkBy(numElements);
-
-    return true;
-}
-
-template <typename Policy>
-inline bool
-OpIter<Policy>::readSimdCtorReturn(ValType simdType)
-{
-    MOZ_ASSERT(Classify(op_) == OpKind::SimdCtor);
+    for (int32_t i = numElements - 1; i >= 0; i--) {
+        Value* argValue = Output ? &(*argValues)[i] : nullptr;
+        if (!popWithType(elementType, argValue))
+            return false;
+    }
 
     infalliblePush(simdType);
 
     return true;
 }
 
 } // namespace wasm
 } // namespace js
--- a/js/src/wasm/WasmBinaryToAST.cpp
+++ b/js/src/wasm/WasmBinaryToAST.cpp
@@ -130,22 +130,16 @@ class AstDecodeContext
     ExprType retType() const { return retType_; }
     const ValTypeVector& locals() const { return *locals_; }
 
     void popBack() { return exprs().popBack(); }
     AstDecodeStackItem popCopy() { return exprs().popCopy(); }
     AstDecodeStackItem& top() { return exprs().back(); }
     MOZ_MUST_USE bool push(AstDecodeStackItem item) { return exprs().append(item); }
 
-    bool checkHasMemory() {
-        if (!module().hasMemory())
-            return iter().fail("can't touch memory without memory");
-        return true;
-    }
-
     bool needFirst() {
         for (size_t i = depths().back(); i < exprs().length(); ++i) {
             if (!exprs()[i].expr->isVoid())
                 return true;
         }
         return false;
     }
 
@@ -251,40 +245,28 @@ GenerateRef(AstDecodeContext& c, const A
     *ref = AstRef(name);
     ref->setIndex(index);
     return true;
 }
 
 static bool
 AstDecodeCallArgs(AstDecodeContext& c, const SigWithId& sig, AstExprVector* funcArgs)
 {
-    MOZ_ASSERT(c.iter().inReachableCode());
+    MOZ_ASSERT(!c.iter().currentBlockHasPolymorphicBase());
 
-    const ValTypeVector& args = sig.args();
-    uint32_t numArgs = args.length();
-
+    uint32_t numArgs = sig.args().length();
     if (!funcArgs->resize(numArgs))
         return false;
 
-    for (size_t i = 0; i < numArgs; ++i) {
-        ValType argType = args[i];
-        AstDecodeStackItem item;
-        if (!c.iter().readCallArg(argType, numArgs, i, nullptr))
-            return false;
+    for (size_t i = 0; i < numArgs; ++i)
         (*funcArgs)[i] = c.exprs()[c.exprs().length() - numArgs + i].expr;
-    }
+
     c.exprs().shrinkBy(numArgs);
 
-    return c.iter().readCallArgsEnd(numArgs);
-}
-
-static bool
-AstDecodeCallReturn(AstDecodeContext& c, const SigWithId& sig)
-{
-    return c.iter().readCallReturn(sig.ret());
+    return true;
 }
 
 static bool
 AstDecodeExpr(AstDecodeContext& c);
 
 static bool
 AstDecodeDrop(AstDecodeContext& c)
 {
@@ -306,43 +288,38 @@ AstDecodeDrop(AstDecodeContext& c)
 
     return true;
 }
 
 static bool
 AstDecodeCall(AstDecodeContext& c)
 {
     uint32_t funcIndex;
-    if (!c.iter().readCall(&funcIndex))
+    AstDecodeOpIter::ValueVector unusedArgs;
+    if (!c.iter().readCall(&funcIndex, &unusedArgs))
         return false;
 
-    if (!c.iter().inReachableCode())
+    if (c.iter().currentBlockHasPolymorphicBase())
         return true;
 
-    if (funcIndex >= c.env().numFuncs())
-        return c.iter().fail("callee index out of range");
-
     AstRef funcRef;
     if (funcIndex < c.module().numFuncImports()) {
         AstImport* import = c.module().imports()[funcIndex];
         funcRef = AstRef(import->name());
     } else {
         if (!GenerateRef(c, AstName(u"func"), funcIndex, &funcRef))
             return false;
     }
 
     const SigWithId* sig = c.env().funcSigs[funcIndex];
 
     AstExprVector args(c.lifo);
     if (!AstDecodeCallArgs(c, *sig, &args))
         return false;
 
-    if (!AstDecodeCallReturn(c, *sig))
-        return false;
-
     AstCall* call = new(c.lifo) AstCall(Op::Call, sig->ret(), funcRef, Move(args));
     if (!call)
         return false;
 
     AstExpr* result = call;
     if (IsVoid(sig->ret()))
         result = c.handleVoidExpr(call);
 
@@ -350,43 +327,35 @@ AstDecodeCall(AstDecodeContext& c)
         return false;
 
     return true;
 }
 
 static bool
 AstDecodeCallIndirect(AstDecodeContext& c)
 {
-    if (!c.env().tables.length())
-        return c.iter().fail("can't call_indirect without a table");
-
     uint32_t sigIndex;
-    if (!c.iter().readCallIndirect(&sigIndex, nullptr))
+    AstDecodeOpIter::ValueVector unusedArgs;
+    if (!c.iter().readCallIndirect(&sigIndex, nullptr, &unusedArgs))
         return false;
 
-    if (!c.iter().inReachableCode())
+    if (c.iter().currentBlockHasPolymorphicBase())
         return true;
 
-    if (sigIndex >= c.module().sigs().length())
-        return c.iter().fail("signature index out of range");
-
     AstDecodeStackItem index = c.popCopy();
 
     AstRef sigRef;
     if (!GenerateRef(c, AstName(u"type"), sigIndex, &sigRef))
         return false;
 
     const SigWithId& sig = c.env().sigs[sigIndex];
     AstExprVector args(c.lifo);
     if (!AstDecodeCallArgs(c, sig, &args))
         return false;
 
-    if (!AstDecodeCallReturn(c, sig))
-        return false;
-
     AstCallIndirect* call = new(c.lifo) AstCallIndirect(sigRef, sig.ret(), Move(args), index.expr);
     if (!call)
         return false;
 
     AstExpr* result = call;
     if (IsVoid(sig.ret()))
         result = c.handleVoidExpr(call);
 
@@ -413,48 +382,41 @@ AstDecodeGetBlockRef(AstDecodeContext& c
     *ref = AstRef(c.blockLabels()[index]);
     ref->setIndex(depth);
     return true;
 }
 
 static bool
 AstDecodeBrTable(AstDecodeContext& c)
 {
-    uint32_t tableLength;
+    Uint32Vector depths;
+    uint32_t defaultDepth;
     ExprType type;
-    if (!c.iter().readBrTable(&tableLength, &type, nullptr, nullptr))
+    if (!c.iter().readBrTable(&depths, &defaultDepth, &type, nullptr, nullptr))
         return false;
 
     AstRefVector table(c.lifo);
-    if (!table.resize(tableLength))
+    if (!table.resize(depths.length()))
         return false;
 
-    uint32_t depth;
-    for (size_t i = 0, e = tableLength; i < e; ++i) {
-        if (!c.iter().readBrTableEntry(&type, nullptr, &depth))
-            return false;
-        if (!AstDecodeGetBlockRef(c, depth, &table[i]))
+    for (size_t i = 0; i < depths.length(); ++i) {
+        if (!AstDecodeGetBlockRef(c, depths[i], &table[i]))
             return false;
     }
 
-    // Read the default label.
-    if (!c.iter().readBrTableDefault(&type, nullptr, &depth))
-        return false;
-
     AstDecodeStackItem index = c.popCopy();
     AstDecodeStackItem value;
     if (!IsVoid(type))
         value = c.popCopy();
 
     AstRef def;
-    if (!AstDecodeGetBlockRef(c, depth, &def))
+    if (!AstDecodeGetBlockRef(c, defaultDepth, &def))
         return false;
 
-    AstBranchTable* branchTable = new(c.lifo) AstBranchTable(*index.expr,
-                                                             def, Move(table), value.expr);
+    auto branchTable = new(c.lifo) AstBranchTable(*index.expr, def, Move(table), value.expr);
     if (!branchTable)
         return false;
 
     if (!c.push(AstDecodeStackItem(branchTable)))
         return false;
 
     return true;
 }
@@ -676,17 +638,17 @@ AstDecodeBinary(AstDecodeContext& c, Val
         return false;
 
     return true;
 }
 
 static bool
 AstDecodeSelect(AstDecodeContext& c)
 {
-    ValType type;
+    StackType type;
     if (!c.iter().readSelect(&type, nullptr, nullptr, nullptr))
         return false;
 
     AstDecodeStackItem selectFalse = c.popCopy();
     AstDecodeStackItem selectTrue = c.popCopy();
     AstDecodeStackItem cond = c.popCopy();
 
     AstTernaryOperator* ternary = new(c.lifo) AstTernaryOperator(Op::Select, cond.expr, selectTrue.expr, selectFalse.expr);
@@ -741,19 +703,16 @@ AstDecodeLoadStoreAddress(const LinearMe
 {
     uint32_t flags = FloorLog2(addr.align);
     return AstLoadStoreAddress(item.expr, flags, addr.offset);
 }
 
 static bool
 AstDecodeLoad(AstDecodeContext& c, ValType type, uint32_t byteSize, Op op)
 {
-    if (!c.checkHasMemory())
-        return false;
-
     LinearMemoryAddress<Nothing> addr;
     if (!c.iter().readLoad(type, byteSize, &addr))
         return false;
 
     AstDecodeStackItem item = c.popCopy();
 
     AstLoad* load = new(c.lifo) AstLoad(op, AstDecodeLoadStoreAddress(addr, item));
     if (!load)
@@ -763,19 +722,16 @@ AstDecodeLoad(AstDecodeContext& c, ValTy
         return false;
 
     return true;
 }
 
 static bool
 AstDecodeStore(AstDecodeContext& c, ValType type, uint32_t byteSize, Op op)
 {
-    if (!c.checkHasMemory())
-        return false;
-
     LinearMemoryAddress<Nothing> addr;
     if (!c.iter().readStore(type, byteSize, &addr, nullptr))
         return false;
 
     AstDecodeStackItem value = c.popCopy();
     AstDecodeStackItem item = c.popCopy();
 
     AstStore* store = new(c.lifo) AstStore(op, AstDecodeLoadStoreAddress(addr, item), value.expr);
@@ -934,17 +890,17 @@ AstDecodeTeeLocal(AstDecodeContext& c)
 
     return true;
 }
 
 static bool
 AstDecodeGetGlobal(AstDecodeContext& c)
 {
     uint32_t globalId;
-    if (!c.iter().readGetGlobal(c.env().globals, &globalId))
+    if (!c.iter().readGetGlobal(&globalId))
         return false;
 
     AstRef globalRef;
     if (!GenerateRef(c, AstName(u"global"), globalId, &globalRef))
         return false;
 
     auto* getGlobal = new(c.lifo) AstGetGlobal(globalRef);
     if (!getGlobal)
@@ -955,17 +911,17 @@ AstDecodeGetGlobal(AstDecodeContext& c)
 
     return true;
 }
 
 static bool
 AstDecodeSetGlobal(AstDecodeContext& c)
 {
     uint32_t globalId;
-    if (!c.iter().readSetGlobal(c.env().globals, &globalId, nullptr))
+    if (!c.iter().readSetGlobal(&globalId, nullptr))
         return false;
 
     AstDecodeStackItem value = c.popCopy();
 
     AstRef globalRef;
     if (!GenerateRef(c, AstName(u"global"), globalId, &globalRef))
         return false;
 
@@ -1437,17 +1393,17 @@ AstDecodeFunctionBody(AstDecodeContext &
 
     ValTypeVector locals;
     if (!locals.appendAll(sig->args()))
         return false;
 
     if (!DecodeLocalEntries(c.d, ModuleKind::Wasm, &locals))
         return false;
 
-    AstDecodeOpIter iter(c.d);
+    AstDecodeOpIter iter(c.env(), c.d);
     c.startFunction(&iter, &locals, sig->ret());
 
     AstName funcName;
     if (!GenerateName(c, AstName(u"func"), funcIndex, &funcName))
         return false;
 
     uint32_t numParams = sig->args().length();
     uint32_t numLocals = locals.length();
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -171,17 +171,17 @@ class FunctionCompiler
 
   public:
     FunctionCompiler(const ModuleEnvironment& env,
                      Decoder& decoder,
                      const FuncBytes& func,
                      const ValTypeVector& locals,
                      MIRGenerator& mirGen)
       : env_(env),
-        iter_(decoder, func.lineOrBytecode()),
+        iter_(env, decoder, func.lineOrBytecode()),
         func_(func),
         locals_(locals),
         lastReadCallSite_(0),
         alloc_(mirGen.alloc()),
         graph_(mirGen.graph()),
         info_(mirGen.info()),
         mirGen_(mirGen),
         curBlock_(nullptr),
@@ -1698,17 +1698,16 @@ EmitEnd(FunctionCompiler& f)
         if (!f.finishBlock(&def))
             return false;
         break;
       case LabelKind::Loop:
         if (!f.closeLoop(block, &def))
             return false;
         break;
       case LabelKind::Then:
-      case LabelKind::UnreachableThen:
         // If we didn't see an Else, create a trivial else block so that we create
         // a diamond anyway, to preserve Ion invariants.
         if (!f.switchToElse(block, &block))
             return false;
 
         if (!f.joinIfElse(block, &def))
             return false;
         break;
@@ -1765,122 +1764,95 @@ EmitBrIf(FunctionCompiler& f)
     }
 
     return true;
 }
 
 static bool
 EmitBrTable(FunctionCompiler& f)
 {
-    uint32_t tableLength;
-    ExprType type;
-    MDefinition* value;
+    Uint32Vector depths;
+    uint32_t defaultDepth;
+    ExprType branchValueType;
+    MDefinition* branchValue;
     MDefinition* index;
-    if (!f.iter().readBrTable(&tableLength, &type, &value, &index))
-        return false;
-
-    Uint32Vector depths;
-    if (!depths.reserve(tableLength))
+    if (!f.iter().readBrTable(&depths, &defaultDepth, &branchValueType, &branchValue, &index))
         return false;
 
-    for (size_t i = 0; i < tableLength; ++i) {
-        uint32_t depth;
-        if (!f.iter().readBrTableEntry(&type, &value, &depth))
-            return false;
-        depths.infallibleAppend(depth);
-    }
-
-    // Read the default label.
-    uint32_t defaultDepth;
-    if (!f.iter().readBrTableDefault(&type, &value, &defaultDepth))
-        return false;
-
-    MDefinition* maybeValue = IsVoid(type) ? nullptr : value;
-
     // If all the targets are the same, or there are no targets, we can just
     // use a goto. This is not just an optimization: MaybeFoldConditionBlock
     // assumes that tables have more than one successor.
     bool allSameDepth = true;
     for (uint32_t depth : depths) {
         if (depth != defaultDepth) {
             allSameDepth = false;
             break;
         }
     }
 
     if (allSameDepth)
-        return f.br(defaultDepth, maybeValue);
-
-    return f.brTable(index, defaultDepth, depths, maybeValue);
+        return f.br(defaultDepth, branchValue);
+
+    return f.brTable(index, defaultDepth, depths, branchValue);
 }
 
 static bool
 EmitReturn(FunctionCompiler& f)
 {
     MDefinition* value;
     if (!f.iter().readReturn(&value))
         return false;
 
-    if (f.inDeadCode())
-        return true;
-
     if (IsVoid(f.sig().ret())) {
         f.returnVoid();
         return true;
     }
 
     f.returnExpr(value);
     return true;
 }
 
+typedef IonOpIter::ValueVector DefVector;
+
 static bool
-EmitCallArgs(FunctionCompiler& f, const Sig& sig, TlsUsage tls, CallCompileState* call)
+EmitCallArgs(FunctionCompiler& f, const Sig& sig, const DefVector& args, TlsUsage tls,
+             CallCompileState* call)
 {
     MOZ_ASSERT(NeedsTls(tls));
 
     if (!f.startCall(call))
         return false;
 
-    MDefinition* arg;
-    const ValTypeVector& argTypes = sig.args();
-    uint32_t numArgs = argTypes.length();
-    for (size_t i = 0; i < numArgs; ++i) {
-        ValType argType = argTypes[i];
-        if (!f.iter().readCallArg(argType, numArgs, i, &arg))
-            return false;
-        if (!f.passArg(arg, argType, call))
+    for (size_t i = 0, n = sig.args().length(); i < n; ++i) {
+        if (!f.passArg(args[i], sig.args()[i], call))
             return false;
     }
 
-    if (!f.iter().readCallArgsEnd(numArgs))
-        return false;
-
     return f.finishCall(call, tls);
 }
 
 static bool
 EmitCall(FunctionCompiler& f)
 {
     uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
 
     uint32_t funcIndex;
-    if (!f.iter().readCall(&funcIndex))
+    DefVector args;
+    if (!f.iter().readCall(&funcIndex, &args))
         return false;
 
     if (f.inDeadCode())
         return true;
 
     const Sig& sig = *f.env().funcSigs[funcIndex];
     bool import = f.env().funcIsImport(funcIndex);
+    TlsUsage tls = import ? TlsUsage::CallerSaved : TlsUsage::Need;
 
     CallCompileState call(f, lineOrBytecode);
-    if (!EmitCallArgs(f, sig, import ? TlsUsage::CallerSaved : TlsUsage::Need, &call))
-        return false;
-
-    if (!f.iter().readCallReturn(sig.ret()))
+    if (!EmitCallArgs(f, sig, args, tls, &call))
         return false;
 
     MDefinition* def;
     if (import) {
         uint32_t globalDataOffset = f.env().funcImportGlobalDataOffsets[funcIndex];
         if (!f.callImport(globalDataOffset, call, sig.ret(), &def))
             return false;
     } else {
@@ -1897,43 +1869,36 @@ EmitCall(FunctionCompiler& f)
 
 static bool
 EmitCallIndirect(FunctionCompiler& f, bool oldStyle)
 {
     uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
 
     uint32_t sigIndex;
     MDefinition* callee;
+    DefVector args;
     if (oldStyle) {
-        if (!f.iter().readOldCallIndirect(&sigIndex))
+        if (!f.iter().readOldCallIndirect(&sigIndex, &callee, &args))
             return false;
     } else {
-        if (!f.iter().readCallIndirect(&sigIndex, &callee))
+        if (!f.iter().readCallIndirect(&sigIndex, &callee, &args))
             return false;
     }
 
     if (f.inDeadCode())
         return true;
 
     const Sig& sig = f.env().sigs[sigIndex];
 
     TlsUsage tls = !f.env().isAsmJS() && f.env().tables[0].external
                    ? TlsUsage::CallerSaved
                    : TlsUsage::Need;
 
     CallCompileState call(f, lineOrBytecode);
-    if (!EmitCallArgs(f, sig, tls, &call))
-        return false;
-
-    if (oldStyle) {
-        if (!f.iter().readOldCallIndirectCallee(&callee))
-            return false;
-    }
-
-    if (!f.iter().readCallReturn(sig.ret()))
+    if (!EmitCallArgs(f, sig, args, tls, &call))
         return false;
 
     MDefinition* def;
     if (!f.callIndirect(sigIndex, callee, call, &def))
         return false;
 
     if (IsVoid(sig.ret()))
         return true;
@@ -1976,17 +1941,17 @@ EmitTeeLocal(FunctionCompiler& f)
     f.assign(id, value);
     return true;
 }
 
 static bool
 EmitGetGlobal(FunctionCompiler& f)
 {
     uint32_t id;
-    if (!f.iter().readGetGlobal(f.env().globals, &id))
+    if (!f.iter().readGetGlobal(&id))
         return false;
 
     const GlobalDesc& global = f.env().globals[id];
     if (!global.isConstant()) {
         f.iter().setResult(f.loadGlobalVar(global.offset(), !global.isMutable(),
                                            ToMIRType(global.type())));
         return true;
     }
@@ -2028,32 +1993,32 @@ EmitGetGlobal(FunctionCompiler& f)
     return true;
 }
 
 static bool
 EmitSetGlobal(FunctionCompiler& f)
 {
     uint32_t id;
     MDefinition* value;
-    if (!f.iter().readSetGlobal(f.env().globals, &id, &value))
+    if (!f.iter().readSetGlobal(&id, &value))
         return false;
 
     const GlobalDesc& global = f.env().globals[id];
     MOZ_ASSERT(global.isMutable());
 
     f.storeGlobalVar(global.offset(), value);
     return true;
 }
 
 static bool
 EmitTeeGlobal(FunctionCompiler& f)
 {
     uint32_t id;
     MDefinition* value;
-    if (!f.iter().readTeeGlobal(f.env().globals, &id, &value))
+    if (!f.iter().readTeeGlobal(&id, &value))
         return false;
 
     const GlobalDesc& global = f.env().globals[id];
     MOZ_ASSERT(global.isMutable());
 
     f.storeGlobalVar(global.offset(), value);
     return true;
 }
@@ -2295,17 +2260,17 @@ EmitComparison(FunctionCompiler& f,
 
     f.iter().setResult(f.compare(lhs, rhs, compareOp, compareType));
     return true;
 }
 
 static bool
 EmitSelect(FunctionCompiler& f)
 {
-    ValType type;
+    StackType type;
     MDefinition* trueValue;
     MDefinition* falseValue;
     MDefinition* condition;
     if (!f.iter().readSelect(&type, &trueValue, &falseValue, &condition))
         return false;
 
     f.iter().setResult(f.select(trueValue, falseValue, condition));
     return true;
@@ -2802,97 +2767,87 @@ EmitSimdSplat(FunctionCompiler& f, ValTy
     return true;
 }
 
 // Build a SIMD vector by inserting lanes one at a time into an initial constant.
 static bool
 EmitSimdChainedCtor(FunctionCompiler& f, ValType valType, MIRType type, const SimdConstant& init)
 {
     const unsigned length = SimdTypeToLength(type);
+
+    DefVector args;
+    if (!f.iter().readSimdCtor(ValType::I32, length, valType, &args))
+        return false;
+
     MDefinition* val = f.constant(init, type);
-    for (unsigned i = 0; i < length; i++) {
-        MDefinition* scalar = 0;
-        if (!f.iter().readSimdCtorArg(ValType::I32, length, i, &scalar))
-            return false;
-        val = f.insertElementSimd(val, scalar, i, type);
-    }
-    if (!f.iter().readSimdCtorArgsEnd(length) || !f.iter().readSimdCtorReturn(valType))
-        return false;
+    for (unsigned i = 0; i < length; i++)
+        val = f.insertElementSimd(val, args[i], i, type);
+
     f.iter().setResult(val);
     return true;
 }
 
 // Build a boolean SIMD vector by inserting lanes one at a time into an initial constant.
 static bool
 EmitSimdBooleanChainedCtor(FunctionCompiler& f, ValType valType, MIRType type,
                            const SimdConstant& init)
 {
     const unsigned length = SimdTypeToLength(type);
+
+    DefVector args;
+    if (!f.iter().readSimdCtor(ValType::I32, length, valType, &args))
+        return false;
+
     MDefinition* val = f.constant(init, type);
-    for (unsigned i = 0; i < length; i++) {
-        MDefinition* scalar = 0;
-        if (!f.iter().readSimdCtorArg(ValType::I32, length, i, &scalar))
-            return false;
-        val = f.insertElementSimd(val, EmitSimdBooleanLaneExpr(f, scalar), i, type);
-    }
-    if (!f.iter().readSimdCtorArgsEnd(length) || !f.iter().readSimdCtorReturn(valType))
-        return false;
+    for (unsigned i = 0; i < length; i++)
+        val = f.insertElementSimd(val, EmitSimdBooleanLaneExpr(f, args[i]), i, type);
+
     f.iter().setResult(val);
     return true;
 }
 
 static bool
 EmitSimdCtor(FunctionCompiler& f, ValType type)
 {
-    if (!f.iter().readSimdCtor())
-        return false;
-
     switch (type) {
       case ValType::I8x16:
         return EmitSimdChainedCtor(f, type, MIRType::Int8x16, SimdConstant::SplatX16(0));
       case ValType::I16x8:
         return EmitSimdChainedCtor(f, type, MIRType::Int16x8, SimdConstant::SplatX8(0));
       case ValType::I32x4: {
-        MDefinition* args[4];
-        for (unsigned i = 0; i < 4; i++) {
-            if (!f.iter().readSimdCtorArg(ValType::I32, 4, i, &args[i]))
-                return false;
-        }
-        if (!f.iter().readSimdCtorArgsEnd(4) || !f.iter().readSimdCtorReturn(type))
+        DefVector args;
+        if (!f.iter().readSimdCtor(ValType::I32, 4, type, &args))
             return false;
+
         f.iter().setResult(f.constructSimd<MSimdValueX4>(args[0], args[1], args[2], args[3],
                                                          MIRType::Int32x4));
         return true;
       }
       case ValType::F32x4: {
-        MDefinition* args[4];
-        for (unsigned i = 0; i < 4; i++) {
-            if (!f.iter().readSimdCtorArg(ValType::F32, 4, i, &args[i]))
-                return false;
-        }
-        if (!f.iter().readSimdCtorArgsEnd(4) || !f.iter().readSimdCtorReturn(type))
+        DefVector args;
+        if (!f.iter().readSimdCtor(ValType::F32, 4, type, &args))
             return false;
+
         f.iter().setResult(f.constructSimd<MSimdValueX4>(args[0], args[1], args[2], args[3],
                            MIRType::Float32x4));
         return true;
       }
       case ValType::B8x16:
         return EmitSimdBooleanChainedCtor(f, type, MIRType::Bool8x16, SimdConstant::SplatX16(0));
       case ValType::B16x8:
         return EmitSimdBooleanChainedCtor(f, type, MIRType::Bool16x8, SimdConstant::SplatX8(0));
       case ValType::B32x4: {
-        MDefinition* args[4];
-        for (unsigned i = 0; i < 4; i++) {
-            MDefinition* i32;
-            if (!f.iter().readSimdCtorArg(ValType::I32, 4, i, &i32))
-                return false;
-            args[i] = EmitSimdBooleanLaneExpr(f, i32);
-        }
-        if (!f.iter().readSimdCtorArgsEnd(4) || !f.iter().readSimdCtorReturn(type))
+        DefVector args;
+        if (!f.iter().readSimdCtor(ValType::I32, 4, type, &args))
             return false;
+
+        MOZ_ASSERT(args.length() == 4);
+        for (unsigned i = 0; i < 4; i++)
+            args[i] = EmitSimdBooleanLaneExpr(f, args[i]);
+
         f.iter().setResult(f.constructSimd<MSimdValueX4>(args[0], args[1], args[2], args[3],
                            MIRType::Bool32x4));
         return true;
       }
       case ValType::I32:
       case ValType::I64:
       case ValType::F32:
       case ValType::F64:
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -303,408 +303,305 @@ wasm::DecodeLocalEntries(Decoder& d, Mod
 struct ValidatingPolicy : OpIterPolicy
 {
     // Validation is what we're all about here.
     static const bool Validate = true;
 };
 
 typedef OpIter<ValidatingPolicy> ValidatingOpIter;
 
-class FunctionDecoder
+static bool
+DecodeFunctionBodyExprs(const ModuleEnvironment& env, const Sig& sig, const ValTypeVector& locals,
+                        Decoder* d)
 {
-    const ModuleEnvironment& env_;
-    const ValTypeVector& locals_;
-    ValidatingOpIter iter_;
-
-  public:
-    FunctionDecoder(const ModuleEnvironment& env, const ValTypeVector& locals, Decoder& d)
-      : env_(env), locals_(locals), iter_(d)
-    {}
-
-    const ModuleEnvironment& env() const { return env_; }
-    ValidatingOpIter& iter() { return iter_; }
-    const ValTypeVector& locals() const { return locals_; }
-
-    bool checkHasMemory() {
-        if (!env().usesMemory())
-            return iter().fail("can't touch memory without memory");
-        return true;
-    }
-};
+    ValidatingOpIter iter(env, *d);
 
-static bool
-DecodeCallArgs(FunctionDecoder& f, const Sig& sig)
-{
-    const ValTypeVector& args = sig.args();
-    uint32_t numArgs = args.length();
-    for (size_t i = 0; i < numArgs; ++i) {
-        ValType argType = args[i];
-        if (!f.iter().readCallArg(argType, numArgs, i, nullptr))
-            return false;
-    }
-
-    return f.iter().readCallArgsEnd(numArgs);
-}
-
-static bool
-DecodeCallReturn(FunctionDecoder& f, const Sig& sig)
-{
-    return f.iter().readCallReturn(sig.ret());
-}
-
-static bool
-DecodeCall(FunctionDecoder& f)
-{
-    uint32_t funcIndex;
-    if (!f.iter().readCall(&funcIndex))
+    if (!iter.readFunctionStart(sig.ret()))
         return false;
 
-    if (funcIndex >= f.env().numFuncs())
-        return f.iter().fail("callee index out of range");
-
-    if (!f.iter().inReachableCode())
-        return true;
-
-    const Sig* sig = f.env().funcSigs[funcIndex];
-
-    return DecodeCallArgs(f, *sig) &&
-           DecodeCallReturn(f, *sig);
-}
-
-static bool
-DecodeCallIndirect(FunctionDecoder& f)
-{
-    if (!f.env().numTables())
-        return f.iter().fail("can't call_indirect without a table");
-
-    uint32_t sigIndex;
-    if (!f.iter().readCallIndirect(&sigIndex, nullptr))
-        return false;
-
-    if (sigIndex >= f.env().numSigs())
-        return f.iter().fail("signature index out of range");
-
-    if (!f.iter().inReachableCode())
-        return true;
-
-    const Sig& sig = f.env().sigs[sigIndex];
-    if (!DecodeCallArgs(f, sig))
-        return false;
-
-    return DecodeCallReturn(f, sig);
-}
-
-static bool
-DecodeBrTable(FunctionDecoder& f)
-{
-    uint32_t tableLength;
-    ExprType type = ExprType::Limit;
-    if (!f.iter().readBrTable(&tableLength, &type, nullptr, nullptr))
-        return false;
-
-    uint32_t depth;
-    for (size_t i = 0, e = tableLength; i < e; ++i) {
-        if (!f.iter().readBrTableEntry(&type, nullptr, &depth))
-            return false;
-    }
-
-    // Read the default label.
-    return f.iter().readBrTableDefault(&type, nullptr, &depth);
-}
-
-static bool
-DecodeFunctionBodyExprs(FunctionDecoder& f)
-{
 #define CHECK(c) if (!(c)) return false; break
 
     while (true) {
         uint16_t op;
-        if (!f.iter().readOp(&op))
+        if (!iter.readOp(&op))
             return false;
 
         switch (op) {
           case uint16_t(Op::End):
-            if (!f.iter().readEnd(nullptr, nullptr, nullptr))
+            if (!iter.readEnd(nullptr, nullptr, nullptr))
                 return false;
-            f.iter().popEnd();
-            if (f.iter().controlStackEmpty())
-                return true;
+            iter.popEnd();
+            if (iter.controlStackEmpty())
+                return iter.readFunctionEnd();
             break;
           case uint16_t(Op::Nop):
-            CHECK(f.iter().readNop());
+            CHECK(iter.readNop());
           case uint16_t(Op::Drop):
-            CHECK(f.iter().readDrop());
+            CHECK(iter.readDrop());
           case uint16_t(Op::Call):
-            CHECK(DecodeCall(f));
+            CHECK(iter.readCall(nullptr, nullptr));
           case uint16_t(Op::CallIndirect):
-            CHECK(DecodeCallIndirect(f));
+            CHECK(iter.readCallIndirect(nullptr, nullptr, nullptr));
           case uint16_t(Op::I32Const):
-            CHECK(f.iter().readI32Const(nullptr));
+            CHECK(iter.readI32Const(nullptr));
           case uint16_t(Op::I64Const):
-            CHECK(f.iter().readI64Const(nullptr));
+            CHECK(iter.readI64Const(nullptr));
           case uint16_t(Op::F32Const):
-            CHECK(f.iter().readF32Const(nullptr));
+            CHECK(iter.readF32Const(nullptr));
           case uint16_t(Op::F64Const):
-            CHECK(f.iter().readF64Const(nullptr));
+            CHECK(iter.readF64Const(nullptr));
           case uint16_t(Op::GetLocal):
-            CHECK(f.iter().readGetLocal(f.locals(), nullptr));
+            CHECK(iter.readGetLocal(locals, nullptr));
           case uint16_t(Op::SetLocal):
-            CHECK(f.iter().readSetLocal(f.locals(), nullptr, nullptr));
+            CHECK(iter.readSetLocal(locals, nullptr, nullptr));
           case uint16_t(Op::TeeLocal):
-            CHECK(f.iter().readTeeLocal(f.locals(), nullptr, nullptr));
+            CHECK(iter.readTeeLocal(locals, nullptr, nullptr));
           case uint16_t(Op::GetGlobal):
-            CHECK(f.iter().readGetGlobal(f.env().globals, nullptr));
+            CHECK(iter.readGetGlobal(nullptr));
           case uint16_t(Op::SetGlobal):
-            CHECK(f.iter().readSetGlobal(f.env().globals, nullptr, nullptr));
+            CHECK(iter.readSetGlobal(nullptr, nullptr));
           case uint16_t(Op::Select):
-            CHECK(f.iter().readSelect(nullptr, nullptr, nullptr, nullptr));
+            CHECK(iter.readSelect(nullptr, nullptr, nullptr, nullptr));
           case uint16_t(Op::Block):
-            CHECK(f.iter().readBlock());
+            CHECK(iter.readBlock());
           case uint16_t(Op::Loop):
-            CHECK(f.iter().readLoop());
+            CHECK(iter.readLoop());
           case uint16_t(Op::If):
-            CHECK(f.iter().readIf(nullptr));
+            CHECK(iter.readIf(nullptr));
           case uint16_t(Op::Else):
-            CHECK(f.iter().readElse(nullptr, nullptr));
+            CHECK(iter.readElse(nullptr, nullptr));
           case uint16_t(Op::I32Clz):
           case uint16_t(Op::I32Ctz):
           case uint16_t(Op::I32Popcnt):
-            CHECK(f.iter().readUnary(ValType::I32, nullptr));
+            CHECK(iter.readUnary(ValType::I32, nullptr));
           case uint16_t(Op::I64Clz):
           case uint16_t(Op::I64Ctz):
           case uint16_t(Op::I64Popcnt):
-            CHECK(f.iter().readUnary(ValType::I64, nullptr));
+            CHECK(iter.readUnary(ValType::I64, nullptr));
           case uint16_t(Op::F32Abs):
           case uint16_t(Op::F32Neg):
           case uint16_t(Op::F32Ceil):
           case uint16_t(Op::F32Floor):
           case uint16_t(Op::F32Sqrt):
           case uint16_t(Op::F32Trunc):
           case uint16_t(Op::F32Nearest):
-            CHECK(f.iter().readUnary(ValType::F32, nullptr));
+            CHECK(iter.readUnary(ValType::F32, nullptr));
           case uint16_t(Op::F64Abs):
           case uint16_t(Op::F64Neg):
           case uint16_t(Op::F64Ceil):
           case uint16_t(Op::F64Floor):
           case uint16_t(Op::F64Sqrt):
           case uint16_t(Op::F64Trunc):
           case uint16_t(Op::F64Nearest):
-            CHECK(f.iter().readUnary(ValType::F64, nullptr));
+            CHECK(iter.readUnary(ValType::F64, nullptr));
           case uint16_t(Op::I32Add):
           case uint16_t(Op::I32Sub):
           case uint16_t(Op::I32Mul):
           case uint16_t(Op::I32DivS):
           case uint16_t(Op::I32DivU):
           case uint16_t(Op::I32RemS):
           case uint16_t(Op::I32RemU):
           case uint16_t(Op::I32And):
           case uint16_t(Op::I32Or):
           case uint16_t(Op::I32Xor):
           case uint16_t(Op::I32Shl):
           case uint16_t(Op::I32ShrS):
           case uint16_t(Op::I32ShrU):
           case uint16_t(Op::I32Rotl):
           case uint16_t(Op::I32Rotr):
-            CHECK(f.iter().readBinary(ValType::I32, nullptr, nullptr));
+            CHECK(iter.readBinary(ValType::I32, nullptr, nullptr));
           case uint16_t(Op::I64Add):
           case uint16_t(Op::I64Sub):
           case uint16_t(Op::I64Mul):
           case uint16_t(Op::I64DivS):
           case uint16_t(Op::I64DivU):
           case uint16_t(Op::I64RemS):
           case uint16_t(Op::I64RemU):
           case uint16_t(Op::I64And):
           case uint16_t(Op::I64Or):
           case uint16_t(Op::I64Xor):
           case uint16_t(Op::I64Shl):
           case uint16_t(Op::I64ShrS):
           case uint16_t(Op::I64ShrU):
           case uint16_t(Op::I64Rotl):
           case uint16_t(Op::I64Rotr):
-            CHECK(f.iter().readBinary(ValType::I64, nullptr, nullptr));
+            CHECK(iter.readBinary(ValType::I64, nullptr, nullptr));
           case uint16_t(Op::F32Add):
           case uint16_t(Op::F32Sub):
           case uint16_t(Op::F32Mul):
           case uint16_t(Op::F32Div):
           case uint16_t(Op::F32Min):
           case uint16_t(Op::F32Max):
           case uint16_t(Op::F32CopySign):
-            CHECK(f.iter().readBinary(ValType::F32, nullptr, nullptr));
+            CHECK(iter.readBinary(ValType::F32, nullptr, nullptr));
           case uint16_t(Op::F64Add):
           case uint16_t(Op::F64Sub):
           case uint16_t(Op::F64Mul):
           case uint16_t(Op::F64Div):
           case uint16_t(Op::F64Min):
           case uint16_t(Op::F64Max):
           case uint16_t(Op::F64CopySign):
-            CHECK(f.iter().readBinary(ValType::F64, nullptr, nullptr));
+            CHECK(iter.readBinary(ValType::F64, nullptr, nullptr));
           case uint16_t(Op::I32Eq):
           case uint16_t(Op::I32Ne):
           case uint16_t(Op::I32LtS):
           case uint16_t(Op::I32LtU):
           case uint16_t(Op::I32LeS):
           case uint16_t(Op::I32LeU):
           case uint16_t(Op::I32GtS):
           case uint16_t(Op::I32GtU):
           case uint16_t(Op::I32GeS):
           case uint16_t(Op::I32GeU):
-            CHECK(f.iter().readComparison(ValType::I32, nullptr, nullptr));
+            CHECK(iter.readComparison(ValType::I32, nullptr, nullptr));
           case uint16_t(Op::I64Eq):
           case uint16_t(Op::I64Ne):
           case uint16_t(Op::I64LtS):
           case uint16_t(Op::I64LtU):
           case uint16_t(Op::I64LeS):
           case uint16_t(Op::I64LeU):
           case uint16_t(Op::I64GtS):
           case uint16_t(Op::I64GtU):
           case uint16_t(Op::I64GeS):
           case uint16_t(Op::I64GeU):
-            CHECK(f.iter().readComparison(ValType::I64, nullptr, nullptr));
+            CHECK(iter.readComparison(ValType::I64, nullptr, nullptr));
           case uint16_t(Op::F32Eq):
           case uint16_t(Op::F32Ne):
           case uint16_t(Op::F32Lt):
           case uint16_t(Op::F32Le):
           case uint16_t(Op::F32Gt):
           case uint16_t(Op::F32Ge):
-            CHECK(f.iter().readComparison(ValType::F32, nullptr, nullptr));
+            CHECK(iter.readComparison(ValType::F32, nullptr, nullptr));
           case uint16_t(Op::F64Eq):
           case uint16_t(Op::F64Ne):
           case uint16_t(Op::F64Lt):
           case uint16_t(Op::F64Le):
           case uint16_t(Op::F64Gt):
           case uint16_t(Op::F64Ge):
-            CHECK(f.iter().readComparison(ValType::F64, nullptr, nullptr));
+            CHECK(iter.readComparison(ValType::F64, nullptr, nullptr));
           case uint16_t(Op::I32Eqz):
-            CHECK(f.iter().readConversion(ValType::I32, ValType::I32, nullptr));
+            CHECK(iter.readConversion(ValType::I32, ValType::I32, nullptr));
           case uint16_t(Op::I64Eqz):
           case uint16_t(Op::I32WrapI64):
-            CHECK(f.iter().readConversion(ValType::I64, ValType::I32, nullptr));
+            CHECK(iter.readConversion(ValType::I64, ValType::I32, nullptr));
           case uint16_t(Op::I32TruncSF32):
           case uint16_t(Op::I32TruncUF32):
           case uint16_t(Op::I32ReinterpretF32):
-            CHECK(f.iter().readConversion(ValType::F32, ValType::I32, nullptr));
+            CHECK(iter.readConversion(ValType::F32, ValType::I32, nullptr));
           case uint16_t(Op::I32TruncSF64):
           case uint16_t(Op::I32TruncUF64):
-            CHECK(f.iter().readConversion(ValType::F64, ValType::I32, nullptr));
+            CHECK(iter.readConversion(ValType::F64, ValType::I32, nullptr));
           case uint16_t(Op::I64ExtendSI32):
           case uint16_t(Op::I64ExtendUI32):
-            CHECK(f.iter().readConversion(ValType::I32, ValType::I64, nullptr));
+            CHECK(iter.readConversion(ValType::I32, ValType::I64, nullptr));
           case uint16_t(Op::I64TruncSF32):
           case uint16_t(Op::I64TruncUF32):
-            CHECK(f.iter().readConversion(ValType::F32, ValType::I64, nullptr));
+            CHECK(iter.readConversion(ValType::F32, ValType::I64, nullptr));
           case uint16_t(Op::I64TruncSF64):
           case uint16_t(Op::I64TruncUF64):
           case uint16_t(Op::I64ReinterpretF64):
-            CHECK(f.iter().readConversion(ValType::F64, ValType::I64, nullptr));
+            CHECK(iter.readConversion(ValType::F64, ValType::I64, nullptr));
           case uint16_t(Op::F32ConvertSI32):
           case uint16_t(Op::F32ConvertUI32):
           case uint16_t(Op::F32ReinterpretI32):
-            CHECK(f.iter().readConversion(ValType::I32, ValType::F32, nullptr));
+            CHECK(iter.readConversion(ValType::I32, ValType::F32, nullptr));
           case uint16_t(Op::F32ConvertSI64):
           case uint16_t(Op::F32ConvertUI64):
-            CHECK(f.iter().readConversion(ValType::I64, ValType::F32, nullptr));
+            CHECK(iter.readConversion(ValType::I64, ValType::F32, nullptr));
           case uint16_t(Op::F32DemoteF64):
-            CHECK(f.iter().readConversion(ValType::F64, ValType::F32, nullptr));
+            CHECK(iter.readConversion(ValType::F64, ValType::F32, nullptr));
           case uint16_t(Op::F64ConvertSI32):
           case uint16_t(Op::F64ConvertUI32):
-            CHECK(f.iter().readConversion(ValType::I32, ValType::F64, nullptr));
+            CHECK(iter.readConversion(ValType::I32, ValType::F64, nullptr));
           case uint16_t(Op::F64ConvertSI64):
           case uint16_t(Op::F64ConvertUI64):
           case uint16_t(Op::F64ReinterpretI64):
-            CHECK(f.iter().readConversion(ValType::I64, ValType::F64, nullptr));
+            CHECK(iter.readConversion(ValType::I64, ValType::F64, nullptr));
           case uint16_t(Op::F64PromoteF32):
-            CHECK(f.iter().readConversion(ValType::F32, ValType::F64, nullptr));
+            CHECK(iter.readConversion(ValType::F32, ValType::F64, nullptr));
           case uint16_t(Op::I32Load8S):
           case uint16_t(Op::I32Load8U):
-            CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I32, 1, nullptr));
+            CHECK(iter.readLoad(ValType::I32, 1, nullptr));
           case uint16_t(Op::I32Load16S):
           case uint16_t(Op::I32Load16U):
-            CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I32, 2, nullptr));
+            CHECK(iter.readLoad(ValType::I32, 2, nullptr));
           case uint16_t(Op::I32Load):
-            CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I32, 4, nullptr));
+            CHECK(iter.readLoad(ValType::I32, 4, nullptr));
           case uint16_t(Op::I64Load8S):
           case uint16_t(Op::I64Load8U):
-            CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I64, 1, nullptr));
+            CHECK(iter.readLoad(ValType::I64, 1, nullptr));
           case uint16_t(Op::I64Load16S):
           case uint16_t(Op::I64Load16U):
-            CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I64, 2, nullptr));
+            CHECK(iter.readLoad(ValType::I64, 2, nullptr));
           case uint16_t(Op::I64Load32S):
           case uint16_t(Op::I64Load32U):
-            CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I64, 4, nullptr));
+            CHECK(iter.readLoad(ValType::I64, 4, nullptr));
           case uint16_t(Op::I64Load):
-            CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I64, 8, nullptr));
+            CHECK(iter.readLoad(ValType::I64, 8, nullptr));
           case uint16_t(Op::F32Load):
-            CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::F32, 4, nullptr));
+            CHECK(iter.readLoad(ValType::F32, 4, nullptr));
           case uint16_t(Op::F64Load):
-            CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::F64, 8, nullptr));
+            CHECK(iter.readLoad(ValType::F64, 8, nullptr));
           case uint16_t(Op::I32Store8):
-            CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I32, 1, nullptr, nullptr));
+            CHECK(iter.readStore(ValType::I32, 1, nullptr, nullptr));
           case uint16_t(Op::I32Store16):
-            CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I32, 2, nullptr, nullptr));
+            CHECK(iter.readStore(ValType::I32, 2, nullptr, nullptr));
           case uint16_t(Op::I32Store):
-            CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I32, 4, nullptr, nullptr));
+            CHECK(iter.readStore(ValType::I32, 4, nullptr, nullptr));
           case uint16_t(Op::I64Store8):
-            CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 1, nullptr, nullptr));
+            CHECK(iter.readStore(ValType::I64, 1, nullptr, nullptr));
           case uint16_t(Op::I64Store16):
-            CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 2, nullptr, nullptr));
+            CHECK(iter.readStore(ValType::I64, 2, nullptr, nullptr));
           case uint16_t(Op::I64Store32):
-            CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 4, nullptr, nullptr));
+            CHECK(iter.readStore(ValType::I64, 4, nullptr, nullptr));
           case uint16_t(Op::I64Store):
-            CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 8, nullptr, nullptr));
+            CHECK(iter.readStore(ValType::I64, 8, nullptr, nullptr));
           case uint16_t(Op::F32Store):
-            CHECK(f.checkHasMemory() && f.iter().readStore(ValType::F32, 4, nullptr, nullptr));
+            CHECK(iter.readStore(ValType::F32, 4, nullptr, nullptr));
           case uint16_t(Op::F64Store):
-            CHECK(f.checkHasMemory() && f.iter().readStore(ValType::F64, 8, nullptr, nullptr));
+            CHECK(iter.readStore(ValType::F64, 8, nullptr, nullptr));
           case uint16_t(Op::GrowMemory):
-            CHECK(f.checkHasMemory() && f.iter().readGrowMemory(nullptr));
+            CHECK(iter.readGrowMemory(nullptr));
           case uint16_t(Op::CurrentMemory):
-            CHECK(f.checkHasMemory() && f.iter().readCurrentMemory());
+            CHECK(iter.readCurrentMemory());
           case uint16_t(Op::Br):
-            CHECK(f.iter().readBr(nullptr, nullptr, nullptr));
+            CHECK(iter.readBr(nullptr, nullptr, nullptr));
           case uint16_t(Op::BrIf):
-            CHECK(f.iter().readBrIf(nullptr, nullptr, nullptr, nullptr));
+            CHECK(iter.readBrIf(nullptr, nullptr, nullptr, nullptr));
           case uint16_t(Op::BrTable):
-            CHECK(DecodeBrTable(f));
+            CHECK(iter.readBrTable(nullptr, nullptr, nullptr, nullptr, nullptr));
           case uint16_t(Op::Return):
-            CHECK(f.iter().readReturn(nullptr));
+            CHECK(iter.readReturn(nullptr));
           case uint16_t(Op::Unreachable):
-            CHECK(f.iter().readUnreachable());
+            CHECK(iter.readUnreachable());
           default:
-            return f.iter().unrecognizedOpcode(op);
+            return iter.unrecognizedOpcode(op);
         }
     }
 
     MOZ_CRASH("unreachable");
 
 #undef CHECK
 }
 
 bool
 wasm::ValidateFunctionBody(const ModuleEnvironment& env, uint32_t funcIndex, uint32_t bodySize,
                            Decoder& d)
 {
+    const Sig& sig = *env.funcSigs[funcIndex];
+
     ValTypeVector locals;
-    const Sig& sig = *env.funcSigs[funcIndex];
     if (!locals.appendAll(sig.args()))
         return false;
 
     const uint8_t* bodyBegin = d.currentPosition();
 
     if (!DecodeLocalEntries(d, ModuleKind::Wasm, &locals))
         return false;
 
-    FunctionDecoder f(env, locals, d);
-
-    if (!f.iter().readFunctionStart(sig.ret()))
-        return false;
-
-    if (!DecodeFunctionBodyExprs(f))
-        return false;
-
-    if (!f.iter().readFunctionEnd())
+    if (!DecodeFunctionBodyExprs(env, sig, locals, &d))
         return false;
 
     if (d.currentPosition() != bodyBegin + bodySize)
         return d.fail("function body length mismatch");
 
     return true;
 }
 
@@ -715,19 +612,20 @@ DecodePreamble(Decoder& d)
 {
     if (d.bytesRemain() > MaxModuleBytes)
         return d.fail("module too big");
 
     uint32_t u32;
     if (!d.readFixedU32(&u32) || u32 != MagicNumber)
         return d.fail("failed to match magic number");
 
-    if (!d.readFixedU32(&u32) || u32 != EncodingVersion)
+    if (!d.readFixedU32(&u32) || (u32 != EncodingVersion && u32 != PrevEncodingVersion)) {
         return d.fail("binary version 0x%" PRIx32 " does not match expected version 0x%" PRIx32,
                       u32, EncodingVersion);
+    }
 
     return true;
 }
 
 static bool
 DecodeTypeSection(Decoder& d, ModuleEnvironment* env)
 {
     uint32_t sectionStart, sectionSize;