Bug 1502035 - DataCount section. r=luke
authorLars T Hansen <lhansen@mozilla.com>
Wed, 23 Jan 2019 16:06:16 +0100
changeset 515445 426bafe23415b19d83dc3e084f66f71f156d53c7
parent 515444 5e914b7f1c0961f2707fc8c5694c9b0fa014dd58
child 515446 83097742e871e019732638bcdc8eeb36fc0c0c89
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1502035
milestone66.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 1502035 - DataCount section. r=luke We now require the presence of a DataCount section to forward-declare the number of data segments if the code uses memory.init or data.drop. Those instructions may only reference segments in the range [0..dataCount-1]. If there is a DataCount section then the number of forward-declared segments must match the actual count of segments. We no longer need the deferred validation state, so we can remove it. That's pretty mechanical and comprises the bulk of the patch. text->binary translation will insert a DataCount section if memory.init or data.drop is used and the DataCount section is not present in the text (this will be the common case); however, if the section is present in the text then translation will just encode whatever the text contains. The section is not synthesized if it is not needed. Also: test cases for all of this. Also: drive-by fix the passive-segments test cases: do not duplicate functionality from wasm-binary.js, and use opcode names when possible.
js/src/jit-test/lib/wasm-binary.js
js/src/jit-test/tests/wasm/passive-segs-boundary.js
js/src/jit-test/tests/wasm/passive-segs-nonboundary.js
js/src/vm/MutexIDs.h
js/src/wasm/WasmAST.h
js/src/wasm/WasmBaselineCompile.cpp
js/src/wasm/WasmBaselineCompile.h
js/src/wasm/WasmCompile.cpp
js/src/wasm/WasmConstants.h
js/src/wasm/WasmCraneliftCompile.cpp
js/src/wasm/WasmCraneliftCompile.h
js/src/wasm/WasmGenerator.cpp
js/src/wasm/WasmGenerator.h
js/src/wasm/WasmIonCompile.cpp
js/src/wasm/WasmIonCompile.h
js/src/wasm/WasmOpIter.h
js/src/wasm/WasmTextToBinary.cpp
js/src/wasm/WasmValidate.cpp
js/src/wasm/WasmValidate.h
--- a/js/src/jit-test/lib/wasm-binary.js
+++ b/js/src/jit-test/lib/wasm-binary.js
@@ -103,16 +103,24 @@ const RefNull          = 0xd0;
 const FirstInvalidOpcode = 0xc5;
 const LastInvalidOpcode = 0xfb;
 const MiscPrefix = 0xfc;
 const SimdPrefix = 0xfd;
 const ThreadPrefix = 0xfe;
 const MozPrefix = 0xff;
 
 // Secondary opcode bytes for misc prefix
+const MemoryInitCode = 0x08;    // Pending
+const DataDropCode = 0x09;      // Pending
+const MemoryCopyCode = 0x0a;    // Pending
+const MemoryFillCode = 0x0b;    // Pending
+const TableInitCode = 0x0c;     // Pending
+const ElemDropCode = 0x0d;      // Pending
+const TableCopyCode = 0x0e;     // Pending
+
 const StructNew = 0x50;         // UNOFFICIAL
 const StructGet = 0x51;         // UNOFFICIAL
 const StructSet = 0x52;         // UNOFFICIAL
 const StructNarrow = 0x53;      // UNOFFICIAL
 
 // DefinitionKind
 const FunctionCode     = 0x00;
 const TableCode        = 0x01;
--- a/js/src/jit-test/tests/wasm/passive-segs-boundary.js
+++ b/js/src/jit-test/tests/wasm/passive-segs-boundary.js
@@ -109,21 +109,21 @@ mem_test("data.drop 3", "",
 
 // init with no memory
 mem_test("(memory.init 1 (i32.const 1234) (i32.const 1) (i32.const 1))", "",
          WebAssembly.CompileError, /can't touch memory without memory/,
          false);
 
 // drop with data seg ix out of range
 mem_test("data.drop 4", "",
-         WebAssembly.CompileError, /memory.{drop,init} index out of range/);
+         WebAssembly.CompileError, /data.drop segment index out of range/);
 
 // init with data seg ix out of range
 mem_test("(memory.init 4 (i32.const 1234) (i32.const 1) (i32.const 1))", "",
-         WebAssembly.CompileError, /memory.{drop,init} index out of range/);
+         WebAssembly.CompileError, /memory.init segment index out of range/);
 
 // drop with data seg ix indicating an active segment
 mem_test("data.drop 2", "",
          WebAssembly.RuntimeError, /use of invalid passive data segment/);
 
 // init with data seg ix indicating an active segment
 mem_test("(memory.init 2 (i32.const 1234) (i32.const 1) (i32.const 1))", "",
          WebAssembly.RuntimeError, /use of invalid passive data segment/);
--- a/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js
+++ b/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js
@@ -1,10 +1,15 @@
 // |jit-test| skip-if: !wasmBulkMemSupported()
 
+load(libdir + "wasm-binary.js");
+
+const v2vSig = {args:[], ret:VoidCode};
+const v2vSigSection = sigSection([v2vSig]);
+
 const Module = WebAssembly.Module;
 const Instance = WebAssembly.Instance;
 
 // Some non-boundary tests for {table,memory}.{init,drop,copy}.  The table
 // case is more complex and appears first.  The memory case is a simplified
 // version of it.
 
 // This module exports 5 functions ..
@@ -248,134 +253,87 @@ mem_test("(memory.init 1 (i32.const 7) (
          "data.drop 3 \n" +
          "(memory.copy (i32.const 20) (i32.const 15) (i32.const 5)) \n" +
          "(memory.copy (i32.const 21) (i32.const 29) (i32.const 1)) \n" +
          "(memory.copy (i32.const 24) (i32.const 10) (i32.const 1)) \n" +
          "(memory.copy (i32.const 13) (i32.const 11) (i32.const 4)) \n" +
          "(memory.copy (i32.const 19) (i32.const 20) (i32.const 5))",
          [e,e,3,1,4, 1,e,2,7,1, 8,e,7,e,7, 5,2,7,e,9, e,7,e,8,8, e,e,e,e,e]);
 
+// DataCount section is present but value is too low for the number of data segments
+assertErrorMessage(() => wasmEvalText(
+    `(module
+       (datacount 1)
+       (data passive "")
+       (data passive ""))`),
+                   WebAssembly.CompileError,
+                   /number of data segments does not match declared count/);
+
+// DataCount section is present but value is too high for the number of data segments
+assertErrorMessage(() => wasmEvalText(
+    `(module
+       (datacount 3)
+       (data passive "")
+       (data passive ""))`),
+                   WebAssembly.CompileError,
+                   /number of data segments does not match declared count/);
+
+// DataCount section is not present but memory.init or data.drop uses it
+function checkNoDataCount(body, err) {
+    let binary = moduleWithSections(
+        [v2vSigSection,
+         declSection([0]),
+         memorySection(1),
+         bodySection(
+             [funcBody({locals:[], body})])]);
+    assertErrorMessage(() => new WebAssembly.Module(binary),
+                       WebAssembly.CompileError,
+                       err);
+}
+
+checkNoDataCount([I32ConstCode, 0,
+                  I32ConstCode, 0,
+                  I32ConstCode, 0,
+                  MiscPrefix, MemoryInitCode, 0, 0],
+                /memory.init requires a DataCount section/);
+
+checkNoDataCount([MiscPrefix, DataDropCode, 0],
+                 /data.drop requires a DataCount section/);
 
 //---------------------------------------------------------------------//
 //---------------------------------------------------------------------//
 // Some further tests for memory.copy and memory.fill.  First, validation
 // tests.
 
-//-----------------------------------------------------------
-// Test helpers.  Copied and simplified from binary.js.
-
-load(libdir + "wasm-binary.js");
-
-function toU8(array) {
-    for (let b of array)
-        assertEq(b < 256, true);
-    return Uint8Array.from(array);
-}
-
-function varU32(u32) {
-    assertEq(u32 >= 0, true);
-    assertEq(u32 < Math.pow(2,32), true);
-    var bytes = [];
-    do {
-        var byte = u32 & 0x7f;
-        u32 >>>= 7;
-        if (u32 != 0)
-            byte |= 0x80;
-        bytes.push(byte);
-    } while (u32 != 0);
-    return bytes;
-}
-
-function moduleHeaderThen(...rest) {
-    return [magic0, magic1, magic2, magic3, ver0, ver1, ver2, ver3, ...rest];
-}
-
-function moduleWithSections(sectionArray) {
-    var bytes = moduleHeaderThen();
-    for (let section of sectionArray) {
-        bytes.push(section.name);
-        bytes.push(...varU32(section.body.length));
-        bytes.push(...section.body);
-    }
-    return toU8(bytes);
-}
-
-function sigSection(sigs) {
-    var body = [];
-    body.push(...varU32(sigs.length));
-    for (let sig of sigs) {
-        body.push(...varU32(FuncCode));
-        body.push(...varU32(sig.args.length));
-        for (let arg of sig.args)
-            body.push(...varU32(arg));
-        body.push(...varU32(sig.ret == VoidCode ? 0 : 1));
-        if (sig.ret != VoidCode)
-            body.push(...varU32(sig.ret));
-    }
-    return { name: typeId, body };
-}
-
-function declSection(decls) {
-    var body = [];
-    body.push(...varU32(decls.length));
-    for (let decl of decls)
-        body.push(...varU32(decl));
-    return { name: functionId, body };
-}
-
-function funcBody(func) {
-    var body = varU32(func.locals.length);
-    for (let local of func.locals)
-        body.push(...varU32(local));
-    body = body.concat(...func.body);
-    body.push(EndCode);
-    body.splice(0, 0, ...varU32(body.length));
-    return body;
-}
-
-function bodySection(bodies) {
-    var body = varU32(bodies.length).concat(...bodies);
-    return { name: codeId, body };
-}
-
-function memorySection(initialSize) {
-    var body = [];
-    body.push(...varU32(1));           // number of memories
-    body.push(...varU32(0x0));         // for now, no maximum
-    body.push(...varU32(initialSize));
-    return { name: memoryId, body };
-}
-
-const v2vSig = {args:[], ret:VoidCode};
-const v2vSigSection = sigSection([v2vSig]);
-
 // Prefixed opcodes
 
 function checkMiscPrefixed(opcode, expect_failure) {
     let binary = moduleWithSections(
            [v2vSigSection, declSection([0]), memorySection(1),
             bodySection(
                 [funcBody(
                     {locals:[],
-                     body:[0x41, 0x0, 0x41, 0x0, 0x41, 0x0, // 3 x const.i32 0
+                     body:[I32ConstCode, 0x0,
+                           I32ConstCode, 0x0,
+                           I32ConstCode, 0x0,
                            MiscPrefix, ...opcode]})])]);
     if (expect_failure) {
         assertErrorMessage(() => new WebAssembly.Module(binary),
                            WebAssembly.CompileError, /unrecognized opcode/);
     } else {
         assertEq(WebAssembly.validate(binary), true);
     }
 }
 
 //-----------------------------------------------------------
 // Verification cases for memory.copy/fill opcode encodings
 
-checkMiscPrefixed([0x0a, 0x00, 0x00], false); // memory.copy src=0 dest=0
-checkMiscPrefixed([0x0b, 0x00], false); // memory.fill mem=0
-checkMiscPrefixed([0x0b, 0x80, 0x00], false); // memory.fill, mem=0 (long encoding)
+checkMiscPrefixed([MemoryCopyCode, 0x00, 0x00], false); // memory.copy src=0 dest=0
+checkMiscPrefixed([MemoryFillCode, 0x00], false); // memory.fill mem=0
+checkMiscPrefixed([MemoryFillCode, 0x80, 0x00], false); // memory.fill, mem=0 (long encoding)
 checkMiscPrefixed([0x13], true);        // table.size+1, which is currently unassigned
 
 //-----------------------------------------------------------
 // Verification cases for memory.copy/fill arguments
 
 // Invalid argument types
 {
     const tys = ['i32', 'f32', 'i64', 'f64'];
--- a/js/src/vm/MutexIDs.h
+++ b/js/src/vm/MutexIDs.h
@@ -55,17 +55,16 @@
   _(WasmStreamEnd, 500)               \
   _(WasmStreamStatus, 500)            \
   _(WasmRuntimeInstances, 500)        \
   _(WasmSignalInstallState, 500)      \
                                       \
   _(IcuTimeZoneStateMutex, 600)       \
   _(ThreadId, 600)                    \
   _(WasmCodeSegmentMap, 600)          \
-  _(WasmDeferredValidation, 600)      \
   _(TraceLoggerGraphState, 600)       \
   _(VTuneLock, 600)
 
 namespace js {
 namespace mutexid {
 
 #define DEFINE_MUTEX_ID(name, order) static const MutexId name{#name, order};
 FOR_EACH_MUTEX(DEFINE_MUTEX_ID)
--- a/js/src/wasm/WasmAST.h
+++ b/js/src/wasm/WasmAST.h
@@ -1292,16 +1292,17 @@ class AstModule : public AstNode {
   FuncTypeMap funcTypeMap_;
   ImportVector imports_;
   NameVector funcImportNames_;
   AstTableVector tables_;
   AstMemoryVector memories_;
 #ifdef ENABLE_WASM_REFTYPES
   uint32_t gcFeatureOptIn_;
 #endif
+  Maybe<uint32_t> dataCount_;
   ExportVector exports_;
   Maybe<AstStartFunc> startFunc_;
   FuncVector funcs_;
   AstDataSegmentVector dataSegments_;
   AstElemSegmentVector elemSegments_;
   AstGlobalVector globals_;
 
   size_t numGlobalImports_;
@@ -1332,16 +1333,22 @@ class AstModule : public AstNode {
   const AstMemoryVector& memories() const { return memories_; }
 #ifdef ENABLE_WASM_REFTYPES
   bool addGcFeatureOptIn(uint32_t version) {
     gcFeatureOptIn_ = version;
     return true;
   }
   uint32_t gcFeatureOptIn() const { return gcFeatureOptIn_; }
 #endif
+  bool initDataCount(uint32_t dataCount) {
+    MOZ_ASSERT(dataCount_.isNothing());
+    dataCount_.emplace(dataCount);
+    return true;
+  }
+  Maybe<uint32_t> dataCount() const { return dataCount_; }
   bool addTable(AstName name, const Limits& table, TableKind tableKind) {
     return tables_.append(AstTable(table, tableKind, false, name));
   }
   bool hasTable() const { return !!tables_.length(); }
   const AstTableVector& tables() const { return tables_; }
   bool append(AstDataSegment* seg) { return dataSegments_.append(seg); }
   const AstDataSegmentVector& dataSegments() const { return dataSegments_; }
   bool append(AstElemSegment* seg) { return elemSegments_.append(seg); }
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -2562,18 +2562,18 @@ class BaseCompiler final : public BaseCo
   RegF64 joinRegF64_;
 
   // There are more members scattered throughout.
 
  public:
   BaseCompiler(const ModuleEnvironment& env, const FuncCompileInput& input,
                const ValTypeVector& locals, const MachineState& trapExitLayout,
                size_t trapExitLayoutNumWords, Decoder& decoder,
-               ExclusiveDeferredValidationState& dvs, TempAllocator* alloc,
-               MacroAssembler* masm, StackMaps* stackMaps);
+               TempAllocator* alloc, MacroAssembler* masm,
+               StackMaps* stackMaps);
 
   MOZ_MUST_USE bool init();
 
   FuncOffsets finish();
 
   MOZ_MUST_USE bool emitFunction();
   void emitInitStackLocals();
 
@@ -11812,21 +11812,20 @@ bool BaseCompiler::emitFunction() {
   return true;
 }
 
 BaseCompiler::BaseCompiler(const ModuleEnvironment& env,
                            const FuncCompileInput& func,
                            const ValTypeVector& locals,
                            const MachineState& trapExitLayout,
                            size_t trapExitLayoutNumWords, Decoder& decoder,
-                           ExclusiveDeferredValidationState& dvs,
                            TempAllocator* alloc, MacroAssembler* masm,
                            StackMaps* stackMaps)
     : env_(env),
-      iter_(env, decoder, dvs),
+      iter_(env, decoder),
       func_(func),
       lastReadCallSite_(0),
       alloc_(*alloc),
       locals_(locals),
       deadCode_(false),
       bceSafe_(0),
       latentOp_(LatentOp::None),
       latentType_(ValType::I32),
@@ -11940,17 +11939,16 @@ bool js::wasm::BaselineCanCompile() {
   return false;
 #endif
 }
 
 bool js::wasm::BaselineCompileFunctions(const ModuleEnvironment& env,
                                         LifoAlloc& lifo,
                                         const FuncCompileInputVector& inputs,
                                         CompiledCode* code,
-                                        ExclusiveDeferredValidationState& dvs,
                                         UniqueChars* error) {
   MOZ_ASSERT(env.tier() == Tier::Baseline);
   MOZ_ASSERT(env.kind == ModuleKind::Wasm);
 
   // The MacroAssembler will sometimes access the jitContext.
 
   TempAllocator alloc(&lifo);
   JitContext jitContext(&alloc);
@@ -11980,17 +11978,17 @@ bool js::wasm::BaselineCompileFunctions(
     if (!DecodeLocalEntries(d, env.kind, env.types, env.gcTypesEnabled(),
                             &locals)) {
       return false;
     }
 
     // One-pass baseline compilation.
 
     BaseCompiler f(env, func, locals, trapExitLayout, trapExitLayoutNumWords, d,
-                   dvs, &alloc, &masm, &code->stackMaps);
+                   &alloc, &masm, &code->stackMaps);
     if (!f.init()) {
       return false;
     }
     if (!f.emitFunction()) {
       return false;
     }
     if (!code->codeRanges.emplaceBack(func.index, func.lineOrBytecode,
                                       f.finish())) {
--- a/js/src/wasm/WasmBaselineCompile.h
+++ b/js/src/wasm/WasmBaselineCompile.h
@@ -27,17 +27,17 @@ namespace wasm {
 // Return whether BaselineCompileFunction can generate code on the current
 // device.
 bool BaselineCanCompile();
 
 // Generate adequate code quickly.
 MOZ_MUST_USE bool BaselineCompileFunctions(
     const ModuleEnvironment& env, LifoAlloc& lifo,
     const FuncCompileInputVector& inputs, CompiledCode* code,
-    ExclusiveDeferredValidationState& dvs, UniqueChars* error);
+    UniqueChars* error);
 
 class BaseLocalIter {
  private:
   using ConstValTypeRange = mozilla::Range<const ValType>;
 
   const ValTypeVector& locals_;
   size_t argsLength_;
   ConstValTypeRange argsRange_;  // range struct cache for ABIArgIter
--- a/js/src/wasm/WasmCompile.cpp
+++ b/js/src/wasm/WasmCompile.cpp
@@ -559,17 +559,17 @@ SharedModule wasm::CompileBuffer(const C
   if (!mg.init()) {
     return nullptr;
   }
 
   if (!DecodeCodeSection(env, d, mg)) {
     return nullptr;
   }
 
-  if (!DecodeModuleTail(d, &env, mg.deferredValidationState())) {
+  if (!DecodeModuleTail(d, &env)) {
     return nullptr;
   }
 
   return mg.finishModule(bytecode, nullptr, maybeLinkData);
 }
 
 void wasm::CompileTier2(const CompileArgs& args, const Bytes& bytecode,
                         const Module& module, Atomic<bool>* cancelled) {
@@ -597,17 +597,17 @@ void wasm::CompileTier2(const CompileArg
   if (!mg.init()) {
     return;
   }
 
   if (!DecodeCodeSection(env, d, mg)) {
     return;
   }
 
-  if (!DecodeModuleTail(d, &env, mg.deferredValidationState())) {
+  if (!DecodeModuleTail(d, &env)) {
     return;
   }
 
   if (!mg.finishTier2(module)) {
     return;
   }
 
   // The caller doesn't care about success or failure; only that compilation
@@ -738,17 +738,17 @@ SharedModule wasm::CompileStreaming(
   }
 
   const StreamEndData& streamEnd = exclusiveStreamEnd.lock();
   const Bytes& tailBytes = *streamEnd.tailBytes;
 
   {
     Decoder d(tailBytes, env->codeSection->end(), error, warnings);
 
-    if (!DecodeModuleTail(d, env.ptr(), mg.deferredValidationState())) {
+    if (!DecodeModuleTail(d, env.ptr())) {
       return nullptr;
     }
 
     MOZ_ASSERT(d.done());
   }
 
   SharedBytes bytecode = CreateBytecode(envBytes, codeBytes, tailBytes, error);
   if (!bytecode) {
--- a/js/src/wasm/WasmConstants.h
+++ b/js/src/wasm/WasmConstants.h
@@ -33,16 +33,17 @@ enum class SectionId {
   Table = 4,
   Memory = 5,
   Global = 6,
   Export = 7,
   Start = 8,
   Elem = 9,
   Code = 10,
   Data = 11,
+  DataCount = 12,
   GcFeatureOptIn = 42  // Arbitrary, but fits in 7 bits
 };
 
 enum class TypeCode {
   I32 = 0x7f,  // SLEB128(-0x01)
   I64 = 0x7e,  // SLEB128(-0x02)
   F32 = 0x7d,  // SLEB128(-0x03)
   F64 = 0x7c,  // SLEB128(-0x04)
--- a/js/src/wasm/WasmCraneliftCompile.cpp
+++ b/js/src/wasm/WasmCraneliftCompile.cpp
@@ -275,17 +275,16 @@ const GlobalDesc* env_global(const Crane
                              size_t globalIndex) {
   return &env->env.globals[globalIndex];
 }
 
 bool wasm::CraneliftCompileFunctions(const ModuleEnvironment& env,
                                      LifoAlloc& lifo,
                                      const FuncCompileInputVector& inputs,
                                      CompiledCode* code,
-                                     ExclusiveDeferredValidationState& dvs,
                                      UniqueChars* error) {
 
   MOZ_RELEASE_ASSERT(CraneliftCanCompile());
 
   MOZ_ASSERT(env.tier() == Tier::Optimized);
   MOZ_ASSERT(env.optimizedBackend() == OptimizedBackend::Cranelift);
   MOZ_ASSERT(!env.isAsmJS());
 
@@ -302,17 +301,17 @@ bool wasm::CraneliftCompileFunctions(con
   // Swap in already-allocated empty vectors to avoid malloc/free.
   MOZ_ASSERT(code->empty());
   if (!code->swap(masm)) {
     return false;
   }
 
   for (const FuncCompileInput& func : inputs) {
     Decoder d(func.begin, func.end, func.lineOrBytecode, error);
-    if (!ValidateFunctionBody(env, func.index, func.end - func.begin, d, dvs)) {
+    if (!ValidateFunctionBody(env, func.index, func.end - func.begin, d)) {
       return false;
     }
 
     CraneliftFuncCompileInput clifInput(func);
 
     CraneliftCompiledFunc clifFunc;
     if (!cranelift_compile_function(compiler, &clifInput, &clifFunc)) {
       *error = JS_smprintf("Cranelift error in clifFunc #%u", clifInput.index);
--- a/js/src/wasm/WasmCraneliftCompile.h
+++ b/js/src/wasm/WasmCraneliftCompile.h
@@ -31,14 +31,14 @@ MOZ_MUST_USE bool CraneliftCanCompile();
 #else
 MOZ_MUST_USE inline bool CraneliftCanCompile() { return false; }
 #endif
 
 // Generates code with Cranelift.
 MOZ_MUST_USE bool CraneliftCompileFunctions(
     const ModuleEnvironment& env, LifoAlloc& lifo,
     const FuncCompileInputVector& inputs, CompiledCode* code,
-    ExclusiveDeferredValidationState& dvs, UniqueChars* error);
+    UniqueChars* error);
 
 }  // namespace wasm
 }  // namespace js
 
 #endif  // wasm_cranelift_compile_h
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -77,17 +77,16 @@ ModuleGenerator::ModuleGenerator(const C
       metadataTier_(nullptr),
       taskState_(mutexid::WasmCompileTaskState),
       lifo_(GENERATOR_LIFO_DEFAULT_CHUNK_SIZE),
       masmAlloc_(&lifo_),
       masm_(masmAlloc_),
       debugTrapCodeOffset_(),
       lastPatchedCallSite_(0),
       startOfUnpatchedCallsites_(0),
-      deferredValidationState_(mutexid::WasmDeferredValidation),
       parallel_(false),
       outstanding_(0),
       currentTask_(nullptr),
       batchedBytecode_(0),
       finishedFuncDefs_(false) {
   MOZ_ASSERT(IsCompilingWasm());
 }
 
@@ -381,20 +380,16 @@ bool ModuleGenerator::init(Metadata* may
     FuncType funcType;
     if (!funcType.clone(*env_->funcTypes[funcIndex.index()])) {
       return false;
     }
     metadataTier_->funcExports.infallibleEmplaceBack(
         std::move(funcType), funcIndex.index(), funcIndex.isExplicit());
   }
 
-  // Ensure that mutable shared state for deferred validation is correctly
-  // set up.
-  deferredValidationState_.lock()->init();
-
   // Determine whether parallel or sequential compilation is to be used and
   // initialize the CompileTasks that will be used in either mode.
 
   GlobalHelperThreadState& threads = HelperThreadState();
   MOZ_ASSERT(threads.threadCount > 1);
 
   uint32_t numTasks;
   if (CanUseExtraThreads() && threads.cpuCount > 1) {
@@ -403,17 +398,17 @@ bool ModuleGenerator::init(Metadata* may
   } else {
     numTasks = 1;
   }
 
   if (!tasks_.initCapacity(numTasks)) {
     return false;
   }
   for (size_t i = 0; i < numTasks; i++) {
-    tasks_.infallibleEmplaceBack(*env_, taskState_, deferredValidationState_,
+    tasks_.infallibleEmplaceBack(*env_, taskState_,
                                  COMPILATION_LIFO_DEFAULT_CHUNK_SIZE);
   }
 
   if (!freeTasks_.reserve(numTasks)) {
     return false;
   }
   for (size_t i = 0; i < numTasks; i++) {
     freeTasks_.infallibleAppend(&tasks_[i]);
@@ -703,31 +698,31 @@ static bool ExecuteCompileTask(CompileTa
   MOZ_ASSERT(task->lifo.isEmpty());
   MOZ_ASSERT(task->output.empty());
 
   switch (task->env.tier()) {
     case Tier::Optimized:
 #ifdef ENABLE_WASM_CRANELIFT
       if (task->env.optimizedBackend() == OptimizedBackend::Cranelift) {
         if (!CraneliftCompileFunctions(task->env, task->lifo, task->inputs,
-                                       &task->output, task->dvs, error)) {
+                                       &task->output, error)) {
           return false;
         }
         break;
       }
 #endif
       MOZ_ASSERT(task->env.optimizedBackend() == OptimizedBackend::Ion);
       if (!IonCompileFunctions(task->env, task->lifo, task->inputs,
-                               &task->output, task->dvs, error)) {
+                               &task->output, error)) {
         return false;
       }
       break;
     case Tier::Baseline:
       if (!BaselineCompileFunctions(task->env, task->lifo, task->inputs,
-                                    &task->output, task->dvs, error)) {
+                                    &task->output, error)) {
         return false;
       }
       break;
   }
 
   MOZ_ASSERT(task->lifo.isEmpty());
   MOZ_ASSERT(task->inputs.length() == task->output.codeRanges.length());
   task->inputs.clear();
@@ -1005,24 +1000,16 @@ UniqueCodeTier ModuleGenerator::finishCo
                      metadataTier_->funcExports, &stubCode)) {
     return nullptr;
   }
 
   if (!linkCompiledCode(stubCode)) {
     return nullptr;
   }
 
-  // All functions and stubs have been compiled.  Perform module-end
-  // validation.
-
-  if (!deferredValidationState_.lock()->performDeferredValidation(*env_,
-                                                                  error_)) {
-    return nullptr;
-  }
-
   // Finish linking and metadata.
 
   if (!finishCodegen()) {
     return nullptr;
   }
 
   if (!finishMetadataTier()) {
     return nullptr;
--- a/js/src/wasm/WasmGenerator.h
+++ b/js/src/wasm/WasmGenerator.h
@@ -112,24 +112,23 @@ struct CompileTaskState {
 typedef ExclusiveWaitableData<CompileTaskState> ExclusiveCompileTaskState;
 
 // A CompileTask holds a batch of input functions that are to be compiled on a
 // helper thread as well as, eventually, the results of compilation.
 
 struct CompileTask {
   const ModuleEnvironment& env;
   ExclusiveCompileTaskState& state;
-  ExclusiveDeferredValidationState& dvs;
   LifoAlloc lifo;
   FuncCompileInputVector inputs;
   CompiledCode output;
 
   CompileTask(const ModuleEnvironment& env, ExclusiveCompileTaskState& state,
-              ExclusiveDeferredValidationState& dvs, size_t defaultChunkSize)
-      : env(env), state(state), dvs(dvs), lifo(defaultChunkSize) {}
+              size_t defaultChunkSize)
+      : env(env), state(state), lifo(defaultChunkSize) {}
 
   size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 };
 
 // A ModuleGenerator encapsulates the creation of a wasm module. During the
 // lifetime of a ModuleGenerator, a sequence of FunctionGenerators are created
 // and destroyed to compile the individual function bodies. After generating all
 // functions, ModuleGenerator::finish() must be called to complete the
@@ -159,19 +158,16 @@ class MOZ_STACK_CLASS ModuleGenerator {
   Uint32Vector funcToCodeRange_;
   uint32_t debugTrapCodeOffset_;
   CallFarJumpVector callFarJumps_;
   CallSiteTargetVector callSiteTargets_;
   uint32_t lastPatchedCallSite_;
   uint32_t startOfUnpatchedCallsites_;
   CodeOffsetVector debugTrapFarJumps_;
 
-  // Data accumulated for deferred validation.  Is shared and mutable.
-  ExclusiveDeferredValidationState deferredValidationState_;
-
   // Parallel compilation
   bool parallel_;
   uint32_t outstanding_;
   CompileTaskVector tasks_;
   CompileTaskPtrVector freeTasks_;
   CompileTask* currentTask_;
   uint32_t batchedBytecode_;
 
@@ -222,18 +218,14 @@ class MOZ_STACK_CLASS ModuleGenerator {
   // a new Module. Otherwise, if env->mode is Tier2, finishTier2() must be
   // called to augment the given Module with tier 2 code.
 
   SharedModule finishModule(
       const ShareableBytes& bytecode,
       JS::OptimizedEncodingListener* maybeTier2Listener = nullptr,
       UniqueLinkData* maybeLinkData = nullptr);
   MOZ_MUST_USE bool finishTier2(const Module& module);
-
-  ExclusiveDeferredValidationState& deferredValidationState() {
-    return deferredValidationState_;
-  }
 };
 
 }  // namespace wasm
 }  // namespace js
 
 #endif  // wasm_generator_h
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -138,21 +138,20 @@ class FunctionCompiler {
   uint32_t blockDepth_;
   ControlFlowPatchsVector blockPatches_;
 
   // TLS pointer argument to the current function.
   MWasmParameter* tlsPointer_;
 
  public:
   FunctionCompiler(const ModuleEnvironment& env, Decoder& decoder,
-                   ExclusiveDeferredValidationState& dvs,
                    const FuncCompileInput& func, const ValTypeVector& locals,
                    MIRGenerator& mirGen)
       : env_(env),
-        iter_(env, decoder, dvs),
+        iter_(env, decoder),
         func_(func),
         locals_(locals),
         lastReadCallSite_(0),
         alloc_(mirGen.alloc()),
         graph_(mirGen.graph()),
         info_(mirGen.info()),
         mirGen_(mirGen),
         curBlock_(nullptr),
@@ -4008,17 +4007,16 @@ static bool EmitBodyExprs(FunctionCompil
   MOZ_CRASH("unreachable");
 
 #undef CHECK
 }
 
 bool wasm::IonCompileFunctions(const ModuleEnvironment& env, LifoAlloc& lifo,
                                const FuncCompileInputVector& inputs,
                                CompiledCode* code,
-                               ExclusiveDeferredValidationState& dvs,
                                UniqueChars* error) {
   MOZ_ASSERT(env.tier() == Tier::Optimized);
   MOZ_ASSERT(env.optimizedBackend() == OptimizedBackend::Ion);
 
   TempAllocator alloc(&lifo);
   JitContext jitContext(&alloc);
   MOZ_ASSERT(IsCompilingWasm());
   WasmMacroAssembler masm(alloc);
@@ -4049,17 +4047,17 @@ bool wasm::IonCompileFunctions(const Mod
     MIRGraph graph(&alloc);
     CompileInfo compileInfo(locals.length());
     MIRGenerator mir(nullptr, options, &alloc, &graph, &compileInfo,
                      IonOptimizations.get(OptimizationLevel::Wasm));
     mir.initMinWasmHeapLength(env.minMemoryLength);
 
     // Build MIR graph
     {
-      FunctionCompiler f(env, d, dvs, func, locals, mir);
+      FunctionCompiler f(env, d, func, locals, mir);
       if (!f.init()) {
         return false;
       }
 
       if (!f.startBlock()) {
         return false;
       }
 
--- a/js/src/wasm/WasmIonCompile.h
+++ b/js/src/wasm/WasmIonCompile.h
@@ -29,15 +29,14 @@ namespace wasm {
 // Return whether IonCompileFunction() can generate code on the current device.
 bool IonCanCompile();
 
 // Generates very fast code at the expense of compilation time.
 MOZ_MUST_USE bool IonCompileFunctions(const ModuleEnvironment& env,
                                       LifoAlloc& lifo,
                                       const FuncCompileInputVector& inputs,
                                       CompiledCode* code,
-                                      ExclusiveDeferredValidationState& dvs,
                                       UniqueChars* error);
 
 }  // namespace wasm
 }  // namespace js
 
 #endif  // wasm_ion_compile_h
--- a/js/src/wasm/WasmOpIter.h
+++ b/js/src/wasm/WasmOpIter.h
@@ -308,17 +308,16 @@ class TypeAndValue<Nothing> {
 // it to be used on the stack.
 template <typename Policy>
 class MOZ_STACK_CLASS OpIter : private Policy {
   typedef typename Policy::Value Value;
   typedef typename Policy::ControlItem ControlItem;
 
   Decoder& d_;
   const ModuleEnvironment& env_;
-  ExclusiveDeferredValidationState& dvs_;
 
   Vector<TypeAndValue<Value>, 8, SystemAllocPolicy> valueStack_;
   Vector<ControlStackEntry<ControlItem>, 8, SystemAllocPolicy> controlStack_;
 
 #ifdef DEBUG
   OpBytes op_;
 #endif
   size_t offsetOfLastReadOp_;
@@ -386,27 +385,24 @@ class MOZ_STACK_CLASS OpIter : private P
   }
 
   inline bool Join(StackType one, StackType two, StackType* result);
 
  public:
   typedef Vector<Value, 8, SystemAllocPolicy> ValueVector;
 
 #ifdef DEBUG
-  explicit OpIter(const ModuleEnvironment& env, Decoder& decoder,
-                  ExclusiveDeferredValidationState& dvs)
+  explicit OpIter(const ModuleEnvironment& env, Decoder& decoder)
       : d_(decoder),
         env_(env),
-        dvs_(dvs),
         op_(OpBytes(Op::Limit)),
         offsetOfLastReadOp_(0) {}
 #else
-  explicit OpIter(const ModuleEnvironment& env, Decoder& decoder,
-                  ExclusiveDeferredValidationState& dvs)
-      : d_(decoder), env_(env), dvs_(dvs), offsetOfLastReadOp_(0) {}
+  explicit OpIter(const ModuleEnvironment& env, Decoder& decoder)
+      : d_(decoder), env_(env), offsetOfLastReadOp_(0) {}
 #endif
 
   // Return the decoding byte offset.
   uint32_t currentOffset() const { return d_.currentOffset(); }
 
   // Return the offset within the entire module of the last-read op.
   size_t lastOpcodeOffset() const {
     return offsetOfLastReadOp_ ? offsetOfLastReadOp_ : d_.currentOffset();
@@ -1930,20 +1926,22 @@ inline bool OpIter<Policy>::readDataOrEl
     }
   }
 
   if (!readVarU32(segIndex)) {
     return false;
   }
 
   if (isData) {
-    // We can't range-check *segIndex at this point since we don't yet
-    // know how many data segments the module has.  So note the index, but
-    // defer the actual check for now.
-    dvs_.lock()->notifyDataSegmentIndex(*segIndex, d_.currentOffset());
+    if (env_.dataCount.isNothing()) {
+      return fail("data.drop requires a DataCount section");
+    }
+    if (*segIndex >= *env_.dataCount) {
+      return fail("data.drop segment index out of range");
+    }
   } else {
     if (*segIndex >= env_.elemSegments.length()) {
       return fail("element segment index out of range for elem.drop");
     }
   }
 
   return true;
 }
@@ -2012,19 +2010,22 @@ inline bool OpIter<Policy>::readMemOrTab
   }
   if (isMem) {
     if (!env_.usesMemory()) {
       return fail("can't touch memory without memory");
     }
     if (memOrTableIndex != 0) {
       return fail("memory index must be zero");
     }
-
-    // Same comment as for readDataOrElemDrop re range checking.
-    dvs_.lock()->notifyDataSegmentIndex(*segIndex, d_.currentOffset());
+    if (env_.dataCount.isNothing()) {
+      return fail("memory.init requires a DataCount section");
+    }
+    if (*segIndex >= *env_.dataCount) {
+      return fail("memory.init segment index out of range");
+    }
   } else {
     if (memOrTableIndex >= env_.tables.length()) {
       return fail("table index out of range for table.init");
     }
     *dstTableIndex = memOrTableIndex;
 
     // Element segments must carry functions exclusively and anyfunc is not
     // yet a subtype of anyref.
--- a/js/src/wasm/WasmTextToBinary.cpp
+++ b/js/src/wasm/WasmTextToBinary.cpp
@@ -71,16 +71,19 @@ class WasmToken {
     Call,
     CallIndirect,
     CloseParen,
     ComparisonOpcode,
     Const,
     ConversionOpcode,
     CurrentMemory,
     Data,
+#ifdef ENABLE_WASM_BULKMEM_OPS
+    DataCount,
+#endif
     Drop,
     Elem,
     Else,
     End,
     EndOfFile,
     Equal,
     Error,
     Export,
@@ -350,16 +353,19 @@ class WasmToken {
       case Unreachable:
       case Wait:
       case Wake:
         return true;
       case Align:
       case AnyFunc:
       case CloseParen:
       case Data:
+#ifdef ENABLE_WASM_BULKMEM_OPS
+      case DataCount:
+#endif
       case Elem:
       case Else:
       case EndOfFile:
       case Equal:
       case End:
       case Error:
       case Export:
       case Field:
@@ -979,16 +985,19 @@ WasmToken WasmTokenStream::next() {
       }
       if (consume(u"current_memory")) {
         return WasmToken(WasmToken::CurrentMemory, begin, cur_);
       }
       break;
 
     case 'd':
       if (consume(u"data")) {
+        if (consume(u"count")) {
+          return WasmToken(WasmToken::DataCount, begin, cur_);
+        }
         if (consume(u".drop")) {
           return WasmToken(WasmToken::DataDrop, begin, cur_);
         }
         return WasmToken(WasmToken::Data, begin, cur_);
       }
       if (consume(u"drop")) {
         return WasmToken(WasmToken::Drop, begin, cur_);
       }
@@ -2273,25 +2282,27 @@ namespace {
 
 struct WasmParseContext {
   WasmTokenStream ts;
   LifoAlloc& lifo;
   UniqueChars* error;
   DtoaState* dtoaState;
   uintptr_t stackLimit;
   uint32_t nextSym;
+  bool requiresDataCount;
 
   WasmParseContext(const char16_t* text, uintptr_t stackLimit, LifoAlloc& lifo,
                    UniqueChars* error)
       : ts(text),
         lifo(lifo),
         error(error),
         dtoaState(NewDtoaState()),
         stackLimit(stackLimit),
-        nextSym(0) {}
+        nextSym(0),
+        requiresDataCount(false) {}
 
   ~WasmParseContext() { DestroyDtoaState(dtoaState); }
 
   AstName gensym(const char* tag) {
     char buf[128];
     MOZ_ASSERT(strlen(tag) < sizeof(buf) - 20);
     SprintfLiteral(buf, ".%s.%u", tag, nextSym);
     nextSym++;
@@ -3663,16 +3674,20 @@ static AstMemOrTableCopy* ParseMemOrTabl
 }
 
 static AstDataOrElemDrop* ParseDataOrElemDrop(WasmParseContext& c, bool isData) {
   WasmToken segIndexTok;
   if (!c.ts.getIf(WasmToken::Index, &segIndexTok)) {
     return nullptr;
   }
 
+  if (isData) {
+    c.requiresDataCount = true;
+  }
+
   return new (c.lifo) AstDataOrElemDrop(isData, segIndexTok.index());
 }
 
 static AstMemFill* ParseMemFill(WasmParseContext& c, bool inParens) {
   AstExpr* start = ParseExpr(c, inParens);
   if (!start) {
     return nullptr;
   }
@@ -3700,16 +3715,17 @@ static AstMemOrTableInit* ParseMemOrTabl
   uint32_t segIndex = 0;
 
   WasmToken segIndexTok;
   if (isMem) {
     if (!c.ts.getIf(WasmToken::Index, &segIndexTok)) {
       return nullptr;
     }
     segIndex = segIndexTok.index();
+    c.requiresDataCount = true;
   } else {
     // Slightly hairy to parse this for tables because the element index "0"
     // could just as well be the table index "0".
     c.ts.getIfRef(&targetMemOrTable);
     if (c.ts.getIf(WasmToken::Index, &segIndexTok)) {
       segIndex = segIndexTok.index();
     } else if (targetMemOrTable.isIndex()) {
       segIndex = targetMemOrTable.index();
@@ -4409,16 +4425,28 @@ static AstDataSegment* ParseDataSegment(
     if (!fragments.append(text.text())) {
       return nullptr;
     }
   }
 
   return new (c.lifo) AstDataSegment(offsetIfActive, std::move(fragments));
 }
 
+#ifdef ENABLE_WASM_BULKMEM_OPS
+static bool ParseDataCount(WasmParseContext& c, AstModule* module) {
+  WasmToken token;
+  if (!c.ts.getIf(WasmToken::Index, &token)) {
+    c.ts.generateError(token, "Literal data segment count required", c.error);
+    return false;
+  }
+
+  return module->initDataCount(token.index());
+}
+#endif
+
 static bool ParseLimits(WasmParseContext& c, Limits* limits,
                         Shareable allowShared) {
   WasmToken initial;
   if (!c.ts.match(WasmToken::Index, &initial, c.error)) {
     return false;
   }
 
   Maybe<uint32_t> maximum;
@@ -5095,16 +5123,24 @@ static AstModule* ParseModule(const char
       }
       case WasmToken::Data: {
         AstDataSegment* segment = ParseDataSegment(c);
         if (!segment || !module->append(segment)) {
           return nullptr;
         }
         break;
       }
+#ifdef ENABLE_WASM_BULKMEM_OPS
+      case WasmToken::DataCount: {
+        if (!ParseDataCount(c, module)) {
+          return nullptr;
+        }
+        break;
+      }
+#endif
       case WasmToken::Import: {
         AstImport* imp = ParseImport(c, module);
         if (!imp || !module->append(imp)) {
           return nullptr;
         }
         break;
       }
       case WasmToken::Export: {
@@ -5145,16 +5181,20 @@ static AstModule* ParseModule(const char
 
   if (!c.ts.match(WasmToken::CloseParen, c.error)) {
     return nullptr;
   }
   if (!c.ts.match(WasmToken::EndOfFile, c.error)) {
     return nullptr;
   }
 
+  if (c.requiresDataCount && module->dataCount().isNothing()) {
+    module->initDataCount(module->dataSegments().length());
+  }
+
   return module;
 }
 
 /*****************************************************************************/
 // wasm name resolution
 
 namespace {
 
@@ -7101,16 +7141,36 @@ static bool EncodeDataSection(Encoder& e
       return false;
     }
   }
 
   e.finishSection(offset);
   return true;
 }
 
+#ifdef ENABLE_WASM_BULKMEM_OPS
+static bool EncodeDataCountSection(Encoder& e, AstModule& module) {
+  if (module.dataCount().isNothing()) {
+    return true;
+  }
+
+  size_t offset;
+  if (!e.startSection(SectionId::DataCount, &offset)) {
+    return false;
+  }
+
+  if (!e.writeVarU32(*module.dataCount())) {
+    return false;
+  }
+
+  e.finishSection(offset);
+  return true;
+}
+#endif
+
 static bool EncodeElemSegment(Encoder& e, AstElemSegment& segment) {
   if (!EncodeDestinationOffsetOrFlags(e, segment.targetTable().index(),
                                       segment.offsetIfActive())) {
     return false;
   }
 
   if (!e.writeVarU32(segment.elems().length())) {
     return false;
@@ -7198,16 +7258,22 @@ static bool EncodeModule(AstModule& modu
   if (!EncodeStartSection(e, module)) {
     return false;
   }
 
   if (!EncodeElemSection(e, module)) {
     return false;
   }
 
+#ifdef ENABLE_WASM_BULKMEM_OPS
+  if (!EncodeDataCountSection(e, module)) {
+    return false;
+  }
+#endif
+
   if (!EncodeCodeSection(e, offsets, module)) {
     return false;
   }
 
   if (!EncodeDataSection(e, module)) {
     return false;
   }
 
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -503,19 +503,18 @@ struct ValidatingPolicy {
   typedef Nothing ControlItem;
 };
 
 typedef OpIter<ValidatingPolicy> ValidatingOpIter;
 
 static bool DecodeFunctionBodyExprs(const ModuleEnvironment& env,
                                     const FuncType& funcType,
                                     const ValTypeVector& locals,
-                                    ExclusiveDeferredValidationState& dvs,
                                     const uint8_t* bodyEnd, Decoder* d) {
-  ValidatingOpIter iter(env, *d, dvs);
+  ValidatingOpIter iter(env, *d);
 
   if (!iter.readFunctionStart(funcType.ret())) {
     return false;
   }
 
 #define CHECK(c)          \
   if (!(c)) return false; \
   break
@@ -1179,33 +1178,32 @@ static bool DecodeFunctionBodyExprs(cons
 
   MOZ_CRASH("unreachable");
 
 #undef CHECK
 }
 
 bool wasm::ValidateFunctionBody(const ModuleEnvironment& env,
                                 uint32_t funcIndex, uint32_t bodySize,
-                                Decoder& d,
-                                ExclusiveDeferredValidationState& dvs) {
+                                Decoder& d) {
   const FuncType& funcType = *env.funcTypes[funcIndex];
 
   ValTypeVector locals;
   if (!locals.appendAll(funcType.args())) {
     return false;
   }
 
   const uint8_t* bodyBegin = d.currentPosition();
 
   if (!DecodeLocalEntries(d, ModuleKind::Wasm, env.types, env.gcTypesEnabled(),
                           &locals)) {
     return false;
   }
 
-  if (!DecodeFunctionBodyExprs(env, funcType, locals, dvs, bodyBegin + bodySize,
+  if (!DecodeFunctionBodyExprs(env, funcType, locals, bodyBegin + bodySize,
                                &d)) {
     return false;
   }
 
   return true;
 }
 
 // Section macros.
@@ -1413,17 +1411,17 @@ static bool DecodeStructType(Decoder& d,
   env->numStructTypes++;
 
   return true;
 }
 
 #ifdef ENABLE_WASM_REFTYPES
 static bool DecodeGCFeatureOptInSection(Decoder& d, ModuleEnvironment* env) {
   MaybeSectionRange range;
-  if (!d.startSection(SectionId::GcFeatureOptIn, env, &range, "type")) {
+  if (!d.startSection(SectionId::GcFeatureOptIn, env, &range, "gcfeatureoptin")) {
     return false;
   }
   if (!range) {
     return true;
   }
 
   uint32_t version;
   if (!d.readVarU32(&version)) {
@@ -2377,16 +2375,37 @@ static bool DecodeElemSection(Decoder& d
     }
 
     env->elemSegments.infallibleAppend(std::move(seg));
   }
 
   return d.finishSection(*range, "elem");
 }
 
+#ifdef ENABLE_WASM_BULKMEM_OPS
+static bool DecodeDataCountSection(Decoder& d, ModuleEnvironment* env) {
+  MaybeSectionRange range;
+  if (!d.startSection(SectionId::DataCount, env, &range, "datacount")) {
+    return false;
+  }
+  if (!range) {
+    return true;
+  }
+
+  uint32_t dataCount;
+  if (!d.readVarU32(&dataCount)) {
+    return d.fail("expected data segment count");
+  }
+
+  env->dataCount.emplace(dataCount);
+
+  return d.finishSection(*range, "datacount");
+}
+#endif
+
 bool wasm::StartsCodeSection(const uint8_t* begin, const uint8_t* end,
                              SectionRange* codeSection) {
   UniqueChars unused;
   Decoder d(begin, end, 0, &unused);
 
   if (!DecodePreamble(d)) {
     return false;
   }
@@ -2458,52 +2477,56 @@ bool wasm::DecodeModuleEnvironment(Decod
   if (!DecodeStartSection(d, env)) {
     return false;
   }
 
   if (!DecodeElemSection(d, env)) {
     return false;
   }
 
+#ifdef ENABLE_WASM_BULKMEM_OPS
+  if (!DecodeDataCountSection(d, env)) {
+    return false;
+  }
+#endif
+
   if (!d.startSection(SectionId::Code, env, &env->codeSection, "code")) {
     return false;
   }
 
   if (env->codeSection && env->codeSection->size > MaxCodeSectionBytes) {
     return d.fail("code section too big");
   }
 
   return true;
 }
 
 static bool DecodeFunctionBody(Decoder& d, const ModuleEnvironment& env,
-                               ExclusiveDeferredValidationState& dvs,
                                uint32_t funcIndex) {
   uint32_t bodySize;
   if (!d.readVarU32(&bodySize)) {
     return d.fail("expected number of function body bytes");
   }
 
   if (bodySize > MaxFunctionBytes) {
     return d.fail("function body too big");
   }
 
   if (d.bytesRemain() < bodySize) {
     return d.fail("function body length too big");
   }
 
-  if (!ValidateFunctionBody(env, funcIndex, bodySize, d, dvs)) {
+  if (!ValidateFunctionBody(env, funcIndex, bodySize, d)) {
     return false;
   }
 
   return true;
 }
 
-static bool DecodeCodeSection(Decoder& d, ModuleEnvironment* env,
-                              ExclusiveDeferredValidationState& dvs) {
+static bool DecodeCodeSection(Decoder& d, ModuleEnvironment* env) {
   if (!env->codeSection) {
     if (env->numFuncDefs() != 0) {
       return d.fail("expected code section");
     }
     return true;
   }
 
   uint32_t numFuncDefs;
@@ -2512,43 +2535,49 @@ static bool DecodeCodeSection(Decoder& d
   }
 
   if (numFuncDefs != env->numFuncDefs()) {
     return d.fail(
         "function body count does not match function signature count");
   }
 
   for (uint32_t funcDefIndex = 0; funcDefIndex < numFuncDefs; funcDefIndex++) {
-    if (!DecodeFunctionBody(d, *env, dvs,
-                            env->numFuncImports() + funcDefIndex)) {
+    if (!DecodeFunctionBody(d, *env, env->numFuncImports() + funcDefIndex)) {
       return false;
     }
   }
 
   return d.finishSection(*env->codeSection, "code");
 }
 
 static bool DecodeDataSection(Decoder& d, ModuleEnvironment* env) {
   MaybeSectionRange range;
   if (!d.startSection(SectionId::Data, env, &range, "data")) {
     return false;
   }
   if (!range) {
+    if (env->dataCount.isSome() && *env->dataCount > 0) {
+      return d.fail("number of data segments does not match declared count");
+    }
     return true;
   }
 
   uint32_t numSegments;
   if (!d.readVarU32(&numSegments)) {
     return d.fail("failed to read number of data segments");
   }
 
   if (numSegments > MaxDataSegments) {
     return d.fail("too many data segments");
   }
 
+  if (env->dataCount.isSome() && numSegments != *env->dataCount) {
+    return d.fail("number of data segments does not match declared count");
+  }
+
   for (uint32_t i = 0; i < numSegments; i++) {
     uint32_t initializerKindVal;
     if (!d.readVarU32(&initializerKindVal)) {
       return d.fail("expected data initializer-kind field");
     }
 
     switch (initializerKindVal) {
       case uint32_t(InitializerKind::Active):
@@ -2728,46 +2757,17 @@ static bool DecodeNameSection(Decoder& d
     }
   }
 
 finish:
   d.finishCustomSection(NameSectionName, *range);
   return true;
 }
 
-void DeferredValidationState::notifyDataSegmentIndex(uint32_t segIndex,
-                                                     size_t offsetInModule) {
-  // If |segIndex| is larger than any previously observed use, or this is
-  // the first index use to be notified, make a note of it and the module
-  // offset it appeared at.  That way, if we have to report it later as an
-  // error, we can at least report a correct offset.
-  if (!haveHighestDataSegIndex || segIndex > highestDataSegIndex) {
-    highestDataSegIndex = segIndex;
-    highestDataSegIndexOffset = offsetInModule;
-  }
-
-  haveHighestDataSegIndex = true;
-}
-
-bool DeferredValidationState::performDeferredValidation(
-    const ModuleEnvironment& env, UniqueChars* error) {
-  if (haveHighestDataSegIndex &&
-      highestDataSegIndex >= env.dataSegments.length()) {
-    UniqueChars str(
-        JS_smprintf("at offset %zu: memory.{drop,init} index out of range",
-                    highestDataSegIndexOffset));
-    *error = std::move(str);
-    return false;
-  }
-
-  return true;
-}
-
-bool wasm::DecodeModuleTail(Decoder& d, ModuleEnvironment* env,
-                            ExclusiveDeferredValidationState& dvs) {
+bool wasm::DecodeModuleTail(Decoder& d, ModuleEnvironment* env) {
   if (!DecodeDataSection(d, env)) {
     return false;
   }
 
   if (!DecodeNameSection(d, env)) {
     return false;
   }
 
@@ -2776,17 +2776,17 @@ bool wasm::DecodeModuleTail(Decoder& d, 
       if (d.resilientMode()) {
         d.clearError();
         return true;
       }
       return false;
     }
   }
 
-  return dvs.lock()->performDeferredValidation(*env, d.error());
+  return true;
 }
 
 // Validate algorithm.
 
 bool wasm::Validate(JSContext* cx, const ShareableBytes& bytecode,
                     UniqueChars* error) {
   Decoder d(bytecode.bytes, 0, error);
 
@@ -2803,21 +2803,19 @@ bool wasm::Validate(JSContext* cx, const
       gcTypesConfigured, &compilerEnv,
       cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()
           ? Shareable::True
           : Shareable::False);
   if (!DecodeModuleEnvironment(d, &env)) {
     return false;
   }
 
-  ExclusiveDeferredValidationState dvs(mutexid::WasmDeferredValidation);
-
-  if (!DecodeCodeSection(d, &env, dvs)) {
+  if (!DecodeCodeSection(d, &env)) {
     return false;
   }
 
-  if (!DecodeModuleTail(d, &env, dvs)) {
+  if (!DecodeModuleTail(d, &env)) {
     return false;
   }
 
   MOZ_ASSERT(!*error, "unreported error in decoding");
   return true;
 }
--- a/js/src/wasm/WasmValidate.h
+++ b/js/src/wasm/WasmValidate.h
@@ -111,20 +111,18 @@ struct CompilerEnvironment {
   }
   bool gcTypes() const {
     MOZ_ASSERT(isComputed());
     return gcTypes_;
   }
 };
 
 // ModuleEnvironment contains all the state necessary to process or render
-// functions, and all of the state necessary to validate aspects of the
-// functions that do not require looking forwards in the bytecode stream.
-// The remaining validation state is accumulated in DeferredValidationState
-// and is checked at the end of a module's bytecode.
+// functions, and all of the state necessary to validate all aspects of the
+// functions.
 //
 // A ModuleEnvironment is created by decoding all the sections before the wasm
 // code section and then used immutably during. When compiling a module using a
 // ModuleGenerator, the ModuleEnvironment holds state shared between the
 // ModuleGenerator thread and background compile threads. All the threads
 // are given a read-only view of the ModuleEnvironment, thus preventing race
 // conditions.
 
@@ -141,16 +139,17 @@ struct ModuleEnvironment {
   // section.  This variable will be removed eventually, allowing it to be
   // replaced everywhere by the value true.
   //
   // The flag is used in the value of gcTypesEnabled(), which controls whether
   // ref types and struct types and associated instructions are accepted
   // during validation.
   bool gcFeatureOptIn;
 #endif
+  Maybe<uint32_t> dataCount;
   MemoryUsage memoryUsage;
   uint32_t minMemoryLength;
   Maybe<uint32_t> maxMemoryLength;
   uint32_t numStructTypes;
   TypeDefVector types;
   FuncTypeWithIdPtrVector funcTypes;
   Uint32Vector funcImportGlobalDataOffsets;
   GlobalDescVector globals;
@@ -402,53 +401,16 @@ class Encoder {
     return writeVarU32(uint32_t(id)) && writePatchableVarU32(offset);
   }
   void finishSection(size_t offset) {
     return patchVarU32(offset,
                        bytes_.length() - offset - varU32ByteLength(offset));
   }
 };
 
-// DeferredValidationState holds mutable state shared between threads that
-// compile a module.  The state accumulates information needed to complete
-// validation at the end of compilation of a module.
-
-struct DeferredValidationState {
-  // These three fields keep track of the highest data segment index
-  // mentioned in the code section, if any, and the associated section
-  // offset, so as to facilitate error message creation.  The use of
-  // |haveHighestDataSegIndex| avoids the difficulty of having to
-  // special-case one of the |highestDataSegIndex| values to mean "we
-  // haven't seen any data segments (yet)."
-
-  bool haveHighestDataSegIndex;
-  uint32_t highestDataSegIndex;
-  size_t highestDataSegIndexOffset;
-
-  DeferredValidationState() { init(); }
-
-  void init() {
-    haveHighestDataSegIndex = false;
-    highestDataSegIndex = 0;
-    highestDataSegIndexOffset = 0;
-  }
-
-  // Call here to notify the use of the data segment index with value
-  // |segIndex| at module offset |offsetInModule| whilst iterating through
-  // the code segment.
-  void notifyDataSegmentIndex(uint32_t segIndex, size_t offsetInModule);
-
-  // Call here to perform all final validation actions once the module tail
-  // has been processed.  Returns |true| if there are no errors.
-  bool performDeferredValidation(const ModuleEnvironment& env,
-                                 UniqueChars* error);
-};
-
-typedef ExclusiveData<DeferredValidationState> ExclusiveDeferredValidationState;
-
 // The Decoder class decodes the bytes in the range it is given during
 // construction. The client is responsible for keeping the byte range alive as
 // long as the Decoder is used.
 
 class Decoder {
   const uint8_t* const beg_;
   const uint8_t* const end_;
   const uint8_t* cur_;
@@ -801,21 +763,19 @@ MOZ_MUST_USE bool StartsCodeSection(cons
 // decode the code section itself, reusing ValidateFunctionBody if necessary,
 // and finally call DecodeModuleTail to decode all remaining sections after the
 // code section (again, performing full validation).
 
 MOZ_MUST_USE bool DecodeModuleEnvironment(Decoder& d, ModuleEnvironment* env);
 
 MOZ_MUST_USE bool ValidateFunctionBody(const ModuleEnvironment& env,
                                        uint32_t funcIndex, uint32_t bodySize,
-                                       Decoder& d,
-                                       ExclusiveDeferredValidationState& dvs);
+                                       Decoder& d);
 
-MOZ_MUST_USE bool DecodeModuleTail(Decoder& d, ModuleEnvironment* env,
-                                   ExclusiveDeferredValidationState& dvs);
+MOZ_MUST_USE bool DecodeModuleTail(Decoder& d, ModuleEnvironment* env);
 
 void ConvertMemoryPagesToBytes(Limits* memory);
 
 // Validate an entire module, returning true if the module was validated
 // successfully. If Validate returns false:
 //  - if *error is null, the caller should report out-of-memory
 //  - otherwise, there was a legitimate error described by *error