Bug 1546592 - Allow element segments to be 'anyref', and generalize. r=rhunt
authorLars T Hansen <lhansen@mozilla.com>
Thu, 17 Oct 2019 13:25:24 +0000
changeset 497974 81babd897f9e153c23fa46dfb48e73855adb2418
parent 497973 7f10a00c6671934812587d1596e56fd6099b7581
child 497975 a2873ee82d772091b21d4ac06a92d1787b63d23f
push id36703
push userccoroiu@mozilla.com
push dateThu, 17 Oct 2019 21:54:25 +0000
treeherdermozilla-central@c260b3893967 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrhunt
bugs1546592
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 1546592 - Allow element segments to be 'anyref', and generalize. r=rhunt The main changes here are: * Element segments that carry an elementexpr can carry a type T that is other than 'funcref', though we require T <: anyref * We generalize type handling around table initialization so that a table-of-T can be initialized from segment-of-U if U <: T. Also: * A declared element segment needs to have type funcref. The spec is silent on this, but it's a conservative choice. * A drive-by fix in serialize/deserialize, the 'kind' field was not being handled properly. This doesn't affect any shipping product as the serialize/deserialize code is currently unused. Differential Revision: https://phabricator.services.mozilla.com/D48690
js/src/jit-test/lib/wasm-binary.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/passive-segs-nonboundary.js
js/src/wasm/WasmAST.h
js/src/wasm/WasmGenerator.cpp
js/src/wasm/WasmInstance.cpp
js/src/wasm/WasmInstance.h
js/src/wasm/WasmModule.cpp
js/src/wasm/WasmOpIter.h
js/src/wasm/WasmTextToBinary.cpp
js/src/wasm/WasmTypes.cpp
js/src/wasm/WasmTypes.h
js/src/wasm/WasmValidate.cpp
--- a/js/src/jit-test/lib/wasm-binary.js
+++ b/js/src/jit-test/lib/wasm-binary.js
@@ -351,16 +351,60 @@ function elemSection(elemArrays) {
         body.push(...varU32(EndCode));
         body.push(...varU32(array.elems.length));
         for (let elem of array.elems)
             body.push(...varU32(elem));
     }
     return { name: elemId, body };
 }
 
+// For now, the encoding spec is here:
+// https://github.com/WebAssembly/bulk-memory-operations/issues/98#issuecomment-507330729
+
+const LegacyActiveExternVal = 0;
+const PassiveExternVal = 1;
+const ActiveExternVal = 2;
+const DeclaredExternVal = 3;
+const LegacyActiveElemExpr = 4;
+const PassiveElemExpr = 5;
+const ActiveElemExpr = 6;
+const DeclaredElemExpr = 7;
+
+function generalElemSection(elemObjs) {
+    let body = [];
+    body.push(...varU32(elemObjs.length));
+    for (let elemObj of elemObjs) {
+        body.push(elemObj.flag);
+        if ((elemObj.flag & 3) == 2)
+            body.push(...varU32(elemObj.table));
+        // TODO: This is not very flexible
+        if ((elemObj.flag & 1) == 0) {
+            body.push(...varU32(I32ConstCode));
+            body.push(...varS32(elemObj.offset));
+            body.push(...varU32(EndCode));
+        }
+        if (elemObj.flag & 4) {
+            if (elemObj.flag & 3)
+                body.push(elemObj.typeCode & 255);
+            // Each element is an array of bytes
+            body.push(...varU32(elemObj.elems.length));
+            for (let elemBytes of elemObj.elems)
+                body.push(...elemBytes);
+        } else {
+            if (elemObj.flag & 3)
+                body.push(elemObj.externKind & 255);
+            // Each element is a putative function index
+            body.push(...varU32(elemObj.elems.length));
+            for (let elem of elemObj.elems)
+                body.push(...varU32(elem));
+        }
+    }
+    return { name: elemId, body };
+}
+
 function moduleNameSubsection(moduleName) {
     var body = [];
     body.push(...varU32(nameTypeModule));
 
     var subsection = encodedString(moduleName);
     body.push(...varU32(subsection.length));
     body.push(...subsection);
 
--- a/js/src/jit-test/tests/wasm/gc/ref-func.js
+++ b/js/src/jit-test/tests/wasm/gc/ref-func.js
@@ -1,10 +1,12 @@
 // |jit-test| skip-if: !wasmReftypesEnabled()
 
+load(libdir + "wasm-binary.js");
+
 // 'ref.func' parses, validates and returns a non-null value
 wasmFullPass(`
 	(module
 		(elem declared $run)
 		(func $run (result i32)
 			ref.func $run
 			ref.is_null
 		)
@@ -66,30 +68,70 @@ wasmFullPass(`
 assertErrorMessage(() => {
 	wasmEvalText(`
 		(module
 			(func (result funcref) ref.func 10)
 		)
 	`);
 }, WebAssembly.CompileError, /function index out of range/);
 
-function validFuncRefText(forwardDeclare) {
+function validFuncRefText(forwardDeclare, tbl_type) {
 	return wasmEvalText(`
 		(module
-			(table 1 funcref)
+			(table 1 ${tbl_type})
 			(func $test (result funcref) ref.func $referenced)
 			(func $referenced)
 			${forwardDeclare}
 		)
 	`);
 }
 
 // referenced function must be forward declared somehow
-assertErrorMessage(() => validFuncRefText(''), WebAssembly.CompileError, /function index is not in an element segment/);
+assertErrorMessage(() => validFuncRefText('', 'funcref'), 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) func $referenced)') instanceof WebAssembly.Instance, true);
-assertEq(validFuncRefText('(elem func $referenced)') instanceof WebAssembly.Instance, true);
-assertEq(validFuncRefText('(elem declared $referenced)') instanceof WebAssembly.Instance, true);
+assertEq(validFuncRefText('(elem 0 (i32.const 0) func $referenced)', 'funcref') instanceof WebAssembly.Instance, true);
+assertEq(validFuncRefText('(elem func $referenced)', 'funcref') instanceof WebAssembly.Instance, true);
+assertEq(validFuncRefText('(elem declared $referenced)', 'funcref') instanceof WebAssembly.Instance, true);
+
+// also when the segment is passive or active 'anyref'
+assertEq(validFuncRefText('(elem 0 (i32.const 0) anyref (ref.func $referenced))', 'anyref') instanceof WebAssembly.Instance, true);
+assertEq(validFuncRefText('(elem anyref (ref.func $referenced))', 'anyref') 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/);
+assertErrorMessage(() => validFuncRefText('(start $referenced)', 'funcref'),
+                   WebAssembly.CompileError,
+                   /function index is not in an element segment/);
+assertErrorMessage(() => validFuncRefText('(export "referenced" $referenced)', 'funcref'),
+                   WebAssembly.CompileError,
+                   /function index is not in an element segment/);
+
+// Tests not expressible in the text format.
+
+// element segment with elemexpr can carry non-reference type, but this must be
+// rejected.
+
+assertErrorMessage(() => new WebAssembly.Module(
+    moduleWithSections([generalElemSection([{ flag: PassiveElemExpr,
+                                              typeCode: I32Code,
+                                              elems: [] }])])),
+                   WebAssembly.CompileError,
+                   /segments with element expressions can only contain references/);
+
+// declared element segment with elemexpr can carry type anyref, but this must be rejected.
+
+assertErrorMessage(() => new WebAssembly.Module(
+    moduleWithSections([generalElemSection([{ flag: DeclaredElemExpr,
+                                              typeCode: AnyrefCode,
+                                              elems: [] }])])),
+                   WebAssembly.CompileError,
+                   /declared segment's element type must be subtype of funcref/);
+
+// declared element segment of type funcref with elemexpr can carry a null
+// value, but the null value must be rejected.
+
+assertErrorMessage(() => new WebAssembly.Module(
+    moduleWithSections([generalElemSection([{ flag: DeclaredElemExpr,
+                                              typeCode: AnyFuncCode,
+                                              elems: [[RefNullCode]] }])])),
+                   WebAssembly.CompileError,
+                   /declared element segments cannot contain ref.null/);
+
--- a/js/src/jit-test/tests/wasm/gc/tables-generalized.js
+++ b/js/src/jit-test/tests/wasm/gc/tables-generalized.js
@@ -111,25 +111,69 @@ assertErrorMessage(() => new WebAssembly
     `(module
        (table 10 funcref)
        (table 10 anyref)
        (func (export "f")
          (table.copy 0 (i32.const 0) 1 (i32.const 0) (i32.const 5))))`)),
                    WebAssembly.CompileError,
                    /expression has type anyref but expected funcref/);
 
-// Wasm: element segments targeting table-of-anyref is forbidden
+// Wasm: Element segments can target tables of anyref whether the element type
+// is anyref or funcref.
+
+for (let elem_ty of ["funcref", "anyref"]) {
+    let ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (func $f1 (export "f") (result i32) (i32.const 0))
+       (func $f2 (result i32) (i32.const 0)) ;; on purpose not exported
+       (table (export "t") 10 anyref)
+       (elem (table 0) (i32.const 0) ${elem_ty} (ref.func $f1) (ref.func $f2))
+       )`)));
+    let t = ins.exports.t;
+    let f = ins.exports.f;
+    assertEq(t.get(0), f);
+    assertEq(t.get(2), null);  // not much of a test since that's the default value
+}
+
+// Wasm: Element segments of anyref can't target tables of funcref
 
 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
     `(module
        (func $f1 (result i32) (i32.const 0))
-       (table 10 anyref)
-       (elem 0 (i32.const 0) func $f1))`)),
+       (table (export "t") 10 funcref)
+       (elem 0 (i32.const 0) anyref (ref.func $f1)))`)),
                    WebAssembly.CompileError,
-                   /only tables of 'funcref' may have element segments/);
+                   /segment's element type must be subtype of table's element type/);
+
+// Wasm: table.init on table-of-anyref is allowed whether the segment has
+// anyrefs or funcrefs.
+
+for (let elem_ty of ["funcref", "anyref"]) {
+    let ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (func $f1 (result i32) (i32.const 0))
+       (table (export "t") 10 anyref)
+       (elem ${elem_ty} (ref.func $f1))
+       (func (export "f")
+         (table.init 0 (i32.const 2) (i32.const 0) (i32.const 1))))`)));
+    ins.exports.f();
+    assertEq(typeof ins.exports.t.get(2), "function");
+}
+
+// Wasm: table.init on table-of-funcref is not allowed when the segment has
+// anyref.
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (table 10 funcref)
+       (elem anyref (ref.null))
+       (func
+         (table.init 0 (i32.const 0) (i32.const 0) (i32.const 0))))`)),
+                   WebAssembly.CompileError,
+                   /expression has type anyref but expected funcref/);
 
 // Wasm: table types must match at link time
 
 assertErrorMessage(
     () => new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
     `(module
        (import "m" "t" (table 10 anyref)))`)),
                                    {m:{t: new WebAssembly.Table({element:"funcref", initial:10})}}),
@@ -454,24 +498,8 @@ let VALUES = [null,
 // If growing by zero elements there are no spurious writes
 
 {
     let t = new WebAssembly.Table({element:"anyref", initial:1});
     t.set(0, 1337);
     t.grow(0, 1789);
     assertEq(t.get(0), 1337);
 }
-
-// Currently 'anyref' segments are not allowed whether passive or active,
-// though that will change
-
-assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
-    `(module
-       (elem (i32.const 0) anyref (ref.null)))`)),
-                   SyntaxError,
-                   /parsing wasm text/);
-
-
-assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
-    `(module
-       (elem anyref (ref.null)))`)),
-                   SyntaxError,
-                   /parsing wasm text/);
--- a/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js
+++ b/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js
@@ -329,17 +329,17 @@ function checkPassiveElemSegment(mangle,
                            WebAssembly.CompileError,
                            err);
     } else {
         new WebAssembly.Module(bin);
     }
 }
 
 checkPassiveElemSegment("");
-checkPassiveElemSegment("type", /segments with element expressions can only contain function references/);
+checkPassiveElemSegment("type", /segments with element expressions can only contain references/);
 checkPassiveElemSegment("ref.func", /failed to read initializer operation/);
 checkPassiveElemSegment("end", /failed to read end of initializer expression/);
 
 // Passive element segments can contain literal null values.
 
 {
     let txt =
         `(module
--- a/js/src/wasm/WasmAST.h
+++ b/js/src/wasm/WasmAST.h
@@ -1302,30 +1302,34 @@ enum class AstElemSegmentKind {
   Passive,
   Declared,
 };
 
 class AstElemSegment : public AstNode {
   AstElemSegmentKind kind_;
   AstRef targetTable_;
   AstExpr* offsetIfActive_;
+  ValType elemType_;
   AstElemVector elems_;
 
  public:
   AstElemSegment(AstElemSegmentKind kind, AstRef targetTable,
-                 AstExpr* offsetIfActive, AstElemVector&& elems)
+                 AstExpr* offsetIfActive, ValType elemType,
+                 AstElemVector&& elems)
       : kind_(kind),
         targetTable_(targetTable),
         offsetIfActive_(offsetIfActive),
+        elemType_(elemType),
         elems_(std::move(elems)) {}
 
   AstElemSegmentKind kind() const { return kind_; }
   AstRef targetTable() const { return targetTable_; }
   AstRef& targetTableRef() { return targetTable_; }
   AstExpr* offsetIfActive() const { return offsetIfActive_; }
+  ValType elemType() const { return elemType_; }
   AstElemVector& elems() { return elems_; }
   const AstElemVector& elems() const { return elems_; }
 };
 
 typedef AstVector<AstElemSegment*> AstElemSegmentVector;
 
 class AstStartFunc : public AstNode {
   AstRef func_;
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -362,32 +362,32 @@ bool ModuleGenerator::init(Metadata* may
     }
   }
 
   if (env_->startFuncIndex) {
     addOrMerge(ExportedFunc(*env_->startFuncIndex, true));
   }
 
   for (const ElemSegment* seg : env_->elemSegments) {
-    TableKind kind = !seg->active() ? TableKind::FuncRef
-                                    : env_->tables[seg->tableIndex].kind;
-    switch (kind) {
-      case TableKind::FuncRef:
-        for (uint32_t funcIndex : seg->elemFuncIndices) {
-          if (funcIndex == NullFuncIndex) {
-            continue;
-          }
-          addOrMerge(ExportedFunc(funcIndex, false));
+    // For now, the segments always carry function indices regardless of the
+    // segment's declared element type; this works because the only legal
+    // element types are funcref and anyref and the only legal values are
+    // functions and null.  We always add functions in segments as exported
+    // functions, regardless of the segment's type.  In the future, if we make
+    // the representation of AnyRef segments different, we will have to consider
+    // function values in those segments specially.
+    bool isAsmJS =
+        seg->active() && env_->tables[seg->tableIndex].kind == TableKind::AsmJS;
+    if (!isAsmJS) {
+      for (uint32_t funcIndex : seg->elemFuncIndices) {
+        if (funcIndex == NullFuncIndex) {
+          continue;
         }
-        break;
-      case TableKind::AsmJS:
-        // asm.js functions are not exported.
-        break;
-      case TableKind::AnyRef:
-        break;
+        addOrMerge(ExportedFunc(funcIndex, false));
+      }
     }
   }
 
   auto* newEnd =
       std::remove_if(exportedFuncs.begin(), exportedFuncs.end(),
                      [](const ExportedFunc& exp) { return exp.isInvalid(); });
   exportedFuncs.erase(newEnd, exportedFuncs.end());
 
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -748,17 +748,17 @@ static int32_t PerformWait(Instance* ins
   SharedElemSegment& segRefPtr = instance->passiveElemSegments_[segIndex];
   MOZ_RELEASE_ASSERT(!segRefPtr->active());
 
   // Drop this instance's reference to the ElemSegment so it can be released.
   segRefPtr = nullptr;
   return 0;
 }
 
-void Instance::initElems(uint32_t tableIndex, const ElemSegment& seg,
+bool Instance::initElems(uint32_t tableIndex, const ElemSegment& seg,
                          uint32_t dstOffset, uint32_t srcOffset, uint32_t len) {
   Table& table = *tables_[tableIndex];
   MOZ_ASSERT(dstOffset <= table.length());
   MOZ_ASSERT(len <= table.length() - dstOffset);
 
   Tier tier = code().bestTier();
   const MetadataTier& metadataTier = metadata(tier);
   const FuncImportVector& funcImports = metadataTier.funcImports;
@@ -768,16 +768,23 @@ void Instance::initElems(uint32_t tableI
   MOZ_ASSERT(srcOffset <= elemFuncIndices.length());
   MOZ_ASSERT(len <= elemFuncIndices.length() - srcOffset);
 
   uint8_t* codeBaseTier = codeBase(tier);
   for (uint32_t i = 0; i < len; i++) {
     uint32_t funcIndex = elemFuncIndices[srcOffset + i];
     if (funcIndex == NullFuncIndex) {
       table.setNull(dstOffset + i);
+    } else if (!table.isFunction()) {
+      // Note, fnref must be rooted if we do anything more than just store it.
+      void* fnref = Instance::funcRef(this, funcIndex);
+      if (fnref == AnyRef::invalid().forCompiledCode()) {
+        return false;  // OOM, which has already been reported.
+      }
+      table.fillAnyRef(dstOffset + i, 1, AnyRef::fromCompiledCode(fnref));
     } else {
       if (funcIndex < funcImports.length()) {
         FuncImportTls& import = funcImportTls(funcImports[funcIndex]);
         JSFunction* fun = import.fun;
         if (IsWasmExportedFunction(fun)) {
           // This element is a wasm function imported from another
           // instance. To preserve the === function identity required by
           // the JS embedding spec, we must set the element to the
@@ -796,16 +803,17 @@ void Instance::initElems(uint32_t tableI
           continue;
         }
       }
       void* code = codeBaseTier +
                    codeRanges[funcToCodeRange[funcIndex]].funcTableEntry();
       table.setFuncRef(dstOffset + i, code, this);
     }
   }
+  return true;
 }
 
 /* static */ int32_t Instance::tableInit(Instance* instance, uint32_t dstOffset,
                                          uint32_t srcOffset, uint32_t len,
                                          uint32_t segIndex,
                                          uint32_t tableIndex) {
   MOZ_ASSERT(SASigTableInit.failureMode == FailureMode::FailOnNegI32);
 
@@ -820,20 +828,16 @@ void Instance::initElems(uint32_t tableI
 
   const ElemSegment& seg = *instance->passiveElemSegments_[segIndex];
   MOZ_RELEASE_ASSERT(!seg.active());
   const uint32_t segLen = seg.length();
 
   const Table& table = *instance->tables()[tableIndex];
   const uint32_t tableLen = table.length();
 
-  // Element segments cannot currently contain arbitrary values, and anyref
-  // tables cannot be initialized from segments.
-  MOZ_ASSERT(table.kind() == TableKind::FuncRef);
-
   // We are proposing to copy
   //
   //   seg[ srcOffset .. srcOffset + len - 1 ]
   // to
   //   tableBase[ dstOffset .. dstOffset + len - 1 ]
 
   if (len == 0) {
     // Zero length inits that are out-of-bounds do not trap.
@@ -855,17 +859,19 @@ void Instance::initElems(uint32_t tableI
     uint64_t srcAvail = segLen < srcOffset ? 0 : segLen - srcOffset;
     uint64_t dstAvail = tableLen < dstOffset ? 0 : tableLen - dstOffset;
     MOZ_ASSERT(len > Min(srcAvail, dstAvail));
     len = uint32_t(Min(srcAvail, dstAvail));
     mustTrap = true;
   }
 
   if (len > 0) {
-    instance->initElems(tableIndex, seg, dstOffset, srcOffset, len);
+    if (!instance->initElems(tableIndex, seg, dstOffset, srcOffset, len)) {
+      return -1;  // OOM, which has already been reported.
+    }
   }
 
   if (!mustTrap) {
     return 0;
   }
 
   JS_ReportErrorNumberASCII(TlsContext.get(), GetErrorMessage, nullptr,
                             JSMSG_WASM_OUT_OF_BOUNDS);
--- a/js/src/wasm/WasmInstance.h
+++ b/js/src/wasm/WasmInstance.h
@@ -157,18 +157,19 @@ class Instance {
   // Called by Wasm(Memory|Table)Object when a moving resize occurs:
 
   void onMovingGrowMemory();
   void onMovingGrowTable(const Table* theTable);
 
   // Called to apply a single ElemSegment at a given offset, assuming
   // that all bounds validation has already been performed.
 
-  void initElems(uint32_t tableIndex, const ElemSegment& seg,
-                 uint32_t dstOffset, uint32_t srcOffset, uint32_t len);
+  MOZ_MUST_USE bool initElems(uint32_t tableIndex, const ElemSegment& seg,
+                              uint32_t dstOffset, uint32_t srcOffset,
+                              uint32_t len);
 
   // Debugger support:
 
   JSString* createDisplayURL(JSContext* cx);
   WasmBreakpointSite* getOrCreateBreakpointSite(JSContext* cx, uint32_t offset);
   void destroyBreakpointSite(JSFreeOp* fop, uint32_t offset);
 
   // about:memory reporting:
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -587,17 +587,19 @@ bool Module::initSegments(JSContext* cx,
           fail = true;
           count = 0;
         } else if (tableLength - offset < count) {
           fail = true;
           count = tableLength - offset;
         }
       }
       if (count) {
-        instance.initElems(seg->tableIndex, *seg, offset, 0, count);
+        if (!instance.initElems(seg->tableIndex, *seg, offset, 0, count)) {
+          return false;  // OOM
+        }
       }
       if (fail) {
         JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                                  JSMSG_WASM_BAD_FIT, "elem", "table");
         return false;
       }
     }
   }
--- a/js/src/wasm/WasmOpIter.h
+++ b/js/src/wasm/WasmOpIter.h
@@ -2441,24 +2441,23 @@ inline bool OpIter<Policy>::readMemOrTab
       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 funcref is not
-    // yet a subtype of anyref.
-    if (env_.tables[*dstTableIndex].kind != TableKind::FuncRef) {
-      return fail("only tables of 'funcref' may have element segments");
-    }
     if (*segIndex >= env_.elemSegments.length()) {
       return fail("table.init segment index out of range");
     }
+    if (!checkIsSubtypeOf(env_.elemSegments[*segIndex]->elemType(),
+                          ToElemValType(env_.tables[*dstTableIndex].kind))) {
+      return false;
+    }
   }
 
   return true;
 }
 
 template <typename Policy>
 inline bool OpIter<Policy>::readTableFill(uint32_t* tableIndex, Value* start,
                                           Value* val, Value* len) {
--- a/js/src/wasm/WasmTextToBinary.cpp
+++ b/js/src/wasm/WasmTextToBinary.cpp
@@ -5040,65 +5040,71 @@ static bool ParseTable(WasmParseContext&
     return false;
   }
 
   auto* zero = new (c.lifo) AstConst(LitVal(uint32_t(0)));
   if (!zero) {
     return false;
   }
 
-  AstElemSegment* segment = new (c.lifo) AstElemSegment(
-      AstElemSegmentKind::Active, AstRef(name), zero, std::move(elems));
+  AstElemSegment* segment =
+      new (c.lifo) AstElemSegment(AstElemSegmentKind::Active, AstRef(name),
+                                  zero, ValType::FuncRef, std::move(elems));
   return segment && module->append(segment);
 }
 
-static bool TryParseFuncOrFuncRef(WasmParseContext& c, bool* isFunc) {
+static bool TryParseElemType(WasmParseContext& c, bool* isFunc, ValType* ty) {
   if (c.ts.getIf(WasmToken::Func)) {
     *isFunc = true;
+    *ty = ValType::FuncRef;
     return true;
   }
 
   WasmToken token = c.ts.peek();
   if (token.kind() == WasmToken::ValueType &&
-      token.valueType() == ValType::FuncRef) {
+      (token.valueType() == ValType::FuncRef ||
+       token.valueType() == ValType::AnyRef)) {
     c.ts.get();
     *isFunc = false;
+    *ty = token.valueType();
     return true;
   }
 
   return false;
 }
 
 static AstElemSegment* ParseElemSegment(WasmParseContext& c) {
   // 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)
+  // <elem-type> is funcref or anyref
   //
   // 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
+  //   (elem <table-use> <init-expr> <elem-type>
   //         "(" (ref.func <fnref>|ref.null) ")" ...)
   //
   // Passive initializers:
   //   (elem func <fnref> ...)
-  //   (elem funcref "(" (ref.func <fnref>|ref.null) ")" ...)
+  //   (elem <elem-type> "(" (ref.func <fnref>|ref.null) ")" ...)
   //
   // Forward declaration for ref.func uses:
   //   (elem declared <fnref> ...)
 
   AstRef targetTable = AstRef(0);
   AstExpr* offsetIfActive = nullptr;
+  ValType elemType = ValType::FuncRef;
   bool haveTableref = false;
   AstElemSegmentKind kind;
 
   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;
@@ -5121,28 +5127,28 @@ static AstElemSegment* ParseElemSegment(
   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)) {
+    if (!TryParseElemType(c, &nakedFnrefs, &elemType)) {
       c.ts.generateError(c.ts.peek(),
-                         "'func' or 'funcref' required for elem segment",
+                         "'func' or element type 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.
+  } else if (TryParseElemType(c, &nakedFnrefs, &elemType)) {
+    // 'func' or element type for a passive segment.
     kind = AstElemSegmentKind::Passive;
   } else {
     if ((offsetIfActive = ParseInitializerExpression(c)) == nullptr) {
       c.ts.generateError(c.ts.peek(),
                          "elem segment for table 0 must have offset expression",
                          c.error);
       return nullptr;
     }
@@ -5182,18 +5188,18 @@ static AstElemSegment* ParseElemSegment(
         return nullptr;
       }
       if (!c.ts.match(WasmToken::CloseParen, c.error)) {
         return nullptr;
       }
     }
   }
 
-  return new (c.lifo)
-      AstElemSegment(kind, targetTable, offsetIfActive, std::move(elems));
+  return new (c.lifo) AstElemSegment(kind, targetTable, offsetIfActive,
+                                     elemType, std::move(elems));
 }
 
 static bool ParseGlobal(WasmParseContext& c, AstModule* module) {
   AstName name = c.ts.getIfName();
 
   AstValType type;
   bool isMutable;
 
@@ -7416,48 +7422,61 @@ static bool EncodeDataCountSection(Encod
     return false;
   }
 
   e.finishSection(offset);
   return true;
 }
 
 static bool EncodeElemSegment(Encoder& e, AstElemSegment& segment) {
-  // There are three bits that control the encoding of an element segment for
-  // up to eight possible encodings. We try to select the encoding for an
-  // element segment that takes the least amount of space, which depends on
-  // whether there are null references in the segment.
+  // There are three bits that control the encoding of an element segment for up
+  // to eight possible encodings. We try to select the encoding for an element
+  // segment that takes the least amount of space.  We can use various
+  // compressed encodings if some or all of these are true:
+  //
+  // - the selected element type is FuncRef
+  // - there are no null references in the segment
+  // - the table and initialization indices are both zero
+  //
+  // Choosing the best encoding is tricky because not all encodings can
+  // represent all situations.  For example, if we have a type other than
+  // FuncRef, or a null value, or a table index, then we can't use the legacy
+  // "Active" encoding, compact though it is.
+
   bool hasRefNull = false;
   for (const AstElem& elem : segment.elems()) {
     if (elem.is<AstNullValue>()) {
       hasRefNull = true;
       break;
     }
   }
 
-  // Select the encoding that takes the least amount of space
+  ElemSegmentPayload payload =
+      hasRefNull || segment.elemType() != ValType::FuncRef
+          ? ElemSegmentPayload::ElemExpression
+          : ElemSegmentPayload::ExternIndex;
+
   ElemSegmentKind kind;
   switch (segment.kind()) {
     case AstElemSegmentKind::Active: {
-      kind = segment.targetTable().index()
+      kind = segment.targetTable().index() ||
+                     payload != ElemSegmentPayload::ExternIndex
                  ? 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.
   if (!e.writeVarU32(ElemSegmentFlags(kind, payload).encoded())) {
     return false;
   }
 
   // Write the table index.
   if (kind == ElemSegmentKind::ActiveWithTableIndex &&
@@ -7477,17 +7496,18 @@ static bool EncodeElemSegment(Encoder& e
   }
 
   // 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.
   if (kind != ElemSegmentKind::Active) {
     if (payload == ElemSegmentPayload::ElemExpression &&
-        !e.writeFixedU8(uint8_t(TypeCode::FuncRef))) {
+        !e.writeFixedU8(
+            uint8_t(UnpackTypeCodeType(segment.elemType().packed())))) {
       return false;
     }
     if (payload == ElemSegmentPayload::ExternIndex &&
         !e.writeFixedU8(uint8_t(DefinitionKind::Function))) {
       return false;
     }
   }
 
--- a/js/src/wasm/WasmTypes.cpp
+++ b/js/src/wasm/WasmTypes.cpp
@@ -474,29 +474,33 @@ const uint8_t* Export::deserialize(const
   return cursor;
 }
 
 size_t Export::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const {
   return fieldName_.sizeOfExcludingThis(mallocSizeOf);
 }
 
 size_t ElemSegment::serializedSize() const {
-  return sizeof(tableIndex) + sizeof(offsetIfActive) +
-         SerializedPodVectorSize(elemFuncIndices);
+  return sizeof(kind) + sizeof(tableIndex) + sizeof(elementType) +
+         sizeof(offsetIfActive) + SerializedPodVectorSize(elemFuncIndices);
 }
 
 uint8_t* ElemSegment::serialize(uint8_t* cursor) const {
+  cursor = WriteBytes(cursor, &kind, sizeof(kind));
   cursor = WriteBytes(cursor, &tableIndex, sizeof(tableIndex));
+  cursor = WriteBytes(cursor, &elementType, sizeof(elementType));
   cursor = WriteBytes(cursor, &offsetIfActive, sizeof(offsetIfActive));
   cursor = SerializePodVector(cursor, elemFuncIndices);
   return cursor;
 }
 
 const uint8_t* ElemSegment::deserialize(const uint8_t* cursor) {
-  (cursor = ReadBytes(cursor, &tableIndex, sizeof(tableIndex))) &&
+  (cursor = ReadBytes(cursor, &kind, sizeof(kind))) &&
+      (cursor = ReadBytes(cursor, &tableIndex, sizeof(tableIndex))) &&
+      (cursor = ReadBytes(cursor, &elementType, sizeof(elementType))) &&
       (cursor = ReadBytes(cursor, &offsetIfActive, sizeof(offsetIfActive))) &&
       (cursor = DeserializePodVector(cursor, &elemFuncIndices));
   return cursor;
 }
 
 size_t ElemSegment::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const {
   return elemFuncIndices.sizeOfExcludingThis(mallocSizeOf);
 }
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -1198,25 +1198,28 @@ struct ElemSegment : AtomicRefCounted<El
   enum class Kind {
     Active,
     Passive,
     Declared,
   };
 
   Kind kind;
   uint32_t tableIndex;
+  ValType elementType;
   Maybe<InitExpr> offsetIfActive;
   Uint32Vector elemFuncIndices;  // Element may be NullFuncIndex
 
   bool active() const { return kind == Kind::Active; }
 
   InitExpr offset() const { return *offsetIfActive; }
 
   size_t length() const { return elemFuncIndices.length(); }
 
+  ValType elemType() const { return elementType; }
+
   WASM_DECLARE_SERIALIZABLE(ElemSegment)
 };
 
 // NullFuncIndex represents the case when an element segment (of type funcref)
 // contains a null element.
 constexpr uint32_t NullFuncIndex = UINT32_MAX;
 static_assert(NullFuncIndex > MaxFuncs, "Invariant");
 
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -2358,19 +2358,16 @@ static bool DecodeElemSection(Decoder& d
       uint32_t tableIndex = 0;
       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");
-      }
       seg->tableIndex = tableIndex;
 
       InitExpr offset;
       if (!DecodeInitializerExpression(d, env, ValType::I32, &offset)) {
         return false;
       }
       seg->offsetIfActive.emplace(offset);
     } else {
@@ -2378,45 +2375,91 @@ static bool DecodeElemSection(Decoder& d
       // or declared segments, there really is no table index, and we should
       // never touch the field.
       MOZ_ASSERT(kind == ElemSegmentKind::Passive ||
                  kind == ElemSegmentKind::Declared);
       seg->tableIndex = (uint32_t)-1;
     }
 
     ElemSegmentPayload payload = flags->payload();
+    ValType elemType;
 
     // `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) {
+    if (kind == ElemSegmentKind::Active) {
+      elemType = ValType::FuncRef;
+    } else {
       uint8_t form;
       if (!d.readFixedU8(&form)) {
         return d.fail("expected type or extern kind");
       }
 
       switch (payload) {
         case ElemSegmentPayload::ElemExpression: {
-          if (form != uint8_t(TypeCode::FuncRef)) {
-            return d.fail(
-                "segments with element expressions can only contain function "
-                "references");
+          switch (form) {
+            case uint8_t(TypeCode::FuncRef):
+              // Below we must in principle check every element expression to
+              // ensure that it is a subtype of FuncRef.  However, the only
+              // reference expressions allowed are ref.null and ref.func, and
+              // they both pass that test, and so no additional check is needed
+              // at this time.
+              elemType = ValType::FuncRef;
+              break;
+            case uint8_t(TypeCode::AnyRef):
+              // Ditto, for AnyRef, just even more trivial.
+              elemType = ValType::AnyRef;
+              break;
+            default:
+              return d.fail(
+                  "segments with element expressions can only contain "
+                  "references");
           }
           break;
         }
         case ElemSegmentPayload::ExternIndex: {
           if (form != uint8_t(DefinitionKind::Function)) {
             return d.fail(
                 "segments with extern indices can only contain function "
                 "references");
           }
+          elemType = ValType::FuncRef;
         }
       }
     }
 
+    // Check constraints on the element type.
+    switch (kind) {
+      case ElemSegmentKind::Declared: {
+        if (!(elemType.isReference() &&
+              env->isRefSubtypeOf(elemType, ValType::FuncRef))) {
+          return d.fail(
+              "declared segment's element type must be subtype of funcref");
+        }
+        break;
+      }
+      case ElemSegmentKind::Active:
+      case ElemSegmentKind::ActiveWithTableIndex: {
+        ValType tblElemType = ToElemValType(env->tables[seg->tableIndex].kind);
+        if (!(elemType == tblElemType ||
+              (elemType.isReference() && tblElemType.isReference() &&
+               env->isRefSubtypeOf(elemType, tblElemType)))) {
+          return d.fail(
+              "segment's element type must be subtype of table's element type");
+        }
+        break;
+      }
+      case ElemSegmentKind::Passive: {
+        // By construction, above.
+        MOZ_ASSERT(elemType.isReference());
+        break;
+      }
+    }
+    seg->elementType = elemType;
+
     uint32_t numElems;
     if (!d.readVarU32(&numElems)) {
       return d.fail("expected segment size");
     }
 
     if (numElems > MaxTableInitialLength) {
       return d.fail("too many table elements");
     }