Bug 1546074 - Remove support for 'passive' syntax for segments. r=rhunt
☠☠ backed out by 940cc46b1ff6 ☠ ☠
authorLars T Hansen <lhansen@mozilla.com>
Fri, 04 Oct 2019 13:51:59 +0000
changeset 496335 341a1c31fb152764937482abb2b0d81c63b3730c
parent 496334 047f2de6aea12c0a9be8fdec4e77a3e82d07b38e
child 496336 315df8dc3c419cfeb4d34b511e681ca975cfeaa8
push id97176
push userlhansen@mozilla.com
push dateFri, 04 Oct 2019 13:58:58 +0000
treeherderautoland@341a1c31fb15 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrhunt
bugs1546074
milestone71.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 1546074 - Remove support for 'passive' syntax for segments. r=rhunt This removes support for 'passive' and makes our element and data segment syntax follow standard syntax much more closely. Element segments now require either 'func' or 'funcref' in the right position, and require a table index for active segments that don't use the designated MVP shorthand. Data segments require an offset when there's a memory index present. Also add support for the noise syntax (offset x) for the initialization offset in active segments. I did not add support for the variant of in-line elements in the table definition that allows the starting offset to be specified; this is followup work. Also tidy up some naming and comments in an attempt to clarify the flow in the encoder. Differential Revision: https://phabricator.services.mozilla.com/D48031
js/src/jit-test/tests/wasm/declared-segs.js
js/src/jit-test/tests/wasm/gc/ref-func.js
js/src/jit-test/tests/wasm/gc/tables-generalized.js
js/src/jit-test/tests/wasm/gc/tables-multiple.js
js/src/jit-test/tests/wasm/memory.js
js/src/jit-test/tests/wasm/passive-segs-boundary.js
js/src/jit-test/tests/wasm/passive-segs-nonboundary.js
js/src/jit-test/tests/wasm/passive-segs-partial-mem.js
js/src/jit-test/tests/wasm/passive-segs-partial-table.js
js/src/jit-test/tests/wasm/regress/startfunc-in-table.js
js/src/jit-test/tests/wasm/tables.js
js/src/wasm/WasmConstants.h
js/src/wasm/WasmTextToBinary.cpp
js/src/wasm/WasmValidate.cpp
--- a/js/src/jit-test/tests/wasm/declared-segs.js
+++ b/js/src/jit-test/tests/wasm/declared-segs.js
@@ -9,17 +9,17 @@ wasmFullPass(`
 		(export "run" $run)
 	)
 `);
 
 // Declared segments cannot use ref.null
 assertThrowsInstanceOf(() => {
 	wasmTextToBinary(`
 		(module
-			(elem declared ref.null)
+			(elem declared (ref.null))
 		)
 	`)
 }, SyntaxError);
 
 // Declared segments cannot be used by bulk-memory operations
 function test(ins) {
 	assertErrorMessage(
 		() => wasmEvalText(`
--- a/js/src/jit-test/tests/wasm/gc/ref-func.js
+++ b/js/src/jit-test/tests/wasm/gc/ref-func.js
@@ -81,15 +81,15 @@ function validFuncRefText(forwardDeclare
 		)
 	`);
 }
 
 // referenced function must be forward declared somehow
 assertErrorMessage(() => validFuncRefText(''), WebAssembly.CompileError, /function index is not in an element segment/);
 
 // referenced function can be forward declared via segments
-assertEq(validFuncRefText('(elem 0 (i32.const 0) $referenced)') instanceof WebAssembly.Instance, true);
-assertEq(validFuncRefText('(elem passive $referenced)') instanceof WebAssembly.Instance, true);
+assertEq(validFuncRefText('(elem 0 (i32.const 0) func $referenced)') instanceof WebAssembly.Instance, true);
+assertEq(validFuncRefText('(elem func $referenced)') instanceof WebAssembly.Instance, true);
 assertEq(validFuncRefText('(elem declared $referenced)') instanceof WebAssembly.Instance, true);
 
 // referenced function cannot be forward declared via start section or export
 assertErrorMessage(() => validFuncRefText('(start $referenced)'), WebAssembly.CompileError, /function index is not in an element segment/);
 assertErrorMessage(() => validFuncRefText('(export "referenced" $referenced)'), WebAssembly.CompileError, /function index is not in an element segment/);
--- a/js/src/jit-test/tests/wasm/gc/tables-generalized.js
+++ b/js/src/jit-test/tests/wasm/gc/tables-generalized.js
@@ -78,27 +78,27 @@ new WebAssembly.Instance(new WebAssembly
 }
 
 // Wasm: element segments targeting table-of-anyref is forbidden
 
 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
     `(module
        (func $f1 (result i32) (i32.const 0))
        (table 10 anyref)
-       (elem (i32.const 0) $f1))`)),
+       (elem 0 (i32.const 0) func $f1))`)),
                    WebAssembly.CompileError,
                    /only tables of 'funcref' may have element segments/);
 
 // Wasm: table.init on table-of-anyref is forbidden
 
 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
     `(module
        (func $f1 (result i32) (i32.const 0))
        (table 10 anyref)
-       (elem passive $f1)
+       (elem func $f1)
        (func
          (table.init 0 (i32.const 0) (i32.const 0) (i32.const 0))))`)),
                    WebAssembly.CompileError,
                    /only tables of 'funcref' may have element segments/);
 
 // Wasm: table types must match at link time
 
 assertErrorMessage(
--- a/js/src/jit-test/tests/wasm/gc/tables-multiple.js
+++ b/js/src/jit-test/tests/wasm/gc/tables-multiple.js
@@ -11,18 +11,18 @@
 // - element segments can point to a table
 // - call-indirect can specify a table and will use it
 
 var ins = wasmEvalText(
     `(module
       (table $t1 2 funcref)
       (table $t2 2 funcref)
       (type $ftype (func (param i32) (result i32)))
-      (elem $t1 (i32.const 0) $f1 $f2)
-      (elem $t2 (i32.const 0) $f3 $f4)
+      (elem $t1 (i32.const 0) func $f1 $f2)
+      (elem $t2 (i32.const 0) func $f3 $f4)
       (func $f1 (param $n i32) (result i32)
        (i32.add (local.get $n) (i32.const 1)))
       (func $f2 (param $n i32) (result i32)
        (i32.add (local.get $n) (i32.const 2)))
       (func $f3 (param $n i32) (result i32)
        (i32.add (local.get $n) (i32.const 3)))
       (func $f4 (param $n i32) (result i32)
        (i32.add (local.get $n) (i32.const 4)))
@@ -60,24 +60,24 @@ var exp = {m:{t0: new WebAssembly.Table(
               t1: new WebAssembly.Table({element:"anyref", initial:3}),
               t2: new WebAssembly.Table({element:"funcref", initial:4}),
               t3: new WebAssembly.Table({element:"anyref", initial:5})}};
 var ins = wasmEvalText(
     `(module
       (table $t0 (import "m" "t0") 2 funcref)
       (type $id_i32_t (func (param i32) (result i32)))
       (func $id_i32 (param i32) (result i32) (local.get 0))
-      (elem $t0 (i32.const 1) $id_i32)
+      (elem $t0 (i32.const 1) func $id_i32)
 
       (table $t1 (import "m" "t1") 3 anyref)
 
       (table $t2 (import "m" "t2") 4 funcref)
       (type $id_f64_t (func (param f64) (result f64)))
       (func $id_f64 (param f64) (result f64) (local.get 0))
-      (elem $t2 (i32.const 3) $id_f64)
+      (elem $t2 (i32.const 3) func $id_f64)
 
       (table $t3 (import "m" "t3") 5 anyref)
 
       (func (export "f0") (param i32) (result i32)
        (call_indirect $t0 $id_i32_t (local.get 0) (i32.const 1)))
 
       (func (export "f1") (param anyref)
        (table.set $t1 (i32.const 2) (local.get 0)))
@@ -184,27 +184,27 @@ for (let [x,y,result,init] of [['(export
                                ['', '(import "m" "t")', arg-11, false]])
 {
     var otherins = wasmEvalText(
         `(module
           (table $t (export "t") 2 funcref)
           (type $fn1 (func (param i32) (result i32)))
           (func $f1 (param $n i32) (result i32)
            (i32.sub (local.get $n) (i32.const 11)))
-          (elem $t (i32.const 1) $f1))`);
+          (elem $t (i32.const 1) func $f1))`);
 
     let text =
         `(module
           (table $t0 ${x} 2 funcref)
 
           (table $t1 ${y} 2 funcref)
           (type $fn1 (func (param i32) (result i32)))
           (func $f1 (param $n i32) (result i32)
            (i32.mul (local.get $n) (i32.const 13)))
-          ${init ? "(elem $t1 (i32.const 1) $f1)" : ""}
+          ${init ? "(elem $t1 (i32.const 1) func $f1)" : ""}
 
           (func (export "f") (param $n i32) (result i32)
            (table.copy $t0 (i32.const 0) $t1 (i32.const 0) (i32.const 2))
            (call_indirect $t0 $fn1 (local.get $n) (i32.const 1))))`;
     var ins = wasmEvalText(text, {m: otherins.exports});
 
     assertEq(ins.exports.f(arg), result);
 }
@@ -239,17 +239,17 @@ ins.exports.t2.grow(3);
 assertEq(ins.exports.size(), 4);
 
 // - table.init on tables other than table 0
 
 var ins = wasmEvalText(
     `(module
       (table $t0 2 funcref)
       (table $t1 2 funcref)
-      (elem passive $f0 $f1) ;; 0
+      (elem func $f0 $f1) ;; 0
       (type $ftype (func (param i32) (result i32)))
       (func $f0 (param i32) (result i32)
        (i32.mul (local.get 0) (i32.const 13)))
       (func $f1 (param i32) (result i32)
        (i32.sub (local.get 0) (i32.const 11)))
       (func (export "call") (param i32) (param i32) (result i32)
        (call_indirect $t1 $ftype (local.get 1) (local.get 0)))
       (func (export "init")
@@ -371,26 +371,26 @@ assertErrorMessage(() => wasmEvalText(
       (func $f (result i32)
        (table.grow 2 (ref.null) (i32.const 1))))`),
                    WebAssembly.CompileError,
                    /table index out of range for table.grow/);
 
 assertErrorMessage(() => wasmEvalText(
     `(module
       (table $t0 2 funcref)
-      (elem passive) ;; 0
+      (elem func) ;; 0
       (func $f (result i32)
        (table.init 2 0 (i32.const 0) (i32.const 0) (i32.const 0))))`),
                    WebAssembly.CompileError,
                    /table index out of range for table.init/);
 
 assertErrorMessage(() => wasmEvalText(
     `(module
       (table $t0 2 funcref)
-      (elem 2 (i32.const 0)))`),
+      (elem 2 (i32.const 0) func))`),
                    WebAssembly.CompileError,
                    /table index out of range for element segment/);
 
 assertErrorMessage(() => wasmEvalText(
     `(module
       (table $t0 2 funcref)
       (type $ft (func (param f64) (result i32)))
       (func $f (result i32)
@@ -398,24 +398,17 @@ assertErrorMessage(() => wasmEvalText(
                    WebAssembly.CompileError,
                    /table index out of range for call_indirect/);
 
 // Syntax errors when parsing text
 
 assertErrorMessage(() => wasmEvalText(
     `(module
       (table $t0 2 funcref)
-      (elem 0 passive (i32.const 0)))`),
-                   SyntaxError,
-                   /passive or declared segment must not have a table/);
-
-assertErrorMessage(() => wasmEvalText(
-    `(module
-      (table $t0 2 funcref)
-      (elem passive) ;; 0
+      (elem func) ;; 0
       (func $f (result i32)
        (table.init $t0 (i32.const 0) (i32.const 0) (i32.const 0))))`), // no segment
                    SyntaxError,
                    /expected element segment reference/);
 
 assertErrorMessage(() => wasmEvalText(
     `(module
       (table $t0 2 funcref)
--- a/js/src/jit-test/tests/wasm/memory.js
+++ b/js/src/jit-test/tests/wasm/memory.js
@@ -451,8 +451,22 @@ setJitCompilerOption('wasm.fold-offsets'
                        WebAssembly.CompileError,
                        /invalid data initializer-kind/);
 
     // Should fail because the memory index is bad
     assertErrorMessage(() => new WebAssembly.Module(makeIt(0x02, 0x01)),
                        WebAssembly.CompileError,
                        /memory index must be zero/);
 }
+
+// Misc syntax for data.
+
+// When memory index is present it must be zero, and the offset must be present too;
+// but it's OK for there to be neither
+new WebAssembly.Module(wasmTextToBinary(`(module (memory 1) (data 0 (i32.const 0) ""))`));
+new WebAssembly.Module(wasmTextToBinary(`(module (memory 1) (data 0 (offset (i32.const 0)) ""))`));
+new WebAssembly.Module(wasmTextToBinary(`(module (memory 1) (data ""))`));
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`(module (memory 1) (data 0 ""))`)),
+                   SyntaxError,
+                   /Data segment with memory index must have offset/);
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`(module (memory 1) (data 1 (i32.const 0) ""))`)),
+                   SyntaxError,
+                   /Can't handle non-default memory/);
--- a/js/src/jit-test/tests/wasm/passive-segs-boundary.js
+++ b/js/src/jit-test/tests/wasm/passive-segs-boundary.js
@@ -24,35 +24,35 @@
 function do_test(insn1, insn2, errKind, errText,
                  isMem, haveStorage, haveInitA, haveInitP)
 {
     let preamble;
     if (isMem) {
         let mem_def  = haveStorage ? "(memory 1 1)" : "";
         let mem_ia1  = `(data (i32.const 2) "\\03\\01\\04\\01")`;
         let mem_ia2  = `(data (i32.const 12) "\\07\\05\\02\\03\\06")`;
-        let mem_ip1  = `(data passive "\\02\\07\\01\\08")`;
-        let mem_ip2  = `(data passive "\\05\\09\\02\\07\\06")`;
+        let mem_ip1  = `(data "\\02\\07\\01\\08")`;
+        let mem_ip2  = `(data "\\05\\09\\02\\07\\06")`;
         let mem_init = ``;
         if (haveInitA && haveInitP)
             mem_init = `${mem_ia1} ${mem_ip1} ${mem_ia2} ${mem_ip2}`;
         else if (haveInitA && !haveInitP) mem_init = `${mem_ia1} ${mem_ia2}`;
         else if (!haveInitA && haveInitP) mem_init = `${mem_ip1} ${mem_ip2}`;
         preamble
             = `;; -------- Memories --------
                ${mem_def}
                ;; -------- Memory initialisers --------
                ${mem_init}
               `;
     } else {
         let tab_def  = haveStorage ? "(table 30 30 funcref)" : "";
         let tab_ia1  = `(elem (i32.const 2) 3 1 4 1)`;
         let tab_ia2  = `(elem (i32.const 12) 7 5 2 3 6)`;
-        let tab_ip1  = `(elem passive 2 7 1 8)`;
-        let tab_ip2  = `(elem passive 5 9 2 7 6)`;
+        let tab_ip1  = `(elem func 2 7 1 8)`;
+        let tab_ip2  = `(elem func 5 9 2 7 6)`;
         let tab_init = ``;
         if (haveInitA && haveInitP)
             tab_init = `${tab_ia1} ${tab_ip1} ${tab_ia2} ${tab_ip2}`;
         else if (haveInitA && !haveInitP) tab_init = `${tab_ia1} ${tab_ia2}`;
         else if (!haveInitA && haveInitP) tab_init = `${tab_ip1} ${tab_ip2}`;
         preamble
             = `;; -------- Tables --------
                ${tab_def}
--- a/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js
+++ b/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js
@@ -34,19 +34,19 @@ function gen_tab_impmod_t(insn)
   let t =
   `(module
      ;; -------- Types --------
      (type (func (result i32)))  ;; type #0
      ;; -------- Tables --------
      (table 30 30 funcref)
      ;; -------- Table initialisers --------
      (elem (i32.const 2) 3 1 4 1)
-     (elem passive 2 7 1 8)
+     (elem func 2 7 1 8)
      (elem (i32.const 12) 7 5 2 3 6)
-     (elem passive 5 9 2 7 6)
+     (elem func 5 9 2 7 6)
      ;; -------- Imports --------
      (import "a" "if0" (result i32))    ;; index 0
      (import "a" "if1" (result i32))
      (import "a" "if2" (result i32))
      (import "a" "if3" (result i32))
      (import "a" "if4" (result i32))    ;; index 4
      ;; -------- Functions --------
      (func (result i32) (i32.const 5))  ;; index 5
@@ -169,19 +169,19 @@ tab_test("(table.init 1 (i32.const 7) (i
 function gen_mem_mod_t(insn)
 {
   let t =
   `(module
      ;; -------- Memories --------
      (memory (export "memory0") 1 1)
      ;; -------- Memory initialisers --------
      (data (i32.const 2) "\\03\\01\\04\\01")
-     (data passive "\\02\\07\\01\\08")
+     (data "\\02\\07\\01\\08")
      (data (i32.const 12) "\\07\\05\\02\\03\\06")
-     (data passive "\\05\\09\\02\\07\\06")
+     (data "\\05\\09\\02\\07\\06")
 
      (func (export "testfn")
        ${insn}
        ;; There's no return value.  The JS driver can just pull out the
        ;; final memory and examine it.
      )
    )`;
    return t;
@@ -259,27 +259,27 @@ mem_test("(memory.init 1 (i32.const 7) (
          "(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 ""))`),
+       (data "")
+       (data ""))`),
                    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 ""))`),
+       (data "")
+       (data ""))`),
                    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]),
@@ -343,17 +343,17 @@ checkPassiveElemSegment("end", /failed t
 {
     let txt =
         `(module
            (table (export "t") 10 funcref)
            (elem (i32.const 1) $m)
            (elem (i32.const 3) $m)
            (elem (i32.const 6) $m)
            (elem (i32.const 8) $m)
-           (elem passive $f ref.null $g ref.null $h)
+           (elem funcref (ref.func $f) (ref.null) (ref.func $g) (ref.null) (ref.func $h))
            (func $m)
            (func $f)
            (func $g)
            (func $h)
            (func (export "doit") (param $idx i32)
              (table.init 4 (local.get $idx) (i32.const 0) (i32.const 5))))`;
     let ins = wasmEvalText(txt);
     ins.exports.doit(0);
--- a/js/src/jit-test/tests/wasm/passive-segs-partial-mem.js
+++ b/js/src/jit-test/tests/wasm/passive-segs-partial-mem.js
@@ -53,17 +53,17 @@ mem_fill(2, 4, "shared", 257, 0xFFFFFFFF
 
 // Note, the length of the data segment is 16.
 const mem_init_len = 16;
 
 function mem_init(min, max, shared, backup, write) {
     let ins = wasmEvalText(
         `(module
            (memory (export "mem") ${min} ${max} ${shared})
-           (data passive "\\42\\42\\42\\42\\42\\42\\42\\42\\42\\42\\42\\42\\42\\42\\42\\42")
+           (data "\\42\\42\\42\\42\\42\\42\\42\\42\\42\\42\\42\\42\\42\\42\\42\\42")
            (func (export "run") (param $offs i32) (param $len i32)
              (memory.init 0 (local.get $offs) (i32.const 0) (local.get $len))))`);
     // A fill writing past the end of the memory should throw *and* have filled
     // all the way up to the end.
     //
     // A fill reading past the end of the segment should throw *and* have filled
     // memory with as much data as was available.
     let offs = min*PAGESIZE - backup;
--- a/js/src/jit-test/tests/wasm/passive-segs-partial-table.js
+++ b/js/src/jit-test/tests/wasm/passive-segs-partial-table.js
@@ -10,17 +10,17 @@
 
 // Note, the length of the element segment is 16.
 const tbl_init_len = 16;
 
 function tbl_init(min, max, backup, write, segoffs=0) {
     let ins = wasmEvalText(
         `(module
            (table (export "tbl") ${min} ${max} funcref)
-           (elem passive $f0 $f1 $f2 $f3 $f4 $f5 $f6 $f7 $f8 $f9 $f10 $f11 $f12 $f13 $f14 $f15)
+           (elem func $f0 $f1 $f2 $f3 $f4 $f5 $f6 $f7 $f8 $f9 $f10 $f11 $f12 $f13 $f14 $f15)
            (func $f0 (export "f0"))
            (func $f1 (export "f1"))
            (func $f2 (export "f2"))
            (func $f3 (export "f3"))
            (func $f4 (export "f4"))
            (func $f5 (export "f5"))
            (func $f6 (export "f6"))
            (func $f7 (export "f7"))
--- a/js/src/jit-test/tests/wasm/regress/startfunc-in-table.js
+++ b/js/src/jit-test/tests/wasm/regress/startfunc-in-table.js
@@ -1,6 +1,6 @@
 wasmEvalText(`(module
     (func)
     (start 0)
     (table $0 1 anyfunc)
-    (elem 0 (i32.const 0) 0)
+    (elem 0 (i32.const 0) func 0)
 )`);
--- a/js/src/jit-test/tests/wasm/tables.js
+++ b/js/src/jit-test/tests/wasm/tables.js
@@ -21,16 +21,42 @@ assertEq(wasmEvalText(`(module (table 0 
 assertErrorMessage(() => wasmEvalText(`(module (table 10 funcref) (import "globals" "a" (global i32)) (elem (global.get 0) $f0) ${callee(0)})`, {globals:{a:10}}), LinkError, /elem segment does not fit/);
 assertErrorMessage(() => wasmEvalText(`(module (table 10 funcref) (import "globals" "a" (global i32)) (elem (global.get 0) $f0 $f0 $f0) ${callee(0)})`, {globals:{a:8}}), LinkError, /elem segment does not fit/);
 
 assertEq(new Module(wasmTextToBinary(`(module (table 10 funcref) (elem (i32.const 1) $f0 $f0) (elem (i32.const 0) $f0) ${callee(0)})`)) instanceof Module, true);
 assertEq(new Module(wasmTextToBinary(`(module (table 10 funcref) (elem (i32.const 1) $f0 $f0) (elem (i32.const 2) $f0) ${callee(0)})`)) instanceof Module, true);
 wasmEvalText(`(module (table 10 funcref) (import "globals" "a" (global i32)) (elem (i32.const 1) $f0 $f0) (elem (global.get 0) $f0) ${callee(0)})`, {globals:{a:0}});
 wasmEvalText(`(module (table 10 funcref) (import "globals" "a" (global i32)) (elem (global.get 0) $f0 $f0) (elem (i32.const 2) $f0) ${callee(0)})`, {globals:{a:1}});
 
+// Explicit table index in a couple of ways, note this requires us to talk about the table type also.
+assertEq(new Module(wasmTextToBinary(`(module
+                                        (table 10 funcref)
+                                        (table 10 funcref)
+                                        (elem 1 (i32.const 1) func $f0 $f0)
+                                        ${callee(0)})`)) instanceof Module, true);
+assertEq(new Module(wasmTextToBinary(`(module
+                                        (table 10 funcref)
+                                        (table 10 funcref)
+                                        (elem (table 1) (i32.const 1) func $f0 $f0)
+                                        ${callee(0)})`)) instanceof Module, true);
+
+// With "funcref" rather than "func".
+assertEq(new Module(wasmTextToBinary(`(module
+                                        (table 10 funcref)
+                                        (table 10 funcref)
+                                        (elem (table 1) (i32.const 1) funcref (ref.func $f0) (ref.func $f0))
+                                        ${callee(0)})`)) instanceof Module, true);
+
+// Syntax for the offset, ditto.
+assertEq(new Module(wasmTextToBinary(`(module
+                                        (table 10 funcref)
+                                        (table 10 funcref)
+                                        (elem 1 (offset (i32.const 1)) func $f0 $f0)
+                                        ${callee(0)})`)) instanceof Module, true);
+
 var m = new Module(wasmTextToBinary(`
     (module
         (import "globals" "table" (table 10 funcref))
         (import "globals" "a" (global i32))
         (elem (global.get 0) $f0 $f0)
         ${callee(0)})
 `));
 var tbl = new Table({initial:50, element:"funcref"});
--- a/js/src/wasm/WasmConstants.h
+++ b/js/src/wasm/WasmConstants.h
@@ -140,23 +140,23 @@ enum class MemoryTableFlags {
   IsShared = 0x2,
 };
 
 enum class MemoryMasks { AllowUnshared = 0x1, AllowShared = 0x3 };
 
 enum class DataSegmentKind {
   Active = 0x00,
   Passive = 0x01,
-  ActiveWithIndex = 0x02
+  ActiveWithMemoryIndex = 0x02
 };
 
 enum class ElemSegmentKind : uint32_t {
   Active = 0x0,
   Passive = 0x1,
-  ActiveWithIndex = 0x2,
+  ActiveWithTableIndex = 0x2,
   Declared = 0x3,
 };
 
 enum class ElemSegmentPayload : uint32_t {
   ExternIndex = 0x0,
   ElemExpression = 0x4,
 };
 
--- a/js/src/wasm/WasmTextToBinary.cpp
+++ b/js/src/wasm/WasmTextToBinary.cpp
@@ -4447,28 +4447,16 @@ static AstTypeDef* ParseTypeDef(WasmPars
 
   if (!c.ts.match(WasmToken::CloseParen, c.error)) {
     return nullptr;
   }
 
   return type;
 }
 
-static bool MaybeParseOwnerIndex(WasmParseContext& c) {
-  if (c.ts.peek().kind() == WasmToken::Index) {
-    WasmToken elemIndex = c.ts.get();
-    if (elemIndex.index()) {
-      c.ts.generateError(elemIndex, "can't handle non-default memory/table yet",
-                         c.error);
-      return false;
-    }
-  }
-  return true;
-}
-
 static AstExpr* ParseInitializerConstExpression(WasmParseContext& c) {
   bool need_rparen = false;
 
   // For const initializer expressions, the parens are optional.
   if (c.ts.getIf(WasmToken::OpenParen)) {
     need_rparen = true;
   }
 
@@ -4484,39 +4472,58 @@ static AstExpr* ParseInitializerConstExp
   return initExpr;
 }
 
 static AstExpr* ParseInitializerExpression(WasmParseContext& c) {
   if (!c.ts.match(WasmToken::OpenParen, c.error)) {
     return nullptr;
   }
 
-  AstExpr* initExpr = ParseExprInsideParens(c);
+  AstExpr* initExpr;
+  if (c.ts.getIf(WasmToken::Offset)) {
+    initExpr = ParseInitializerConstExpression(c);
+  } else {
+    initExpr = ParseExprInsideParens(c);
+  }
   if (!initExpr) {
     return nullptr;
   }
 
   if (!c.ts.match(WasmToken::CloseParen, c.error)) {
     return nullptr;
   }
 
   return initExpr;
 }
 
 static AstDataSegment* ParseDataSegment(WasmParseContext& c) {
-  if (!MaybeParseOwnerIndex(c)) {
+  // The syntax is effectively this:
+  //   (data [[<mem-index>] <offset>] <string-list>)
+  //
+  // The segment is passive if and only if <mem-index> and <offset> are both
+  // absent.
+
+  WasmToken elemIndex;
+  bool haveMemIndex = c.ts.getIf(WasmToken::Index, &elemIndex);
+  if (haveMemIndex && elemIndex.index()) {
+    c.ts.generateError(elemIndex, "can't handle non-default memory", c.error);
     return nullptr;
   }
 
   AstExpr* offsetIfActive = nullptr;
-  if (!c.ts.getIf(WasmToken::Passive)) {
+  if (c.ts.peek().kind() == WasmToken::OpenParen) {
     offsetIfActive = ParseInitializerExpression(c);
     if (!offsetIfActive) {
       return nullptr;
     }
+  } else if (haveMemIndex) {
+    c.ts.generateError(c.ts.peek(),
+                       "data segment with memory index must have offset",
+                       c.error);
+    return nullptr;
   }
 
   AstNameVector fragments(c.lifo);
 
   WasmToken text;
   while (c.ts.getIf(WasmToken::Text, &text)) {
     if (!fragments.append(text.text())) {
       return nullptr;
@@ -4524,17 +4531,17 @@ static AstDataSegment* ParseDataSegment(
   }
 
   return new (c.lifo) AstDataSegment(offsetIfActive, std::move(fragments));
 }
 
 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);
+    c.ts.generateError(token, "literal data segment count required", c.error);
     return false;
   }
 
   return module->initDataCount(token.index());
 }
 
 static bool ParseLimits(WasmParseContext& c, Limits* limits,
                         Shareable allowShared) {
@@ -5041,65 +5048,151 @@ static bool ParseTable(WasmParseContext&
     return false;
   }
 
   AstElemSegment* segment = new (c.lifo) AstElemSegment(
       AstElemSegmentKind::Active, AstRef(name), zero, std::move(elems));
   return segment && module->append(segment);
 }
 
+static bool TryParseFuncOrFuncRef(WasmParseContext& c, bool* isFunc) {
+  if (c.ts.getIf(WasmToken::Func)) {
+    *isFunc = true;
+    return true;
+  }
+
+  WasmToken token = c.ts.peek();
+  if (token.kind() == WasmToken::ValueType &&
+      token.valueType() == ValType::FuncRef) {
+    c.ts.get();
+    *isFunc = false;
+    return true;
+  }
+
+  return false;
+}
+
 static AstElemSegment* ParseElemSegment(WasmParseContext& c) {
-  // (elem table-name init-expr (fnref|ref.null)...)
-  // (elem init-expr (fnref|ref.null)...)
-  // (elem passive (fnref|ref.null)...)
-  // (elem declared fnref...)
+  // Syntax from bulk memory proposal.
+  // <init-expr> is any <expr> or (offset <const>).
+  // <table-use> is (table <n>) or just <n> (an extension).
+  // <fnref> is a naked function reference (index or name)
+  //
+  // Active initializer for table 0 which must be table-of-functions, this is
+  // sugar:
+  //   (elem <init-expr> <fnref> ...)
+  //
+  // Active initializer for a given table of functions:
+  //   (elem <table-use> <init-expr> func <fnref> ...)
+  //
+  // Active initializer for a given table of functions, allowing null.  Note the
+  // parens are required also around ref.null:
+  //   (elem <table-use> <init-expr> funcref
+  //         "(" (ref.func <fnref>|ref.null) ")" ...)
+  //
+  // Passive initializers:
+  //   (elem func <fnref> ...)
+  //   (elem funcref "(" (ref.func <fnref>|ref.null) ")" ...)
+  //
+  // Forward declaration for ref.func uses:
+  //   (elem declared <fnref> ...)
 
   AstRef targetTable = AstRef(0);
-  bool hasTableName = c.ts.getIfRef(&targetTable);
-
+  AstExpr* offsetIfActive = nullptr;
+  bool haveTableref = false;
   AstElemSegmentKind kind;
-  AstExpr* offsetIfActive = nullptr;
-
-  if (c.ts.getIf(WasmToken::Passive)) {
-    kind = AstElemSegmentKind::Passive;
+
+  if (c.ts.peek().kind() == WasmToken::OpenParen) {
+    WasmToken lparen = c.ts.get();
+    if (c.ts.getIf(WasmToken::Table)) {
+      if (!c.ts.matchRef(&targetTable, c.error)) {
+        return nullptr;
+      }
+      if (!c.ts.match(WasmToken::CloseParen, c.error)) {
+        return nullptr;
+      }
+      haveTableref = true;
+    } else {
+      c.ts.unget(lparen);
+    }
+  } else if (c.ts.getIfRef(&targetTable)) {
+    haveTableref = true;
+  }
+
+  // The order of clauses matters because ParseInitializerExpression changes the
+  // state of the token stream, we can't recover after that fails.
+
+  bool nakedFnrefs = false;
+  if (haveTableref) {
+    if ((offsetIfActive = ParseInitializerExpression(c)) == nullptr) {
+      c.ts.generateError(
+          c.ts.peek(),
+          "elem segment with table reference must have offset expression",
+          c.error);
+      return nullptr;
+    }
+    if (!TryParseFuncOrFuncRef(c, &nakedFnrefs)) {
+      c.ts.generateError(c.ts.peek(),
+                         "'func' or 'funcref' required for elem segment",
+                         c.error);
+      return nullptr;
+    }
+    kind = AstElemSegmentKind::Active;
   } else if (c.ts.getIf(WasmToken::Declared)) {
     kind = AstElemSegmentKind::Declared;
+    nakedFnrefs = true;
+  } else if (TryParseFuncOrFuncRef(c, &nakedFnrefs)) {
+    // 'func' or 'funcref' for a passive segment.
+    kind = AstElemSegmentKind::Passive;
   } else {
-    kind = AstElemSegmentKind::Active;
-    offsetIfActive = ParseInitializerExpression(c);
-    if (!offsetIfActive) {
+    if ((offsetIfActive = ParseInitializerExpression(c)) == nullptr) {
+      c.ts.generateError(c.ts.peek(),
+                         "elem segment for table 0 must have offset expression",
+                         c.error);
       return nullptr;
     }
-  }
-
-  if (hasTableName && kind != AstElemSegmentKind::Active) {
-    c.ts.generateError(c.ts.peek(),
-                       "passive or declared segment must not have a table",
-                       c.error);
-    return nullptr;
+    // Implicit table 0 with implicit naked fnrefs
+    nakedFnrefs = true;
+    kind = AstElemSegmentKind::Active;
   }
 
   AstElemVector elems(c.lifo);
 
-  for (;;) {
+  if (nakedFnrefs) {
     AstRef elemRef;
-    if (c.ts.getIfRef(&elemRef)) {
+    while (c.ts.getIfRef(&elemRef)) {
       if (!elems.append(AstElem(elemRef))) {
         return nullptr;
       }
-      continue;
-    }
-    if (kind != AstElemSegmentKind::Declared &&
-        c.ts.getIf(WasmToken::RefNull)) {
-      if (!elems.append(AstElem(AstNullValue()))) {
+    }
+  } else {
+    AstRef elemRef;
+    WasmToken openParen;
+    while (c.ts.getIf(WasmToken::OpenParen, &openParen)) {
+      if (c.ts.getIf(WasmToken::RefFunc)) {
+        if (!c.ts.matchRef(&elemRef, c.error)) {
+          return nullptr;
+        }
+        if (!elems.append(AstElem(elemRef))) {
+          return nullptr;
+        }
+      } else if (c.ts.getIf(WasmToken::RefNull)) {
+        if (!elems.append(AstElem(AstNullValue()))) {
+          return nullptr;
+        }
+      } else {
+        c.ts.generateError(c.ts.peek(),
+                           "ref.func or ref.null required in element segment",
+                           c.error);
         return nullptr;
       }
-      continue;
-    }
-    break;
+      if (!c.ts.match(WasmToken::CloseParen, c.error)) {
+        return nullptr;
+      }
+    }
   }
 
   return new (c.lifo)
       AstElemSegment(kind, targetTable, offsetIfActive, std::move(elems));
 }
 
 static bool ParseGlobal(WasmParseContext& c, AstModule* module) {
   AstName name = c.ts.getIfName();
@@ -7231,17 +7324,17 @@ static bool EncodeCodeSection(Encoder& e
 
 static bool EncodeDataInitializerKind(Encoder& e, uint32_t index,
                                       AstExpr* offsetIfActive) {
   if (offsetIfActive) {
     // In the MVP, the following VarU32 is the linear memory index and it must
     // be zero.  In the bulk-mem-ops proposal, it is repurposed as a flag
     // field, and if the index is not zero it must be present.
     if (index) {
-      if (!e.writeVarU32(uint32_t(DataSegmentKind::ActiveWithIndex)) ||
+      if (!e.writeVarU32(uint32_t(DataSegmentKind::ActiveWithMemoryIndex)) ||
           !e.writeVarU32(index)) {
         return false;
       }
     } else {
       if (!e.writeVarU32(uint32_t(DataSegmentKind::Active))) {
         return false;
       }
     }
@@ -7342,72 +7435,76 @@ static bool EncodeElemSegment(Encoder& e
       break;
     }
   }
 
   // Select the encoding that takes the least amount of space
   ElemSegmentKind kind;
   switch (segment.kind()) {
     case AstElemSegmentKind::Active: {
-      kind = segment.targetTable().index() ? ElemSegmentKind::ActiveWithIndex
-                                           : ElemSegmentKind::Active;
+      kind = segment.targetTable().index()
+                 ? ElemSegmentKind::ActiveWithTableIndex
+                 : ElemSegmentKind::Active;
       break;
     }
     case AstElemSegmentKind::Passive: {
       kind = ElemSegmentKind::Passive;
       break;
     }
     case AstElemSegmentKind::Declared: {
       kind = ElemSegmentKind::Declared;
       break;
     }
   }
   ElemSegmentPayload payload = hasRefNull ? ElemSegmentPayload::ElemExpression
                                           : ElemSegmentPayload::ExternIndex;
 
-  // Write the flags field
+  // Write the flags field.
   if (!e.writeVarU32(ElemSegmentFlags(kind, payload).encoded())) {
     return false;
   }
 
-  // Write the table index if it is not zero
-  if (kind == ElemSegmentKind::ActiveWithIndex &&
+  // Write the table index.
+  if (kind == ElemSegmentKind::ActiveWithTableIndex &&
       !e.writeVarU32(segment.targetTable().index())) {
     return false;
   }
 
+  // Write the offset expression.
   if (kind == ElemSegmentKind::Active ||
-      kind == ElemSegmentKind::ActiveWithIndex) {
-    // Write the offset expression
+      kind == ElemSegmentKind::ActiveWithTableIndex) {
     if (!EncodeExpr(e, *segment.offsetIfActive())) {
       return false;
     }
     if (!e.writeOp(Op::End)) {
       return false;
     }
   }
 
+  // Write the type or definition kind.
+  //
   // An active element segment without explicit index uses the original MVP
-  // encoding, which doesn't include an explicit type or definition kind
+  // encoding, which doesn't include an explicit type or definition kind.
   if (kind != ElemSegmentKind::Active) {
-    // Write the type or definition kind
     if (payload == ElemSegmentPayload::ElemExpression &&
         !e.writeFixedU8(uint8_t(TypeCode::FuncRef))) {
       return false;
     }
     if (payload == ElemSegmentPayload::ExternIndex &&
         !e.writeFixedU8(uint8_t(DefinitionKind::Function))) {
       return false;
     }
   }
 
+  // Write the number of elements.
   if (!e.writeVarU32(segment.elems().length())) {
     return false;
   }
 
+  // Write the elements.
   for (const AstElem& elem : segment.elems()) {
     if (elem.is<AstRef>()) {
       const AstRef& ref = elem.as<AstRef>();
       if (payload == ElemSegmentPayload::ElemExpression &&
           !e.writeFixedU8(uint8_t(Op::RefFunc))) {
         return false;
       }
       if (!e.writeVarU32(ref.index())) {
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -2290,17 +2290,17 @@ static bool DecodeStartSection(Decoder& 
 
   return d.finishSection(*range, "start");
 }
 
 static inline ElemSegment::Kind NormalizeElemSegmentKind(
     ElemSegmentKind decodedKind) {
   switch (decodedKind) {
     case ElemSegmentKind::Active:
-    case ElemSegmentKind::ActiveWithIndex: {
+    case ElemSegmentKind::ActiveWithTableIndex: {
       return ElemSegment::Kind::Active;
     }
     case ElemSegmentKind::Passive: {
       return ElemSegment::Kind::Passive;
     }
     case ElemSegmentKind::Declared: {
       return ElemSegment::Kind::Declared;
     }
@@ -2345,23 +2345,23 @@ static bool DecodeElemSection(Decoder& d
     if (!seg) {
       return false;
     }
 
     ElemSegmentKind kind = flags->kind();
     seg->kind = NormalizeElemSegmentKind(kind);
 
     if (kind == ElemSegmentKind::Active ||
-        kind == ElemSegmentKind::ActiveWithIndex) {
+        kind == ElemSegmentKind::ActiveWithTableIndex) {
       if (env->tables.length() == 0) {
         return d.fail("active elem segment requires a table");
       }
 
       uint32_t tableIndex = 0;
-      if (kind == ElemSegmentKind::ActiveWithIndex &&
+      if (kind == ElemSegmentKind::ActiveWithTableIndex &&
           !d.readVarU32(&tableIndex)) {
         return d.fail("expected table index");
       }
       if (tableIndex >= env->tables.length()) {
         return d.fail("table index out of range for element segment");
       }
       if (env->tables[tableIndex].kind != TableKind::FuncRef) {
         return d.fail("only tables of 'funcref' may have element segments");
@@ -2379,18 +2379,18 @@ static bool DecodeElemSection(Decoder& d
       // never touch the field.
       MOZ_ASSERT(kind == ElemSegmentKind::Passive ||
                  kind == ElemSegmentKind::Declared);
       seg->tableIndex = (uint32_t)-1;
     }
 
     ElemSegmentPayload payload = flags->payload();
 
-    // `ActiveWithIndex`, `Declared`, and `Passive` element segments encode the
-    // type or definition kind of the payload. `Active` element segments are
+    // `ActiveWithTableIndex`, `Declared`, and `Passive` element segments encode
+    // the type or definition kind of the payload. `Active` element segments are
     // restricted to MVP behavior, which assumes only function indices.
     if (kind != ElemSegmentKind::Active) {
       uint8_t form;
       if (!d.readFixedU8(&form)) {
         return d.fail("expected type or extern kind");
       }
 
       switch (payload) {
@@ -2697,41 +2697,41 @@ static bool DecodeDataSection(Decoder& d
     uint32_t initializerKindVal;
     if (!d.readVarU32(&initializerKindVal)) {
       return d.fail("expected data initializer-kind field");
     }
 
     switch (initializerKindVal) {
       case uint32_t(DataSegmentKind::Active):
       case uint32_t(DataSegmentKind::Passive):
-      case uint32_t(DataSegmentKind::ActiveWithIndex):
+      case uint32_t(DataSegmentKind::ActiveWithMemoryIndex):
         break;
       default:
         return d.fail("invalid data initializer-kind field");
     }
 
     DataSegmentKind initializerKind = DataSegmentKind(initializerKindVal);
 
     if (initializerKind != DataSegmentKind::Passive && !env->usesMemory()) {
       return d.fail("active data segment requires a memory section");
     }
 
     uint32_t memIndex = 0;
-    if (initializerKind == DataSegmentKind::ActiveWithIndex) {
+    if (initializerKind == DataSegmentKind::ActiveWithMemoryIndex) {
       if (!d.readVarU32(&memIndex)) {
         return d.fail("expected memory index");
       }
       if (memIndex > 0) {
         return d.fail("memory index must be zero");
       }
     }
 
     DataSegmentEnv seg;
     if (initializerKind == DataSegmentKind::Active ||
-        initializerKind == DataSegmentKind::ActiveWithIndex) {
+        initializerKind == DataSegmentKind::ActiveWithMemoryIndex) {
       InitExpr segOffset;
       if (!DecodeInitializerExpression(d, env, ValType::I32, &segOffset)) {
         return false;
       }
       seg.offsetIfActive.emplace(segOffset);
     }
 
     if (!d.readVarU32(&seg.length)) {