Bug 1253137 - Baldr: switch to bottom-up validation in Wasm.cpp (r=sunfish)
authorLuke Wagner <luke@mozilla.com>
Fri, 04 Mar 2016 18:42:57 -0600
changeset 323205 46b17b64ec464a71981e7f157cbe0e9f597fa8a5
parent 323204 3cf0ffedcb7731b4c3b7d89ec8b1c2d01a71c05a
child 323206 d0c4157a7fc6d74975b44864e489afb292845751
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssunfish
bugs1253137
milestone47.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1253137 - Baldr: switch to bottom-up validation in Wasm.cpp (r=sunfish) MozReview-Commit-ID: 8Ml0V6RcoSR
js/src/asmjs/Wasm.cpp
js/src/jit-test/tests/wasm/basic-control-flow.js
js/src/jit-test/tests/wasm/basic-float.js
js/src/jit-test/tests/wasm/basic-integer.js
--- a/js/src/asmjs/Wasm.cpp
+++ b/js/src/asmjs/Wasm.cpp
@@ -54,72 +54,100 @@ Fail(JSContext* cx, Decoder& d, const ch
     uint32_t offset = d.currentOffset();
     char offsetStr[sizeof "4294967295"];
     JS_snprintf(offsetStr, sizeof offsetStr, "%" PRIu32, offset);
     JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_DECODE_FAIL, offsetStr, str);
     return false;
 }
 
 /*****************************************************************************/
-// wasm function body validation
+// wasm validation type lattice
+
+// ExprType::Limit is an out-of-band value and has no wasm-semantic meaning. For
+// the purpose of recursive validation, we use this value to represent the type
+// of branch/return instructions that don't actually return to the parent
+// expression and can thus be used in any context.
+static const ExprType AnyType = ExprType::Limit;
+
+static ExprType
+Unify(ExprType one, ExprType two)
+{
+    if (one == AnyType)
+        return two;
+    if (two == AnyType)
+        return one;
+    if (one == two)
+        return one;
+    return ExprType::Void;
+}
 
 class FunctionDecoder
 {
     JSContext* cx_;
     Decoder& d_;
     ModuleGenerator& mg_;
     FunctionGenerator& fg_;
     uint32_t funcIndex_;
-    uint32_t blockDepth_;
+    Vector<ExprType> blocks_;
 
   public:
     FunctionDecoder(JSContext* cx, Decoder& d, ModuleGenerator& mg, FunctionGenerator& fg,
                     uint32_t funcIndex)
-      : cx_(cx), d_(d), mg_(mg), fg_(fg), funcIndex_(funcIndex), blockDepth_(0)
+      : cx_(cx), d_(d), mg_(mg), fg_(fg), funcIndex_(funcIndex), blocks_(cx)
     {}
     JSContext* cx() const { return cx_; }
     Decoder& d() const { return d_; }
     ModuleGenerator& mg() const { return mg_; }
     FunctionGenerator& fg() const { return fg_; }
     uint32_t funcIndex() const { return funcIndex_; }
     ExprType ret() const { return mg_.funcSig(funcIndex_).ret(); }
 
     bool fail(const char* str) {
         return Fail(cx_, d_, str);
     }
 
-    MOZ_WARN_UNUSED_RESULT bool pushLabel() {
-        ++blockDepth_;
-        return blockDepth_ != 0;
+    MOZ_WARN_UNUSED_RESULT bool pushBlock() {
+        return blocks_.append(AnyType);
+    }
+    ExprType popBlock() {
+        return blocks_.popCopy();
     }
-    void popLabel() {
-        MOZ_ASSERT(blockDepth_ != 0);
-        --blockDepth_;
-    }
-    MOZ_WARN_UNUSED_RESULT bool isLabelInBounds(uint32_t depth) {
-        return depth < blockDepth_;
+    MOZ_WARN_UNUSED_RESULT bool branchWithType(uint32_t depth, ExprType type) {
+        if (depth >= blocks_.length())
+            return false;
+        uint32_t absolute = blocks_.length() - 1 - depth;
+        blocks_[absolute] = Unify(blocks_[absolute], type);
+        return true;
     }
 };
 
 static bool
-CheckType(FunctionDecoder& f, ExprType actual, ExprType expected)
+CheckType(FunctionDecoder& f, ExprType actual, ValType expected)
 {
-    if (actual == expected || expected == ExprType::Void)
+    if (actual == AnyType || actual == ToExprType(expected))
         return true;
 
     UniqueChars error(JS_smprintf("type mismatch: expression has type %s but expected %s",
                                   ToCString(actual), ToCString(expected)));
     if (!error)
         return false;
 
     return f.fail(error.get());
 }
 
 static bool
-DecodeExpr(FunctionDecoder& f, ExprType expected);
+CheckType(FunctionDecoder& f, ExprType actual, ExprType expected)
+{
+    MOZ_ASSERT(expected != AnyType);
+    return expected == ExprType::Void ||
+           CheckType(f, actual, NonVoidToValType(expected));
+}
+
+static bool
+DecodeExpr(FunctionDecoder& f, ExprType* type);
 
 static bool
 DecodeValType(JSContext* cx, Decoder& d, ValType *type)
 {
     if (!d.readValType(type))
         return Fail(cx, d, "bad value type");
 
     switch (*type) {
@@ -167,376 +195,495 @@ DecodeExprType(JSContext* cx, Decoder& d
       case ExprType::Limit:
         MOZ_CRASH("Limit");
     }
 
     return true;
 }
 
 static bool
-DecodeCallWithSig(FunctionDecoder& f, const Sig& sig, ExprType expected)
+DecodeNop(FunctionDecoder& f, ExprType* type)
+{
+    *type = ExprType::Void;
+    return true;
+}
+
+static bool
+DecodeCallWithSig(FunctionDecoder& f, const Sig& sig, ExprType* type)
 {
     for (ValType argType : sig.args()) {
-        if (!DecodeExpr(f, ToExprType(argType)))
+        ExprType exprType;
+        if (!DecodeExpr(f, &exprType))
+            return false;
+
+        if (!CheckType(f, exprType, argType))
             return false;
     }
 
-    return CheckType(f, sig.ret(), expected);
+    *type = sig.ret();
+    return true;
 }
 
 static bool
-DecodeCall(FunctionDecoder& f, ExprType expected)
+DecodeCall(FunctionDecoder& f, ExprType* type)
 {
     uint32_t funcIndex;
     if (!f.d().readVarU32(&funcIndex))
         return f.fail("unable to read import index");
 
     if (funcIndex >= f.mg().numFuncSigs())
         return f.fail("callee index out of range");
 
-    return DecodeCallWithSig(f, f.mg().funcSig(funcIndex), expected);
+    return DecodeCallWithSig(f, f.mg().funcSig(funcIndex), type);
 }
 
 static bool
-DecodeCallImport(FunctionDecoder& f, ExprType expected)
+DecodeCallImport(FunctionDecoder& f, ExprType* type)
 {
     uint32_t importIndex;
     if (!f.d().readVarU32(&importIndex))
         return f.fail("unable to read import index");
 
     if (importIndex >= f.mg().numImports())
         return f.fail("import index out of range");
 
-    return DecodeCallWithSig(f, *f.mg().import(importIndex).sig, expected);
+    return DecodeCallWithSig(f, *f.mg().import(importIndex).sig, type);
 }
 
 static bool
-DecodeCallIndirect(FunctionDecoder& f, ExprType expected)
+DecodeCallIndirect(FunctionDecoder& f, ExprType* type)
 {
     uint32_t sigIndex;
     if (!f.d().readVarU32(&sigIndex))
         return f.fail("unable to read indirect call signature index");
 
     if (sigIndex >= f.mg().numSigs())
         return f.fail("signature index out of range");
 
-    if (!DecodeExpr(f, ExprType::I32))
+    ExprType indexType;
+    if (!DecodeExpr(f, &indexType))
         return false;
 
-    return DecodeCallWithSig(f, f.mg().sig(sigIndex), expected);
+    if (!CheckType(f, indexType, ValType::I32))
+        return false;
+
+    return DecodeCallWithSig(f, f.mg().sig(sigIndex), type);
 }
 
 static bool
-DecodeConstI32(FunctionDecoder& f, ExprType expected)
+DecodeConstI32(FunctionDecoder& f, ExprType* type)
 {
     if (!f.d().readVarU32())
         return f.fail("unable to read i32.const immediate");
 
-    return CheckType(f, ExprType::I32, expected);
+    *type = ExprType::I32;
+    return true;
 }
 
 static bool
-DecodeConstI64(FunctionDecoder& f, ExprType expected)
+DecodeConstI64(FunctionDecoder& f, ExprType* type)
 {
     if (!f.d().readVarU64())
         return f.fail("unable to read i64.const immediate");
 
-    return CheckType(f, ExprType::I64, expected);
+    *type = ExprType::I64;
+    return true;
 }
 
 static bool
-DecodeConstF32(FunctionDecoder& f, ExprType expected)
+DecodeConstF32(FunctionDecoder& f, ExprType* type)
 {
     float value;
     if (!f.d().readFixedF32(&value))
         return f.fail("unable to read f32.const immediate");
+
     if (IsNaN(value)) {
         const float jsNaN = (float)JS::GenericNaN();
         if (memcmp(&value, &jsNaN, sizeof(value)) != 0)
             return f.fail("NYI: NaN literals with custom payloads");
     }
 
-    return CheckType(f, ExprType::F32, expected);
+    *type = ExprType::F32;
+    return true;
 }
 
 static bool
-DecodeConstF64(FunctionDecoder& f, ExprType expected)
+DecodeConstF64(FunctionDecoder& f, ExprType* type)
 {
     double value;
     if (!f.d().readFixedF64(&value))
         return f.fail("unable to read f64.const immediate");
+
     if (IsNaN(value)) {
         const double jsNaN = JS::GenericNaN();
         if (memcmp(&value, &jsNaN, sizeof(value)) != 0)
             return f.fail("NYI: NaN literals with custom payloads");
     }
 
-    return CheckType(f, ExprType::F64, expected);
+    *type = ExprType::F64;
+    return true;
 }
 
 static bool
-DecodeGetLocal(FunctionDecoder& f, ExprType expected)
+DecodeGetLocal(FunctionDecoder& f, ExprType* type)
 {
     uint32_t localIndex;
     if (!f.d().readVarU32(&localIndex))
         return f.fail("unable to read get_local index");
 
     if (localIndex >= f.fg().locals().length())
         return f.fail("get_local index out of range");
 
-    return CheckType(f, ToExprType(f.fg().locals()[localIndex]), expected);
+    *type = ToExprType(f.fg().locals()[localIndex]);
+    return true;
 }
 
 static bool
-DecodeSetLocal(FunctionDecoder& f, ExprType expected)
+DecodeSetLocal(FunctionDecoder& f, ExprType* type)
 {
     uint32_t localIndex;
     if (!f.d().readVarU32(&localIndex))
         return f.fail("unable to read set_local index");
 
     if (localIndex >= f.fg().locals().length())
         return f.fail("set_local index out of range");
 
-    ExprType localType = ToExprType(f.fg().locals()[localIndex]);
+    *type = ToExprType(f.fg().locals()[localIndex]);
 
-    if (!DecodeExpr(f, localType))
+    ExprType rhsType;
+    if (!DecodeExpr(f, &rhsType))
         return false;
 
-    return CheckType(f, localType, expected);
+    return CheckType(f, rhsType, *type);
 }
 
 static bool
-DecodeBlock(FunctionDecoder& f, bool isLoop, ExprType expected)
+DecodeBlock(FunctionDecoder& f, bool isLoop, ExprType* type)
 {
-    if (!f.pushLabel())
+    if (!f.pushBlock())
         return f.fail("nesting overflow");
 
     if (isLoop) {
-        if (!f.pushLabel())
+        if (!f.pushBlock())
             return f.fail("nesting overflow");
     }
 
     uint32_t numExprs;
     if (!f.d().readVarU32(&numExprs))
         return f.fail("unable to read block's number of expressions");
 
-    if (numExprs) {
-        for (uint32_t i = 0; i < numExprs - 1; i++) {
-            if (!DecodeExpr(f, ExprType::Void))
-                return false;
-        }
-        if (!DecodeExpr(f, expected))
-            return false;
-    } else {
-        if (!CheckType(f, ExprType::Void, expected))
+    ExprType exprType = ExprType::Void;
+
+    for (uint32_t i = 0; i < numExprs; i++) {
+        if (!DecodeExpr(f, &exprType))
             return false;
     }
 
     if (isLoop)
-        f.popLabel();
+        f.popBlock();
+
+    ExprType branchType = f.popBlock();
+    *type = Unify(branchType, exprType);
+    return true;
+}
+
+static bool
+DecodeUnaryOperator(FunctionDecoder& f, ValType argType, ExprType *type)
+{
+    ExprType actual;
+    if (!DecodeExpr(f, &actual))
+        return false;
+
+    if (!CheckType(f, actual, argType))
+        return false;
 
-    f.popLabel();
+    *type = ToExprType(argType);
+    return true;
+}
+
+static bool
+DecodeBinaryOperator(FunctionDecoder& f, ValType argType, ExprType* type)
+{
+    ExprType actual;
 
+    if (!DecodeExpr(f, &actual))
+        return false;
+
+    if (!CheckType(f, actual, argType))
+        return false;
+
+    if (!DecodeExpr(f, &actual))
+        return false;
+
+    if (!CheckType(f, actual, argType))
+        return false;
+
+    *type = ToExprType(argType);
     return true;
 }
 
 static bool
-DecodeUnaryOperator(FunctionDecoder& f, ExprType expected, ExprType type)
+DecodeComparisonOperator(FunctionDecoder& f, ValType argType, ExprType* type)
 {
-    return CheckType(f, type, expected) &&
-           DecodeExpr(f, type);
-}
+    ExprType actual;
+
+    if (!DecodeExpr(f, &actual))
+        return false;
 
-static bool
-DecodeBinaryOperator(FunctionDecoder& f, ExprType expected, ExprType type)
-{
-    return CheckType(f, type, expected) &&
-           DecodeExpr(f, type) &&
-           DecodeExpr(f, type);
+    if (!CheckType(f, actual, argType))
+        return false;
+
+    if (!DecodeExpr(f, &actual))
+        return false;
+
+    if (!CheckType(f, actual, argType))
+        return false;
+
+    *type = ExprType::I32;
+    return true;
 }
 
 static bool
-DecodeComparisonOperator(FunctionDecoder& f, ExprType expected, ExprType type)
+DecodeConversionOperator(FunctionDecoder& f, ValType to, ValType argType, ExprType* type)
 {
-    return CheckType(f, ExprType::I32, expected) &&
-           DecodeExpr(f, type) &&
-           DecodeExpr(f, type);
+    ExprType actual;
+    if (!DecodeExpr(f, &actual))
+        return false;
+
+    if (!CheckType(f, actual, argType))
+        return false;
+
+    *type = ToExprType(to);
+    return true;
 }
 
 static bool
-DecodeConversionOperator(FunctionDecoder& f, ExprType expected,
-                         ExprType dstType, ExprType srcType)
+DecodeIfElse(FunctionDecoder& f, bool hasElse, ExprType* type)
 {
-    return CheckType(f, dstType, expected) &&
-           DecodeExpr(f, srcType);
-}
+    ExprType condType;
+    if (!DecodeExpr(f, &condType))
+        return false;
+
+    if (!CheckType(f, condType, ValType::I32))
+        return false;
 
-static bool
-DecodeIfElse(FunctionDecoder& f, bool hasElse, ExprType expected)
-{
-    return DecodeExpr(f, ExprType::I32) &&
-           DecodeExpr(f, expected) &&
-           (hasElse
-            ? DecodeExpr(f, expected)
-            : CheckType(f, ExprType::Void, expected));
+    ExprType thenType;
+    if (!DecodeExpr(f, &thenType))
+        return false;
+
+    if (hasElse) {
+        ExprType elseType;
+        if (!DecodeExpr(f, &elseType))
+            return false;
+
+        *type = Unify(thenType, elseType);
+    } else {
+        *type = ExprType::Void;
+    }
+
+    return true;
 }
 
 static bool
 DecodeLoadStoreAddress(FunctionDecoder &f)
 {
     uint32_t offset;
     if (!f.d().readVarU32(&offset))
         return f.fail("expected memory access offset");
 
     uint32_t align;
     if (!f.d().readVarU32(&align))
         return f.fail("expected memory access alignment");
 
     if (!mozilla::IsPowerOfTwo(align))
         return f.fail("memory access alignment must be a power of two");
 
-    return DecodeExpr(f, ExprType::I32);
+    ExprType baseType;
+    if (!DecodeExpr(f, &baseType))
+        return false;
+
+    return CheckType(f, baseType, ExprType::I32);
+}
+
+static bool
+DecodeLoad(FunctionDecoder& f, ValType loadType, ExprType* type)
+{
+    if (!DecodeLoadStoreAddress(f))
+        return false;
+
+    *type = ToExprType(loadType);
+    return true;
 }
 
 static bool
-DecodeLoad(FunctionDecoder& f, ExprType expected, ExprType type)
+DecodeStore(FunctionDecoder& f, ValType storeType, ExprType* type)
 {
-    return DecodeLoadStoreAddress(f) &&
-           CheckType(f, type, expected);
+    if (!DecodeLoadStoreAddress(f))
+        return false;
+
+    ExprType actual;
+    if (!DecodeExpr(f, &actual))
+        return false;
+
+    if (!CheckType(f, actual, storeType))
+        return false;
+
+    *type = ToExprType(storeType);
+    return true;
 }
 
 static bool
-DecodeStore(FunctionDecoder& f, ExprType expected, ExprType type)
-{
-    return DecodeLoadStoreAddress(f) &&
-           DecodeExpr(f, type) &&
-           CheckType(f, type, expected);
-}
-
-static bool
-DecodeBr(FunctionDecoder& f, ExprType expected)
+DecodeBr(FunctionDecoder& f, ExprType* type)
 {
     uint32_t relativeDepth;
     if (!f.d().readVarU32(&relativeDepth))
         return f.fail("expected relative depth");
 
-    if (!f.isLabelInBounds(relativeDepth))
+    if (!f.branchWithType(relativeDepth, ExprType::Void))
         return f.fail("branch depth exceeds current nesting level");
 
-    return CheckType(f, ExprType::Void, expected);
+    *type = AnyType;
+    return true;
 }
 
 static bool
-DecodeBrIf(FunctionDecoder& f, ExprType expected)
+DecodeBrIf(FunctionDecoder& f, ExprType* type)
 {
     uint32_t relativeDepth;
     if (!f.d().readVarU32(&relativeDepth))
         return f.fail("expected relative depth");
 
-    if (!f.isLabelInBounds(relativeDepth))
+    if (!f.branchWithType(relativeDepth, ExprType::Void))
         return f.fail("branch depth exceeds current nesting level");
 
-    return CheckType(f, ExprType::Void, expected) &&
-           DecodeExpr(f, ExprType::I32);
+    ExprType actual;
+    if (!DecodeExpr(f, &actual))
+        return false;
+
+    if (!CheckType(f, actual, ValType::I32))
+        return false;
+
+    *type = ExprType::Void;
+    return true;
 }
 
 static bool
-DecodeBrTable(FunctionDecoder& f)
+DecodeBrTable(FunctionDecoder& f, ExprType* type)
 {
     uint32_t tableLength;
     if (!f.d().readVarU32(&tableLength))
         return false;
+
     if (tableLength > MaxBrTableElems)
         return f.fail("too many br_table entries");
 
     for (uint32_t i = 0; i < tableLength; i++) {
         uint32_t depth;
         if (!f.d().readVarU32(&depth))
             return f.fail("missing br_table entry");
-        if (!f.isLabelInBounds(depth))
+
+        if (!f.branchWithType(depth, ExprType::Void))
             return f.fail("branch depth exceeds current nesting level");
     }
 
     uint32_t defaultDepth;
     if (!f.d().readVarU32(&defaultDepth))
         return f.fail("expected default relative depth");
-    if (!f.isLabelInBounds(defaultDepth))
+
+    if (!f.branchWithType(defaultDepth, ExprType::Void))
         return f.fail("branch depth exceeds current nesting level");
 
-    return DecodeExpr(f, ExprType::I32);
+    ExprType actual;
+    if (!DecodeExpr(f, &actual))
+        return false;
+
+    if (!CheckType(f, actual, ExprType::I32))
+        return false;
+
+    *type = AnyType;
+    return true;
 }
 
 static bool
-DecodeReturn(FunctionDecoder& f)
+DecodeReturn(FunctionDecoder& f, ExprType* type)
 {
-    return f.ret() == ExprType::Void ||
-           DecodeExpr(f, f.ret());
+    if (f.ret() != ExprType::Void) {
+        ExprType actual;
+        if (!DecodeExpr(f, &actual))
+            return false;
+
+        if (!CheckType(f, actual, f.ret()))
+            return false;
+    }
+
+    *type = AnyType;
+    return true;
 }
 
 static bool
-DecodeExpr(FunctionDecoder& f, ExprType expected)
+DecodeExpr(FunctionDecoder& f, ExprType* type)
 {
     Expr expr;
     if (!f.d().readExpr(&expr))
         return f.fail("unable to read expression");
 
     switch (expr) {
       case Expr::Nop:
-        return CheckType(f, ExprType::Void, expected);
+        return DecodeNop(f, type);
       case Expr::Call:
-        return DecodeCall(f, expected);
+        return DecodeCall(f, type);
       case Expr::CallImport:
-        return DecodeCallImport(f, expected);
+        return DecodeCallImport(f, type);
       case Expr::CallIndirect:
-        return DecodeCallIndirect(f, expected);
+        return DecodeCallIndirect(f, type);
       case Expr::I32Const:
-        return DecodeConstI32(f, expected);
+        return DecodeConstI32(f, type);
       case Expr::I64Const:
-        return DecodeConstI64(f, expected);
+        return DecodeConstI64(f, type);
       case Expr::F32Const:
-        return DecodeConstF32(f, expected);
+        return DecodeConstF32(f, type);
       case Expr::F64Const:
-        return DecodeConstF64(f, expected);
+        return DecodeConstF64(f, type);
       case Expr::GetLocal:
-        return DecodeGetLocal(f, expected);
+        return DecodeGetLocal(f, type);
       case Expr::SetLocal:
-        return DecodeSetLocal(f, expected);
+        return DecodeSetLocal(f, type);
       case Expr::Block:
-        return DecodeBlock(f, /* isLoop */ false, expected);
+        return DecodeBlock(f, /* isLoop */ false, type);
       case Expr::Loop:
-        return DecodeBlock(f, /* isLoop */ true, expected);
+        return DecodeBlock(f, /* isLoop */ true, type);
       case Expr::If:
-        return DecodeIfElse(f, /* hasElse */ false, expected);
+        return DecodeIfElse(f, /* hasElse */ false, type);
       case Expr::IfElse:
-        return DecodeIfElse(f, /* hasElse */ true, expected);
+        return DecodeIfElse(f, /* hasElse */ true, type);
       case Expr::I32Clz:
       case Expr::I32Ctz:
       case Expr::I32Popcnt:
-        return DecodeUnaryOperator(f, expected, ExprType::I32);
+        return DecodeUnaryOperator(f, ValType::I32, type);
       case Expr::I64Clz:
       case Expr::I64Ctz:
       case Expr::I64Popcnt:
         return f.fail("NYI: i64") &&
-               DecodeUnaryOperator(f, expected, ExprType::I64);
+               DecodeUnaryOperator(f, ValType::I64, type);
       case Expr::F32Abs:
       case Expr::F32Neg:
       case Expr::F32Ceil:
       case Expr::F32Floor:
       case Expr::F32Sqrt:
-        return DecodeUnaryOperator(f, expected, ExprType::F32);
+        return DecodeUnaryOperator(f, ValType::F32, type);
       case Expr::F32Trunc:
         return f.fail("NYI: trunc");
       case Expr::F32Nearest:
         return f.fail("NYI: nearest");
       case Expr::F64Abs:
       case Expr::F64Neg:
       case Expr::F64Ceil:
       case Expr::F64Floor:
       case Expr::F64Sqrt:
-        return DecodeUnaryOperator(f, expected, ExprType::F64);
+        return DecodeUnaryOperator(f, ValType::F64, type);
       case Expr::F64Trunc:
         return f.fail("NYI: trunc");
       case Expr::F64Nearest:
         return f.fail("NYI: nearest");
       case Expr::I32Add:
       case Expr::I32Sub:
       case Expr::I32Mul:
       case Expr::I32DivS:
@@ -544,168 +691,168 @@ DecodeExpr(FunctionDecoder& f, ExprType 
       case Expr::I32RemS:
       case Expr::I32RemU:
       case Expr::I32And:
       case Expr::I32Or:
       case Expr::I32Xor:
       case Expr::I32Shl:
       case Expr::I32ShrS:
       case Expr::I32ShrU:
-        return DecodeBinaryOperator(f, expected, ExprType::I32);
+        return DecodeBinaryOperator(f, ValType::I32, type);
       case Expr::I64Add:
       case Expr::I64Sub:
       case Expr::I64Mul:
       case Expr::I64DivS:
       case Expr::I64DivU:
       case Expr::I64RemS:
       case Expr::I64RemU:
       case Expr::I64And:
       case Expr::I64Or:
       case Expr::I64Xor:
       case Expr::I64Shl:
       case Expr::I64ShrS:
       case Expr::I64ShrU:
-        return DecodeBinaryOperator(f, expected, ExprType::I64);
+        return DecodeBinaryOperator(f, ValType::I64, type);
       case Expr::F32Add:
       case Expr::F32Sub:
       case Expr::F32Mul:
       case Expr::F32Div:
       case Expr::F32Min:
       case Expr::F32Max:
-        return DecodeBinaryOperator(f, expected, ExprType::F32);
+        return DecodeBinaryOperator(f, ValType::F32, type);
       case Expr::F32CopySign:
         return f.fail("NYI: copysign");
       case Expr::F64Add:
       case Expr::F64Sub:
       case Expr::F64Mul:
       case Expr::F64Div:
       case Expr::F64Min:
       case Expr::F64Max:
-        return DecodeBinaryOperator(f, expected, ExprType::F64);
+        return DecodeBinaryOperator(f, ValType::F64, type);
       case Expr::F64CopySign:
         return f.fail("NYI: copysign");
       case Expr::I32Eq:
       case Expr::I32Ne:
       case Expr::I32LtS:
       case Expr::I32LtU:
       case Expr::I32LeS:
       case Expr::I32LeU:
       case Expr::I32GtS:
       case Expr::I32GtU:
       case Expr::I32GeS:
       case Expr::I32GeU:
-        return DecodeComparisonOperator(f, expected, ExprType::I32);
+        return DecodeComparisonOperator(f, ValType::I32, type);
       case Expr::I64Eq:
       case Expr::I64Ne:
       case Expr::I64LtS:
       case Expr::I64LtU:
       case Expr::I64LeS:
       case Expr::I64LeU:
       case Expr::I64GtS:
       case Expr::I64GtU:
       case Expr::I64GeS:
       case Expr::I64GeU:
-        return DecodeComparisonOperator(f, expected, ExprType::I64);
+        return DecodeComparisonOperator(f, ValType::I64, type);
       case Expr::F32Eq:
       case Expr::F32Ne:
       case Expr::F32Lt:
       case Expr::F32Le:
       case Expr::F32Gt:
       case Expr::F32Ge:
-        return DecodeComparisonOperator(f, expected, ExprType::F32);
+        return DecodeComparisonOperator(f, ValType::F32, type);
       case Expr::F64Eq:
       case Expr::F64Ne:
       case Expr::F64Lt:
       case Expr::F64Le:
       case Expr::F64Gt:
       case Expr::F64Ge:
-        return DecodeComparisonOperator(f, expected, ExprType::F64);
+        return DecodeComparisonOperator(f, ValType::F64, type);
       case Expr::I32WrapI64:
-        return DecodeConversionOperator(f, expected, ExprType::I32, ExprType::I64);
+        return DecodeConversionOperator(f, ValType::I32, ValType::I64, type);
       case Expr::I32TruncSF32:
       case Expr::I32TruncUF32:
-        return DecodeConversionOperator(f, expected, ExprType::I32, ExprType::F32);
+        return DecodeConversionOperator(f, ValType::I32, ValType::F32, type);
       case Expr::I32ReinterpretF32:
         return f.fail("NYI: reinterpret");
       case Expr::I32TruncSF64:
       case Expr::I32TruncUF64:
-        return DecodeConversionOperator(f, expected, ExprType::I32, ExprType::F64);
+        return DecodeConversionOperator(f, ValType::I32, ValType::F64, type);
       case Expr::I64ExtendSI32:
       case Expr::I64ExtendUI32:
-        return DecodeConversionOperator(f, expected, ExprType::I64, ExprType::I32);
+        return DecodeConversionOperator(f, ValType::I64, ValType::I32, type);
       case Expr::I64TruncSF32:
       case Expr::I64TruncUF32:
-        return DecodeConversionOperator(f, expected, ExprType::I64, ExprType::F32);
+        return DecodeConversionOperator(f, ValType::I64, ValType::F32, type);
       case Expr::I64TruncSF64:
       case Expr::I64TruncUF64:
-        return DecodeConversionOperator(f, expected, ExprType::I64, ExprType::F64);
+        return DecodeConversionOperator(f, ValType::I64, ValType::F64, type);
       case Expr::I64ReinterpretF64:
         return f.fail("NYI: i64");
       case Expr::F32ConvertSI32:
       case Expr::F32ConvertUI32:
-        return DecodeConversionOperator(f, expected, ExprType::F32, ExprType::I32);
+        return DecodeConversionOperator(f, ValType::F32, ValType::I32, type);
       case Expr::F32ReinterpretI32:
         return f.fail("NYI: reinterpret");
       case Expr::F32ConvertSI64:
       case Expr::F32ConvertUI64:
         return f.fail("NYI: i64") &&
-               DecodeConversionOperator(f, expected, ExprType::F32, ExprType::I64);
+               DecodeConversionOperator(f, ValType::F32, ValType::I64, type);
       case Expr::F32DemoteF64:
-        return DecodeConversionOperator(f, expected, ExprType::F32, ExprType::F64);
+        return DecodeConversionOperator(f, ValType::F32, ValType::F64, type);
       case Expr::F64ConvertSI32:
       case Expr::F64ConvertUI32:
-        return DecodeConversionOperator(f, expected, ExprType::F64, ExprType::I32);
+        return DecodeConversionOperator(f, ValType::F64, ValType::I32, type);
       case Expr::F64ConvertSI64:
       case Expr::F64ConvertUI64:
       case Expr::F64ReinterpretI64:
         return f.fail("NYI: i64") &&
-               DecodeConversionOperator(f, expected, ExprType::F64, ExprType::I64);
+               DecodeConversionOperator(f, ValType::F64, ValType::I64, type);
       case Expr::F64PromoteF32:
-        return DecodeConversionOperator(f, expected, ExprType::F64, ExprType::F32);
+        return DecodeConversionOperator(f, ValType::F64, ValType::F32, type);
       case Expr::I32LoadMem:
       case Expr::I32LoadMem8S:
       case Expr::I32LoadMem8U:
       case Expr::I32LoadMem16S:
       case Expr::I32LoadMem16U:
-        return DecodeLoad(f, expected, ExprType::I32);
+        return DecodeLoad(f, ValType::I32, type);
       case Expr::I64LoadMem:
       case Expr::I64LoadMem8S:
       case Expr::I64LoadMem8U:
       case Expr::I64LoadMem16S:
       case Expr::I64LoadMem16U:
       case Expr::I64LoadMem32S:
       case Expr::I64LoadMem32U:
         return f.fail("NYI: i64") &&
-               DecodeLoad(f, expected, ExprType::I64);
+               DecodeLoad(f, ValType::I64, type);
       case Expr::F32LoadMem:
-        return DecodeLoad(f, expected, ExprType::F32);
+        return DecodeLoad(f, ValType::F32, type);
       case Expr::F64LoadMem:
-        return DecodeLoad(f, expected, ExprType::F64);
+        return DecodeLoad(f, ValType::F64, type);
       case Expr::I32StoreMem:
       case Expr::I32StoreMem8:
       case Expr::I32StoreMem16:
-        return DecodeStore(f, expected, ExprType::I32);
+        return DecodeStore(f, ValType::I32, type);
       case Expr::I64StoreMem:
       case Expr::I64StoreMem8:
       case Expr::I64StoreMem16:
       case Expr::I64StoreMem32:
         return f.fail("NYI: i64") &&
-               DecodeStore(f, expected, ExprType::I64);
+               DecodeStore(f, ValType::I64, type);
       case Expr::F32StoreMem:
-        return DecodeStore(f, expected, ExprType::F32);
+        return DecodeStore(f, ValType::F32, type);
       case Expr::F64StoreMem:
-        return DecodeStore(f, expected, ExprType::F64);
+        return DecodeStore(f, ValType::F64, type);
       case Expr::Br:
-        return DecodeBr(f, expected);
+        return DecodeBr(f, type);
       case Expr::BrIf:
-        return DecodeBrIf(f, expected);
+        return DecodeBrIf(f, type);
       case Expr::BrTable:
-        return DecodeBrTable(f);
+        return DecodeBrTable(f, type);
       case Expr::Return:
-        return DecodeReturn(f);
+        return DecodeReturn(f, type);
       default:
         break;
     }
 
     return f.fail("bad expression code");
 }
 
 /*****************************************************************************/
@@ -1118,30 +1265,25 @@ DecodeFunctionBody(JSContext* cx, Decode
     const uint8_t* bodyBegin = d.currentPosition();
 
     FunctionDecoder f(cx, d, mg, fg, funcIndex);
 
     uint32_t numExprs;
     if (!d.readVarU32(&numExprs))
         return Fail(cx, d, "expected number of function body expressions");
 
-    if (numExprs) {
-        for (size_t i = 0; i < numExprs - 1; i++) {
-            if (!DecodeExpr(f, ExprType::Void))
-                return false;
-        }
+    ExprType type = ExprType::Void;
 
-        if (!DecodeExpr(f, f.ret()))
-            return false;
-    } else {
-        if (!CheckType(f, ExprType::Void, f.ret()))
+    for (size_t i = 0; i < numExprs; i++) {
+        if (!DecodeExpr(f, &type))
             return false;
     }
 
-    MOZ_ASSERT(!f.isLabelInBounds(0));
+    if (!CheckType(f, type, f.ret()))
+        return false;
 
     const uint8_t* bodyEnd = d.currentPosition();
     uintptr_t bodyLength = bodyEnd - bodyBegin;
     if (!fg.bytecode().resize(bodyLength))
         return false;
 
     memcpy(fg.bytecode().begin(), bodyBegin, bodyLength);
 
--- a/js/src/jit-test/tests/wasm/basic-control-flow.js
+++ b/js/src/jit-test/tests/wasm/basic-control-flow.js
@@ -6,18 +6,18 @@ load(libdir + "wasm.js");
 // Condition is an int32
 assertErrorMessage(() => wasmEvalText('(module (func (local f32) (if (get_local 0) (i32.const 1))))'), TypeError, mismatchError("f32", "i32"));
 assertErrorMessage(() => wasmEvalText('(module (func (local f32) (if_else (get_local 0) (i32.const 1) (i32.const 0))))'), TypeError, mismatchError("f32", "i32"));
 assertErrorMessage(() => wasmEvalText('(module (func (local f64) (if_else (get_local 0) (i32.const 1) (i32.const 0))))'), TypeError, mismatchError("f64", "i32"));
 wasmEvalText('(module (func (local i32) (if (get_local 0) (nop))) (export "" 0))');
 wasmEvalText('(module (func (local i32) (if_else (get_local 0) (nop) (nop))) (export "" 0))');
 
 // Expression values types are consistent
-assertErrorMessage(() => wasmEvalText('(module (func (result i32) (local f32) (if_else (i32.const 42) (get_local 0) (i32.const 0))))'), TypeError, mismatchError("f32", "i32"));
-assertErrorMessage(() => wasmEvalText('(module (func (result i32) (local f64) (if_else (i32.const 42) (i32.const 0) (get_local 0))))'), TypeError, mismatchError("f64", "i32"));
+assertErrorMessage(() => wasmEvalText('(module (func (result i32) (local f32) (if_else (i32.const 42) (get_local 0) (i32.const 0))))'), TypeError, mismatchError("void", "i32"));
+assertErrorMessage(() => wasmEvalText('(module (func (result i32) (local f64) (if_else (i32.const 42) (i32.const 0) (get_local 0))))'), TypeError, mismatchError("void", "i32"));
 assertEq(wasmEvalText('(module (func (result i32) (if_else (i32.const 42) (i32.const 1) (i32.const 2))) (export "" 0))')(), 1);
 assertEq(wasmEvalText('(module (func (result i32) (if_else (i32.const 0) (i32.const 1) (i32.const 2))) (export "" 0))')(), 2);
 
 // If we don't yield, sub expressions types don't have to match
 assertEq(wasmEvalText('(module (func (if_else (i32.const 42) (i32.const 1) (i32.const 0))) (export "" 0))')(), undefined);
 assertEq(wasmEvalText('(module (func (param f32) (if_else (i32.const 42) (i32.const 1) (get_local 0))) (export "" 0))')(13.37), undefined);
 
 // Sub-expression values are returned
@@ -165,17 +165,17 @@ assertErrorMessage(() => wasmEvalText(`(
     (if_else
       (br 0)
       (i32.const 0)
       (i32.const 2)
     )
   )
 ) (export "" 0))`), TypeError, mismatchError("void", "i32"));
 
-assertErrorMessage(() => wasmEvalText(`(module (func (block $out (br_if $out (br 0)))) (export "" 0))`), TypeError, mismatchError("void", "i32"));
+assertEq(wasmEvalText(`(module (func (block $out (br_if $out (br 0)))) (export "" 0))`)(), undefined);
 
 assertEq(wasmEvalText('(module (func (block (br 0))) (export "" 0))')(), undefined);
 assertEq(wasmEvalText('(module (func (block $l (br $l))) (export "" 0))')(), undefined);
 
 assertEq(wasmEvalText('(module (func (block (block (br 1)))) (export "" 0))')(), undefined);
 assertEq(wasmEvalText('(module (func (block $l (block (br $l)))) (export "" 0))')(), undefined);
 
 assertEq(wasmEvalText('(module (func (block $l (block $m (br $l)))) (export "" 0))')(), undefined);
--- a/js/src/jit-test/tests/wasm/basic-float.js
+++ b/js/src/jit-test/tests/wasm/basic-float.js
@@ -68,29 +68,29 @@ testComparison('f64', 'eq', 40, 40, 1);
 testComparison('f64', 'ne', 40, 40, 0);
 testComparison('f64', 'lt', 40, 40, 0);
 testComparison('f64', 'le', 40, 40, 1);
 testComparison('f64', 'gt', 40, 40, 0);
 testComparison('f64', 'ge', 40, 40, 1);
 
 assertErrorMessage(() => wasmEvalText('(module (func (param i32) (result f32) (f32.sqrt (get_local 0))))'), TypeError, mismatchError("i32", "f32"));
 assertErrorMessage(() => wasmEvalText('(module (func (param f32) (result i32) (f32.sqrt (get_local 0))))'), TypeError, mismatchError("f32", "i32"));
-assertErrorMessage(() => wasmEvalText('(module (func (param i32) (result i32) (f32.sqrt (get_local 0))))'), TypeError, mismatchError("f32", "i32"));
+assertErrorMessage(() => wasmEvalText('(module (func (param i32) (result i32) (f32.sqrt (get_local 0))))'), TypeError, mismatchError("i32", "f32"));
 assertErrorMessage(() => wasmEvalText('(module (func (param i32) (result f64) (f64.sqrt (get_local 0))))'), TypeError, mismatchError("i32", "f64"));
 assertErrorMessage(() => wasmEvalText('(module (func (param f64) (result i32) (f64.sqrt (get_local 0))))'), TypeError, mismatchError("f64", "i32"));
-assertErrorMessage(() => wasmEvalText('(module (func (param i32) (result i32) (f64.sqrt (get_local 0))))'), TypeError, mismatchError("f64", "i32"));
+assertErrorMessage(() => wasmEvalText('(module (func (param i32) (result i32) (f64.sqrt (get_local 0))))'), TypeError, mismatchError("i32", "f64"));
 assertErrorMessage(() => wasmEvalText('(module (func (f32.sqrt (nop))))'), TypeError, mismatchError("void", "f32"));
 
 assertErrorMessage(() => wasmEvalText('(module (func (param i32) (param f32) (result f32) (f32.add (get_local 0) (get_local 1))))'), TypeError, mismatchError("i32", "f32"));
 assertErrorMessage(() => wasmEvalText('(module (func (param f32) (param i32) (result f32) (f32.add (get_local 0) (get_local 1))))'), TypeError, mismatchError("i32", "f32"));
 assertErrorMessage(() => wasmEvalText('(module (func (param f32) (param f32) (result i32) (f32.add (get_local 0) (get_local 1))))'), TypeError, mismatchError("f32", "i32"));
-assertErrorMessage(() => wasmEvalText('(module (func (param i32) (param i32) (result i32) (f32.add (get_local 0) (get_local 1))))'), TypeError, mismatchError("f32", "i32"));
+assertErrorMessage(() => wasmEvalText('(module (func (param i32) (param i32) (result i32) (f32.add (get_local 0) (get_local 1))))'), TypeError, mismatchError("i32", "f32"));
 assertErrorMessage(() => wasmEvalText('(module (func (param i32) (param f64) (result f64) (f64.add (get_local 0) (get_local 1))))'), TypeError, mismatchError("i32", "f64"));
 assertErrorMessage(() => wasmEvalText('(module (func (param f64) (param i32) (result f64) (f64.add (get_local 0) (get_local 1))))'), TypeError, mismatchError("i32", "f64"));
 assertErrorMessage(() => wasmEvalText('(module (func (param f64) (param f64) (result i32) (f64.add (get_local 0) (get_local 1))))'), TypeError, mismatchError("f64", "i32"));
-assertErrorMessage(() => wasmEvalText('(module (func (param i32) (param i32) (result i32) (f64.add (get_local 0) (get_local 1))))'), TypeError, mismatchError("f64", "i32"));
+assertErrorMessage(() => wasmEvalText('(module (func (param i32) (param i32) (result i32) (f64.add (get_local 0) (get_local 1))))'), TypeError, mismatchError("i32", "f64"));
 
 assertErrorMessage(() => wasmEvalText('(module (func (param i32) (param f32) (result f32) (f32.eq (get_local 0) (get_local 1))))'), TypeError, mismatchError("i32", "f32"));
 assertErrorMessage(() => wasmEvalText('(module (func (param f32) (param i32) (result f32) (f32.eq (get_local 0) (get_local 1))))'), TypeError, mismatchError("i32", "f32"));
 assertErrorMessage(() => wasmEvalText('(module (func (param f32) (param f32) (result f32) (f32.eq (get_local 0) (get_local 1))))'), TypeError, mismatchError("i32", "f32"));
 assertErrorMessage(() => wasmEvalText('(module (func (param i32) (param f64) (result f64) (f64.eq (get_local 0) (get_local 1))))'), TypeError, mismatchError("i32", "f64"));
 assertErrorMessage(() => wasmEvalText('(module (func (param f64) (param i32) (result f64) (f64.eq (get_local 0) (get_local 1))))'), TypeError, mismatchError("i32", "f64"));
 assertErrorMessage(() => wasmEvalText('(module (func (param f64) (param f64) (result f64) (f64.eq (get_local 0) (get_local 1))))'), TypeError, mismatchError("i32", "f64"));
--- a/js/src/jit-test/tests/wasm/basic-integer.js
+++ b/js/src/jit-test/tests/wasm/basic-integer.js
@@ -164,18 +164,18 @@ if (getBuildConfiguration().x64) {
         assertEq(0, 1);
     } catch(e) {
         assertEq(e.toString().indexOf("NYI on this platform") >= 0, true);
     }
 }
 
 assertErrorMessage(() => wasmEvalText('(module (func (param f32) (result i32) (i32.clz (get_local 0))))'), TypeError, mismatchError("f32", "i32"));
 assertErrorMessage(() => wasmEvalText('(module (func (param i32) (result f32) (i32.clz (get_local 0))))'), TypeError, mismatchError("i32", "f32"));
-assertErrorMessage(() => wasmEvalText('(module (func (param f32) (result f32) (i32.clz (get_local 0))))'), TypeError, mismatchError("i32", "f32"));
+assertErrorMessage(() => wasmEvalText('(module (func (param f32) (result f32) (i32.clz (get_local 0))))'), TypeError, mismatchError("f32", "i32"));
 
 assertErrorMessage(() => wasmEvalText('(module (func (param f32) (param i32) (result i32) (i32.add (get_local 0) (get_local 1))))'), TypeError, mismatchError("f32", "i32"));
 assertErrorMessage(() => wasmEvalText('(module (func (param i32) (param f32) (result i32) (i32.add (get_local 0) (get_local 1))))'), TypeError, mismatchError("f32", "i32"));
 assertErrorMessage(() => wasmEvalText('(module (func (param i32) (param i32) (result f32) (i32.add (get_local 0) (get_local 1))))'), TypeError, mismatchError("i32", "f32"));
-assertErrorMessage(() => wasmEvalText('(module (func (param f32) (param f32) (result f32) (i32.add (get_local 0) (get_local 1))))'), TypeError, mismatchError("i32", "f32"));
+assertErrorMessage(() => wasmEvalText('(module (func (param f32) (param f32) (result f32) (i32.add (get_local 0) (get_local 1))))'), TypeError, mismatchError("f32", "i32"));
 
 assertErrorMessage(() => wasmEvalText('(module (func (param f32) (param i32) (result i32) (i32.eq (get_local 0) (get_local 1))))'), TypeError, mismatchError("f32", "i32"));
 assertErrorMessage(() => wasmEvalText('(module (func (param i32) (param f32) (result i32) (i32.eq (get_local 0) (get_local 1))))'), TypeError, mismatchError("f32", "i32"));
 assertErrorMessage(() => wasmEvalText('(module (func (param i32) (param i32) (result f32) (i32.eq (get_local 0) (get_local 1))))'), TypeError, mismatchError("i32", "f32"));