Bug 1253137 - Baldr: move local definitions into the body (r=sunfish)
authorLuke Wagner <luke@mozilla.com>
Sat, 05 Mar 2016 17:45:52 -0600
changeset 323292 60fb79d430d82659bff21892764a22305abea2ff
parent 323291 2d1476dbf842eef4a7fb058cdcefe32f4218d57c
child 323293 939b709ea842bc69a70fce50eeccc3f953a50a28
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssunfish
bugs1253137
milestone47.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1253137 - Baldr: move local definitions into the body (r=sunfish) MozReview-Commit-ID: KuAIrpdP2ND
js/src/asmjs/AsmJS.cpp
js/src/asmjs/Wasm.cpp
js/src/asmjs/WasmBinary.h
js/src/asmjs/WasmGenerator.cpp
js/src/asmjs/WasmGenerator.h
js/src/asmjs/WasmIonCompile.cpp
js/src/asmjs/WasmText.cpp
js/src/jit-test/tests/wasm/binary.js
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -2675,18 +2675,17 @@ class MOZ_STACK_CLASS FunctionValidator
     }
 
     /***************************************************** Local scope setup */
 
     bool addLocal(ParseNode* pn, PropertyName* name, Type type) {
         LocalMap::AddPtr p = locals_.lookupForAdd(name);
         if (p)
             return failName(pn, "duplicate local name '%s' not allowed", name);
-        return locals_.add(p, name, Local(type, locals_.count())) &&
-               fg_.addLocal(type.canonicalToValType());
+        return locals_.add(p, name, Local(type, locals_.count()));
     }
 
     /****************************** For consistency of returns in a function */
 
     bool hasAlreadyReturned() const {
         return hasAlreadyReturned_;
     }
 
@@ -2818,17 +2817,17 @@ class MOZ_STACK_CLASS FunctionValidator
     /**************************************************** Encoding interface */
 
     Encoder& encoder() { return *encoder_; }
 
     MOZ_WARN_UNUSED_RESULT bool writeInt32Lit(int32_t i32) {
         return encoder().writeExpr(Expr::I32Const) &&
                encoder().writeVarU32(i32);
     }
-    MOZ_WARN_UNUSED_RESULT bool writeLit(NumLit lit) {
+    MOZ_WARN_UNUSED_RESULT bool writeConstExpr(NumLit lit) {
         switch (lit.which()) {
           case NumLit::Fixnum:
           case NumLit::NegativeInt:
           case NumLit::BigUnsigned:
             return writeInt32Lit(lit.toInt32());
           case NumLit::Float:
             return encoder().writeExpr(Expr::F32Const) &&
                    encoder().writeFixedF32(lit.toFloat());
@@ -3481,25 +3480,17 @@ CheckFinalReturn(FunctionValidator& f, P
 
     if (!lastNonEmptyStmt->isKind(PNK_RETURN) && !IsVoid(f.returnedType()))
         return f.fail(lastNonEmptyStmt, "void incompatible with previous return type");
 
     return true;
 }
 
 static bool
-SetLocal(FunctionValidator& f, NumLit lit)
-{
-    return f.encoder().writeExpr(Expr::SetLocal) &&
-           f.encoder().writeVarU32(f.numLocals()) &&
-           f.writeLit(lit);
-}
-
-static bool
-CheckVariable(FunctionValidator& f, ParseNode* var)
+CheckVariable(FunctionValidator& f, ParseNode* var, ValTypeVector* types, Vector<NumLit>* inits)
 {
     if (!IsDefinition(var))
         return f.fail(var, "local variable names must not restate argument names");
 
     PropertyName* name = var->name();
 
     if (!CheckIdentifier(f.m(), var, name))
         return false;
@@ -3510,51 +3501,77 @@ CheckVariable(FunctionValidator& f, Pars
 
     NumLit lit;
     if (!IsLiteralOrConst(f, initNode, &lit))
         return f.failName(var, "var '%s' initializer must be literal or const literal", name);
 
     if (!lit.valid())
         return f.failName(var, "var '%s' initializer out of range", name);
 
-    if (!lit.isZeroBits()) {
-        if (!SetLocal(f, lit))
-            return false;
-    }
-
-    return f.addLocal(var, name, Type::canonicalize(Type::lit(lit)));
+    Type type = Type::canonicalize(Type::lit(lit));
+
+    return f.addLocal(var, name, type) &&
+           types->append(type.canonicalToValType()) &&
+           inits->append(lit);
 }
 
 static bool
 CheckVariables(FunctionValidator& f, ParseNode** stmtIter)
 {
     ParseNode* stmt = *stmtIter;
 
+    uint32_t firstVar = f.numLocals();
+
+    ValTypeVector types;
+    Vector<NumLit> inits(f.cx());
+
     for (; stmt && stmt->isKind(PNK_VAR); stmt = NextNonEmptyStatement(stmt)) {
         for (ParseNode* var = VarListHead(stmt); var; var = NextNode(var)) {
-            if (!CheckVariable(f, var))
+            if (!CheckVariable(f, var, &types, &inits))
                 return false;
         }
     }
 
+    MOZ_ASSERT(f.encoder().empty());
+
+    if (!f.encoder().writeVarU32(types.length()))
+        return false;
+
+    for (ValType v : types) {
+        if (!f.encoder().writeValType(v))
+            return false;
+    }
+
+    for (uint32_t i = 0; i < inits.length(); i++) {
+        NumLit lit = inits[i];
+        if (lit.isZeroBits())
+            continue;
+        if (!f.encoder().writeExpr(Expr::SetLocal))
+            return false;
+        if (!f.encoder().writeVarU32(firstVar + i))
+            return false;
+        if (!f.writeConstExpr(lit))
+            return false;
+    }
+
     *stmtIter = stmt;
     return true;
 }
 
 static bool
 CheckExpr(FunctionValidator& f, ParseNode* expr, Type* type);
 
 static bool
 CheckNumericLiteral(FunctionValidator& f, ParseNode* num, Type* type)
 {
     NumLit lit = ExtractNumericLiteral(f.m(), num);
     if (!lit.valid())
         return f.fail(num, "numeric literal out of representable integer range");
     *type = Type::lit(lit);
-    return f.writeLit(lit);
+    return f.writeConstExpr(lit);
 }
 
 static bool
 CheckVarRef(FunctionValidator& f, ParseNode* varRef, Type* type)
 {
     PropertyName* name = varRef->name();
 
     if (const FunctionValidator::Local* local = f.lookupLocal(name)) {
@@ -3565,17 +3582,17 @@ CheckVarRef(FunctionValidator& f, ParseN
         *type = local->type;
         return true;
     }
 
     if (const ModuleValidator::Global* global = f.lookupGlobal(name)) {
         switch (global->which()) {
           case ModuleValidator::Global::ConstantLiteral:
             *type = global->varOrConstType();
-            return f.writeLit(global->constLiteralValue());
+            return f.writeConstExpr(global->constLiteralValue());
           case ModuleValidator::Global::ConstantImport:
           case ModuleValidator::Global::Variable: {
             *type = global->varOrConstType();
             return f.encoder().writeExpr(Expr::LoadGlobal) &&
                    f.encoder().writeVarU32(global->varOrConstIndex());
           }
           case ModuleValidator::Global::Function:
           case ModuleValidator::Global::FFI:
@@ -5456,17 +5473,17 @@ CheckCoercedCall(FunctionValidator& f, P
 
     JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed());
 
     if (IsNumericLiteral(f.m(), call)) {
         size_t coerceOp;
         if (!f.encoder().writePatchableExpr(&coerceOp))
             return false;
         NumLit lit = ExtractNumericLiteral(f.m(), call);
-        if (!f.writeLit(lit))
+        if (!f.writeConstExpr(lit))
             return false;
         return CoerceResult(f, call, ret, Type::lit(lit), coerceOp, type);
     }
 
     ParseNode* callee = CallCallee(call);
 
     if (callee->isKind(PNK_ELEM))
         return CheckFuncPtrCall(f, call, ret, type);
--- a/js/src/asmjs/Wasm.cpp
+++ b/js/src/asmjs/Wasm.cpp
@@ -81,29 +81,31 @@ Unify(ExprType one, ExprType two)
 
 class FunctionDecoder
 {
     JSContext* cx_;
     Decoder& d_;
     ModuleGenerator& mg_;
     FunctionGenerator& fg_;
     uint32_t funcIndex_;
+    const ValTypeVector& locals_;
     Vector<ExprType> blocks_;
 
   public:
     FunctionDecoder(JSContext* cx, Decoder& d, ModuleGenerator& mg, FunctionGenerator& fg,
-                    uint32_t funcIndex)
-      : cx_(cx), d_(d), mg_(mg), fg_(fg), funcIndex_(funcIndex), blocks_(cx)
+                    uint32_t funcIndex, const ValTypeVector& locals)
+      : cx_(cx), d_(d), mg_(mg), fg_(fg), funcIndex_(funcIndex), locals_(locals), blocks_(cx)
     {}
     JSContext* cx() const { return cx_; }
     Decoder& d() const { return d_; }
     ModuleGenerator& mg() const { return mg_; }
     FunctionGenerator& fg() const { return fg_; }
     uint32_t funcIndex() const { return funcIndex_; }
-    ExprType ret() const { return mg_.funcSig(funcIndex_).ret(); }
+    const ValTypeVector& locals() const { return locals_; }
+    const DeclaredSig& sig() const { return mg_.funcSig(funcIndex_); }
 
     bool fail(const char* str) {
         return Fail(cx_, d_, str);
     }
 
     MOZ_WARN_UNUSED_RESULT bool pushBlock() {
         return blocks_.append(AnyType);
     }
@@ -324,34 +326,34 @@ DecodeConstF64(FunctionDecoder& f, ExprT
 
 static bool
 DecodeGetLocal(FunctionDecoder& f, ExprType* type)
 {
     uint32_t localIndex;
     if (!f.d().readVarU32(&localIndex))
         return f.fail("unable to read get_local index");
 
-    if (localIndex >= f.fg().locals().length())
+    if (localIndex >= f.locals().length())
         return f.fail("get_local index out of range");
 
-    *type = ToExprType(f.fg().locals()[localIndex]);
+    *type = ToExprType(f.locals()[localIndex]);
     return true;
 }
 
 static bool
 DecodeSetLocal(FunctionDecoder& f, ExprType* type)
 {
     uint32_t localIndex;
     if (!f.d().readVarU32(&localIndex))
         return f.fail("unable to read set_local index");
 
-    if (localIndex >= f.fg().locals().length())
+    if (localIndex >= f.locals().length())
         return f.fail("set_local index out of range");
 
-    *type = ToExprType(f.fg().locals()[localIndex]);
+    *type = ToExprType(f.locals()[localIndex]);
 
     ExprType rhsType;
     if (!DecodeExpr(f, &rhsType))
         return false;
 
     return CheckType(f, rhsType, *type);
 }
 
@@ -600,22 +602,22 @@ DecodeBrTable(FunctionDecoder& f, ExprTy
 
     *type = AnyType;
     return true;
 }
 
 static bool
 DecodeReturn(FunctionDecoder& f, ExprType* type)
 {
-    if (f.ret() != ExprType::Void) {
+    if (f.sig().ret() != ExprType::Void) {
         ExprType actual;
         if (!DecodeExpr(f, &actual))
             return false;
 
-        if (!CheckType(f, actual, f.ret()))
+        if (!CheckType(f, actual, f.sig().ret()))
             return false;
     }
 
     *type = AnyType;
     return true;
 }
 
 static bool
@@ -1235,68 +1237,65 @@ DecodeExportTable(JSContext* cx, Decoder
     return true;
 }
 
 static bool
 DecodeFunctionBody(JSContext* cx, Decoder& d, ModuleGenerator& mg, uint32_t funcIndex)
 {
     int64_t before = PRMJ_Now();
 
+    uint32_t bodySize;
+    if (!d.readVarU32(&bodySize))
+        return Fail(cx, d, "expected number of function body bytes");
+
+    if (d.bytesRemain() < bodySize)
+        return Fail(cx, d, "function body length too big");
+
+    const uint8_t* bodyBegin = d.currentPosition();
+    const uint8_t* bodyEnd = bodyBegin + bodySize;
+
     FunctionGenerator fg;
     if (!mg.startFuncDef(d.currentOffset(), &fg))
         return false;
 
-    const DeclaredSig& sig = mg.funcSig(funcIndex);
-    for (ValType type : sig.args()) {
-        if (!fg.addLocal(type))
-            return false;
-    }
+    ValTypeVector locals;
+    if (!locals.appendAll(mg.funcSig(funcIndex).args()))
+        return false;
 
     uint32_t numVars;
     if (!d.readVarU32(&numVars))
         return Fail(cx, d, "expected number of local vars");
 
     for (uint32_t i = 0; i < numVars; i++) {
         ValType type;
         if (!DecodeValType(cx, d, &type))
             return false;
-        if (!fg.addLocal(type))
+        if (!locals.append(type))
             return false;
     }
 
-    FunctionDecoder f(cx, d, mg, fg, funcIndex);
-
-    uint32_t numBytes;
-    if (!d.readVarU32(&numBytes))
-        return Fail(cx, d, "expected number of function body bytes");
-
-    if (d.bytesRemain() < numBytes)
-        return Fail(cx, d, "function body length too big");
-
-    const uint8_t* bodyBegin = d.currentPosition();
-    const uint8_t* bodyEnd = bodyBegin + numBytes;
+    FunctionDecoder f(cx, d, mg, fg, funcIndex, locals);
 
     ExprType type = ExprType::Void;
 
     while (d.currentPosition() < bodyEnd) {
         if (!DecodeExpr(f, &type))
             return false;
     }
 
+    if (!CheckType(f, type, f.sig().ret()))
+        return false;
+
     if (d.currentPosition() != bodyEnd)
         return Fail(cx, d, "function body length mismatch");
 
-    if (!CheckType(f, type, f.ret()))
+    if (!fg.bytecode().resize(bodySize))
         return false;
 
-    uintptr_t bodyLength = bodyEnd - bodyBegin;
-    if (!fg.bytecode().resize(bodyLength))
-        return false;
-
-    memcpy(fg.bytecode().begin(), bodyBegin, bodyLength);
+    memcpy(fg.bytecode().begin(), bodyBegin, bodySize);
 
     int64_t after = PRMJ_Now();
     unsigned generateTime = (after - before) / PRMJ_USEC_PER_MSEC;
 
     return mg.finishFuncDef(funcIndex, generateTime, &fg);
 }
 
 static bool
--- a/js/src/asmjs/WasmBinary.h
+++ b/js/src/asmjs/WasmBinary.h
@@ -746,16 +746,19 @@ class Decoder
         return uncheckedReadEnum<Expr>();
     }
     Expr uncheckedPeekExpr() {
         const uint8_t* before = cur_;
         Expr ret = uncheckedReadEnum<Expr>();
         cur_ = before;
         return ret;
     }
+    ValType uncheckedReadValType() {
+        return uncheckedReadEnum<ValType>();
+    }
 
     // Temporary encoding forms which should be removed as part of the
     // conversion to wasm:
 
     MOZ_WARN_UNUSED_RESULT bool readFixedU8(uint8_t* i = nullptr) {
         return read<uint8_t>(i);
     }
     MOZ_WARN_UNUSED_RESULT bool readFixedI32(int32_t* i = nullptr) {
@@ -768,54 +771,47 @@ class Decoder
 
 // The FuncBytecode class contains the intermediate representation of a
 // parsed/decoded and validated asm.js/WebAssembly function. The FuncBytecode
 // lives only until it is fully compiled.
 class FuncBytecode
 {
     // Function metadata
     const DeclaredSig& sig_;
-    ValTypeVector locals_;
     uint32_t lineOrBytecode_;
     Uint32Vector callSiteLineNums_;
 
     // Compilation bookkeeping
     uint32_t index_;
     unsigned generateTime_;
 
     UniqueBytecode bytecode_;
 
   public:
     FuncBytecode(uint32_t index,
                  const DeclaredSig& sig,
                  UniqueBytecode bytecode,
-                 ValTypeVector&& locals,
                  uint32_t lineOrBytecode,
                  Uint32Vector&& callSiteLineNums,
                  unsigned generateTime)
       : sig_(sig),
-        locals_(Move(locals)),
         lineOrBytecode_(lineOrBytecode),
         callSiteLineNums_(Move(callSiteLineNums)),
         index_(index),
         generateTime_(generateTime),
         bytecode_(Move(bytecode))
     {}
 
     UniqueBytecode recycleBytecode() { return Move(bytecode_); }
 
     uint32_t lineOrBytecode() const { return lineOrBytecode_; }
     const Uint32Vector& callSiteLineNums() const { return callSiteLineNums_; }
     uint32_t index() const { return index_; }
     const DeclaredSig& sig() const { return sig_; }
     const Bytecode& bytecode() const { return *bytecode_; }
-
-    size_t numLocals() const { return locals_.length(); }
-    ValType localType(size_t i) const { return locals_[i]; }
-
     unsigned generateTime() const { return generateTime_; }
 };
 
 typedef UniquePtr<FuncBytecode> UniqueFuncBytecode;
 
 // Module generator limits
 
 static const unsigned MaxSigs            =   4 * 1024;
--- a/js/src/asmjs/WasmGenerator.cpp
+++ b/js/src/asmjs/WasmGenerator.cpp
@@ -802,17 +802,16 @@ bool
 ModuleGenerator::finishFuncDef(uint32_t funcIndex, unsigned generateTime, FunctionGenerator* fg)
 {
     MOZ_ASSERT(activeFunc_ == fg);
 
     UniqueFuncBytecode func =
         js::MakeUnique<FuncBytecode>(funcIndex,
                                      funcSig(funcIndex),
                                      Move(fg->bytecode_),
-                                     Move(fg->locals_),
                                      fg->lineOrBytecode_,
                                      Move(fg->callSiteLineNums_),
                                      generateTime);
     if (!func)
         return false;
 
     fg->task_->init(Move(func));
 
--- a/js/src/asmjs/WasmGenerator.h
+++ b/js/src/asmjs/WasmGenerator.h
@@ -290,35 +290,28 @@ class MOZ_STACK_CLASS FunctionGenerator
 
     ModuleGenerator*   m_;
     IonCompileTask*    task_;
 
     // Data created during function generation, then handed over to the
     // FuncBytecode in ModuleGenerator::finishFunc().
     UniqueBytecode     bytecode_;
     Uint32Vector       callSiteLineNums_;
-    ValTypeVector      locals_;
 
     uint32_t lineOrBytecode_;
 
   public:
     FunctionGenerator()
       : m_(nullptr), task_(nullptr), lineOrBytecode_(0)
     {}
 
     Bytecode& bytecode() const {
         return *bytecode_;
     }
     bool addCallSiteLineNum(uint32_t lineno) {
         return callSiteLineNums_.append(lineno);
     }
-    bool addLocal(ValType v) {
-        return locals_.append(v);
-    }
-    const ValTypeVector& locals() const {
-        return locals_;
-    }
 };
 
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_generator_h
--- a/js/src/asmjs/WasmIonCompile.cpp
+++ b/js/src/asmjs/WasmIonCompile.cpp
@@ -34,39 +34,45 @@ typedef Vector<MBasicBlock*, 8, SystemAl
 // function compiler handles the creation and final backend compilation of the
 // MIR graph.
 class FunctionCompiler
 {
   private:
     typedef Vector<BlockVector, 0, SystemAllocPolicy> BlocksVector;
 
     ModuleGeneratorThreadView& mg_;
+    Decoder&                   decoder_;
     const FuncBytecode&        func_;
-    Decoder                    decoder_;
+    const ValTypeVector&       locals_;
     size_t                     lastReadCallSite_;
 
     TempAllocator&             alloc_;
     MIRGraph&                  graph_;
     const CompileInfo&         info_;
     MIRGenerator&              mirGen_;
 
     MBasicBlock*               curBlock_;
 
     uint32_t                   loopDepth_;
     uint32_t                   blockDepth_;
     BlocksVector               targets_;
 
     FuncCompileResults&        compileResults_;
 
   public:
-    FunctionCompiler(ModuleGeneratorThreadView& mg, const FuncBytecode& func, MIRGenerator& mirGen,
+    FunctionCompiler(ModuleGeneratorThreadView& mg,
+                     Decoder& decoder,
+                     const FuncBytecode& func,
+                     const ValTypeVector& locals,
+                     MIRGenerator& mirGen,
                      FuncCompileResults& compileResults)
       : mg_(mg),
+        decoder_(decoder),
         func_(func),
-        decoder_(func.bytecode()),
+        locals_(locals),
         lastReadCallSite_(0),
         alloc_(mirGen.alloc()),
         graph_(mirGen.graph()),
         info_(mirGen.info()),
         mirGen_(mirGen),
         curBlock_(nullptr),
         loopDepth_(0),
         blockDepth_(0),
@@ -92,19 +98,19 @@ class FunctionCompiler
         for (ABIArgValTypeIter i(args); !i.done(); i++) {
             MAsmJSParameter* ins = MAsmJSParameter::New(alloc(), *i, i.mirType());
             curBlock_->add(ins);
             curBlock_->initSlot(info().localSlot(i.index()), ins);
             if (!mirGen_.ensureBallast())
                 return false;
         }
 
-        for (size_t i = args.length(); i < func_.numLocals(); i++) {
+        for (size_t i = args.length(); i < locals_.length(); i++) {
             MInstruction* ins = nullptr;
-            switch (func_.localType(i)) {
+            switch (locals_[i]) {
               case ValType::I32:
                 ins = MConstant::NewAsmJS(alloc(), Int32Value(0), MIRType_Int32);
                 break;
               case ValType::I64:
                 ins = MConstant::NewInt64(alloc(), 0);
                 break;
               case ValType::F32:
                 ins = MConstant::NewAsmJS(alloc(), Float32Value(0.f), MIRType_Float32);
@@ -159,17 +165,17 @@ class FunctionCompiler
 
     MDefinition* getLocalDef(unsigned slot)
     {
         if (inDeadCode())
             return nullptr;
         return curBlock_->getSlot(info().localSlot(slot));
     }
 
-    ValType localType(unsigned slot) const { return func_.localType(slot); }
+    ValType localType(unsigned slot) const { return locals_[slot]; }
 
     /***************************** Code generation (after local scope setup) */
 
     MDefinition* constant(const SimdConstant& v, MIRType type)
     {
         if (inDeadCode())
             return nullptr;
         MInstruction* constant;
@@ -3040,29 +3046,44 @@ EmitExpr(FunctionCompiler& f, MDefinitio
 bool
 wasm::IonCompileFunction(IonCompileTask* task)
 {
     int64_t before = PRMJ_Now();
 
     const FuncBytecode& func = task->func();
     FuncCompileResults& results = task->results();
 
+    // Read in the variable types to build the local types vector.
+
+    Decoder d(func.bytecode());
+
+    ValTypeVector locals;
+    if (!locals.appendAll(func.sig().args()))
+        return false;
+
+    uint32_t numVars = d.uncheckedReadVarU32();
+    for (uint32_t i = 0; i < numVars; i++) {
+        if (!locals.append(d.uncheckedReadValType()))
+            return false;
+    }
+
+    // Set up for Ion compilation.
+
     JitContext jitContext(CompileRuntime::get(task->runtime()), &results.alloc());
-
     const JitCompileOptions options;
     MIRGraph graph(&results.alloc());
-    CompileInfo compileInfo(func.numLocals());
+    CompileInfo compileInfo(locals.length());
     MIRGenerator mir(nullptr, options, &results.alloc(), &graph, &compileInfo,
                      IonOptimizations.get(OptimizationLevel::AsmJS));
     mir.initUsesSignalHandlersForAsmJSOOB(task->mg().args().useSignalHandlersForOOB);
     mir.initMinAsmJSHeapLength(task->mg().minHeapLength());
 
     // Build MIR graph
     {
-        FunctionCompiler f(task->mg(), func, mir, results);
+        FunctionCompiler f(task->mg(), d, func, locals, mir, results);
         if (!f.init())
             return false;
 
         MDefinition* last;
         while (!f.done()) {
             if (!EmitExpr(f, &last))
                 return false;
         }
--- a/js/src/asmjs/WasmText.cpp
+++ b/js/src/asmjs/WasmText.cpp
@@ -3914,30 +3914,30 @@ EncodeFunctionTable(Encoder& e, WasmAstM
 
     e.finishSection(offset);
     return true;
 }
 
 static bool
 EncodeFunctionBody(Encoder& e, WasmAstFunc& func)
 {
+    size_t bodySizeAt;
+    if (!e.writePatchableVarU32(&bodySizeAt))
+        return false;
+
+    size_t beforeBody = e.currentOffset();
+
     if (!e.writeVarU32(func.vars().length()))
         return false;
 
     for (ValType type : func.vars()) {
         if (!e.writeValType(type))
             return false;
     }
 
-    size_t bodySizeAt;
-    if (!e.writePatchableVarU32(&bodySizeAt))
-        return false;
-
-    size_t beforeBody = e.currentOffset();
-
     for (WasmAstExpr* expr : func.body()) {
         if (!EncodeExpr(e, *expr))
             return false;
     }
 
     e.patchVarU32(bodySizeAt, e.currentOffset() - beforeBody);
     return true;
 }
--- a/js/src/jit-test/tests/wasm/binary.js
+++ b/js/src/jit-test/tests/wasm/binary.js
@@ -116,18 +116,19 @@ function declSection(decls) {
         body.push(...varU32(decl));
     return { name: functionSignaturesId, body };
 }
 
 function funcBody(func) {
     var body = varU32(func.locals.length);
     for (let local of func.locals)
         body.push(...varU32(local));
-    body.push(...varU32(func.body.length));
-    return body.concat(...func.body);
+    body = body.concat(...func.body);
+    body.splice(0, 0, ...varU32(body.length));
+    return body;
 }
 
 function bodySection(bodies) {
     var body = [].concat(...bodies);
     return { name: functionBodiesId, body };
 }
 
 function importSection(imports) {