Bug 1253137 - Baldr: switch local array to local entry array (r=sunfish)
authorLuke Wagner <luke@mozilla.com>
Sun, 06 Mar 2016 17:46:22 -0600
changeset 287002 57e9d3626218d7adf9463db51ad9aae8c44cd709
parent 287001 f1ff5388e591a53187620f03de8278c496442a05
child 287003 5eabc3a7368a09eb317dabd86b598e4f32ff6088
push id18032
push usercbook@mozilla.com
push dateMon, 07 Mar 2016 10:38:51 +0000
treeherderfx-team@087905ffec78 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssunfish
bugs1253137
milestone47.0a1
Bug 1253137 - Baldr: switch local array to local entry array (r=sunfish) MozReview-Commit-ID: HUdKHzTuLqo
js/src/asmjs/AsmJS.cpp
js/src/asmjs/Wasm.cpp
js/src/asmjs/WasmBinary.cpp
js/src/asmjs/WasmBinary.h
js/src/asmjs/WasmGenerator.cpp
js/src/asmjs/WasmIonCompile.cpp
js/src/asmjs/WasmModule.cpp
js/src/asmjs/WasmModule.h
js/src/asmjs/WasmText.cpp
js/src/asmjs/WasmTypes.h
js/src/gc/StoreBuffer.h
js/src/jit-test/tests/wasm/binary.js
js/src/moz.build
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -3527,23 +3527,18 @@ CheckVariables(FunctionValidator& f, Par
         for (ParseNode* var = VarListHead(stmt); var; var = NextNode(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;
-    }
+    if (!EncodeLocalEntries(f.encoder(), types))
+        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))
--- a/js/src/asmjs/Wasm.cpp
+++ b/js/src/asmjs/Wasm.cpp
@@ -139,68 +139,46 @@ static bool
 CheckType(FunctionDecoder& f, ExprType actual, ExprType expected)
 {
     MOZ_ASSERT(expected != AnyType);
     return expected == ExprType::Void ||
            CheckType(f, actual, NonVoidToValType(expected));
 }
 
 static bool
-DecodeExpr(FunctionDecoder& f, ExprType* type);
-
-static bool
-DecodeValType(JSContext* cx, Decoder& d, ValType *type)
+CheckValType(JSContext* cx, Decoder& d, ValType type)
 {
-    if (!d.readValType(type))
-        return Fail(cx, d, "bad value type");
-
-    switch (*type) {
+    switch (type) {
       case ValType::I32:
       case ValType::F32:
       case ValType::F64:
         return true;
       case ValType::I64:
 #ifndef JS_CPU_X64
         return Fail(cx, d, "i64 NYI on this platform");
 #endif
         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(cx, "bad value type");
+    return Fail(cx, d, "bad value type");
 }
 
 static bool
-DecodeExprType(JSContext* cx, Decoder& d, ExprType *type)
+CheckExprType(JSContext* cx, Decoder& d, ExprType type)
 {
-    if (!d.readExprType(type))
-        return Fail(cx, d, "bad expression type");
+    return type == ExprType::Void ||
+           CheckValType(cx, d, NonVoidToValType(type));
+}
 
-    switch (*type) {
-      case ExprType::I32:
-      case ExprType::F32:
-      case ExprType::F64:
-      case ExprType::Void:
-        return true;
-      case ExprType::I64:
-#ifndef JS_CPU_X64
-        return Fail(cx, d, "i64 NYI on this platform");
-#endif
-        return true;
-      default:
-        // Note: it's important not to remove this default since readExprType()
-        // can return ExprType values for which there is no enumerator.
-        break;
-    }
-
-    return Fail(cx, "bad expression type");
-}
+static bool
+DecodeExpr(FunctionDecoder& f, ExprType* type);
 
 static bool
 DecodeNop(FunctionDecoder& f, ExprType* type)
 {
     *type = ExprType::Void;
     return true;
 }
 
@@ -907,25 +885,31 @@ DecodeSignatures(JSContext* cx, Decoder&
         uint32_t numArgs;
         if (!d.readVarU32(&numArgs))
             return Fail(cx, d, "bad number of signature args");
 
         if (numArgs > MaxArgsPerFunc)
             return Fail(cx, d, "too many arguments in signature");
 
         ExprType result;
-        if (!DecodeExprType(cx, d, &result))
+        if (!d.readExprType(&result))
+            return Fail(cx, d, "bad expression type");
+
+        if (!CheckExprType(cx, d, result))
             return false;
 
         ValTypeVector args;
         if (!args.resize(numArgs))
             return false;
 
         for (uint32_t i = 0; i < numArgs; i++) {
-            if (!DecodeValType(cx, d, &args[i]))
+            if (!d.readValType(&args[i]))
+                return Fail(cx, d, "bad value type");
+
+            if (!CheckValType(cx, d, args[i]))
                 return false;
         }
 
         init->sigs[sigIndex] = Sig(Move(args), result);
 
         SigSet::AddPtr p = dupSet.lookupForAdd(init->sigs[sigIndex]);
         if (p)
             return Fail(cx, d, "duplicate signature");
@@ -1253,25 +1237,21 @@ DecodeFunctionBody(JSContext* cx, Decode
     FunctionGenerator fg;
     if (!mg.startFuncDef(d.currentOffset(), &fg))
         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");
+    if (!DecodeLocalEntries(d, &locals))
+        return Fail(cx, d, "failed decoding local entries");
 
-    for (uint32_t i = 0; i < numVars; i++) {
-        ValType type;
-        if (!DecodeValType(cx, d, &type))
-            return false;
-        if (!locals.append(type))
+    for (ValType type : locals) {
+        if (!CheckValType(cx, d, type))
             return false;
     }
 
     FunctionDecoder f(cx, d, mg, fg, funcIndex, locals);
 
     ExprType type = ExprType::Void;
 
     while (d.currentPosition() < bodyEnd) {
new file mode 100644
--- /dev/null
+++ b/js/src/asmjs/WasmBinary.cpp
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ *
+ * Copyright 2016 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * 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 "asmjs/WasmTypes.h"
+
+using namespace js;
+using namespace js::wasm;
+
+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;
+        }
+    }
+
+    if (!e.writeVarU32(numLocalEntries))
+        return false;
+
+    if (numLocalEntries) {
+        prev = locals[0];
+        uint32_t count = 1;
+        for (uint32_t i = 1; i < locals.length(); i++, count++) {
+            if (prev != locals[i]) {
+                if (!e.writeVarU32(count))
+                    return false;
+                if (!e.writeValType(prev))
+                    return false;
+                prev = locals[i];
+                count = 0;
+            }
+        }
+        if (!e.writeVarU32(count))
+            return false;
+        if (!e.writeValType(prev))
+            return false;
+    }
+
+    return true;
+}
+
+bool
+wasm::DecodeLocalEntries(Decoder& d, ValTypeVector* locals)
+{
+    uint32_t numLocalEntries;
+    if (!d.readVarU32(&numLocalEntries))
+        return false;
+
+    for (uint32_t i = 0; i < numLocalEntries; i++) {
+        uint32_t count;
+        if (!d.readVarU32(&count))
+            return false;
+
+        if (MaxLocals - locals->length() < count)
+            return false;
+
+        ValType type;
+        if (!d.readValType(&type))
+            return false;
+
+        if (!locals->appendN(type, count))
+            return false;
+    }
+
+    return true;
+}
--- a/js/src/asmjs/WasmBinary.h
+++ b/js/src/asmjs/WasmBinary.h
@@ -793,12 +793,23 @@ class Decoder
     MOZ_WARN_UNUSED_RESULT bool readFixedI32(int32_t* i = nullptr) {
         return read<int32_t>(i);
     }
     uint8_t uncheckedReadFixedU8() {
         return uncheckedRead<uint8_t>();
     }
 };
 
+// Reusable macro encoding/decoding functions reused by both the two
+// encoders (AsmJS/WasmText) and decoders (Wasm/WasmIonCompile).
+
+typedef Vector<ValType, 8, SystemAllocPolicy> ValTypeVector;
+
+bool
+EncodeLocalEntries(Encoder& d, const ValTypeVector& locals);
+
+bool
+DecodeLocalEntries(Decoder& d, ValTypeVector* locals);
+
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_binary_h
--- a/js/src/asmjs/WasmGenerator.cpp
+++ b/js/src/asmjs/WasmGenerator.cpp
@@ -720,17 +720,17 @@ ModuleGenerator::numExports() const
 {
     return module_->exports.length();
 }
 
 bool
 ModuleGenerator::addMemoryExport(UniqueChars fieldName)
 {
     return exportMap_->fieldNames.append(Move(fieldName)) &&
-           exportMap_->fieldsToExports.append(ExportMap::MemoryExport);
+           exportMap_->fieldsToExports.append(MemoryExport);
 }
 
 bool
 ModuleGenerator::startFuncDefs()
 {
     MOZ_ASSERT(!startedFuncDefs());
     threadView_ = MakeUnique<ModuleGeneratorThreadView>(*shared_);
     if (!threadView_)
--- a/js/src/asmjs/WasmIonCompile.cpp
+++ b/js/src/asmjs/WasmIonCompile.cpp
@@ -3046,29 +3046,25 @@ 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());
 
+    // Build the local types vector.
+
     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;
-    }
+    if (!DecodeLocalEntries(d, &locals))
+        return false;
 
     // Set up for Ion compilation.
 
     JitContext jitContext(CompileRuntime::get(task->runtime()), &results.alloc());
     const JitCompileOptions options;
     MIRGraph graph(&results.alloc());
     CompileInfo compileInfo(locals.length());
     MIRGenerator mir(nullptr, options, &results.alloc(), &graph, &compileInfo,
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -49,18 +49,16 @@ using namespace js::jit;
 using namespace js::wasm;
 using mozilla::BinarySearch;
 using mozilla::MakeEnumeratedRange;
 using mozilla::PodCopy;
 using mozilla::PodZero;
 using mozilla::Swap;
 using JS::GenericNaN;
 
-const uint32_t ExportMap::MemoryExport;
-
 UniqueCodePtr
 wasm::AllocateCode(ExclusiveContext* cx, size_t bytes)
 {
     // On most platforms, this will allocate RWX memory. On iOS, or when
     // --non-writable-jitcode is used, this will allocate RW memory. In this
     // case, DynamicallyLinkModule will reprotect the code as RX.
     unsigned permissions =
         ExecutableAllocator::initialProtectionFlags(ExecutableAllocator::Writable);
@@ -1125,17 +1123,17 @@ CreateExportObject(JSContext* cx,
     MOZ_ASSERT(exportMap.exportFuncIndices.length() == exports.length());
     MOZ_ASSERT(exportMap.fieldNames.length() == exportMap.fieldsToExports.length());
 
     for (size_t fieldIndex = 0; fieldIndex < exportMap.fieldNames.length(); fieldIndex++) {
         const char* fieldName = exportMap.fieldNames[fieldIndex].get();
         if (!*fieldName) {
             MOZ_ASSERT(!exportObj);
             uint32_t exportIndex = exportMap.fieldsToExports[fieldIndex];
-            if (exportIndex == ExportMap::MemoryExport) {
+            if (exportIndex == MemoryExport) {
                 MOZ_ASSERT(heap);
                 exportObj.set(heap);
             } else {
                 exportObj.set(NewExportedFunction(cx, moduleObj, exportMap, exportIndex));
                 if (!exportObj)
                     return false;
             }
             break;
@@ -1162,17 +1160,17 @@ CreateExportObject(JSContext* cx,
 
         JSAtom* atom = AtomizeUTF8Chars(cx, fieldName, strlen(fieldName));
         if (!atom)
             return false;
 
         RootedId id(cx, AtomToId(atom));
         RootedValue val(cx);
         uint32_t exportIndex = exportMap.fieldsToExports[fieldIndex];
-        if (exportIndex == ExportMap::MemoryExport)
+        if (exportIndex == MemoryExport)
             val = ObjectValue(*heap);
         else
             val = vals[exportIndex];
 
         if (!JS_DefinePropertyById(cx, exportObj, id, val, JSPROP_ENUMERATE))
             return false;
     }
 
--- a/js/src/asmjs/WasmModule.h
+++ b/js/src/asmjs/WasmModule.h
@@ -331,20 +331,20 @@ typedef Vector<CacheableChars, 0, System
 // The 'fieldNames' vector provides the list of names of the module's exports.
 // For each field in fieldNames, 'fieldsToExports' provides either:
 //  - the sentinel value MemoryExport indicating an export of linear memory; or
 //  - the index of an export (both into the module's ExportVector and the
 //    ExportMap's exportFuncIndices vector).
 // Lastly, the 'exportFuncIndices' vector provides, for each exported function,
 // the internal index of the function.
 
+static const uint32_t MemoryExport = UINT32_MAX;
+
 struct ExportMap
 {
-    static const uint32_t MemoryExport = UINT32_MAX;
-
     CacheableCharsVector fieldNames;
     Uint32Vector fieldsToExports;
     Uint32Vector exportFuncIndices;
 
     WASM_DECLARE_SERIALIZABLE(ExportMap)
 };
 
 typedef UniquePtr<ExportMap> UniqueExportMap;
--- a/js/src/asmjs/WasmText.cpp
+++ b/js/src/asmjs/WasmText.cpp
@@ -3932,23 +3932,21 @@ 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()))
+    ValTypeVector varTypes;
+    if (!varTypes.appendAll(func.vars()))
         return false;
-
-    for (ValType type : func.vars()) {
-        if (!e.writeValType(type))
-            return false;
-    }
+    if (!EncodeLocalEntries(e, varTypes))
+        return false;
 
     for (WasmAstExpr* expr : func.body()) {
         if (!EncodeExpr(e, *expr))
             return false;
     }
 
     e.patchVarU32(bodySizeAt, e.currentOffset() - beforeBody);
     return true;
--- a/js/src/asmjs/WasmTypes.h
+++ b/js/src/asmjs/WasmTypes.h
@@ -38,17 +38,16 @@ class PropertyName;
 
 namespace wasm {
 
 using mozilla::EnumeratedArray;
 using mozilla::Move;
 using mozilla::MallocSizeOf;
 
 typedef Vector<uint32_t, 0, SystemAllocPolicy> Uint32Vector;
-typedef Vector<ValType, 8, SystemAllocPolicy> ValTypeVector;
 
 // ValType/ExprType utilities
 
 static inline bool
 IsVoid(ExprType et)
 {
     return et == ExprType::Void;
 }
@@ -591,16 +590,17 @@ enum ModuleKind
 static const unsigned ActivationGlobalDataOffset = 0;
 static const unsigned HeapGlobalDataOffset       = ActivationGlobalDataOffset + sizeof(void*);
 static const unsigned NaN64GlobalDataOffset      = HeapGlobalDataOffset + sizeof(void*);
 static const unsigned NaN32GlobalDataOffset      = NaN64GlobalDataOffset + sizeof(double);
 static const unsigned InitialGlobalDataBytes     = NaN32GlobalDataOffset + sizeof(float);
 
 static const unsigned MaxSigs                    =        4 * 1024;
 static const unsigned MaxFuncs                   =      512 * 1024;
+static const unsigned MaxLocals                  =       64 * 1024;
 static const unsigned MaxImports                 =       64 * 1024;
 static const unsigned MaxExports                 =       64 * 1024;
 static const unsigned MaxTableElems              =      128 * 1024;
 static const unsigned MaxArgsPerFunc             =        4 * 1024;
 static const unsigned MaxBrTableElems            = 4 * 1024 * 1024;
 
 } // namespace wasm
 } // namespace js
--- a/js/src/gc/StoreBuffer.h
+++ b/js/src/gc/StoreBuffer.h
@@ -309,18 +309,18 @@ class StoreBuffer
                    (start <= otherEnd && otherEnd <= end);
         }
 
         // Destructively make this SlotsEdge range the union of the other
         // SlotsEdge range and this one. A precondition is that the ranges must
         // overlap.
         void merge(const SlotsEdge& other) {
             MOZ_ASSERT(overlaps(other));
-            auto end = std::max(start_ + count_, other.start_ + other.count_);
-            start_ = std::min(start_, other.start_);
+            auto end = Max(start_ + count_, other.start_ + other.count_);
+            start_ = Min(start_, other.start_);
             count_ = end - start_;
         }
 
         bool maybeInRememberedSet(const Nursery& n) const {
             return !IsInsideNursery(reinterpret_cast<Cell*>(object()));
         }
 
         void trace(TenuringTracer& mover) const;
--- a/js/src/jit-test/tests/wasm/binary.js
+++ b/js/src/jit-test/tests/wasm/binary.js
@@ -158,18 +158,18 @@ assertErrorMessage(() => wasmEval(module
 assertThrowsInstanceOf(() => wasmEval(moduleWithSections([{name: sigId, body: [1]}])), TypeError);
 assertThrowsInstanceOf(() => wasmEval(moduleWithSections([{name: sigId, body: [1, 1, 0]}])), TypeError);
 
 wasmEval(moduleWithSections([sigSection([])]));
 wasmEval(moduleWithSections([sigSection([v2vSig])]));
 wasmEval(moduleWithSections([sigSection([i2vSig])]));
 wasmEval(moduleWithSections([sigSection([v2vSig, i2vSig])]));
 
-assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([{args:[], ret:100}])])), TypeError, /bad expression type/);
-assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([{args:[100], ret:VoidCode}])])), TypeError, /bad value type/);
+assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([{args:[], ret:100}])])), TypeError, /expression type/);
+assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([{args:[100], ret:VoidCode}])])), TypeError, /value type/);
 
 assertThrowsInstanceOf(() => wasmEval(moduleWithSections([sigSection([]), declSection([0])])), TypeError, /signature index out of range/);
 assertThrowsInstanceOf(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([1])])), TypeError, /signature index out of range/);
 assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0])])), TypeError, /expected function bodies/);
 wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([v2vBody])]));
 
 assertThrowsInstanceOf(() => wasmEval(moduleWithSections([sigSection([v2vSig]), {name: importId, body:[]}])), TypeError);
 assertErrorMessage(() => wasmEval(moduleWithSections([importSection([{sigIndex:0, module:"a", func:"b"}])])), TypeError, /signature index out of range/);
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -142,16 +142,17 @@ EXPORTS.js += [
     '../public/Value.h',
     '../public/Vector.h',
     '../public/WeakMapPtr.h',
 ]
 
 UNIFIED_SOURCES += [
     'asmjs/AsmJS.cpp',
     'asmjs/Wasm.cpp',
+    'asmjs/WasmBinary.cpp',
     'asmjs/WasmFrameIterator.cpp',
     'asmjs/WasmGenerator.cpp',
     'asmjs/WasmIonCompile.cpp',
     'asmjs/WasmModule.cpp',
     'asmjs/WasmSignalHandlers.cpp',
     'asmjs/WasmStubs.cpp',
     'asmjs/WasmText.cpp',
     'asmjs/WasmTypes.cpp',