Merge mozilla-central to autoland. a=merge on a CLOSED TREE
authorRazvan Maries <rmaries@mozilla.com>
Fri, 26 Apr 2019 19:05:13 +0300
changeset 530343 b19746e81434e5461b91286e93c0918f49c9fae6
parent 530342 f13945ab88b10a0ab3fbbef21d667d30caebba8a (current diff)
parent 530288 ff3ec9547e4f15209af87010ed39a0590c253efe (diff)
child 530344 aaec576505b20b6f72dbfae8b6e42cb12b3cdfc0
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.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
Merge mozilla-central to autoland. a=merge on a CLOSED TREE
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/tables-fill.js
@@ -0,0 +1,226 @@
+// |jit-test| skip-if: !wasmReftypesEnabled()
+
+let ins
+   = wasmEvalText(
+      `(module
+         (table 8 anyref)     ;; table 0
+         (table $t 10 anyref) ;; table 1
+
+         ;; fill/get for table 0, referenced implicitly
+         (func (export "fill0") (param $i i32) (param $r anyref) (param $n i32)
+           (table.fill (local.get $i) (local.get $r) (local.get $n))
+         )
+         (func (export "get0") (param $i i32) (result anyref)
+           (table.get (local.get $i))
+         )
+
+         ;; fill/get for table 1, referenced explicitly
+         (func (export "fill1") (param $i i32) (param $r anyref) (param $n i32)
+           (table.fill $t (local.get $i) (local.get $r) (local.get $n))
+         )
+         (func (export "get1") (param $i i32) (result anyref)
+           (table.get $t (local.get $i))
+         )
+       )`);
+
+function Obj(n) {
+    this.n = n;
+}
+
+function mkObj(n) {
+    return new Obj(n);
+}
+
+const obj1 = mkObj(1);
+const obj2 = mkObj(2);
+const obj3 = mkObj(3);
+const obj4 = mkObj(4);
+const obj5 = mkObj(5);
+const obj6 = mkObj(6);
+const obj7 = mkObj(7);
+const obj8 = mkObj(8);
+
+// An initial test to ascertain that tables 0 and 1 are independent
+
+// Fill in table 0, then check it.
+assertEq(ins.exports.fill0(2, obj7, 5), undefined)
+assertEq(ins.exports.fill0(1, obj8, 3), undefined);
+
+function check_table0() {
+    assertEq(ins.exports.get0(0), null);
+    assertEq(ins.exports.get0(1), obj8);
+    assertEq(ins.exports.get0(2), obj8);
+    assertEq(ins.exports.get0(3), obj8);
+    assertEq(ins.exports.get0(4), obj7);
+    assertEq(ins.exports.get0(5), obj7);
+    assertEq(ins.exports.get0(6), obj7);
+    assertEq(ins.exports.get0(7), null);
+}
+
+// Check that table 0 has the expected content.
+check_table0();
+
+// Check that messing with table 0 above hasn't changed table 1.
+for (let i = 0; i < 10; i++) {
+    assertEq(ins.exports.get1(i), null);
+}
+
+
+// Now a bunch of tests involving only table 1.
+
+// Within the table
+assertEq(ins.exports.fill1(2, obj1, 3), undefined);
+assertEq(ins.exports.get1(1), null);
+assertEq(ins.exports.get1(2), obj1);
+assertEq(ins.exports.get1(3), obj1);
+assertEq(ins.exports.get1(4), obj1);
+assertEq(ins.exports.get1(5), null);
+
+// Within the table
+assertEq(ins.exports.fill1(4, obj2, 2), undefined);
+assertEq(ins.exports.get1(3), obj1);
+assertEq(ins.exports.get1(4), obj2);
+assertEq(ins.exports.get1(5), obj2);
+assertEq(ins.exports.get1(6), null);
+
+// Within the table
+assertEq(ins.exports.fill1(4, obj3, 0), undefined);
+assertEq(ins.exports.get1(3), obj1);
+assertEq(ins.exports.get1(4), obj2);
+assertEq(ins.exports.get1(5), obj2);
+
+// Within the table
+assertEq(ins.exports.fill1(8, obj4, 2), undefined);
+assertEq(ins.exports.get1(7), null);
+assertEq(ins.exports.get1(8), obj4);
+assertEq(ins.exports.get1(9), obj4);
+
+// Within the table
+assertEq(ins.exports.fill1(9, null, 1), undefined);
+assertEq(ins.exports.get1(8), obj4);
+assertEq(ins.exports.get1(9), null);
+
+// Within the table
+assertEq(ins.exports.fill1(10, obj5, 0), undefined);
+assertEq(ins.exports.get1(9), null);
+
+// Partly outside the table
+assertErrorMessage(() => ins.exports.fill1(8, obj6, 3),
+                   RangeError, /table index out of bounds/);
+
+assertEq(ins.exports.get1(7), null);
+assertEq(ins.exports.get1(8), obj6);
+assertEq(ins.exports.get1(9), obj6);
+
+
+// Boundary tests on table 1: at the edge of the table.
+
+// Length-zero fill1 at the edge of the table must succeed
+assertEq(ins.exports.fill1(10, null, 0), undefined);
+
+// Length-one fill1 at the edge of the table fails
+assertErrorMessage(() => ins.exports.fill1(10, null, 1),
+                   RangeError, /table index out of bounds/);
+
+// Length-more-than-one fill1 at the edge of the table fails
+assertErrorMessage(() => ins.exports.fill1(10, null, 2),
+                   RangeError, /table index out of bounds/);
+
+
+// Boundary tests on table 1: beyond the edge of the table:
+
+// Length-zero fill1 beyond the edge of the table fails
+assertErrorMessage(() => ins.exports.fill1(11, null, 0),
+                   RangeError, /table index out of bounds/);
+
+// Length-one fill1 beyond the edge of the table fails
+assertErrorMessage(() => ins.exports.fill1(11, null, 1),
+                   RangeError, /table index out of bounds/);
+
+// Length-more-than-one fill1 beyond the edge of the table fails
+assertErrorMessage(() => ins.exports.fill1(11, null, 2),
+                   RangeError, /table index out of bounds/);
+
+
+// Type errors.  Required sig is: (i32, anyref, i32) -> void
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (table $t 10 anyref)
+      (func $expected-3-args-got-0
+        (table.fill $t)
+     ))`),
+     WebAssembly.CompileError, /popping value from empty stack/);
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (table $t 10 anyref)
+      (func $expected-3-args-got-1
+        (table.fill $t (i32.const 0))
+     ))`),
+     WebAssembly.CompileError, /popping value from empty stack/);
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (table $t 10 anyref)
+      (func $expected-3-args-got-2
+        (table.fill $t (ref.null) (i32.const 0))
+     ))`),
+     WebAssembly.CompileError, /popping value from empty stack/);
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (table $t 10 anyref)
+      (func $argty-1-wrong
+        (table.fill $t (i32.const 0) (ref.null) (f64.const 0))
+     ))`),
+     WebAssembly.CompileError,
+     /type mismatch: expression has type f64 but expected i32/);
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (table $t 10 anyref)
+      (func $argty-2-wrong
+        (table.fill $t (i32.const 0) (f32.const 0) (i32.const 0))
+     ))`),
+     WebAssembly.CompileError,
+     /type mismatch: expression has type f32 but expected anyref/);
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (table $t 10 anyref)
+      (func $argty-3-wrong
+        (table.fill $t (i64.const 0) (ref.null) (i32.const 0))
+     ))`),
+     WebAssembly.CompileError,
+     /type mismatch: expression has type i64 but expected i32/);
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (table $t 10 anyref)
+      (func $retty-wrong (result i32)
+        (table.fill $t (i32.const 0) (ref.null) (i32.const 0))
+     ))`),
+     WebAssembly.CompileError,
+     /popping value from empty stack/);
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (table $t 0 funcref)
+      (func $tables-of-funcref-not-allowed (param $r anyref)
+        (table.fill $t (i32.const 1) (local.get $r) (i32.const 1))
+     ))`),
+     WebAssembly.CompileError, /table.fill only on tables of anyref/);
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (table $t1 1 anyref)
+      (table $t2 1 funcref)
+      (func $tables-of-funcref-not-allowed-2 (param $r anyref)
+        (table.fill $t2 (i32.const 0) (local.get $r) (i32.const 1))
+     ))`),
+     WebAssembly.CompileError, /table.fill only on tables of anyref/);
+
+
+// Following all the above tests on table 1, check table 0 hasn't changed.
+check_table0();
--- a/js/src/wasm/WasmAST.h
+++ b/js/src/wasm/WasmAST.h
@@ -425,16 +425,17 @@ enum class AstExprKind {
   SetLocal,
 #ifdef ENABLE_WASM_GC
   StructNew,
   StructGet,
   StructSet,
   StructNarrow,
 #endif
 #ifdef ENABLE_WASM_REFTYPES
+  TableFill,
   TableGet,
   TableGrow,
   TableSet,
   TableSize,
 #endif
   TeeLocal,
   Store,
   TernaryOperator,
@@ -914,16 +915,35 @@ class AstMemOrTableInit : public AstExpr
   }
   AstExpr& dst() const { return *dst_; }
   AstExpr& src() const { return *src_; }
   AstExpr& len() const { return *len_; }
 };
 #endif
 
 #ifdef ENABLE_WASM_REFTYPES
+class AstTableFill : public AstExpr {
+  AstRef targetTable_;
+  AstExpr* start_;
+  AstExpr* val_;
+  AstExpr* len_;
+
+ public:
+  static const AstExprKind Kind = AstExprKind::TableFill;
+  explicit AstTableFill(AstRef targetTable,
+                        AstExpr* start, AstExpr* val, AstExpr* len)
+      : AstExpr(Kind, ExprType::Void), targetTable_(targetTable),
+        start_(start), val_(val), len_(len) {}
+
+  AstRef& targetTable() { return targetTable_; }
+  AstExpr& start() const { return *start_; }
+  AstExpr& val() const { return *val_; }
+  AstExpr& len() const { return *len_; }
+};
+
 class AstTableGet : public AstExpr {
   AstRef targetTable_;
   AstExpr* index_;
 
  public:
   static const AstExprKind Kind = AstExprKind::TableGet;
   explicit AstTableGet(AstRef targetTable, AstExpr* index)
       : AstExpr(Kind, ExprType::AnyRef),
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -6845,16 +6845,17 @@ class BaseCompiler final : public BaseCo
   void emitAtomicXchg64(MemoryAccessDesc* access, ValType type,
                         WantResult wantResult);
 #ifdef ENABLE_WASM_BULKMEM_OPS
   MOZ_MUST_USE bool emitMemOrTableCopy(bool isMem);
   MOZ_MUST_USE bool emitDataOrElemDrop(bool isData);
   MOZ_MUST_USE bool emitMemFill();
   MOZ_MUST_USE bool emitMemOrTableInit(bool isMem);
 #endif
+  MOZ_MUST_USE bool emitTableFill();
   MOZ_MUST_USE bool emitTableGet();
   MOZ_MUST_USE bool emitTableGrow();
   MOZ_MUST_USE bool emitTableSet();
   MOZ_MUST_USE bool emitTableSize();
   MOZ_MUST_USE bool emitStructNew();
   MOZ_MUST_USE bool emitStructGet();
   MOZ_MUST_USE bool emitStructSet();
   MOZ_MUST_USE bool emitStructNarrow();
@@ -10328,16 +10329,47 @@ bool BaseCompiler::emitMemOrTableInit(bo
   trap(Trap::ThrowReported);
   masm.bind(&ok);
 
   return true;
 }
 #endif
 
 MOZ_MUST_USE
+bool BaseCompiler::emitTableFill() {
+  uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
+
+  Nothing nothing;
+  uint32_t tableIndex;
+  if (!iter_.readTableFill(&tableIndex, &nothing, &nothing, &nothing)) {
+    return false;
+  }
+
+  if (deadCode_) {
+    return true;
+  }
+
+  // fill(start:u32, val:ref, len:u32, table:u32) -> u32
+  //
+  // Returns -1 on trap, otherwise 0.
+  pushI32(tableIndex);
+  if (!emitInstanceCall(lineOrBytecode, SASigTableFill,
+                        /*pushReturnedValue=*/false)) {
+    return false;
+  }
+
+  Label ok;
+  masm.branchTest32(Assembler::NotSigned, ReturnReg, ReturnReg, &ok);
+  trap(Trap::ThrowReported);
+  masm.bind(&ok);
+
+  return true;
+}
+
+MOZ_MUST_USE
 bool BaseCompiler::emitTableGet() {
   uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
   Nothing index;
   uint32_t tableIndex;
   if (!iter_.readTableGet(&tableIndex, &index)) {
     return false;
   }
   if (deadCode_) {
@@ -11560,16 +11592,18 @@ bool BaseCompiler::emitBody() {
           case uint32_t(MiscOp::TableCopy):
             CHECK_NEXT(emitMemOrTableCopy(/*isMem=*/false));
           case uint32_t(MiscOp::ElemDrop):
             CHECK_NEXT(emitDataOrElemDrop(/*isData=*/false));
           case uint32_t(MiscOp::TableInit):
             CHECK_NEXT(emitMemOrTableInit(/*isMem=*/false));
 #endif  // ENABLE_WASM_BULKMEM_OPS
 #ifdef ENABLE_WASM_REFTYPES
+          case uint32_t(MiscOp::TableFill):
+            CHECK_NEXT(emitTableFill());
           case uint32_t(MiscOp::TableGrow):
             CHECK_NEXT(emitTableGrow());
           case uint32_t(MiscOp::TableSize):
             CHECK_NEXT(emitTableSize());
 #endif
 #ifdef ENABLE_WASM_GC
           case uint32_t(MiscOp::StructNew):
             if (!env_.gcTypesEnabled()) {
--- a/js/src/wasm/WasmBuiltins.cpp
+++ b/js/src/wasm/WasmBuiltins.cpp
@@ -116,16 +116,18 @@ const SymbolicAddressSignature SASigMemI
     SymbolicAddress::MemInit, _I32, 5, {_PTR, _I32, _I32, _I32, _I32, _END}};
 const SymbolicAddressSignature SASigTableCopy = {
     SymbolicAddress::TableCopy,
     _I32,
     6,
     {_PTR, _I32, _I32, _I32, _I32, _I32, _END}};
 const SymbolicAddressSignature SASigElemDrop = {
     SymbolicAddress::ElemDrop, _I32, 2, {_PTR, _I32, _END}};
+const SymbolicAddressSignature SASigTableFill = {
+    SymbolicAddress::TableFill, _I32, 5, {_PTR, _I32, _RoN, _I32, _I32, _END}};
 const SymbolicAddressSignature SASigTableGet = {
     SymbolicAddress::TableGet, _PTR, 3, {_PTR, _I32, _I32, _END}};
 const SymbolicAddressSignature SASigTableGrow = {
     SymbolicAddress::TableGrow, _I32, 4, {_PTR, _RoN, _I32, _I32, _END}};
 const SymbolicAddressSignature SASigTableInit = {
     SymbolicAddress::TableInit,
     _I32,
     6,
@@ -761,16 +763,19 @@ void* wasm::AddressOf(SymbolicAddress im
       *abiType = Args_General5;
       return FuncCast(Instance::memInit, *abiType);
     case SymbolicAddress::TableCopy:
       *abiType = Args_General6;
       return FuncCast(Instance::tableCopy, *abiType);
     case SymbolicAddress::ElemDrop:
       *abiType = Args_General2;
       return FuncCast(Instance::elemDrop, *abiType);
+    case SymbolicAddress::TableFill:
+      *abiType = Args_General5;
+      return FuncCast(Instance::tableFill, *abiType);
     case SymbolicAddress::TableInit:
       *abiType = Args_General6;
       return FuncCast(Instance::tableInit, *abiType);
     case SymbolicAddress::TableGet:
       *abiType = Args_General3;
       return FuncCast(Instance::tableGet, *abiType);
     case SymbolicAddress::TableGrow:
       *abiType = Args_General4;
@@ -890,16 +895,17 @@ bool wasm::NeedsBuiltinThunk(SymbolicAdd
     case SymbolicAddress::CoerceInPlace_JitEntry:
     case SymbolicAddress::ReportInt64JSCall:
     case SymbolicAddress::MemCopy:
     case SymbolicAddress::DataDrop:
     case SymbolicAddress::MemFill:
     case SymbolicAddress::MemInit:
     case SymbolicAddress::TableCopy:
     case SymbolicAddress::ElemDrop:
+    case SymbolicAddress::TableFill:
     case SymbolicAddress::TableGet:
     case SymbolicAddress::TableGrow:
     case SymbolicAddress::TableInit:
     case SymbolicAddress::TableSet:
     case SymbolicAddress::TableSize:
     case SymbolicAddress::PostBarrier:
     case SymbolicAddress::PostBarrierFiltering:
     case SymbolicAddress::StructNew:
--- a/js/src/wasm/WasmBuiltins.h
+++ b/js/src/wasm/WasmBuiltins.h
@@ -53,16 +53,17 @@ extern const SymbolicAddressSignature SA
 extern const SymbolicAddressSignature SASigWaitI64;
 extern const SymbolicAddressSignature SASigWake;
 extern const SymbolicAddressSignature SASigMemCopy;
 extern const SymbolicAddressSignature SASigDataDrop;
 extern const SymbolicAddressSignature SASigMemFill;
 extern const SymbolicAddressSignature SASigMemInit;
 extern const SymbolicAddressSignature SASigTableCopy;
 extern const SymbolicAddressSignature SASigElemDrop;
+extern const SymbolicAddressSignature SASigTableFill;
 extern const SymbolicAddressSignature SASigTableGet;
 extern const SymbolicAddressSignature SASigTableGrow;
 extern const SymbolicAddressSignature SASigTableInit;
 extern const SymbolicAddressSignature SASigTableSet;
 extern const SymbolicAddressSignature SASigTableSize;
 extern const SymbolicAddressSignature SASigPostBarrier;
 extern const SymbolicAddressSignature SASigPostBarrierFiltering;
 extern const SymbolicAddressSignature SASigStructNew;
--- a/js/src/wasm/WasmConstants.h
+++ b/js/src/wasm/WasmConstants.h
@@ -380,17 +380,17 @@ enum class MiscOp {
   MemFill = 0x0b,
   TableInit = 0x0c,
   ElemDrop = 0x0d,
   TableCopy = 0x0e,
 
   // Reftypes, per proposal as of February 2019.
   TableGrow = 0x0f,
   TableSize = 0x10,
-  // TableFill = 0x11, // reserved
+  TableFill = 0x11,
 
   // Structure operations.  Note, these are unofficial.
   StructNew = 0x50,
   StructGet = 0x51,
   StructSet = 0x52,
   StructNarrow = 0x53,
 
   Limit
--- a/js/src/wasm/WasmFrameIter.cpp
+++ b/js/src/wasm/WasmFrameIter.cpp
@@ -1356,16 +1356,18 @@ static const char* ThunkedNativeToDescri
     case SymbolicAddress::DataDrop:
       return "call to native data.drop function";
     case SymbolicAddress::MemFill:
       return "call to native memory.fill function";
     case SymbolicAddress::MemInit:
       return "call to native memory.init function";
     case SymbolicAddress::TableCopy:
       return "call to native table.copy function";
+    case SymbolicAddress::TableFill:
+      return "call to native table.fill function";
     case SymbolicAddress::ElemDrop:
       return "call to native elem.drop function";
     case SymbolicAddress::TableGet:
       return "call to native table.get function";
     case SymbolicAddress::TableGrow:
       return "call to native table.grow function";
     case SymbolicAddress::TableInit:
       return "call to native table.init function";
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -846,16 +846,63 @@ Instance::tableInit(Instance* instance, 
     }
   }
 
   JS_ReportErrorNumberASCII(TlsContext.get(), GetErrorMessage, nullptr,
                             JSMSG_WASM_OUT_OF_BOUNDS);
   return -1;
 }
 
+/* static */ int32_t /* -1 to signal trap; 0 for ok */
+Instance::tableFill(Instance* instance, uint32_t start, void* value,
+                    uint32_t len, uint32_t tableIndex)
+{
+  Table& table = *instance->tables()[tableIndex];
+  MOZ_RELEASE_ASSERT(table.kind() == TableKind::AnyRef);
+
+  if (len == 0) {
+    // Even though the length is zero, we must check for a valid offset.  But
+    // zero-length operations at the edge of the table are allowed.
+    if (start <= table.length()) {
+      return 0;
+    }
+  } else {
+    // Here, we know that |len - 1| cannot underflow.
+
+    bool mustTrap = false;
+
+    // We must write the table until we trap, so we have to deal with
+    // arithmetic overflow in the limit calculation.
+    uint64_t highestOffset = uint64_t(start) + uint64_t(len - 1);
+    if (highestOffset >= table.length()) {
+      // We would write past the end.  Compute what we have space for in the
+      // target and make that the new len.
+      uint64_t avail = table.length() < start ? 0 : table.length() - start;
+      MOZ_ASSERT(len > avail);
+      len = uint32_t(avail);
+      mustTrap = true;
+    }
+
+    for (uint32_t i = 0; i < len; i++) {
+      uint32_t index = start + i;
+      MOZ_ASSERT(index < table.length());
+      table.setAnyRef(index, AnyRef::fromCompiledCode(value));
+    }
+
+    if (!mustTrap) {
+      return 0;
+    }
+  }
+
+  JSContext* cx = TlsContext.get();
+  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+                            JSMSG_WASM_TABLE_OUT_OF_BOUNDS);
+  return -1;
+}
+
 // The return convention for tableGet() is awkward but avoids a situation where
 // Ion code has to hold a value that may or may not be a pointer to GC'd
 // storage, or where Ion has to pass in a pointer to storage where a return
 // value can be written.
 //
 // Note carefully that the pointer that is returned may not be valid past
 // operations that change the size of the table or cause GC work; it is strictly
 // to be used to retrieve the return value.
--- a/js/src/wasm/WasmInstance.h
+++ b/js/src/wasm/WasmInstance.h
@@ -195,16 +195,18 @@ class Instance {
   static int32_t memFill(Instance* instance, uint32_t byteOffset,
                          uint32_t value, uint32_t len);
   static int32_t memInit(Instance* instance, uint32_t dstOffset,
                          uint32_t srcOffset, uint32_t len, uint32_t segIndex);
   static int32_t tableCopy(Instance* instance, uint32_t dstOffset,
                            uint32_t srcOffset, uint32_t len,
                            uint32_t dstTableIndex, uint32_t srcTableIndex);
   static int32_t elemDrop(Instance* instance, uint32_t segIndex);
+  static int32_t tableFill(Instance* instance, uint32_t start, void* value,
+                           uint32_t len, uint32_t tableIndex);
   static void* tableGet(Instance* instance, uint32_t index,
                         uint32_t tableIndex);
   static uint32_t tableGrow(Instance* instance, void* initValue, uint32_t delta,
                             uint32_t tableIndex);
   static int32_t tableSet(Instance* instance, uint32_t index, void* value,
                           uint32_t tableIndex);
   static uint32_t tableSize(Instance* instance, uint32_t tableIndex);
   static int32_t tableInit(Instance* instance, uint32_t dstOffset,
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -3132,16 +3132,70 @@ static bool EmitMemOrTableInit(FunctionC
   return true;
 }
 #endif  // ENABLE_WASM_BULKMEM_OPS
 
 #ifdef ENABLE_WASM_REFTYPES
 // Note, table.{get,grow,set} on table(funcref) are currently rejected by the
 // verifier.
 
+static bool EmitTableFill(FunctionCompiler& f) {
+  uint32_t tableIndex;
+  MDefinition *start, *val, *len;
+  if (!f.iter().readTableFill(&tableIndex, &start, &val, &len)) {
+    return false;
+  }
+
+  if (f.inDeadCode()) {
+    return true;
+  }
+
+  uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+  const SymbolicAddressSignature& callee = SASigTableFill;
+  CallCompileState args;
+  if (!f.passInstance(callee.argTypes[0], &args)) {
+    return false;
+  }
+
+  if (!f.passArg(start, callee.argTypes[1], &args)) {
+    return false;
+  }
+  if (!f.passArg(val, callee.argTypes[2], &args)) {
+    return false;
+  }
+  if (!f.passArg(len, callee.argTypes[3], &args)) {
+    return false;
+  }
+
+  MDefinition* tableIndexArg =
+      f.constant(Int32Value(tableIndex), MIRType::Int32);
+  if (!tableIndexArg) {
+    return false;
+  }
+  if (!f.passArg(tableIndexArg, callee.argTypes[4], &args)) {
+    return false;
+  }
+
+  if (!f.finishCall(&args)) {
+    return false;
+  }
+
+  MDefinition* ret;
+  if (!f.builtinInstanceMethodCall(callee, lineOrBytecode, args, &ret)) {
+    return false;
+  }
+
+  if (!f.checkI32NegativeMeansFailedResult(ret)) {
+    return false;
+  }
+
+  return true;
+}
+
 static bool EmitTableGet(FunctionCompiler& f) {
   uint32_t tableIndex;
   MDefinition* index;
   if (!f.iter().readTableGet(&tableIndex, &index)) {
     return false;
   }
 
   if (f.inDeadCode()) {
@@ -3860,16 +3914,18 @@ static bool EmitBodyExprs(FunctionCompil
           case uint32_t(MiscOp::TableCopy):
             CHECK(EmitMemOrTableCopy(f, /*isMem=*/false));
           case uint32_t(MiscOp::ElemDrop):
             CHECK(EmitDataOrElemDrop(f, /*isData=*/false));
           case uint32_t(MiscOp::TableInit):
             CHECK(EmitMemOrTableInit(f, /*isMem=*/false));
 #endif
 #ifdef ENABLE_WASM_REFTYPES
+          case uint32_t(MiscOp::TableFill):
+            CHECK(EmitTableFill(f));
           case uint32_t(MiscOp::TableGrow):
             CHECK(EmitTableGrow(f));
           case uint32_t(MiscOp::TableSize):
             CHECK(EmitTableSize(f));
 #endif
 #ifdef ENABLE_WASM_GC
           case uint32_t(MiscOp::StructNew):
           case uint32_t(MiscOp::StructGet):
--- a/js/src/wasm/WasmOpIter.cpp
+++ b/js/src/wasm/WasmOpIter.cpp
@@ -291,16 +291,18 @@ OpKind wasm::Classify(OpBytes op) {
         case MiscOp::DataDrop:
         case MiscOp::ElemDrop:
           WASM_BULK_OP(OpKind::DataOrElemDrop);
         case MiscOp::MemFill:
           WASM_BULK_OP(OpKind::MemFill);
         case MiscOp::MemInit:
         case MiscOp::TableInit:
           WASM_BULK_OP(OpKind::MemOrTableInit);
+        case MiscOp::TableFill:
+          WASM_REF_OP(OpKind::TableFill);
         case MiscOp::TableGrow:
           WASM_REF_OP(OpKind::TableGrow);
         case MiscOp::TableSize:
           WASM_REF_OP(OpKind::TableSize);
         case MiscOp::StructNew:
           WASM_GC_OP(OpKind::StructNew);
         case MiscOp::StructGet:
           WASM_GC_OP(OpKind::StructGet);
--- a/js/src/wasm/WasmOpIter.h
+++ b/js/src/wasm/WasmOpIter.h
@@ -163,16 +163,17 @@ enum class OpKind {
   ReplaceLane,
   Swizzle,
   Shuffle,
   Splat,
   MemOrTableCopy,
   DataOrElemDrop,
   MemFill,
   MemOrTableInit,
+  TableFill,
   TableGet,
   TableGrow,
   TableSet,
   TableSize,
   RefNull,
   RefFunc,
   StructNew,
   StructGet,
@@ -461,16 +462,18 @@ class MOZ_STACK_CLASS OpIter : private P
   MOZ_MUST_USE bool readMemOrTableCopy(bool isMem, uint32_t* dstMemOrTableIndex,
                                        Value* dst, uint32_t* srcMemOrTableIndex,
                                        Value* src, Value* len);
   MOZ_MUST_USE bool readDataOrElemDrop(bool isData, uint32_t* segIndex);
   MOZ_MUST_USE bool readMemFill(Value* start, Value* val, Value* len);
   MOZ_MUST_USE bool readMemOrTableInit(bool isMem, uint32_t* segIndex,
                                        uint32_t* dstTableIndex, Value* dst,
                                        Value* src, Value* len);
+  MOZ_MUST_USE bool readTableFill(uint32_t* tableIndex, Value* start,
+                                  Value* val, Value* len);
   MOZ_MUST_USE bool readTableGet(uint32_t* tableIndex, Value* index);
   MOZ_MUST_USE bool readTableGrow(uint32_t* tableIndex, Value* initValue,
                                   Value* delta);
   MOZ_MUST_USE bool readTableSet(uint32_t* tableIndex, Value* index,
                                  Value* value);
   MOZ_MUST_USE bool readTableSize(uint32_t* tableIndex);
   MOZ_MUST_USE bool readStructNew(uint32_t* typeIndex, ValueVector* argValues);
   MOZ_MUST_USE bool readStructGet(uint32_t* typeIndex, uint32_t* fieldIndex,
@@ -1969,16 +1972,48 @@ inline bool OpIter<Policy>::readMemOrTab
       return fail("table.init segment index out of range");
     }
   }
 
   return true;
 }
 
 template <typename Policy>
+inline bool OpIter<Policy>::readTableFill(uint32_t* tableIndex, Value* start,
+                                          Value* val, Value* len) {
+  MOZ_ASSERT(Classify(op_) == OpKind::TableFill);
+
+  if (!popWithType(ValType::I32, len)) {
+    return false;
+  }
+
+  if (!popWithType(ValType::AnyRef, val)) {
+    return false;
+  }
+
+  if (!popWithType(ValType::I32, start)) {
+    return false;
+  }
+
+  if (!readVarU32(tableIndex)) {
+    return false;
+  }
+
+  if (*tableIndex >= env_.tables.length()) {
+    return fail("table index out of range for table.fill");
+  }
+
+  if (env_.tables[*tableIndex].kind != TableKind::AnyRef) {
+    return fail("table.fill only on tables of anyref");
+  }
+
+  return true;
+}
+
+template <typename Policy>
 inline bool OpIter<Policy>::readTableGet(uint32_t* tableIndex, Value* index) {
   MOZ_ASSERT(Classify(op_) == OpKind::TableGet);
 
   if (!popWithType(ValType::I32, index)) {
     return false;
   }
 
   if (!readVarU32(tableIndex)) {
--- a/js/src/wasm/WasmTextToBinary.cpp
+++ b/js/src/wasm/WasmTextToBinary.cpp
@@ -141,16 +141,17 @@ class WasmToken {
     Store,
     Table,
 #ifdef ENABLE_WASM_BULKMEM_OPS
     TableCopy,
     ElemDrop,
     TableInit,
 #endif
 #ifdef ENABLE_WASM_REFTYPES
+    TableFill,
     TableGet,
     TableGrow,
     TableSet,
     TableSize,
 #endif
     TeeLocal,
     TernaryOpcode,
     Text,
@@ -341,16 +342,17 @@ class WasmToken {
       case SetGlobal:
       case SetLocal:
       case Store:
 #ifdef ENABLE_WASM_BULKMEM_OPS
       case TableCopy:
       case TableInit:
 #endif
 #ifdef ENABLE_WASM_REFTYPES
+      case TableFill:
       case TableGet:
       case TableGrow:
       case TableSet:
       case TableSize:
 #endif
       case TeeLocal:
       case TernaryOpcode:
       case UnaryOpcode:
@@ -2255,16 +2257,19 @@ WasmToken WasmTokenStream::next() {
         if (consume(u"copy")) {
           return WasmToken(WasmToken::TableCopy, begin, cur_);
         }
         if (consume(u"init")) {
           return WasmToken(WasmToken::TableInit, begin, cur_);
         }
 #endif
 #ifdef ENABLE_WASM_REFTYPES
+        if (consume(u"fill")) {
+          return WasmToken(WasmToken::TableFill, begin, cur_);
+        }
         if (consume(u"get")) {
           return WasmToken(WasmToken::TableGet, begin, cur_);
         }
         if (consume(u"grow")) {
           return WasmToken(WasmToken::TableGrow, begin, cur_);
         }
         if (consume(u"set")) {
           return WasmToken(WasmToken::TableSet, begin, cur_);
@@ -3786,16 +3791,41 @@ static AstMemOrTableInit* ParseMemOrTabl
   }
 
   return new (c.lifo)
       AstMemOrTableInit(isMem, segIndex, targetMemOrTable, dst, src, len);
 }
 #endif
 
 #ifdef ENABLE_WASM_REFTYPES
+static AstTableFill* ParseTableFill(WasmParseContext& c, bool inParens) {
+  // (table.fill table start val len)
+  // (table.fill start val len)
+
+  AstRef targetTable = AstRef(0);
+  c.ts.getIfRef(&targetTable);
+
+  AstExpr* start = ParseExpr(c, inParens);
+  if (!start) {
+    return nullptr;
+  }
+
+  AstExpr* val = ParseExpr(c, inParens);
+  if (!val) {
+    return nullptr;
+  }
+
+  AstExpr* len = ParseExpr(c, inParens);
+  if (!len) {
+    return nullptr;
+  }
+
+  return new (c.lifo) AstTableFill(targetTable, start, val, len);
+}
+
 static AstTableGet* ParseTableGet(WasmParseContext& c, bool inParens) {
   // (table.get table index)
   // (table.get index)
 
   AstRef targetTable = AstRef(0);
   c.ts.getIfRef(&targetTable);
 
   AstExpr* index = ParseExpr(c, inParens);
@@ -4041,16 +4071,18 @@ static AstExpr* ParseExprBody(WasmParseC
     case WasmToken::TableCopy:
       return ParseMemOrTableCopy(c, inParens, /*isMem=*/false);
     case WasmToken::ElemDrop:
       return ParseDataOrElemDrop(c, /*isData=*/false);
     case WasmToken::TableInit:
       return ParseMemOrTableInit(c, inParens, /*isMem=*/false);
 #endif
 #ifdef ENABLE_WASM_REFTYPES
+    case WasmToken::TableFill:
+      return ParseTableFill(c, inParens);
     case WasmToken::TableGet:
       return ParseTableGet(c, inParens);
     case WasmToken::TableGrow:
       return ParseTableGrow(c, inParens);
     case WasmToken::TableSet:
       return ParseTableSet(c, inParens);
     case WasmToken::TableSize:
       return ParseTableSize(c, inParens);
@@ -5654,16 +5686,21 @@ static bool ResolveMemOrTableInit(Resolv
   return ResolveExpr(r, s.dst()) && ResolveExpr(r, s.src()) &&
          ResolveExpr(r, s.len()) &&
          (s.isMem() ? r.resolveMemory(s.targetMemory())
                     : r.resolveTable(s.targetTable()));
 }
 #endif
 
 #ifdef ENABLE_WASM_REFTYPES
+static bool ResolveTableFill(Resolver& r, AstTableFill& s) {
+  return ResolveExpr(r, s.start()) && ResolveExpr(r, s.val()) &&
+         ResolveExpr(r, s.len()) && r.resolveTable(s.targetTable());
+}
+
 static bool ResolveTableGet(Resolver& r, AstTableGet& s) {
   return ResolveExpr(r, s.index()) && r.resolveTable(s.targetTable());
 }
 
 static bool ResolveTableGrow(Resolver& r, AstTableGrow& s) {
   return ResolveExpr(r, s.delta()) && ResolveExpr(r, s.initValue()) &&
          r.resolveTable(s.targetTable());
 }
@@ -5806,16 +5843,18 @@ static bool ResolveExpr(Resolver& r, Ast
     case AstExprKind::DataOrElemDrop:
       return true;
     case AstExprKind::MemFill:
       return ResolveMemFill(r, expr.as<AstMemFill>());
     case AstExprKind::MemOrTableInit:
       return ResolveMemOrTableInit(r, expr.as<AstMemOrTableInit>());
 #endif
 #ifdef ENABLE_WASM_REFTYPES
+    case AstExprKind::TableFill:
+      return ResolveTableFill(r, expr.as<AstTableFill>());
     case AstExprKind::TableGet:
       return ResolveTableGet(r, expr.as<AstTableGet>());
     case AstExprKind::TableGrow:
       return ResolveTableGrow(r, expr.as<AstTableGrow>());
     case AstExprKind::TableSet:
       return ResolveTableSet(r, expr.as<AstTableSet>());
     case AstExprKind::TableSize:
       return ResolveTableSize(r, expr.as<AstTableSize>());
@@ -6428,16 +6467,22 @@ static bool EncodeMemOrTableInit(Encoder
   return EncodeExpr(e, s.dst()) && EncodeExpr(e, s.src()) &&
          EncodeExpr(e, s.len()) &&
          e.writeOp(s.isMem() ? MiscOp::MemInit : MiscOp::TableInit) &&
          e.writeVarU32(s.segIndex()) && e.writeVarU32(s.target().index());
 }
 #endif
 
 #ifdef ENABLE_WASM_REFTYPES
+static bool EncodeTableFill(Encoder& e, AstTableFill& s) {
+  return EncodeExpr(e, s.start()) && EncodeExpr(e, s.val()) &&
+         EncodeExpr(e, s.len()) && e.writeOp(MiscOp::TableFill) &&
+         e.writeVarU32(s.targetTable().index());
+}
+
 static bool EncodeTableGet(Encoder& e, AstTableGet& s) {
   return EncodeExpr(e, s.index()) && e.writeOp(Op::TableGet) &&
          e.writeVarU32(s.targetTable().index());
 }
 
 static bool EncodeTableGrow(Encoder& e, AstTableGrow& s) {
   return EncodeExpr(e, s.initValue()) && EncodeExpr(e, s.delta()) &&
          e.writeOp(MiscOp::TableGrow) && e.writeVarU32(s.targetTable().index());
@@ -6605,16 +6650,18 @@ static bool EncodeExpr(Encoder& e, AstEx
     case AstExprKind::DataOrElemDrop:
       return EncodeDataOrElemDrop(e, expr.as<AstDataOrElemDrop>());
     case AstExprKind::MemFill:
       return EncodeMemFill(e, expr.as<AstMemFill>());
     case AstExprKind::MemOrTableInit:
       return EncodeMemOrTableInit(e, expr.as<AstMemOrTableInit>());
 #endif
 #ifdef ENABLE_WASM_REFTYPES
+    case AstExprKind::TableFill:
+      return EncodeTableFill(e, expr.as<AstTableFill>());
     case AstExprKind::TableGet:
       return EncodeTableGet(e, expr.as<AstTableGet>());
     case AstExprKind::TableGrow:
       return EncodeTableGrow(e, expr.as<AstTableGrow>());
     case AstExprKind::TableSet:
       return EncodeTableSet(e, expr.as<AstTableSet>());
     case AstExprKind::TableSize:
       return EncodeTableSize(e, expr.as<AstTableSize>());
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -1885,16 +1885,17 @@ enum class SymbolicAddress {
   WaitI64,
   Wake,
   MemCopy,
   DataDrop,
   MemFill,
   MemInit,
   TableCopy,
   ElemDrop,
+  TableFill,
   TableGet,
   TableGrow,
   TableInit,
   TableSet,
   TableSize,
   PostBarrier,
   PostBarrierFiltering,
   StructNew,
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -865,16 +865,21 @@ static bool DecodeFunctionBodyExprs(cons
             uint32_t unusedSegIndex;
             uint32_t unusedTableIndex;
             CHECK(iter.readMemOrTableInit(/*isMem=*/false, &unusedSegIndex,
                                           &unusedTableIndex, &nothing, &nothing,
                                           &nothing));
           }
 #endif
 #ifdef ENABLE_WASM_REFTYPES
+          case uint32_t(MiscOp::TableFill): {
+            uint32_t unusedTableIndex;
+            CHECK(iter.readTableFill(&unusedTableIndex,
+                                     &nothing, &nothing, &nothing));
+          }
           case uint32_t(MiscOp::TableGrow): {
             uint32_t unusedTableIndex;
             CHECK(iter.readTableGrow(&unusedTableIndex, &nothing, &nothing));
           }
           case uint32_t(MiscOp::TableSize): {
             uint32_t unusedTableIndex;
             CHECK(iter.readTableSize(&unusedTableIndex));
           }