Bug 1229399: Remove expression statements opcodes in wasm; r=luke
authorBenjamin Bouvier <benj@benj.me>
Thu, 21 Jan 2016 12:31:45 +0100
changeset 280926 a8b5e254799b4ba7050c2291cc3b07cf6c7ee98d
parent 280925 95a25c159a0ebe141f0c87ad2ba6cd0b7ac1d316
child 280927 9809139812ebe073b84d0fa12705062b1b50845b
push id70624
push userbenj@benj.me
push dateThu, 21 Jan 2016 13:26:42 +0000
treeherdermozilla-inbound@9809139812eb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1229399
milestone46.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 1229399: Remove expression statements opcodes in wasm; r=luke
js/src/asmjs/AsmJS.cpp
js/src/asmjs/WasmBinary.h
js/src/asmjs/WasmGenerator.cpp
js/src/asmjs/WasmGenerator.h
js/src/asmjs/WasmIonCompile.cpp
js/src/jit-test/tests/asm.js/testFloat32.js
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -1009,16 +1009,39 @@ class NumLit
         MOZ_ASSERT(isSimd());
         return u.simd_;
     }
 
     bool valid() const {
         return which_ != OutOfRangeInt;
     }
 
+    bool isZeroBits() const {
+        MOZ_ASSERT(valid());
+        switch (which()) {
+          case NumLit::Fixnum:
+          case NumLit::NegativeInt:
+          case NumLit::BigUnsigned:
+            return toInt32() == 0;
+          case NumLit::Double:
+            return toDouble() == 0.0 && !IsNegativeZero(toDouble());
+          case NumLit::Float:
+            return toFloat() == 0.f && !IsNegativeZero(toFloat());
+          case NumLit::Int32x4:
+            return simdValue() == SimdConstant::SplatX4(0);
+          case NumLit::Float32x4:
+            return simdValue() == SimdConstant::SplatX4(0.f);
+          case NumLit::Bool32x4:
+            return simdValue() == SimdConstant::SplatX4(0);
+          case NumLit::OutOfRangeInt:
+            MOZ_CRASH("can't be here because of valid() check above");
+        }
+        return false;
+    }
+
     ValType type() const {
         switch (which_) {
           case NumLit::Fixnum:
           case NumLit::NegativeInt:
           case NumLit::BigUnsigned:
             return ValType::I32;
           case NumLit::Double:
             return ValType::F64;
@@ -1436,17 +1459,17 @@ class MOZ_STACK_CLASS ModuleValidator
             SimdOperation
         };
 
       private:
         Which which_;
         union {
             struct {
                 Type::Which type_;
-                uint32_t globalDataOffset_;
+                unsigned index_;
                 NumLit literalValue_;
             } varOrConst;
             uint32_t funcIndex_;
             uint32_t funcPtrTableIndex_;
             uint32_t ffiIndex_;
             struct {
                 Scalar::Type viewType_;
             } viewInfo;
@@ -1467,19 +1490,20 @@ class MOZ_STACK_CLASS ModuleValidator
       public:
         Which which() const {
             return which_;
         }
         Type varOrConstType() const {
             MOZ_ASSERT(which_ == Variable || which_ == ConstantLiteral || which_ == ConstantImport);
             return u.varOrConst.type_;
         }
-        uint32_t varOrConstGlobalDataOffset() const {
+        unsigned varOrConstIndex() const {
             MOZ_ASSERT(which_ == Variable || which_ == ConstantImport);
-            return u.varOrConst.globalDataOffset_;
+            MOZ_ASSERT(u.varOrConst.index_ != -1u);
+            return u.varOrConst.index_;
         }
         bool isConst() const {
             return which_ == ConstantLiteral || which_ == ConstantImport;
         }
         NumLit constLiteralValue() const {
             MOZ_ASSERT(which_ == ConstantLiteral);
             return u.varOrConst.literalValue_;
         }
@@ -1824,55 +1848,55 @@ class MOZ_STACK_CLASS ModuleValidator
         MOZ_ASSERT(n->isTenured());
         module_->importArgumentName = n;
     }
     void initBufferArgumentName(PropertyName* n) {
         MOZ_ASSERT(n->isTenured());
         module_->bufferArgumentName = n;
     }
     bool addGlobalVarInit(PropertyName* var, const NumLit& lit, bool isConst) {
-        uint32_t globalDataOffset;
-        if (!mg_.allocateGlobalVar(lit.type(), &globalDataOffset))
+        uint32_t index;
+        if (!mg_.allocateGlobalVar(lit.type(), isConst, &index))
             return false;
 
         Global::Which which = isConst ? Global::ConstantLiteral : Global::Variable;
         Global* global = validationLifo_.new_<Global>(which);
         if (!global)
             return false;
-        global->u.varOrConst.globalDataOffset_ = globalDataOffset;
+        global->u.varOrConst.index_ = index;
         global->u.varOrConst.type_ = (isConst ? Type::lit(lit) : Type::var(lit.type())).which();
         if (isConst)
             global->u.varOrConst.literalValue_ = lit;
         if (!globalMap_.putNew(var, global))
             return false;
 
         AsmJSGlobal g(AsmJSGlobal::Variable, nullptr);
         g.pod.u.var.initKind_ = AsmJSGlobal::InitConstant;
         g.pod.u.var.u.val_ = lit.value();
-        g.pod.u.var.globalDataOffset_ = globalDataOffset;
+        g.pod.u.var.globalDataOffset_ = mg_.globalVar(index).globalDataOffset;
         return module_->globals.append(g);
     }
     bool addGlobalVarImport(PropertyName* var, PropertyName* field, ValType type, bool isConst) {
-        uint32_t globalDataOffset;
-        if (!mg_.allocateGlobalVar(type, &globalDataOffset))
+        uint32_t index;
+        if (!mg_.allocateGlobalVar(type, isConst, &index))
             return false;
 
         Global::Which which = isConst ? Global::ConstantImport : Global::Variable;
         Global* global = validationLifo_.new_<Global>(which);
         if (!global)
             return false;
-        global->u.varOrConst.globalDataOffset_ = globalDataOffset;
+        global->u.varOrConst.index_ = index;
         global->u.varOrConst.type_ = Type::var(type).which();
         if (!globalMap_.putNew(var, global))
             return false;
 
         AsmJSGlobal g(AsmJSGlobal::Variable, field);
         g.pod.u.var.initKind_ = AsmJSGlobal::InitImport;
         g.pod.u.var.u.importType_ = type;
-        g.pod.u.var.globalDataOffset_ = globalDataOffset;
+        g.pod.u.var.globalDataOffset_ = mg_.globalVar(index).globalDataOffset;
         return module_->globals.append(g);
     }
     bool addArrayView(PropertyName* var, Scalar::Type vt, PropertyName* maybeField) {
         if (!arrayViews_.append(ArrayView(var, vt)))
             return false;
 
         Global* global = validationLifo_.new_<Global>(Global::ArrayView);
         if (!global)
@@ -2226,17 +2250,17 @@ class MOZ_STACK_CLASS ModuleValidator
         if (SimdOperationNameMap::Ptr p = standardLibrarySimdOpNames_.lookup(name)) {
             *op = p->value();
             return true;
         }
         return false;
     }
 
     bool startFunctionBodies() {
-        return true;
+        return mg_.startFuncDefs();
     }
     bool finishFunctionBodies() {
         return mg_.finishFuncDefs();
     }
     bool finish(MutableHandle<WasmModuleObject*> moduleObj, SlowFunctionVector* slowFuncs) {
         HeapUsage heap = arrayViews_.empty()
                               ? HeapUsage::None
                               : atomicsPresent_
@@ -2637,26 +2661,22 @@ class MOZ_STACK_CLASS FunctionValidator
     }
 
     bool failName(ParseNode* pn, const char* fmt, PropertyName* name) {
         return m_.failName(pn, fmt, name);
     }
 
     /***************************************************** Local scope setup */
 
-    bool addFormal(ParseNode* pn, PropertyName* name, ValType type) {
+    bool addLocal(ParseNode* pn, PropertyName* name, ValType type) {
         LocalMap::AddPtr p = locals_.lookupForAdd(name);
         if (p)
             return failName(pn, "duplicate local name '%s' not allowed", name);
-        return locals_.add(p, name, Local(type, locals_.count()));
-    }
-
-    bool addVariable(ParseNode* pn, PropertyName* name, ValType type) {
-        return addFormal(pn, name, type) &&
-               fg_.addVariable(type);
+        return locals_.add(p, name, Local(type, locals_.count())) &&
+               fg_.addLocal(type);
     }
 
     /****************************** For consistency of returns in a function */
 
     bool hasAlreadyReturned() const {
         return hasAlreadyReturned_;
     }
 
@@ -3370,17 +3390,17 @@ CheckArguments(FunctionValidator& f, Par
 
         ValType type;
         if (!CheckArgumentType(f, stmt, name, &type))
             return false;
 
         if (!argTypes->append(type))
             return false;
 
-        if (!f.addFormal(argpn, name, type))
+        if (!f.addLocal(argpn, name, type))
             return false;
     }
 
     *stmtIter = stmt;
     return true;
 }
 
 static bool
@@ -3416,20 +3436,19 @@ CheckFinalReturn(FunctionValidator& f, P
 
         return f.writeOp(Expr::Return);
     }
 
     return true;
 }
 
 static bool
-SetLocal(FunctionValidator& f, Expr exprStmt, NumLit lit)
-{
-    return f.writeOp(exprStmt) &&
-           f.writeOp(Expr::SetLocal) &&
+SetLocal(FunctionValidator& f, NumLit lit)
+{
+    return f.writeOp(Expr::SetLocal) &&
            f.writeVarU32(f.numLocals()) &&
            f.writeLit(lit);
 }
 
 static bool
 CheckVariable(FunctionValidator& f, ParseNode* var)
 {
     if (!IsDefinition(var))
@@ -3446,53 +3465,20 @@ CheckVariable(FunctionValidator& f, Pars
 
     NumLit lit;
     if (!IsLiteralOrConst(f, initNode, &lit))
         return f.failName(var, "var '%s' initializer must be literal or const literal", name);
 
     if (!lit.valid())
         return f.failName(var, "var '%s' initializer out of range", name);
 
-    switch (lit.which()) {
-      case NumLit::Fixnum:
-      case NumLit::NegativeInt:
-      case NumLit::BigUnsigned:
-        if (lit.toInt32() != 0 && !SetLocal(f, Expr::I32Expr, lit))
-            return false;
-        break;
-      case NumLit::Double:
-        if ((lit.toDouble() != 0.0 || IsNegativeZero(lit.toDouble())) &&
-            !SetLocal(f, Expr::F64Expr, lit))
-            return false;
-        break;
-      case NumLit::Float:
-        if ((lit.toFloat() != 0.f || !IsNegativeZero(lit.toFloat())) &&
-            !SetLocal(f, Expr::F32Expr, lit))
-            return false;
-        break;
-      case NumLit::Int32x4:
-        if (lit.simdValue() != SimdConstant::SplatX4(0) &&
-            !SetLocal(f, Expr::I32X4Expr, lit))
-            return false;
-        break;
-      case NumLit::Float32x4:
-        if (lit.simdValue() != SimdConstant::SplatX4(0.f) &&
-            !SetLocal(f, Expr::F32X4Expr, lit))
-            return false;
-        break;
-      case NumLit::Bool32x4:
-        if (lit.simdValue() != SimdConstant::SplatX4(0) &&
-            !SetLocal(f, Expr::B32X4Expr, lit))
-            return false;
-        break;
-      case NumLit::OutOfRangeInt:
-        MOZ_CRASH("can't be here because of valid() check above");
-    }
-
-    return f.addVariable(var, name, lit.type());
+    if (!lit.isZeroBits() && !SetLocal(f, lit))
+        return false;
+
+    return f.addLocal(var, name, lit.type());
 }
 
 static bool
 CheckVariables(FunctionValidator& f, ParseNode** stmtIter)
 {
     ParseNode* stmt = *stmtIter;
 
     for (; stmt && stmt->isKind(PNK_VAR); stmt = NextNonEmptyStatement(stmt)) {
@@ -3537,19 +3523,17 @@ CheckVarRef(FunctionValidator& f, ParseN
     if (const ModuleValidator::Global* global = f.lookupGlobal(name)) {
         switch (global->which()) {
           case ModuleValidator::Global::ConstantLiteral:
             *type = global->varOrConstType();
             return f.writeLit(global->constLiteralValue());
           case ModuleValidator::Global::ConstantImport:
           case ModuleValidator::Global::Variable: {
             *type = global->varOrConstType();
-            return f.writeOp(Expr::LoadGlobal) &&
-                   f.writeVarU32(global->varOrConstGlobalDataOffset()) &&
-                   f.writeU8(uint8_t(global->isConst()));
+            return f.writeOp(Expr::LoadGlobal) && f.writeVarU32(global->varOrConstIndex());
           }
           case ModuleValidator::Global::Function:
           case ModuleValidator::Global::FFI:
           case ModuleValidator::Global::MathBuiltinFunction:
           case ModuleValidator::Global::AtomicsBuiltinFunction:
           case ModuleValidator::Global::FuncPtrTable:
           case ModuleValidator::Global::ArrayView:
           case ModuleValidator::Global::ArrayViewCtor:
@@ -3850,17 +3834,17 @@ CheckAssignName(FunctionValidator& f, Pa
         *type = rhsType;
         return true;
     }
 
     if (const ModuleValidator::Global* global = f.lookupGlobal(name)) {
         if (global->which() != ModuleValidator::Global::Variable)
             return f.failName(lhs, "'%s' is not a mutable variable", name);
 
-        if (!f.writeOp(Expr::StoreGlobal) || !f.writeVarU32(global->varOrConstGlobalDataOffset()))
+        if (!f.writeOp(Expr::StoreGlobal) || !f.writeVarU32(global->varOrConstIndex()))
             return false;
 
         Type rhsType;
         if (!CheckExpr(f, rhs, &rhsType))
             return false;
 
         Type globType = global->varOrConstType();
         if (!(rhsType <= globType))
@@ -5526,32 +5510,17 @@ CheckUncoercedCall(FunctionValidator& f,
 static bool
 CoerceResult(FunctionValidator& f, ParseNode* expr, ExprType expected, Type actual, size_t patchAt,
              Type* type)
 {
     // At this point, the bytecode resembles this:
     //      | patchAt | the thing we wanted to coerce | current position |>
     switch (expected) {
       case ExprType::Void:
-        if (actual.isIntish())
-            f.patchOp(patchAt, Expr::I32Expr);
-        else if (actual.isFloatish())
-            f.patchOp(patchAt, Expr::F32Expr);
-        else if (actual.isMaybeDouble())
-            f.patchOp(patchAt, Expr::F64Expr);
-        else if (actual.isInt32x4())
-            f.patchOp(patchAt, Expr::I32X4Expr);
-        else if (actual.isFloat32x4())
-            f.patchOp(patchAt, Expr::F32X4Expr);
-        else if (actual.isBool32x4())
-            f.patchOp(patchAt, Expr::B32X4Expr);
-        else if (actual.isVoid())
-            f.patchOp(patchAt, Expr::Id);
-        else
-            MOZ_CRASH("unhandled return type");
+        f.patchOp(patchAt, Expr::Id);
         break;
       case ExprType::I32:
         if (!actual.isIntish())
             return f.failf(expr, "%s is not a subtype of intish", actual.toChars());
         f.patchOp(patchAt, Expr::Id);
         break;
       case ExprType::I64:
         MOZ_CRASH("no int64 in asm.js");
@@ -6258,45 +6227,20 @@ CheckExpr(FunctionValidator& f, ParseNod
 }
 
 static bool
 CheckStatement(FunctionValidator& f, ParseNode* stmt);
 
 static bool
 CheckAsExprStatement(FunctionValidator& f, ParseNode* expr)
 {
-    if (expr->isKind(PNK_CALL)) {
-        Type _;
-        return CheckCoercedCall(f, expr, ExprType::Void, &_);
-    }
-
-    size_t opcodeAt;
-    if (!f.tempOp(&opcodeAt))
-        return false;
-
-    Type type;
-    if (!CheckExpr(f, expr, &type))
-        return false;
-
-    if (type.isIntish())
-        f.patchOp(opcodeAt, Expr::I32Expr);
-    else if (type.isFloatish())
-        f.patchOp(opcodeAt, Expr::F32Expr);
-    else if (type.isMaybeDouble())
-        f.patchOp(opcodeAt, Expr::F64Expr);
-    else if (type.isInt32x4())
-        f.patchOp(opcodeAt, Expr::I32X4Expr);
-    else if (type.isFloat32x4())
-        f.patchOp(opcodeAt, Expr::F32X4Expr);
-    else if (type.isBool32x4())
-        f.patchOp(opcodeAt, Expr::B32X4Expr);
-    else
-        MOZ_CRASH("unexpected or unimplemented expression statement");
-
-    return true;
+    Type ignored;
+    if (expr->isKind(PNK_CALL))
+        return CheckCoercedCall(f, expr, ExprType::Void, &ignored);
+    return CheckExpr(f, expr, &ignored);
 }
 
 static bool
 CheckExprStatement(FunctionValidator& f, ParseNode* exprStmt)
 {
     MOZ_ASSERT(exprStmt->isKind(PNK_SEMI));
     ParseNode* expr = UnaryKid(exprStmt);
     if (!expr)
--- a/js/src/asmjs/WasmBinary.h
+++ b/js/src/asmjs/WasmBinary.h
@@ -181,20 +181,16 @@ enum class Expr : uint8_t
     ForNoInitInc,
 
     Label,
     Continue,
     ContinueLabel,
     Break,
     BreakLabel,
 
-    I32Expr,        // to be removed
-    F32Expr,
-    F64Expr,
-
     Id,
 
     InterruptCheckHead,
     InterruptCheckLoop,
 
     DebugCheckPoint,
 
     I32Min,
@@ -208,20 +204,16 @@ enum class Expr : uint8_t
     I32AtomicsStore,
     I32AtomicsBinOp,
 
     // SIMD
     I32X4Const,
     B32X4Const,
     F32X4Const,
 
-    I32X4Expr,
-    F32X4Expr,
-    B32X4Expr,
-
     I32I32X4ExtractLane,
     I32B32X4ExtractLane,
     I32B32X4AllTrue,
     I32B32X4AnyTrue,
 
     F32F32X4ExtractLane,
 
     I32X4Ctor,
@@ -589,65 +581,67 @@ struct SourceCoords {
 
 typedef Vector<SourceCoords, 0, SystemAllocPolicy> SourceCoordsVector;
 
 // The FuncBytecode class contains the intermediate representation of a
 // parsed/decoded and validated asm.js/WebAssembly function. The FuncBytecode
 // lives only until it is fully compiled.
 class FuncBytecode
 {
+    // Function metadata
+    SourceCoordsVector callSourceCoords_;
+    const DeclaredSig& sig_;
+    ValTypeVector locals_;
+
     // Note: this unrooted field assumes AutoKeepAtoms via TokenStream via
     // asm.js compilation.
     PropertyName* name_;
     unsigned line_;
     unsigned column_;
 
-    SourceCoordsVector callSourceCoords_;
-
+    // Compilation bookkeeping
     uint32_t index_;
-    const DeclaredSig& sig_;
+    unsigned generateTime_;
+
     UniqueBytecode bytecode_;
-    ValTypeVector localVars_;
-    unsigned generateTime_;
 
   public:
     FuncBytecode(PropertyName* name,
                  unsigned line,
                  unsigned column,
                  SourceCoordsVector&& sourceCoords,
                  uint32_t index,
                  const DeclaredSig& sig,
                  UniqueBytecode bytecode,
-                 ValTypeVector&& localVars,
+                 ValTypeVector&& locals,
                  unsigned generateTime)
-      : name_(name),
+      : callSourceCoords_(Move(sourceCoords)),
+        sig_(sig),
+        locals_(Move(locals)),
+        name_(name),
         line_(line),
         column_(column),
-        callSourceCoords_(Move(sourceCoords)),
         index_(index),
-        sig_(sig),
-        bytecode_(Move(bytecode)),
-        localVars_(Move(localVars)),
-        generateTime_(generateTime)
+        generateTime_(generateTime),
+        bytecode_(Move(bytecode))
     {}
 
     UniqueBytecode recycleBytecode() { return Move(bytecode_); }
 
     PropertyName* name() const { return name_; }
     unsigned line() const { return line_; }
     unsigned column() const { return column_; }
     const SourceCoords& sourceCoords(size_t i) const { return callSourceCoords_[i]; }
 
     uint32_t index() const { return index_; }
     const DeclaredSig& sig() const { return sig_; }
     const Bytecode& bytecode() const { return *bytecode_; }
 
-    size_t numLocalVars() const { return localVars_.length(); }
-    ValType localVarType(size_t i) const { return localVars_[i]; }
-    size_t numLocals() const { return sig_.args().length() + numLocalVars(); }
+    size_t numLocals() const { return locals_.length(); }
+    ValType localType(size_t i) const { return locals_[i]; }
 
     unsigned generateTime() const { return generateTime_; }
 };
 
 typedef UniquePtr<FuncBytecode> UniqueFuncBytecode;
 
 } // namespace wasm
 } // namespace js
--- a/js/src/asmjs/WasmGenerator.cpp
+++ b/js/src/asmjs/WasmGenerator.cpp
@@ -127,53 +127,16 @@ ModuleGenerator::init(UniqueModuleGenera
         numSigs_ = shared_->sigs.length();
         numFuncSigs_ = shared_->funcSigs.length();
         for (uint32_t i = 0; i < shared_->imports.length(); i++) {
             if (!addImport(*shared_->imports[i].sig, shared_->imports[i].globalDataOffset))
                 return false;
         }
     }
 
-    threadView_ = MakeUnique<ModuleGeneratorThreadView>(*shared_);
-    if (!threadView_)
-        return false;
-
-    if (!funcIndexToExport_.init())
-        return false;
-
-    uint32_t numTasks;
-    if (ParallelCompilationEnabled(cx_) &&
-        HelperThreadState().wasmCompilationInProgress.compareExchange(false, true))
-    {
-#ifdef DEBUG
-        {
-            AutoLockHelperThreadState lock;
-            MOZ_ASSERT(!HelperThreadState().wasmFailed());
-            MOZ_ASSERT(HelperThreadState().wasmWorklist().empty());
-            MOZ_ASSERT(HelperThreadState().wasmFinishedList().empty());
-        }
-#endif
-
-        parallel_ = true;
-        numTasks = HelperThreadState().maxWasmCompilationThreads();
-    } else {
-        numTasks = 1;
-    }
-
-    if (!tasks_.initCapacity(numTasks))
-        return false;
-    JSRuntime* rt = cx_->compartment()->runtimeFromAnyThread();
-    for (size_t i = 0; i < numTasks; i++)
-        tasks_.infallibleEmplaceBack(rt, args(), *threadView_, COMPILATION_LIFO_DEFAULT_CHUNK_SIZE);
-
-    if (!freeTasks_.reserve(numTasks))
-        return false;
-    for (size_t i = 0; i < numTasks; i++)
-        freeTasks_.infallibleAppend(&tasks_[i]);
-
     return true;
 }
 
 bool
 ModuleGenerator::finishOutstandingTask()
 {
     MOZ_ASSERT(parallel_);
 
@@ -269,35 +232,42 @@ ModuleGenerator::allocateGlobalBytes(uin
     *globalDataOffset = globalBytes;
     globalBytes += bytes;
 
     module_->globalBytes = globalBytes;
     return true;
 }
 
 bool
-ModuleGenerator::allocateGlobalVar(ValType type, uint32_t* globalDataOffset)
+ModuleGenerator::allocateGlobalVar(ValType type, bool isConst, uint32_t* index)
 {
+    MOZ_ASSERT(!startedFuncDefs());
     unsigned width = 0;
     switch (type) {
       case wasm::ValType::I32:
       case wasm::ValType::F32:
         width = 4;
         break;
       case wasm::ValType::I64:
       case wasm::ValType::F64:
         width = 8;
         break;
       case wasm::ValType::I32x4:
       case wasm::ValType::F32x4:
       case wasm::ValType::B32x4:
         width = 16;
         break;
     }
-    return allocateGlobalBytes(width, width, globalDataOffset);
+
+    uint32_t offset;
+    if (!allocateGlobalBytes(width, width, &offset))
+        return false;
+
+    *index = shared_->globals.length();
+    return shared_->globals.append(AsmJSGlobalVariable(ToExprType(type), offset, isConst));
 }
 
 void
 ModuleGenerator::initSig(uint32_t sigIndex, Sig&& sig)
 {
     MOZ_ASSERT(module_->kind == ModuleKind::AsmJS);
     MOZ_ASSERT(sigIndex == numSigs_);
     numSigs_++;
@@ -410,19 +380,65 @@ ModuleGenerator::numExports() const
 bool
 ModuleGenerator::defineExport(uint32_t index, Offsets offsets)
 {
     module_->exports[index].initStubOffset(offsets.begin);
     return module_->codeRanges.emplaceBack(CodeRange::Entry, offsets);
 }
 
 bool
+ModuleGenerator::startFuncDefs()
+{
+    MOZ_ASSERT(!startedFuncDefs());
+    threadView_ = MakeUnique<ModuleGeneratorThreadView>(*shared_);
+    if (!threadView_)
+        return false;
+
+    if (!funcIndexToExport_.init())
+        return false;
+
+    uint32_t numTasks;
+    if (ParallelCompilationEnabled(cx_) &&
+        HelperThreadState().wasmCompilationInProgress.compareExchange(false, true))
+    {
+#ifdef DEBUG
+        {
+            AutoLockHelperThreadState lock;
+            MOZ_ASSERT(!HelperThreadState().wasmFailed());
+            MOZ_ASSERT(HelperThreadState().wasmWorklist().empty());
+            MOZ_ASSERT(HelperThreadState().wasmFinishedList().empty());
+        }
+#endif
+
+        parallel_ = true;
+        numTasks = HelperThreadState().maxWasmCompilationThreads();
+    } else {
+        numTasks = 1;
+    }
+
+    if (!tasks_.initCapacity(numTasks))
+        return false;
+    JSRuntime* rt = cx_->compartment()->runtimeFromAnyThread();
+    for (size_t i = 0; i < numTasks; i++)
+        tasks_.infallibleEmplaceBack(rt, args(), *threadView_, COMPILATION_LIFO_DEFAULT_CHUNK_SIZE);
+
+    if (!freeTasks_.reserve(numTasks))
+        return false;
+    for (size_t i = 0; i < numTasks; i++)
+        freeTasks_.infallibleAppend(&tasks_[i]);
+
+    MOZ_ASSERT(startedFuncDefs());
+    return true;
+}
+
+bool
 ModuleGenerator::startFuncDef(PropertyName* name, unsigned line, unsigned column,
                               FunctionGenerator* fg)
 {
+    MOZ_ASSERT(startedFuncDefs());
     MOZ_ASSERT(!activeFunc_);
     MOZ_ASSERT(!finishedFuncs_);
 
     if (freeTasks_.empty() && !finishOutstandingTask())
         return false;
 
     IonCompileTask* task = freeTasks_.popCopy();
 
@@ -479,16 +495,17 @@ ModuleGenerator::finishFuncDef(uint32_t 
     fg->task_ = nullptr;
     activeFunc_ = nullptr;
     return true;
 }
 
 bool
 ModuleGenerator::finishFuncDefs()
 {
+    MOZ_ASSERT(startedFuncDefs());
     MOZ_ASSERT(!activeFunc_);
     MOZ_ASSERT(!finishedFuncs_);
 
     while (outstanding_ > 0) {
         if (!finishOutstandingTask())
             return false;
     }
 
--- a/js/src/asmjs/WasmGenerator.h
+++ b/js/src/asmjs/WasmGenerator.h
@@ -59,21 +59,35 @@ typedef Vector<SlowFunction> SlowFunctio
 struct ModuleImportGeneratorData
 {
     DeclaredSig* sig;
     uint32_t globalDataOffset;
 };
 
 typedef Vector<ModuleImportGeneratorData, 0, SystemAllocPolicy> ModuleImportGeneratorDataVector;
 
+// Global variable descriptor, in asm.js only.
+
+struct AsmJSGlobalVariable {
+    ExprType type;
+    unsigned globalDataOffset;
+    bool isConst;
+    AsmJSGlobalVariable(ExprType type, unsigned offset, bool isConst)
+      : type(type), globalDataOffset(offset), isConst(isConst)
+    {}
+};
+
+typedef Vector<AsmJSGlobalVariable, 0, SystemAllocPolicy> AsmJSGlobalVariableVector;
+
 struct ModuleGeneratorData
 {
     DeclaredSigVector               sigs;
     DeclaredSigPtrVector            funcSigs;
     ModuleImportGeneratorDataVector imports;
+    AsmJSGlobalVariableVector       globals;
 };
 
 typedef UniquePtr<ModuleGeneratorData> UniqueModuleGeneratorData;
 
 // The ModuleGeneratorThreadView class presents a restricted, read-only view of
 // the shared state needed by helper threads. There is only one
 // ModuleGeneratorThreadView object owned by ModuleGenerator and referenced by
 // all compile tasks.
@@ -92,16 +106,19 @@ class ModuleGeneratorThreadView
     const DeclaredSig& funcSig(uint32_t funcIndex) const {
         MOZ_ASSERT(shared_.funcSigs[funcIndex]);
         return *shared_.funcSigs[funcIndex];
     }
     const ModuleImportGeneratorData& import(uint32_t importIndex) const {
         MOZ_ASSERT(shared_.imports[importIndex].sig);
         return shared_.imports[importIndex];
     }
+    const AsmJSGlobalVariable& globalVar(uint32_t globalIndex) const {
+        return shared_.globals[globalIndex];
+    }
 };
 
 // A ModuleGenerator encapsulates the creation of a wasm module. During the
 // lifetime of a ModuleGenerator, a sequence of FunctionGenerators are created
 // and destroyed to compile the individual function bodies. After generating all
 // functions, ModuleGenerator::finish() must be called to complete the
 // compilation and extract the resulting wasm module.
 
@@ -151,17 +168,18 @@ class MOZ_STACK_CLASS ModuleGenerator
     bool init(UniqueModuleGeneratorData shared, ModuleKind = ModuleKind::Wasm);
 
     CompileArgs args() const { return module_->compileArgs; }
     jit::MacroAssembler& masm() { return masm_; }
     const Uint32Vector& funcEntryOffsets() const { return funcEntryOffsets_; }
 
     // Global data:
     bool allocateGlobalBytes(uint32_t bytes, uint32_t align, uint32_t* globalDataOffset);
-    bool allocateGlobalVar(ValType type, uint32_t* globalDataOffset);
+    bool allocateGlobalVar(ValType type, bool isConst, uint32_t* index);
+    const AsmJSGlobalVariable& globalVar(unsigned index) const { return shared_->globals[index]; }
 
     // Signatures:
     void initSig(uint32_t sigIndex, Sig&& sig);
     uint32_t numSigs() const { return numSigs_; }
     const DeclaredSig& sig(uint32_t sigIndex) const;
 
     // Function declarations:
     bool initFuncSig(uint32_t funcIndex, uint32_t sigIndex);
@@ -177,16 +195,18 @@ class MOZ_STACK_CLASS ModuleGenerator
     // Exports:
     bool declareExport(uint32_t funcIndex, uint32_t* exportIndex);
     uint32_t numExports() const;
     uint32_t exportFuncIndex(uint32_t index) const;
     const Sig& exportSig(uint32_t index) const;
     bool defineExport(uint32_t index, Offsets offsets);
 
     // Function definitions:
+    bool startFuncDefs();
+    bool startedFuncDefs() const { return !!threadView_; }
     bool startFuncDef(PropertyName* name, unsigned line, unsigned column, FunctionGenerator* fg);
     bool finishFuncDef(uint32_t funcIndex, unsigned generateTime, FunctionGenerator* fg);
     bool finishFuncDefs();
 
     // Function-pointer tables:
     bool declareFuncPtrTable(uint32_t numElems, uint32_t* index);
     uint32_t funcPtrTableGlobalDataOffset(uint32_t index) const;
     void defineFuncPtrTable(uint32_t index, const Vector<uint32_t>& elemFuncIndices);
@@ -243,17 +263,17 @@ class MOZ_STACK_CLASS FunctionGenerator
 
     Bytecode& bytecode() const {
         return *bytecode_;
     }
     bool addSourceCoords(size_t byteOffset, uint32_t line, uint32_t column) {
         SourceCoords sc = { byteOffset, line, column };
         return callSourceCoords_.append(sc);
     }
-    bool addVariable(ValType v) {
+    bool addLocal(ValType v) {
         return localVars_.append(v);
     }
 };
 
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_generator_h
--- a/js/src/asmjs/WasmIonCompile.cpp
+++ b/js/src/asmjs/WasmIonCompile.cpp
@@ -20,16 +20,17 @@
 
 #include "jit/CodeGenerator.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 
 using mozilla::DebugOnly;
+using mozilla::Maybe;
 
 typedef Vector<size_t, 1, SystemAllocPolicy> LabelVector;
 typedef Vector<MBasicBlock*, 8, SystemAllocPolicy> BlockVector;
 
 // Encapsulates the compilation of a single function in an asm.js module. The
 // function compiler handles the creation and final backend compilation of the
 // MIR graph.
 class FunctionCompiler
@@ -90,34 +91,33 @@ class FunctionCompiler
             !labeledContinues_.init())
         {
             return false;
         }
 
         // Prepare the entry block for MIR generation:
 
         const ValTypeVector& args = func_.sig().args();
-        unsigned firstVarSlot = args.length();
 
         if (!mirGen_.ensureBallast())
             return false;
         if (!newBlock(/* pred = */ nullptr, &curBlock_))
             return false;
 
         for (ABIArgValTypeIter i(args); !i.done(); i++) {
             MAsmJSParameter* ins = MAsmJSParameter::New(alloc(), *i, i.mirType());
             curBlock_->add(ins);
             curBlock_->initSlot(info().localSlot(i.index()), ins);
             if (!mirGen_.ensureBallast())
                 return false;
         }
 
-        for (size_t i = 0; i < func_.numLocalVars(); i++) {
+        for (size_t i = args.length(); i < func_.numLocals(); i++) {
             MInstruction* ins = nullptr;
-            switch (func_.localVarType(i)) {
+            switch (func_.localType(i)) {
               case ValType::I32:
                 ins = MConstant::NewAsmJS(alloc(), Int32Value(0), MIRType_Int32);
                 break;
               case ValType::I64:
                 MOZ_CRASH("int64");
               case ValType::F32:
                 ins = MConstant::NewAsmJS(alloc(), Float32Value(0.f), MIRType_Float32);
                 break;
@@ -132,17 +132,17 @@ class FunctionCompiler
                 break;
               case ValType::B32x4:
                 // Bool32x4 uses the same data layout as Int32x4.
                 ins = MSimdConstant::New(alloc(), SimdConstant::SplatX4(0), MIRType_Bool32x4);
                 break;
             }
 
             curBlock_->add(ins);
-            curBlock_->initSlot(info().localSlot(firstVarSlot + i), ins);
+            curBlock_->initSlot(info().localSlot(i), ins);
             if (!mirGen_.ensureBallast())
                 return false;
         }
 
         return true;
     }
 
     void checkPostconditions()
@@ -164,16 +164,18 @@ class FunctionCompiler
 
     MDefinition* getLocalDef(unsigned slot)
     {
         if (inDeadCode())
             return nullptr;
         return curBlock_->getSlot(info().localSlot(slot));
     }
 
+    ExprType localType(unsigned slot) const { return ToExprType(func_.localType(slot)); }
+
     /***************************** Code generation (after local scope setup) */
 
     MDefinition* constant(const SimdConstant& v, MIRType type)
     {
         if (inDeadCode())
             return nullptr;
         MInstruction* constant;
         constant = MSimdConstant::New(alloc(), v, type);
@@ -1283,16 +1285,30 @@ class FunctionCompiler
             if (!bindBreaksOrContinues(&p->value(), &createdJoinBlock))
                 return false;
             unlabeledBreaks_.remove(p);
         }
         return true;
     }
 };
 
+// A Type or Undefined, implicitly constructed from an ExprType and usabled as
+// an ExprType. Will assert if we're trying to access the type although we
+// don't have one.
+class MaybeType
+{
+    Maybe<ExprType> maybe_;
+    MaybeType() {}
+  public:
+    MOZ_IMPLICIT MaybeType(ExprType t) : maybe_() { maybe_.emplace(t); }
+    static MaybeType Undefined() { return MaybeType(); }
+    operator bool() { return maybe_.isSome(); }
+    MOZ_IMPLICIT operator ExprType() { return maybe_.value(); }
+};
+
 static bool
 EmitLiteral(FunctionCompiler& f, ExprType type, MDefinition**def)
 {
     switch (type) {
       case ExprType::I32: {
         int32_t val = f.readI32();
         *def = f.constant(Int32Value(val), MIRType_Int32);
         return true;
@@ -1329,35 +1345,38 @@ EmitLiteral(FunctionCompiler& f, ExprTyp
       case ExprType::Void: {
         break;
       }
     }
     MOZ_CRASH("unexpected literal type");
 }
 
 static bool
-EmitGetLocal(FunctionCompiler& f, ExprType type, MDefinition** def)
+EmitGetLocal(FunctionCompiler& f, MaybeType type, MDefinition** def)
 {
     uint32_t slot = f.readVarU32();
     *def = f.getLocalDef(slot);
-    MOZ_ASSERT_IF(*def, (*def)->type() == ToMIRType(type));
+    MOZ_ASSERT_IF(type, f.localType(slot) == type);
     return true;
 }
 
 static bool
-EmitLoadGlobal(FunctionCompiler& f, ExprType type, MDefinition** def)
+EmitLoadGlobal(FunctionCompiler& f, MaybeType type, MDefinition** def)
 {
-    uint32_t globalDataOffset = f.readVarU32();
-    bool isConst = bool(f.readU8());
-    *def = f.loadGlobalVar(globalDataOffset, isConst, ToMIRType(type));
+    uint32_t index = f.readVarU32();
+    const AsmJSGlobalVariable& global = f.mg().globalVar(index);
+    *def = f.loadGlobalVar(global.globalDataOffset, global.isConst, ToMIRType(global.type));
+    MOZ_ASSERT_IF(type, global.type == type);
     return true;
 }
 
-static bool EmitExpr(FunctionCompiler&, ExprType, Expr, MDefinition**, LabelVector* = nullptr);
-static bool EmitExpr(FunctionCompiler&, ExprType, MDefinition**, LabelVector* = nullptr);
+static bool EmitExpr(FunctionCompiler&, MaybeType, Expr, MDefinition**, LabelVector* = nullptr);
+static bool EmitExpr(FunctionCompiler&, MaybeType, MDefinition**, LabelVector* = nullptr);
+static bool EmitExprStmt(FunctionCompiler&, MDefinition**, LabelVector* = nullptr);
+static bool EmitExprStmt(FunctionCompiler&, Expr, MDefinition**, LabelVector* = nullptr);
 static bool EmitSimdBooleanLaneExpr(FunctionCompiler& f, MDefinition** def);
 
 static bool
 EmitLoadArray(FunctionCompiler& f, Scalar::Type scalarType, MDefinition** def)
 {
     NeedsBoundsCheck needsBoundsCheck = NeedsBoundsCheck(f.readU8());
     MDefinition* ptr;
     if (!EmitExpr(f, ExprType::I32, &ptr))
@@ -1423,35 +1442,39 @@ EmitStoreWithCoercion(FunctionCompiler& 
     }
 
     f.storeHeap(viewType, ptr, coerced, needsBoundsCheck);
     *def = rhs;
     return true;
 }
 
 static bool
-EmitSetLocal(FunctionCompiler& f, ExprType type, MDefinition** def)
+EmitSetLocal(FunctionCompiler& f, MaybeType expected, MDefinition** def)
 {
     uint32_t slot = f.readVarU32();
     MDefinition* expr;
-    if (!EmitExpr(f, type, &expr))
+    ExprType actual = f.localType(slot);
+    MOZ_ASSERT_IF(expected, actual == expected);
+    if (!EmitExpr(f, actual, &expr))
         return false;
     f.assign(slot, expr);
     *def = expr;
     return true;
 }
 
 static bool
-EmitStoreGlobal(FunctionCompiler& f, ExprType type, MDefinition**def)
+EmitStoreGlobal(FunctionCompiler& f, MaybeType type, MDefinition**def)
 {
-    uint32_t globalDataOffset = f.readVarU32();
+    uint32_t index = f.readVarU32();
+    const AsmJSGlobalVariable& global = f.mg().globalVar(index);
+    MOZ_ASSERT_IF(type, global.type == type);
     MDefinition* expr;
-    if (!EmitExpr(f, type, &expr))
+    if (!EmitExpr(f, global.type, &expr))
         return false;
-    f.storeGlobalVar(globalDataOffset, expr);
+    f.storeGlobalVar(global.globalDataOffset, expr);
     *def = expr;
     return true;
 }
 
 typedef bool IsMax;
 
 static bool
 EmitMathMinMax(FunctionCompiler& f, ExprType type, bool isMax, MDefinition** def)
@@ -1567,74 +1590,74 @@ EmitCallArgs(FunctionCompiler& f, const 
         if (!f.passArg(arg, sig.arg(i), call))
             return false;
     }
     f.finishCallArgs(call);
     return true;
 }
 
 static bool
-EmitInternalCall(FunctionCompiler& f, ExprType ret, MDefinition** def)
+EmitInternalCall(FunctionCompiler& f, MaybeType ret, MDefinition** def)
 {
     uint32_t funcIndex = f.readU32();
 
     const Sig& sig = f.mg().funcSig(funcIndex);
-    MOZ_ASSERT_IF(!IsVoid(sig.ret()), sig.ret() == ret);
+    MOZ_ASSERT_IF(!IsVoid(sig.ret()) && ret, sig.ret() == ret);
 
     uint32_t lineno, column;
     f.readCallLineCol(&lineno, &column);
 
     FunctionCompiler::Call call(f, lineno, column);
     if (!EmitCallArgs(f, sig, &call))
         return false;
 
     return f.internalCall(sig, funcIndex, call, def);
 }
 
 static bool
-EmitFuncPtrCall(FunctionCompiler& f, ExprType ret, MDefinition** def)
+EmitFuncPtrCall(FunctionCompiler& f, MaybeType ret, MDefinition** def)
 {
     uint32_t mask = f.readU32();
     uint32_t globalDataOffset = f.readU32();
     uint32_t sigIndex = f.readU32();
 
     const Sig& sig = f.mg().sig(sigIndex);
-    MOZ_ASSERT_IF(!IsVoid(sig.ret()), sig.ret() == ret);
+    MOZ_ASSERT_IF(!IsVoid(sig.ret()) && ret, sig.ret() == ret);
 
     uint32_t lineno, column;
     f.readCallLineCol(&lineno, &column);
 
     MDefinition *index;
     if (!EmitExpr(f, ExprType::I32, &index))
         return false;
 
     FunctionCompiler::Call call(f, lineno, column);
     if (!EmitCallArgs(f, sig, &call))
         return false;
 
     return f.funcPtrCall(sig, mask, globalDataOffset, index, call, def);
 }
 
 static bool
-EmitFFICall(FunctionCompiler& f, ExprType ret, MDefinition** def)
+EmitFFICall(FunctionCompiler& f, MaybeType ret, MDefinition** def)
 {
     uint32_t importIndex = f.readU32();
 
     uint32_t lineno, column;
     f.readCallLineCol(&lineno, &column);
 
     const ModuleImportGeneratorData& import = f.mg().import(importIndex);
     const Sig& sig = *import.sig;
-    MOZ_ASSERT_IF(!IsVoid(sig.ret()), sig.ret() == ret);
+    MOZ_ASSERT_IF(!IsVoid(sig.ret()) && ret, sig.ret() == ret);
 
     FunctionCompiler::Call call(f, lineno, column);
     if (!EmitCallArgs(f, sig, &call))
         return false;
 
-    return f.ffiCall(import.globalDataOffset, call, ret, def);
+    return f.ffiCall(import.globalDataOffset, call, sig.ret(), def);
 }
 
 static bool
 EmitF32MathBuiltinCall(FunctionCompiler& f, Expr f32, MDefinition** def)
 {
     MOZ_ASSERT(f32 == Expr::F32Ceil || f32 == Expr::F32Floor);
 
     uint32_t lineno, column;
@@ -2029,17 +2052,17 @@ EmitUnaryMir(FunctionCompiler& f, ExprTy
     MDefinition* in;
     if (!EmitExpr(f, type, &in))
         return false;
     *def = f.unary<T>(in, ToMIRType(type));
     return true;
 }
 
 static bool
-EmitTernary(FunctionCompiler& f, ExprType type, MDefinition** def)
+EmitTernary(FunctionCompiler& f, MaybeType type, MDefinition** def)
 {
     MDefinition* cond;
     if (!EmitExpr(f, ExprType::I32, &cond))
         return false;
 
     MBasicBlock* thenBlock = nullptr;
     MBasicBlock* elseBlock = nullptr;
     if (!f.branchAndStartThen(cond, &thenBlock, &elseBlock))
@@ -2061,16 +2084,18 @@ EmitTernary(FunctionCompiler& f, ExprTyp
     if (!EmitExpr(f, type, &ifFalse))
         return false;
 
     f.pushPhiInput(ifFalse);
 
     if (!f.joinIfElse(thenBlocks))
         return false;
 
+    MOZ_ASSERT(ifTrue->type() == ifFalse->type(), "both sides of a ternary have the same type");
+
     *def = f.popPhiOutput();
     return true;
 }
 
 static bool
 EmitMultiply(FunctionCompiler& f, ExprType type, MDefinition** def)
 {
     MDefinition* lhs;
@@ -2267,17 +2292,17 @@ EmitInterruptCheck(FunctionCompiler& f)
 }
 
 static bool
 EmitInterruptCheckLoop(FunctionCompiler& f)
 {
     if (!EmitInterruptCheck(f))
         return false;
     MDefinition* _;
-    return EmitExpr(f, ExprType::Void, &_);
+    return EmitExprStmt(f, &_);
 }
 
 static bool
 EmitWhile(FunctionCompiler& f, const LabelVector* maybeLabels)
 {
     size_t headId = f.nextId();
 
     MBasicBlock* loopEntry;
@@ -2288,17 +2313,17 @@ EmitWhile(FunctionCompiler& f, const Lab
     if (!EmitExpr(f, ExprType::I32, &condDef))
         return false;
 
     MBasicBlock* afterLoop;
     if (!f.branchAndStartLoopBody(condDef, &afterLoop))
         return false;
 
     MDefinition* _;
-    if (!EmitExpr(f, ExprType::Void, &_))
+    if (!EmitExprStmt(f, &_))
         return false;
 
     if (!f.bindContinues(headId, maybeLabels))
         return false;
 
     return f.closeLoop(loopEntry, afterLoop);
 }
 
@@ -2306,42 +2331,42 @@ static bool
 EmitFor(FunctionCompiler& f, Expr expr, const LabelVector* maybeLabels)
 {
     MOZ_ASSERT(expr == Expr::ForInitInc || expr == Expr::ForInitNoInc ||
                expr == Expr::ForNoInitInc || expr == Expr::ForNoInitNoInc);
     size_t headId = f.nextId();
 
     if (expr == Expr::ForInitInc || expr == Expr::ForInitNoInc) {
         MDefinition* _;
-        if (!EmitExpr(f, ExprType::Void, &_))
+        if (!EmitExprStmt(f, &_))
             return false;
     }
 
     MBasicBlock* loopEntry;
     if (!f.startPendingLoop(headId, &loopEntry))
         return false;
 
     MDefinition* condDef;
     if (!EmitExpr(f, ExprType::I32, &condDef))
         return false;
 
     MBasicBlock* afterLoop;
     if (!f.branchAndStartLoopBody(condDef, &afterLoop))
         return false;
 
     MDefinition* _;
-    if (!EmitExpr(f, ExprType::Void, &_))
+    if (!EmitExprStmt(f, &_))
         return false;
 
     if (!f.bindContinues(headId, maybeLabels))
         return false;
 
     if (expr == Expr::ForInitInc || expr == Expr::ForNoInitInc) {
         MDefinition* _;
-        if (!EmitExpr(f, ExprType::Void, &_))
+        if (!EmitExprStmt(f, &_))
             return false;
     }
 
     f.assertDebugCheckPoint();
 
     return f.closeLoop(loopEntry, afterLoop);
 }
 
@@ -2350,17 +2375,17 @@ EmitDoWhile(FunctionCompiler& f, const L
 {
     size_t headId = f.nextId();
 
     MBasicBlock* loopEntry;
     if (!f.startPendingLoop(headId, &loopEntry))
         return false;
 
     MDefinition* _;
-    if (!EmitExpr(f, ExprType::Void, &_))
+    if (!EmitExprStmt(f, &_))
         return false;
 
     if (!f.bindContinues(headId, maybeLabels))
         return false;
 
     MDefinition* condDef;
     if (!EmitExpr(f, ExprType::I32, &condDef))
         return false;
@@ -2372,25 +2397,25 @@ static bool
 EmitLabel(FunctionCompiler& f, LabelVector* maybeLabels)
 {
     uint32_t labelId = f.readU32();
 
     if (maybeLabels) {
         if (!maybeLabels->append(labelId))
             return false;
         MDefinition* _;
-        return EmitExpr(f, ExprType::Void, &_, maybeLabels);
+        return EmitExprStmt(f, &_, maybeLabels);
     }
 
     LabelVector labels;
     if (!labels.append(labelId))
         return false;
 
     MDefinition* _;
-    if (!EmitExpr(f, ExprType::Void, &_, &labels))
+    if (!EmitExprStmt(f, &_, &labels))
         return false;
 
     return f.bindLabeledBreaks(&labels);
 }
 
 typedef bool HasElseBlock;
 
 static bool
@@ -2408,17 +2433,17 @@ EmitIfElse(FunctionCompiler& f, bool has
         return false;
 
     MBasicBlock* thenBlock = nullptr;
     MBasicBlock* elseOrJoinBlock = nullptr;
     if (!f.branchAndStartThen(condition, &thenBlock, &elseOrJoinBlock))
         return false;
 
     MDefinition* _;
-    if (!EmitExpr(f, ExprType::Void, &_))
+    if (!EmitExprStmt(f, &_))
         return false;
 
     if (!f.appendThenBlock(&thenBlocks))
         return false;
 
     if (hasElse) {
         f.switchToElse(elseOrJoinBlock);
 
@@ -2428,17 +2453,17 @@ EmitIfElse(FunctionCompiler& f, bool has
             goto recurse;
         }
         if (nextStmt == Expr::IfElse) {
             hasElse = true;
             goto recurse;
         }
 
         MDefinition* _;
-        if (!EmitExpr(f, ExprType::Void, nextStmt, &_))
+        if (!EmitExprStmt(f, nextStmt, &_))
             return false;
 
         return f.joinIfElse(thenBlocks);
     } else {
         return f.joinIf(thenBlocks, elseOrJoinBlock);
     }
 }
 
@@ -2468,26 +2493,26 @@ EmitTableSwitch(FunctionCompiler& f)
 
     while (numCases--) {
         int32_t caseValue = f.readI32();
         MOZ_ASSERT(caseValue >= low && caseValue <= high);
         unsigned caseIndex = caseValue - low;
         if (!f.startSwitchCase(switchBlock, &cases[caseIndex]))
             return false;
         MDefinition* _;
-        if (!EmitExpr(f, ExprType::Void, &_))
+        if (!EmitExprStmt(f, &_))
             return false;
     }
 
     MBasicBlock* defaultBlock;
     if (!f.startSwitchDefault(switchBlock, &cases, &defaultBlock))
         return false;
 
     MDefinition* _;
-    if (hasDefault && !EmitExpr(f, ExprType::Void, &_))
+    if (hasDefault && !EmitExprStmt(f, &_))
         return false;
 
     return f.joinSwitch(switchBlock, cases, defaultBlock);
 }
 
 static bool
 EmitRet(FunctionCompiler& f)
 {
@@ -2501,24 +2526,26 @@ EmitRet(FunctionCompiler& f)
     MDefinition *def = nullptr;
     if (!EmitExpr(f, ret, &def))
         return false;
     f.returnExpr(def);
     return true;
 }
 
 static bool
-EmitBlock(FunctionCompiler& f, ExprType type, MDefinition** def)
+EmitBlock(FunctionCompiler& f, MaybeType type, MDefinition** def)
 {
     size_t numStmt = f.readU32();
-    for (size_t i = 0; i < numStmt; i++) {
+    for (size_t i = 1; i < numStmt; i++) {
         // Fine to clobber def, we only want the last use.
-        if (!EmitExpr(f, type, def))
+        if (!EmitExprStmt(f, def))
             return false;
     }
+    if (numStmt && !EmitExpr(f, type, def))
+        return false;
     f.assertDebugCheckPoint();
     return true;
 }
 
 typedef bool HasLabel;
 
 static bool
 EmitContinue(FunctionCompiler& f, bool hasLabel)
@@ -2534,17 +2561,17 @@ EmitBreak(FunctionCompiler& f, bool hasL
 {
     if (!hasLabel)
         return f.addBreak(nullptr);
     uint32_t labelId = f.readU32();
     return f.addBreak(&labelId);
 }
 
 static bool
-EmitExpr(FunctionCompiler& f, ExprType type, Expr op, MDefinition** def, LabelVector* maybeLabels)
+EmitExpr(FunctionCompiler& f, MaybeType type, Expr op, MDefinition** def, LabelVector* maybeLabels)
 {
     if (!f.mirGen().ensureBallast())
         return false;
 
     switch (op) {
       case Expr::Nop:
         return true;
       case Expr::Block:
@@ -2572,28 +2599,16 @@ EmitExpr(FunctionCompiler& f, ExprType t
       case Expr::ContinueLabel:
         return EmitContinue(f, HasLabel(true));
       case Expr::Break:
         return EmitBreak(f, HasLabel(false));
       case Expr::BreakLabel:
         return EmitBreak(f, HasLabel(true));
       case Expr::Return:
         return EmitRet(f);
-      case Expr::I32Expr:
-        return EmitExpr(f, ExprType::I32, def);
-      case Expr::F32Expr:
-        return EmitExpr(f, ExprType::F32, def);
-      case Expr::F64Expr:
-        return EmitExpr(f, ExprType::F64, def);
-      case Expr::I32X4Expr:
-        return EmitExpr(f, ExprType::I32x4, def);
-      case Expr::F32X4Expr:
-        return EmitExpr(f, ExprType::F32x4, def);
-      case Expr::B32X4Expr:
-        return EmitExpr(f, ExprType::B32x4, def);
       case Expr::CallInternal:
         return EmitInternalCall(f, type, def);
       case Expr::CallIndirect:
         return EmitFuncPtrCall(f, type, def);
       case Expr::CallImport:
         return EmitFFICall(f, type, def);
       case Expr::AtomicsFence:
         f.memoryBarrier(MembarFull);
@@ -2917,21 +2932,33 @@ EmitExpr(FunctionCompiler& f, ExprType t
       case Expr::Unreachable:
         break;
     }
 
     MOZ_CRASH("unexpected wasm opcode");
 }
 
 static bool
-EmitExpr(FunctionCompiler& f, ExprType type, MDefinition** def, LabelVector* maybeLabels)
+EmitExpr(FunctionCompiler& f, MaybeType type, MDefinition** def, LabelVector* maybeLabels)
 {
     return EmitExpr(f, type, f.readOpcode(), def, maybeLabels);
 }
 
+static bool
+EmitExprStmt(FunctionCompiler& f, Expr op, MDefinition** def, LabelVector* maybeLabels)
+{
+    return EmitExpr(f, MaybeType::Undefined(), op, def, maybeLabels);
+}
+
+static bool
+EmitExprStmt(FunctionCompiler& f, MDefinition** def, LabelVector* maybeLabels)
+{
+    return EmitExprStmt(f, f.readOpcode(), def, maybeLabels);
+}
+
 bool
 wasm::IonCompileFunction(IonCompileTask* task)
 {
     int64_t before = PRMJ_Now();
 
     const FuncBytecode& func = task->func();
     FuncCompileResults& results = task->results();
 
@@ -2947,17 +2974,17 @@ wasm::IonCompileFunction(IonCompileTask*
     // Build MIR graph
     {
         FunctionCompiler f(task->mg(), func, mir, results);
         if (!f.init())
             return false;
 
         while (!f.done()) {
             MDefinition* _;
-            if (!EmitExpr(f, ExprType::Void, &_))
+            if (!EmitExprStmt(f, &_))
                 return false;
         }
 
         f.checkPostconditions();
     }
 
     // Compile MIR graph
     {
--- a/js/src/jit-test/tests/asm.js/testFloat32.js
+++ b/js/src/jit-test/tests/asm.js/testFloat32.js
@@ -43,16 +43,18 @@ assertEq(asmLink(asmCompile('glob', USE_
 assertEq(asmLink(asmCompile('glob', USE_ASM + TO_FLOAT32 + "function f() { return toF(13.37); } return f"), this)(), Math.fround(13.37));
 assertEq(asmLink(asmCompile('glob', USE_ASM + TO_FLOAT32 + "function f() { return +toF(4.); } return f"), this)(), 4);
 assertEq(asmLink(asmCompile('glob', USE_ASM + TO_FLOAT32 + "function f() { return +~~toF(4.5); } return f"), this)(), 4);
 
 // Assign values
 assertAsmTypeFail('glob', USE_ASM + TO_FLOAT32 + "function f() { var i = toF(5.); i = 5; return toF(i); } return f");
 assertAsmTypeFail('glob', USE_ASM + TO_FLOAT32 + "function f() { var i = toF(5.); i = 6.; return toF(i); } return f");
 
+assertEq(asmLink(asmCompile('glob', USE_ASM + TO_FLOAT32 + "function f() { var i = toF(-0.); return toF(i); } return f"), this)(), -0);
+assertEq(asmLink(asmCompile('glob', USE_ASM + TO_FLOAT32 + "function f() { var i = toF(0.); return toF(i); } return f"), this)(), 0);
 assertEq(asmLink(asmCompile('glob', USE_ASM + TO_FLOAT32 + "function f() { var i = toF(5.); return toF(i); } return f"), this)(), 5);
 assertEq(asmLink(asmCompile('glob', USE_ASM + TO_FLOAT32 + "function f() { var i = toF(5.); i = toF(42); return toF(i); } return f"), this)(), 42);
 assertEq(asmLink(asmCompile('glob', USE_ASM + TO_FLOAT32 + "function f() { var i = toF(5.); i = toF(6.); return toF(i); } return f"), this)(), 6);
 
 assertAsmTypeFail('glob', 'ffi', 'heap', USE_ASM + TO_FLOAT32 + HEAP32 + "function f() { var i = toF(5.); f32[0] = toF(6.); i = f32[0]; return toF(i); } return f");
 assertEq(asmLink(asmCompile('glob', 'ffi', 'heap', USE_ASM + TO_FLOAT32 + HEAP32 + "function f() { var i = toF(5.); f32[0] = toF(6.); i = toF(f32[0]); return toF(i); } return f"), this, null, heap)(), 6);
 
 // Special array assignments (the other ones are tested in testHeapAccess)