Bug 1287220 - Baldr: update to binary version 0xc (r=luke)
authorDan Gohman <sunfish@mozilla.com>
Fri, 23 Sep 2016 09:13:15 -0500
changeset 315209 4119fba22f7fc6a60e504933d2e9a89d00aab0cd
parent 315208 8dd081bff02011225f8383a6e1ad1657e965b5f7
child 315210 06baf9c96c5b2f45df49941ef00f190c79445295
push id32563
push userihsiao@mozilla.com
push dateMon, 26 Sep 2016 11:18:33 +0000
treeherderautoland@eb840c87b5fd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1287220
milestone52.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 1287220 - Baldr: update to binary version 0xc (r=luke) MozReview-Commit-ID: EZmgEwtd4Yt * * * [mq]: fix-close-loop MozReview-Commit-ID: IGx436dWetv * * * [mq]: ensure-webassembly-in-eval MozReview-Commit-ID: J6eGrJPHN4A * * * [mq]: fix-unreachable MozReview-Commit-ID: IBVUVuRRm4t * * * [mq]: update-names-section MozReview-Commit-ID: 8LcjQh5lpcx * * * [mq]: skip-unknown MozReview-Commit-ID: Ekj9t3ydS9a
js/src/asmjs/AsmJS.cpp
js/src/asmjs/WasmAST.h
js/src/asmjs/WasmBaselineCompile.cpp
js/src/asmjs/WasmBinary.cpp
js/src/asmjs/WasmBinary.h
js/src/asmjs/WasmBinaryIterator.cpp
js/src/asmjs/WasmBinaryIterator.h
js/src/asmjs/WasmBinaryToAST.cpp
js/src/asmjs/WasmBinaryToExperimentalText.cpp
js/src/asmjs/WasmBinaryToText.cpp
js/src/asmjs/WasmCompile.cpp
js/src/asmjs/WasmGenerator.cpp
js/src/asmjs/WasmIonCompile.cpp
js/src/asmjs/WasmJS.cpp
js/src/asmjs/WasmJS.h
js/src/asmjs/WasmModule.cpp
js/src/asmjs/WasmTextToBinary.cpp
js/src/asmjs/WasmTextToBinary.h
js/src/asmjs/WasmTypes.cpp
js/src/asmjs/WasmTypes.h
js/src/builtin/TestingFunctions.cpp
js/src/doc/Debugger/Debugger.Script.md
js/src/doc/Debugger/Debugger.Source.md
js/src/jit-test/lib/wasm.js
js/src/jit-test/tests/asm.js/testAsmJSWasmMixing.js
js/src/jit-test/tests/asm.js/testBasic.js
js/src/jit-test/tests/debug/bug1257045.js
js/src/jit-test/tests/debug/wasm-01.js
js/src/jit-test/tests/debug/wasm-02.js
js/src/jit-test/tests/debug/wasm-03.js
js/src/jit-test/tests/debug/wasm-04.js
js/src/jit-test/tests/debug/wasm-05.js
js/src/jit-test/tests/wasm/backtrace.js
js/src/jit-test/tests/wasm/basic.js
js/src/jit-test/tests/wasm/bce.js
js/src/jit-test/tests/wasm/big-resize.js
js/src/jit-test/tests/wasm/binary.js
js/src/jit-test/tests/wasm/comments.js
js/src/jit-test/tests/wasm/compiler-frame-depth.js
js/src/jit-test/tests/wasm/const.js
js/src/jit-test/tests/wasm/control-flow.js
js/src/jit-test/tests/wasm/conversion.js
js/src/jit-test/tests/wasm/cross-global.js
js/src/jit-test/tests/wasm/fac.js
js/src/jit-test/tests/wasm/float.js
js/src/jit-test/tests/wasm/globals.js
js/src/jit-test/tests/wasm/grow-memory.js
js/src/jit-test/tests/wasm/import-export.js
js/src/jit-test/tests/wasm/import-gc.js
js/src/jit-test/tests/wasm/integer.js
js/src/jit-test/tests/wasm/jsapi.js
js/src/jit-test/tests/wasm/memory-aliasing.js
js/src/jit-test/tests/wasm/memory.js
js/src/jit-test/tests/wasm/nan-semantics.js
js/src/jit-test/tests/wasm/profiling.js
js/src/jit-test/tests/wasm/regress/misc-control-flow.js
js/src/jit-test/tests/wasm/regress/oom-eval.js
js/src/jit-test/tests/wasm/regress/regalloc-muli64.js
js/src/jit-test/tests/wasm/regress/reserve-joinreg.js
js/src/jit-test/tests/wasm/regress/select-any.js
js/src/jit-test/tests/wasm/regress/teavm-bugs.js
js/src/jit-test/tests/wasm/resizing.js
js/src/jit-test/tests/wasm/spec.js
js/src/jit-test/tests/wasm/spec/address.wast
js/src/jit-test/tests/wasm/spec/binary.wast
js/src/jit-test/tests/wasm/spec/block.wast
js/src/jit-test/tests/wasm/spec/block_comments.wast
js/src/jit-test/tests/wasm/spec/block_comments.wast.js
js/src/jit-test/tests/wasm/spec/br.wast
js/src/jit-test/tests/wasm/spec/br.wast.js
js/src/jit-test/tests/wasm/spec/br_if.wast
js/src/jit-test/tests/wasm/spec/br_if.wast.js
js/src/jit-test/tests/wasm/spec/br_table.wast
js/src/jit-test/tests/wasm/spec/br_table.wast.js
js/src/jit-test/tests/wasm/spec/break-drop.wast
js/src/jit-test/tests/wasm/spec/call.wast
js/src/jit-test/tests/wasm/spec/call.wast.js
js/src/jit-test/tests/wasm/spec/call_indirect.wast
js/src/jit-test/tests/wasm/spec/call_indirect.wast.js
js/src/jit-test/tests/wasm/spec/comments.wast
js/src/jit-test/tests/wasm/spec/comments.wast.js
js/src/jit-test/tests/wasm/spec/conversions.wast
js/src/jit-test/tests/wasm/spec/endianness.wast
js/src/jit-test/tests/wasm/spec/exports.wast
js/src/jit-test/tests/wasm/spec/exports.wast.js
js/src/jit-test/tests/wasm/spec/f32.wast
js/src/jit-test/tests/wasm/spec/f32_cmp.wast
js/src/jit-test/tests/wasm/spec/f64.wast
js/src/jit-test/tests/wasm/spec/f64_cmp.wast
js/src/jit-test/tests/wasm/spec/fac.wast
js/src/jit-test/tests/wasm/spec/float_exprs.wast
js/src/jit-test/tests/wasm/spec/float_literals.wast
js/src/jit-test/tests/wasm/spec/float_memory.wast
js/src/jit-test/tests/wasm/spec/float_misc.wast
js/src/jit-test/tests/wasm/spec/forward.wast
js/src/jit-test/tests/wasm/spec/func.wast
js/src/jit-test/tests/wasm/spec/func.wast.js
js/src/jit-test/tests/wasm/spec/func_ptrs.wast
js/src/jit-test/tests/wasm/spec/func_ptrs.wast.js
js/src/jit-test/tests/wasm/spec/functions.wast
js/src/jit-test/tests/wasm/spec/functions.wast.js
js/src/jit-test/tests/wasm/spec/get_local.wast
js/src/jit-test/tests/wasm/spec/get_local.wast.js
js/src/jit-test/tests/wasm/spec/globals.wast
js/src/jit-test/tests/wasm/spec/grow-memory.wast
js/src/jit-test/tests/wasm/spec/i32.wast
js/src/jit-test/tests/wasm/spec/i64.wast
js/src/jit-test/tests/wasm/spec/imports.wast
js/src/jit-test/tests/wasm/spec/imports.wast.js
js/src/jit-test/tests/wasm/spec/int_exprs.wast
js/src/jit-test/tests/wasm/spec/int_literals.wast
js/src/jit-test/tests/wasm/spec/labels.wast
js/src/jit-test/tests/wasm/spec/labels.wast.js
js/src/jit-test/tests/wasm/spec/left-to-right.wast
js/src/jit-test/tests/wasm/spec/left-to-right.wast.js
js/src/jit-test/tests/wasm/spec/linking.wast
js/src/jit-test/tests/wasm/spec/linking.wast.js
js/src/jit-test/tests/wasm/spec/loop.wast
js/src/jit-test/tests/wasm/spec/loop.wast.js
js/src/jit-test/tests/wasm/spec/memory.wast
js/src/jit-test/tests/wasm/spec/memory.wast.js
js/src/jit-test/tests/wasm/spec/memory_redundancy.wast
js/src/jit-test/tests/wasm/spec/memory_trap.wast
js/src/jit-test/tests/wasm/spec/names.wast
js/src/jit-test/tests/wasm/spec/names.wast.js
js/src/jit-test/tests/wasm/spec/nan-propagation.wast
js/src/jit-test/tests/wasm/spec/nop.wast
js/src/jit-test/tests/wasm/spec/nop.wast.js
js/src/jit-test/tests/wasm/spec/resizing.wast
js/src/jit-test/tests/wasm/spec/resizing.wast.js
js/src/jit-test/tests/wasm/spec/return.wast
js/src/jit-test/tests/wasm/spec/return.wast.js
js/src/jit-test/tests/wasm/spec/runaway-recursion.wast
js/src/jit-test/tests/wasm/spec/runaway-recursion.wast.js
js/src/jit-test/tests/wasm/spec/select.wast
js/src/jit-test/tests/wasm/spec/set_local.wast
js/src/jit-test/tests/wasm/spec/set_local.wast.js
js/src/jit-test/tests/wasm/spec/stack.wast
js/src/jit-test/tests/wasm/spec/stack.wast.js
js/src/jit-test/tests/wasm/spec/start.wast
js/src/jit-test/tests/wasm/spec/start.wast.js
js/src/jit-test/tests/wasm/spec/store_retval.wast
js/src/jit-test/tests/wasm/spec/switch.wast
js/src/jit-test/tests/wasm/spec/tee_local.wast
js/src/jit-test/tests/wasm/spec/tee_local.wast.js
js/src/jit-test/tests/wasm/spec/traps.wast
js/src/jit-test/tests/wasm/spec/typecheck.wast
js/src/jit-test/tests/wasm/spec/typecheck.wast.js
js/src/jit-test/tests/wasm/spec/unreachable.wast
js/src/jit-test/tests/wasm/spec/unreachable.wast.js
js/src/jit-test/tests/wasm/stack.js
js/src/jit-test/tests/wasm/start.js
js/src/jit-test/tests/wasm/stealing.js
js/src/jit-test/tests/wasm/table-gc.js
js/src/jit-test/tests/wasm/table-pre-barrier.js
js/src/jit-test/tests/wasm/tables.js
js/src/jit-test/tests/wasm/text.js
js/src/jit-test/tests/wasm/to-text-experimental.js
js/src/jit-test/tests/wasm/to-text.js
js/src/jit-test/tests/wasm/totext1.js
js/src/jit-test/tests/wasm/totext2.js
js/src/jit-test/tests/wasm/unreachable.js
js/src/js.msg
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -2950,87 +2950,106 @@ class MOZ_STACK_CLASS FunctionValidator
     }
 
     /**************************************************************** Labels */
   private:
     bool writeBr(uint32_t absolute, Expr expr = Expr::Br) {
         MOZ_ASSERT(expr == Expr::Br || expr == Expr::BrIf);
         MOZ_ASSERT(absolute < blockDepth_);
         return encoder().writeExpr(expr) &&
-               encoder().writeVarU32(0) &&  // break arity
                encoder().writeVarU32(blockDepth_ - 1 - absolute);
     }
     void removeLabel(PropertyName* label, LabelMap* map) {
         LabelMap::Ptr p = map->lookup(label);
         MOZ_ASSERT(p);
         map->remove(p);
     }
 
   public:
     bool pushBreakableBlock() {
         return encoder().writeExpr(Expr::Block) &&
+               encoder().writeFixedU8(uint8_t(ExprType::Void)) &&
                breakableStack_.append(blockDepth_++);
     }
     bool popBreakableBlock() {
         JS_ALWAYS_TRUE(breakableStack_.popCopy() == --blockDepth_);
         return encoder().writeExpr(Expr::End);
     }
 
     bool pushUnbreakableBlock(const NameVector* labels = nullptr) {
         if (labels) {
             for (PropertyName* label : *labels) {
                 if (!breakLabels_.putNew(label, blockDepth_))
                     return false;
             }
         }
         blockDepth_++;
-        return encoder().writeExpr(Expr::Block);
+        return encoder().writeExpr(Expr::Block) &&
+               encoder().writeFixedU8(uint8_t(ExprType::Void));
     }
     bool popUnbreakableBlock(const NameVector* labels = nullptr) {
         if (labels) {
             for (PropertyName* label : *labels)
                 removeLabel(label, &breakLabels_);
         }
         --blockDepth_;
         return encoder().writeExpr(Expr::End);
     }
 
     bool pushContinuableBlock() {
         return encoder().writeExpr(Expr::Block) &&
+               encoder().writeFixedU8(uint8_t(ExprType::Void)) &&
                continuableStack_.append(blockDepth_++);
     }
     bool popContinuableBlock() {
         JS_ALWAYS_TRUE(continuableStack_.popCopy() == --blockDepth_);
         return encoder().writeExpr(Expr::End);
     }
 
     bool pushLoop() {
-        return encoder().writeExpr(Expr::Loop) &&
+        return encoder().writeExpr(Expr::Block) &&
+               encoder().writeFixedU8(uint8_t(ExprType::Void)) &&
+               encoder().writeExpr(Expr::Loop) &&
+               encoder().writeFixedU8(uint8_t(ExprType::Void)) &&
                breakableStack_.append(blockDepth_++) &&
                continuableStack_.append(blockDepth_++);
     }
     bool popLoop() {
         JS_ALWAYS_TRUE(continuableStack_.popCopy() == --blockDepth_);
         JS_ALWAYS_TRUE(breakableStack_.popCopy() == --blockDepth_);
-        return encoder().writeExpr(Expr::End);
-    }
-
-    bool pushIf() {
+        return encoder().writeExpr(Expr::End) &&
+               encoder().writeExpr(Expr::End);
+    }
+
+    bool pushIf(size_t* typeAt) {
         ++blockDepth_;
-        return encoder().writeExpr(Expr::If);
+        return encoder().writeExpr(Expr::If) &&
+               encoder().writePatchableFixedU7(typeAt);
     }
     bool switchToElse() {
         MOZ_ASSERT(blockDepth_ > 0);
         return encoder().writeExpr(Expr::Else);
     }
+    void setIfType(size_t typeAt, ExprType type) {
+        encoder().patchFixedU7(typeAt, uint8_t(type));
+    }
     bool popIf() {
         MOZ_ASSERT(blockDepth_ > 0);
         --blockDepth_;
         return encoder().writeExpr(Expr::End);
     }
+    bool popIf(size_t typeAt, ExprType type) {
+        MOZ_ASSERT(blockDepth_ > 0);
+        --blockDepth_;
+        if (!encoder().writeExpr(Expr::End))
+            return false;
+
+        setIfType(typeAt, type);
+        return true;
+    }
 
     bool writeBreakIf() {
         return writeBr(breakableStack_.back(), Expr::BrIf);
     }
     bool writeContinueIf() {
         return writeBr(continuableStack_.back(), Expr::BrIf);
     }
     bool writeUnlabeledBreakOrContinue(bool isBreak) {
@@ -3807,16 +3826,19 @@ IsLiteralOrConst(FunctionValidator& f, P
 
     *lit = ExtractNumericLiteral(f.m(), pn);
     return true;
 }
 
 static bool
 CheckFinalReturn(FunctionValidator& f, ParseNode* lastNonEmptyStmt)
 {
+    if (!f.encoder().writeExpr(Expr::End))
+        return false;
+
     if (!f.hasAlreadyReturned()) {
         f.setReturnedType(ExprType::Void);
         return true;
     }
 
     if (!lastNonEmptyStmt->isKind(PNK_RETURN) && !IsVoid(f.returnedType()))
         return f.fail(lastNonEmptyStmt, "void incompatible with previous return type");
 
@@ -4137,44 +4159,44 @@ CheckStoreArray(FunctionValidator& f, Pa
         break;
       default:
         MOZ_CRASH("Unexpected view type");
     }
 
     switch (viewType) {
       case Scalar::Int8:
       case Scalar::Uint8:
-        if (!f.encoder().writeExpr(Expr::I32Store8))
+        if (!f.encoder().writeExpr(Expr::I32TeeStore8))
             return false;
         break;
       case Scalar::Int16:
       case Scalar::Uint16:
-        if (!f.encoder().writeExpr(Expr::I32Store16))
+        if (!f.encoder().writeExpr(Expr::I32TeeStore16))
             return false;
         break;
       case Scalar::Int32:
       case Scalar::Uint32:
-        if (!f.encoder().writeExpr(Expr::I32Store))
+        if (!f.encoder().writeExpr(Expr::I32TeeStore))
             return false;
         break;
       case Scalar::Float32:
         if (rhsType.isFloatish()) {
-            if (!f.encoder().writeExpr(Expr::F32Store))
+            if (!f.encoder().writeExpr(Expr::F32TeeStore))
                 return false;
         } else {
-            if (!f.encoder().writeExpr(Expr::F64StoreF32))
+            if (!f.encoder().writeExpr(Expr::F64TeeStoreF32))
                 return false;
         }
         break;
       case Scalar::Float64:
         if (rhsType.isFloatish()) {
-            if (!f.encoder().writeExpr(Expr::F32StoreF64))
+            if (!f.encoder().writeExpr(Expr::F32TeeStoreF64))
                 return false;
         } else {
-            if (!f.encoder().writeExpr(Expr::F64Store))
+            if (!f.encoder().writeExpr(Expr::F64TeeStore))
                 return false;
         }
         break;
       default: MOZ_CRASH("unexpected scalar type");
     }
 
     if (!WriteArrayAccessFlags(f, viewType))
         return false;
@@ -4188,17 +4210,17 @@ CheckAssignName(FunctionValidator& f, Pa
 {
     RootedPropertyName name(f.cx(), lhs->name());
 
     if (const FunctionValidator::Local* lhsVar = f.lookupLocal(name)) {
         Type rhsType;
         if (!CheckExpr(f, rhs, &rhsType))
             return false;
 
-        if (!f.encoder().writeExpr(Expr::SetLocal))
+        if (!f.encoder().writeExpr(Expr::TeeLocal))
             return false;
         if (!f.encoder().writeVarU32(lhsVar->slot))
             return false;
 
         if (!(rhsType <= lhsVar->type)) {
             return f.failf(lhs, "%s is not a subtype of %s",
                            rhsType.toChars(), lhsVar->type.toChars());
         }
@@ -4212,17 +4234,17 @@ CheckAssignName(FunctionValidator& f, Pa
 
         Type rhsType;
         if (!CheckExpr(f, rhs, &rhsType))
             return false;
 
         Type globType = global->varOrConstType();
         if (!(rhsType <= globType))
             return f.failf(lhs, "%s is not a subtype of %s", rhsType.toChars(), globType.toChars());
-        if (!f.encoder().writeExpr(Expr::SetGlobal))
+        if (!f.encoder().writeExpr(Expr::TeeGlobal))
             return false;
         if (!f.encoder().writeVarU32(global->varOrConstIndex()))
             return false;
 
         *type = rhsType;
         return true;
     }
 
@@ -4711,30 +4733,25 @@ CheckInternalCall(FunctionValidator& f, 
                   Type ret, Type* type)
 {
     MOZ_ASSERT(ret.isCanonical());
 
     ValTypeVector args;
     if (!CheckCallArgs<CheckIsArgType>(f, callNode, &args))
         return false;
 
-    uint32_t arity = args.length();
     Sig sig(Move(args), ret.canonicalToExprType());
 
     ModuleValidator::Func* callee;
     if (!CheckFunctionSignature(f.m(), callNode, Move(sig), calleeName, &callee))
         return false;
 
     if (!f.writeCall(callNode, Expr::Call))
         return false;
 
-    // Call arity
-    if (!f.encoder().writeVarU32(arity))
-        return false;
-
     // Function's index, to find out the function's entry
     if (!f.encoder().writeVarU32(callee->index()))
         return false;
 
     *type = Type::ret(ret);
     return true;
 }
 
@@ -4800,28 +4817,23 @@ CheckFuncPtrCall(FunctionValidator& f, P
 
     if (!indexType.isIntish())
         return f.failf(indexNode, "%s is not a subtype of intish", indexType.toChars());
 
     ValTypeVector args;
     if (!CheckCallArgs<CheckIsArgType>(f, callNode, &args))
         return false;
 
-    uint32_t arity = args.length();
     Sig sig(Move(args), ret.canonicalToExprType());
 
     uint32_t tableIndex;
     if (!CheckFuncPtrTableAgainstExisting(f.m(), tableNode, name, Move(sig), mask, &tableIndex))
         return false;
 
-    if (!f.writeCall(callNode, Expr::CallIndirect))
-        return false;
-
-    // Call arity
-    if (!f.encoder().writeVarU32(arity))
+    if (!f.writeCall(callNode, Expr::OldCallIndirect))
         return false;
 
     // Call signature
     if (!f.encoder().writeVarU32(f.m().funcPtrTable(tableIndex).sigIndex()))
         return false;
 
     *type = Type::ret(ret);
     return true;
@@ -4846,30 +4858,25 @@ CheckFFICall(FunctionValidator& f, Parse
         return f.fail(callNode, "FFI calls can't return float");
     if (ret.isSimd())
         return f.fail(callNode, "FFI calls can't return SIMD values");
 
     ValTypeVector args;
     if (!CheckCallArgs<CheckIsExternType>(f, callNode, &args))
         return false;
 
-    uint32_t arity = args.length();
     Sig sig(Move(args), ret.canonicalToExprType());
 
     uint32_t importIndex;
     if (!f.m().declareImport(calleeName, Move(sig), ffiIndex, &importIndex))
         return false;
 
     if (!f.writeCall(callNode, Expr::CallImport))
         return false;
 
-    // Call arity
-    if (!f.encoder().writeVarU32(arity))
-        return false;
-
     // Import index
     if (!f.encoder().writeVarU32(importIndex))
         return false;
 
     *type = Type::ret(ret);
     return true;
 }
 
@@ -5668,16 +5675,20 @@ CoerceResult(FunctionValidator& f, Parse
              Type* type)
 {
     MOZ_ASSERT(expected.isCanonical());
 
     // At this point, the bytecode resembles this:
     //      | the thing we wanted to coerce | current position |>
     switch (expected.which()) {
       case Type::Void:
+        if (!actual.isVoid()) {
+            if (!f.encoder().writeExpr(Expr::Drop))
+                return false;
+        }
         break;
       case Type::Int:
         if (!actual.isIntish())
             return f.failf(expr, "%s is not a subtype of intish", actual.toChars());
         break;
       case Type::Float:
         if (!CheckFloatCoercionArg(f, expr, actual))
             return false;
@@ -5920,25 +5931,31 @@ CheckComma(FunctionValidator& f, ParseNo
     MOZ_ASSERT(comma->isKind(PNK_COMMA));
     ParseNode* operands = ListHead(comma);
 
     // The block depth isn't taken into account here, because a comma list can't
     // contain breaks and continues and nested control flow structures.
     if (!f.encoder().writeExpr(Expr::Block))
         return false;
 
+    size_t typeAt;
+    if (!f.encoder().writePatchableFixedU7(&typeAt))
+        return false;
+
     ParseNode* pn = operands;
     for (; NextNode(pn); pn = NextNode(pn)) {
         if (!CheckAsExprStatement(f, pn))
             return false;
     }
 
     if (!CheckExpr(f, pn, type))
         return false;
 
+    f.encoder().patchFixedU7(typeAt, uint8_t(Type::canonicalize(*type).canonicalToExprType()));
+
     return f.encoder().writeExpr(Expr::End);
 }
 
 static bool
 CheckConditional(FunctionValidator& f, ParseNode* ternary, Type* type)
 {
     MOZ_ASSERT(ternary->isKind(PNK_CONDITIONAL));
 
@@ -5948,17 +5965,18 @@ CheckConditional(FunctionValidator& f, P
 
     Type condType;
     if (!CheckExpr(f, cond, &condType))
         return false;
 
     if (!condType.isInt())
         return f.failf(cond, "%s is not a subtype of int", condType.toChars());
 
-    if (!f.pushIf())
+    size_t typeAt;
+    if (!f.pushIf(&typeAt))
         return false;
 
     Type thenType;
     if (!CheckExpr(f, thenExpr, &thenType))
         return false;
 
     if (!f.switchToElse())
         return false;
@@ -5976,17 +5994,17 @@ CheckConditional(FunctionValidator& f, P
     } else if (thenType.isSimd() && elseType == thenType) {
         *type = thenType;
     } else {
         return f.failf(ternary, "then/else branches of conditional must both produce int, float, "
                        "double or SIMD types, current types are %s and %s",
                        thenType.toChars(), elseType.toChars());
     }
 
-    if (!f.popIf())
+    if (!f.popIf(typeAt, Type::canonicalize(*type).canonicalToExprType()))
         return false;
 
     return true;
 }
 
 static bool
 IsValidIntMultiplyConstant(ModuleValidator& m, ParseNode* expr)
 {
@@ -6347,20 +6365,31 @@ CheckExpr(FunctionValidator& f, ParseNod
 }
 
 static bool
 CheckStatement(FunctionValidator& f, ParseNode* stmt);
 
 static bool
 CheckAsExprStatement(FunctionValidator& f, ParseNode* expr)
 {
-    Type ignored;
-    if (expr->isKind(PNK_CALL))
+    if (expr->isKind(PNK_CALL)) {
+        Type ignored;
         return CheckCoercedCall(f, expr, Type::Void, &ignored);
-    return CheckExpr(f, expr, &ignored);
+    }
+
+    Type resultType;
+    if (!CheckExpr(f, expr, &resultType))
+        return false;
+
+    if (!resultType.isVoid()) {
+        if (!f.encoder().writeExpr(Expr::Drop))
+            return false;
+    }
+
+    return true;
 }
 
 static bool
 CheckExprStatement(FunctionValidator& f, ParseNode* exprStmt)
 {
     MOZ_ASSERT(exprStmt->isKind(PNK_SEMI));
     ParseNode* expr = UnaryKid(exprStmt);
     if (!expr)
@@ -6398,20 +6427,22 @@ CheckLoopConditionOnEntry(FunctionValida
 static bool
 CheckWhile(FunctionValidator& f, ParseNode* whileStmt, const NameVector* labels = nullptr)
 {
     MOZ_ASSERT(whileStmt->isKind(PNK_WHILE));
     ParseNode* cond = BinaryLeft(whileStmt);
     ParseNode* body = BinaryRight(whileStmt);
 
     // A while loop `while(#cond) #body` is equivalent to:
-    // (loop $after_loop $top
-    //    (brIf $after_loop (i32.eq 0 #cond))
-    //    #body
-    //    (br $top)
+    // (block $after_loop
+    //    (loop $top
+    //       (brIf $after_loop (i32.eq 0 #cond))
+    //       #body
+    //       (br $top)
+    //    )
     // )
     if (labels && !f.addLabels(*labels, 0, 1))
         return false;
 
     if (!f.pushLoop())
         return false;
 
     if (!CheckLoopConditionOnEntry(f, cond))
@@ -6440,21 +6471,23 @@ CheckFor(FunctionValidator& f, ParseNode
 
     ParseNode* maybeInit = TernaryKid1(forHead);
     ParseNode* maybeCond = TernaryKid2(forHead);
     ParseNode* maybeInc = TernaryKid3(forHead);
 
     // A for-loop `for (#init; #cond; #inc) #body` is equivalent to:
     // (block                                               // depth X
     //   (#init)
-    //   (loop $after_loop $loop_top                        // depth X+2 (loop)
-    //     (brIf $after (eq 0 #cond))
-    //     (block $after_body #body)                        // depth X+3
-    //     #inc
-    //     (br $loop_top)
+    //   (block $after_loop                                 // depth X+1 (block)
+    //     (loop $loop_top                                  // depth X+2 (loop)
+    //       (brIf $after (eq 0 #cond))
+    //       (block $after_body #body)                      // depth X+3
+    //       #inc
+    //       (br $loop_top)
+    //     )
     //   )
     // )
     // A break in the body should break out to $after_loop, i.e. depth + 1.
     // A continue in the body should break out to $after_body, i.e. depth + 3.
     if (labels && !f.addLabels(*labels, 1, 3))
         return false;
 
     if (!f.pushUnbreakableBlock())
@@ -6501,19 +6534,21 @@ CheckFor(FunctionValidator& f, ParseNode
 static bool
 CheckDoWhile(FunctionValidator& f, ParseNode* whileStmt, const NameVector* labels = nullptr)
 {
     MOZ_ASSERT(whileStmt->isKind(PNK_DOWHILE));
     ParseNode* body = BinaryLeft(whileStmt);
     ParseNode* cond = BinaryRight(whileStmt);
 
     // A do-while loop `do { #body } while (#cond)` is equivalent to:
-    // (loop $after_loop $top       // depth X
-    //   (block #body)              // depth X+2
-    //   (brIf #cond $top)
+    // (block $after_loop           // depth X
+    //   (loop $top                 // depth X+1
+    //     (block #body)            // depth X+2
+    //     (brIf #cond $top)
+    //   )
     // )
     // A break should break out of the entire loop, i.e. at depth 0.
     // A continue should break out to the condition, i.e. at depth 2.
     if (labels && !f.addLabels(*labels, 0, 2))
         return false;
 
     if (!f.pushLoop())
         return false;
@@ -6595,18 +6630,21 @@ CheckIf(FunctionValidator& f, ParseNode*
     ParseNode* elseStmt = TernaryKid3(ifStmt);
 
     Type condType;
     if (!CheckExpr(f, cond, &condType))
         return false;
     if (!condType.isInt())
         return f.failf(cond, "%s is not a subtype of int", condType.toChars());
 
-    if (!f.pushIf())
-        return false;
+    size_t typeAt;
+    if (!f.pushIf(&typeAt))
+        return false;
+
+    f.setIfType(typeAt, ExprType::Void);
 
     if (!CheckStatement(f, thenStmt))
         return false;
 
     if (elseStmt) {
         if (!f.switchToElse())
             return false;
 
@@ -6741,18 +6779,23 @@ CheckSwitch(FunctionValidator& f, ParseN
 
     if (switchBody->isKind(PNK_LEXICALSCOPE)) {
         if (!switchBody->isEmptyScope())
             return f.fail(switchBody, "switch body may not contain lexical declarations");
         switchBody = switchBody->scopeBody();
     }
 
     ParseNode* stmt = ListHead(switchBody);
-    if (!stmt)
-        return CheckSwitchExpr(f, switchExpr);
+    if (!stmt) {
+        if (!CheckSwitchExpr(f, switchExpr))
+            return false;
+        if (!f.encoder().writeExpr(Expr::Drop))
+            return false;
+        return true;
+    }
 
     if (!CheckDefaultAtEnd(f, stmt))
         return false;
 
     int32_t low = 0, high = 0;
     uint32_t tableLength = 0;
     if (!CheckSwitchRange(f, stmt, &low, &high, &tableLength))
         return false;
@@ -6805,35 +6848,31 @@ CheckSwitch(FunctionValidator& f, ParseN
         if (!CheckSwitchExpr(f, switchExpr))
             return false;
     }
 
     // Start the br_table block.
     if (!f.encoder().writeExpr(Expr::BrTable))
         return false;
 
-    // The br_table arity.
-    if (!f.encoder().writeVarU32(0))
-        return false;
-
     // Write the number of cases (tableLength - 1 + 1 (default)).
     // Write the number of cases (tableLength - 1 + 1 (default)).
     if (!f.encoder().writeVarU32(tableLength))
         return false;
 
     // Each case value describes the relative depth to the actual block. When
     // a case is not explicitly defined, it goes to the default.
     for (size_t i = 0; i < tableLength; i++) {
         uint32_t target = caseDepths[i] == CASE_NOT_DEFINED ? defaultDepth : caseDepths[i];
-        if (!f.encoder().writeFixedU32(target))
+        if (!f.encoder().writeVarU32(target))
             return false;
     }
 
     // Write the default depth.
-    if (!f.encoder().writeFixedU32(defaultDepth))
+    if (!f.encoder().writeVarU32(defaultDepth))
         return false;
 
     // Our br_table is done. Close its block, write the cases down in order.
     if (!f.popUnbreakableBlock())
         return false;
 
     for (; stmt && !IsDefaultCase(stmt); stmt = NextNode(stmt)) {
         if (!CheckStatement(f, CaseBody(stmt)))
@@ -6888,19 +6927,16 @@ CheckReturn(FunctionValidator& f, ParseN
 
         if (!CheckReturnType(f, expr, Type::canonicalize(type)))
             return false;
     }
 
     if (!f.encoder().writeExpr(Expr::Return))
         return false;
 
-    if (!f.encoder().writeVarU32(expr ? 1 : 0))
-        return false;
-
     return true;
 }
 
 static bool
 CheckStatementList(FunctionValidator& f, ParseNode* stmtList, const NameVector* labels /*= nullptr */)
 {
     MOZ_ASSERT(stmtList->isKind(PNK_STATEMENTLIST));
 
--- a/js/src/asmjs/WasmAST.h
+++ b/js/src/asmjs/WasmAST.h
@@ -190,94 +190,129 @@ enum class AstExprKind
     Block,
     Branch,
     BranchTable,
     Call,
     CallIndirect,
     ComparisonOperator,
     Const,
     ConversionOperator,
+    Drop,
+    First,
     GetGlobal,
     GetLocal,
     If,
     Load,
+    Nop,
+    Pop,
     Return,
     SetGlobal,
     SetLocal,
+    TeeLocal,
     Store,
     TernaryOperator,
     UnaryOperator,
     NullaryOperator,
     Unreachable
 };
 
 class AstExpr : public AstNode
 {
     const AstExprKind kind_;
+    ExprType type_;
 
   protected:
-    explicit AstExpr(AstExprKind kind)
-      : kind_(kind)
+    AstExpr(AstExprKind kind, ExprType type)
+      : kind_(kind), type_(type)
     {}
 
   public:
     AstExprKind kind() const { return kind_; }
 
+    bool isVoid() const { return IsVoid(type_); }
+
+    // Note that for nodes other than blocks and block-like things, this
+    // may return ExprType::Limit for nodes with non-void types.
+    ExprType type() const { return type_; }
+
     template <class T>
     T& as() {
         MOZ_ASSERT(kind() == T::Kind);
         return static_cast<T&>(*this);
     }
 };
 
+struct AstNop : AstExpr
+{
+   static const AstExprKind Kind = AstExprKind::Nop;
+   AstNop()
+      : AstExpr(AstExprKind::Nop, ExprType::Void)
+   {}
+};
+
 struct AstUnreachable : AstExpr
 {
     static const AstExprKind Kind = AstExprKind::Unreachable;
     AstUnreachable()
-      : AstExpr(AstExprKind::Unreachable)
+      : AstExpr(AstExprKind::Unreachable, ExprType::Void)
     {}
 };
 
+class AstDrop : public AstExpr
+{
+    AstExpr& value_;
+
+  public:
+    static const AstExprKind Kind = AstExprKind::Drop;
+    explicit AstDrop(AstExpr& value)
+      : AstExpr(AstExprKind::Drop, ExprType::Void),
+        value_(value)
+    {}
+    AstExpr& value() const {
+        return value_;
+    }
+};
+
 class AstConst : public AstExpr
 {
     const Val val_;
 
   public:
     static const AstExprKind Kind = AstExprKind::Const;
     explicit AstConst(Val val)
-      : AstExpr(Kind),
+      : AstExpr(Kind, ExprType::Limit),
         val_(val)
     {}
     Val val() const { return val_; }
 };
 
 class AstGetLocal : public AstExpr
 {
     AstRef local_;
 
   public:
     static const AstExprKind Kind = AstExprKind::GetLocal;
     explicit AstGetLocal(AstRef local)
-      : AstExpr(Kind),
+      : AstExpr(Kind, ExprType::Limit),
         local_(local)
     {}
     AstRef& local() {
         return local_;
     }
 };
 
 class AstSetLocal : public AstExpr
 {
     AstRef local_;
     AstExpr& value_;
 
   public:
     static const AstExprKind Kind = AstExprKind::SetLocal;
     AstSetLocal(AstRef local, AstExpr& value)
-      : AstExpr(Kind),
+      : AstExpr(Kind, ExprType::Void),
         local_(local),
         value_(value)
     {}
     AstRef& local() {
         return local_;
     }
     AstExpr& value() const {
         return value_;
@@ -286,78 +321,96 @@ class AstSetLocal : public AstExpr
 
 class AstGetGlobal : public AstExpr
 {
     AstRef global_;
 
   public:
     static const AstExprKind Kind = AstExprKind::GetGlobal;
     explicit AstGetGlobal(AstRef global)
-      : AstExpr(Kind),
+      : AstExpr(Kind, ExprType::Limit),
         global_(global)
     {}
     AstRef& global() {
         return global_;
     }
 };
 
 class AstSetGlobal : public AstExpr
 {
     AstRef global_;
     AstExpr& value_;
 
   public:
     static const AstExprKind Kind = AstExprKind::SetGlobal;
     AstSetGlobal(AstRef global, AstExpr& value)
-      : AstExpr(Kind),
+      : AstExpr(Kind, ExprType::Void),
         global_(global),
         value_(value)
     {}
     AstRef& global() {
         return global_;
     }
     AstExpr& value() const {
         return value_;
     }
 };
 
+class AstTeeLocal : public AstExpr
+{
+    AstRef local_;
+    AstExpr& value_;
+
+  public:
+    static const AstExprKind Kind = AstExprKind::TeeLocal;
+    AstTeeLocal(AstRef local, AstExpr& value)
+      : AstExpr(Kind, ExprType::Limit),
+        local_(local),
+        value_(value)
+    {}
+    AstRef& local() {
+        return local_;
+    }
+    AstExpr& value() const {
+        return value_;
+    }
+};
+
 class AstBlock : public AstExpr
 {
     Expr expr_;
-    AstName breakName_;
-    AstName continueName_;
+    AstName name_;
     AstExprVector exprs_;
 
   public:
     static const AstExprKind Kind = AstExprKind::Block;
-    explicit AstBlock(Expr expr, AstName breakName, AstName continueName, AstExprVector&& exprs)
-      : AstExpr(Kind),
+    explicit AstBlock(Expr expr, ExprType type, AstName name, AstExprVector&& exprs)
+      : AstExpr(Kind, type),
         expr_(expr),
-        breakName_(breakName),
-        continueName_(continueName),
+        name_(name),
         exprs_(Move(exprs))
     {}
 
     Expr expr() const { return expr_; }
-    AstName breakName() const { return breakName_; }
-    AstName continueName() const { return continueName_; }
+    AstName name() const { return name_; }
     const AstExprVector& exprs() const { return exprs_; }
 };
 
 class AstBranch : public AstExpr
 {
     Expr expr_;
     AstExpr* cond_;
     AstRef target_;
     AstExpr* value_;
 
   public:
     static const AstExprKind Kind = AstExprKind::Branch;
-    explicit AstBranch(Expr expr, AstExpr* cond, AstRef target, AstExpr* value)
-      : AstExpr(Kind),
+    explicit AstBranch(Expr expr, ExprType type,
+                       AstExpr* cond, AstRef target, AstExpr* value)
+      : AstExpr(Kind, type),
         expr_(expr),
         cond_(cond),
         target_(target),
         value_(value)
     {}
 
     Expr expr() const { return expr_; }
     AstRef& target() { return target_; }
@@ -368,80 +421,77 @@ class AstBranch : public AstExpr
 class AstCall : public AstExpr
 {
     Expr expr_;
     AstRef func_;
     AstExprVector args_;
 
   public:
     static const AstExprKind Kind = AstExprKind::Call;
-    AstCall(Expr expr, AstRef func, AstExprVector&& args)
-      : AstExpr(Kind), expr_(expr), func_(func), args_(Move(args))
+    AstCall(Expr expr, ExprType type, AstRef func, AstExprVector&& args)
+      : AstExpr(Kind, type), expr_(expr), func_(func), args_(Move(args))
     {}
 
     Expr expr() const { return expr_; }
     AstRef& func() { return func_; }
     const AstExprVector& args() const { return args_; }
 };
 
 class AstCallIndirect : public AstExpr
 {
     AstRef sig_;
+    AstExprVector args_;
     AstExpr* index_;
-    AstExprVector args_;
 
   public:
     static const AstExprKind Kind = AstExprKind::CallIndirect;
-    AstCallIndirect(AstRef sig, AstExpr* index, AstExprVector&& args)
-      : AstExpr(Kind), sig_(sig), index_(index), args_(Move(args))
+    AstCallIndirect(AstRef sig, ExprType type, AstExprVector&& args, AstExpr* index)
+      : AstExpr(Kind, type), sig_(sig), args_(Move(args)), index_(index)
     {}
     AstRef& sig() { return sig_; }
+    const AstExprVector& args() const { return args_; }
     AstExpr* index() const { return index_; }
-    const AstExprVector& args() const { return args_; }
 };
 
 class AstReturn : public AstExpr
 {
     AstExpr* maybeExpr_;
 
   public:
     static const AstExprKind Kind = AstExprKind::Return;
     explicit AstReturn(AstExpr* maybeExpr)
-      : AstExpr(Kind),
+      : AstExpr(Kind, ExprType::Void),
         maybeExpr_(maybeExpr)
     {}
     AstExpr* maybeExpr() const { return maybeExpr_; }
 };
 
 class AstIf : public AstExpr
 {
     AstExpr* cond_;
-    AstName thenName_;
+    AstName name_;
     AstExprVector thenExprs_;
-    AstName elseName_;
     AstExprVector elseExprs_;
 
   public:
     static const AstExprKind Kind = AstExprKind::If;
-    AstIf(AstExpr* cond, AstName thenName, AstExprVector&& thenExprs,
-          AstName elseName, AstExprVector&& elseExprs)
-      : AstExpr(Kind),
+    AstIf(ExprType type, AstExpr* cond, AstName name,
+          AstExprVector&& thenExprs, AstExprVector&& elseExprs)
+      : AstExpr(Kind, type),
         cond_(cond),
-        thenName_(thenName),
+        name_(name),
         thenExprs_(Move(thenExprs)),
-        elseName_(elseName),
         elseExprs_(Move(elseExprs))
     {}
 
     AstExpr& cond() const { return *cond_; }
     const AstExprVector& thenExprs() const { return thenExprs_; }
     bool hasElse() const { return elseExprs_.length(); }
     const AstExprVector& elseExprs() const { MOZ_ASSERT(hasElse()); return elseExprs_; }
-    AstName thenName() const { return thenName_; }
-    AstName elseName() const { return elseName_; }
+    AstName name() const { return name_; }
 };
 
 class AstLoadStoreAddress
 {
     AstExpr* base_;
     int32_t flags_;
     int32_t offset_;
 
@@ -460,17 +510,17 @@ class AstLoadStoreAddress
 class AstLoad : public AstExpr
 {
     Expr expr_;
     AstLoadStoreAddress address_;
 
   public:
     static const AstExprKind Kind = AstExprKind::Load;
     explicit AstLoad(Expr expr, const AstLoadStoreAddress &address)
-      : AstExpr(Kind),
+      : AstExpr(Kind, ExprType::Limit),
         expr_(expr),
         address_(address)
     {}
 
     Expr expr() const { return expr_; }
     const AstLoadStoreAddress& address() const { return address_; }
 };
 
@@ -479,17 +529,17 @@ class AstStore : public AstExpr
     Expr expr_;
     AstLoadStoreAddress address_;
     AstExpr* value_;
 
   public:
     static const AstExprKind Kind = AstExprKind::Store;
     explicit AstStore(Expr expr, const AstLoadStoreAddress &address,
                           AstExpr* value)
-      : AstExpr(Kind),
+      : AstExpr(Kind, ExprType::Void),
         expr_(expr),
         address_(address),
         value_(value)
     {}
 
     Expr expr() const { return expr_; }
     const AstLoadStoreAddress& address() const { return address_; }
     AstExpr& value() const { return *value_; }
@@ -500,18 +550,18 @@ class AstBranchTable : public AstExpr
     AstExpr& index_;
     AstRef default_;
     AstRefVector table_;
     AstExpr* value_;
 
   public:
     static const AstExprKind Kind = AstExprKind::BranchTable;
     explicit AstBranchTable(AstExpr& index, AstRef def, AstRefVector&& table,
-                                AstExpr* maybeValue)
-      : AstExpr(Kind),
+                            AstExpr* maybeValue)
+      : AstExpr(Kind, ExprType::Void),
         index_(index),
         default_(def),
         table_(Move(table)),
         value_(maybeValue)
     {}
     AstExpr& index() const { return index_; }
     AstRef& def() { return default_; }
     AstRefVector& table() { return table_; }
@@ -537,30 +587,16 @@ class AstFunc : public AstNode
     {}
     AstRef& sig() { return sig_; }
     const AstValTypeVector& vars() const { return vars_; }
     const AstNameVector& locals() const { return localNames_; }
     const AstExprVector& body() const { return body_; }
     AstName name() const { return name_; }
 };
 
-class AstResizable
-{
-    uint32_t initial_;
-    Maybe<uint32_t> maximum_;
-
-  public:
-    AstResizable() : initial_(0), maximum_() {}
-    AstResizable(uint32_t initial, Maybe<uint32_t> maximum)
-      : initial_(initial), maximum_(maximum)
-    {}
-    uint32_t initial() const { return initial_; }
-    const Maybe<uint32_t>& maximum() const { return maximum_; }
-};
-
 class AstGlobal : public AstNode
 {
     AstName name_;
     uint32_t flags_;
     ValType type_;
     Maybe<AstExpr*> init_;
 
   public:
@@ -585,40 +621,40 @@ typedef AstVector<AstGlobal*> AstGlobalV
 class AstImport : public AstNode
 {
     AstName name_;
     AstName module_;
     AstName field_;
     DefinitionKind kind_;
 
     AstRef funcSig_;
-    AstResizable resizable_;
+    ResizableLimits resizable_;
     AstGlobal global_;
 
   public:
     AstImport(AstName name, AstName module, AstName field, AstRef funcSig)
       : name_(name), module_(module), field_(field), kind_(DefinitionKind::Function), funcSig_(funcSig)
     {}
-    AstImport(AstName name, AstName module, AstName field, DefinitionKind kind, AstResizable resizable)
+    AstImport(AstName name, AstName module, AstName field, DefinitionKind kind, ResizableLimits resizable)
       : name_(name), module_(module), field_(field), kind_(kind), resizable_(resizable)
     {}
     AstImport(AstName name, AstName module, AstName field, AstGlobal global)
       : name_(name), module_(module), field_(field), kind_(DefinitionKind::Global), global_(global)
     {}
 
     AstName name() const { return name_; }
     AstName module() const { return module_; }
     AstName field() const { return field_; }
 
     DefinitionKind kind() const { return kind_; }
     AstRef& funcSig() {
         MOZ_ASSERT(kind_ == DefinitionKind::Function);
         return funcSig_;
     }
-    AstResizable resizable() const {
+    ResizableLimits resizable() const {
         MOZ_ASSERT(kind_ == DefinitionKind::Memory || kind_ == DefinitionKind::Table);
         return resizable_;
     }
     const AstGlobal& global() const {
         MOZ_ASSERT(kind_ == DefinitionKind::Global);
         return global_;
     }
 };
@@ -693,70 +729,73 @@ class AstStartFunc : public AstNode
 
 class AstModule : public AstNode
 {
   public:
     typedef AstVector<AstFunc*> FuncVector;
     typedef AstVector<AstImport*> ImportVector;
     typedef AstVector<AstExport*> ExportVector;
     typedef AstVector<AstSig*> SigVector;
+    typedef AstVector<AstName> NameVector;
 
   private:
     typedef AstHashMap<AstSig*, uint32_t, AstSig> SigMap;
 
     LifoAlloc&           lifo_;
     SigVector            sigs_;
     SigMap               sigMap_;
     ImportVector         imports_;
-    Maybe<AstResizable>  table_;
-    Maybe<AstResizable>  memory_;
+    NameVector           funcImportNames_;
+    Maybe<ResizableLimits> table_;
+    Maybe<ResizableLimits> memory_;
     ExportVector         exports_;
     Maybe<AstStartFunc>  startFunc_;
     FuncVector           funcs_;
     AstDataSegmentVector dataSegments_;
     AstElemSegmentVector elemSegments_;
     AstGlobalVector      globals_;
 
   public:
     explicit AstModule(LifoAlloc& lifo)
       : lifo_(lifo),
         sigs_(lifo),
         sigMap_(lifo),
         imports_(lifo),
+        funcImportNames_(lifo),
         exports_(lifo),
         funcs_(lifo),
         dataSegments_(lifo),
         elemSegments_(lifo),
         globals_(lifo)
     {}
     bool init() {
         return sigMap_.init();
     }
-    bool setMemory(AstResizable memory) {
+    bool setMemory(ResizableLimits memory) {
         if (memory_)
             return false;
         memory_.emplace(memory);
         return true;
     }
     bool hasMemory() const {
         return !!memory_;
     }
-    const AstResizable& memory() const {
+    const ResizableLimits& memory() const {
         return *memory_;
     }
-    bool setTable(AstResizable table) {
+    bool setTable(ResizableLimits table) {
         if (table_)
             return false;
         table_.emplace(table);
         return true;
     }
     bool hasTable() const {
         return !!table_;
     }
-    const AstResizable& table() const {
+    const ResizableLimits& table() const {
         return *table_;
     }
     bool append(AstDataSegment* seg) {
         return dataSegments_.append(seg);
     }
     const AstDataSegmentVector& dataSegments() const {
         return dataSegments_;
     }
@@ -802,21 +841,29 @@ class AstModule : public AstNode
     }
     bool append(AstFunc* func) {
         return funcs_.append(func);
     }
     const FuncVector& funcs() const {
         return funcs_;
     }
     bool append(AstImport* imp) {
+        if (imp->kind() == DefinitionKind::Function) {
+            if (!funcImportNames_.append(imp->name()))
+                return false;
+        }
+
         return imports_.append(imp);
     }
     const ImportVector& imports() const {
         return imports_;
     }
+    const NameVector& funcImportNames() const {
+        return funcImportNames_;
+    }
     bool append(AstExport* exp) {
         return exports_.append(exp);
     }
     const ExportVector& exports() const {
         return exports_;
     }
     bool append(AstGlobal* glob) {
         return globals_.append(glob);
@@ -828,49 +875,49 @@ class AstModule : public AstNode
 
 class AstNullaryOperator final : public AstExpr
 {
     Expr expr_;
 
   public:
     static const AstExprKind Kind = AstExprKind::NullaryOperator;
     explicit AstNullaryOperator(Expr expr)
-      : AstExpr(Kind),
+      : AstExpr(Kind, ExprType::Limit),
         expr_(expr)
     {}
 
     Expr expr() const { return expr_; }
 };
 
 class AstUnaryOperator final : public AstExpr
 {
     Expr expr_;
     AstExpr* op_;
 
   public:
     static const AstExprKind Kind = AstExprKind::UnaryOperator;
     explicit AstUnaryOperator(Expr expr, AstExpr* op)
-      : AstExpr(Kind),
+      : AstExpr(Kind, ExprType::Limit),
         expr_(expr), op_(op)
     {}
 
     Expr expr() const { return expr_; }
     AstExpr* op() const { return op_; }
 };
 
 class AstBinaryOperator final : public AstExpr
 {
     Expr expr_;
     AstExpr* lhs_;
     AstExpr* rhs_;
 
   public:
     static const AstExprKind Kind = AstExprKind::BinaryOperator;
     explicit AstBinaryOperator(Expr expr, AstExpr* lhs, AstExpr* rhs)
-      : AstExpr(Kind),
+      : AstExpr(Kind, ExprType::Limit),
         expr_(expr), lhs_(lhs), rhs_(rhs)
     {}
 
     Expr expr() const { return expr_; }
     AstExpr* lhs() const { return lhs_; }
     AstExpr* rhs() const { return rhs_; }
 };
 
@@ -879,17 +926,17 @@ class AstTernaryOperator : public AstExp
     Expr expr_;
     AstExpr* op0_;
     AstExpr* op1_;
     AstExpr* op2_;
 
   public:
     static const AstExprKind Kind = AstExprKind::TernaryOperator;
     AstTernaryOperator(Expr expr, AstExpr* op0, AstExpr* op1, AstExpr* op2)
-      : AstExpr(Kind),
+      : AstExpr(Kind, ExprType::Limit),
         expr_(expr), op0_(op0), op1_(op1), op2_(op2)
     {}
 
     Expr expr() const { return expr_; }
     AstExpr* op0() const { return op0_; }
     AstExpr* op1() const { return op1_; }
     AstExpr* op2() const { return op2_; }
 };
@@ -898,37 +945,67 @@ class AstComparisonOperator final : publ
 {
     Expr expr_;
     AstExpr* lhs_;
     AstExpr* rhs_;
 
   public:
     static const AstExprKind Kind = AstExprKind::ComparisonOperator;
     explicit AstComparisonOperator(Expr expr, AstExpr* lhs, AstExpr* rhs)
-      : AstExpr(Kind),
+      : AstExpr(Kind, ExprType::Limit),
         expr_(expr), lhs_(lhs), rhs_(rhs)
     {}
 
     Expr expr() const { return expr_; }
     AstExpr* lhs() const { return lhs_; }
     AstExpr* rhs() const { return rhs_; }
 };
 
 class AstConversionOperator final : public AstExpr
 {
     Expr expr_;
     AstExpr* op_;
 
   public:
     static const AstExprKind Kind = AstExprKind::ConversionOperator;
     explicit AstConversionOperator(Expr expr, AstExpr* op)
-      : AstExpr(Kind),
+      : AstExpr(Kind, ExprType::Limit),
         expr_(expr), op_(op)
     {}
 
     Expr expr() const { return expr_; }
     AstExpr* op() const { return op_; }
 };
 
+// This is an artificial AST node which can fill operand slots in an AST
+// constructed from parsing or decoding stack-machine code that doesn't have
+// an inherent AST structure.
+class AstPop final : public AstExpr
+{
+  public:
+    static const AstExprKind Kind = AstExprKind::Pop;
+    AstPop()
+      : AstExpr(Kind, ExprType::Void)
+    {}
+};
+
+// This is an artificial AST node which can be used to represent some forms
+// of stack-machine code in an AST form. It similar to Block, but returns the
+// value of its first operand, rather than the last.
+class AstFirst : public AstExpr
+{
+    AstExprVector exprs_;
+
+  public:
+    static const AstExprKind Kind = AstExprKind::First;
+    explicit AstFirst(AstExprVector&& exprs)
+      : AstExpr(Kind, ExprType::Limit),
+        exprs_(Move(exprs))
+    {}
+
+    AstExprVector& exprs() { return exprs_; }
+    const AstExprVector& exprs() const { return exprs_; }
+};
+
 } // end wasm namespace
 } // end js namespace
 
 #endif // namespace wasmast_h
--- a/js/src/asmjs/WasmBaselineCompile.cpp
+++ b/js/src/asmjs/WasmBaselineCompile.cpp
@@ -1320,23 +1320,16 @@ class BaseCompiler
         x.setSlot(Stk::LocalF64, slot);
     }
 
     void pushLocalF32(uint32_t slot) {
         Stk& x = push();
         x.setSlot(Stk::LocalF32, slot);
     }
 
-    // Push a void value.  Like constants this is never flushed to memory,
-    // it just helps maintain the invariants of the type system.
-
-    void pushVoid() {
-        push();
-    }
-
     // PRIVATE.  Call only from other popI32() variants.
     // v must be the stack top.
 
     void popI32(Stk& v, RegI32 r) {
         switch (v.kind()) {
           case Stk::ConstI32:
             loadConstI32(r.reg, v);
             break;
@@ -1600,20 +1593,42 @@ class BaseCompiler
           case Stk::None:
             stk_.popBack();
             return AnyReg();
           default:
             MOZ_CRASH("Compiler bug: unexpected value on stack");
         }
     }
 
+    MOZ_MUST_USE
+    AnyReg allocJoinReg(ExprType type) {
+        switch (type) {
+          case ExprType::I32:
+            allocGPR(joinRegI32.reg);
+            return AnyReg(joinRegI32);
+          case ExprType::I64:
+            allocInt64(joinRegI64.reg);
+            return AnyReg(joinRegI64);
+          case ExprType::F32:
+            allocFPU(joinRegF32.reg);
+            return AnyReg(joinRegF32);
+          case ExprType::F64:
+            allocFPU(joinRegF64.reg);
+            return AnyReg(joinRegF64);
+          case ExprType::Void:
+            MOZ_CRASH("Compiler bug: allocating void join reg");
+          default:
+            MOZ_CRASH("Compiler bug: unexpected type");
+        }
+    }
+
     void pushJoinReg(AnyReg r) {
         switch (r.tag) {
           case AnyReg::NONE:
-            pushVoid();
+            MOZ_CRASH("Compile bug: attempting to push void");
             break;
           case AnyReg::I32:
             pushI32(r.i32());
             break;
           case AnyReg::I64:
             pushI64(r.i64());
             break;
           case AnyReg::F64:
@@ -1623,16 +1638,17 @@ class BaseCompiler
             pushF32(r.f32());
             break;
         }
     }
 
     void freeJoinReg(AnyReg r) {
         switch (r.tag) {
           case AnyReg::NONE:
+            MOZ_CRASH("Compile bug: attempting to free void reg");
             break;
           case AnyReg::I32:
             freeI32(r.i32());
             break;
           case AnyReg::I64:
             freeI64(r.i64());
             break;
           case AnyReg::F64:
@@ -1732,37 +1748,35 @@ class BaseCompiler
 
     Vector<Control, 8, SystemAllocPolicy> ctl_;
 
     MOZ_MUST_USE
     bool pushControl(UniquePooledLabel* label, UniquePooledLabel* otherLabel = nullptr) {
         uint32_t framePushed = masm.framePushed();
         uint32_t stackSize = stk_.length();
 
-        // Always a void value at the beginning of a block, ensures
-        // stack is never empty even if the block has no expressions.
-        if (!deadCode_)
-            pushVoid();
-
         if (!ctl_.emplaceBack(Control(framePushed, stackSize)))
             return false;
         if (label)
             ctl_.back().label = label->release();
         if (otherLabel)
             ctl_.back().otherLabel = otherLabel->release();
         ctl_.back().deadOnArrival = deadCode_;
         return true;
     }
 
     void popControl() {
         Control last = ctl_.popCopy();
         if (last.label)
             freeLabel(last.label);
         if (last.otherLabel)
             freeLabel(last.otherLabel);
+
+        if (deadCode_ && !ctl_.empty())
+            popValueStackTo(ctl_.back().stackSize);
     }
 
     Control& controlItem(uint32_t relativeDepth) {
         return ctl_[ctl_.length() - 1 - relativeDepth];
     }
 
     MOZ_MUST_USE
     PooledLabel* newLabel() {
@@ -3142,50 +3156,54 @@ class BaseCompiler
     bool emitBrIf();
     MOZ_MUST_USE
     bool emitBrTable();
     MOZ_MUST_USE
     bool emitReturn();
     MOZ_MUST_USE
     bool emitCallArgs(const ValTypeVector& args, FunctionCall& baselineCall);
     MOZ_MUST_USE
-    bool skipCall(const ValTypeVector& args, ExprType maybeReturnType = ExprType::Limit);
-    MOZ_MUST_USE
     bool emitCallImportCommon(uint32_t lineOrBytecode, uint32_t funcImportIndex);
     MOZ_MUST_USE
     bool emitCall(uint32_t callOffset);
     MOZ_MUST_USE
     bool emitCallImport(uint32_t callOffset);
     MOZ_MUST_USE
-    bool emitCallIndirect(uint32_t callOffset);
+    bool emitCallIndirect(uint32_t callOffset, bool oldStyle);
     MOZ_MUST_USE
     bool emitUnaryMathBuiltinCall(uint32_t callOffset, SymbolicAddress callee, ValType operandType);
     MOZ_MUST_USE
     bool emitBinaryMathBuiltinCall(uint32_t callOffset, SymbolicAddress callee, ValType operandType);
     MOZ_MUST_USE
     bool emitGetLocal();
     MOZ_MUST_USE
     bool emitSetLocal();
+    bool emitTeeLocal();
+    MOZ_MUST_USE
     MOZ_MUST_USE
     bool emitGetGlobal();
     MOZ_MUST_USE
     bool emitSetGlobal();
     MOZ_MUST_USE
+    bool emitTeeGlobal();
+    MOZ_MUST_USE
     bool emitLoad(ValType type, Scalar::Type viewType);
     MOZ_MUST_USE
     bool emitStore(ValType resultType, Scalar::Type viewType);
     MOZ_MUST_USE
-    bool emitStoreWithCoercion(ValType resultType, Scalar::Type viewType);
+    bool emitTeeStore(ValType resultType, Scalar::Type viewType);
+    MOZ_MUST_USE
+    bool emitTeeStoreWithCoercion(ValType resultType, Scalar::Type viewType);
     MOZ_MUST_USE
     bool emitSelect();
 
-    void endBlock();
-    void endLoop();
+    void endBlock(ExprType type, bool isFunctionBody);
+    void endLoop(ExprType type);
     void endIfThen();
-    void endIfThenElse();
+    void endIfThenElse(ExprType type);
 
     void doReturn(ExprType returnType);
     void pushReturned(const FunctionCall& call, ExprType type);
 
     void emitCompareI32(JSOp compareOp, MCompare::CompareType compareType);
     void emitCompareI64(JSOp compareOp, MCompare::CompareType compareType);
     void emitCompareF32(JSOp compareOp, MCompare::CompareType compareType);
     void emitCompareF64(JSOp compareOp, MCompare::CompareType compareType);
@@ -4427,96 +4445,91 @@ BaseCompiler::emitBlock()
 
     if (!deadCode_)
         sync();                    // Simplifies branching out from block
 
     return pushControl(&blockEnd);
 }
 
 void
-BaseCompiler::endBlock()
+BaseCompiler::endBlock(ExprType type, bool isFunctionBody)
 {
     Control& block = controlItem(0);
 
     // Save the value.
     AnyReg r;
-    if (!deadCode_)
+    if (!deadCode_ && !IsVoid(type))
         r = popJoinReg();
 
     // Leave the block.
     popStackOnBlockExit(block.framePushed);
 
     // Bind after cleanup: branches out will have popped the stack.
     if (block.label->used()) {
         masm.bind(block.label);
+        if (deadCode_ && !IsVoid(type))
+            r = allocJoinReg(type);
         deadCode_ = false;
     }
 
-    popValueStackTo(block.stackSize);
-    popControl();
+    MOZ_ASSERT(stk_.length() == block.stackSize);
 
     // Retain the value stored in joinReg by all paths.
-    if (!deadCode_)
-        pushJoinReg(r);
+    if (!deadCode_) {
+        if (!IsVoid(type))
+            pushJoinReg(r);
+
+        if (isFunctionBody)
+            doReturn(func_.sig().ret());
+    }
+
+    popControl();
 }
 
 bool
 BaseCompiler::emitLoop()
 {
     if (!iter_.readLoop())
         return false;
 
-    UniquePooledLabel blockEnd(newLabel());
-    if (!blockEnd)
-        return false;
-
     UniquePooledLabel blockCont(newLabel());
     if (!blockCont)
         return false;
 
     if (!deadCode_)
         sync();                    // Simplifies branching out from block
 
-    if (!pushControl(&blockEnd))
-        return false;
-
     if (!pushControl(&blockCont))
         return false;
 
     if (!deadCode_) {
         masm.bind(controlItem(0).label);
         addInterruptCheck();
     }
 
     return true;
 }
 
 void
-BaseCompiler::endLoop()
+BaseCompiler::endLoop(ExprType type)
 {
-    Control& block = controlItem(1);
+    Control& block = controlItem(0);
 
     AnyReg r;
-    if (!deadCode_)
+    if (!deadCode_ && !IsVoid(type))
         r = popJoinReg();
 
     popStackOnBlockExit(block.framePushed);
 
-    // Bind after cleanup: branches out will have popped the stack.
-    if (block.label->used()) {
-        masm.bind(block.label);
-        deadCode_ = false;
-    }
-
-    popValueStackTo(block.stackSize);
-    popControl();
+    MOZ_ASSERT(stk_.length() == block.stackSize);
+
     popControl();
 
     // Retain the value stored in joinReg by all paths.
-    if (!deadCode_)
+    if (!deadCode_ && !IsVoid(type))
         pushJoinReg(r);
 }
 
 // The bodies of the "then" and "else" arms can be arbitrary sequences
 // of expressions, they push control and increment the nesting and can
 // even be targeted by jumps.  A branch to the "if" block branches to
 // the exit of the if, ie, it's like "break".  Consider:
 //
@@ -4571,22 +4584,19 @@ BaseCompiler::endIfThen()
     if (ifThen.otherLabel->used())
         masm.bind(ifThen.otherLabel);
 
     if (ifThen.label->used())
         masm.bind(ifThen.label);
 
     deadCode_ = ifThen.deadOnArrival;
 
-    popValueStackTo(ifThen.stackSize);
+    MOZ_ASSERT(stk_.length() == ifThen.stackSize);
+
     popControl();
-
-    // No value to preserve.
-    if (!deadCode_)
-        pushVoid();
 }
 
 bool
 BaseCompiler::emitElse()
 {
     ExprType thenType;
     Nothing unused_thenValue;
     if (!iter_.readElse(&thenType, &unused_thenValue))
@@ -4596,90 +4606,89 @@ BaseCompiler::emitElse()
 
     // See comment in endIfThenElse, below.
 
     // Exit the "then" branch.
 
     ifThenElse.deadThenBranch = deadCode_;
 
     AnyReg r;
-    if (!deadCode_)
+    if (!deadCode_ && !IsVoid(thenType))
         r = popJoinReg();
 
     popStackOnBlockExit(ifThenElse.framePushed);
 
     if (!deadCode_)
         masm.jump(ifThenElse.label);
 
     if (ifThenElse.otherLabel->used())
         masm.bind(ifThenElse.otherLabel);
 
     // Reset to the "else" branch.
 
-    popValueStackTo(ifThenElse.stackSize);
-
-    if (!deadCode_)
+    MOZ_ASSERT(stk_.length() == ifThenElse.stackSize);
+
+    if (!deadCode_ && !IsVoid(thenType))
         freeJoinReg(r);
 
     deadCode_ = ifThenElse.deadOnArrival;
 
-    // The following pushVoid() duplicates the pushVoid() in
-    // pushControl() that sets up a value in the "then" block: a block
-    // never leaves the stack empty, and both the "then" and "else"
-    // arms are implicit blocks.
-    if (!deadCode_)
-        pushVoid();
-
     return true;
 }
 
 void
-BaseCompiler::endIfThenElse()
+BaseCompiler::endIfThenElse(ExprType type)
 {
     Control& ifThenElse = controlItem(0);
 
     // The expression type is not a reliable guide to what we'll find
     // on the stack, we could have (if E (i32.const 1) (unreachable))
     // in which case the "else" arm is AnyType but the type of the
     // full expression is I32.  So restore whatever's there, not what
     // we want to find there.  The "then" arm has the same constraint.
 
     AnyReg r;
-    if (!deadCode_)
+    if (!deadCode_ && !IsVoid(type))
         r = popJoinReg();
 
     popStackOnBlockExit(ifThenElse.framePushed);
 
     if (ifThenElse.label->used())
         masm.bind(ifThenElse.label);
 
-    deadCode_ = ifThenElse.deadOnArrival ||
-                (ifThenElse.deadThenBranch && deadCode_ && !ifThenElse.label->bound());
-
-    popValueStackTo(ifThenElse.stackSize);
+    if (!ifThenElse.deadOnArrival &&
+        (!ifThenElse.deadThenBranch || !deadCode_ || ifThenElse.label->bound())) {
+        if (deadCode_ && !IsVoid(type))
+            r = allocJoinReg(type);
+        deadCode_ = false;
+    }
+
+    MOZ_ASSERT(stk_.length() == ifThenElse.stackSize);
+
     popControl();
 
-    if (!deadCode_)
+    if (!deadCode_ && !IsVoid(type))
         pushJoinReg(r);
 }
 
 bool
 BaseCompiler::emitEnd()
 {
     LabelKind kind;
     ExprType type;
     Nothing unused_value;
     if (!iter_.readEnd(&kind, &type, &unused_value))
         return false;
 
     switch (kind) {
-      case LabelKind::Block: endBlock(); break;
-      case LabelKind::Loop:  endLoop(); break;
+      case LabelKind::Block: endBlock(type, iter_.controlStackEmpty()); break;
+      case LabelKind::Loop:  endLoop(type); break;
+      case LabelKind::UnreachableThen:
       case LabelKind::Then:  endIfThen(); break;
-      case LabelKind::Else:  endIfThenElse(); break;
+      case LabelKind::Else:  endIfThenElse(type); break;
     }
 
     return true;
 }
 
 bool
 BaseCompiler::emitBr()
 {
@@ -4689,37 +4698,36 @@ BaseCompiler::emitBr()
     if (!iter_.readBr(&relativeDepth, &type, &unused_value))
         return false;
 
     if (deadCode_)
         return true;
 
     Control& target = controlItem(relativeDepth);
 
-    // If there is no value then generate one for popJoinReg() to
-    // consume.
-
-    if (IsVoid(type))
-        pushVoid();
-
     // Save any value in the designated join register, where the
     // normal block exit code will also leave it.
 
-    AnyReg r = popJoinReg();
+    AnyReg r;
+    if (!IsVoid(type))
+        r = popJoinReg();
 
     popStackBeforeBranch(target.framePushed);
     masm.jump(target.label);
 
     // The register holding the join value is free for the remainder
     // of this block.
 
-    freeJoinReg(r);
+    if (!IsVoid(type))
+        freeJoinReg(r);
 
     deadCode_ = true;
 
+    popValueStackTo(ctl_.back().stackSize);
+
     return true;
 }
 
 bool
 BaseCompiler::emitBrIf()
 {
     uint32_t relativeDepth;
     ExprType type;
@@ -4727,63 +4735,45 @@ BaseCompiler::emitBrIf()
     if (!iter_.readBrIf(&relativeDepth, &type, &unused_value, &unused_condition))
         return false;
 
     if (deadCode_)
         return true;
 
     Control& target = controlItem(relativeDepth);
 
-    Label notTaken;
-
-    // Conditional branches are a little awkward.  If the branch is
-    // taken we must pop the execution stack along that edge, which
-    // means that the branch instruction becomes inverted to jump
-    // around a cleanup + unconditional branch pair.
-    //
-    // TODO / OPTIMIZE: We can generate better code if no cleanup code
-    // need be executed along the taken edge.
-    //
     // TODO / OPTIMIZE: Optimize boolean evaluation for control by
     // allowing a conditional expression to be left on the stack and
     // reified here as part of the branch instruction.
 
     // We'll need the joinreg later, so don't use it for rc.
     // We assume joinRegI32 and joinRegI64 overlap.
     if (type == ExprType::I32 || type == ExprType::I64)
         needI32(joinRegI32);
 
     // Condition value is on top, always I32.
     RegI32 rc = popI32();
 
-    // There may or may not be a value underneath, to be carried along
-    // the taken edge.
-    if (IsVoid(type))
-        pushVoid();
-
     if (type == ExprType::I32 || type == ExprType::I64)
         freeI32(joinRegI32);
 
     // Save any value in the designated join register, where the
     // normal block exit code will also leave it.
-    AnyReg r = popJoinReg();
-
-    masm.branch32(Assembler::Equal, rc.reg, Imm32(0), &notTaken);
-
-    popStackBeforeBranch(target.framePushed);
-    masm.jump(target.label);
-
-    masm.bind(&notTaken);
-
-    // These registers are free in the remainder of the block.
+    AnyReg r;
+    if (!IsVoid(type))
+        r = popJoinReg();
+
+    masm.branch32(Assembler::NotEqual, rc.reg, Imm32(0), target.label);
+
+    // This register is free in the remainder of the block.
     freeI32(rc);
-    freeJoinReg(r);
-
-    // The non-taken edge currently carries a void value.
-    pushVoid();
+
+    // br_if returns its value(s).
+    if (!IsVoid(type))
+        pushJoinReg(r);
 
     return true;
 }
 
 bool
 BaseCompiler::emitBrTable()
 {
     uint32_t tableLength;
@@ -4797,45 +4787,42 @@ BaseCompiler::emitBrTable()
         return false;
 
     Uint32Vector depths;
     if (!depths.reserve(tableLength))
         return false;
 
     for (size_t i = 0; i < tableLength; ++i) {
         uint32_t depth;
-        if (!iter_.readBrTableEntry(type, &depth))
+        if (!iter_.readBrTableEntry(&type, &unused_value, &depth))
             return false;
         depths.infallibleAppend(depth);
     }
 
     uint32_t defaultDepth;
-    if (!iter_.readBrTableEntry(type, &defaultDepth))
+    if (!iter_.readBrTableDefault(&type, &unused_value, &defaultDepth))
         return false;
 
     if (deadCode_)
         return true;
 
     // We'll need the joinreg later, so don't use it for rc.
     // We assume joinRegI32 and joinRegI64 overlap.
     if (type == ExprType::I32 || type == ExprType::I64)
         needI32(joinRegI32);
 
     // Table switch value always on top.
     RegI32 rc = popI32();
 
-    // There may or may not be a value underneath, to be carried along
-    // the taken edge.
-    if (IsVoid(type))
-        pushVoid();
-
     if (type == ExprType::I32 || type == ExprType::I64)
         freeI32(joinRegI32);
 
-    AnyReg r = popJoinReg();
+    AnyReg r;
+    if (!IsVoid(type))
+        r = popJoinReg();
 
     Label dispatchCode;
     masm.branch32(Assembler::Below, rc.reg, Imm32(tableLength), &dispatchCode);
 
     // This is the out-of-range stub.  rc is dead here but we don't need it.
 
     popStackBeforeBranch(controlItem(defaultDepth).framePushed);
     masm.jump(controlItem(defaultDepth).label);
@@ -4866,21 +4853,24 @@ BaseCompiler::emitBrTable()
     masm.bind(&dispatchCode);
     tableSwitch(&theTable, rc);
 
     deadCode_ = true;
 
     // Clean up.
 
     freeI32(rc);
-    freeJoinReg(r);
+    if (!IsVoid(type))
+        freeJoinReg(r);
 
     for (uint32_t i = 0; i < tableLength; i++)
         freeLabel(stubs[i]);
 
+    popValueStackTo(ctl_.back().stackSize);
+
     return true;
 }
 
 void
 BaseCompiler::doReturn(ExprType type)
 {
     switch (type) {
       case ExprType::Void: {
@@ -4925,16 +4915,18 @@ BaseCompiler::emitReturn()
         return false;
 
     if (deadCode_)
         return true;
 
     doReturn(func_.sig().ret());
     deadCode_ = true;
 
+    popValueStackTo(ctl_.back().stackSize);
+
     return true;
 }
 
 bool
 BaseCompiler::emitCallArgs(const ValTypeVector& args, FunctionCall& baselineCall)
 {
     MOZ_ASSERT(!deadCode_);
 
@@ -4956,48 +4948,23 @@ BaseCompiler::emitCallArgs(const ValType
     loadFromFramePtr(WasmTlsReg, frameOffsetFromSlot(tlsSlot_, MIRType::Pointer));
 
     if (!iter_.readCallArgsEnd(numArgs))
         return false;
 
     return true;
 }
 
-bool
-BaseCompiler::skipCall(const ValTypeVector& args, ExprType maybeReturnType)
-{
-    MOZ_ASSERT(deadCode_);
-
-    uint32_t numArgs = args.length();
-    for (size_t i = 0; i < numArgs; ++i) {
-        ValType argType = args[i];
-        Nothing arg_;
-        if (!iter_.readCallArg(argType, numArgs, i, &arg_))
-            return false;
-    }
-
-    if (!iter_.readCallArgsEnd(numArgs))
-        return false;
-
-    if (maybeReturnType != ExprType::Limit) {
-        if (!iter_.readCallReturn(maybeReturnType))
-            return false;
-    }
-
-    return true;
-}
-
 void
 BaseCompiler::pushReturned(const FunctionCall& call, ExprType type)
 {
     switch (type) {
-      case ExprType::Void: {
-        pushVoid();
+      case ExprType::Void:
+        MOZ_CRASH("Compiler bug: attempt to push void return");
         break;
-      }
       case ExprType::I32: {
         RegI32 rv = needI32();
         captureReturnedI32(rv);
         pushI32(rv);
         break;
       }
       case ExprType::I64: {
         RegI64 rv = needI64();
@@ -5038,17 +5005,17 @@ BaseCompiler::pushReturned(const Functio
 
 bool
 BaseCompiler::emitCallImportCommon(uint32_t lineOrBytecode, uint32_t funcImportIndex)
 {
     const FuncImportGenDesc& funcImport = mg_.funcImports[funcImportIndex];
     const Sig& sig = *funcImport.sig;
 
     if (deadCode_)
-        return skipCall(sig.args(), sig.ret());
+        return true;
 
     sync();
 
     uint32_t numArgs = sig.args().length();
     size_t stackSpace = stackConsumed(numArgs);
 
     FunctionCall baselineCall(lineOrBytecode);
     beginCall(baselineCall, EscapesSandbox(true), IsBuiltinCall(false));
@@ -5064,45 +5031,44 @@ BaseCompiler::emitCallImportCommon(uint3
     endCall(baselineCall);
 
     // TODO / OPTIMIZE: It would be better to merge this freeStack()
     // into the one in endCall, if we can.
 
     popValueStackBy(numArgs);
     masm.freeStack(stackSpace);
 
-    pushReturned(baselineCall, sig.ret());
+    if (!IsVoid(sig.ret()))
+        pushReturned(baselineCall, sig.ret());
 
     return true;
 }
 
 bool
 BaseCompiler::emitCall(uint32_t callOffset)
 {
     uint32_t lineOrBytecode = readCallSiteLineOrBytecode(callOffset);
 
     uint32_t calleeIndex;
-    uint32_t arity;
-    if (!iter_.readCall(&calleeIndex, &arity))
+    if (!iter_.readCall(&calleeIndex))
         return false;
 
-    // For asm.js and old-format wasm code, imports are not part of the function
-    // index space so in these cases firstFuncDefIndex is fixed to 0, even if
-    // there are function imports.
+    // For asm.js, imports are not part of the function index space so in
+    // these cases firstFuncDefIndex is fixed to 0, even if there are
+    // function imports.
     if (calleeIndex < mg_.firstFuncDefIndex)
         return emitCallImportCommon(lineOrBytecode, calleeIndex);
 
+    if (deadCode_)
+        return true;
+
+    sync();
+
     uint32_t funcDefIndex = calleeIndex - mg_.firstFuncDefIndex;
     const Sig& sig = *mg_.funcDefSigs[funcDefIndex];
-
-    if (deadCode_)
-        return skipCall(sig.args(), sig.ret());
-
-    sync();
-
     uint32_t numArgs = sig.args().length();
     size_t stackSpace = stackConsumed(numArgs);
 
     FunctionCall baselineCall(lineOrBytecode);
     beginCall(baselineCall, EscapesSandbox(false), IsBuiltinCall(false));
 
     if (!emitCallArgs(sig.args(), baselineCall))
         return false;
@@ -5115,105 +5081,114 @@ BaseCompiler::emitCall(uint32_t callOffs
     endCall(baselineCall);
 
     // TODO / OPTIMIZE: It would be better to merge this freeStack()
     // into the one in endCall, if we can.
 
     popValueStackBy(numArgs);
     masm.freeStack(stackSpace);
 
-    pushReturned(baselineCall, sig.ret());
+    if (!IsVoid(sig.ret()))
+        pushReturned(baselineCall, sig.ret());
 
     return true;
 }
 
 bool
 BaseCompiler::emitCallImport(uint32_t callOffset)
 {
     MOZ_ASSERT(!mg_.firstFuncDefIndex);
 
     uint32_t lineOrBytecode = readCallSiteLineOrBytecode(callOffset);
 
     uint32_t funcImportIndex;
-    uint32_t arity;
-    if (!iter_.readCallImport(&funcImportIndex, &arity))
+    if (!iter_.readCallImport(&funcImportIndex))
         return false;
 
     return emitCallImportCommon(lineOrBytecode, funcImportIndex);
 }
 
 bool
-BaseCompiler::emitCallIndirect(uint32_t callOffset)
+BaseCompiler::emitCallIndirect(uint32_t callOffset, bool oldStyle)
 {
     uint32_t lineOrBytecode = readCallSiteLineOrBytecode(callOffset);
 
     uint32_t sigIndex;
-    uint32_t arity;
-    if (!iter_.readCallIndirect(&sigIndex, &arity))
-        return false;
-
     Nothing callee_;
+    if (oldStyle) {
+        if (!iter_.readOldCallIndirect(&sigIndex))
+            return false;
+    } else {
+        if (!iter_.readCallIndirect(&sigIndex, &callee_))
+            return false;
+    }
+
+    if (deadCode_)
+        return true;
+
+    sync();
 
     const SigWithId& sig = mg_.sigs[sigIndex];
 
-    if (deadCode_) {
-        return skipCall(sig.args()) && iter_.readCallIndirectCallee(&callee_) &&
-               iter_.readCallReturn(sig.ret());
-    }
-
-    sync();
-
-    // Stack: ... index arg1 .. argn
+    // new style: Stack: ... arg1 .. argn index
+    // old style: Stack: ... index arg1 .. argn
+
+    Stk callee;
+    if (!oldStyle)
+        callee = stk_.popCopy();
 
     uint32_t numArgs = sig.args().length();
-    size_t stackSpace = stackConsumed(numArgs+1);
+    size_t stackSpace;
+    if (oldStyle)
+        stackSpace = stackConsumed(numArgs+1);
+    else
+        stackSpace = stackConsumed(numArgs);
 
     FunctionCall baselineCall(lineOrBytecode);
     beginCall(baselineCall, EscapesSandbox(false), IsBuiltinCall(false));
 
     if (!emitCallArgs(sig.args(), baselineCall))
         return false;
 
-    if (!iter_.readCallIndirectCallee(&callee_))
-        return false;
+    if (oldStyle) {
+        if (!iter_.readOldCallIndirectCallee(&callee_))
+            return false;
+
+        callee = peek(numArgs);
+    }
 
     if (!iter_.readCallReturn(sig.ret()))
         return false;
 
-    Stk& callee = peek(numArgs);
-
     callIndirect(sigIndex, callee, baselineCall);
 
     endCall(baselineCall);
 
     // TODO / OPTIMIZE: It would be better to merge this freeStack()
     // into the one in endCall, if we can.
 
-    popValueStackBy(numArgs+1);
+    if (oldStyle)
+        popValueStackBy(numArgs+1);
+    else
+        popValueStackBy(numArgs);
     masm.freeStack(stackSpace);
 
-    pushReturned(baselineCall, sig.ret());
+    if (!IsVoid(sig.ret()))
+        pushReturned(baselineCall, sig.ret());
 
     return true;
 }
 
+
 bool
 BaseCompiler::emitUnaryMathBuiltinCall(uint32_t callOffset, SymbolicAddress callee,
                                        ValType operandType)
 {
-    if (deadCode_) {
-        switch (operandType) {
-          case ValType::F64:
-            return skipCall(SigD_, ExprType::F64);
-          case ValType::F32:
-            return skipCall(SigF_, ExprType::F32);
-          default:
-            MOZ_CRASH("Compiler bug: not a float type");
-        }
-    }
+    if (deadCode_)
+        return true;
 
     uint32_t lineOrBytecode = readCallSiteLineOrBytecode(callOffset);
 
     sync();
 
     uint32_t numArgs = 1;
     size_t stackSpace = stackConsumed(numArgs);
 
@@ -5256,17 +5231,17 @@ BaseCompiler::emitUnaryMathBuiltinCall(u
 
 bool
 BaseCompiler::emitBinaryMathBuiltinCall(uint32_t callOffset, SymbolicAddress callee,
                                         ValType operandType)
 {
     MOZ_ASSERT(operandType == ValType::F64);
 
     if (deadCode_)
-        return skipCall(SigDD_, ExprType::F64);
+        return true;
 
     uint32_t lineOrBytecode = 0;
     if (callee == SymbolicAddress::ModD) {
         // Not actually a call in the binary representation
     } else {
         readCallSiteLineOrBytecode(callOffset);
     }
 
@@ -5345,16 +5320,63 @@ BaseCompiler::emitSetLocal()
     if (deadCode_)
         return true;
 
     switch (locals_[slot]) {
       case ValType::I32: {
         RegI32 rv = popI32();
         syncLocal(slot);
         storeToFrameI32(rv.reg, frameOffsetFromSlot(slot, MIRType::Int32));
+        freeI32(rv);
+        break;
+      }
+      case ValType::I64: {
+        RegI64 rv = popI64();
+        syncLocal(slot);
+        storeToFrameI64(rv.reg, frameOffsetFromSlot(slot, MIRType::Int64));
+        freeI64(rv);
+        break;
+      }
+      case ValType::F64: {
+        RegF64 rv = popF64();
+        syncLocal(slot);
+        storeToFrameF64(rv.reg, frameOffsetFromSlot(slot, MIRType::Double));
+        freeF64(rv);
+        break;
+      }
+      case ValType::F32: {
+        RegF32 rv = popF32();
+        syncLocal(slot);
+        storeToFrameF32(rv.reg, frameOffsetFromSlot(slot, MIRType::Float32));
+        freeF32(rv);
+        break;
+      }
+      default:
+        MOZ_CRASH("Local variable type");
+    }
+
+    return true;
+}
+
+bool
+BaseCompiler::emitTeeLocal()
+{
+    uint32_t slot;
+    Nothing unused_value;
+    if (!iter_.readTeeLocal(locals_, &slot, &unused_value))
+        return false;
+
+    if (deadCode_)
+        return true;
+
+    switch (locals_[slot]) {
+      case ValType::I32: {
+        RegI32 rv = popI32();
+        syncLocal(slot);
+        storeToFrameI32(rv.reg, frameOffsetFromSlot(slot, MIRType::Int32));
         pushI32(rv);
         break;
       }
       case ValType::I64: {
         RegI64 rv = popI64();
         syncLocal(slot);
         storeToFrameI64(rv.reg, frameOffsetFromSlot(slot, MIRType::Int64));
         pushI64(rv);
@@ -5458,16 +5480,57 @@ BaseCompiler::emitSetGlobal()
         return true;
 
     const GlobalDesc& global = mg_.globals[id];
 
     switch (global.type()) {
       case ValType::I32: {
         RegI32 rv = popI32();
         storeGlobalVarI32(global.offset(), rv);
+        break;
+      }
+      case ValType::I64: {
+        RegI64 rv = popI64();
+        storeGlobalVarI64(global.offset(), rv);
+        break;
+      }
+      case ValType::F32: {
+        RegF32 rv = popF32();
+        storeGlobalVarF32(global.offset(), rv);
+        break;
+      }
+      case ValType::F64: {
+        RegF64 rv = popF64();
+        storeGlobalVarF64(global.offset(), rv);
+        break;
+      }
+      default:
+        MOZ_CRASH("Global variable type");
+        break;
+    }
+    return true;
+}
+
+bool
+BaseCompiler::emitTeeGlobal()
+{
+    uint32_t id;
+    Nothing unused_value;
+    if (!iter_.readTeeGlobal(mg_.globals, &id, &unused_value))
+        return false;
+
+    if (deadCode_)
+        return true;
+
+    const GlobalDesc& global = mg_.globals[id];
+
+    switch (global.type()) {
+      case ValType::I32: {
+        RegI32 rv = popI32();
+        storeGlobalVarI32(global.offset(), rv);
         pushI32(rv);
         break;
       }
       case ValType::I64: {
         RegI64 rv = popI64();
         storeGlobalVarI64(global.offset(), rv);
         pushI64(rv);
         break;
@@ -5566,16 +5629,76 @@ BaseCompiler::emitStore(ValType resultTy
 
     switch (resultType) {
       case ValType::I32: {
         RegI32 rp, rv;
         pop2xI32(&rp, &rv);
         if (!store(access, rp, AnyReg(rv)))
             return false;
         freeI32(rp);
+        freeI32(rv);
+        break;
+      }
+      case ValType::I64: {
+        RegI64 rv = popI64();
+        RegI32 rp = popI32();
+        if (!store(access, rp, AnyReg(rv)))
+            return false;
+        freeI32(rp);
+        freeI64(rv);
+        break;
+      }
+      case ValType::F32: {
+        RegF32 rv = popF32();
+        RegI32 rp = popI32();
+        if (!store(access, rp, AnyReg(rv)))
+            return false;
+        freeI32(rp);
+        freeF32(rv);
+        break;
+      }
+      case ValType::F64: {
+        RegF64 rv = popF64();
+        RegI32 rp = popI32();
+        if (!store(access, rp, AnyReg(rv)))
+            return false;
+        freeI32(rp);
+        freeF64(rv);
+        break;
+      }
+      default:
+        MOZ_CRASH("store type");
+        break;
+    }
+    return true;
+}
+
+bool
+BaseCompiler::emitTeeStore(ValType resultType, Scalar::Type viewType)
+{
+    LinearMemoryAddress<Nothing> addr;
+    Nothing unused_value;
+    if (!iter_.readTeeStore(resultType, Scalar::byteSize(viewType), &addr, &unused_value))
+        return false;
+
+    if (deadCode_)
+        return true;
+
+    // TODO / OPTIMIZE: Disable bounds checking on constant accesses
+    // below the minimum heap length.
+
+    MWasmMemoryAccess access(viewType, addr.align, addr.offset);
+
+    switch (resultType) {
+      case ValType::I32: {
+        RegI32 rp, rv;
+        pop2xI32(&rp, &rv);
+        if (!store(access, rp, AnyReg(rv)))
+            return false;
+        freeI32(rp);
         pushI32(rv);
         break;
       }
       case ValType::I64: {
         RegI64 rv = popI64();
         RegI32 rp = popI32();
         if (!store(access, rp, AnyReg(rv)))
             return false;
@@ -5606,70 +5729,64 @@ BaseCompiler::emitStore(ValType resultTy
         break;
     }
     return true;
 }
 
 bool
 BaseCompiler::emitSelect()
 {
-    ExprType type;
+    ValType type;
     Nothing unused_trueValue;
     Nothing unused_falseValue;
     Nothing unused_condition;
     if (!iter_.readSelect(&type, &unused_trueValue, &unused_falseValue, &unused_condition))
         return false;
 
     if (deadCode_)
         return true;
 
     // I32 condition on top, then false, then true.
 
     RegI32 rc = popI32();
     switch (type) {
-      case AnyType:
-      case ExprType::Void: {
-        popValueStackBy(2);
-        pushVoid();
-        break;
-      }
-      case ExprType::I32: {
+      case ValType::I32: {
         Label done;
         RegI32 r0, r1;
         pop2xI32(&r0, &r1);
         masm.branch32(Assembler::NotEqual, rc.reg, Imm32(0), &done);
         moveI32(r1, r0);
         masm.bind(&done);
         freeI32(r1);
         pushI32(r0);
         break;
       }
-      case ExprType::I64: {
+      case ValType::I64: {
         Label done;
         RegI64 r0, r1;
         pop2xI64(&r0, &r1);
         masm.branch32(Assembler::NotEqual, rc.reg, Imm32(0), &done);
         moveI64(r1, r0);
         masm.bind(&done);
         freeI64(r1);
         pushI64(r0);
         break;
       }
-      case ExprType::F32: {
+      case ValType::F32: {
         Label done;
         RegF32 r0, r1;
         pop2xF32(&r0, &r1);
         masm.branch32(Assembler::NotEqual, rc.reg, Imm32(0), &done);
         moveF32(r1, r0);
         masm.bind(&done);
         freeF32(r1);
         pushF32(r0);
         break;
       }
-      case ExprType::F64: {
+      case ValType::F64: {
         Label done;
         RegF64 r0, r1;
         pop2xF64(&r0, &r1);
         masm.branch32(Assembler::NotEqual, rc.reg, Imm32(0), &done);
         moveF64(r1, r0);
         masm.bind(&done);
         freeF64(r1);
         pushF64(r0);
@@ -5832,21 +5949,21 @@ BaseCompiler::emitCompareF64(JSOp compar
     masm.mov(ImmWord(0), i0.reg);
     masm.bind(&across);
     freeF64(r0);
     freeF64(r1);
     pushI32(i0);
 }
 
 bool
-BaseCompiler::emitStoreWithCoercion(ValType resultType, Scalar::Type viewType)
+BaseCompiler::emitTeeStoreWithCoercion(ValType resultType, Scalar::Type viewType)
 {
     LinearMemoryAddress<Nothing> addr;
     Nothing unused_value;
-    if (!iter_.readStore(resultType, Scalar::byteSize(viewType), &addr, &unused_value))
+    if (!iter_.readTeeStore(resultType, Scalar::byteSize(viewType), &addr, &unused_value))
         return false;
 
     if (deadCode_)
         return true;
 
     // TODO / OPTIMIZE: Disable bounds checking on constant accesses
     // below the minimum heap length.
 
@@ -5879,17 +5996,17 @@ BaseCompiler::emitStoreWithCoercion(ValT
 
     return true;
 }
 
 bool
 BaseCompiler::emitGrowMemory(uint32_t callOffset)
 {
     if (deadCode_)
-        return skipCall(SigI_, ExprType::I32);
+        return true;
 
     uint32_t lineOrBytecode = readCallSiteLineOrBytecode(callOffset);
 
     sync();
 
     uint32_t numArgs = 1;
     size_t stackSpace = stackConsumed(numArgs);
 
@@ -5915,17 +6032,17 @@ BaseCompiler::emitGrowMemory(uint32_t ca
 
     return true;
 }
 
 bool
 BaseCompiler::emitCurrentMemory(uint32_t callOffset)
 {
     if (deadCode_)
-        return skipCall(Sig_, ExprType::I32);
+        return true;
 
     uint32_t lineOrBytecode = readCallSiteLineOrBytecode(callOffset);
 
     sync();
 
     FunctionCall baselineCall(lineOrBytecode);
     beginCall(baselineCall, EscapesSandbox(true), IsBuiltinCall(true));
 
@@ -6000,19 +6117,22 @@ BaseCompiler::emitBody()
         uint32_t exprOffset = iter_.currentOffset();
 
         Expr expr;
         CHECK(iter_.readExpr(&expr));
 
         switch (expr) {
           // Control opcodes
           case Expr::Nop:
-            CHECK(iter_.readNullary(ExprType::Void));
+            CHECK(iter_.readNop());
+            NEXT();
+          case Expr::Drop:
+            CHECK(iter_.readDrop());
             if (!deadCode_)
-                pushVoid();
+                popValueStackBy(1);
             NEXT();
           case Expr::Block:
             CHECK_NEXT(emitBlock());
           case Expr::Loop:
             CHECK_NEXT(emitLoop());
           case Expr::If:
             CHECK_NEXT(emitIf());
           case Expr::Else:
@@ -6027,36 +6147,43 @@ BaseCompiler::emitBody()
             CHECK_NEXT(emitBrTable());
           case Expr::Return:
             CHECK_NEXT(emitReturn());
           case Expr::Unreachable:
             CHECK(iter_.readUnreachable());
             if (!deadCode_) {
                 unreachableTrap();
                 deadCode_ = true;
+                popValueStackTo(ctl_.back().stackSize);
             }
             NEXT();
 
           // Calls
           case Expr::Call:
             CHECK_NEXT(emitCall(exprOffset));
           case Expr::CallIndirect:
-            CHECK_NEXT(emitCallIndirect(exprOffset));
+            CHECK_NEXT(emitCallIndirect(exprOffset, /* oldStyle = */ false));
+          case Expr::OldCallIndirect:
+            CHECK_NEXT(emitCallIndirect(exprOffset, /* oldStyle = */ true));
           case Expr::CallImport:
             CHECK_NEXT(emitCallImport(exprOffset));
 
           // Locals and globals
           case Expr::GetLocal:
             CHECK_NEXT(emitGetLocal());
           case Expr::SetLocal:
             CHECK_NEXT(emitSetLocal());
+          case Expr::TeeLocal:
+            CHECK_NEXT(emitTeeLocal());
           case Expr::GetGlobal:
             CHECK_NEXT(emitGetGlobal());
           case Expr::SetGlobal:
             CHECK_NEXT(emitSetGlobal());
+          case Expr::TeeGlobal:
+            CHECK_NEXT(emitTeeGlobal());
 
           // Select
           case Expr::Select:
             CHECK_NEXT(emitSelect());
 
           // I32
           case Expr::I32Const: {
             int32_t i32;
@@ -6128,20 +6255,26 @@ BaseCompiler::emitBody()
           case Expr::I32Load16S:
             CHECK_NEXT(emitLoad(ValType::I32, Scalar::Int16));
           case Expr::I32Load16U:
             CHECK_NEXT(emitLoad(ValType::I32, Scalar::Uint16));
           case Expr::I32Load:
             CHECK_NEXT(emitLoad(ValType::I32, Scalar::Int32));
           case Expr::I32Store8:
             CHECK_NEXT(emitStore(ValType::I32, Scalar::Int8));
+          case Expr::I32TeeStore8:
+            CHECK_NEXT(emitTeeStore(ValType::I32, Scalar::Int8));
           case Expr::I32Store16:
             CHECK_NEXT(emitStore(ValType::I32, Scalar::Int16));
+          case Expr::I32TeeStore16:
+            CHECK_NEXT(emitTeeStore(ValType::I32, Scalar::Int16));
           case Expr::I32Store:
             CHECK_NEXT(emitStore(ValType::I32, Scalar::Int32));
+          case Expr::I32TeeStore:
+            CHECK_NEXT(emitTeeStore(ValType::I32, Scalar::Int32));
           case Expr::I32Rotr:
             CHECK_NEXT(emitBinary(emitRotrI32, ValType::I32));
           case Expr::I32Rotl:
             CHECK_NEXT(emitBinary(emitRotlI32, ValType::I32));
 
           // I64
           case Expr::I64Const: {
             int64_t i64;
@@ -6213,22 +6346,30 @@ BaseCompiler::emitBody()
           case Expr::I64Load16U:
             CHECK_NEXT(emitLoad(ValType::I64, Scalar::Uint16));
           case Expr::I64Load32U:
             CHECK_NEXT(emitLoad(ValType::I64, Scalar::Uint32));
           case Expr::I64Load:
             CHECK_NEXT(emitLoad(ValType::I64, Scalar::Int64));
           case Expr::I64Store8:
             CHECK_NEXT(emitStore(ValType::I64, Scalar::Int8));
+          case Expr::I64TeeStore8:
+            CHECK_NEXT(emitTeeStore(ValType::I64, Scalar::Int8));
           case Expr::I64Store16:
             CHECK_NEXT(emitStore(ValType::I64, Scalar::Int16));
+          case Expr::I64TeeStore16:
+            CHECK_NEXT(emitTeeStore(ValType::I64, Scalar::Int16));
           case Expr::I64Store32:
             CHECK_NEXT(emitStore(ValType::I64, Scalar::Int32));
+          case Expr::I64TeeStore32:
+            CHECK_NEXT(emitTeeStore(ValType::I64, Scalar::Int32));
           case Expr::I64Store:
             CHECK_NEXT(emitStore(ValType::I64, Scalar::Int64));
+          case Expr::I64TeeStore:
+            CHECK_NEXT(emitTeeStore(ValType::I64, Scalar::Int64));
 
           // F32
           case Expr::F32Const: {
             RawF32 f32;
             CHECK(iter_.readF32Const(&f32));
             if (!deadCode_)
                 pushF32(f32);
             NEXT();
@@ -6266,18 +6407,20 @@ BaseCompiler::emitBody()
           case Expr::F32ConvertUI64:
             CHECK_NEXT(emitConversion(emitConvertU64ToF32, ValType::I64, ValType::F32));
           case Expr::F32ReinterpretI32:
             CHECK_NEXT(emitConversion(emitReinterpretI32AsF32, ValType::I32, ValType::F32));
           case Expr::F32Load:
             CHECK_NEXT(emitLoad(ValType::F32, Scalar::Float32));
           case Expr::F32Store:
             CHECK_NEXT(emitStore(ValType::F32, Scalar::Float32));
-          case Expr::F32StoreF64:
-            CHECK_NEXT(emitStoreWithCoercion(ValType::F32, Scalar::Float64));
+          case Expr::F32TeeStore:
+            CHECK_NEXT(emitTeeStore(ValType::F32, Scalar::Float32));
+          case Expr::F32TeeStoreF64:
+            CHECK_NEXT(emitTeeStoreWithCoercion(ValType::F32, Scalar::Float64));
           case Expr::F32CopySign:
             CHECK_NEXT(emitBinary(emitCopysignF32, ValType::F32));
           case Expr::F32Nearest:
             CHECK_NEXT(emitUnaryMathBuiltinCall(exprOffset, SymbolicAddress::NearbyIntF, ValType::F32));
           case Expr::F32Trunc:
             CHECK_NEXT(emitUnaryMathBuiltinCall(exprOffset, SymbolicAddress::TruncF, ValType::F32));
 
           // F64
@@ -6341,18 +6484,20 @@ BaseCompiler::emitBody()
           case Expr::F64ConvertSI64:
             CHECK_NEXT(emitConversion(emitConvertI64ToF64, ValType::I64, ValType::F64));
           case Expr::F64ConvertUI64:
             CHECK_NEXT(emitConversion(emitConvertU64ToF64, ValType::I64, ValType::F64));
           case Expr::F64Load:
             CHECK_NEXT(emitLoad(ValType::F64, Scalar::Float64));
           case Expr::F64Store:
             CHECK_NEXT(emitStore(ValType::F64, Scalar::Float64));
-          case Expr::F64StoreF32:
-            CHECK_NEXT(emitStoreWithCoercion(ValType::F64, Scalar::Float32));
+          case Expr::F64TeeStore:
+            CHECK_NEXT(emitTeeStore(ValType::F64, Scalar::Float64));
+          case Expr::F64TeeStoreF32:
+            CHECK_NEXT(emitTeeStoreWithCoercion(ValType::F64, Scalar::Float32));
           case Expr::F64ReinterpretI64:
             CHECK_NEXT(emitConversion(emitReinterpretI64AsF64, ValType::I64, ValType::F64));
           case Expr::F64CopySign:
             CHECK_NEXT(emitBinary(emitCopysignF64, ValType::F64));
           case Expr::F64Nearest:
             CHECK_NEXT(emitUnaryMathBuiltinCall(exprOffset, SymbolicAddress::NearbyIntD, ValType::F64));
           case Expr::F64Trunc:
             CHECK_NEXT(emitUnaryMathBuiltinCall(exprOffset, SymbolicAddress::TruncD, ValType::F64));
@@ -6525,46 +6670,40 @@ BaseCompiler::emitFunction()
     // emitBody() will ensure that there is enough memory reserved in the
     // vector for infallible allocation to succeed within the compiler, but we
     // need a little headroom for the initial pushControl(), which pushes a
     // void value onto the value stack.
 
     if (!stk_.reserve(8))
         return false;
 
-    if (!iter_.readFunctionStart())
+    const Sig& sig = func_.sig();
+
+    if (!iter_.readFunctionStart(sig.ret()))
         return false;
 
     beginFunction();
 
-    if (!pushControl(nullptr))
+    UniquePooledLabel functionEnd(newLabel());
+    if (!pushControl(&functionEnd))
         return false;
 
 #ifdef JS_CODEGEN_ARM64
     // FIXME: There is a hack up at the top to allow the baseline
     // compiler to compile on ARM64 (by defining StackPointer), but
     // the resulting code cannot run.  So prevent it from running.
     MOZ_CRASH("Several adjustments required for ARM64 operation");
 #endif
 
     if (!emitBody())
         return false;
 
-    const Sig& sig = func_.sig();
-
-    Nothing unused_value;
-    if (!iter_.readFunctionEnd(sig.ret(), &unused_value))
+    if (!iter_.readFunctionEnd())
         return false;
 
-    if (!deadCode_)
-        doReturn(sig.ret());
-
-    popStackOnBlockExit(ctl_[0].framePushed);
-    popControl();
-
     if (!endFunction())
         return false;
 
     return true;
 }
 
 BaseCompiler::BaseCompiler(const ModuleGeneratorData& mg,
                            Decoder& decoder,
--- a/js/src/asmjs/WasmBinary.cpp
+++ b/js/src/asmjs/WasmBinary.cpp
@@ -13,22 +13,61 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "asmjs/WasmBinary.h"
 
+#include <stdarg.h>
+
+#include "jsprf.h"
 #include "asmjs/WasmTypes.h"
 
 using namespace js;
 using namespace js::wasm;
 
 bool
+Decoder::fail(const char* msg, ...) {
+    va_list ap;
+    va_start(ap, msg);
+    UniqueChars str(JS_vsmprintf(msg, ap));
+    va_end(ap);
+    if (!str)
+        return false;
+
+    return fail(Move(str));
+}
+
+bool
+Decoder::fail(UniqueChars msg) {
+    UniqueChars strWithOffset(JS_smprintf("at offset %zu: %s", currentOffset(), msg.get()));
+    if (!strWithOffset)
+        return false;
+
+    *error_ = Move(strWithOffset);
+    return false;
+}
+
+bool
+wasm::DecodePreamble(Decoder& d)
+{
+    uint32_t u32;
+    if (!d.readFixedU32(&u32) || u32 != MagicNumber)
+        return d.fail("failed to match magic number");
+
+    if (!d.readFixedU32(&u32) || u32 != EncodingVersion)
+        return d.fail("binary version 0x%" PRIx32 " does not match expected version 0x%" PRIx32,
+                      u32, EncodingVersion);
+
+    return true;
+}
+
+bool
 wasm::EncodeLocalEntries(Encoder& e, const ValTypeVector& locals)
 {
     uint32_t numLocalEntries = 0;
     ValType prev = ValType::Limit;
     for (ValType t : locals) {
         if (t != prev) {
             numLocalEntries++;
             prev = t;
@@ -80,8 +119,68 @@ wasm::DecodeLocalEntries(Decoder& d, Val
             return false;
 
         if (!locals->appendN(type, count))
             return false;
     }
 
     return true;
 }
+
+bool
+wasm::DecodeGlobalType(Decoder& d, ValType* type, uint32_t* flags)
+{
+    if (!d.readValType(type))
+        return d.fail("bad global type");
+
+    if (!d.readVarU32(flags))
+        return d.fail("expected global flags");
+
+    if (*flags & ~uint32_t(GlobalFlags::AllowedMask))
+        return d.fail("unexpected bits set in global flags");
+
+    return true;
+}
+
+bool
+wasm::DecodeResizable(Decoder& d, ResizableLimits* limits)
+{
+    uint32_t flags;
+    if (!d.readVarU32(&flags))
+        return d.fail("expected flags");
+
+    if (flags & ~uint32_t(ResizableFlags::AllowedMask))
+        return d.fail("unexpected bits set in flags: %" PRIu32,
+                      (flags & ~uint32_t(ResizableFlags::AllowedMask)));
+
+    if (!(flags & uint32_t(ResizableFlags::Default)))
+        return d.fail("currently, every memory/table must be declared default");
+
+    if (!d.readVarU32(&limits->initial))
+        return d.fail("expected initial length");
+
+    if (flags & uint32_t(ResizableFlags::HasMaximum)) {
+        uint32_t maximum;
+        if (!d.readVarU32(&maximum))
+            return d.fail("expected maximum length");
+
+        if (limits->initial > maximum) {
+            return d.fail("memory size minimum must not be greater than maximum; "
+                          "maximum length %" PRIu32 " is less than initial length %" PRIu32,
+                          maximum, limits->initial);
+        }
+
+        limits->maximum.emplace(maximum);
+    }
+
+    return true;
+}
+
+bool
+wasm::DecodeUnknownSections(Decoder& d)
+{
+    while (!d.done()) {
+        if (!d.skipUserDefinedSection())
+            return false;
+    }
+
+    return true;
+}
--- a/js/src/asmjs/WasmBinary.h
+++ b/js/src/asmjs/WasmBinary.h
@@ -63,30 +63,34 @@ using RawF32 = Raw<float>;
 // or WebAssembly is used.
 
 enum class Telemetry {
     ASMJS = 0,
     WASM = 1
 };
 
 static const uint32_t MagicNumber        = 0x6d736100; // "\0asm"
-static const uint32_t EncodingVersion    = 0x0b;
+static const uint32_t EncodingVersion    = 0x0c;
 
-static const char TypeSectionId[]        = "type";
-static const char GlobalSectionId[]      = "global";
-static const char ImportSectionId[]      = "import";
-static const char FunctionSectionId[]    = "function";
-static const char TableSectionId[]       = "table";
-static const char MemorySectionId[]      = "memory";
-static const char ExportSectionId[]      = "export";
-static const char StartSectionId[]       = "start";
-static const char CodeSectionId[]        = "code";
-static const char ElemSectionId[]        = "elem";
-static const char DataSectionId[]        = "data";
-static const char NameSectionId[]        = "name";
+enum class SectionId {
+    UserDefined = 0,
+    Type        = 1,
+    Import      = 2,
+    Function    = 3,
+    Table       = 4,
+    Memory      = 5,
+    Global      = 6,
+    Export      = 7,
+    Start       = 8,
+    Elem        = 9,
+    Code        = 10,
+    Data        = 11
+};
+
+static const char NameSectionName[] = "name";
 
 enum class ValType
 {
     I32                                  = 0x01,
     I64                                  = 0x02,
     F32                                  = 0x03,
     F64                                  = 0x04,
 
@@ -130,39 +134,41 @@ enum class GlobalFlags
 {
     IsMutable                            = 0x1,
     AllowedMask                          = 0x1
 };
 
 enum class Expr
 {
     // Control flow operators
-    Nop                                  = 0x00,
+    Unreachable                          = 0x00,
     Block                                = 0x01,
     Loop                                 = 0x02,
     If                                   = 0x03,
     Else                                 = 0x04,
     Select                               = 0x05,
     Br                                   = 0x06,
     BrIf                                 = 0x07,
     BrTable                              = 0x08,
     Return                               = 0x09,
-    Unreachable                          = 0x0a,
+    Nop                                  = 0x0a,
+    Drop                                 = 0x0b,
     End                                  = 0x0f,
 
     // Basic operators
     I32Const                             = 0x10,
     I64Const                             = 0x11,
     F64Const                             = 0x12,
     F32Const                             = 0x13,
     GetLocal                             = 0x14,
     SetLocal                             = 0x15,
     Call                                 = 0x16,
     CallIndirect                         = 0x17,
     CallImport                           = 0x18,
+    TeeLocal                             = 0x19,
 
     // Memory-related operators
     I32Load8S                            = 0x20,
     I32Load8U                            = 0x21,
     I32Load16S                           = 0x22,
     I32Load16U                           = 0x23,
     I64Load8S                            = 0x24,
     I64Load8U                            = 0x25,
@@ -327,35 +333,48 @@ enum class Expr
     GetGlobal                            = 0xc0,
     SetGlobal                            = 0xc1,
 
     // ------------------------------------------------------------------------
     // The rest of these operators are currently only emitted internally when
     // compiling asm.js and are rejected by wasm validation.
 
     // asm.js-specific operators
+    TeeGlobal                            = 0xc8,
     I32Min,
     I32Max,
     I32Neg,
     I32BitNot,
     I32Abs,
-    F32StoreF64,
-    F64StoreF32,
+    F32TeeStoreF64,
+    F64TeeStoreF32,
+    I32TeeStore8,
+    I32TeeStore16,
+    I64TeeStore8,
+    I64TeeStore16,
+    I64TeeStore32,
+    I32TeeStore,
+    I64TeeStore,
+    F32TeeStore,
+    F64TeeStore,
     F64Mod,
     F64Sin,
     F64Cos,
     F64Tan,
     F64Asin,
     F64Acos,
     F64Atan,
     F64Exp,
     F64Log,
     F64Pow,
     F64Atan2,
 
+    // asm.js-style call_indirect with the callee evaluated first.
+    OldCallIndirect,
+
     // Atomics
     I32AtomicsCompareExchange,
     I32AtomicsExchange,
     I32AtomicsLoad,
     I32AtomicsStore,
     I32AtomicsBinOp,
 
     // SIMD
@@ -518,16 +537,26 @@ class Encoder
                 patchByte |= 0x80;
             }
             MOZ_ASSERT(assertByte == bytes_[offset]);
             bytes_[offset] = patchByte;
             offset++;
         } while(assertBits != 0);
     }
 
+    void patchFixedU7(size_t offset, uint8_t patchBits, uint8_t assertBits) {
+        MOZ_ASSERT(patchBits <= uint8_t(INT8_MAX));
+        patchFixedU8(offset, patchBits, assertBits);
+    }
+
+    void patchFixedU8(size_t offset, uint8_t patchBits, uint8_t assertBits) {
+        MOZ_ASSERT(bytes_[offset] == assertBits);
+        bytes_[offset] = patchBits;
+    }
+
     uint32_t varU32ByteLength(size_t offset) const {
         size_t start = offset;
         while (bytes_[offset] & 0x80)
             offset++;
         return offset - start + 1;
     }
 
     static const size_t ExprLimit = 2 * UINT8_MAX - 1;
@@ -540,16 +569,20 @@ class Encoder
     }
 
     size_t currentOffset() const { return bytes_.length(); }
     bool empty() const { return currentOffset() == 0; }
 
     // Fixed-size encoding operations simply copy the literal bytes (without
     // attempting to align).
 
+    MOZ_MUST_USE bool writeFixedU7(uint8_t i) {
+        MOZ_ASSERT(i <= uint8_t(INT8_MAX));
+        return writeFixedU8(i);
+    }
     MOZ_MUST_USE bool writeFixedU8(uint8_t i) {
         return write<uint8_t>(i);
     }
     MOZ_MUST_USE bool writeFixedU32(uint32_t i) {
         return write<uint32_t>(i);
     }
     MOZ_MUST_USE bool writeFixedF32(RawF32 f) {
         return write<uint32_t>(f.bits());
@@ -583,24 +616,38 @@ class Encoder
     }
     MOZ_MUST_USE bool writeVarS64(int64_t i) {
         return writeVarS<int64_t>(i);
     }
     MOZ_MUST_USE bool writeValType(ValType type) {
         static_assert(size_t(ValType::Limit) <= INT8_MAX, "fits");
         return writeFixedU8(size_t(type));
     }
+    MOZ_MUST_USE bool writeExprType(ExprType type) {
+        static_assert(size_t(ExprType::Limit) <= INT8_MAX, "fits");
+        return writeFixedU8(size_t(type));
+    }
     MOZ_MUST_USE bool writeExpr(Expr expr) {
         static_assert(size_t(Expr::Limit) <= ExprLimit, "fits");
         if (size_t(expr) < UINT8_MAX)
             return writeFixedU8(uint8_t(expr));
         return writeFixedU8(UINT8_MAX) &&
                writeFixedU8(size_t(expr) - UINT8_MAX);
     }
 
+    // Fixed-length encodings that allow back-patching.
+
+    MOZ_MUST_USE bool writePatchableFixedU7(size_t* offset) {
+        *offset = bytes_.length();
+        return writeFixedU8(UINT8_MAX);
+    }
+    void patchFixedU7(size_t offset, uint8_t patchBits) {
+        return patchFixedU7(offset, patchBits, UINT8_MAX);
+    }
+
     // Variable-length encodings that allow back-patching.
 
     MOZ_MUST_USE bool writePatchableVarU32(size_t* offset) {
         *offset = bytes_.length();
         return writeVarU32(UINT32_MAX);
     }
     void patchVarU32(size_t offset, uint32_t patchBits) {
         return patchVarU32(offset, patchBits, UINT32_MAX);
@@ -615,22 +662,20 @@ class Encoder
     }
 
     // A "section" is a contiguous range of bytes that stores its own size so
     // that it may be trivially skipped without examining the contents. Sections
     // require backpatching since the size of the section is only known at the
     // end while the size's varU32 must be stored at the beginning. Immediately
     // after the section length is the string id of the section.
 
-    template <size_t IdSizeWith0>
-    MOZ_MUST_USE bool startSection(const char (&id)[IdSizeWith0], size_t* offset) {
-        static const size_t IdSize = IdSizeWith0 - 1;
-        MOZ_ASSERT(id[IdSize] == '\0');
-        return writeVarU32(IdSize) &&
-               bytes_.append(reinterpret_cast<const uint8_t*>(id), IdSize) &&
+    MOZ_MUST_USE bool startSection(SectionId id, size_t* offset) {
+        MOZ_ASSERT(id != SectionId::UserDefined); // not supported yet
+
+        return writeVarU32(uint32_t(id)) &&
                writePatchableVarU32(offset);
     }
     void finishSection(size_t offset) {
         return patchVarU32(offset, bytes_.length() - offset - varU32ByteLength(offset));
     }
 };
 
 // The Decoder class decodes the bytes in the range it is given during
@@ -735,24 +780,18 @@ class Decoder
     }
     explicit Decoder(const Bytes& bytes, UniqueChars* error = nullptr)
       : beg_(bytes.begin()),
         end_(bytes.end()),
         cur_(bytes.begin()),
         error_(error)
     {}
 
-    bool fail(const char* msg) {
-        error_->reset(strdup(msg));
-        return false;
-    }
-    bool fail(UniqueChars msg) {
-        *error_ = Move(msg);
-        return false;
-    }
+    bool fail(const char* msg, ...);
+    bool fail(UniqueChars msg);
     void clearError() {
         if (error_)
             error_->reset();
     }
 
     bool done() const {
         MOZ_ASSERT(cur_ <= end_);
         return cur_ == end_;
@@ -854,58 +893,112 @@ class Decoder
         cur_ += numBytes;
         return true;
     }
 
     // See "section" description in Encoder.
 
     static const uint32_t NotStarted = UINT32_MAX;
 
-    template <size_t IdSizeWith0>
-    MOZ_MUST_USE bool startSection(const char (&id)[IdSizeWith0],
+    MOZ_MUST_USE bool startSection(SectionId id,
                                    uint32_t* startOffset,
-                                   uint32_t* size) {
-        static const size_t IdSize = IdSizeWith0 - 1;
-        MOZ_ASSERT(id[IdSize] == '\0');
-        const uint8_t* before = cur_;
-        uint32_t idSize;
-        if (!readVarU32(&idSize))
+                                   uint32_t* size,
+                                   const char* sectionName)
+    {
+        const uint8_t* const before = cur_;
+        const uint8_t* beforeId = before;
+        uint32_t idValue;
+        if (!readVarU32(&idValue))
             goto backup;
-        if (bytesRemain() < idSize)
-            return false;
-        if (idSize != IdSize || !!memcmp(cur_, id, IdSize))
-            goto backup;
-        cur_ += IdSize;
+        while (idValue != uint32_t(id)) {
+            if (idValue != uint32_t(SectionId::UserDefined))
+                goto backup;
+            // Rewind to the section id since skipUserDefinedSection expects it.
+            cur_ = beforeId;
+            if (!skipUserDefinedSection())
+                return false;
+            beforeId = cur_;
+            if (!readVarU32(&idValue))
+                goto backup;
+        }
         if (!readVarU32(size))
-            goto backup;
+            goto fail;
         if (bytesRemain() < *size)
-            return false;
+            goto fail;
         *startOffset = cur_ - beg_;
         return true;
       backup:
         cur_ = before;
         *startOffset = NotStarted;
         return true;
-    }
-    MOZ_MUST_USE bool finishSection(uint32_t startOffset, uint32_t size) {
-        return size == (cur_ - beg_) - startOffset;
+      fail:
+        return fail("failed to start %s section", sectionName);
     }
-    void ignoreSection(uint32_t startOffset, uint32_t size) {
-        cur_ = (beg_ + startOffset) + size;
-        MOZ_ASSERT(cur_ <= end_);
+    MOZ_MUST_USE bool finishSection(uint32_t startOffset, uint32_t size,
+                                    const char* sectionName)
+    {
+        if (size != (cur_ - beg_) - startOffset)
+            return fail("byte size mismatch in %s section", sectionName);
+        return true;
     }
-    MOZ_MUST_USE bool skipSection() {
-        uint32_t idSize;
-        if (!readVarU32(&idSize) || bytesRemain() < idSize)
+
+    // "User sections" do not cause validation errors unless the error is in
+    // the user-defined section header itself.
+
+    MOZ_MUST_USE bool startUserDefinedSection(const char* expectedId,
+                                              size_t expectedIdSize,
+                                              uint32_t* sectionStart,
+                                              uint32_t* sectionSize)
+    {
+        const uint8_t* const before = cur_;
+        while (true) {
+            if (!startSection(SectionId::UserDefined, sectionStart, sectionSize, "user-defined"))
+                return false;
+            if (*sectionStart == NotStarted) {
+                cur_ = before;
+                return true;
+            }
+            uint32_t idSize;
+            if (!readVarU32(&idSize))
+                goto fail;
+            if (idSize > bytesRemain() || currentOffset() + idSize > *sectionStart + *sectionSize)
+                goto fail;
+            if (expectedId && (expectedIdSize != idSize || !!memcmp(cur_, expectedId, idSize))) {
+                finishUserDefinedSection(*sectionStart, *sectionSize);
+                continue;
+            }
+            cur_ += idSize;
+            return true;
+        }
+        MOZ_CRASH("unreachable");
+      fail:
+        return fail("failed to start user-defined section");
+    }
+    template <size_t IdSizeWith0>
+    MOZ_MUST_USE bool startUserDefinedSection(const char (&id)[IdSizeWith0],
+                                              uint32_t* sectionStart,
+                                              uint32_t* sectionSize)
+    {
+        MOZ_ASSERT(id[IdSizeWith0 - 1] == '\0');
+        return startUserDefinedSection(id, IdSizeWith0 - 1, sectionStart, sectionSize);
+    }
+    void finishUserDefinedSection(uint32_t sectionStart, uint32_t sectionSize) {
+        MOZ_ASSERT(cur_ >= beg_);
+        MOZ_ASSERT(cur_ <= end_);
+        cur_ = (beg_ + sectionStart) + sectionSize;
+        MOZ_ASSERT(cur_ <= end_);
+        clearError();
+    }
+    MOZ_MUST_USE bool skipUserDefinedSection() {
+        uint32_t sectionStart, sectionSize;
+        if (!startUserDefinedSection(nullptr, 0, &sectionStart, &sectionSize))
             return false;
-        cur_ += idSize;
-        uint32_t size;
-        if (!readVarU32(&size) || bytesRemain() < size)
-            return false;
-        cur_ += size;
+        if (sectionStart == NotStarted)
+            return fail("expected user-defined section");
+        finishUserDefinedSection(sectionStart, sectionSize);
         return true;
     }
 
     // The infallible "unchecked" decoding functions can be used when we are
     // sure that the bytes are well-formed (by construction or due to previous
     // validation).
 
     uint8_t uncheckedReadFixedU8() {
@@ -987,17 +1080,31 @@ class Decoder
 };
 
 // Reusable macro encoding/decoding functions reused by both the two
 // encoders (AsmJS/WasmText) and decoders (Wasm/WasmIonCompile).
 
 typedef Vector<ValType, 8, SystemAllocPolicy> ValTypeVector;
 
 MOZ_MUST_USE bool
+DecodePreamble(Decoder& d);
+
+MOZ_MUST_USE bool
 EncodeLocalEntries(Encoder& d, const ValTypeVector& locals);
 
 MOZ_MUST_USE bool
 DecodeLocalEntries(Decoder& d, ValTypeVector* locals);
 
+MOZ_MUST_USE bool
+DecodeGlobalType(Decoder& d, ValType* type, uint32_t* flags);
+
+struct ResizableLimits;
+
+MOZ_MUST_USE bool
+DecodeResizable(Decoder& d, ResizableLimits* resizable);
+
+MOZ_MUST_USE bool
+DecodeUnknownSections(Decoder& d);
+
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_binary_h
--- a/js/src/asmjs/WasmBinaryIterator.cpp
+++ b/js/src/asmjs/WasmBinaryIterator.cpp
@@ -28,16 +28,18 @@ wasm::Classify(Expr expr)
 {
     switch (expr) {
       case Expr::Block:
         return ExprKind::Block;
       case Expr::Loop:
         return ExprKind::Loop;
       case Expr::Unreachable:
         return ExprKind::Unreachable;
+      case Expr::Drop:
+        return ExprKind::Drop;
       case Expr::I32Const:
         return ExprKind::I32;
       case Expr::I64Const:
         return ExprKind::I64;
       case Expr::F32Const:
         return ExprKind::F32;
       case Expr::F64Const:
         return ExprKind::F64;
@@ -57,17 +59,17 @@ wasm::Classify(Expr expr)
         return ExprKind::F32x4;
       case Expr::Br:
         return ExprKind::Br;
       case Expr::BrIf:
         return ExprKind::BrIf;
       case Expr::BrTable:
         return ExprKind::BrTable;
       case Expr::Nop:
-        return ExprKind::Nullary;
+        return ExprKind::Nop;
       case Expr::I32Clz:
       case Expr::I32Ctz:
       case Expr::I32Popcnt:
       case Expr::I64Clz:
       case Expr::I64Ctz:
       case Expr::I64Popcnt:
       case Expr::F32Abs:
       case Expr::F32Neg:
@@ -309,41 +311,59 @@ wasm::Classify(Expr expr)
       case Expr::I32Store16:
       case Expr::I64Store8:
       case Expr::I64Store16:
       case Expr::I64Store32:
       case Expr::I32Store:
       case Expr::I64Store:
       case Expr::F32Store:
       case Expr::F64Store:
-      case Expr::F32StoreF64:
-      case Expr::F64StoreF32:
+        return ExprKind::Store;
+      case Expr::I32TeeStore8:
+      case Expr::I32TeeStore16:
+      case Expr::I64TeeStore8:
+      case Expr::I64TeeStore16:
+      case Expr::I64TeeStore32:
+      case Expr::I32TeeStore:
+      case Expr::I64TeeStore:
+      case Expr::F32TeeStore:
+      case Expr::F64TeeStore:
+      case Expr::F32TeeStoreF64:
+      case Expr::F64TeeStoreF32:
       case Expr::I8x16store:
       case Expr::I16x8store:
       case Expr::I32x4store:
       case Expr::I32x4store1:
       case Expr::I32x4store2:
       case Expr::I32x4store3:
       case Expr::F32x4store:
       case Expr::F32x4store1:
       case Expr::F32x4store2:
       case Expr::F32x4store3:
-        return ExprKind::Store;
+        return ExprKind::TeeStore;
       case Expr::Select:
         return ExprKind::Select;
       case Expr::GetLocal:
-      case Expr::GetGlobal:
-        return ExprKind::GetVar;
+        return ExprKind::GetLocal;
       case Expr::SetLocal:
+        return ExprKind::SetLocal;
+      case Expr::TeeLocal:
+        return ExprKind::TeeLocal;
+      case Expr::GetGlobal:
+        return ExprKind::GetGlobal;
       case Expr::SetGlobal:
-        return ExprKind::SetVar;
+        return ExprKind::SetGlobal;
+      case Expr::TeeGlobal:
+        return ExprKind::TeeGlobal;
       case Expr::Call:
         return ExprKind::Call;
       case Expr::CallIndirect:
         return ExprKind::CallIndirect;
+      case Expr::OldCallIndirect:
+        return ExprKind::OldCallIndirect;
       case Expr::CallImport:
         return ExprKind::CallImport;
       case Expr::Return:
       case Expr::Limit:
         // Accept Limit, for use in decoding the end of a function after the body.
         return ExprKind::Return;
       case Expr::If:
         return ExprKind::If;
--- a/js/src/asmjs/WasmBinaryIterator.h
+++ b/js/src/asmjs/WasmBinaryIterator.h
@@ -25,50 +25,64 @@
 
 #include "asmjs/WasmTypes.h"
 #include "jit/AtomicOp.h"
 
 namespace js {
 namespace wasm {
 
 // The kind of a control-flow stack item.
-enum class LabelKind : uint8_t { Block, Loop, Then, Else };
+enum class LabelKind : uint8_t {
+    Block,
+    Loop,
+    Then,
+    UnreachableThen, // like Then, but not reachable
+    Else
+};
 
 #ifdef DEBUG
 // Families of opcodes that share a signature and validation logic.
 enum class ExprKind {
     Block,
     Loop,
     Unreachable,
+    Drop,
     I32,
     I64,
     F32,
     F64,
     I8x16,
     I16x8,
     I32x4,
     F32x4,
     B8x16,
     B16x8,
     B32x4,
     Br,
     BrIf,
     BrTable,
+    Nop,
     Nullary,
     Unary,
     Binary,
     Comparison,
     Conversion,
     Load,
     Store,
+    TeeStore,
     Select,
-    GetVar,
-    SetVar,
+    GetLocal,
+    SetLocal,
+    TeeLocal,
+    GetGlobal,
+    SetGlobal,
+    TeeGlobal,
     Call,
     CallIndirect,
+    OldCallIndirect,
     CallImport,
     Return,
     If,
     Else,
     End,
     AtomicLoad,
     AtomicStore,
     AtomicBinOp,
@@ -106,92 +120,116 @@ struct LinearMemoryAddress
       : base(base), offset(offset), align(align)
     {}
 };
 
 template <typename ControlItem>
 class ControlStackEntry
 {
     LabelKind kind_;
+    bool reachable_;
     ExprType type_;
     size_t valueStackStart_;
     ControlItem controlItem_;
 
   public:
-    ControlStackEntry(LabelKind kind, size_t valueStackStart)
-      : kind_(kind), type_(AnyType), valueStackStart_(valueStackStart)
-    {}
+    ControlStackEntry(LabelKind kind, ExprType type, bool reachable, size_t valueStackStart)
+      : kind_(kind), reachable_(reachable), type_(type), valueStackStart_(valueStackStart),
+        controlItem_()
+    {
+        MOZ_ASSERT(type != ExprType::Limit);
+    }
 
     LabelKind kind() const { return kind_; }
+    ExprType type() const { return type_; }
+    bool reachable() const { return reachable_; }
     size_t valueStackStart() const { return valueStackStart_; }
-    const ExprType& type() const { return type_; }
-    ExprType& type() { return type_; }
     ControlItem& controlItem() { return controlItem_; }
+
+    void setReachable() { reachable_ = true; }
+
+    void switchToElse(bool reachable) {
+        MOZ_ASSERT(kind_ == LabelKind::Then || kind_ == LabelKind::UnreachableThen);
+        reachable_ = reachable;
+        kind_ = LabelKind::Else;
+        controlItem_ = ControlItem();
+    }
 };
 
 // Specialization for when there is no additional data needed.
 template <>
 class ControlStackEntry<Nothing>
 {
     LabelKind kind_;
+    bool reachable_;
     ExprType type_;
     size_t valueStackStart_;
 
   public:
-    ControlStackEntry(LabelKind kind, size_t valueStackStart)
-      : kind_(kind), type_(AnyType), valueStackStart_(valueStackStart)
-    {}
+    ControlStackEntry(LabelKind kind, ExprType type, bool reachable, size_t valueStackStart)
+      : kind_(kind), reachable_(reachable), type_(type), valueStackStart_(valueStackStart)
+    {
+        MOZ_ASSERT(type != ExprType::Limit);
+    }
 
     LabelKind kind() const { return kind_; }
+    ExprType type() const { return type_; }
+    bool reachable() const { return reachable_; }
     size_t valueStackStart() const { return valueStackStart_; }
-    const ExprType& type() const { return type_; }
-    ExprType& type() { return type_; }
     Nothing controlItem() { return Nothing(); }
+
+    void setReachable() { reachable_ = true; }
+
+    void switchToElse(bool reachable) {
+        MOZ_ASSERT(kind_ == LabelKind::Then || kind_ == LabelKind::UnreachableThen);
+        reachable_ = reachable;
+        kind_ = LabelKind::Else;
+    }
 };
 
 template <typename Value>
 class TypeAndValue
 {
-    ExprType type_;
+    ValType type_;
     Value value_;
 
   public:
-    TypeAndValue() = default;
-    explicit TypeAndValue(ExprType type)
-      : type_(type)
+    TypeAndValue() : type_(ValType::Limit), value_() {}
+    explicit TypeAndValue(ValType type)
+      : type_(type), value_()
     {}
-    TypeAndValue(ExprType type, Value value)
+    TypeAndValue(ValType type, Value value)
       : type_(type), value_(value)
     {}
-    ExprType type() const {
+    ValType type() const {
         return type_;
     }
     Value value() const {
         return value_;
     }
     void setValue(Value value) {
         value_ = value;
     }
 };
 
 // Specialization for when there is no additional data needed.
 template <>
 class TypeAndValue<Nothing>
 {
-    ExprType type_;
+    ValType type_;
 
   public:
-    TypeAndValue() = default;
-    explicit TypeAndValue(ExprType type) : type_(type) {}
+    TypeAndValue() : type_(ValType::Limit) {}
+    explicit TypeAndValue(ValType type) : type_(type) {}
 
-    TypeAndValue(ExprType type, Nothing value)
+    TypeAndValue(ValType type, Nothing value)
       : type_(type)
     {}
 
-    ExprType type() const { return type_; }
+    ValType type() const { return type_; }
     Nothing value() const { return Nothing(); }
     void setValue(Nothing value) {}
 };
 
 // A policy class for configuring ExprIter. Clients can use this as a
 // base class, and override the behavior as needed.
 struct ExprIterPolicy
 {
@@ -222,16 +260,17 @@ class MOZ_STACK_CLASS ExprIter : private
     static const bool Output = Policy::Output;
     typedef typename Policy::Value Value;
     typedef typename Policy::ControlItem ControlItem;
 
     Decoder& d_;
 
     Vector<TypeAndValue<Value>, 0, SystemAllocPolicy> valueStack_;
     Vector<ControlStackEntry<ControlItem>, 0, SystemAllocPolicy> controlStack_;
+    bool reachable_;
 
     DebugOnly<Expr> expr_;
 
     MOZ_MUST_USE bool readFixedU8(uint8_t* out) {
         if (Validate)
             return d_.readFixedU8(out);
         *out = d_.uncheckedReadFixedU8();
         return true;
@@ -301,120 +340,174 @@ class MOZ_STACK_CLASS ExprIter : private
             return d_.readFixedF32x4(out);
         d_.uncheckedReadFixedF32x4(out);
         return true;
     }
 
     MOZ_MUST_USE bool readAtomicViewType(Scalar::Type* viewType) {
         uint8_t x;
         if (!readFixedU8(&x))
-            return false;
+            return fail("unable to read atomic view");
         if (Validate && x >= Scalar::MaxTypedArrayViewType)
             return fail("invalid atomic view type");
         *viewType = Scalar::Type(x);
         return true;
     }
 
     MOZ_MUST_USE bool readAtomicBinOpOp(jit::AtomicOp* op) {
         uint8_t x;
         if (!readFixedU8(&x))
-            return false;
+            return fail("unable to read atomic opcode");
         if (Validate) {
             switch (x) {
               case jit::AtomicFetchAddOp:
               case jit::AtomicFetchSubOp:
               case jit::AtomicFetchAndOp:
               case jit::AtomicFetchOrOp:
               case jit::AtomicFetchXorOp:
                 break;
               default:
                 return fail("unrecognized atomic binop");
             }
         }
         *op = jit::AtomicOp(x);
         return true;
     }
 
+    MOZ_MUST_USE bool readLinearMemoryAddress(uint32_t byteSize, LinearMemoryAddress<Value>* addr);
+    MOZ_MUST_USE bool readExprType(ExprType* expr);
+
     MOZ_MUST_USE bool typeMismatch(ExprType actual, ExprType expected) MOZ_COLD;
+    MOZ_MUST_USE bool checkType(ValType actual, ValType expected);
     MOZ_MUST_USE bool checkType(ExprType actual, ExprType expected);
-    MOZ_MUST_USE bool readFunctionReturnValue(ExprType ret);
-    MOZ_MUST_USE bool checkBranch(uint32_t relativeDepth, ExprType type);
-    MOZ_MUST_USE bool pushControl(LabelKind kind);
+
+    MOZ_MUST_USE bool pushControl(LabelKind kind, ExprType type, bool reachable);
+    MOZ_MUST_USE bool mergeControl(LabelKind* kind, ExprType* type, Value* value);
     MOZ_MUST_USE bool popControl(LabelKind* kind, ExprType* type, Value* value);
-    MOZ_MUST_USE bool popControlAfterCheck(LabelKind* kind, ExprType* type, Value* value);
-    MOZ_MUST_USE bool push(ExprType t) { return valueStack_.emplaceBack(t); }
-    MOZ_MUST_USE bool push(TypeAndValue<Value> tv) { return valueStack_.append(tv); }
 
-    MOZ_MUST_USE bool readLinearMemoryAddress(uint32_t byteSize, LinearMemoryAddress<Value>* addr);
-
-    void infallibleCheckSuccessor(ControlStackEntry<ControlItem>& controlItem, ExprType type);
-    void infalliblePush(ExprType t) { valueStack_.infallibleEmplaceBack(t); }
-    void infalliblePush(TypeAndValue<Value> tv) { valueStack_.infallibleAppend(tv); }
+    MOZ_MUST_USE bool push(ValType t) {
+        if (MOZ_UNLIKELY(!reachable_))
+            return true;
+        return valueStack_.emplaceBack(t);
+    }
+    MOZ_MUST_USE bool push(TypeAndValue<Value> tv) {
+        if (MOZ_UNLIKELY(!reachable_))
+            return true;
+        return valueStack_.append(tv);
+    }
+    void infalliblePush(ValType t) {
+        if (MOZ_UNLIKELY(!reachable_))
+            return;
+        valueStack_.infallibleEmplaceBack(t);
+    }
+    void infalliblePush(TypeAndValue<Value> tv) {
+        if (MOZ_UNLIKELY(!reachable_))
+            return;
+        valueStack_.infallibleAppend(tv);
+    }
 
     // Test whether reading the top of the value stack is currently valid.
     MOZ_MUST_USE bool checkTop() {
-        if (Validate && valueStack_.length() <= controlStack_.back().valueStackStart())
+        MOZ_ASSERT(reachable_);
+        if (Validate && valueStack_.length() <= controlStack_.back().valueStackStart()) {
+            if (valueStack_.empty())
+                return fail("popping value from empty stack");
             return fail("popping value from outside block");
+        }
         return true;
     }
 
     // Pop the top of the value stack.
     MOZ_MUST_USE bool pop(TypeAndValue<Value>* tv) {
+        if (MOZ_UNLIKELY(!reachable_))
+            return true;
         if (!checkTop())
             return false;
         *tv = valueStack_.popCopy();
         return true;
     }
 
     // Pop the top of the value stack and check that it has the given type.
-    MOZ_MUST_USE bool popWithType(ExprType expectedType, Value* value) {
+    MOZ_MUST_USE bool popWithType(ValType expectedType, Value* value) {
+        if (MOZ_UNLIKELY(!reachable_))
+            return true;
         if (!checkTop())
             return false;
         TypeAndValue<Value> tv = valueStack_.popCopy();
         if (!checkType(tv.type(), expectedType))
             return false;
         if (Output)
             *value = tv.value();
         return true;
     }
 
+    // Pop the top of the value stack and discard the result.
+    MOZ_MUST_USE bool pop() {
+        if (MOZ_UNLIKELY(!reachable_))
+            return true;
+        if (!checkTop())
+            return false;
+        valueStack_.popBack();
+        return true;
+    }
+
     // Read the top of the value stack (without popping it).
     MOZ_MUST_USE bool top(TypeAndValue<Value>* tv) {
+        if (MOZ_UNLIKELY(!reachable_))
+            return true;
         if (!checkTop())
             return false;
         *tv = valueStack_.back();
         return true;
     }
 
     // Read the top of the value stack (without popping it) and check that it
     // has the given type.
-    MOZ_MUST_USE bool topWithType(ExprType expectedType, Value* value) {
+    MOZ_MUST_USE bool topWithType(ValType expectedType, Value* value) {
+        if (MOZ_UNLIKELY(!reachable_))
+            return true;
         if (!checkTop())
             return false;
         TypeAndValue<Value>& tv = valueStack_.back();
         if (!checkType(tv.type(), expectedType))
             return false;
         if (Output)
             *value = tv.value();
         return true;
     }
 
     // Read the value stack entry at depth |index|.
     MOZ_MUST_USE bool peek(uint32_t index, TypeAndValue<Value>* tv) {
-        if (Validate && valueStack_.length() - controlStack_.back().valueStackStart() <= index)
+        MOZ_ASSERT(reachable_);
+        if (Validate && valueStack_.length() - controlStack_.back().valueStackStart() < index)
             return fail("peeking at value from outside block");
         *tv = valueStack_[valueStack_.length() - index];
         return true;
     }
 
+    bool getControl(uint32_t relativeDepth, ControlStackEntry<ControlItem>** controlEntry) {
+        if (Validate && relativeDepth >= controlStack_.length())
+            return fail("branch depth exceeds current nesting level");
+
+        *controlEntry = &controlStack_[controlStack_.length() - 1 - relativeDepth];
+        return true;
+    }
+
+    void enterUnreachableCode() {
+        valueStack_.shrinkTo(controlStack_.back().valueStackStart());
+        reachable_ = false;
+    }
+
+    bool checkBrValue(uint32_t relativeDepth, ExprType* type, Value* value);
+    bool checkBrIfValues(uint32_t relativeDepth, Value* condition, ExprType* type, Value* value);
+
   public:
     explicit ExprIter(Decoder& decoder)
-      : d_(decoder)
+      : d_(decoder), reachable_(true), expr_(Expr::Limit)
     {
-        expr_ = Expr::Limit;
     }
 
     // Return the decoding byte offset.
     uint32_t currentOffset() const { return d_.currentOffset(); }
 
     // Test whether the iterator has reached the end of the buffer.
     bool done() const { return d_.done(); }
 
@@ -422,67 +515,78 @@ class MOZ_STACK_CLASS ExprIter : private
     MOZ_MUST_USE bool fail(const char* msg) MOZ_COLD;
 
     // Report an unimplemented feature.
     MOZ_MUST_USE bool notYetImplemented(const char* what) MOZ_COLD;
 
     // Report an unrecognized opcode.
     MOZ_MUST_USE bool unrecognizedOpcode(Expr expr) MOZ_COLD;
 
+    // Test whether the iterator is currently in "reachable" code.
+    bool inReachableCode() const { return reachable_; }
+
     // ------------------------------------------------------------------------
     // Decoding and validation interface.
 
     MOZ_MUST_USE bool readExpr(Expr* expr);
-    MOZ_MUST_USE bool readFunctionStart();
-    MOZ_MUST_USE bool readFunctionEnd(ExprType ret, Value* value);
+    MOZ_MUST_USE bool readFunctionStart(ExprType ret);
+    MOZ_MUST_USE bool readFunctionEnd();
     MOZ_MUST_USE bool readReturn(Value* value);
     MOZ_MUST_USE bool readBlock();
     MOZ_MUST_USE bool readLoop();
     MOZ_MUST_USE bool readIf(Value* condition);
     MOZ_MUST_USE bool readElse(ExprType* thenType, Value* thenValue);
     MOZ_MUST_USE bool readEnd(LabelKind* kind, ExprType* type, Value* value);
     MOZ_MUST_USE bool readBr(uint32_t* relativeDepth, ExprType* type, Value* value);
     MOZ_MUST_USE bool readBrIf(uint32_t* relativeDepth, ExprType* type,
                                Value* value, Value* condition);
     MOZ_MUST_USE bool readBrTable(uint32_t* tableLength, ExprType* type,
                                   Value* value, Value* index);
-    MOZ_MUST_USE bool readBrTableEntry(ExprType type, uint32_t* depth);
+    MOZ_MUST_USE bool readBrTableEntry(ExprType* type, Value* value, uint32_t* depth);
+    MOZ_MUST_USE bool readBrTableDefault(ExprType* type, Value* value, uint32_t* depth);
     MOZ_MUST_USE bool readUnreachable();
+    MOZ_MUST_USE bool readDrop();
     MOZ_MUST_USE bool readUnary(ValType operandType, Value* input);
     MOZ_MUST_USE bool readConversion(ValType operandType, ValType resultType, Value* input);
     MOZ_MUST_USE bool readBinary(ValType operandType, Value* lhs, Value* rhs);
     MOZ_MUST_USE bool readComparison(ValType operandType, Value* lhs, Value* rhs);
     MOZ_MUST_USE bool readLoad(ValType resultType, uint32_t byteSize,
                                LinearMemoryAddress<Value>* addr);
     MOZ_MUST_USE bool readStore(ValType resultType, uint32_t byteSize,
                                 LinearMemoryAddress<Value>* addr, Value* value);
-    MOZ_MUST_USE bool readNullary(ExprType retType);
-    MOZ_MUST_USE bool readSelect(ExprType* type,
+    MOZ_MUST_USE bool readTeeStore(ValType resultType, uint32_t byteSize,
+                                   LinearMemoryAddress<Value>* addr, Value* value);
+    MOZ_MUST_USE bool readNop();
+    MOZ_MUST_USE bool readNullary(ValType retType);
+    MOZ_MUST_USE bool readSelect(ValType* type,
                                  Value* trueValue, Value* falseValue, Value* condition);
     MOZ_MUST_USE bool readGetLocal(const ValTypeVector& locals, uint32_t* id);
     MOZ_MUST_USE bool readSetLocal(const ValTypeVector& locals, uint32_t* id, Value* value);
+    MOZ_MUST_USE bool readTeeLocal(const ValTypeVector& locals, uint32_t* id, Value* value);
     MOZ_MUST_USE bool readGetGlobal(const GlobalDescVector& globals, uint32_t* id);
     MOZ_MUST_USE bool readSetGlobal(const GlobalDescVector& globals, uint32_t* id, Value* value);
+    MOZ_MUST_USE bool readTeeGlobal(const GlobalDescVector& globals, uint32_t* id, Value* value);
     MOZ_MUST_USE bool readI32Const(int32_t* i32);
     MOZ_MUST_USE bool readI64Const(int64_t* i64);
     MOZ_MUST_USE bool readF32Const(RawF32* f32);
     MOZ_MUST_USE bool readF64Const(RawF64* f64);
     MOZ_MUST_USE bool readI8x16Const(I8x16* i8x16);
     MOZ_MUST_USE bool readI16x8Const(I16x8* i16x8);
     MOZ_MUST_USE bool readI32x4Const(I32x4* i32x4);
     MOZ_MUST_USE bool readF32x4Const(F32x4* f32x4);
     MOZ_MUST_USE bool readB8x16Const(I8x16* i8x16);
     MOZ_MUST_USE bool readB16x8Const(I16x8* i16x8);
     MOZ_MUST_USE bool readB32x4Const(I32x4* i32x4);
-    MOZ_MUST_USE bool readCall(uint32_t* calleeIndex, uint32_t* arity);
-    MOZ_MUST_USE bool readCallIndirect(uint32_t* sigIndex, uint32_t* arity);
-    MOZ_MUST_USE bool readCallImport(uint32_t* importIndex, uint32_t* arity);
+    MOZ_MUST_USE bool readCall(uint32_t* calleeIndex);
+    MOZ_MUST_USE bool readCallIndirect(uint32_t* sigIndex, Value* callee);
+    MOZ_MUST_USE bool readOldCallIndirect(uint32_t* sigIndex);
+    MOZ_MUST_USE bool readCallImport(uint32_t* importIndex);
     MOZ_MUST_USE bool readCallArg(ValType type, uint32_t numArgs, uint32_t argIndex, Value* arg);
     MOZ_MUST_USE bool readCallArgsEnd(uint32_t numArgs);
-    MOZ_MUST_USE bool readCallIndirectCallee(Value* callee);
+    MOZ_MUST_USE bool readOldCallIndirectCallee(Value* callee);
     MOZ_MUST_USE bool readCallReturn(ExprType ret);
     MOZ_MUST_USE bool readAtomicLoad(LinearMemoryAddress<Value>* addr,
                                      Scalar::Type* viewType);
     MOZ_MUST_USE bool readAtomicStore(LinearMemoryAddress<Value>* addr,
                                       Scalar::Type* viewType,
                                       Value* value);
     MOZ_MUST_USE bool readAtomicBinOp(LinearMemoryAddress<Value>* addr,
                                       Scalar::Type* viewType,
@@ -507,974 +611,1205 @@ class MOZ_STACK_CLASS ExprIter : private
     MOZ_MUST_USE bool readSplat(ValType simdType, Value* scalar);
     MOZ_MUST_USE bool readSwizzle(ValType simdType, uint8_t (* lanes)[16], Value* vector);
     MOZ_MUST_USE bool readShuffle(ValType simdType, uint8_t (* lanes)[16],
                                   Value* lhs, Value* rhs);
     MOZ_MUST_USE bool readSimdSelect(ValType simdType, Value* trueValue,
                                      Value* falseValue,
                                      Value* condition);
     MOZ_MUST_USE bool readSimdCtor();
-    MOZ_MUST_USE bool readSimdCtorArg(ValType elementType, uint32_t numElements, uint32_t argIndex, Value* arg);
+    MOZ_MUST_USE bool readSimdCtorArg(ValType elementType, uint32_t numElements, uint32_t argIndex,
+                                      Value* arg);
     MOZ_MUST_USE bool readSimdCtorArgsEnd(uint32_t numElements);
     MOZ_MUST_USE bool readSimdCtorReturn(ValType simdType);
 
     // ------------------------------------------------------------------------
     // Stack management.
 
     // Set the result value of the current top-of-value-stack expression.
     void setResult(Value value) {
-        valueStack_.back().setValue(value);
+        if (MOZ_LIKELY(reachable_))
+            valueStack_.back().setValue(value);
     }
 
     // Return the result value of the current top-of-value-stack expression.
     Value getResult() {
+        MOZ_ASSERT(reachable_);
         return valueStack_.back().value();
     }
 
     // Return a reference to the top of the control stack.
     ControlItem& controlItem() {
         return controlStack_.back().controlItem();
     }
+
+    // Return the signature of the top of the control stack.
+    ExprType controlType() {
+        return controlStack_.back().type();
+    }
+
+    // Test whether the control-stack is empty, meaning we've consumed the final
+    // end of the function body.
+    bool controlStackEmpty() const {
+        return controlStack_.empty();
+    }
 };
 
 template <typename Policy>
-inline bool
+bool
 ExprIter<Policy>::typeMismatch(ExprType actual, ExprType expected)
 {
+    MOZ_ASSERT(Validate);
+    MOZ_ASSERT(reachable_);
+
     UniqueChars error(JS_smprintf("type mismatch: expression has type %s but expected %s",
                                   ToCString(actual), ToCString(expected)));
     if (!error)
         return false;
 
     return fail(error.get());
 }
 
 template <typename Policy>
 inline bool
+ExprIter<Policy>::checkType(ValType actual, ValType expected)
+{
+    return checkType(ToExprType(actual), ToExprType(expected));
+}
+
+template <typename Policy>
+inline bool
 ExprIter<Policy>::checkType(ExprType actual, ExprType expected)
 {
+    MOZ_ASSERT(reachable_);
+
     if (!Validate) {
-        MOZ_ASSERT(actual == AnyType || actual == expected, "type mismatch");
+        MOZ_ASSERT(actual == expected, "type mismatch");
         return true;
     }
 
-    if (MOZ_LIKELY(actual == AnyType || actual == expected))
+    if (MOZ_LIKELY(actual == expected))
         return true;
 
     return typeMismatch(actual, expected);
 }
 
 template <typename Policy>
-inline bool
+bool
 ExprIter<Policy>::notYetImplemented(const char* what)
 {
     UniqueChars error(JS_smprintf("not yet implemented: %s", what));
     if (!error)
         return false;
 
     return fail(error.get());
 }
 
 template <typename Policy>
-inline bool
+bool
 ExprIter<Policy>::unrecognizedOpcode(Expr expr)
 {
     UniqueChars error(JS_smprintf("unrecognized opcode: %x", uint32_t(expr)));
     if (!error)
         return false;
 
     return fail(error.get());
 }
 
 template <typename Policy>
-inline bool
+bool
 ExprIter<Policy>::fail(const char* msg)
 {
     return d_.fail(msg);
 }
 
 template <typename Policy>
 inline bool
+ExprIter<Policy>::pushControl(LabelKind kind, ExprType type, bool reachable)
+{
+    return controlStack_.emplaceBack(kind, type, reachable, valueStack_.length());
+}
+
+template <typename Policy>
+inline bool
+ExprIter<Policy>::mergeControl(LabelKind* kind, ExprType* type, Value* value)
+{
+    MOZ_ASSERT(!controlStack_.empty());
+
+    ControlStackEntry<ControlItem>& controlItem = controlStack_.back();
+    *kind = controlItem.kind();
+
+    if (reachable_) {
+        // Unlike branching, exiting a scope via fallthrough does not implicitly
+        // pop excess items on the stack.
+        size_t valueStackStart = controlItem.valueStackStart();
+        size_t valueStackLength = valueStack_.length();
+        MOZ_ASSERT(valueStackLength >= valueStackStart);
+        if (valueStackLength == valueStackStart) {
+            *type = ExprType::Void;
+            if (!checkType(ExprType::Void, controlItem.type()))
+                return false;
+        } else {
+            *type = controlItem.type();
+            if (Validate && valueStackLength - valueStackStart > (IsVoid(*type) ? 0u : 1u))
+                return fail("unused values not explicitly dropped by end of block");
+            if (!topWithType(NonVoidToValType(*type), value))
+                return false;
+        }
+    } else {
+        if (*kind != LabelKind::Loop && controlItem.reachable()) {
+            // There was no fallthrough path, but there was some other reachable
+            // branch to the end.
+            reachable_ = true;
+            *type = controlItem.type();
+            if (!IsVoid(*type)) {
+                if (!push(NonVoidToValType(*type)))
+                    return false;
+            }
+        } else {
+            // No fallthrough and no branch to the end either; we remain
+            // unreachable.
+            *type = ExprType::Void;
+        }
+        if (Output)
+            *value = Value();
+    }
+
+    return true;
+}
+
+template <typename Policy>
+inline bool
+ExprIter<Policy>::popControl(LabelKind* kind, ExprType* type, Value* value)
+{
+    if (!mergeControl(kind, type, value))
+        return false;
+
+    if (*kind == LabelKind::Then) {
+        // A reachable If without an Else. Forbid a result value.
+        if (reachable_) {
+            if (Validate && !IsVoid(*type))
+                return fail("if without else with a result value");
+        }
+        reachable_ = true;
+    }
+
+    controlStack_.popBack();
+
+    if (!reachable_ && !controlStack_.empty())
+        valueStack_.shrinkTo(controlStack_.back().valueStackStart());
+
+    return true;
+}
+
+template <typename Policy>
+inline bool
+ExprIter<Policy>::readExprType(ExprType* type)
+{
+    uint8_t byte;
+    if (!readFixedU8(&byte))
+        return fail("unable to read block signature");
+
+    if (Validate && byte >= uint8_t(ExprType::Limit))
+        return fail("invalid inline type");
+
+    *type = ExprType(byte);
+
+    return true;
+}
+
+template <typename Policy>
+inline bool
 ExprIter<Policy>::readExpr(Expr* expr)
 {
     if (Validate) {
         if (MOZ_UNLIKELY(!d_.readExpr(expr)))
             return fail("unable to read opcode");
     } else {
         *expr = d_.uncheckedReadExpr();
     }
 
     expr_ = *expr;
 
     return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readFunctionStart()
+ExprIter<Policy>::readFunctionStart(ExprType ret)
 {
     MOZ_ASSERT(valueStack_.empty());
     MOZ_ASSERT(controlStack_.empty());
     MOZ_ASSERT(Expr(expr_) == Expr::Limit);
+    MOZ_ASSERT(reachable_);
 
-    return pushControl(LabelKind::Block);
+    return pushControl(LabelKind::Block, ret, false);
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readFunctionEnd(ExprType ret, Value* value)
+ExprIter<Policy>::readFunctionEnd()
 {
-    expr_ = Expr::Limit;
-
     if (Validate) {
-        MOZ_ASSERT(controlStack_.length() > 0);
-        if (controlStack_.length() != 1)
+        if (!controlStack_.empty())
             return fail("unbalanced function body control flow");
     } else {
-        MOZ_ASSERT(controlStack_.length() == 1);
+        MOZ_ASSERT(controlStack_.empty());
     }
 
-    ExprType type;
-    LabelKind kind;
-    if (!popControlAfterCheck(&kind, &type, value))
-        return false;
-
-    MOZ_ASSERT(kind == LabelKind::Block);
-    MOZ_ASSERT(valueStack_.length() == 1);
-
-    if (!IsVoid(ret)) {
-        if (!checkType(type, ret))
-            return false;
-    }
-
+    expr_ = Expr::Limit;
+    valueStack_.clear();
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readReturn(Value* value)
 {
-    ControlStackEntry<ControlItem>& controlItem = controlStack_[0];
-    MOZ_ASSERT(controlItem.kind() == LabelKind::Block);
     MOZ_ASSERT(Classify(expr_) == ExprKind::Return);
 
-    uint32_t arity;
-    if (!readVarU32(&arity))
-        return fail("failed to read return arity");
-    if (Validate && arity > 1)
-        return fail("return arity too big");
+    if (MOZ_LIKELY(reachable_)) {
+        ControlStackEntry<ControlItem>& controlItem = controlStack_[0];
+        MOZ_ASSERT(controlItem.kind() == LabelKind::Block);
 
-    TypeAndValue<Value> tv;
-    if (arity) {
-        if (!pop(&tv))
-            return false;
-    } else {
-        tv = TypeAndValue<Value>(ExprType::Void);
+        controlItem.setReachable();
+
+        if (!IsVoid(controlItem.type())) {
+            if (!popWithType(NonVoidToValType(controlItem.type()), value))
+                return false;
+        }
     }
 
-    infallibleCheckSuccessor(controlItem, tv.type());
-
-    if (!push(AnyType))
-        return false;
-
-    if (Output)
-        *value = tv.value();
-
-    return true;
-}
-
-template <typename Policy>
-inline void
-ExprIter<Policy>::infallibleCheckSuccessor(ControlStackEntry<ControlItem>& controlItem,
-                                     ExprType type)
-{
-    controlItem.type() = Unify(controlItem.type(), type);
-}
-
-template <typename Policy>
-inline bool
-ExprIter<Policy>::checkBranch(uint32_t relativeDepth, ExprType type)
-{
-    // FIXME: Don't allow branching to the function-body block for now.
-    if (Validate && relativeDepth >= controlStack_.length() - 1)
-        return fail("branch depth exceeds current nesting level");
-
-    ControlStackEntry<ControlItem>& controlItem =
-        controlStack_[controlStack_.length() - 1 - relativeDepth];
-
-    if (controlItem.kind() != LabelKind::Loop)
-       infallibleCheckSuccessor(controlItem, type);
-
-    return true;
-}
-
-template <typename Policy>
-inline bool
-ExprIter<Policy>::pushControl(LabelKind kind)
-{
-    size_t length = valueStack_.length();
-
-    // Push a void value at the start of every control region, in case the
-    // region is empty.
-    if (!push(ExprType::Void))
-        return false;
-
-    return controlStack_.emplaceBack(kind, length);
-}
-
-template <typename Policy>
-inline bool
-ExprIter<Policy>::popControl(LabelKind* kind, ExprType* type, Value* value)
-{
-    MOZ_ASSERT(controlStack_.length() > 0);
-    if (controlStack_.length() <= 1)
-        return fail("unbalanced function body control flow");
-
-    return popControlAfterCheck(kind, type, value);
-}
-
-template <typename Policy>
-inline bool
-ExprIter<Policy>::popControlAfterCheck(LabelKind* kind, ExprType* type, Value* value)
-{
-    TypeAndValue<Value> tv;
-    if (!pop(&tv))
-        return false;
-
-    if (Output)
-        *value = tv.value();
-
-    ControlStackEntry<ControlItem> controlItem = controlStack_.popCopy();
-    *kind = controlItem.kind();
-
-    infallibleCheckSuccessor(controlItem, tv.type());
-
-    *type = controlItem.type();
-
-    // Clear out the value stack up to the start of the block/loop.
-    valueStack_.shrinkTo(controlItem.valueStackStart());
-
-    infalliblePush(controlItem.type());
+    enterUnreachableCode();
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readBlock()
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::Block);
 
-    return pushControl(LabelKind::Block);
+    ExprType type = ExprType::Limit;
+    if (!readExprType(&type))
+        return false;
+
+    return pushControl(LabelKind::Block, type, false);
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readLoop()
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::Loop);
 
-    return pushControl(LabelKind::Block) &&
-           pushControl(LabelKind::Loop);
+    ExprType type = ExprType::Limit;
+    if (!readExprType(&type))
+        return false;
+
+    return pushControl(LabelKind::Loop, type, reachable_);
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readIf(Value* condition)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::If);
 
-    if (!popWithType(ExprType::I32, condition))
+    ExprType type = ExprType::Limit;
+    if (!readExprType(&type))
         return false;
 
-    return pushControl(LabelKind::Then);
+    if (MOZ_LIKELY(reachable_)) {
+        if (!popWithType(ValType::I32, condition))
+            return false;
+
+        return pushControl(LabelKind::Then, type, false);
+    }
+
+    return pushControl(LabelKind::UnreachableThen, type, false);
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readElse(ExprType* thenType, Value* thenValue)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::Else);
 
+    // Finish up the then arm.
     ExprType type = ExprType::Limit;
     LabelKind kind;
-    if (!popControl(&kind, &type, thenValue))
+    if (!mergeControl(&kind, &type, thenValue))
         return false;
 
-    if (Validate && kind != LabelKind::Then)
-        return fail("else can only be used within an if");
-
-    // Pop and discard the old then value for now.
-    TypeAndValue<Value> tv;
-    if (!pop(&tv))
-        return false;
-
-    if (!pushControl(LabelKind::Else))
-        return false;
-
-    // Initialize the else block's type with the then block's type, so that
-    // the two get unified.
-    ControlStackEntry<ControlItem>& controlItem = controlStack_.back();
-    controlItem.type() = type;
-
     if (Output)
         *thenType = type;
 
+    // Pop the old then value from the stack.
+    if (!IsVoid(type))
+        valueStack_.popBack();
+
+    if (Validate && kind != LabelKind::Then && kind != LabelKind::UnreachableThen)
+        return fail("else can only be used within an if");
+
+    // Switch to the else arm.
+    controlStack_.back().switchToElse(reachable_);
+
+    reachable_ = kind != LabelKind::UnreachableThen;
+
+    MOZ_ASSERT(valueStack_.length() == controlStack_.back().valueStackStart());
+
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readEnd(LabelKind* kind, ExprType* type, Value* value)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::End);
 
     LabelKind validateKind = static_cast<LabelKind>(-1);
-    ExprType validateType;
+    ExprType validateType = ExprType::Limit;
     if (!popControl(&validateKind, &validateType, value))
         return false;
 
-    switch (validateKind) {
-      case LabelKind::Block:
-        break;
-      case LabelKind::Loop: {
-        // Note: Propose a spec change: loops don't implicitly have an end label.
-
-        if (Output)
-            setResult(*value);
-
-        LabelKind blockKind;
-        if (!popControl(&blockKind, &validateType, value))
-            return false;
-
-        MOZ_ASSERT(blockKind == LabelKind::Block);
-        break;
-      }
-      case LabelKind::Then:
-        valueStack_.back() = TypeAndValue<Value>(ExprType::Void);
-        if (Output)
-            *type = ExprType::Void;
-        break;
-      case LabelKind::Else:
-        break;
-    }
-
     if (Output) {
         *kind = validateKind;
         *type = validateType;
     }
 
     return true;
 }
 
 template <typename Policy>
 inline bool
+ExprIter<Policy>::checkBrValue(uint32_t relativeDepth, ExprType* type, Value* value)
+{
+    if (MOZ_LIKELY(reachable_)) {
+        ControlStackEntry<ControlItem>* controlItem = nullptr;
+        if (!getControl(relativeDepth, &controlItem))
+            return false;
+
+        if (controlItem->kind() != LabelKind::Loop) {
+            controlItem->setReachable();
+
+            ExprType expectedType = controlItem->type();
+            if (Output)
+                *type = expectedType;
+
+            if (!IsVoid(expectedType))
+                return topWithType(NonVoidToValType(expectedType), value);
+        }
+    }
+
+    if (Output) {
+        *type = ExprType::Void;
+        *value = Value();
+    }
+
+    return true;
+}
+
+template <typename Policy>
+inline bool
 ExprIter<Policy>::readBr(uint32_t* relativeDepth, ExprType* type, Value* value)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::Br);
 
-    uint32_t arity;
-    if (!readVarU32(&arity))
-        return fail("unable to read br arity");
-    if (Validate && arity > 1)
-        return fail("br arity too big");
-
     uint32_t validateRelativeDepth;
     if (!readVarU32(&validateRelativeDepth))
         return fail("unable to read br depth");
 
-    TypeAndValue<Value> tv;
-    if (arity) {
-        if (!pop(&tv))
+    if (!checkBrValue(validateRelativeDepth, type, value))
+        return false;
+
+    if (Output)
+        *relativeDepth = validateRelativeDepth;
+
+    enterUnreachableCode();
+    return true;
+}
+
+template <typename Policy>
+inline bool
+ExprIter<Policy>::checkBrIfValues(uint32_t relativeDepth, Value* condition,
+                                  ExprType* type, Value* value)
+{
+    if (MOZ_LIKELY(reachable_)) {
+        if (!popWithType(ValType::I32, condition))
             return false;
-    } else {
-        tv = TypeAndValue<Value>(ExprType::Void);
+
+        ControlStackEntry<ControlItem>* controlItem = nullptr;
+        if (!getControl(relativeDepth, &controlItem))
+            return false;
+
+        if (controlItem->kind() != LabelKind::Loop) {
+            controlItem->setReachable();
+
+            ExprType expectedType = controlItem->type();
+            if (Output)
+                *type = expectedType;
+
+            if (!IsVoid(expectedType))
+                return topWithType(NonVoidToValType(expectedType), value);
+        }
     }
 
-    if (!checkBranch(validateRelativeDepth, tv.type()))
-        return false;
-
-    if (!push(AnyType))
-        return false;
-
     if (Output) {
-        *relativeDepth = validateRelativeDepth;
-        *type = tv.type();
-        *value = tv.value();
+        *type = ExprType::Void;
+        *value = Value();
     }
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readBrIf(uint32_t* relativeDepth, ExprType* type, Value* value, Value* condition)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::BrIf);
 
-    uint32_t arity;
-    if (!readVarU32(&arity))
-        return fail("unable to read br_if arity");
-    if (Validate && arity > 1)
-        return fail("br_if arity too big");
-
     uint32_t validateRelativeDepth;
     if (!readVarU32(&validateRelativeDepth))
         return fail("unable to read br_if depth");
 
-    if (!popWithType(ExprType::I32, condition))
+    if (!checkBrIfValues(validateRelativeDepth, condition, type, value))
         return false;
 
-    TypeAndValue<Value> tv;
-    if (arity) {
-        if (!top(&tv))
-            return false;
-    } else {
-        tv = TypeAndValue<Value>(ExprType::Void);
-        if (!push(tv))
-            return false;
-    }
-
-    if (!checkBranch(validateRelativeDepth, tv.type()))
-        return false;
-
-    if (Output) {
+    if (Output)
         *relativeDepth = validateRelativeDepth;
-        *type = tv.type();
-        *value = tv.value();
-    }
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readBrTable(uint32_t* tableLength, ExprType* type,
                               Value* value, Value* index)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::BrTable);
 
-    if (!popWithType(ExprType::I32, index))
-        return false;
+    if (!readVarU32(tableLength))
+        return fail("unable to read br_table table length");
 
-    uint32_t arity;
-    if (!readVarU32(&arity))
-        return fail("unable to read br_table arity");
-    if (Validate && arity > 1)
-        return fail("br_table arity too big");
-
-    TypeAndValue<Value> tv;
-    if (arity) {
-        if (!top(&tv))
-            return false;
-    } else {
-        tv = TypeAndValue<Value>(ExprType::Void);
-        if (!push(tv))
+    if (MOZ_LIKELY(reachable_)) {
+        if (!popWithType(ValType::I32, index))
             return false;
     }
-    *type = tv.type();
+
+    // Set *type to indicate that we don't know the type yet.
+    *type = ExprType::Limit;
     if (Output)
-        *value = tv.value();
-
-    if (!readVarU32(tableLength))
-        return fail("unable to read br_table table length");
+        *value = Value();
 
     return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readBrTableEntry(ExprType type, uint32_t* depth)
+ExprIter<Policy>::readBrTableEntry(ExprType* type, Value* value, uint32_t* depth)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::BrTable);
 
-    return readFixedU32(depth) &&
-           checkBranch(*depth, type);
+    if (!readVarU32(depth))
+        return false;
+
+    if (MOZ_LIKELY(reachable_)) {
+        ControlStackEntry<ControlItem>* controlItem = nullptr;
+        if (!getControl(*depth, &controlItem))
+            return false;
+
+        if (controlItem->kind() != LabelKind::Loop) {
+            controlItem->setReachable();
+
+            // If we've already seen one label, we know the type and can check
+            // that the type for the current label matches it.
+            ExprType knownType = *type;
+            if (knownType != ExprType::Limit)
+                return checkType(knownType, controlItem->type());
+
+            // This is the first label; record the type and the value now.
+            ExprType expectedType = controlItem->type();
+            if (!IsVoid(expectedType)) {
+                *type = expectedType;
+                return popWithType(NonVoidToValType(expectedType), value);
+            }
+        }
+    }
+
+    *type = ExprType::Void;
+    if (Output)
+        *value = Value();
+    return true;
+}
+
+template <typename Policy>
+inline bool
+ExprIter<Policy>::readBrTableDefault(ExprType* type, Value* value, uint32_t* depth)
+{
+    if (!readBrTableEntry(type, value, depth))
+        return false;
+
+    MOZ_ASSERT(!reachable_ || *type != ExprType::Limit);
+
+    enterUnreachableCode();
+    return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readUnreachable()
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::Unreachable);
 
-    return push(AnyType);
+    enterUnreachableCode();
+    return true;
+}
+
+template <typename Policy>
+inline bool
+ExprIter<Policy>::readDrop()
+{
+    MOZ_ASSERT(Classify(expr_) == ExprKind::Drop);
+
+    if (!pop())
+        return false;
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readUnary(ValType operandType, Value* input)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::Unary);
 
-    if (!popWithType(ToExprType(operandType), input))
+    if (!popWithType(operandType, input))
         return false;
 
-    infalliblePush(ToExprType(operandType));
+    infalliblePush(operandType);
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readConversion(ValType operandType, ValType resultType, Value* input)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::Conversion);
 
-    if (!popWithType(ToExprType(operandType), input))
+    if (!popWithType(operandType, input))
         return false;
 
-    infalliblePush(ToExprType(resultType));
+    infalliblePush(resultType);
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readBinary(ValType operandType, Value* lhs, Value* rhs)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::Binary);
 
-    if (!popWithType(ToExprType(operandType), rhs))
+    if (!popWithType(operandType, rhs))
         return false;
 
-    if (!popWithType(ToExprType(operandType), lhs))
+    if (!popWithType(operandType, lhs))
         return false;
 
-    infalliblePush(ToExprType(operandType));
+    infalliblePush(operandType);
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readComparison(ValType operandType, Value* lhs, Value* rhs)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::Comparison);
 
-    if (!popWithType(ToExprType(operandType), rhs))
+    if (!popWithType(operandType, rhs))
         return false;
 
-    if (!popWithType(ToExprType(operandType), lhs))
+    if (!popWithType(operandType, lhs))
         return false;
 
-    infalliblePush(ExprType::I32);
+    infalliblePush(ValType::I32);
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readLinearMemoryAddress(uint32_t byteSize, LinearMemoryAddress<Value>* addr)
 {
-    Value unused;
-    if (!popWithType(ExprType::I32, Output ? &addr->base : &unused))
-        return false;
-
     uint8_t alignLog2;
     if (!readFixedU8(&alignLog2))
         return fail("unable to read load alignment");
-    if (Validate && (alignLog2 >= 32 || (uint32_t(1) << alignLog2) > byteSize))
-        return fail("greater than natural alignment");
-    if (Output)
-        addr->align = uint32_t(1) << alignLog2;
 
     uint32_t unusedOffset;
     if (!readVarU32(Output ? &addr->offset : &unusedOffset))
         return fail("unable to read load offset");
 
+    if (Validate && (alignLog2 >= 32 || (uint32_t(1) << alignLog2) > byteSize))
+        return fail("greater than natural alignment");
+
+    Value unused;
+    if (!popWithType(ValType::I32, Output ? &addr->base : &unused))
+        return false;
+
+    if (Output)
+        addr->align = uint32_t(1) << alignLog2;
+
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readLoad(ValType resultType, uint32_t byteSize,
                            LinearMemoryAddress<Value>* addr)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::Load);
 
     if (!readLinearMemoryAddress(byteSize, addr))
         return false;
 
-    infalliblePush(ToExprType(resultType));
+    infalliblePush(resultType);
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readStore(ValType resultType, uint32_t byteSize,
                             LinearMemoryAddress<Value>* addr, Value* value)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::Store);
 
-    if (!popWithType(ToExprType(resultType), value))
+    if (!popWithType(resultType, value))
         return false;
 
     if (!readLinearMemoryAddress(byteSize, addr))
         return false;
 
-    infalliblePush(TypeAndValue<Value>(ToExprType(resultType), Output ? *value : Value()));
-
     return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readNullary(ExprType retType)
+ExprIter<Policy>::readTeeStore(ValType resultType, uint32_t byteSize,
+                               LinearMemoryAddress<Value>* addr, Value* value)
 {
-    MOZ_ASSERT(Classify(expr_) == ExprKind::Nullary);
+    MOZ_ASSERT(Classify(expr_) == ExprKind::TeeStore);
+
+    if (!popWithType(resultType, value))
+        return false;
 
-    return push(retType);
+    if (!readLinearMemoryAddress(byteSize, addr))
+        return false;
+
+    infalliblePush(TypeAndValue<Value>(resultType, Output ? *value : Value()));
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readSelect(ExprType* type, Value* trueValue, Value* falseValue, Value* condition)
+ExprIter<Policy>::readNop()
+{
+    MOZ_ASSERT(Classify(expr_) == ExprKind::Nop);
+
+    return true;
+}
+
+template <typename Policy>
+inline bool
+ExprIter<Policy>::readNullary(ValType retType)
+{
+    MOZ_ASSERT(Classify(expr_) == ExprKind::Nullary);
+
+    if (!push(retType))
+        return false;
+
+    return true;
+}
+
+template <typename Policy>
+inline bool
+ExprIter<Policy>::readSelect(ValType* type, Value* trueValue, Value* falseValue, Value* condition)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::Select);
 
-    if (!popWithType(ExprType::I32, condition))
+    if (!popWithType(ValType::I32, condition))
         return false;
 
     TypeAndValue<Value> false_;
     if (!pop(&false_))
         return false;
 
     TypeAndValue<Value> true_;
     if (!pop(&true_))
         return false;
 
-    ExprType resultType = Unify(true_.type(), false_.type());
+    ValType resultType = true_.type();
+    if (Validate && resultType != false_.type())
+        return fail("select operand types must match");
+
     infalliblePush(resultType);
 
     if (Output) {
         *type = resultType;
         *trueValue = true_.value();
         *falseValue = false_.value();
     }
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readGetLocal(const ValTypeVector& locals, uint32_t* id)
 {
-    MOZ_ASSERT(Classify(expr_) == ExprKind::GetVar);
+    MOZ_ASSERT(Classify(expr_) == ExprKind::GetLocal);
 
     uint32_t validateId;
     if (!readVarU32(&validateId))
         return false;
 
     if (Validate && validateId >= locals.length())
         return fail("get_local index out of range");
 
-    if (!push(ToExprType(locals[validateId])))
+    if (!push(locals[validateId]))
         return false;
 
     if (Output)
         *id = validateId;
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readSetLocal(const ValTypeVector& locals, uint32_t* id, Value* value)
 {
-    MOZ_ASSERT(Classify(expr_) == ExprKind::SetVar);
+    MOZ_ASSERT(Classify(expr_) == ExprKind::SetLocal);
 
     uint32_t validateId;
     if (!readVarU32(&validateId))
         return false;
 
     if (Validate && validateId >= locals.length())
         return fail("set_local index out of range");
 
-    if (!topWithType(ToExprType(locals[validateId]), value))
+    if (!popWithType(locals[validateId], value))
+        return false;
+
+    if (Output)
+        *id = validateId;
+
+    return true;
+}
+
+template <typename Policy>
+inline bool
+ExprIter<Policy>::readTeeLocal(const ValTypeVector& locals, uint32_t* id, Value* value)
+{
+    MOZ_ASSERT(Classify(expr_) == ExprKind::TeeLocal);
+
+    uint32_t validateId;
+    if (!readVarU32(&validateId))
+        return false;
+
+    if (Validate && validateId >= locals.length())
+        return fail("set_local index out of range");
+
+    if (!topWithType(locals[validateId], value))
         return false;
 
     if (Output)
         *id = validateId;
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readGetGlobal(const GlobalDescVector& globals, uint32_t* id)
 {
-    MOZ_ASSERT(Classify(expr_) == ExprKind::GetVar);
+    MOZ_ASSERT(Classify(expr_) == ExprKind::GetGlobal);
 
     uint32_t validateId;
     if (!readVarU32(&validateId))
         return false;
 
     if (Validate && validateId >= globals.length())
         return fail("get_global index out of range");
 
-    if (!push(ToExprType(globals[validateId].type())))
+    if (!push(globals[validateId].type()))
         return false;
 
     if (Output)
         *id = validateId;
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readSetGlobal(const GlobalDescVector& globals, uint32_t* id, Value* value)
 {
-    MOZ_ASSERT(Classify(expr_) == ExprKind::SetVar);
+    MOZ_ASSERT(Classify(expr_) == ExprKind::SetGlobal);
 
     uint32_t validateId;
     if (!readVarU32(&validateId))
         return false;
 
     if (Validate && validateId >= globals.length())
         return fail("set_global index out of range");
 
     if (Validate && !globals[validateId].isMutable())
         return fail("can't write an immutable global");
 
-    if (!topWithType(ToExprType(globals[validateId].type()), value))
+    if (!popWithType(globals[validateId].type(), value))
+        return false;
+
+    if (Output)
+        *id = validateId;
+
+    return true;
+}
+
+template <typename Policy>
+inline bool
+ExprIter<Policy>::readTeeGlobal(const GlobalDescVector& globals, uint32_t* id, Value* value)
+{
+    MOZ_ASSERT(Classify(expr_) == ExprKind::TeeGlobal);
+
+    uint32_t validateId;
+    if (!readVarU32(&validateId))
+        return false;
+
+    if (Validate && validateId >= globals.length())
+        return fail("set_global index out of range");
+
+    if (Validate && !globals[validateId].isMutable())
+        return fail("can't write an immutable global");
+
+    if (!topWithType(globals[validateId].type(), value))
         return false;
 
     if (Output)
         *id = validateId;
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readI32Const(int32_t* i32)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::I32);
 
     int32_t unused;
-    return readVarS32(Output ? i32 : &unused) &&
-           push(ExprType::I32);
+    if (!readVarS32(Output ? i32 : &unused))
+        return false;
+
+    if (!push(ValType::I32))
+       return false;
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readI64Const(int64_t* i64)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::I64);
 
     int64_t unused;
-    return readVarS64(Output ? i64 : &unused) &&
-           push(ExprType::I64);
+    if (!readVarS64(Output ? i64 : &unused))
+        return false;
+
+    if (!push(ValType::I64))
+        return false;
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readF32Const(RawF32* f32)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::F32);
 
     RawF32 unused;
     if (!readFixedF32(Output ? f32 : &unused))
         return false;
 
-    return push(ExprType::F32);
+    if (!push(ValType::F32))
+        return false;
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readF64Const(RawF64* f64)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::F64);
 
     RawF64 unused;
     if (!readFixedF64(Output ? f64 : &unused))
+       return false;
+
+    if (!push(ValType::F64))
         return false;
 
-    return push(ExprType::F64);
+    return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readI8x16Const(I8x16* i8x16)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::I8x16);
 
     I8x16 unused;
-    return readFixedI8x16(Output ? i8x16 : &unused) &&
-           push(ExprType::I8x16);
+    if (!readFixedI8x16(Output ? i8x16 : &unused))
+        return false;
+
+    if (!push(ValType::I8x16))
+        return false;
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readI16x8Const(I16x8* i16x8)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::I16x8);
 
     I16x8 unused;
-    return readFixedI16x8(Output ? i16x8 : &unused) &&
-           push(ExprType::I16x8);
+    if (!readFixedI16x8(Output ? i16x8 : &unused))
+        return false;
+
+    if (!push(ValType::I16x8))
+        return false;
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readI32x4Const(I32x4* i32x4)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::I32x4);
 
     I32x4 unused;
-    return readFixedI32x4(Output ? i32x4 : &unused) &&
-           push(ExprType::I32x4);
+    if (!readFixedI32x4(Output ? i32x4 : &unused))
+        return false;
+
+    if (!push(ValType::I32x4))
+        return false;
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readF32x4Const(F32x4* f32x4)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::F32x4);
 
     F32x4 unused;
-    return readFixedF32x4(Output ? f32x4 : &unused) &&
-           push(ExprType::F32x4);
+    if (!readFixedF32x4(Output ? f32x4 : &unused))
+        return false;
+
+    if (!push(ValType::F32x4))
+        return false;
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readB8x16Const(I8x16* i8x16)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::B8x16);
 
     I8x16 unused;
-    return readFixedI8x16(Output ? i8x16 : &unused) &&
-           push(ExprType::B8x16);
+    if (!readFixedI8x16(Output ? i8x16 : &unused))
+        return false;
+
+    if (!push(ValType::B8x16))
+        return false;
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readB16x8Const(I16x8* i16x8)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::B16x8);
 
     I16x8 unused;
-    return readFixedI16x8(Output ? i16x8 : &unused) &&
-           push(ExprType::B16x8);
+    if (!readFixedI16x8(Output ? i16x8 : &unused))
+        return false;
+
+    if (!push(ValType::B16x8))
+        return false;
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readB32x4Const(I32x4* i32x4)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::B32x4);
 
     I32x4 unused;
-    return readFixedI32x4(Output ? i32x4 : &unused) &&
-           push(ExprType::B32x4);
+    if (!readFixedI32x4(Output ? i32x4 : &unused))
+        return false;
+
+    if (!push(ValType::B32x4))
+        return false;
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readCall(uint32_t* calleeIndex, uint32_t* arity)
+ExprIter<Policy>::readCall(uint32_t* calleeIndex)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::Call);
 
-    if (!readVarU32(arity))
-        return fail("unable to read call arity");
-
     if (!readVarU32(calleeIndex))
         return fail("unable to read call function index");
 
     return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readCallIndirect(uint32_t* sigIndex, uint32_t* arity)
+ExprIter<Policy>::readCallIndirect(uint32_t* sigIndex, Value* callee)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::CallIndirect);
 
-    if (!readVarU32(arity))
-        return fail("unable to read call_indirect arity");
+    if (!readVarU32(sigIndex))
+        return fail("unable to read call_indirect signature index");
+
+    if (reachable_) {
+        if (!popWithType(ValType::I32, callee))
+            return false;
+    }
+
+    return true;
+}
+
+template <typename Policy>
+inline bool
+ExprIter<Policy>::readOldCallIndirect(uint32_t* sigIndex)
+{
+    MOZ_ASSERT(Classify(expr_) == ExprKind::OldCallIndirect);
 
     if (!readVarU32(sigIndex))
         return fail("unable to read call_indirect signature index");
 
     return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readCallImport(uint32_t* importIndex, uint32_t* arity)
+ExprIter<Policy>::readCallImport(uint32_t* importIndex)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::CallImport);
 
-    if (!readVarU32(arity))
-        return fail("unable to read call_import arity");
-
     if (!readVarU32(importIndex))
         return fail("unable to read call_import import index");
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readCallArg(ValType type, uint32_t numArgs, uint32_t argIndex, Value* arg)
 {
+    MOZ_ASSERT(reachable_);
+
     TypeAndValue<Value> tv;
+
     if (!peek(numArgs - argIndex, &tv))
         return false;
-    if (!checkType(tv.type(), ToExprType(type)))
+    if (!checkType(tv.type(), type))
         return false;
 
     if (Output)
         *arg = tv.value();
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readCallArgsEnd(uint32_t numArgs)
 {
+    MOZ_ASSERT(reachable_);
     MOZ_ASSERT(numArgs <= valueStack_.length());
 
     valueStack_.shrinkBy(numArgs);
+
     return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readCallIndirectCallee(Value* callee)
+ExprIter<Policy>::readOldCallIndirectCallee(Value* callee)
 {
-    return popWithType(ExprType::I32, callee);
+    MOZ_ASSERT(Classify(expr_) == ExprKind::OldCallIndirect);
+    MOZ_ASSERT(reachable_);
+
+    if (!popWithType(ValType::I32, callee))
+        return false;
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readCallReturn(ExprType ret)
 {
-    return push(ret);
+    MOZ_ASSERT(reachable_);
+
+    if (!IsVoid(ret)) {
+        if (!push(NonVoidToValType(ret)))
+            return false;
+    }
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readAtomicLoad(LinearMemoryAddress<Value>* addr, Scalar::Type* viewType)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::AtomicLoad);
 
     Scalar::Type validateViewType;
     if (!readAtomicViewType(&validateViewType))
         return false;
 
     uint32_t byteSize = Scalar::byteSize(validateViewType);
     if (!readLinearMemoryAddress(byteSize, addr))
         return false;
 
-    infalliblePush(ExprType::I32);
+    infalliblePush(ValType::I32);
 
     if (Output)
         *viewType = validateViewType;
 
     return true;
 }
 
 template <typename Policy>
@@ -1487,20 +1822,20 @@ ExprIter<Policy>::readAtomicStore(Linear
     Scalar::Type validateViewType;
     if (!readAtomicViewType(&validateViewType))
         return false;
 
     uint32_t byteSize = Scalar::byteSize(validateViewType);
     if (!readLinearMemoryAddress(byteSize, addr))
         return false;
 
-    if (!popWithType(ExprType::I32, value))
+    if (!popWithType(ValType::I32, value))
         return false;
 
-    infalliblePush(ExprType::I32);
+    infalliblePush(ValType::I32);
 
     if (Output)
         *viewType = validateViewType;
 
     return true;
 }
 
 template <typename Policy>
@@ -1516,20 +1851,20 @@ ExprIter<Policy>::readAtomicBinOp(Linear
 
     if (!readAtomicBinOpOp(op))
         return false;
 
     uint32_t byteSize = Scalar::byteSize(validateViewType);
     if (!readLinearMemoryAddress(byteSize, addr))
         return false;
 
-    if (!popWithType(ExprType::I32, value))
+    if (!popWithType(ValType::I32, value))
         return false;
 
-    infalliblePush(ExprType::I32);
+    infalliblePush(ValType::I32);
 
     if (Output)
         *viewType = validateViewType;
 
     return true;
 }
 
 template <typename Policy>
@@ -1543,23 +1878,23 @@ ExprIter<Policy>::readAtomicCompareExcha
     Scalar::Type validateViewType;
     if (!readAtomicViewType(&validateViewType))
         return false;
 
     uint32_t byteSize = Scalar::byteSize(validateViewType);
     if (!readLinearMemoryAddress(byteSize, addr))
         return false;
 
-    if (!popWithType(ExprType::I32, newValue))
+    if (!popWithType(ValType::I32, newValue))
         return false;
 
-    if (!popWithType(ExprType::I32, oldValue))
+    if (!popWithType(ValType::I32, oldValue))
         return false;
 
-    infalliblePush(ExprType::I32);
+    infalliblePush(ValType::I32);
 
     if (Output)
         *viewType = validateViewType;
 
     return true;
 }
 
 template <typename Policy>
@@ -1573,250 +1908,259 @@ ExprIter<Policy>::readAtomicExchange(Lin
     Scalar::Type validateViewType;
     if (!readAtomicViewType(&validateViewType))
         return false;
 
     uint32_t byteSize = Scalar::byteSize(validateViewType);
     if (!readLinearMemoryAddress(byteSize, addr))
         return false;
 
-    if (!popWithType(ExprType::I32, value))
+    if (!popWithType(ValType::I32, value))
         return false;
 
-    infalliblePush(ExprType::I32);
+    infalliblePush(ValType::I32);
 
     if (Output)
         *viewType = validateViewType;
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readSimdComparison(ValType simdType, Value* lhs, Value* rhs)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::SimdComparison);
 
-    if (!popWithType(ToExprType(simdType), rhs))
+    if (!popWithType(simdType, rhs))
         return false;
 
-    if (!popWithType(ToExprType(simdType), lhs))
+    if (!popWithType(simdType, lhs))
         return false;
 
-    infalliblePush(ToExprType(SimdBoolType(simdType)));
+    infalliblePush(SimdBoolType(simdType));
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readSimdShiftByScalar(ValType simdType, Value* lhs, Value* rhs)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::SimdShiftByScalar);
 
-    if (!popWithType(ExprType::I32, rhs))
+    if (!popWithType(ValType::I32, rhs))
         return false;
 
-    if (!popWithType(ToExprType(simdType), lhs))
+    if (!popWithType(simdType, lhs))
         return false;
 
-    infalliblePush(ToExprType(simdType));
+    infalliblePush(simdType);
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readSimdBooleanReduction(ValType simdType, Value* input)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::SimdBooleanReduction);
 
-    if (!popWithType(ToExprType(simdType), input))
+    if (!popWithType(simdType, input))
         return false;
 
-    infalliblePush(ExprType::I32);
+    infalliblePush(ValType::I32);
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readExtractLane(ValType simdType, uint8_t* lane, Value* vector)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::ExtractLane);
 
     uint32_t laneBits;
     if (!readVarU32(&laneBits))
         return false;
+
     if (Validate && laneBits >= NumSimdElements(simdType))
         return fail("simd lane out of bounds for simd type");
+
+    if (!popWithType(simdType, vector))
+        return false;
+
+    infalliblePush(SimdElementType(simdType));
+
     if (Output)
         *lane = uint8_t(laneBits);
 
-    if (!popWithType(ToExprType(simdType), vector))
-        return false;
-
-    infalliblePush(ToExprType(SimdElementType(simdType)));
-
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readReplaceLane(ValType simdType, uint8_t* lane, Value* vector, Value* scalar)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::ReplaceLane);
 
     uint32_t laneBits;
     if (!readVarU32(&laneBits))
         return false;
+
     if (Validate && laneBits >= NumSimdElements(simdType))
         return fail("simd lane out of bounds for simd type");
+
+    if (!popWithType(SimdElementType(simdType), scalar))
+        return false;
+
+    if (!popWithType(simdType, vector))
+        return false;
+
+    infalliblePush(simdType);
+
     if (Output)
         *lane = uint8_t(laneBits);
 
-    if (!popWithType(ToExprType(SimdElementType(simdType)), scalar))
-        return false;
-
-    if (!popWithType(ToExprType(simdType), vector))
-        return false;
-
-    infalliblePush(ToExprType(simdType));
-
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readSplat(ValType simdType, Value* scalar)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::Splat);
 
-    if (!popWithType(ToExprType(SimdElementType(simdType)), scalar))
+    if (!popWithType(SimdElementType(simdType), scalar))
         return false;
 
-    infalliblePush(ToExprType(simdType));
+    infalliblePush(simdType);
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readSwizzle(ValType simdType, uint8_t (* lanes)[16], Value* vector)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::Swizzle);
 
     uint32_t numSimdLanes = NumSimdElements(simdType);
     MOZ_ASSERT(numSimdLanes <= mozilla::ArrayLength(*lanes));
     for (uint32_t i = 0; i < numSimdLanes; ++i) {
         uint8_t validateLane;
         if (!readFixedU8(Output ? &(*lanes)[i] : &validateLane))
-            return false;
+            return fail("unable to read swizzle lane");
         if (Validate && (Output ? (*lanes)[i] : validateLane) >= numSimdLanes)
             return fail("swizzle index out of bounds");
     }
 
-    if (!popWithType(ToExprType(simdType), vector))
+    if (!popWithType(simdType, vector))
         return false;
 
-    infalliblePush(ToExprType(simdType));
+    infalliblePush(simdType);
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readShuffle(ValType simdType, uint8_t (* lanes)[16], Value* lhs, Value* rhs)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::Shuffle);
 
     uint32_t numSimdLanes = NumSimdElements(simdType);
     MOZ_ASSERT(numSimdLanes <= mozilla::ArrayLength(*lanes));
     for (uint32_t i = 0; i < numSimdLanes; ++i) {
         uint8_t validateLane;
         if (!readFixedU8(Output ? &(*lanes)[i] : &validateLane))
-            return false;
+            return fail("unable to read shuffle lane");
         if (Validate && (Output ? (*lanes)[i] : validateLane) >= numSimdLanes * 2)
             return fail("shuffle index out of bounds");
     }
 
-    if (!popWithType(ToExprType(simdType), rhs))
+    if (!popWithType(simdType, rhs))
         return false;
 
-    if (!popWithType(ToExprType(simdType), lhs))
+    if (!popWithType(simdType, lhs))
         return false;
 
-    infalliblePush(ToExprType(simdType));
+    infalliblePush(simdType);
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readSimdSelect(ValType simdType, Value* trueValue, Value* falseValue,
                                  Value* condition)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::SimdSelect);
 
-    if (!popWithType(ToExprType(simdType), falseValue))
+    if (!popWithType(simdType, falseValue))
         return false;
-    if (!popWithType(ToExprType(simdType), trueValue))
+    if (!popWithType(simdType, trueValue))
         return false;
-    if (!popWithType(ToExprType(SimdBoolType(simdType)), condition))
+    if (!popWithType(SimdBoolType(simdType), condition))
         return false;
 
-    infalliblePush(ToExprType(simdType));
+    infalliblePush(simdType);
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readSimdCtor()
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::SimdCtor);
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readSimdCtorArg(ValType elementType, uint32_t numElements, uint32_t index,
-                                      Value* arg)
+                                  Value* arg)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::SimdCtor);
+    MOZ_ASSERT(numElements > 0);
 
     TypeAndValue<Value> tv;
+
     if (!peek(numElements - index, &tv))
         return false;
-    if (!checkType(tv.type(), ToExprType(elementType)))
+    if (!checkType(tv.type(), elementType))
         return false;
 
     *arg = tv.value();
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readSimdCtorArgsEnd(uint32_t numElements)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::SimdCtor);
     MOZ_ASSERT(numElements <= valueStack_.length());
 
     valueStack_.shrinkBy(numElements);
+
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readSimdCtorReturn(ValType simdType)
 {
     MOZ_ASSERT(Classify(expr_) == ExprKind::SimdCtor);
 
-    return push(ToExprType(simdType));
+    infalliblePush(simdType);
+
+    return true;
 }
 
 } // namespace wasm
 } // namespace js
 
 namespace mozilla {
 
 // Specialize IsPod for the Nothing specializations.
--- a/js/src/asmjs/WasmBinaryToAST.cpp
+++ b/js/src/asmjs/WasmBinaryToAST.cpp
@@ -17,16 +17,18 @@
  */
 
 #include "asmjs/WasmBinaryToAST.h"
 
 #include "mozilla/CheckedInt.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/Sprintf.h"
 
+#include "jscntxt.h"
+
 #include "asmjs/WasmBinaryIterator.h"
 
 using namespace js;
 using namespace js::wasm;
 
 using mozilla::CheckedInt;
 using mozilla::FloorLog2;
 
@@ -35,102 +37,158 @@ enum AstDecodeTerminationKind
     Unknown,
     End,
     Else
 };
 
 struct AstDecodeStackItem
 {
     AstExpr* expr;
-    union {
-      uint32_t popped;
-      AstDecodeTerminationKind terminationKind;
-    };
+    AstDecodeTerminationKind terminationKind;
+    ExprType type;
 
-    explicit AstDecodeStackItem(): expr(nullptr), terminationKind(AstDecodeTerminationKind::Unknown) {}
-    explicit AstDecodeStackItem(AstDecodeTerminationKind terminationKind): expr(nullptr), terminationKind(terminationKind) {}
-    explicit AstDecodeStackItem(AstExpr* expr): expr(expr), popped(0) {}
-    explicit AstDecodeStackItem(AstExpr* expr, uint32_t popped): expr(expr), popped(popped) {}
+    explicit AstDecodeStackItem()
+        : expr(nullptr),
+          terminationKind(AstDecodeTerminationKind::Unknown),
+          type(ExprType::Limit) {}
+    explicit AstDecodeStackItem(AstDecodeTerminationKind terminationKind,
+                                ExprType type)
+        : expr(nullptr), terminationKind(terminationKind), type(type) {}
+    explicit AstDecodeStackItem(AstExpr* expr)
+        : expr(expr),
+          terminationKind(AstDecodeTerminationKind::Unknown),
+          type(ExprType::Limit) {}
 };
 
+// We don't define a Value type because ExprIter doesn't push void values, which
+// we actually need here because we're building an AST, so we maintain our own
+// stack.
 struct AstDecodePolicy : ExprIterPolicy
 {
+    // Enable validation because we can be called from wasmBinaryToText on bytes
+    // which are not necessarily valid, and we shouldn't run the decoder in
+    // non-validating mode on invalid code.
+    static const bool Validate = true;
+
     static const bool Output = true;
-    typedef AstDecodeStackItem Value;
 };
 
 typedef ExprIter<AstDecodePolicy> AstDecodeExprIter;
 
 class AstDecodeContext
 {
   public:
     typedef AstVector<uint32_t> AstIndexVector;
+    typedef AstVector<AstDecodeStackItem> AstDecodeStack;
+    typedef AstVector<uint32_t> DepthStack;
 
     JSContext* cx;
     LifoAlloc& lifo;
     Decoder& d;
     bool generateNames;
 
   private:
     AstModule& module_;
     AstIndexVector funcSigs_;
     AstDecodeExprIter *iter_;
+    AstDecodeStack exprs_;
+    DepthStack depths_;
     const ValTypeVector* locals_;
     AstNameVector blockLabels_;
     uint32_t currentLabelIndex_;
+    ExprType retType_;
 
   public:
     AstDecodeContext(JSContext* cx, LifoAlloc& lifo, Decoder& d, AstModule& module, bool generateNames)
      : cx(cx),
        lifo(lifo),
        d(d),
        generateNames(generateNames),
        module_(module),
        funcSigs_(lifo),
        iter_(nullptr),
+       exprs_(lifo),
+       depths_(lifo),
        locals_(nullptr),
        blockLabels_(lifo),
-       currentLabelIndex_(0)
+       currentLabelIndex_(0),
+       retType_(ExprType::Limit)
     {}
 
     AstModule& module() { return module_; }
     AstIndexVector& funcSigs() { return funcSigs_; }
     AstDecodeExprIter& iter() { return *iter_; }
+    AstDecodeStack& exprs() { return exprs_; }
+    DepthStack& depths() { return depths_; }
     const ValTypeVector& locals() { return *locals_; }
     AstNameVector& blockLabels() { return blockLabels_; }
+    ExprType retType() const { return retType_; }
 
-    void startFunction(AstDecodeExprIter *iter, const ValTypeVector* locals)
+    void popBack() { return exprs().popBack(); }
+    AstDecodeStackItem popCopy() { return exprs().popCopy(); }
+    AstDecodeStackItem& top() { return exprs().back(); }
+    MOZ_MUST_USE bool push(AstDecodeStackItem item) { return exprs().append(item); }
+
+    bool needFirst() {
+        for (size_t i = depths().back(); i < exprs().length(); ++i) {
+            if (!exprs()[i].expr->isVoid())
+                return true;
+        }
+        return false;
+    }
+
+    AstExpr* handleVoidExpr(AstExpr* voidNode)
+    {
+        MOZ_ASSERT(voidNode->isVoid());
+
+        // To attach a node that "returns void" to the middle of an AST, wrap it
+        // in a first node next to the node it should accompany.
+        if (needFirst()) {
+            AstExpr *prev = popCopy().expr;
+
+            // If the previous/A node is already a First, reuse it.
+            if (prev->kind() == AstExprKind::First) {
+                if (!prev->as<AstFirst>().exprs().append(voidNode))
+                    return nullptr;
+                return prev;
+            }
+
+            AstExprVector exprs(lifo);
+            if (!exprs.append(prev))
+                return nullptr;
+            if (!exprs.append(voidNode))
+                return nullptr;
+
+            return new(lifo) AstFirst(Move(exprs));
+        }
+
+        return voidNode;
+    }
+
+    void startFunction(AstDecodeExprIter *iter, const ValTypeVector* locals, ExprType retType)
     {
         iter_ = iter;
         locals_ = locals;
         currentLabelIndex_ = 0;
+        retType_ = retType;
     }
     void endFunction()
     {
         iter_ = nullptr;
         locals_ = nullptr;
+        retType_ = ExprType::Limit;
         MOZ_ASSERT(blockLabels_.length() == 0);
     }
     uint32_t nextLabelIndex()
     {
         return currentLabelIndex_++;
     }
 };
 
 static bool
-AstDecodeFail(AstDecodeContext& c, const char* str)
-{
-    uint32_t offset = c.d.currentOffset();
-    char offsetStr[sizeof "4294967295"];
-    SprintfLiteral(offsetStr, "%" PRIu32, offset);
-    JS_ReportErrorNumber(c.cx, GetErrorMessage, nullptr, JSMSG_WASM_DECODE_FAIL, offsetStr, str);
-    return false;
-}
-
-static bool
 AstDecodeGenerateName(AstDecodeContext& c, const AstName& prefix, uint32_t index, AstName* name)
 {
     if (!c.generateNames) {
         *name = AstName();
         return true;
     }
 
     AstVector<char16_t> result(c.lifo);
@@ -177,148 +235,198 @@ AstDecodeGenerateRef(AstDecodeContext& c
     MOZ_ASSERT(!name.empty());
 
     *ref = AstRef(name, AstNoIndex);
     ref->setIndex(index);
     return true;
 }
 
 static bool
-AstDecodeCallArgs(AstDecodeContext& c, uint32_t arity, const AstSig& sig, AstExprVector* funcArgs)
+AstDecodeCallArgs(AstDecodeContext& c, const AstSig& sig, AstExprVector* funcArgs)
 {
-    if (arity != sig.args().length())
-        return c.iter().fail("call arity out of range");
+    MOZ_ASSERT(c.iter().inReachableCode());
 
     const AstValTypeVector& args = sig.args();
     uint32_t numArgs = args.length();
 
     if (!funcArgs->resize(numArgs))
         return false;
 
     for (size_t i = 0; i < numArgs; ++i) {
         ValType argType = args[i];
         AstDecodeStackItem item;
-        if (!c.iter().readCallArg(argType, numArgs, i, &item))
+        if (!c.iter().readCallArg(argType, numArgs, i, nullptr))
             return false;
-        (*funcArgs)[i] = item.expr;
+        (*funcArgs)[i] = c.exprs()[c.exprs().length() - numArgs + i].expr;
     }
+    c.exprs().shrinkBy(numArgs);
 
     return c.iter().readCallArgsEnd(numArgs);
 }
 
 static bool
 AstDecodeCallReturn(AstDecodeContext& c, const AstSig& sig)
 {
     return c.iter().readCallReturn(sig.ret());
 }
 
 static bool
 AstDecodeExpr(AstDecodeContext& c);
 
 static bool
+AstDecodeDrop(AstDecodeContext& c)
+{
+    if (!c.iter().readDrop())
+        return false;
+
+    AstDecodeStackItem value = c.popCopy();
+
+    AstExpr* tmp = new(c.lifo) AstDrop(*value.expr);
+    if (!tmp)
+        return false;
+
+    tmp = c.handleVoidExpr(tmp);
+    if (!tmp)
+        return false;
+
+    if (!c.push(AstDecodeStackItem(tmp)))
+        return false;
+
+    return true;
+}
+
+static bool
 AstDecodeCall(AstDecodeContext& c)
 {
     uint32_t calleeIndex;
-    uint32_t arity;
-    if (!c.iter().readCall(&calleeIndex, &arity))
+    if (!c.iter().readCall(&calleeIndex))
         return false;
 
-    if (calleeIndex >= c.funcSigs().length())
-        return c.iter().fail("callee index out of range");
+    uint32_t sigIndex;
+    AstRef funcRef;
+    if (calleeIndex < c.module().funcImportNames().length()) {
+        AstImport* import = c.module().imports()[calleeIndex];
+        sigIndex = import->funcSig().index();
+        funcRef = AstRef(import->name(), AstNoIndex);
+    } else {
+        uint32_t funcDefIndex = calleeIndex - c.module().funcImportNames().length();
+        if (funcDefIndex >= c.funcSigs().length())
+            return c.iter().fail("callee index out of range");
 
-    uint32_t sigIndex = c.funcSigs()[calleeIndex];
+        sigIndex = c.funcSigs()[funcDefIndex];
+
+        if (!AstDecodeGenerateRef(c, AstName(u"func"), calleeIndex, &funcRef))
+            return false;
+    }
+
+    if (!c.iter().inReachableCode())
+        return false;
+
     const AstSig* sig = c.module().sigs()[sigIndex];
 
-    AstRef funcRef;
-    if (!AstDecodeGenerateRef(c, AstName(u"func"), calleeIndex, &funcRef))
-        return false;
-
     AstExprVector args(c.lifo);
-    if (!AstDecodeCallArgs(c, arity, *sig, &args))
+    if (!AstDecodeCallArgs(c, *sig, &args))
         return false;
 
     if (!AstDecodeCallReturn(c, *sig))
         return false;
 
-    uint32_t argsLength = args.length();
-    AstCall* call = new(c.lifo) AstCall(Expr::Call, funcRef, Move(args));
+    AstCall* call = new(c.lifo) AstCall(Expr::Call, sig->ret(), funcRef, Move(args));
     if (!call)
         return false;
 
-    c.iter().setResult(AstDecodeStackItem(call, argsLength));
+    AstExpr* result = call;
+    if (IsVoid(sig->ret()))
+        result = c.handleVoidExpr(call);
+
+    if (!c.push(AstDecodeStackItem(result)))
+        return false;
+
     return true;
 }
 
 static bool
 AstDecodeCallIndirect(AstDecodeContext& c)
 {
     uint32_t sigIndex;
-    uint32_t arity;
-    if (!c.iter().readCallIndirect(&sigIndex, &arity))
-        return false;
-
-    AstRef sigRef;
-    if (!AstDecodeGenerateRef(c, AstName(u"type"), sigIndex, &sigRef))
+    if (!c.iter().readCallIndirect(&sigIndex, nullptr))
         return false;
 
     if (sigIndex >= c.module().sigs().length())
         return c.iter().fail("signature index out of range");
 
-    const AstSig* sig = c.module().sigs()[sigIndex];
-    AstExprVector args(c.lifo);
-    if (!AstDecodeCallArgs(c, arity, *sig, &args))
+    if (!c.iter().inReachableCode())
         return false;
 
-    AstDecodeStackItem index;
-    if (!c.iter().readCallIndirectCallee(&index))
+    AstDecodeStackItem index = c.popCopy();
+
+    AstRef sigRef;
+    if (!AstDecodeGenerateRef(c, AstName(u"type"), sigIndex, &sigRef))
         return false;
 
+    const AstSig* sig = c.module().sigs()[sigIndex];
+    AstExprVector args(c.lifo);
+    if (!AstDecodeCallArgs(c, *sig, &args))
+        return false;
 
     if (!AstDecodeCallReturn(c, *sig))
         return false;
 
-    uint32_t argsLength = args.length();
-    AstCallIndirect* call = new(c.lifo) AstCallIndirect(sigRef, index.expr, Move(args));
+    AstCallIndirect* call = new(c.lifo) AstCallIndirect(sigRef, sig->ret(),
+                                                        Move(args), index.expr);
     if (!call)
         return false;
 
-    c.iter().setResult(AstDecodeStackItem(call, 1 + argsLength));
+    AstExpr* result = call;
+    if (IsVoid(sig->ret()))
+        result = c.handleVoidExpr(call);
+
+    if (!c.push(AstDecodeStackItem(result)))
+        return false;
+
     return true;
 }
 
 static bool
 AstDecodeCallImport(AstDecodeContext& c)
 {
     uint32_t importIndex;
-    uint32_t arity;
-    if (!c.iter().readCallImport(&importIndex, &arity))
+    if (!c.iter().readCallImport(&importIndex))
         return false;
 
     if (importIndex >= c.module().imports().length())
         return c.iter().fail("import index out of range");
 
+    if (!c.iter().inReachableCode())
+        return false;
+
     AstImport* import = c.module().imports()[importIndex];
     AstSig* sig = c.module().sigs()[import->funcSig().index()];
     AstRef funcRef;
     if (!AstDecodeGenerateRef(c, AstName(u"import"), importIndex, &funcRef))
         return false;
 
     AstExprVector args(c.lifo);
-    if (!AstDecodeCallArgs(c, arity, *sig, &args))
+    if (!AstDecodeCallArgs(c, *sig, &args))
         return false;
 
     if (!AstDecodeCallReturn(c, *sig))
         return false;
 
-    uint32_t argsLength = args.length();
-    AstCall* call = new(c.lifo) AstCall(Expr::CallImport, funcRef, Move(args));
+    AstCall* call = new(c.lifo) AstCall(Expr::CallImport, sig->ret(), funcRef, Move(args));
     if (!call)
         return false;
 
-    c.iter().setResult(AstDecodeStackItem(call, argsLength));
+    AstExpr* result = call;
+    if (IsVoid(sig->ret()))
+        result = c.handleVoidExpr(call);
+
+    if (!c.push(AstDecodeStackItem(result)))
+        return false;
+
     return true;
 }
 
 static bool
 AstDecodeGetBlockRef(AstDecodeContext& c, uint32_t depth, AstRef* ref)
 {
     if (!c.generateNames || depth >= c.blockLabels().length()) {
         // Also ignoring if it's a function body label.
@@ -336,335 +444,431 @@ AstDecodeGetBlockRef(AstDecodeContext& c
     return true;
 }
 
 static bool
 AstDecodeBrTable(AstDecodeContext& c)
 {
     uint32_t tableLength;
     ExprType type;
-    AstDecodeStackItem index;
-    AstDecodeStackItem value;
-    if (!c.iter().readBrTable(&tableLength, &type, &value, &index))
+    if (!c.iter().readBrTable(&tableLength, &type, nullptr, nullptr))
         return false;
 
     AstRefVector table(c.lifo);
     if (!table.resize(tableLength))
         return false;
 
     uint32_t depth;
     for (size_t i = 0, e = tableLength; i < e; ++i) {
-        if (!c.iter().readBrTableEntry(type, &depth))
+        if (!c.iter().readBrTableEntry(&type, nullptr, &depth))
             return false;
         if (!AstDecodeGetBlockRef(c, depth, &table[i]))
             return false;
     }
 
     // Read the default label.
-    if (!c.iter().readBrTableEntry(type, &depth))
+    if (!c.iter().readBrTableDefault(&type, nullptr, &depth))
         return false;
 
+    AstDecodeStackItem index = c.popCopy();
+    AstDecodeStackItem value;
+    if (!IsVoid(type))
+        value = c.popCopy();
+
     AstRef def;
     if (!AstDecodeGetBlockRef(c, depth, &def))
         return false;
 
-    AstBranchTable* branchTable = new(c.lifo) AstBranchTable(*index.expr, def, Move(table), value.expr);
+    AstBranchTable* branchTable = new(c.lifo) AstBranchTable(*index.expr,
+                                                             def, Move(table), value.expr);
     if (!branchTable)
         return false;
 
-    c.iter().setResult(AstDecodeStackItem(branchTable, value.expr ? 2 : 1));
+    if (!c.push(AstDecodeStackItem(branchTable)))
+        return false;
+
     return true;
 }
 
 static bool
 AstDecodeBlock(AstDecodeContext& c, Expr expr)
 {
     MOZ_ASSERT(expr == Expr::Block || expr == Expr::Loop);
 
+    if (!c.blockLabels().append(AstName()))
+        return false;
+
     if (expr == Expr::Loop) {
-      if (!c.blockLabels().append(AstName()) || !c.blockLabels().append(AstName()))
-          return false;
       if (!c.iter().readLoop())
           return false;
     } else {
-      if (!c.blockLabels().append(AstName()))
-          return false;
       if (!c.iter().readBlock())
           return false;
     }
 
-    AstExprVector exprs(c.lifo);
+    if (!c.depths().append(c.exprs().length()))
+        return false;
+
+    ExprType type;
     while (true) {
         if (!AstDecodeExpr(c))
             return false;
 
-        AstDecodeStackItem item = c.iter().getResult();
-        if (!item.expr) // Expr::End was found
+        const AstDecodeStackItem& item = c.top();
+        if (!item.expr) { // Expr::End was found
+            type = item.type;
+            c.popBack();
             break;
+        }
+    }
 
-        exprs.shrinkBy(item.popped);
-        if (!exprs.append(item.expr))
+    AstExprVector exprs(c.lifo);
+    for (auto i = c.exprs().begin() + c.depths().back(), e = c.exprs().end();
+         i != e; ++i) {
+        if (!exprs.append(i->expr))
             return false;
     }
+    c.exprs().shrinkTo(c.depths().popCopy());
 
-    AstName continueName;
-    if (expr == Expr::Loop)
-        continueName = c.blockLabels().popCopy();
-    AstName breakName = c.blockLabels().popCopy();
-    AstBlock* block = new(c.lifo) AstBlock(expr, breakName, continueName, Move(exprs));
+    AstName name = c.blockLabels().popCopy();
+    AstBlock* block = new(c.lifo) AstBlock(expr, type, name, Move(exprs));
     if (!block)
         return false;
 
-    c.iter().setResult(AstDecodeStackItem(block));
+    AstExpr* result = block;
+    if (IsVoid(type))
+        result = c.handleVoidExpr(block);
+
+    if (!c.push(AstDecodeStackItem(result)))
+        return false;
+
     return true;
 }
 
 static bool
 AstDecodeIf(AstDecodeContext& c)
 {
-    AstDecodeStackItem cond;
-    if (!c.iter().readIf(&cond))
+    if (!c.iter().readIf(nullptr))
         return false;
 
+    AstDecodeStackItem cond = c.popCopy();
+
     bool hasElse = false;
 
+    if (!c.depths().append(c.exprs().length()))
+        return false;
+
     if (!c.blockLabels().append(AstName()))
         return false;
-    AstExprVector thenExprs(c.lifo);
+
+    ExprType type;
     while (true) {
         if (!AstDecodeExpr(c))
             return false;
 
-        AstDecodeStackItem item = c.iter().getResult();
-        if (!item.expr) {
+        const AstDecodeStackItem& item = c.top();
+        if (!item.expr) { // Expr::End was found
             hasElse = item.terminationKind == AstDecodeTerminationKind::Else;
+            type = item.type;
+            c.popBack();
             break;
         }
+    }
 
-        thenExprs.shrinkBy(item.popped);
-        if (!thenExprs.append(item.expr))
+    AstExprVector thenExprs(c.lifo);
+    for (auto i = c.exprs().begin() + c.depths().back(), e = c.exprs().end();
+         i != e; ++i) {
+        if (!thenExprs.append(i->expr))
             return false;
     }
-    AstName thenName = c.blockLabels().popCopy();
+    c.exprs().shrinkTo(c.depths().back());
 
-    AstName elseName;
     AstExprVector elseExprs(c.lifo);
     if (hasElse) {
-        if (!c.blockLabels().append(AstName()))
-            return false;
         while (true) {
             if (!AstDecodeExpr(c))
                 return false;
 
-            AstDecodeStackItem item = c.iter().getResult();
-            if (!item.expr) // Expr::End was found
+            const AstDecodeStackItem& item = c.top();
+            if (!item.expr) { // Expr::End was found
+                c.popBack();
                 break;
+            }
+        }
 
-            elseExprs.shrinkBy(item.popped);
-            if (!elseExprs.append(item.expr))
+        for (auto i = c.exprs().begin() + c.depths().back(), e = c.exprs().end();
+             i != e; ++i) {
+            if (!elseExprs.append(i->expr))
                 return false;
         }
-        elseName = c.blockLabels().popCopy();
+        c.exprs().shrinkTo(c.depths().back());
     }
 
-    AstIf* if_ = new(c.lifo) AstIf(cond.expr, thenName, Move(thenExprs), elseName, Move(elseExprs));
+    c.depths().popBack();
+
+    AstName name = c.blockLabels().popCopy();
+
+    AstIf* if_ = new(c.lifo) AstIf(type, cond.expr, name, Move(thenExprs), Move(elseExprs));
     if (!if_)
         return false;
 
-    c.iter().setResult(AstDecodeStackItem(if_, 1));
+    AstExpr* result = if_;
+    if (IsVoid(type))
+        result = c.handleVoidExpr(if_);
+
+    if (!c.push(AstDecodeStackItem(result)))
+        return false;
+
     return true;
 }
 
 static bool
 AstDecodeEnd(AstDecodeContext& c)
 {
     LabelKind kind;
     ExprType type;
-    AstDecodeStackItem tmp;
-    if (!c.iter().readEnd(&kind, &type, &tmp))
+    if (!c.iter().readEnd(&kind, &type, nullptr))
         return false;
 
-    c.iter().setResult(AstDecodeStackItem(AstDecodeTerminationKind::End));
+    if (!c.push(AstDecodeStackItem(AstDecodeTerminationKind::End, type)))
+        return false;
+
     return true;
 }
 
 static bool
 AstDecodeElse(AstDecodeContext& c)
 {
     ExprType type;
-    AstDecodeStackItem tmp;
 
-    if (!c.iter().readElse(&type, &tmp))
+    if (!c.iter().readElse(&type, nullptr))
+        return false;
+
+    if (!c.push(AstDecodeStackItem(AstDecodeTerminationKind::Else, type)))
         return false;
 
-    c.iter().setResult(AstDecodeStackItem(AstDecodeTerminationKind::Else));
+    return true;
+}
+
+static bool
+AstDecodeNop(AstDecodeContext& c)
+{
+    if (!c.iter().readNop())
+        return false;
+
+    AstExpr* tmp = new(c.lifo) AstNop();
+    if (!tmp)
+        return false;
+
+    tmp = c.handleVoidExpr(tmp);
+    if (!tmp)
+        return false;
+
+    if (!c.push(AstDecodeStackItem(tmp)))
+        return false;
+
     return true;
 }
 
 static bool
 AstDecodeUnary(AstDecodeContext& c, ValType type, Expr expr)
 {
-    AstDecodeStackItem op;
-    if (!c.iter().readUnary(type, &op))
+    if (!c.iter().readUnary(type, nullptr))
         return false;
 
+    AstDecodeStackItem op = c.popCopy();
+
     AstUnaryOperator* unary = new(c.lifo) AstUnaryOperator(expr, op.expr);
     if (!unary)
         return false;
 
-    c.iter().setResult(AstDecodeStackItem(unary, 1));
+    if (!c.push(AstDecodeStackItem(unary)))
+        return false;
+
     return true;
 }
 
 static bool
-AstDecodeNullary(AstDecodeContext& c, ExprType type, Expr expr)
+AstDecodeNullary(AstDecodeContext& c, ValType type, Expr expr)
 {
     if (!c.iter().readNullary(type))
         return false;
 
     AstNullaryOperator* nullary = new(c.lifo) AstNullaryOperator(expr);
     if (!nullary)
         return false;
 
-    c.iter().setResult(AstDecodeStackItem(nullary, 0));
+    if (!c.push(AstDecodeStackItem(nullary)))
+        return false;
+
     return true;
 }
 
 static bool
 AstDecodeBinary(AstDecodeContext& c, ValType type, Expr expr)
 {
-    AstDecodeStackItem lhs;
-    AstDecodeStackItem rhs;
-    if (!c.iter().readBinary(type, &lhs, &rhs))
+    if (!c.iter().readBinary(type, nullptr, nullptr))
         return false;
 
+    AstDecodeStackItem rhs = c.popCopy();
+    AstDecodeStackItem lhs = c.popCopy();
+
     AstBinaryOperator* binary = new(c.lifo) AstBinaryOperator(expr, lhs.expr, rhs.expr);
     if (!binary)
         return false;
 
-    c.iter().setResult(AstDecodeStackItem(binary, 2));
+    if (!c.push(AstDecodeStackItem(binary)))
+        return false;
+
     return true;
 }
 
 static bool
 AstDecodeSelect(AstDecodeContext& c)
 {
-    ExprType type;
-    AstDecodeStackItem cond;
-    AstDecodeStackItem selectTrue;
-    AstDecodeStackItem selectFalse;
-    if (!c.iter().readSelect(&type, &cond, &selectTrue, &selectFalse))
+    ValType type;
+    if (!c.iter().readSelect(&type, nullptr, nullptr, nullptr))
         return false;
 
+    AstDecodeStackItem selectFalse = c.popCopy();
+    AstDecodeStackItem selectTrue = c.popCopy();
+    AstDecodeStackItem cond = c.popCopy();
+
     AstTernaryOperator* ternary = new(c.lifo) AstTernaryOperator(Expr::Select, cond.expr, selectTrue.expr, selectFalse.expr);
     if (!ternary)
         return false;
 
-    c.iter().setResult(AstDecodeStackItem(ternary, 3));
+    if (!c.push(AstDecodeStackItem(ternary)))
+        return false;
+
     return true;
 }
 
 static bool
 AstDecodeComparison(AstDecodeContext& c, ValType type, Expr expr)
 {
-    AstDecodeStackItem lhs;
-    AstDecodeStackItem rhs;
-    if (!c.iter().readComparison(type, &lhs, &rhs))
+    if (!c.iter().readComparison(type, nullptr, nullptr))
         return false;
 
+    AstDecodeStackItem rhs = c.popCopy();
+    AstDecodeStackItem lhs = c.popCopy();
+
     AstComparisonOperator* comparison = new(c.lifo) AstComparisonOperator(expr, lhs.expr, rhs.expr);
     if (!comparison)
         return false;
 
-    c.iter().setResult(AstDecodeStackItem(comparison, 2));
+    if (!c.push(AstDecodeStackItem(comparison)))
+        return false;
+
     return true;
 }
 
 static bool
 AstDecodeConversion(AstDecodeContext& c, ValType fromType, ValType toType, Expr expr)
 {
-    AstDecodeStackItem op;
-    if (!c.iter().readConversion(fromType, toType, &op))
+    if (!c.iter().readConversion(fromType, toType, nullptr))
         return false;
 
+    AstDecodeStackItem op = c.popCopy();
+
     AstConversionOperator* conversion = new(c.lifo) AstConversionOperator(expr, op.expr);
     if (!conversion)
         return false;
 
-    c.iter().setResult(AstDecodeStackItem(conversion, 1));
+    if (!c.push(AstDecodeStackItem(conversion)))
+        return false;
+
     return true;
 }
 
 static AstLoadStoreAddress
-AstDecodeLoadStoreAddress(const LinearMemoryAddress<AstDecodeStackItem>& addr)
+AstDecodeLoadStoreAddress(const LinearMemoryAddress<Nothing>& addr, const AstDecodeStackItem& item)
 {
     uint32_t flags = FloorLog2(addr.align);
-    return AstLoadStoreAddress(addr.base.expr, flags, addr.offset);
+    return AstLoadStoreAddress(item.expr, flags, addr.offset);
 }
 
 static bool
 AstDecodeLoad(AstDecodeContext& c, ValType type, uint32_t byteSize, Expr expr)
 {
-    LinearMemoryAddress<AstDecodeStackItem> addr;
+    LinearMemoryAddress<Nothing> addr;
     if (!c.iter().readLoad(type, byteSize, &addr))
         return false;
 
-    AstLoad* load = new(c.lifo) AstLoad(expr, AstDecodeLoadStoreAddress(addr));
+    AstDecodeStackItem item = c.popCopy();
+
+    AstLoad* load = new(c.lifo) AstLoad(expr, AstDecodeLoadStoreAddress(addr, item));
     if (!load)
         return false;
 
-    c.iter().setResult(AstDecodeStackItem(load, 1));
+    if (!c.push(AstDecodeStackItem(load)))
+        return false;
+
     return true;
 }
 
 static bool
 AstDecodeStore(AstDecodeContext& c, ValType type, uint32_t byteSize, Expr expr)
 {
-    LinearMemoryAddress<AstDecodeStackItem> addr;
-    AstDecodeStackItem value;
-    if (!c.iter().readStore(type, byteSize, &addr, &value))
+    LinearMemoryAddress<Nothing> addr;
+    if (!c.iter().readStore(type, byteSize, &addr, nullptr))
         return false;
 
-    AstStore* store = new(c.lifo) AstStore(expr, AstDecodeLoadStoreAddress(addr), value.expr);
+    AstDecodeStackItem value = c.popCopy();
+    AstDecodeStackItem item = c.popCopy();
+
+    AstStore* store = new(c.lifo) AstStore(expr, AstDecodeLoadStoreAddress(addr, item), value.expr);
     if (!store)
         return false;
 
-    c.iter().setResult(AstDecodeStackItem(store, 2));
+    AstExpr* wrapped = c.handleVoidExpr(store);
+    if (!wrapped)
+        return false;
+
+    if (!c.push(AstDecodeStackItem(wrapped)))
+        return false;
+
     return true;
 }
 
 static bool
 AstDecodeBranch(AstDecodeContext& c, Expr expr)
 {
     MOZ_ASSERT(expr == Expr::Br || expr == Expr::BrIf);
 
     uint32_t depth;
     ExprType type;
     AstDecodeStackItem value;
     AstDecodeStackItem cond;
-    uint32_t popped;
     if (expr == Expr::Br) {
-        if (!c.iter().readBr(&depth, &type, &value))
+        if (!c.iter().readBr(&depth, &type, nullptr))
             return false;
-        popped = value.expr ? 1 : 0;
+        if (!IsVoid(type))
+            value = c.popCopy();
     } else {
-        if (!c.iter().readBrIf(&depth, &type, &value, &cond))
+        if (!c.iter().readBrIf(&depth, &type, nullptr, nullptr))
             return false;
-        popped = value.expr ? 2 : 1;
+        if (!IsVoid(type))
+            value = c.popCopy();
+        cond = c.popCopy();
     }
 
     AstRef depthRef;
     if (!AstDecodeGetBlockRef(c, depth, &depthRef))
         return false;
 
-    AstBranch* branch = new(c.lifo) AstBranch(expr, cond.expr, depthRef, value.expr);
+    if (expr == Expr::Br || !value.expr)
+        type = ExprType::Void;
+    AstBranch* branch = new(c.lifo) AstBranch(expr, type, cond.expr, depthRef, value.expr);
     if (!branch)
         return false;
 
-    c.iter().setResult(AstDecodeStackItem(branch, popped));
+    if (!c.push(AstDecodeStackItem(branch)))
+        return false;
+
     return true;
 }
 
 static bool
 AstDecodeGetLocal(AstDecodeContext& c)
 {
     uint32_t getLocalId;
     if (!c.iter().readGetLocal(c.locals(), &getLocalId))
@@ -673,67 +877,108 @@ AstDecodeGetLocal(AstDecodeContext& c)
     AstRef localRef;
     if (!AstDecodeGenerateRef(c, AstName(u"var"), getLocalId, &localRef))
         return false;
 
     AstGetLocal* getLocal = new(c.lifo) AstGetLocal(localRef);
     if (!getLocal)
         return false;
 
-    c.iter().setResult(AstDecodeStackItem(getLocal));
+    if (!c.push(AstDecodeStackItem(getLocal)))
+        return false;
+
     return true;
 }
 
 static bool
 AstDecodeSetLocal(AstDecodeContext& c)
 {
     uint32_t setLocalId;
-    AstDecodeStackItem setLocalValue;
-    if (!c.iter().readSetLocal(c.locals(), &setLocalId, &setLocalValue))
+    if (!c.iter().readSetLocal(c.locals(), &setLocalId, nullptr))
         return false;
 
+    AstDecodeStackItem setLocalValue = c.popCopy();
+
     AstRef localRef;
     if (!AstDecodeGenerateRef(c, AstName(u"var"), setLocalId, &localRef))
         return false;
 
     AstSetLocal* setLocal = new(c.lifo) AstSetLocal(localRef, *setLocalValue.expr);
     if (!setLocal)
         return false;
 
-    c.iter().setResult(AstDecodeStackItem(setLocal, 1));
+    AstExpr* expr = c.handleVoidExpr(setLocal);
+    if (!expr)
+        return false;
+
+    if (!c.push(AstDecodeStackItem(expr)))
+        return false;
+
+    return true;
+}
+
+static bool
+AstDecodeTeeLocal(AstDecodeContext& c)
+{
+    uint32_t teeLocalId;
+    if (!c.iter().readTeeLocal(c.locals(), &teeLocalId, nullptr))
+        return false;
+
+    AstDecodeStackItem teeLocalValue = c.popCopy();
+
+    AstRef localRef;
+    if (!AstDecodeGenerateRef(c, AstName(u"var"), teeLocalId, &localRef))
+        return false;
+
+    AstTeeLocal* teeLocal = new(c.lifo) AstTeeLocal(localRef, *teeLocalValue.expr);
+    if (!teeLocal)
+        return false;
+
+    if (!c.push(AstDecodeStackItem(teeLocal)))
+        return false;
+
     return true;
 }
 
 static bool
 AstDecodeReturn(AstDecodeContext& c)
 {
+    if (!c.iter().readReturn(nullptr))
+        return false;
+
     AstDecodeStackItem result;
-    if (!c.iter().readReturn(&result))
-        return false;
+    if (!IsVoid(c.retType()))
+       result = c.popCopy();
 
     AstReturn* ret = new(c.lifo) AstReturn(result.expr);
     if (!ret)
         return false;
 
-    c.iter().setResult(AstDecodeStackItem(ret, result.expr ? 1 : 0));
+    if (!c.push(AstDecodeStackItem(ret)))
+        return false;
+
     return true;
 }
 
 static bool
 AstDecodeExpr(AstDecodeContext& c)
 {
     uint32_t exprOffset = c.iter().currentOffset();
     Expr expr;
     if (!c.iter().readExpr(&expr))
         return false;
 
     AstExpr* tmp;
     switch (expr) {
       case Expr::Nop:
-        if (!AstDecodeNullary(c, ExprType::Void, expr))
+        if (!AstDecodeNop(c))
+            return false;
+        break;
+      case Expr::Drop:
+        if (!AstDecodeDrop(c))
             return false;
         break;
       case Expr::Call:
         if (!AstDecodeCall(c))
             return false;
         break;
       case Expr::CallIndirect:
         if (!AstDecodeCallIndirect(c))
@@ -743,57 +988,57 @@ AstDecodeExpr(AstDecodeContext& c)
         if (!AstDecodeCallImport(c))
             return false;
         break;
       case Expr::I32Const:
         int32_t i32;
         if (!c.iter().readI32Const(&i32))
             return false;
         tmp = new(c.lifo) AstConst(Val((uint32_t)i32));
-        if (!tmp)
+        if (!tmp || !c.push(AstDecodeStackItem(tmp)))
             return false;
-        c.iter().setResult(AstDecodeStackItem(tmp));
         break;
       case Expr::I64Const:
         int64_t i64;
         if (!c.iter().readI64Const(&i64))
             return false;
         tmp = new(c.lifo) AstConst(Val((uint64_t)i64));
-        if (!tmp)
+        if (!tmp || !c.push(AstDecodeStackItem(tmp)))
             return false;
-        c.iter().setResult(AstDecodeStackItem(tmp));
         break;
       case Expr::F32Const: {
         RawF32 f32;
         if (!c.iter().readF32Const(&f32))
             return false;
         tmp = new(c.lifo) AstConst(Val(f32));
-        if (!tmp)
+        if (!tmp || !c.push(AstDecodeStackItem(tmp)))
             return false;
-        c.iter().setResult(AstDecodeStackItem(tmp));
         break;
       }
       case Expr::F64Const: {
         RawF64 f64;
         if (!c.iter().readF64Const(&f64))
             return false;
         tmp = new(c.lifo) AstConst(Val(f64));
-        if (!tmp)
+        if (!tmp || !c.push(AstDecodeStackItem(tmp)))
             return false;
-        c.iter().setResult(AstDecodeStackItem(tmp));
         break;
       }
       case Expr::GetLocal:
         if (!AstDecodeGetLocal(c))
             return false;
         break;
       case Expr::SetLocal:
         if (!AstDecodeSetLocal(c))
             return false;
         break;
+      case Expr::TeeLocal:
+        if (!AstDecodeTeeLocal(c))
+            return false;
+        break;
       case Expr::Select:
         if (!AstDecodeSelect(c))
             return false;
         break;
       case Expr::Block:
       case Expr::Loop:
         if (!AstDecodeBlock(c, expr))
             return false;
@@ -1094,196 +1339,192 @@ AstDecodeExpr(AstDecodeContext& c)
         if (!AstDecodeBrTable(c))
             return false;
         break;
       case Expr::Return:
         if (!AstDecodeReturn(c))
             return false;
         break;
       case Expr::CurrentMemory:
-        if (!AstDecodeNullary(c, ExprType::I32, expr))
+        if (!AstDecodeNullary(c, ValType::I32, expr))
             return false;
         break;
       case Expr::Unreachable:
         if (!c.iter().readUnreachable())
             return false;
         tmp = new(c.lifo) AstUnreachable();
         if (!tmp)
             return false;
-        c.iter().setResult(AstDecodeStackItem(tmp));
+        if (!c.push(AstDecodeStackItem(tmp)))
+            return false;
         break;
       default:
         // Note: it's important not to remove this default since readExpr()
         // can return Expr values for which there is no enumerator.
         return c.iter().unrecognizedOpcode(expr);
     }
 
-    AstExpr* lastExpr = c.iter().getResult().expr;
+    AstExpr* lastExpr = c.top().expr;
     if (lastExpr)
         lastExpr->setOffset(exprOffset);
     return true;
 }
 
 /*****************************************************************************/
 // wasm decoding and generation
 
 static bool
 AstDecodeTypeSection(AstDecodeContext& c)
 {
     uint32_t sectionStart, sectionSize;
-    if (!c.d.startSection(TypeSectionId, &sectionStart, &sectionSize))
-        return AstDecodeFail(c, "failed to start section");
+    if (!c.d.startSection(SectionId::Type, &sectionStart, &sectionSize, "type"))
+        return false;
     if (sectionStart == Decoder::NotStarted)
         return true;
 
     uint32_t numSigs;
     if (!c.d.readVarU32(&numSigs))
-        return AstDecodeFail(c, "expected number of signatures");
+        return c.d.fail("expected number of signatures");
 
     if (numSigs > MaxSigs)
-        return AstDecodeFail(c, "too many signatures");
+        return c.d.fail("too many signatures");
 
     for (uint32_t sigIndex = 0; sigIndex < numSigs; sigIndex++) {
         uint32_t form;
         if (!c.d.readVarU32(&form) || form != uint32_t(TypeConstructor::Function))
-            return AstDecodeFail(c, "expected function form");
+            return c.d.fail("expected function form");
 
         uint32_t numArgs;
         if (!c.d.readVarU32(&numArgs))
-            return AstDecodeFail(c, "bad number of function args");
+            return c.d.fail("bad number of function args");
 
         if (numArgs > MaxArgsPerFunc)
-            return AstDecodeFail(c, "too many arguments in signature");
+            return c.d.fail("too many arguments in signature");
 
         AstValTypeVector args(c.lifo);
         if (!args.resize(numArgs))
             return false;
 
         for (uint32_t i = 0; i < numArgs; i++) {
             if (!c.d.readValType(&args[i]))
-                return AstDecodeFail(c, "bad value type");
+                return c.d.fail("bad value type");
         }
 
         uint32_t numRets;
         if (!c.d.readVarU32(&numRets))
-            return AstDecodeFail(c, "bad number of function returns");
+            return c.d.fail("bad number of function returns");
 
         if (numRets > 1)
-            return AstDecodeFail(c, "too many returns in signature");
+            return c.d.fail("too many returns in signature");
 
         ExprType result = ExprType::Void;
 
         if (numRets == 1) {
             ValType type;
             if (!c.d.readValType(&type))
-                return AstDecodeFail(c, "bad expression type");
+                return c.d.fail("bad expression type");
 
             result = ToExprType(type);
         }
 
         AstSig sigNoName(Move(args), result);
         AstName sigName;
         if (!AstDecodeGenerateName(c, AstName(u"type"), sigIndex, &sigName))
             return false;
 
         AstSig* sig = new(c.lifo) AstSig(sigName, Move(sigNoName));
         if (!sig || !c.module().append(sig))
             return false;
     }
 
-    if (!c.d.finishSection(sectionStart, sectionSize))
-        return AstDecodeFail(c, "decls section byte size mismatch");
+    if (!c.d.finishSection(sectionStart, sectionSize, "type"))
+        return false;
 
     return true;
 }
 
 static bool
 AstDecodeSignatureIndex(AstDecodeContext& c, uint32_t* sigIndex)
 {
     if (!c.d.readVarU32(sigIndex))
-        return AstDecodeFail(c, "expected signature index");
+        return c.d.fail("expected signature index");
 
     if (*sigIndex >= c.module().sigs().length())
-        return AstDecodeFail(c, "signature index out of range");
+        return c.d.fail("signature index out of range");
 
     return true;
 }
 
 static bool
 AstDecodeFunctionSection(AstDecodeContext& c)
 {
     uint32_t sectionStart, sectionSize;
-    if (!c.d.startSection(FunctionSectionId, &sectionStart, &sectionSize))
-        return AstDecodeFail(c, "failed to start section");
+    if (!c.d.startSection(SectionId::Function, &sectionStart, &sectionSize, "function"))
+        return false;
     if (sectionStart == Decoder::NotStarted)
         return true;
 
     uint32_t numDecls;
     if (!c.d.readVarU32(&numDecls))
-        return AstDecodeFail(c, "expected number of declarations");
+        return c.d.fail("expected number of declarations");
 
     if (numDecls > MaxFuncs)
-        return AstDecodeFail(c, "too many functions");
+        return c.d.fail("too many functions");
 
 
     if (!c.funcSigs().resize(numDecls))
         return false;
 
     for (uint32_t i = 0; i < numDecls; i++) {
         if (!AstDecodeSignatureIndex(c, &c.funcSigs()[i]))
             return false;
     }
 
-    if (!c.d.finishSection(sectionStart, sectionSize))
-        return AstDecodeFail(c, "decls section byte size mismatch");
+    if (!c.d.finishSection(sectionStart, sectionSize, "function"))
+        return false;
 
     return true;
 }
 
 static bool
 AstDecodeTableSection(AstDecodeContext& c)
 {
     uint32_t sectionStart, sectionSize;
-    if (!c.d.startSection(TableSectionId, &sectionStart, &sectionSize))
-        return AstDecodeFail(c, "failed to start section");
+    if (!c.d.startSection(SectionId::Table, &sectionStart, &sectionSize, "table"))
+        return false;
     if (sectionStart == Decoder::NotStarted)
         return true;
 
-    uint32_t numElems;
-    if (!c.d.readVarU32(&numElems))
-        return AstDecodeFail(c, "expected number of table elems");
+    uint32_t numTables;
+    if (!c.d.readVarU32(&numTables))
+        return c.d.fail("failed to read number of tables");
+
+    if (numTables != 1)
+        return c.d.fail("the number of tables must be exactly one");
 
-    if (numElems > MaxTableElems)
-        return AstDecodeFail(c, "too many table elements");
+    uint32_t typeConstructorValue;
+    if (!c.d.readVarU32(&typeConstructorValue))
+        return c.d.fail("expected type constructor kind");
 
-    AstRefVector elems(c.lifo);
-    if (!elems.resize(numElems))
+    if (typeConstructorValue != uint32_t(TypeConstructor::AnyFunc))
+        return c.d.fail("unknown type constructor kind");
+
+    ResizableLimits table;
+    if (!DecodeResizable(c.d, &table))
         return false;
 
-    for (uint32_t i = 0; i < numElems; i++) {
-        uint32_t funcIndex;
-        if (!c.d.readVarU32(&funcIndex))
-            return AstDecodeFail(c, "expected table element");
-
-        if (funcIndex >= c.funcSigs().length())
-            return AstDecodeFail(c, "table element out of range");
+    if (table.initial > MaxTableElems)
+        return c.d.fail("too many table elements");
 
-        elems[i] = AstRef(AstName(), funcIndex);
-    }
+    if (!c.module().setTable(table))
+        return c.d.fail("already have a table");
 
-    AstElemSegment* seg = new(c.lifo) AstElemSegment(0, Move(elems));
-    if (!seg || !c.module().append(seg))
+    if (!c.d.finishSection(sectionStart, sectionSize, "table"))
         return false;
 
-    if (!c.module().setTable(AstResizable(numElems, Nothing())))
-        return AstDecodeFail(c, "already have a table");
-
-    if (!c.d.finishSection(sectionStart, sectionSize))
-        return AstDecodeFail(c, "table section byte size mismatch");
-
     return true;
 }
 
 static bool
 AstDecodeName(AstDecodeContext& c, AstName* name)
 {
     uint32_t length;
     if (!c.d.readVarU32(&length))
@@ -1297,250 +1538,339 @@ AstDecodeName(AstDecodeContext& c, AstNa
     for (size_t i = 0; i < length; i++)
         buffer[i] = bytes[i];
 
     *name = AstName(buffer, length);
     return true;
 }
 
 static bool
-AstDecodeImport(AstDecodeContext& c, uint32_t importIndex, AstImport** import)
+AstDecodeResizableTable(AstDecodeContext& c, ResizableLimits* resizable)
 {
-    uint32_t sigIndex = AstNoIndex;
-    if (!AstDecodeSignatureIndex(c, &sigIndex))
+    uint32_t kind;
+    if (!c.d.readVarU32(&kind))
         return false;
 
-    AstRef sigRef;
-    if (!AstDecodeGenerateRef(c, AstName(u"type"), sigIndex, &sigRef))
+    if (kind != uint32_t(TypeConstructor::AnyFunc))
+        return c.d.fail("unknown type constructor kind");
+
+    if (!DecodeResizable(c.d, resizable))
         return false;
 
+    return true;
+}
+
+static bool
+AstDecodeImport(AstDecodeContext& c, uint32_t importIndex, AstImport** import)
+{
     AstName moduleName;
     if (!AstDecodeName(c, &moduleName))
-        return AstDecodeFail(c, "expected import module name");
+        return c.d.fail("expected import module name");
 
     if (moduleName.empty())
-        return AstDecodeFail(c, "module name cannot be empty");
+        return c.d.fail("module name cannot be empty");
 
-    AstName funcName;
-    if (!AstDecodeName(c, &funcName))
-        return AstDecodeFail(c, "expected import func name");
+    AstName fieldName;
+    if (!AstDecodeName(c, &fieldName))
+        return c.d.fail("expected import field name");
 
     AstName importName;
     if (!AstDecodeGenerateName(c, AstName(u"import"), importIndex, &importName))
         return false;
 
-    *import = new(c.lifo) AstImport(importName, moduleName, funcName, sigRef);
+    uint32_t kind;
+    if (!c.d.readVarU32(&kind))
+        return c.d.fail("expected import kind");
+
+    switch (kind) {
+      case uint32_t(DefinitionKind::Function): {
+        uint32_t sigIndex = AstNoIndex;
+        if (!AstDecodeSignatureIndex(c, &sigIndex))
+            return false;
+
+        AstRef sigRef;
+        if (!AstDecodeGenerateRef(c, AstName(u"type"), sigIndex, &sigRef))
+            return false;
+
+        *import = new(c.lifo) AstImport(importName, moduleName, fieldName, sigRef);
+        break;
+      }
+      case uint32_t(DefinitionKind::Global): {
+        ValType type;
+        if (!c.d.readValType(&type))
+            return false;
+
+        uint32_t flags;
+        if (!c.d.readVarU32(&flags))
+            return false;
+
+        *import = new(c.lifo) AstImport(importName, moduleName, fieldName,
+                                        AstGlobal(importName, type, flags));
+        break;
+      }
+      case uint32_t(DefinitionKind::Table): {
+        ResizableLimits table;
+        if (!AstDecodeResizableTable(c, &table))
+            return false;
+
+        *import = new(c.lifo) AstImport(importName, moduleName, fieldName,
+                                        DefinitionKind::Table, table);
+        break;
+      }
+      case uint32_t(DefinitionKind::Memory): {
+        ResizableLimits memory;
+        if (!DecodeResizable(c.d, &memory))
+            return false;
+
+        *import = new(c.lifo) AstImport(importName, moduleName, fieldName,
+                                        DefinitionKind::Memory, memory);
+        break;
+      }
+      default:
+        return c.d.fail("unknown import kind");
+    }
+
     if (!*import)
         return false;
 
     return true;
 }
 
 static bool
 AstDecodeImportSection(AstDecodeContext& c)
 {
     uint32_t sectionStart, sectionSize;
-    if (!c.d.startSection(ImportSectionId, &sectionStart, &sectionSize))
-        return AstDecodeFail(c, "failed to start section");
+    if (!c.d.startSection(SectionId::Import, &sectionStart, &sectionSize, "import"))
+        return false;
     if (sectionStart == Decoder::NotStarted)
         return true;
 
     uint32_t numImports;
     if (!c.d.readVarU32(&numImports))
-        return AstDecodeFail(c, "failed to read number of imports");
+        return c.d.fail("failed to read number of imports");
 
     if (numImports > MaxImports)
-        return AstDecodeFail(c,  "too many imports");
+        return c.d.fail( "too many imports");
 
     for (uint32_t i = 0; i < numImports; i++) {
         AstImport* import = nullptr;
         if (!AstDecodeImport(c, i, &import))
             return false;
         if (!c.module().append(import))
             return false;
     }
 
-    if (!c.d.finishSection(sectionStart, sectionSize))
-        return AstDecodeFail(c, "import section byte size mismatch");
+    if (!c.d.finishSection(sectionStart, sectionSize, "import"))
+        return false;
 
     return true;
 }
 
 static bool
 AstDecodeMemorySection(AstDecodeContext& c)
 {
     uint32_t sectionStart, sectionSize;
-    if (!c.d.startSection(MemorySectionId, &sectionStart, &sectionSize))
-        return AstDecodeFail(c, "failed to start section");
+    if (!c.d.startSection(SectionId::Memory, &sectionStart, &sectionSize, "memory"))
+        return false;
     if (sectionStart == Decoder::NotStarted)
         return true;
 
-    uint32_t initialSizePages;
-    if (!c.d.readVarU32(&initialSizePages))
-        return AstDecodeFail(c, "expected initial memory size");
+    uint32_t numMemories;
+    if (!c.d.readVarU32(&numMemories))
+        return c.d.fail("failed to read number of memories");
+
+    if (numMemories != 1)
+        return c.d.fail("the number of memories must be exactly one");
+
+    ResizableLimits memory;
+    if (!DecodeResizable(c.d, &memory))
+        return false;
 
-    CheckedInt<uint32_t> initialSize = initialSizePages;
-    initialSize *= PageSize;
-    if (!initialSize.isValid())
-        return AstDecodeFail(c, "initial memory size too big");
+    if (!c.d.finishSection(sectionStart, sectionSize, "memory"))
+        return false;
+
+    c.module().setMemory(memory);
+    return true;
+}
 
-    uint32_t maxSizePages;
-    if (!c.d.readVarU32(&maxSizePages))
-        return AstDecodeFail(c, "expected initial memory size");
+static bool
+AstDecodeInitializerExpression(AstDecodeContext& c, AstExpr** init, ValType type)
+{
+    Expr expr;
+    if (!c.d.readExpr(&expr))
+        return c.d.fail("missing initializer opcode");
 
-    CheckedInt<uint32_t> maxSize = maxSizePages;
-    maxSize *= PageSize;
-    if (!maxSize.isValid())
-        return AstDecodeFail(c, "maximum memory size too big");
-
-    uint8_t exported;
-    if (!c.d.readFixedU8(&exported))
-        return AstDecodeFail(c, "expected exported byte");
-
-    if (exported) {
-        AstName fieldName(u"memory");
-        AstExport* export_ = new(c.lifo) AstExport(fieldName, DefinitionKind::Memory);
-        if (!export_ || !c.module().append(export_))
+    switch (expr) {
+      case Expr::I32Const: {
+        int32_t i32;
+        if (!c.d.readVarS32(&i32))
+            return c.d.fail("missing initializer value");
+        *init = new(c.lifo) AstConst(Val((uint32_t)i32));
+        if (!*init)
+            return false;
+        if (type != ValType::I32)
+            return c.d.fail("initializer expression has incorrect type");
+        break;
+      }
+      case Expr::I64Const: {
+        int64_t i64;
+        if (!c.d.readVarS64(&i64))
+            return c.d.fail("missing initializer value");
+        *init = new(c.lifo) AstConst(Val((uint64_t)i64));
+        if (!*init)
             return false;
+        if (type != ValType::I64)
+            return c.d.fail("initializer expression has incorrect type");
+        break;
+      }
+      case Expr::F32Const: {
+        RawF32 f32;
+        if (!c.d.readFixedF32(&f32))
+            return c.d.fail("missing initializer value");
+        *init = new(c.lifo) AstConst(Val(f32));
+        if (!*init)
+            return false;
+        if (type != ValType::F32)
+            return c.d.fail("initializer expression has incorrect type");
+        break;
+      }
+      case Expr::F64Const: {
+        RawF64 f64;
+        if (!c.d.readFixedF64(&f64))
+            return c.d.fail("missing initializer value");
+        *init = new(c.lifo) AstConst(Val(f64));
+        if (!*init)
+            return false;
+        if (type != ValType::F64)
+            return c.d.fail("initializer expression has incorrect type");
+        break;
+      }
+      default:
+        return c.d.fail("unknown initializer opcode");
     }
 
-    if (!c.d.finishSection(sectionStart, sectionSize))
-        return AstDecodeFail(c, "memory section byte size mismatch");
+    if (!c.d.readExpr(&expr))
+        return c.d.fail("missing initializer end");
 
-    c.module().setMemory(AstResizable(initialSizePages, Some(maxSizePages)));
+    if (expr != Expr::End)
+        return c.d.fail("initializer end isn't an end");
+
     return true;
 }
 
 static bool
 AstDecodeGlobal(AstDecodeContext& c, uint32_t i, AstGlobal* global)
 {
     AstName name;
     if (!AstDecodeGenerateName(c, AstName(u"global"), i, &name))
         return false;
 
     ValType type;
-    if (!c.d.readValType(&type))
-        return AstDecodeFail(c, "bad global type");
-
     uint32_t flags;
-    if (!c.d.readVarU32(&flags))
-        return AstDecodeFail(c, "expected flags");
-
-    if (flags & ~uint32_t(GlobalFlags::AllowedMask))
-        return AstDecodeFail(c, "unexpected bits set in flags");
-
-    if (!AstDecodeExpr(c))
+    if (!DecodeGlobalType(c.d, &type, &flags))
         return false;
 
-    AstDecodeStackItem item = c.iter().getResult();
-    MOZ_ASSERT(item.popped == 0);
-    if (!item.expr)
-        return AstDecodeFail(c, "missing initializer expression");
-
-    AstExpr* init = item.expr;
-
-    if (!AstDecodeEnd(c))
-        return AstDecodeFail(c, "missing initializer end");
-
-    item = c.iter().getResult();
-    MOZ_ASSERT(item.terminationKind == AstDecodeTerminationKind::End);
+    AstExpr* init;
+    if (!AstDecodeInitializerExpression(c, &init, type))
+        return c.d.fail("missing initializer expression");
 
     *global = AstGlobal(name, type, flags, Some(init));
     return true;
 }
 
 static bool
 AstDecodeGlobalSection(AstDecodeContext& c)
 {
     uint32_t sectionStart, sectionSize;
-    if (!c.d.startSection(GlobalSectionId, &sectionStart, &sectionSize))
-        return AstDecodeFail(c, "failed to start section");
+    if (!c.d.startSection(SectionId::Global, &sectionStart, &sectionSize, "global"))
+        return false;
     if (sectionStart == Decoder::NotStarted)
         return true;
 
     uint32_t numGlobals;
     if (!c.d.readVarU32(&numGlobals))
-        return AstDecodeFail(c, "expected number of globals");
+        return c.d.fail("expected number of globals");
 
     for (uint32_t i = 0; i < numGlobals; i++) {
         auto* global = new(c.lifo) AstGlobal;
         if (!AstDecodeGlobal(c, i, global))
             return false;
         if (!c.module().append(global))
             return false;
     }
 
-    if (!c.d.finishSection(sectionStart, sectionSize))
-        return AstDecodeFail(c, "globals section byte size mismatch");
+    if (!c.d.finishSection(sectionStart, sectionSize, "global"))
+        return false;
 
     return true;
 }
 
 static bool
 AstDecodeFunctionExport(AstDecodeContext& c, AstExport** export_)
 {
-    uint32_t funcIndex;
-    if (!c.d.readVarU32(&funcIndex))
-        return AstDecodeFail(c, "expected export internal index");
-
-    if (funcIndex >= c.funcSigs().length())
-        return AstDecodeFail(c, "export function index out of range");
-
     AstName fieldName;
     if (!AstDecodeName(c, &fieldName))
-        return AstDecodeFail(c, "expected export name");
+        return c.d.fail("expected export name");
+
+    uint32_t kindValue;
+    if (!c.d.readVarU32(&kindValue))
+        return c.d.fail("expected export kind");
 
-    *export_ = new(c.lifo) AstExport(fieldName, DefinitionKind::Function,
-                                     AstRef(AstName(), funcIndex));
+    uint32_t index;
+    if (!c.d.readVarU32(&index))
+        return c.d.fail("expected export internal index");
+
+    *export_ = new(c.lifo) AstExport(fieldName, DefinitionKind(kindValue),
+                                     AstRef(AstName(), index));
     if (!*export_)
         return false;
 
     return true;
 }
 
 static bool
 AstDecodeExportSection(AstDecodeContext& c)
 {
     uint32_t sectionStart, sectionSize;
-    if (!c.d.startSection(ExportSectionId, &sectionStart, &sectionSize))
-        return AstDecodeFail(c, "failed to start section");
+    if (!c.d.startSection(SectionId::Export, &sectionStart, &sectionSize, "export"))
+        return false;
     if (sectionStart == Decoder::NotStarted)
         return true;
 
     uint32_t numExports;
     if (!c.d.readVarU32(&numExports))
-        return AstDecodeFail(c, "failed to read number of exports");
+        return c.d.fail("failed to read number of exports");
 
     if (numExports > MaxExports)
-        return AstDecodeFail(c, "too many exports");
+        return c.d.fail("too many exports");
 
     for (uint32_t i = 0; i < numExports; i++) {
         AstExport* export_ = nullptr;
         if (!AstDecodeFunctionExport(c, &export_))
             return false;
         if (!c.module().append(export_))
             return false;
     }
 
-    if (!c.d.finishSection(sectionStart, sectionSize))
-        return AstDecodeFail(c, "export section byte size mismatch");
+    if (!c.d.finishSection(sectionStart, sectionSize, "export"))
+        return false;
 
     return true;
 }
 
 static bool
 AstDecodeFunctionBody(AstDecodeContext &c, uint32_t funcIndex, AstFunc** func)
 {
     uint32_t offset = c.d.currentOffset();
     uint32_t bodySize;
     if (!c.d.readVarU32(&bodySize))
-        return AstDecodeFail(c, "expected number of function body bytes");
+        return c.d.fail("expected number of function body bytes");
 
     if (c.d.bytesRemain() < bodySize)
-        return AstDecodeFail(c, "function body length too big");
+        return c.d.fail("function body length too big");
 
     const uint8_t* bodyBegin = c.d.currentPosition();
     const uint8_t* bodyEnd = bodyBegin + bodySize;
 
     AstDecodeExprIter iter(c.d);
 
     uint32_t sigIndex = c.funcSigs()[funcIndex];
     const AstSig* sig = c.module().sigs()[sigIndex];
@@ -1549,58 +1879,71 @@ AstDecodeFunctionBody(AstDecodeContext &
     AstNameVector localsNames(c.lifo);
     AstExprVector body(c.lifo);
 
     ValTypeVector locals;
     if (!locals.appendAll(sig->args()))
         return false;
 
     if (!DecodeLocalEntries(c.d, &locals))
-        return AstDecodeFail(c, "failed decoding local entries");
+        return c.d.fail("failed decoding local entries");
 
-    c.startFunction(&iter, &locals);
+    c.startFunction(&iter, &locals, sig->ret());
 
     AstName funcName;
-    if (!AstDecodeGenerateName(c, AstName(u"func"), funcIndex, &funcName))
+    if (!AstDecodeGenerateName(c, AstName(u"func"),
+                               c.module().funcImportNames().length() + funcIndex,
+                               &funcName))
         return false;
 
     uint32_t numParams = sig->args().length();
     uint32_t numLocals = locals.length();
     for (uint32_t i = numParams; i < numLocals; i++) {
         if (!vars.append(locals[i]))
             return false;
     }
     for (uint32_t i = 0; i < numLocals; i++) {
         AstName varName;
         if (!AstDecodeGenerateName(c, AstName(u"var"), i, &varName))
             return false;
         if (!localsNames.append(varName))
             return false;
     }
 
-    if (!c.iter().readFunctionStart())
+    if (!c.iter().readFunctionStart(sig->ret()))
+        return false;
+
+    if (!c.depths().append(c.exprs().length()))
         return false;
 
     while (c.d.currentPosition() < bodyEnd) {
         if (!AstDecodeExpr(c))
             return false;
-        AstDecodeStackItem item = c.iter().getResult();
-        body.shrinkBy(item.popped);
-        if (!body.append(item.expr))
+
+        const AstDecodeStackItem& item = c.top();
+        if (!item.expr) { // Expr::End was found
+            c.popBack();
+            break;
+        }
+    }
+
+    for (auto i = c.exprs().begin() + c.depths().back(), e = c.exprs().end();
+         i != e; ++i) {
+        if (!body.append(i->expr))
             return false;
     }
+    c.exprs().shrinkTo(c.depths().popCopy());
 
-    AstDecodeStackItem tmp;
-    if (!c.iter().readFunctionEnd(sig->ret(), &tmp))
+    if (!c.iter().readFunctionEnd())
         return false;
 
     c.endFunction();
 
     if (c.d.currentPosition() != bodyEnd)
-        return AstDecodeFail(c, "function body length mismatch");
+        return c.d.fail("function body length mismatch");
 
     AstRef sigRef;
     if (!AstDecodeGenerateRef(c, AstName(u"type"), sigIndex, &sigRef))
         return false;
 
     *func = new(c.lifo) AstFunc(funcName, sigRef, Move(vars), Move(localsNames), Move(body));
     if (!*func)
         return false;
@@ -1608,144 +1951,202 @@ AstDecodeFunctionBody(AstDecodeContext &
 
     return true;
 }
 
 static bool
 AstDecodeCodeSection(AstDecodeContext &c)
 {
     uint32_t sectionStart, sectionSize;
-    if (!c.d.startSection(CodeSectionId, &sectionStart, &sectionSize))
-        return AstDecodeFail(c, "failed to start section");
+    if (!c.d.startSection(SectionId::Code, &sectionStart, &sectionSize, "code"))
+        return false;
 
     if (sectionStart == Decoder::NotStarted) {
         if (c.funcSigs().length() != 0)
-            return AstDecodeFail(c, "expected function bodies");
+            return c.d.fail("expected function bodies");
 
         return false;
     }
 
     uint32_t numFuncBodies;
     if (!c.d.readVarU32(&numFuncBodies))
-        return AstDecodeFail(c, "expected function body count");
+        return c.d.fail("expected function body count");
 
     if (numFuncBodies != c.funcSigs().length())
-        return AstDecodeFail(c, "function body count does not match function signature count");
+        return c.d.fail("function body count does not match function signature count");
 
     for (uint32_t funcIndex = 0; funcIndex < numFuncBodies; funcIndex++) {
         AstFunc* func;
         if (!AstDecodeFunctionBody(c, funcIndex, &func))
             return false;
         if (!c.module().append(func))
             return false;
     }
 
-    if (!c.d.finishSection(sectionStart, sectionSize))
-        return AstDecodeFail(c, "function section byte size mismatch");
+    if (!c.d.finishSection(sectionStart, sectionSize, "code"))
+        return false;
 
     return true;
 }
 
 static bool
 AstDecodeDataSection(AstDecodeContext &c)
 {
     uint32_t sectionStart, sectionSize;
-    if (!c.d.startSection(DataSectionId, &sectionStart, &sectionSize))
-        return AstDecodeFail(c, "failed to start section");
+    if (!c.d.startSection(SectionId::Data, &sectionStart, &sectionSize, "data"))
+        return false;
     if (sectionStart == Decoder::NotStarted)
         return true;
 
     uint32_t numSegments;
     if (!c.d.readVarU32(&numSegments))
-        return AstDecodeFail(c, "failed to read number of data segments");
+        return c.d.fail("failed to read number of data segments");
 
-    const uint32_t heapLength = c.module().hasMemory() ? c.module().memory().initial() : 0;
+    const uint32_t heapLength = c.module().hasMemory() ? c.module().memory().initial : 0;
 
     for (uint32_t i = 0; i < numSegments; i++) {
         uint32_t dstOffset;
         if (!c.d.readVarU32(&dstOffset))
-            return AstDecodeFail(c, "expected segment destination offset");
+            return c.d.fail("expected segment destination offset");
 
         uint32_t numBytes;
         if (!c.d.readVarU32(&numBytes))
-            return AstDecodeFail(c, "expected segment size");
+            return c.d.fail("expected segment size");
 
         if (dstOffset > heapLength || heapLength - dstOffset < numBytes)
-            return AstDecodeFail(c, "data segment does not fit in memory");
+            return c.d.fail("data segment does not fit in memory");
 
         const uint8_t* src;
         if (!c.d.readBytes(numBytes, &src))
-            return AstDecodeFail(c, "data segment shorter than declared");
+            return c.d.fail("data segment shorter than declared");
 
         char16_t *buffer = static_cast<char16_t *>(c.lifo.alloc(numBytes * sizeof(char16_t)));
         for (size_t i = 0; i < numBytes; i++)
             buffer[i] = src[i];
 
         AstExpr* offset = new(c.lifo) AstConst(Val(dstOffset));
         if (!offset)
             return false;
 
         AstName name(buffer, numBytes);
         AstDataSegment* segment = new(c.lifo) AstDataSegment(offset, name);
         if (!segment || !c.module().append(segment))
             return false;
     }
 
-    if (!c.d.finishSection(sectionStart, sectionSize))
-        return AstDecodeFail(c, "data section byte size mismatch");
+    if (!c.d.finishSection(sectionStart, sectionSize, "data"))
+        return false;
 
     return true;
 }
 
 
+static bool
+AstDecodeElemSection(AstDecodeContext &c)
+{
+    uint32_t sectionStart, sectionSize;
+    if (!c.d.startSection(SectionId::Elem, &sectionStart, &sectionSize, "elem"))
+        return false;
+    if (sectionStart == Decoder::NotStarted)
+        return true;
+
+    uint32_t numElems;
+    if (!c.d.readVarU32(&numElems))
+        return c.d.fail("failed to read number of table elements");
+
+    for (uint32_t i = 0; i < numElems; i++) {
+        uint32_t tableIndex;
+        if (!c.d.readVarU32(&tableIndex))
+            return c.d.fail("expected table index for element");
+
+        if (tableIndex != 0)
+            return c.d.fail("non-zero table index for element");
+
+        AstExpr* offset;
+        if (!AstDecodeInitializerExpression(c, &offset, ValType::I32))
+            return c.d.fail("missing initializer expression");
+
+        uint32_t count;
+        if (!c.d.readVarU32(&count))
+            return c.d.fail("expected element count");
+
+        AstRefVector elems(c.lifo);
+        if (!elems.resize(count))
+            return false;
+
+        for (uint32_t i = 0; i < count; i++) {
+            uint32_t index;
+            if (!c.d.readVarU32(&index))
+                return c.d.fail("expected element index");
+
+            elems[i] = AstRef(AstName(), index);
+        }
+
+        AstElemSegment* segment = new(c.lifo) AstElemSegment(offset, Move(elems));
+        if (!segment || !c.module().append(segment))
+            return false;
+    }
+
+    if (!c.d.finishSection(sectionStart, sectionSize, "elem"))
+        return false;
+
+    return true;
+}
+
+static bool
+AstDecodeStartSection(AstDecodeContext &c)
+{
+    uint32_t sectionStart, sectionSize;
+    if (!c.d.startSection(SectionId::Start, &sectionStart, &sectionSize, "start"))
+        return false;
+    if (sectionStart == Decoder::NotStarted)
+        return true;
+
+    uint32_t funcIndex;
+    if (!c.d.readVarU32(&funcIndex))
+        return c.d.fail("failed to read start func index");
+
+    c.module().setStartFunc(AstStartFunc(AstRef(AstName(), funcIndex)));
+
+    if (!c.d.finishSection(sectionStart, sectionSize, "start"))
+        return false;
+
+    return true;
+}
+
 bool
 wasm::BinaryToAst(JSContext* cx, const uint8_t* bytes, uint32_t length,
                   LifoAlloc& lifo, AstModule** module)
 {
     AstModule* result = new(lifo) AstModule(lifo);
     if (!result->init())
         return false;
 
-    Decoder d(bytes, bytes + length);
+    UniqueChars error;
+    Decoder d(bytes, bytes + length, &error);
     AstDecodeContext c(cx, lifo, d, *result, true);
 
-    uint32_t u32;
-    if (!d.readFixedU32(&u32) || u32 != MagicNumber)
-        return AstDecodeFail(c, "failed to match magic number");
-
-    if (!d.readFixedU32(&u32) || u32 != EncodingVersion)
-        return AstDecodeFail(c, "failed to match binary version");
-
-    if (!AstDecodeTypeSection(c))
-        return false;
-
-    if (!AstDecodeImportSection(c))
-        return false;
-
-    if (!AstDecodeFunctionSection(c))
-        return false;
-
-    if (!AstDecodeTableSection(c))
+    if (!DecodePreamble(d) ||
+        !AstDecodeTypeSection(c) ||
+        !AstDecodeImportSection(c) ||
+        !AstDecodeFunctionSection(c) ||
+        !AstDecodeTableSection(c) ||
+        !AstDecodeMemorySection(c) ||
+        !AstDecodeGlobalSection(c) ||
+        !AstDecodeExportSection(c) ||
+        !AstDecodeStartSection(c) ||
+        !AstDecodeElemSection(c) ||
+        !AstDecodeCodeSection(c) ||
+        !AstDecodeDataSection(c) ||
+        !DecodeUnknownSections(c.d))
+    {
+        if (error) {
+            JS_ReportErrorNumber(c.cx, GetErrorMessage, nullptr, JSMSG_WASM_DECODE_FAIL,
+                                 error.get());
+            return false;
+        }
+        ReportOutOfMemory(c.cx);
         return false;
-
-    if (!AstDecodeMemorySection(c))
-        return false;
-
-    if (!AstDecodeGlobalSection(c))
-        return false;
-
-    if (!AstDecodeExportSection(c))
-        return false;
-
-    if (!AstDecodeCodeSection(c))
-        return false;
-
-    if (!AstDecodeDataSection(c))
-        return false;
-
-    while (!d.done()) {
-        if (!d.skipSection())
-            return AstDecodeFail(c, "failed to skip unknown section at end");
     }
 
     *module = result;
     return true;
 }
--- a/js/src/asmjs/WasmBinaryToExperimentalText.cpp
+++ b/js/src/asmjs/WasmBinaryToExperimentalText.cpp
@@ -154,20 +154,22 @@ IsDropValueExpr(AstExpr& expr)
     // the function return type.
     switch (expr.kind()) {
       case AstExprKind::Branch:
         return !expr.as<AstBranch>().maybeValue();
       case AstExprKind::BranchTable:
         return !expr.as<AstBranchTable>().maybeValue();
       case AstExprKind::If:
         return !expr.as<AstIf>().hasElse();
-      case AstExprKind::NullaryOperator:
-        return expr.as<AstNullaryOperator>().expr() == Expr::Nop;
+      case AstExprKind::Nop:
+      case AstExprKind::Drop:
       case AstExprKind::Unreachable:
       case AstExprKind::Return:
+      case AstExprKind::SetLocal:
+      case AstExprKind::Store:
         return true;
       default:
         return false;
     }
 }
 
 static bool
 PrintIndent(WasmPrintContext& c)
@@ -372,25 +374,39 @@ PrintBlockLevelExpr(WasmPrintContext& c,
 // binary format parsing and rendering
 
 static bool
 PrintNullaryOperator(WasmPrintContext& c, AstNullaryOperator& op)
 {
     const char* opStr;
 
     switch (op.expr()) {
-        case Expr::Nop:             opStr = "nop"; break;
         case Expr::CurrentMemory:   opStr = "curent_memory"; break;
         default:  return false;
     }
 
     return c.buffer.append(opStr, strlen(opStr));
 }
 
 static bool
+PrintNop(WasmPrintContext& c)
+{
+    return c.buffer.append("nop");
+}
+
+static bool
+PrintDrop(WasmPrintContext& c, AstDrop& drop)
+{
+    if (!PrintExpr(c, drop.value()))
+        return false;
+
+    return true;
+}
+
+static bool
 PrintUnreachable(WasmPrintContext& c, AstUnreachable& unreachable)
 {
     return c.buffer.append("unreachable");
 }
 
 static bool
 PrintCallArgs(WasmPrintContext& c, const AstExprVector& args)
 {
@@ -442,30 +458,34 @@ PrintCall(WasmPrintContext& c, AstCall& 
 static bool
 PrintCallIndirect(WasmPrintContext& c, AstCallIndirect& call)
 {
     if (!c.buffer.append("call_indirect "))
         return false;
     if (!PrintRef(c, call.sig()))
         return false;
 
+    if (!c.buffer.append(' '))
+        return false;
+
+    if (!PrintCallArgs(c, call.args()))
+        return false;
+
     if (!c.buffer.append(" ["))
         return false;
 
     PrintOperatorPrecedence lastPrecedence = c.currentPrecedence;
     c.currentPrecedence = ExpressionPrecedence;
 
     if (!PrintExpr(c, *call.index()))
         return false;
 
     c.currentPrecedence = lastPrecedence;
 
-    if (!c.buffer.append("] "))
-        return false;
-    if (!PrintCallArgs(c, call.args()))
+    if (!c.buffer.append(']'))
         return false;
     return true;
 }
 
 static bool
 PrintConst(WasmPrintContext& c, AstConst& cst)
 {
     switch (ToExprType(cst.val().type())) {
@@ -526,16 +546,45 @@ PrintSetLocal(WasmPrintContext& c, AstSe
             return false;
     }
 
     c.currentPrecedence = lastPrecedence;
     return true;
 }
 
 static bool
+PrintTeeLocal(WasmPrintContext& c, AstTeeLocal& sl)
+{
+    PrintOperatorPrecedence lastPrecedence = c.currentPrecedence;
+
+    if (!c.f.reduceParens || lastPrecedence > AssignmentPrecedence) {
+        if (!c.buffer.append("("))
+            return false;
+    }
+
+    if (!PrintRef(c, sl.local()))
+        return false;
+    if (!c.buffer.append(" = "))
+        return false;
+
+    c.currentPrecedence = AssignmentPrecedence;
+
+    if (!PrintExpr(c, sl.value()))
+        return false;
+
+    if (!c.f.reduceParens || lastPrecedence > AssignmentPrecedence) {
+        if (!c.buffer.append(")"))
+            return false;
+    }
+
+    c.currentPrecedence = lastPrecedence;
+    return true;
+}
+
+static bool
 PrintExprList(WasmPrintContext& c, const AstExprVector& exprs, uint32_t startFrom = 0)
 {
     for (uint32_t i = startFrom; i < exprs.length(); i++) {
         if (!PrintBlockLevelExpr(c, *exprs[i], i + 1 == exprs.length()))
             return false;
     }
     return true;
 }
@@ -553,20 +602,20 @@ PrintGroupedBlock(WasmPrintContext& c, A
     c.indent++;
     if (!PrintExprList(c, block.exprs(), skip))
         return false;
     c.indent--;
     if (!PrintIndent(c))
         return false;
 
     // If no br/br_if/br_table refer this block, use some non-existent label.
-    if (block.breakName().empty())
+    if (block.name().empty())
         return c.buffer.append("$label:\n");
 
-    if (!PrintName(c, block.breakName()))
+    if (!PrintName(c, block.name()))
         return false;
     if (!c.buffer.append(":\n"))
         return false;
     return true;
 }
 
 static bool
 PrintBlockName(WasmPrintContext& c, const AstName& name) {
@@ -585,52 +634,57 @@ PrintBlock(WasmPrintContext& c, AstBlock
 {
     PrintOperatorPrecedence lastPrecedence = c.currentPrecedence;
     if (block.expr() == Expr::Block) {
         if (!c.buffer.append("{\n"))
             return false;
     } else if (block.expr() == Expr::Loop) {
         if (!c.buffer.append("loop"))
             return false;
-        if (!block.continueName().empty()) {
+        if (!block.name().empty()) {
             if (!c.buffer.append(" "))
                 return false;
-            if (!PrintName(c, block.continueName()))
+            if (!PrintName(c, block.name()))
                 return false;
         }
         if (!c.buffer.append(" {\n"))
             return false;
     } else
         return false;
 
     c.currentPrecedence = ExpressionPrecedence;
 
     bool skip = 0;
     if (c.f.groupBlocks && block.expr() == Expr::Block &&
         block.exprs().length() > 0 && block.exprs()[0]->kind() == AstExprKind::Block) {
-        if (!PrintGroupedBlock(c, *static_cast<AstBlock*>(block.exprs()[0])))
-            return false;
-        skip = 1;
-        if (block.exprs().length() == 1 && block.breakName().empty()) {
-          // Special case to resolve ambiguity in parsing of optional end block label.
-          if (!PrintIndent(c))
-              return false;
-          if (!c.buffer.append("$exit$:\n"))
-              return false;
+        AstBlock* innerBlock = static_cast<AstBlock*>(block.exprs()[0]);
+        if (innerBlock->expr() == Expr::Block) {
+            if (!PrintGroupedBlock(c, *innerBlock))
+                return false;
+            skip = 1;
+            if (block.exprs().length() == 1 && block.name().empty()) {
+              // Special case to resolve ambiguity in parsing of optional end block label.
+              if (!PrintIndent(c))
+                  return false;
+              if (!c.buffer.append("$exit$:\n"))
+                  return false;
+            }
         }
     }
 
     c.indent++;
     if (!PrintExprList(c, block.exprs(), skip))
         return false;
     c.indent--;
     c.currentPrecedence = lastPrecedence;
 
-    if (!PrintBlockName(c, block.breakName()))
-      return false;
+    if (block.expr() != Expr::Loop) {
+        if (!PrintBlockName(c, block.name()))
+          return false;
+    }
 
     if (!PrintIndent(c))
         return false;
 
     return c.buffer.append("}");
 }
 
 static bool
@@ -1004,30 +1058,30 @@ PrintIf(WasmPrintContext& c, AstIf& if_)
     if (!c.buffer.append(") {\n"))
         return false;
 
     c.indent++;
     if (!PrintExprList(c, if_.thenExprs()))
         return false;
     c.indent--;
 
-    if (!PrintBlockName(c, if_.thenName()))
+    if (!PrintBlockName(c, if_.name()))
         return false;
 
     if (if_.hasElse()) {
         if (!PrintIndent(c))
             return false;
         if (!c.buffer.append("} else {\n"))
             return false;
 
         c.indent++;
         if (!PrintExprList(c, if_.elseExprs()))
             return false;
         c.indent--;
-        if (!PrintBlockName(c, if_.elseName()))
+        if (!PrintBlockName(c, if_.name()))
             return false;
     }
 
     if (!PrintIndent(c))
         return false;
 
     c.currentPrecedence = lastPrecedence;
 
@@ -1342,40 +1396,67 @@ PrintReturn(WasmPrintContext& c, AstRetu
         if (!PrintExpr(c, *(ret.maybeExpr())))
             return false;
     }
 
     return true;
 }
 
 static bool
+PrintFirst(WasmPrintContext& c, AstFirst& first)
+{
+    if (!c.buffer.append("first("))
+        return false;
+
+    for (uint32_t i = 0; i < first.exprs().length(); i++) {
+        if (!PrintExpr(c, *first.exprs()[i]))
+            return false;
+        if (i + 1 == first.exprs().length())
+            break;
+        if (!c.buffer.append(", "))
+            return false;
+    }
+
+    if (!c.buffer.append(")"))
+        return false;
+
+    return true;
+}
+
+static bool
 PrintExpr(WasmPrintContext& c, AstExpr& expr)
 {
     if (c.maybeSourceMap) {
         uint32_t lineno = c.buffer.lineno();
         uint32_t column = c.buffer.column();
         if (!c.maybeSourceMap->exprlocs().emplaceBack(lineno, column, expr.offset()))
             return false;
     }
 
     switch (expr.kind()) {
+      case AstExprKind::Nop:
+        return PrintNop(c);
+      case AstExprKind::Drop:
+        return PrintDrop(c, expr.as<AstDrop>());
       case AstExprKind::NullaryOperator:
         return PrintNullaryOperator(c, expr.as<AstNullaryOperator>());
       case AstExprKind::Unreachable:
         return PrintUnreachable(c, expr.as<AstUnreachable>());
       case AstExprKind::Call:
         return PrintCall(c, expr.as<AstCall>());
       case AstExprKind::CallIndirect:
         return PrintCallIndirect(c, expr.as<AstCallIndirect>());
       case AstExprKind::Const:
         return PrintConst(c, expr.as<AstConst>());
       case AstExprKind::GetLocal:
         return PrintGetLocal(c, expr.as<AstGetLocal>());
       case AstExprKind::SetLocal:
         return PrintSetLocal(c, expr.as<AstSetLocal>());
+      case AstExprKind::TeeLocal:
+        return PrintTeeLocal(c, expr.as<AstTeeLocal>());
       case AstExprKind::Block:
         return PrintBlock(c, expr.as<AstBlock>());
       case AstExprKind::If:
         return PrintIf(c, expr.as<AstIf>());
       case AstExprKind::UnaryOperator:
         return PrintUnaryOperator(c, expr.as<AstUnaryOperator>());
       case AstExprKind::BinaryOperator:
         return PrintBinaryOperator(c, expr.as<AstBinaryOperator>());
@@ -1390,16 +1471,18 @@ PrintExpr(WasmPrintContext& c, AstExpr& 
       case AstExprKind::Store:
         return PrintStore(c, expr.as<AstStore>());
       case AstExprKind::Branch:
         return PrintBranch(c, expr.as<AstBranch>());
       case AstExprKind::BranchTable:
         return PrintBrTable(c, expr.as<AstBranchTable>());
       case AstExprKind::Return:
         return PrintReturn(c, expr.as<AstReturn>());
+      case AstExprKind::First:
+        return PrintFirst(c, expr.as<AstFirst>());
       default:
         // Note: it's important not to remove this default since readExpr()
         // can return Expr values for which there is no enumerator.
         break;
     }
 
     return false;
 }
@@ -1491,22 +1574,25 @@ PrintTableSection(WasmPrintContext& c, c
 
     const AstElemSegment& segment = *module.elemSegments()[0];
 
     if (!c.buffer.append("table ["))
         return false;
 
     for (uint32_t i = 0; i < segment.elems().length(); i++) {
         const AstRef& elem = segment.elems()[i];
-        AstFunc* func = module.funcs()[elem.index()];
-        if (func->name().empty()) {
-            if (!PrintInt32(c, elem.index()))
+        uint32_t index = elem.index();
+        AstName name = index < module.funcImportNames().length()
+                           ? module.funcImportNames()[index]
+                           : module.funcs()[index - module.funcImportNames().length()]->name();
+        if (name.empty()) {
+            if (!PrintInt32(c, index))
                 return false;
         } else {
-          if (!PrintName(c, func->name()))
+          if (!PrintName(c, name))
               return false;
         }
         if (i + 1 == segment.elems().length())
             break;
         if (!c.buffer.append(", "))
             return false;
     }
 
@@ -1570,51 +1656,58 @@ PrintImportSection(WasmPrintContext& c, 
       if (!c.buffer.append("\n"))
           return false;
     }
 
     return true;
 }
 
 static bool
-PrintExport(WasmPrintContext& c, AstExport& export_, const AstModule::FuncVector& funcs)
+PrintExport(WasmPrintContext& c, AstExport& export_,
+            const AstModule::NameVector& funcImportNames,
+            const AstModule::FuncVector& funcs)
 {
     if (!PrintIndent(c))
         return false;
     if (!c.buffer.append("export "))
         return false;
     if (export_.kind() == DefinitionKind::Memory) {
         if (!c.buffer.append("memory"))
           return false;
     } else {
-        const AstFunc* func = funcs[export_.ref().index()];
-        if (func->name().empty()) {
-            if (!PrintInt32(c, export_.ref().index()))
+        uint32_t index = export_.ref().index();
+        AstName name = index < funcImportNames.length()
+                           ? funcImportNames[index]
+                           : funcs[index - funcImportNames.length()]->name();
+        if (name.empty()) {
+            if (!PrintInt32(c, index))
                 return false;
         } else {
-            if (!PrintName(c, func->name()))
+            if (!PrintName(c, name))
                 return false;
         }
     }
     if (!c.buffer.append(" as \""))
         return false;
     if (!PrintEscapedString(c, export_.name()))
         return false;
     if (!c.buffer.append("\";\n"))
         return false;
 
     return true;
 }
 
 static bool
-PrintExportSection(WasmPrintContext& c, const AstModule::ExportVector& exports, const AstModule::FuncVector& funcs)
+PrintExportSection(WasmPrintContext& c, const AstModule::ExportVector& exports,
+                   const AstModule::NameVector& funcImportNames,
+                   const AstModule::FuncVector& funcs)
 {
     uint32_t numExports = exports.length();
     for (uint32_t i = 0; i < numExports; i++) {
-        if (!PrintExport(c, *exports[i], funcs))
+        if (!PrintExport(c, *exports[i], funcImportNames, funcs))
             return false;
     }
     if (numExports) {
       if (!c.buffer.append("\n"))
           return false;
     }
     return true;
 }
@@ -1717,22 +1810,22 @@ PrintDataSection(WasmPrintContext& c, co
 {
     if (!module.hasMemory())
         return true;
 
     if (!PrintIndent(c))
         return false;
     if (!c.buffer.append("memory "))
         return false;
-    if (!PrintInt32(c, module.memory().initial()))
+    if (!PrintInt32(c, module.memory().initial))
        return false;
-    if (module.memory().maximum()) {
+    if (module.memory().maximum) {
         if (!c.buffer.append(", "))
             return false;
-        if (!PrintInt32(c, *module.memory().maximum()))
+        if (!PrintInt32(c, *module.memory().maximum))
             return false;
     }
 
     c.indent++;
 
     uint32_t numSegments = module.dataSegments().length();
     if (!numSegments) {
       if (!c.buffer.append(" {}\n\n"))
@@ -1774,17 +1867,17 @@ PrintModule(WasmPrintContext& c, AstModu
         return false;
 
     if (!PrintImportSection(c, module.imports(), module.sigs()))
         return false;
 
     if (!PrintTableSection(c, module))
         return false;
 
-    if (!PrintExportSection(c, module.exports(), module.funcs()))
+    if (!PrintExportSection(c, module.exports(), module.funcImportNames(), module.funcs()))
         return false;
 
     if (!PrintCodeSection(c, module.funcs(), module.sigs()))
         return false;
 
     if (!PrintDataSection(c, module))
         return false;
 
--- a/js/src/asmjs/WasmBinaryToText.cpp
+++ b/js/src/asmjs/WasmBinaryToText.cpp
@@ -194,105 +194,140 @@ RenderRef(WasmRenderContext& c, const As
 {
     if (ref.name().empty())
         return RenderInt32(c, ref.index());
 
     return RenderName(c, ref.name());
 }
 
 static bool
-RenderExpr(WasmRenderContext& c, AstExpr& expr);
+RenderBlockNameAndSignature(WasmRenderContext& c, const AstName& name, ExprType type)
+{
+    if (!name.empty()) {
+        if (!c.buffer.append(' '))
+            return false;
+
+        if (!RenderName(c, name))
+            return false;
+    }
+
+    if (!IsVoid(type)) {
+        if (!c.buffer.append(' '))
+            return false;
+
+        if (!RenderExprType(c, type))
+            return false;
+    }
+
+    return true;
+}
 
 static bool
-RenderFullLine(WasmRenderContext& c, AstExpr& expr)
-{
-    if (!RenderIndent(c))
-        return false;
-    if (!RenderExpr(c, expr))
-        return false;
-    return c.buffer.append('\n');
-}
+RenderExpr(WasmRenderContext& c, AstExpr& expr);
 
 /*****************************************************************************/
 // binary format parsing and rendering
 
 static bool
-RenderUnreachable(WasmRenderContext& c, AstUnreachable& unreachable)
+RenderNop(WasmRenderContext& c)
+{
+    if (!RenderIndent(c))
+        return false;
+    return c.buffer.append("nop\n");
+}
+
+static bool
+RenderDrop(WasmRenderContext& c, AstDrop& drop)
 {
-    return c.buffer.append("(trap)");
+    if (!RenderExpr(c, drop.value()))
+        return false;
+
+    if (!RenderIndent(c))
+        return false;
+    if (!c.buffer.append("drop\n"))
+        return false;
+    return true;
+}
+
+static bool
+RenderUnreachable(WasmRenderContext& c)
+{
+    if (!RenderIndent(c))
+        return false;
+    return c.buffer.append("unreachable\n");
 }
 
 static bool
 RenderCallArgs(WasmRenderContext& c, const AstExprVector& args)
 {
     for (uint32_t i = 0; i < args.length(); i++) {
-        if (!c.buffer.append(" "))
-            return false;
         if (!RenderExpr(c, *args[i]))
             return false;
     }
 
     return true;
 }
 
 static bool
 RenderCall(WasmRenderContext& c, AstCall& call)
 {
+    if (!RenderCallArgs(c, call.args()))
+        return false;
+
+    if (!RenderIndent(c))
+        return false;
+
     if (call.expr() == Expr::Call) {
-        if (!c.buffer.append("(call "))
+        if (!c.buffer.append("call "))
             return false;
     } else if (call.expr() == Expr::CallImport) {
-        if (!c.buffer.append("(call_import "))
+        if (!c.buffer.append("call_import "))
             return false;
     } else {
         return false;
     }
 
     if (!RenderRef(c, call.func()))
         return false;
 
-    if (!RenderCallArgs(c, call.args()))
-        return false;
-
-    if (!c.buffer.append(")"))
+    if (!c.buffer.append('\n'))
         return false;
 
     return true;
 }
 
 static bool
 RenderCallIndirect(WasmRenderContext& c, AstCallIndirect& call)
 {
-    if (!c.buffer.append("(call_indirect "))
-        return false;
-    if (!RenderRef(c, call.sig()))
-        return false;
-
-    if (!c.buffer.append(" "))
+    if (!RenderCallArgs(c, call.args()))
         return false;
 
     if (!RenderExpr(c, *call.index()))
         return false;
 
-    if (!c.buffer.append(" "))
-        return false;
-    if (!RenderCallArgs(c, call.args()))
+    if (!RenderIndent(c))
         return false;
 
-    if (!c.buffer.append(")"))
+    if (!c.buffer.append("call_indirect "))
+        return false;
+    if (!RenderRef(c, call.sig()))
+        return false;
+
+    if (!c.buffer.append('\n'))
         return false;
 
     return true;
 }
 
 static bool
 RenderConst(WasmRenderContext& c, AstConst& cst)
 {
-    if (!c.buffer.append('('))
+    if (!RenderIndent(c))
         return false;
+
     if (!RenderValType(c, cst.val().type()))
         return false;
     if (!c.buffer.append(".const "))
         return false;
 
     switch (ToExprType(cst.val().type())) {
       case ExprType::I32:
         if (!RenderInt32(c, (uint32_t)cst.val().i32()))
@@ -309,123 +344,151 @@ RenderConst(WasmRenderContext& c, AstCon
       case ExprType::F64:
         if (!RenderDouble(c, cst.val().f64()))
             return false;
         break;
       default:
         return false;
     }
 
-    if (!c.buffer.append(")"))
+    if (!c.buffer.append('\n'))
         return false;
     return true;
 }
 
 static bool
 RenderGetLocal(WasmRenderContext& c, AstGetLocal& gl)
 {
-    if (!c.buffer.append("(get_local "))
+    if (!RenderIndent(c))
+        return false;
+
+    if (!c.buffer.append("get_local "))
         return false;
     if (!RenderRef(c, gl.local()))
         return false;
-    if (!c.buffer.append(")"))
+    if (!c.buffer.append('\n'))
         return false;
     return true;
 }
 
 static bool
 RenderSetLocal(WasmRenderContext& c, AstSetLocal& sl)
-{
-    if (!c.buffer.append("(set_local "))
+ {
+    if (!RenderExpr(c, sl.value()))
+        return false;
+
+    if (!RenderIndent(c))
+        return false;
+
+    if (!c.buffer.append("set_local "))
         return false;
     if (!RenderRef(c, sl.local()))
         return false;
-    if (!c.buffer.append(" "))
+
+    if (!c.buffer.append('\n'))
+        return false;
+    return true;
+}
+
+static bool
+RenderTeeLocal(WasmRenderContext& c, AstTeeLocal& tl)
+{
+    if (!RenderExpr(c, tl.value()))
         return false;
 
-    if (!RenderExpr(c, sl.value()))
+    if (!RenderIndent(c))
         return false;
 
-    if (!c.buffer.append(")"))
+    if (!c.buffer.append("tee_local "))
+        return false;
+    if (!RenderRef(c, tl.local()))
+        return false;
+
+    if (!c.buffer.append('\n'))
         return false;
     return true;
 }
 
 static bool
 RenderExprList(WasmRenderContext& c, const AstExprVector& exprs)
 {
     for (uint32_t i = 0; i < exprs.length(); i++) {
-        if (!c.buffer.append(" "))
-            return false;
         if (!RenderExpr(c, *exprs[i]))
             return false;
     }
     return true;
 }
 
 static bool
 RenderBlock(WasmRenderContext& c, AstBlock& block)
 {
+    if (!RenderIndent(c))
+        return false;
+
     if (block.expr() == Expr::Block) {
-        if (!c.buffer.append("(block "))
-            return false;
-        if (!RenderName(c, block.breakName()))
+        if (!c.buffer.append("block"))
             return false;
     } else if (block.expr() == Expr::Loop) {
-        if (!c.buffer.append("(loop "))
+        if (!c.buffer.append("loop"))
             return false;
-        if (block.breakName().empty() && !block.continueName().empty()) {
-            // Giving auto label if continue label is present.
-            if (!c.buffer.append("$exit$"))
-                return false;
-        } else {
-            if (!RenderName(c, block.breakName()))
-                return false;
-        }
-        if (!block.continueName().empty()) {
-          if (!c.buffer.append(" "))
-              return false;
-          if (!RenderName(c, block.continueName()))
-              return false;
-        }
     } else
         return false;
 
+    if (!RenderBlockNameAndSignature(c, block.name(), block.type()))
+        return false;
+
+    if (!c.buffer.append('\n'))
+        return false;
+
     c.indent++;
     if (!RenderExprList(c, block.exprs()))
         return false;
     c.indent--;
 
-    return c.buffer.append(")");
+    if (!RenderIndent(c))
+        return false;
+
+    return c.buffer.append("end\n");
+}
+
+static bool
+RenderFirst(WasmRenderContext& c, AstFirst& first)
+{
+    if (!RenderExprList(c, first.exprs()))
+        return false;
+
+    return true;
 }
 
 static bool
 RenderNullaryOperator(WasmRenderContext& c, AstNullaryOperator& op)
 {
-    if (!c.buffer.append("("))
+    if (!RenderIndent(c))
       return false;
 
     const char* opStr;
     switch (op.expr()) {
-      case Expr::Nop:               opStr = "nop"; break;
       case Expr::CurrentMemory:     opStr = "current_memory"; break;
       default: return false;
     }
 
     if (!c.buffer.append(opStr, strlen(opStr)))
         return false;
 
-    return c.buffer.append(")");
+    return c.buffer.append('\n');
 }
 
 static bool
 RenderUnaryOperator(WasmRenderContext& c, AstUnaryOperator& op)
 {
-    if (!c.buffer.append("("))
-      return false;
+    if (!RenderExpr(c, *op.op()))
+        return false;
+
+    if (!RenderIndent(c))
+        return false;
 
     const char* opStr;
     switch (op.expr()) {
       case Expr::I32Eqz:     opStr = "i32.eqz"; break;
       case Expr::I32Clz:     opStr = "i32.clz"; break;
       case Expr::I32Ctz:     opStr = "i32.ctz"; break;
       case Expr::I32Popcnt:  opStr = "i32.popcnt"; break;
       case Expr::I64Clz:     opStr = "i64.clz"; break;
@@ -444,30 +507,29 @@ RenderUnaryOperator(WasmRenderContext& c
       case Expr::F64Floor:   opStr = "f64.floor"; break;
       case Expr::F64Sqrt:    opStr = "f64.sqrt"; break;
       case Expr::GrowMemory: opStr = "grow_memory"; break;
       default: return false;
     }
     if (!c.buffer.append(opStr, strlen(opStr)))
         return false;
 
-    if (!c.buffer.append(" "))
-        return false;
-
-    if (!RenderExpr(c, *op.op()))
-        return false;
-
-    return c.buffer.append(")");
+    return c.buffer.append('\n');
 }
 
 static bool
 RenderBinaryOperator(WasmRenderContext& c, AstBinaryOperator& op)
 {
-    if (!c.buffer.append("("))
-      return false;
+    if (!RenderExpr(c, *op.lhs()))
+        return false;
+    if (!RenderExpr(c, *op.rhs()))
+        return false;
+
+    if (!RenderIndent(c))
+        return false;
 
     const char* opStr;
     switch (op.expr()) {
       case Expr::I32Add:      opStr = "i32.add"; break;
       case Expr::I32Sub:      opStr = "i32.sub"; break;
       case Expr::I32Mul:      opStr = "i32.mul"; break;
       case Expr::I32DivS:     opStr = "i32.div_s"; break;
       case Expr::I32DivU:     opStr = "i32.div_u"; break;
@@ -505,67 +567,59 @@ RenderBinaryOperator(WasmRenderContext& 
       case Expr::F64Div:      opStr = "f64.div"; break;
       case Expr::F64Min:      opStr = "f64.min"; break;
       case Expr::F64Max:      opStr = "f64.max"; break;
       default: return false;
     }
     if (!c.buffer.append(opStr, strlen(opStr)))
         return false;
 
-    if (!c.buffer.append(" "))
-        return false;
-    if (!RenderExpr(c, *op.lhs()))
-        return false;
-    if (!c.buffer.append(" "))
-        return false;
-    if (!RenderExpr(c, *op.rhs()))
-        return false;
-    if (!c.buffer.append(")"))
+    if (!c.buffer.append('\n'))
         return false;
 
     return true;
 }
 
 static bool
 RenderTernaryOperator(WasmRenderContext& c, AstTernaryOperator& op)
 {
-    if (!c.buffer.append("("))
+    if (!RenderExpr(c, *op.op0()))
+        return false;
+    if (!RenderExpr(c, *op.op1()))
+        return false;
+    if (!RenderExpr(c, *op.op2()))
+        return false;
+
+    if (!RenderIndent(c))
         return false;
 
     const char* opStr;
     switch (op.expr()) {
       case Expr::Select: opStr = "select"; break;
       default: return false;
     }
     if (!c.buffer.append(opStr, strlen(opStr)))
         return false;
 
-    if (!c.buffer.append(" "))
-        return false;
-    if (!RenderExpr(c, *op.op0()))
-        return false;
-    if (!c.buffer.append(" "))
-        return false;
-    if (!RenderExpr(c, *op.op1()))
-        return false;
-    if (!c.buffer.append(" "))
-        return false;
-    if (!RenderExpr(c, *op.op2()))
-        return false;
-    if (!c.buffer.append(")"))
+    if (!c.buffer.append('\n'))
         return false;
 
     return true;
 }
 
 static bool
 RenderComparisonOperator(WasmRenderContext& c, AstComparisonOperator& op)
 {
-    if (!c.buffer.append("("))
-      return false;
+    if (!RenderExpr(c, *op.lhs()))
+        return false;
+    if (!RenderExpr(c, *op.rhs()))
+        return false;
+
+    if (!RenderIndent(c))
+        return false;
 
     const char* opStr;
     switch (op.expr()) {
       case Expr::I32Eq:  opStr = "i32.eq"; break;
       case Expr::I32Ne:  opStr = "i32.ne"; break;
       case Expr::I32LtS: opStr = "i32.lt_s"; break;
       case Expr::I32LtU: opStr = "i32.lt_u"; break;
       case Expr::I32LeS: opStr = "i32.le_s"; break;
@@ -596,35 +650,30 @@ RenderComparisonOperator(WasmRenderConte
       case Expr::F64Le:  opStr = "f64.le"; break;
       case Expr::F64Gt:  opStr = "f64.gt"; break;
       case Expr::F64Ge:  opStr = "f64.ge"; break;
       default: return false;
     }
     if (!c.buffer.append(opStr, strlen(opStr)))
         return false;
 
-    if (!c.buffer.append(" "))
-        return false;
-    if (!RenderExpr(c, *op.lhs()))
-        return false;
-    if (!c.buffer.append(" "))
-        return false;
-    if (!RenderExpr(c, *op.rhs()))
-        return false;
-    if (!c.buffer.append(")"))
+    if (!c.buffer.append('\n'))
         return false;
 
     return true;
 }
 
 static bool
 RenderConversionOperator(WasmRenderContext& c, AstConversionOperator& op)
 {
-    if (!c.buffer.append("("))
-      return false;
+    if (!RenderExpr(c, *op.op()))
+        return false;
+
+    if (!RenderIndent(c))
+        return false;
 
     const char* opStr;
     switch (op.expr()) {
       case Expr::I32WrapI64:        opStr = "i32.wrap/i64"; break;
       case Expr::I32TruncSF32:      opStr = "i32.trunc_s/f32"; break;
       case Expr::I32TruncUF32:      opStr = "i32.trunc_u/f32"; break;
       case Expr::I32ReinterpretF32: opStr = "i32.reinterpret/f32"; break;
       case Expr::I32TruncSF64:      opStr = "i32.trunc_s/f64"; break;
@@ -643,63 +692,83 @@ RenderConversionOperator(WasmRenderConte
       case Expr::F32ConvertUI64:    opStr = "f32.convert_u/i64"; break;
       case Expr::F32DemoteF64:      opStr = "f32.demote/f64"; break;
       case Expr::F64ConvertSI32:    opStr = "f64.convert_s/i32"; break;
       case Expr::F64ConvertUI32:    opStr = "f64.convert_u/i32"; break;
       case Expr::F64ConvertSI64:    opStr = "f64.convert_s/i64"; break;
       case Expr::F64ConvertUI64:    opStr = "f64.convert_u/i64"; break;
       case Expr::F64ReinterpretI64: opStr = "f64.reinterpret/i64"; break;
       case Expr::F64PromoteF32:     opStr = "f64.promote/f32"; break;
+      case Expr::I32Eqz:            opStr = "i32.eqz"; break;
+      case Expr::I64Eqz:            opStr = "i64.eqz"; break;
       default: return false;
     }
     if (!c.buffer.append(opStr, strlen(opStr)))
         return false;
 
-    if (!c.buffer.append(" "))
-        return false;
-
-    if (!RenderExpr(c, *op.op()))
-        return false;
-
-    return c.buffer.append(")");
+    return c.buffer.append('\n');
 }
 
 static bool
 RenderIf(WasmRenderContext& c, AstIf& if_)
 {
-    if (!c.buffer.append("(if "))
-        return false;
     if (!RenderExpr(c, if_.cond()))
         return false;
 
-    if (!c.buffer.append(" (then "))
+    if (!RenderIndent(c))
+        return false;
+
+    if (!c.buffer.append("if"))
+        return false;
+
+    if (!RenderBlockNameAndSignature(c, if_.name(), if_.type()))
         return false;
 
-    if (!RenderName(c, if_.thenName()))
+    if (!if_.name().empty()) {
+        if (!c.buffer.append(' '))
+            return false;
+
+        if (!RenderName(c, if_.name()))
+            return false;
+    }
+
+    if (!c.buffer.append('\n'))
         return false;
 
     c.indent++;
     if (!RenderExprList(c, if_.thenExprs()))
         return false;
     c.indent--;
 
     if (if_.hasElse()) {
-        if (!c.buffer.append(") (else "))
+        if (!RenderIndent(c))
             return false;
 
-        if (!RenderName(c, if_.elseName()))
+        if (!c.buffer.append("else\n"))
             return false;
 
         c.indent++;
         if (!RenderExprList(c, if_.elseExprs()))
             return false;
         c.indent--;
     }
 
-    return c.buffer.append("))");
+    if (!RenderIndent(c))
+        return false;
+
+    return c.buffer.append("end\n");
+}
+
+static bool
+RenderLoadStoreBase(WasmRenderContext& c, const AstLoadStoreAddress& lsa)
+{
+    if (!RenderExpr(c, lsa.base()))
+        return false;
+
+    return true;
 }
 
 static bool
 RenderLoadStoreAddress(WasmRenderContext& c, const AstLoadStoreAddress& lsa, uint32_t defaultAlignLog2)
 {
     if (lsa.offset() != 0) {
       if (!c.buffer.append(" offset="))
           return false;
@@ -710,281 +779,285 @@ RenderLoadStoreAddress(WasmRenderContext
     uint32_t alignLog2 = lsa.flags();
     if (defaultAlignLog2 != alignLog2) {
       if (!c.buffer.append(" align="))
           return false;
       if (!RenderInt32(c, 1 << alignLog2))
           return false;
     }
 
-    if (!c.buffer.append(" "))
-        return false;
-
-    if (!RenderExpr(c, lsa.base()))
-        return false;
-
     return true;
 }
 
 static bool
 RenderLoad(WasmRenderContext& c, AstLoad& load)
 {
+    if (!RenderLoadStoreBase(c, load.address()))
+        return false;
+
+    if (!RenderIndent(c))
+        return false;
+
     uint32_t defaultAlignLog2;
     switch (load.expr()) {
       case Expr::I32Load8S:
-        if (!c.buffer.append("(i32.load8_s"))
+        if (!c.buffer.append("i32.load8_s"))
             return false;
         defaultAlignLog2 = 0;
         break;
       case Expr::I64Load8S:
-        if (!c.buffer.append("(i64.load8_s"))
+        if (!c.buffer.append("i64.load8_s"))
             return false;
         defaultAlignLog2 = 0;
         break;
       case Expr::I32Load8U:
-        if (!c.buffer.append("(i32.load8_u"))
+        if (!c.buffer.append("i32.load8_u"))
             return false;
         defaultAlignLog2 = 0;
         break;
       case Expr::I64Load8U:
-        if (!c.buffer.append("(i64.load8_u"))
+        if (!c.buffer.append("i64.load8_u"))
             return false;
         defaultAlignLog2 = 0;
         break;
       case Expr::I32Load16S:
-        if (!c.buffer.append("(i32.load16_s"))
+        if (!c.buffer.append("i32.load16_s"))
             return false;
         defaultAlignLog2 = 1;
         break;
       case Expr::I64Load16S:
-        if (!c.buffer.append("(i64.load16_s"))
+        if (!c.buffer.append("i64.load16_s"))
             return false;
         defaultAlignLog2 = 1;
         break;
       case Expr::I32Load16U:
-        if (!c.buffer.append("(i32.load16_u"))
+        if (!c.buffer.append("i32.load16_u"))
             return false;
         defaultAlignLog2 = 1;
         break;
       case Expr::I64Load16U:
-        if (!c.buffer.append("(i64.load16_u"))
+        if (!c.buffer.append("i64.load16_u"))
             return false;
         defaultAlignLog2 = 1;
         break;
       case Expr::I64Load32S:
-        if (!c.buffer.append("(i64.load32_s"))
+        if (!c.buffer.append("i64.load32_s"))
             return false;
         defaultAlignLog2 = 2;
         break;
       case Expr::I64Load32U:
-        if (!c.buffer.append("(i64.load32_u"))
+        if (!c.buffer.append("i64.load32_u"))
             return false;
         defaultAlignLog2 = 2;
         break;
       case Expr::I32Load:
-        if (!c.buffer.append("(i32.load"))
+        if (!c.buffer.append("i32.load"))
             return false;
         defaultAlignLog2 = 2;
         break;
       case Expr::I64Load:
-        if (!c.buffer.append("(i64.load"))
+        if (!c.buffer.append("i64.load"))
             return false;
         defaultAlignLog2 = 3;
         break;
       case Expr::F32Load:
-        if (!c.buffer.append("(f32.load"))
+        if (!c.buffer.append("f32.load"))
             return false;
         defaultAlignLog2 = 2;
         break;
       case Expr::F64Load:
-        if (!c.buffer.append("(f64.load"))
+        if (!c.buffer.append("f64.load"))
             return false;
         defaultAlignLog2 = 3;
         break;
       default:
         return false;
     }
 
-
     if (!RenderLoadStoreAddress(c, load.address(), defaultAlignLog2))
         return false;
 
-    return c.buffer.append(")");
+    return c.buffer.append('\n');
 }
 
 static bool
 RenderStore(WasmRenderContext& c, AstStore& store)
 {
+    if (!RenderLoadStoreBase(c, store.address()))
+        return false;
+
+    if (!RenderExpr(c, store.value()))
+        return false;
+
+    if (!RenderIndent(c))
+        return false;
+
     uint32_t defaultAlignLog2;
     switch (store.expr()) {
       case Expr::I32Store8:
-        if (!c.buffer.append("(i32.store8"))
+        if (!c.buffer.append("i32.store8"))
             return false;
         defaultAlignLog2 = 0;
         break;
       case Expr::I64Store8:
-        if (!c.buffer.append("(i64.store8"))
+        if (!c.buffer.append("i64.store8"))
             return false;
         defaultAlignLog2 = 0;
         break;
       case Expr::I32Store16:
-        if (!c.buffer.append("(i32.store16"))
+        if (!c.buffer.append("i32.store16"))
             return false;
         defaultAlignLog2 = 1;
         break;
       case Expr::I64Store16:
-        if (!c.buffer.append("(i64.store16"))
+        if (!c.buffer.append("i64.store16"))
             return false;
         defaultAlignLog2 = 1;
         break;
       case Expr::I64Store32:
-        if (!c.buffer.append("(i64.store32"))
+        if (!c.buffer.append("i64.store32"))
             return false;
         defaultAlignLog2 = 2;
         break;
       case Expr::I32Store:
-        if (!c.buffer.append("(i32.store"))
+        if (!c.buffer.append("i32.store"))
             return false;
         defaultAlignLog2 = 2;
         break;
       case Expr::I64Store:
-        if (!c.buffer.append("(i64.store"))
+        if (!c.buffer.append("i64.store"))
             return false;
         defaultAlignLog2 = 3;
         break;
       case Expr::F32Store:
-        if (!c.buffer.append("(f32.store"))
+        if (!c.buffer.append("f32.store"))
             return false;
         defaultAlignLog2 = 2;
         break;
       case Expr::F64Store:
-        if (!c.buffer.append("(f64.store"))
+        if (!c.buffer.append("f64.store"))
             return false;
         defaultAlignLog2 = 3;
         break;
       default:
         return false;
     }
 
     if (!RenderLoadStoreAddress(c, store.address(), defaultAlignLog2))
         return false;
 
-    if (!c.buffer.append(" "))
-        return false;
-
-    if (!RenderExpr(c, store.value()))
-        return false;
-
-    return c.buffer.append(")");
+    return c.buffer.append('\n');
 }
 
 static bool
 RenderBranch(WasmRenderContext& c, AstBranch& branch)
 {
     Expr expr = branch.expr();
     MOZ_ASSERT(expr == Expr::BrIf || expr == Expr::Br);
 
-    if (expr == Expr::BrIf ? !c.buffer.append("(br_if ") : !c.buffer.append("(br "))
+    if (expr == Expr::BrIf) {
+        if (!RenderExpr(c, branch.cond()))
+            return false;
+    }
+
+    if (branch.maybeValue()) {
+        if (!RenderExpr(c, *(branch.maybeValue())))
+            return false;
+    }
+
+    if (!RenderIndent(c))
+        return false;
+
+    if (expr == Expr::BrIf ? !c.buffer.append("br_if ") : !c.buffer.append("br "))
         return false;
 
     if (!RenderRef(c, branch.target()))
         return false;
 
-    if (expr == Expr::BrIf) {
-        if (!c.buffer.append(" "))
-            return false;
-
-        if (!RenderExpr(c, branch.cond()))
-            return false;
-    }
-
-    if (branch.maybeValue()) {
-        if (!c.buffer.append(" "))
-            return false;
-        if (!RenderExpr(c, *(branch.maybeValue())))
-            return false;
-    }
-
-    return c.buffer.append(")");
+    return c.buffer.append('\n');
 }
 
 static bool
 RenderBrTable(WasmRenderContext& c, AstBranchTable& table)
 {
-    if (!c.buffer.append("(br_table "))
+    if (table.maybeValue()) {
+      if (!RenderExpr(c, *(table.maybeValue())))
+          return false;
+    }
+
+    // Index
+    if (!RenderExpr(c, table.index()))
+        return false;
+
+    if (!RenderIndent(c))
+        return false;
+
+    if (!c.buffer.append("br_table "))
         return false;
 
     uint32_t tableLength = table.table().length();
     for (uint32_t i = 0; i < tableLength; i++) {
         if (!RenderRef(c, table.table()[i]))
             return false;
 
         if (!c.buffer.append(" "))
             return false;
     }
 
     if (!RenderRef(c, table.def()))
         return false;
 
-    if (!c.buffer.append(" "))
-        return false;
-
-    if (table.maybeValue()) {
-      if (!RenderExpr(c, *(table.maybeValue())))
-          return false;
-
-      if (!c.buffer.append(" "))
-          return false;
-    }
-
-    // Index
-    if (!RenderExpr(c, table.index()))
-        return false;
-
-    return c.buffer.append(")");
+    return c.buffer.append('\n');
 }
 
 static bool
 RenderReturn(WasmRenderContext& c, AstReturn& ret)
 {
-    if (!c.buffer.append("(return"))
-        return false;
-
     if (ret.maybeExpr()) {
-        if (!c.buffer.append(" "))
-            return false;
         if (!RenderExpr(c, *(ret.maybeExpr())))
             return false;
     }
 
-    return c.buffer.append(")");
+    if (!RenderIndent(c))
+        return false;
+
+    if (!c.buffer.append("return"))
+        return false;
+
+    return c.buffer.append('\n');
 }
 
 static bool
 RenderExpr(WasmRenderContext& c, AstExpr& expr)
 {
     switch (expr.kind()) {
-      case AstExprKind::NullaryOperator:
-        return RenderNullaryOperator(c, expr.as<AstNullaryOperator>());
+      case AstExprKind::Drop:
+        return RenderDrop(c, expr.as<AstDrop>());
+      case AstExprKind::Nop:
+        return RenderNop(c);
       case AstExprKind::Unreachable:
-        return RenderUnreachable(c, expr.as<AstUnreachable>());
+        return RenderUnreachable(c);
       case AstExprKind::Call:
         return RenderCall(c, expr.as<AstCall>());
       case AstExprKind::CallIndirect:
         return RenderCallIndirect(c, expr.as<AstCallIndirect>());
       case AstExprKind::Const:
         return RenderConst(c, expr.as<AstConst>());
       case AstExprKind::GetLocal:
         return RenderGetLocal(c, expr.as<AstGetLocal>());
       case AstExprKind::SetLocal:
         return RenderSetLocal(c, expr.as<AstSetLocal>());
+      case AstExprKind::TeeLocal:
+        return RenderTeeLocal(c, expr.as<AstTeeLocal>());
       case AstExprKind::Block:
         return RenderBlock(c, expr.as<AstBlock>());
       case AstExprKind::If:
         return RenderIf(c, expr.as<AstIf>());
+      case AstExprKind::NullaryOperator:
+        return RenderNullaryOperator(c, expr.as<AstNullaryOperator>());
       case AstExprKind::UnaryOperator:
         return RenderUnaryOperator(c, expr.as<AstUnaryOperator>());
       case AstExprKind::BinaryOperator:
         return RenderBinaryOperator(c, expr.as<AstBinaryOperator>());
       case AstExprKind::TernaryOperator:
         return RenderTernaryOperator(c, expr.as<AstTernaryOperator>());
       case AstExprKind::ComparisonOperator:
         return RenderComparisonOperator(c, expr.as<AstComparisonOperator>());
@@ -995,16 +1068,18 @@ RenderExpr(WasmRenderContext& c, AstExpr
       case AstExprKind::Store:
         return RenderStore(c, expr.as<AstStore>());
       case AstExprKind::Branch:
         return RenderBranch(c, expr.as<AstBranch>());
       case AstExprKind::BranchTable:
         return RenderBrTable(c, expr.as<AstBranchTable>());
       case AstExprKind::Return:
         return RenderReturn(c, expr.as<AstReturn>());
+      case AstExprKind::First:
+        return RenderFirst(c, expr.as<AstFirst>());
       default:
         // Note: it's important not to remove this default since readExpr()
         // can return Expr values for which there is no enumerator.
         break;
     }
 
     return false;
 }
@@ -1096,22 +1171,25 @@ RenderTableSection(WasmRenderContext& c,
         return false;
 
     if (!c.buffer.append("(table"))
         return false;
 
     for (const AstRef& elem : segment.elems()) {
         if (!c.buffer.append(" "))
             return false;
-        AstFunc* func = module.funcs()[elem.index()];
-        if (func->name().empty()) {
-            if (!RenderInt32(c, elem.index()))
+        uint32_t index = elem.index();
+        AstName name = index < module.funcImportNames().length()
+                           ? module.funcImportNames()[index]
+                           : module.funcs()[index - module.funcImportNames().length()]->name();
+        if (name.empty()) {
+            if (!RenderInt32(c, index))
                 return false;
         } else {
-          if (!RenderName(c, func->name()))
+          if (!RenderName(c, name))
               return false;
         }
     }
 
     if (!c.buffer.append(")\n"))
         return false;
 
     return true;
@@ -1162,61 +1240,67 @@ RenderImportSection(WasmRenderContext& c
         if (!RenderImport(c, *imports[i], sigs))
             return false;
     }
 
     return true;
 }
 
 static bool
-RenderExport(WasmRenderContext& c, AstExport& export_, const AstModule::FuncVector& funcs)
+RenderExport(WasmRenderContext& c, AstExport& export_,
+             const AstModule::NameVector& funcImportNames,
+             const AstModule::FuncVector& funcs)
 {
     if (!RenderIndent(c))
         return false;
     if (!c.buffer.append("(export \""))
         return false;
     if (!RenderEscapedString(c, export_.name()))
         return false;
     if (!c.buffer.append("\" "))
         return false;
     if (export_.kind() == DefinitionKind::Memory) {
         if (!c.buffer.append("memory"))
           return false;
     } else {
-        const AstFunc* func = funcs[export_.ref().index()];
-        if (func->name().empty()) {
-            if (!RenderInt32(c, export_.ref().index()))
+        uint32_t index = export_.ref().index();
+        AstName name = index < funcImportNames.length()
+                           ? funcImportNames[index]
+                           : funcs[index - funcImportNames.length()]->name();
+        if (name.empty()) {
+            if (!RenderInt32(c, index))
                 return false;
         } else {
-            if (!RenderName(c, func->name()))
+            if (!RenderName(c, name))
                 return false;
         }
     }
     if (!c.buffer.append(")\n"))
         return false;
 
     return true;
 }
 
 static bool
-RenderExportSection(WasmRenderContext& c, const AstModule::ExportVector& exports, const AstModule::FuncVector& funcs)
+RenderExportSection(WasmRenderContext& c, const AstModule::ExportVector& exports,
+                    const AstModule::NameVector& funcImportNames,
+                    const AstModule::FuncVector& funcs)
 {
     uint32_t numExports = exports.length();
     for (uint32_t i = 0; i < numExports; i++) {
-        if (!RenderExport(c, *exports[i], funcs))
+        if (!RenderExport(c, *exports[i], funcImportNames, funcs))
             return false;
     }
     return true;
 }
 
 static bool
 RenderFunctionBody(WasmRenderContext& c, AstFunc& func, const AstModule::SigVector& sigs)
 {
     const AstSig* sig = sigs[func.sig().index()];
-    c.indent++;
 
     uint32_t argsNum = sig->args().length();
     uint32_t localsNum = func.vars().length();
     if (localsNum > 0) {
         if (!RenderIndent(c))
             return false;
         for (uint32_t i = 0; i < localsNum; i++) {
             if (!c.buffer.append("(local "))
@@ -1236,22 +1320,20 @@ RenderFunctionBody(WasmRenderContext& c,
         }
         if (!c.buffer.append("\n"))
             return false;
     }
 
 
     uint32_t exprsNum = func.body().length();
     for (uint32_t i = 0; i < exprsNum; i++) {
-      if (!RenderFullLine(c, *func.body()[i]))
+      if (!RenderExpr(c, *func.body()[i]))
           return false;
     }
 
-    c.indent--;
-
     return true;
 }
 
 static bool
 RenderCodeSection(WasmRenderContext& c, const AstModule::FuncVector& funcs, const AstModule::SigVector& sigs)
 {
     uint32_t numFuncBodies = funcs.length();
     for (uint32_t funcIndex = 0; funcIndex < numFuncBodies; funcIndex++) {
@@ -1294,19 +1376,19 @@ RenderDataSection(WasmRenderContext& c, 
 {
     if (!module.hasMemory())
         return true;
 
     if (!RenderIndent(c))
         return false;
     if (!c.buffer.append("(memory "))
         return false;
-    if (!RenderInt32(c, module.memory().initial()))
+    if (!RenderInt32(c, module.memory().initial))
        return false;
-    Maybe<uint32_t> memMax = module.memory().maximum();
+    Maybe<uint32_t> memMax = module.memory().maximum;
     if (memMax) {
         if (!c.buffer.append(" "))
             return false;
         if (!RenderInt32(c, *memMax))
             return false;
     }
 
     c.indent++;
@@ -1357,17 +1439,17 @@ RenderModule(WasmRenderContext& c, AstMo
         return false;
 
     if (!RenderImportSection(c, module.imports(), module.sigs()))
         return false;
 
     if (!RenderTableSection(c, module))
         return false;
 
-    if (!RenderExportSection(c, module.exports(), module.funcs()))
+    if (!RenderExportSection(c, module.exports(), module.funcImportNames(), module.funcs()))
         return false;
 
     if (!RenderCodeSection(c, module.funcs(), module.sigs()))
         return false;
 
     if (!RenderDataSection(c, module))
         return false;
 
--- a/js/src/asmjs/WasmCompile.cpp
+++ b/js/src/asmjs/WasmCompile.cpp
@@ -28,61 +28,45 @@
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 
 using mozilla::CheckedInt;
 using mozilla::IsNaN;
 
-static bool
-Fail(Decoder& d, const char* str)
-{
-    uint32_t offset = d.currentOffset();
-    d.fail(UniqueChars(JS_smprintf("compile error at offset %" PRIu32 ": %s", offset, str)));
-    return false;
-}
-
 namespace {
 
 struct ValidatingPolicy : ExprIterPolicy
 {
     // Validation is what we're all about here.
     static const bool Validate = true;
 };
 
 typedef ExprIter<ValidatingPolicy> ValidatingExprIter;
 
 class FunctionDecoder
 {
     const ModuleGenerator& mg_;
     const ValTypeVector& locals_;
     ValidatingExprIter iter_;
-    bool newFormat_;
 
   public:
-    FunctionDecoder(const ModuleGenerator& mg, const ValTypeVector& locals, Decoder& d, bool newFormat)
-      : mg_(mg), locals_(locals), iter_(d), newFormat_(newFormat)
+    FunctionDecoder(const ModuleGenerator& mg, const ValTypeVector& locals, Decoder& d)
+      : mg_(mg), locals_(locals), iter_(d)
     {}
     const ModuleGenerator& mg() const { return mg_; }
     ValidatingExprIter& iter() { return iter_; }
     const ValTypeVector& locals() const { return locals_; }
-    bool newFormat() const { return newFormat_; }
 
     bool checkHasMemory() {
         if (!mg().usesMemory())
             return iter().fail("can't touch memory without memory");
         return true;
     }
-
-    bool checkIsOldFormat() {
-        if (newFormat_)
-            return iter().fail("opcode no longer in new format");
-        return true;
-    }
 };
 
 } // end anonymous namespace
 
 static bool
 CheckValType(Decoder& d, ValType type)
 {
     switch (type) {
@@ -92,25 +76,22 @@ CheckValType(Decoder& d, ValType type)
       case ValType::I64:
         return true;
       default:
         // Note: it's important not to remove this default since readValType()
         // can return ValType values for which there is no enumerator.
         break;
     }
 
-    return Fail(d, "bad type");
+    return d.fail("bad type");
 }
 
 static bool
-DecodeCallArgs(FunctionDecoder& f, uint32_t arity, const Sig& sig)
+DecodeCallArgs(FunctionDecoder& f, const Sig& sig)
 {
-    if (arity != sig.args().length())
-        return f.iter().fail("call arity out of range");
-
     const ValTypeVector& args = sig.args();
     uint32_t numArgs = args.length();
     for (size_t i = 0; i < numArgs; ++i) {
         ValType argType = args[i];
         if (!f.iter().readCallArg(argType, numArgs, i, nullptr))
             return false;
     }
 
@@ -122,497 +103,486 @@ DecodeCallReturn(FunctionDecoder& f, con
 {
     return f.iter().readCallReturn(sig.ret());
 }
 
 static bool
 DecodeCall(FunctionDecoder& f)
 {
     uint32_t calleeIndex;
-    uint32_t arity;
-    if (!f.iter().readCall(&calleeIndex, &arity))
+    if (!f.iter().readCall(&calleeIndex))
         return false;
 
-    const Sig* sig;
-    if (f.newFormat()) {
-        if (calleeIndex >= f.mg().numFuncs())
-            return f.iter().fail("callee index out of range");
+    if (calleeIndex >= f.mg().numFuncs())
+        return f.iter().fail("callee index out of range");
 
-        sig = &f.mg().funcSig(calleeIndex);
-    } else {
-        if (calleeIndex >= f.mg().numFuncDefs())
-            return f.iter().fail("callee index out of range");
+    if (!f.iter().inReachableCode())
+        return true;
 
-        sig = &f.mg().funcDefSig(calleeIndex);
-    }
+    const Sig* sig = &f.mg().funcSig(calleeIndex);
 
-    return DecodeCallArgs(f, arity, *sig) &&
+    return DecodeCallArgs(f, *sig) &&
            DecodeCallReturn(f, *sig);
 }
 
 static bool
 DecodeCallIndirect(FunctionDecoder& f)
 {
     if (!f.mg().numTables())
         return f.iter().fail("can't call_indirect without a table");
 
     uint32_t sigIndex;
-    uint32_t arity;
-    if (!f.iter().readCallIndirect(&sigIndex, &arity))
+    if (!f.iter().readCallIndirect(&sigIndex, nullptr))
         return false;
 
     if (sigIndex >= f.mg().numSigs())
         return f.iter().fail("signature index out of range");
 
+    if (!f.iter().inReachableCode())
+        return true;
+
     const Sig& sig = f.mg().sig(sigIndex);
-    if (!DecodeCallArgs(f, arity, sig))
-        return false;
-
-    if (!f.iter().readCallIndirectCallee(nullptr))
+    if (!DecodeCallArgs(f, sig))
         return false;
 
     return DecodeCallReturn(f, sig);
 }
 
 static bool
-DecodeCallImport(FunctionDecoder& f)
+DecodeBlock(FunctionDecoder& f)
 {
-    uint32_t funcImportIndex;
-    uint32_t arity;
-    if (!f.iter().readCallImport(&funcImportIndex, &arity))
+    if (!f.iter().readBlock())
+        return false;
+
+    if (f.iter().controlType() > ExprType::F64)
+        return f.iter().fail("unknown block signature type");
+
+    return true;
+}
+
+static bool
+DecodeLoop(FunctionDecoder& f)
+{
+    if (!f.iter().readLoop())
         return false;
 
-    if (funcImportIndex >= f.mg().numFuncImports())
-        return f.iter().fail("import index out of range");
+    if (f.iter().controlType() > ExprType::F64)
+        return f.iter().fail("unknown loop signature type");
+
+    return true;
+}
 
-    const Sig& sig = *f.mg().funcImport(funcImportIndex).sig;
-    return DecodeCallArgs(f, arity, sig) &&
-           DecodeCallReturn(f, sig);
+static bool
+DecodeIf(FunctionDecoder& f)
+{
+    if (!f.iter().readIf(nullptr))
+        return false;
+
+    if (f.iter().controlType() > ExprType::F64)
+        return f.iter().fail("unknown if signature type");
+
+    return true;
 }
 
 static bool
 DecodeBrTable(FunctionDecoder& f)
 {
     uint32_t tableLength;
     ExprType type = ExprType::Limit;
     if (!f.iter().readBrTable(&tableLength, &type, nullptr, nullptr))
         return false;
 
     uint32_t depth;
     for (size_t i = 0, e = tableLength; i < e; ++i) {
-        if (!f.iter().readBrTableEntry(type, &depth))
+        if (!f.iter().readBrTableEntry(&type, nullptr, &depth))
             return false;
     }