Bug 1508559 - Ion instructions for reftypes. r=jseward
authorLars T Hansen <lhansen@mozilla.com>
Tue, 19 Feb 2019 10:06:59 +0100
changeset 519020 96a0237880cf5fb1b77ca04c4a285c90b625a3ed
parent 519019 3098f2ba2d33867e1b6d4a255575b9724f929930
child 519021 0659ccd37ee77462c135b590e39fc8e25b9b71a0
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjseward
bugs1508559
milestone67.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 1508559 - Ion instructions for reftypes. r=jseward Add support to WasmIonCompile for the instructions in the reftypes proposal: ref.null, ref.is_null, table.get, table.set, table.grow, table.size. Also add support for the ref.eq instruction from the gc proposal. Also update the test suite so that we will not ion-compile test cases that use gc features that are not landed here. Note that this patch does not change the compiler-selection behavior: If --wasm-gc is enabled then only the baseline compiler will be used; if --wasm-gc is not enabled then no compiler will recognize these opcodes. Enabling Ion for reftypes content is the subject of subsequent work. Differential Revision: https://phabricator.services.mozilla.com/D20519
js/src/jit-test/tests/wasm/gc/TypedObject.js
js/src/jit-test/tests/wasm/gc/anyref-boxing-struct.js
js/src/jit-test/tests/wasm/gc/ref-global.js
js/src/jit-test/tests/wasm/gc/ref-struct.js
js/src/jit-test/tests/wasm/gc/structs.js
js/src/jit-test/tests/wasm/gc/tables-generalized-struct.js
js/src/jit/CodeGenerator.cpp
js/src/jit/LIR.h
js/src/jit/Lowering.cpp
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/jit/arm/CodeGenerator-arm.cpp
js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
js/src/jit/shared/LIR-shared.h
js/src/jit/x64/Assembler-x64.h
js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
js/src/jit/x86/Assembler-x86.h
js/src/wasm/WasmInstance.cpp
js/src/wasm/WasmIonCompile.cpp
--- a/js/src/jit-test/tests/wasm/gc/TypedObject.js
+++ b/js/src/jit-test/tests/wasm/gc/TypedObject.js
@@ -1,9 +1,9 @@
-// |jit-test| skip-if: !wasmGcEnabled()
+// |jit-test| skip-if: !wasmGcEnabled() || wasmCompileMode() != 'baseline'
 
 // We can read the object fields from JS, and write them if they are mutable.
 
 {
     let ins = wasmEvalText(`(module
                              (gc_feature_opt_in 3)
 
                              (type $p (struct (field f64) (field (mut i32))))
--- a/js/src/jit-test/tests/wasm/gc/anyref-boxing-struct.js
+++ b/js/src/jit-test/tests/wasm/gc/anyref-boxing-struct.js
@@ -1,9 +1,9 @@
-// |jit-test| skip-if: !wasmGcEnabled()
+// |jit-test| skip-if: !wasmGcEnabled() || wasmCompileMode() != 'baseline'
 
 // Moving a JS value through a wasm anyref is a pair of boxing/unboxing
 // conversions that leaves the value unchanged.  There are many cases,
 // along these axes:
 //
 //  - global variables, see anyref-boxing.js
 //  - tables, see anyref-boxing.js
 //  - function parameters and returns, see anyref-boxing.js
--- a/js/src/jit-test/tests/wasm/gc/ref-global.js
+++ b/js/src/jit-test/tests/wasm/gc/ref-global.js
@@ -1,9 +1,9 @@
-// |jit-test| skip-if: !wasmGcEnabled()
+// |jit-test| skip-if: !wasmGcEnabled() || wasmCompileMode() != 'baseline'
 
 // Basic private-to-module functionality.  At the moment all we have is null
 // pointers, not very exciting.
 
 {
     let bin = wasmTextToBinary(
         `(module
           (gc_feature_opt_in 3)
--- a/js/src/jit-test/tests/wasm/gc/ref-struct.js
+++ b/js/src/jit-test/tests/wasm/gc/ref-struct.js
@@ -1,9 +1,9 @@
-// |jit-test| skip-if: !wasmGcEnabled()
+// |jit-test| skip-if: !wasmGcEnabled() || wasmCompileMode() != 'baseline'
 
 // We'll be running some binary-format tests shortly.
 
 load(libdir + "wasm-binary.js");
 
 const v2vSigSection = sigSection([{args:[], ret:VoidCode}]);
 
 function checkInvalid(body, errorMessage) {
--- a/js/src/jit-test/tests/wasm/gc/structs.js
+++ b/js/src/jit-test/tests/wasm/gc/structs.js
@@ -1,9 +1,9 @@
-// |jit-test| skip-if: !wasmGcEnabled()
+// |jit-test| skip-if: !wasmGcEnabled() || wasmCompileMode() != 'baseline'
 
 var conf = getBuildConfiguration();
 
 var bin = wasmTextToBinary(
     `(module
       (gc_feature_opt_in 3)
 
       (table 2 anyfunc)
--- a/js/src/jit-test/tests/wasm/gc/tables-generalized-struct.js
+++ b/js/src/jit-test/tests/wasm/gc/tables-generalized-struct.js
@@ -1,9 +1,9 @@
-// |jit-test| skip-if: !wasmGeneralizedTables() || !wasmGcEnabled()
+// |jit-test| skip-if: !wasmGeneralizedTables() || !wasmGcEnabled() || wasmCompileMode() != 'baseline'
 
 // table.set in bounds with i32 x anyref - works, no value generated
 // table.set with (ref T) - works
 // table.set with null - works
 // table.set out of bounds - fails
 
 {
     let ins = wasmEvalText(
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -5701,17 +5701,19 @@ void CodeGenerator::emitAssertGCThingRes
 
     masm.assumeUnreachable(
         "MIR instruction returned object with unexpected type");
 
     masm.bind(&ok);
   }
 
   // Check that we have a valid GC pointer.
-  if (JitOptions.fullDebugChecks) {
+  // Disable for wasm because we don't have a context on wasm compilation threads
+  // and this needs a context.
+  if (JitOptions.fullDebugChecks && !IsCompilingWasm()) {
     saveVolatile();
     masm.setupUnalignedABICall(temp);
     masm.loadJSContext(temp);
     masm.passABIArg(temp);
     masm.passABIArg(input);
 
     void* callee;
     switch (type) {
@@ -7386,16 +7388,20 @@ void CodeGenerator::visitWasmStoreSlot(L
   }
 }
 
 void CodeGenerator::visitWasmDerivedPointer(LWasmDerivedPointer* ins) {
   masm.movePtr(ToRegister(ins->base()), ToRegister(ins->output()));
   masm.addPtr(Imm32(int32_t(ins->offset())), ToRegister(ins->output()));
 }
 
+void CodeGenerator::visitWasmLoadRef(LWasmLoadRef* lir) {
+  masm.loadPtr(Address(ToRegister(lir->ptr()), 0), ToRegister(lir->output()));
+}
+
 void CodeGenerator::visitWasmStoreRef(LWasmStoreRef* ins) {
   Register tls = ToRegister(ins->tls());
   Register valueAddr = ToRegister(ins->valueAddr());
   Register value = ToRegister(ins->value());
   Register temp = ToRegister(ins->temp());
 
   Label skipPreBarrier;
   wasm::EmitWasmPreBarrierGuard(masm, tls, temp, valueAddr, &skipPreBarrier);
@@ -13681,13 +13687,22 @@ void CodeGenerator::emitIonToWasmCallBas
 
 void CodeGenerator::visitIonToWasmCall(LIonToWasmCall* lir) {
   emitIonToWasmCallBase(lir);
 }
 void CodeGenerator::visitIonToWasmCallV(LIonToWasmCallV* lir) {
   emitIonToWasmCallBase(lir);
 }
 
+void CodeGenerator::visitWasmNullConstant(LWasmNullConstant* lir) {
+  masm.xorPtr(ToRegister(lir->output()), ToRegister(lir->output()));
+}
+
+void CodeGenerator::visitIsNullPointer(LIsNullPointer* lir) {
+  masm.cmpPtrSet(Assembler::Equal, ToRegister(lir->value()), ImmWord(0),
+                 ToRegister(lir->output()));
+}
+
 static_assert(!std::is_polymorphic<CodeGenerator>::value,
               "CodeGenerator should not have any virtual methods");
 
 }  // namespace jit
 }  // namespace js
--- a/js/src/jit/LIR.h
+++ b/js/src/jit/LIR.h
@@ -523,16 +523,17 @@ class LDefinition {
         static_assert(sizeof(bool) <= sizeof(int32_t),
                       "bool doesn't fit in an int32 slot");
         return LDefinition::INT32;
       case MIRType::String:
       case MIRType::Symbol:
       case MIRType::BigInt:
       case MIRType::Object:
       case MIRType::ObjectOrNull:
+      case MIRType::RefOrNull:
         return LDefinition::OBJECT;
       case MIRType::Double:
         return LDefinition::DOUBLE;
       case MIRType::Float32:
         return LDefinition::FLOAT32;
 #if defined(JS_PUNBOX64)
       case MIRType::Value:
         return LDefinition::BOX;
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -1007,21 +1007,22 @@ void LIRGenerator::visitCompare(MCompare
     MOZ_ASSERT(right->type() == MIRType::Boolean);
 
     LCompareB* lir =
         new (alloc()) LCompareB(useBox(left), useRegisterOrConstant(right));
     define(lir, comp);
     return;
   }
 
-  // Compare Int32, Symbol or Object pointers.
+  // Compare Int32, Symbol, Object or Wasm pointers.
   if (comp->isInt32Comparison() ||
       comp->compareType() == MCompare::Compare_UInt32 ||
       comp->compareType() == MCompare::Compare_Object ||
-      comp->compareType() == MCompare::Compare_Symbol) {
+      comp->compareType() == MCompare::Compare_Symbol ||
+      comp->compareType() == MCompare::Compare_RefOrNull) {
     JSOp op = ReorderComparison(comp->jsop(), &left, &right);
     LAllocation lhs = useRegister(left);
     LAllocation rhs;
     if (comp->isInt32Comparison() ||
         comp->compareType() == MCompare::Compare_UInt32) {
       rhs = useAnyOrConstant(right);
     } else {
       rhs = useRegister(right);
@@ -4380,16 +4381,21 @@ void LIRGenerator::visitWasmStoreGlobalC
   }
 }
 
 void LIRGenerator::visitWasmDerivedPointer(MWasmDerivedPointer* ins) {
   LAllocation base = useRegisterAtStart(ins->base());
   define(new (alloc()) LWasmDerivedPointer(base), ins);
 }
 
+void LIRGenerator::visitWasmLoadRef(MWasmLoadRef* ins) {
+  define(new (alloc()) LWasmLoadRef(useRegisterAtStart(ins->getOperand(0))),
+         ins);
+}
+
 void LIRGenerator::visitWasmStoreRef(MWasmStoreRef* ins) {
   LAllocation tls = useRegister(ins->tls());
   LAllocation valueAddr = useFixed(ins->valueAddr(), PreBarrierReg);
   LAllocation value = useRegister(ins->value());
   add(new (alloc()) LWasmStoreRef(tls, valueAddr, value, temp()), ins);
 }
 
 void LIRGenerator::visitWasmParameter(MWasmParameter* ins) {
@@ -4414,17 +4420,17 @@ void LIRGenerator::visitWasmParameter(MW
 #if defined(JS_NUNBOX32)
         LInt64Allocation(LArgument(abi.offsetFromArgBase() + INT64HIGH_OFFSET),
                          LArgument(abi.offsetFromArgBase() + INT64LOW_OFFSET))
 #else
         LInt64Allocation(LArgument(abi.offsetFromArgBase()))
 #endif
     );
   } else {
-    MOZ_ASSERT(IsNumberType(ins->type()));
+    MOZ_ASSERT(IsNumberType(ins->type()) || ins->type() == MIRType::RefOrNull);
     defineFixed(new (alloc()) LWasmParameter, ins,
                 LArgument(abi.offsetFromArgBase()));
   }
 }
 
 void LIRGenerator::visitWasmReturn(MWasmReturn* ins) {
   MDefinition* rval = ins->getOperand(0);
 
@@ -4433,17 +4439,18 @@ void LIRGenerator::visitWasmReturn(MWasm
     return;
   }
 
   LWasmReturn* lir = new (alloc()) LWasmReturn;
   if (rval->type() == MIRType::Float32) {
     lir->setOperand(0, useFixed(rval, ReturnFloat32Reg));
   } else if (rval->type() == MIRType::Double) {
     lir->setOperand(0, useFixed(rval, ReturnDoubleReg));
-  } else if (rval->type() == MIRType::Int32) {
+  } else if (rval->type() == MIRType::Int32 ||
+             rval->type() == MIRType::RefOrNull) {
     lir->setOperand(0, useFixed(rval, ReturnReg));
   } else {
     MOZ_CRASH("Unexpected wasm return type");
   }
 
   add(lir);
 }
 
@@ -4740,16 +4747,25 @@ void LIRGenerator::visitConstant(MConsta
       break;
     default:
       // Constants of special types (undefined, null) should never flow into
       // here directly. Operations blindly consuming them require a Box.
       MOZ_CRASH("unexpected constant type");
   }
 }
 
+void LIRGenerator::visitWasmNullConstant(MWasmNullConstant* ins) {
+  define(new (alloc()) LWasmNullConstant(), ins);
+}
+
+void LIRGenerator::visitIsNullPointer(MIsNullPointer* ins) {
+  define(new (alloc()) LIsNullPointer(
+           useRegisterAtStart(ins->getOperand(0))), ins);
+}
+
 void LIRGenerator::visitWasmFloatConstant(MWasmFloatConstant* ins) {
   switch (ins->type()) {
     case MIRType::Double:
       define(new (alloc()) LDouble(ins->toDouble()), ins);
       break;
     case MIRType::Float32:
       define(new (alloc()) LFloat32(ins->toFloat32()), ins);
       break;
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -1240,16 +1240,20 @@ HashNumber MWasmFloatConstant::valueHash
   return ConstantValueHash(type(), u.bits_);
 }
 
 bool MWasmFloatConstant::congruentTo(const MDefinition* ins) const {
   return ins->isWasmFloatConstant() && type() == ins->type() &&
          u.bits_ == ins->toWasmFloatConstant()->u.bits_;
 }
 
+HashNumber MWasmNullConstant::valueHash() const {
+  return ConstantValueHash(MIRType::Pointer, 0);
+}
+
 #ifdef JS_JITSPEW
 void MControlInstruction::printOpcode(GenericPrinter& out) const {
   MDefinition::printOpcode(out);
   for (size_t j = 0; j < numSuccessors(); j++) {
     if (getSuccessor(j)) {
       out.printf(" block%u", getSuccessor(j)->id());
     } else {
       out.printf(" (null-to-be-patched)");
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -344,20 +344,21 @@ class AliasSet {
     DOMProperty = 1 << 5,     // A DOM property
     FrameArgument = 1 << 6,   // An argument kept on the stack frame
     WasmGlobalVar = 1 << 7,   // An asm.js/wasm private global var
     WasmHeap = 1 << 8,        // An asm.js/wasm heap load
     WasmHeapMeta = 1 << 9,    // The asm.js/wasm heap base pointer and
                               // bounds check limit, in Tls.
     TypedArrayLengthOrOffset = 1 << 10,  // A typed array's length or byteOffset
     WasmGlobalCell = 1 << 11,            // A wasm global cell
-    Last = WasmGlobalCell,
+    WasmTableElement = 1 << 12,          // An element of a wasm table
+    Last = WasmTableElement,
     Any = Last | (Last - 1),
 
-    NumCategories = 12,
+    NumCategories = 13,
 
     // Indicates load or store.
     Store_ = 1 << 31
   };
 
   static_assert((1 << NumCategories) - 1 == Any,
                 "NumCategories must include all flags present in Any");
 
@@ -1543,16 +1544,60 @@ class MConstant : public MNullaryInstruc
   // Convert this constant to a js::Value. Float32 constants will be stored
   // as DoubleValue and NaNs are canonicalized. Callers must be careful: not
   // all constants can be represented by js::Value (wasm supports int64).
   Value toJSValue() const;
 
   bool appendRoots(MRootList& roots) const override;
 };
 
+class MWasmNullConstant : public MNullaryInstruction {
+  explicit MWasmNullConstant() : MNullaryInstruction(classOpcode) {
+    setResultType(MIRType::RefOrNull);
+    setMovable();
+  }
+
+ public:
+  INSTRUCTION_HEADER(WasmNullConstant)
+
+  static MWasmNullConstant* New(TempAllocator& alloc) {
+    return new (alloc) MWasmNullConstant();
+  }
+
+  HashNumber valueHash() const override;
+  bool congruentTo(const MDefinition* ins) const override {
+    return ins->isWasmNullConstant();
+  }
+  AliasSet getAliasSet() const override { return AliasSet::None(); }
+
+  ALLOW_CLONE(MWasmNullConstant)
+};
+
+class MIsNullPointer : public MUnaryInstruction, public NoTypePolicy::Data {
+  explicit MIsNullPointer(MDefinition* value) : MUnaryInstruction(classOpcode,
+                                                                  value) {
+    MOZ_ASSERT(value->type() == MIRType::Pointer);
+    setResultType(MIRType::Boolean);
+    setMovable();
+  }
+ public:
+  INSTRUCTION_HEADER(IsNullPointer);
+
+  static MIsNullPointer* New(TempAllocator& alloc, MDefinition* value) {
+    return new (alloc) MIsNullPointer(value);
+  }
+
+  bool congruentTo(const MDefinition* ins) const override {
+    return congruentIfOperandsEqual(ins);
+  }
+  AliasSet getAliasSet() const override { return AliasSet::None(); }
+
+  ALLOW_CLONE(MIsNullPointer)
+};
+
 // Floating-point value as created by wasm. Just a constant value, used to
 // effectively inhibite all the MIR optimizations. This uses the same LIR nodes
 // as a MConstant of the same type would.
 class MWasmFloatConstant : public MNullaryInstruction {
   union {
     float f32_;
     double f64_;
     uint64_t bits_;
@@ -3131,16 +3176,19 @@ class MCompare : public MBinaryInstructi
     // Double    compared to String
     // Object    compared to String
     // Value     compared to String
     Compare_StrictString,
 
     // Object compared to Object
     Compare_Object,
 
+    // Wasm Ref/AnyRef/NullRef compared to Ref/AnyRef/NullRef
+    Compare_RefOrNull,
+
     // Compare 2 values bitwise
     Compare_Bitwise,
 
     // All other possible compares
     Compare_Unknown
   };
 
  private:
@@ -3165,17 +3213,18 @@ class MCompare : public MBinaryInstructi
     setMovable();
   }
 
   MCompare(MDefinition* left, MDefinition* right, JSOp jsop,
            CompareType compareType)
       : MCompare(left, right, jsop) {
     MOZ_ASSERT(compareType == Compare_Int32 || compareType == Compare_UInt32 ||
                compareType == Compare_Int64 || compareType == Compare_UInt64 ||
-               compareType == Compare_Double || compareType == Compare_Float32);
+               compareType == Compare_Double || compareType == Compare_Float32 ||
+               compareType == Compare_RefOrNull);
     compareType_ = compareType;
     operandMightEmulateUndefined_ = false;
     setResultType(MIRType::Int32);
   }
 
  public:
   INSTRUCTION_HEADER(Compare)
   TRIVIAL_NEW_WRAPPERS
@@ -11780,17 +11829,17 @@ class MWasmAtomicBinopHeap : public MVar
 
 class MWasmLoadGlobalVar : public MUnaryInstruction, public NoTypePolicy::Data {
   MWasmLoadGlobalVar(MIRType type, unsigned globalDataOffset, bool isConstant,
                      MDefinition* tlsPtr)
       : MUnaryInstruction(classOpcode, tlsPtr),
         globalDataOffset_(globalDataOffset),
         isConstant_(isConstant) {
     MOZ_ASSERT(IsNumberType(type) || IsSimdType(type) ||
-               type == MIRType::Pointer);
+               type == MIRType::Pointer || type == MIRType::RefOrNull);
     setResultType(type);
     setMovable();
   }
 
   unsigned globalDataOffset_;
   bool isConstant_;
 
  public:
@@ -11899,23 +11948,48 @@ class MWasmDerivedPointer : public MUnar
 
   bool congruentTo(const MDefinition* ins) const override {
     return congruentIfOperandsEqual(ins);
   }
 
   ALLOW_CLONE(MWasmDerivedPointer)
 };
 
+class MWasmLoadRef : public MUnaryInstruction,
+                     public NoTypePolicy::Data {
+  AliasSet::Flag aliasSet_;
+
+  explicit MWasmLoadRef(MDefinition* valueAddr, AliasSet::Flag aliasSet)
+      : MUnaryInstruction(classOpcode, valueAddr) {
+    MOZ_ASSERT(valueAddr->type() == MIRType::Pointer);
+    setResultType(MIRType::RefOrNull);
+    setMovable();
+  }
+
+ public:
+  INSTRUCTION_HEADER(WasmLoadRef)
+  TRIVIAL_NEW_WRAPPERS
+
+  bool congruentTo(const MDefinition* ins) const override {
+    return congruentIfOperandsEqual(ins);
+  }
+  AliasSet getAliasSet() const override { return AliasSet::Load(aliasSet_); }
+
+  ALLOW_CLONE(MWasmLoadRef)
+};
+
 class MWasmStoreRef : public MAryInstruction<3>,
                       public NoTypePolicy::Data {
   AliasSet::Flag aliasSet_;
 
   MWasmStoreRef(MDefinition* tls, MDefinition* valueAddr, MDefinition* value,
                 AliasSet::Flag aliasSet)
     : MAryInstruction<3>(classOpcode), aliasSet_(aliasSet) {
+    MOZ_ASSERT(valueAddr->type() == MIRType::Pointer);
+    MOZ_ASSERT(value->type() == MIRType::RefOrNull);
     initOperand(0, tls);
     initOperand(1, valueAddr);
     initOperand(2, value);
   }
 
  public:
   INSTRUCTION_HEADER(WasmStoreRef)
   TRIVIAL_NEW_WRAPPERS
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -1777,17 +1777,17 @@ void CodeGenerator::visitAtomicTypedArra
 }
 
 void CodeGenerator::visitWasmSelect(LWasmSelect* ins) {
   MIRType mirType = ins->mir()->type();
 
   Register cond = ToRegister(ins->condExpr());
   masm.as_cmp(cond, Imm8(0));
 
-  if (mirType == MIRType::Int32) {
+  if (mirType == MIRType::Int32 || mirType == MIRType::RefOrNull) {
     Register falseExpr = ToRegister(ins->falseExpr());
     Register out = ToRegister(ins->output());
     MOZ_ASSERT(ToRegister(ins->trueExpr()) == out,
                "true expr input is reused for output");
     masm.ma_mov(falseExpr, out, LeaveCC, Assembler::Zero);
     return;
   }
 
--- a/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
+++ b/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
@@ -2188,17 +2188,17 @@ void CodeGenerator::visitWasmStackArgI64
 }
 
 void CodeGenerator::visitWasmSelect(LWasmSelect* ins) {
   MIRType mirType = ins->mir()->type();
 
   Register cond = ToRegister(ins->condExpr());
   const LAllocation* falseExpr = ins->falseExpr();
 
-  if (mirType == MIRType::Int32) {
+  if (mirType == MIRType::Int32 || mirType == MIRType::RefOrNull) {
     Register out = ToRegister(ins->output());
     MOZ_ASSERT(ToRegister(ins->trueExpr()) == out,
                "true expr input is reused for output");
     masm.as_movz(out, ToRegister(falseExpr), cond);
     return;
   }
 
   FloatRegister out = ToFloatRegister(ins->output());
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -6722,16 +6722,27 @@ class LWasmDerivedPointer : public LInst
   explicit LWasmDerivedPointer(const LAllocation& base)
     : LInstructionHelper(classOpcode) {
     setOperand(0, base);
   }
   const LAllocation* base() { return getOperand(0); }
   size_t offset() { return mirRaw()->toWasmDerivedPointer()->offset(); }
 };
 
+class LWasmLoadRef : public LInstructionHelper<1, 1, 0> {
+ public:
+  LIR_HEADER(WasmLoadRef);
+  explicit LWasmLoadRef(const LAllocation& ptr)
+    : LInstructionHelper(classOpcode) {
+    setOperand(0, ptr);
+  }
+  MWasmLoadRef* mir() const { return mirRaw()->toWasmLoadRef(); }
+  const LAllocation* ptr() { return getOperand(0); }
+};
+
 class LWasmStoreRef : public LInstructionHelper<0, 3, 1> {
  public:
   LIR_HEADER(WasmStoreRef);
   LWasmStoreRef(const LAllocation& tls, const LAllocation& valueAddr,
                 const LAllocation& value, const LDefinition& temp)
     : LInstructionHelper(classOpcode) {
     setOperand(0, tls);
     setOperand(1, valueAddr);
@@ -6805,16 +6816,35 @@ class LWasmStackArgI64 : public LInstruc
   const LInt64Allocation arg() { return getInt64Operand(0); }
 };
 
 inline bool IsWasmCall(LNode::Opcode op) {
   return (op == LNode::Opcode::WasmCall || op == LNode::Opcode::WasmCallVoid ||
           op == LNode::Opcode::WasmCallI64);
 }
 
+class LWasmNullConstant : public LInstructionHelper<1, 0, 0> {
+ public:
+  LIR_HEADER(WasmNullConstant);
+  explicit LWasmNullConstant()
+      : LInstructionHelper(classOpcode) {
+  }
+};
+
+class LIsNullPointer : public LInstructionHelper<1, 1, 0> {
+ public:
+  LIR_HEADER(IsNullPointer);
+  explicit LIsNullPointer(const LAllocation& value)
+    : LInstructionHelper(classOpcode) {
+    setOperand(0, value);
+  }
+  MIsNullPointer* mir() const { return mirRaw()->toIsNullPointer(); }
+  const LAllocation* value() { return getOperand(0); }
+};
+
 template <size_t Defs>
 class LWasmCallBase : public LVariadicInstruction<Defs, 0> {
   using Base = LVariadicInstruction<Defs, 0>;
 
   bool needsBoundsCheck_;
 
  public:
   LWasmCallBase(LNode::Opcode opcode, uint32_t numOperands,
--- a/js/src/jit/x64/Assembler-x64.h
+++ b/js/src/jit/x64/Assembler-x64.h
@@ -955,16 +955,19 @@ class Assembler : public AssemblerX86Sha
         masm.leaq_mr(src.disp(), src.base(), src.index(), src.scale(),
                      dest.encoding());
         break;
       default:
         MOZ_CRASH("unexepcted operand kind");
     }
   }
 
+  void cmovz32(const Operand& src, Register dest) { return cmovzl(src, dest); }
+  void cmovzPtr(const Operand& src, Register dest) { return cmovzq(src, dest); }
+
   CodeOffset loadRipRelativeInt32(Register dest) {
     return CodeOffset(masm.movl_ripr(dest.encoding()).offset());
   }
   CodeOffset loadRipRelativeInt64(Register dest) {
     return CodeOffset(masm.movq_ripr(dest.encoding()).offset());
   }
   CodeOffset loadRipRelativeDouble(FloatRegister dest) {
     return CodeOffset(masm.vmovsd_ripr(dest.encoding()).offset());
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
@@ -302,21 +302,25 @@ void CodeGenerator::visitWasmStackArgI64
 void CodeGenerator::visitWasmSelect(LWasmSelect* ins) {
   MIRType mirType = ins->mir()->type();
 
   Register cond = ToRegister(ins->condExpr());
   Operand falseExpr = ToOperand(ins->falseExpr());
 
   masm.test32(cond, cond);
 
-  if (mirType == MIRType::Int32) {
+  if (mirType == MIRType::Int32 || mirType == MIRType::RefOrNull) {
     Register out = ToRegister(ins->output());
     MOZ_ASSERT(ToRegister(ins->trueExpr()) == out,
                "true expr input is reused for output");
-    masm.cmovzl(falseExpr, out);
+    if (mirType == MIRType::Int32) {
+      masm.cmovz32(falseExpr, out);
+    } else {
+      masm.cmovzPtr(falseExpr, out);
+    }
     return;
   }
 
   FloatRegister out = ToFloatRegister(ins->output());
   MOZ_ASSERT(ToFloatRegister(ins->trueExpr()) == out,
              "true expr input is reused for output");
 
   Label done;
--- a/js/src/jit/x86/Assembler-x86.h
+++ b/js/src/jit/x86/Assembler-x86.h
@@ -347,16 +347,18 @@ class Assembler : public AssemblerX86Sha
   void mov(CodeLabel* label, Register dest) {
     // Put a placeholder value in the instruction stream.
     masm.movl_i32r(0, dest.encoding());
     label->patchAt()->bind(masm.size());
   }
   void mov(Register src, Register dest) { movl(src, dest); }
   void xchg(Register src, Register dest) { xchgl(src, dest); }
   void lea(const Operand& src, Register dest) { return leal(src, dest); }
+  void cmovz32(const Operand& src, Register dest) { return cmovzl(src, dest); }
+  void cmovzPtr(const Operand& src, Register dest) { return cmovzl(src, dest); }
 
   void fstp32(const Operand& src) {
     switch (src.kind()) {
       case Operand::MEM_REG_DISP:
         masm.fstp32_m(src.disp(), src.base());
         break;
       default:
         MOZ_CRASH("unexpected operand kind");
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -842,16 +842,21 @@ Instance::tableInit(Instance* instance, 
     }
   }
 
   JS_ReportErrorNumberASCII(TlsContext.get(), GetErrorMessage, nullptr,
                             JSMSG_WASM_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.
+
 /* static */ void* /* nullptr to signal trap; pointer to table location
                       otherwise */
 Instance::tableGet(Instance* instance, uint32_t index, uint32_t tableIndex) {
   const Table& table = *instance->tables()[tableIndex];
   MOZ_RELEASE_ASSERT(table.kind() == TableKind::AnyRef);
   if (index >= table.length()) {
     JS_ReportErrorNumberASCII(TlsContext.get(), GetErrorMessage, nullptr,
                               JSMSG_WASM_TABLE_OUT_OF_BOUNDS);
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -142,16 +142,17 @@ class FunctionCompiler {
     if (!mirGen_.ensureBallast()) {
       return false;
     }
     if (!newBlock(/* prev */ nullptr, &curBlock_)) {
       return false;
     }
 
     for (ABIArgIter<ValTypeVector> i(args); !i.done(); i++) {
+      MOZ_ASSERT(i.mirType() != MIRType::Pointer);
       MWasmParameter* ins = MWasmParameter::New(alloc(), *i, i.mirType());
       curBlock_->add(ins);
       curBlock_->initSlot(info().localSlot(i.index()), ins);
       if (!mirGen_.ensureBallast()) {
         return false;
       }
     }
 
@@ -175,17 +176,17 @@ class FunctionCompiler {
         case ValType::F32:
           ins = MConstant::New(alloc(), Float32Value(0.f), MIRType::Float32);
           break;
         case ValType::F64:
           ins = MConstant::New(alloc(), DoubleValue(0.0), MIRType::Double);
           break;
         case ValType::Ref:
         case ValType::AnyRef:
-          MOZ_CRASH("ion support for ref/anyref value NYI");
+          ins = MWasmNullConstant::New(alloc());
           break;
         case ValType::NullRef:
           MOZ_CRASH("NullRef not expressible");
       }
 
       curBlock_->add(ins);
       curBlock_->initSlot(info().localSlot(i), ins);
       if (!mirGen_.ensureBallast()) {
@@ -259,16 +260,26 @@ class FunctionCompiler {
     if (inDeadCode()) {
       return nullptr;
     }
     MConstant* constant = MConstant::NewInt64(alloc(), i);
     curBlock_->add(constant);
     return constant;
   }
 
+  MDefinition* nullRefConstant() {
+    if (inDeadCode()) {
+      return nullptr;
+    }
+    // MConstant has a lot of baggage so we don't use that here.
+    MWasmNullConstant* constant = MWasmNullConstant::New(alloc());
+    curBlock_->add(constant);
+    return constant;
+  }
+
   template <class T>
   MDefinition* unary(MDefinition* op) {
     if (inDeadCode()) {
       return nullptr;
     }
     T* ins = T::New(alloc(), op);
     curBlock_->add(ins);
     return ins;
@@ -684,16 +695,48 @@ class FunctionCompiler {
 
     curBlock_->end(MTest::New(alloc(), cond, failBlock, okBlock));
     failBlock->end(
         MWasmTrap::New(alloc(), wasm::Trap::ThrowReported, bytecodeOffset()));
     curBlock_ = okBlock;
     return true;
   }
 
+  bool checkPointerNullMeansFailedResult(MDefinition* value) {
+    if (inDeadCode()) {
+      return true;
+    }
+
+    auto* cond = MIsNullPointer::New(alloc(), value);
+    curBlock_->add(cond);
+
+    MBasicBlock* failBlock;
+    if (!newBlock(curBlock_, &failBlock)) {
+      return false;
+    }
+
+    MBasicBlock* okBlock;
+    if (!newBlock(curBlock_, &okBlock)) {
+      return false;
+    }
+
+    curBlock_->end(MTest::New(alloc(), cond, failBlock, okBlock));
+    failBlock->end(
+        MWasmTrap::New(alloc(), wasm::Trap::ThrowReported, bytecodeOffset()));
+    curBlock_ = okBlock;
+    return true;
+  }
+
+  MDefinition* derefTableElementPointer(MDefinition* base) {
+    MWasmLoadRef* load =
+      MWasmLoadRef::New(alloc(), base, AliasSet::WasmTableElement);
+    curBlock_->add(load);
+    return load;
+  }
+
   MDefinition* load(MDefinition* base, MemoryAccessDesc* access,
                     ValType result) {
     if (inDeadCode()) {
       return nullptr;
     }
 
     MWasmLoadTls* memoryBase = maybeLoadMemoryBase();
     MInstruction* load = nullptr;
@@ -1101,36 +1144,43 @@ class FunctionCompiler {
 
     curBlock_->add(ins);
     *def = ins;
     return true;
   }
 
   bool builtinInstanceMethodCall(SymbolicAddress builtin,
                                  uint32_t lineOrBytecode,
-                                 const CallCompileState& call, ValType ret,
+                                 const CallCompileState& call, MIRType ret,
                                  MDefinition** def) {
     if (inDeadCode()) {
       *def = nullptr;
       return true;
     }
 
     CallSiteDesc desc(lineOrBytecode, CallSiteDesc::Symbolic);
     auto* ins = MWasmCall::NewBuiltinInstanceMethodCall(
-        alloc(), desc, builtin, call.instanceArg_, call.regArgs_,
-        ToMIRType(ret));
+        alloc(), desc, builtin, call.instanceArg_, call.regArgs_, ret);
     if (!ins) {
       return false;
     }
 
     curBlock_->add(ins);
     *def = ins;
     return true;
   }
 
+  bool builtinInstanceMethodCall(SymbolicAddress builtin,
+                                 uint32_t lineOrBytecode,
+                                 const CallCompileState& call, ValType ret,
+                                 MDefinition** def) {
+    return builtinInstanceMethodCall(builtin, lineOrBytecode, call,
+                                     ToMIRType(ret), def);
+  }
+
   /*********************************************** Control flow generation */
 
   inline bool inDeadCode() const { return curBlock_ == nullptr; }
 
   void returnExpr(MDefinition* operand) {
     if (inDeadCode()) {
       return;
     }
@@ -2111,16 +2161,20 @@ static bool EmitGetGlobal(FunctionCompil
       result = f.constant(int64_t(value.i64()));
       break;
     case ValType::F32:
       result = f.constant(value.f32());
       break;
     case ValType::F64:
       result = f.constant(value.f64());
       break;
+    case ValType::AnyRef:
+      MOZ_ASSERT(value.anyref().isNull());
+      result = f.nullRefConstant();
+      break;
     default:
       MOZ_CRASH("unexpected type in EmitGetGlobal");
   }
 
   f.iter().setResult(result);
   return true;
 }
 
@@ -3058,53 +3112,173 @@ static bool EmitMemOrTableInit(FunctionC
     return false;
   }
 
   return true;
 }
 #endif  // ENABLE_WASM_BULKMEM_OPS
 
 #ifdef ENABLE_WASM_GENERALIZED_TABLES
-// About these implementations: table.{get,grow,set} on table(anyfunc) is
-// rejected by the verifier, while table.{get,grow,set} on table(anyref)
-// requires gc_feature_opt_in and will always be handled by the baseline
-// compiler; we should never get here in that case.
-//
-// table.size must however be handled properly here.
+// Note, table.{get,grow,set} on table(anyfunc) are currently rejected by the
+// verifier.
 
 static bool EmitTableGet(FunctionCompiler& f) {
   uint32_t tableIndex;
   MDefinition* index;
   if (!f.iter().readTableGet(&tableIndex, &index)) {
     return false;
   }
 
-  MOZ_CRASH("Should not happen");  // See above
+  if (f.inDeadCode()) {
+    return false;
+  }
+
+  uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+  CallCompileState args;
+  if (!f.passInstance(&args)) {
+    return false;
+  }
+
+  if (!f.passArg(index, ValType::I32, &args)) {
+    return false;
+  }
+
+  MDefinition* tableIndexArg = f.constant(Int32Value(tableIndex),
+                                          MIRType::Int32);
+  if (!tableIndexArg) {
+    return false;
+  }
+  if (!f.passArg(tableIndexArg, ValType::I32, &args)) {
+    return false;
+  }
+
+  if (!f.finishCall(&args)) {
+    return false;
+  }
+
+  // The return value here is either null, denoting an error, or a pointer to an
+  // unmovable location containing a possibly-null ref.
+  MDefinition* result;
+  if (!f.builtinInstanceMethodCall(SymbolicAddress::TableGet, lineOrBytecode,
+                                   args, MIRType::Pointer, &result)) {
+    return false;
+  }
+  if (!f.checkPointerNullMeansFailedResult(result)) {
+    return false;
+  }
+
+  MDefinition* ret = f.derefTableElementPointer(result);
+  if (!ret) {
+    return false;
+  }
+
+  f.iter().setResult(ret);
+  return true;
 }
 
 static bool EmitTableGrow(FunctionCompiler& f) {
   uint32_t tableIndex;
   MDefinition* delta;
   MDefinition* initValue;
   if (!f.iter().readTableGrow(&tableIndex, &delta, &initValue)) {
     return false;
   }
 
-  MOZ_CRASH("Should not happen");  // See above
+  if (f.inDeadCode()) {
+    return false;
+  }
+
+  uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+  CallCompileState args;
+  if (!f.passInstance(&args)) {
+    return false;
+  }
+
+  if (!f.passArg(delta, ValType::I32, &args)) {
+    return false;
+  }
+
+  if (!f.passArg(initValue, ValType::AnyRef, &args)) {
+    return false;
+  }
+
+  MDefinition* tableIndexArg = f.constant(Int32Value(tableIndex),
+                                          MIRType::Int32);
+  if (!tableIndexArg) {
+    return false;
+  }
+  if (!f.passArg(tableIndexArg, ValType::I32, &args)) {
+    return false;
+  }
+
+  if (!f.finishCall(&args)) {
+    return false;
+  }
+
+  MDefinition* ret;
+  if (!f.builtinInstanceMethodCall(SymbolicAddress::TableGrow, lineOrBytecode,
+                                   args, ValType::I32, &ret)) {
+    return false;
+  }
+
+  f.iter().setResult(ret);
+  return true;
 }
 
 static bool EmitTableSet(FunctionCompiler& f) {
   uint32_t tableIndex;
   MDefinition* index;
   MDefinition* value;
   if (!f.iter().readTableSet(&tableIndex, &index, &value)) {
     return false;
   }
 
-  MOZ_CRASH("Should not happen");  // See above
+  if (f.inDeadCode()) {
+    return false;
+  }
+
+  uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+  CallCompileState args;
+  if (!f.passInstance(&args)) {
+    return false;
+  }
+
+  if (!f.passArg(index, ValType::I32, &args)) {
+    return false;
+  }
+
+  if (!f.passArg(value, ValType::AnyRef, &args)) {
+    return false;
+  }
+
+  MDefinition* tableIndexArg = f.constant(Int32Value(tableIndex),
+                                          MIRType::Int32);
+  if (!tableIndexArg) {
+    return false;
+  }
+  if (!f.passArg(tableIndexArg, ValType::I32, &args)) {
+    return false;
+  }
+
+  if (!f.finishCall(&args)) {
+    return false;
+  }
+
+  MDefinition* ret;
+  if (!f.builtinInstanceMethodCall(SymbolicAddress::TableSet, lineOrBytecode,
+                                   args, ValType::I32, &ret)) {
+    return false;
+  }
+  if (!f.checkI32NegativeMeansFailedResult(ret)) {
+    return false;
+  }
+  return true;
 }
 
 static bool EmitTableSize(FunctionCompiler& f) {
   uint32_t tableIndex;
   if (!f.iter().readTableSize(&tableIndex)) {
     return false;
   }
 
@@ -3138,16 +3312,54 @@ static bool EmitTableSize(FunctionCompil
     return false;
   }
 
   f.iter().setResult(ret);
   return true;
 }
 #endif  // ENABLE_WASM_GENERALIZED_TABLES
 
+#ifdef ENABLE_WASM_REFTYPES
+static bool EmitRefNull(FunctionCompiler& f) {
+  if (!f.iter().readRefNull()) {
+    return false;
+  }
+
+  if (f.inDeadCode()) {
+    return false;
+  }
+
+  MDefinition* nullVal = f.nullRefConstant();
+  if (!nullVal) {
+    return false;
+  }
+  f.iter().setResult(nullVal);
+  return true;
+}
+
+static bool EmitRefIsNull(FunctionCompiler& f) {
+  MDefinition* input;
+  if (!f.iter().readConversion(ValType::AnyRef, ValType::I32, &input)) {
+    return false;
+  }
+
+  if (f.inDeadCode()) {
+    return false;
+  }
+
+  MDefinition* nullVal = f.nullRefConstant();
+  if (!nullVal) {
+    return false;
+  }
+  f.iter().setResult(f.compare(input, nullVal, JSOP_EQ,
+                               MCompare::Compare_RefOrNull));
+  return true;
+}
+#endif
+
 static bool EmitBodyExprs(FunctionCompiler& f) {
   if (!f.iter().readFunctionStart(f.funcType().ret())) {
     return false;
   }
 
 #define CHECK(c)          \
   if (!(c)) return false; \
   break
@@ -3578,22 +3790,33 @@ static bool EmitBodyExprs(FunctionCompil
         CHECK(EmitReinterpret(f, ValType::I64, ValType::F64, MIRType::Int64));
       case uint16_t(Op::F32ReinterpretI32):
         CHECK(EmitReinterpret(f, ValType::F32, ValType::I32, MIRType::Float32));
       case uint16_t(Op::F64ReinterpretI64):
         CHECK(EmitReinterpret(f, ValType::F64, ValType::I64, MIRType::Double));
 
 #ifdef ENABLE_WASM_GC
       case uint16_t(Op::RefEq):
+        if (!f.env().gcTypesEnabled()) {
+          return f.iter().unrecognizedOpcode(&op);
+        }
+        CHECK(EmitComparison(f, ValType::AnyRef, JSOP_EQ,
+                             MCompare::Compare_RefOrNull));
 #endif
 #ifdef ENABLE_WASM_REFTYPES
       case uint16_t(Op::RefNull):
+        if (!f.env().gcTypesEnabled()) {
+          return f.iter().unrecognizedOpcode(&op);
+        }
+        CHECK(EmitRefNull(f));
       case uint16_t(Op::RefIsNull):
-        // Not yet supported
-        return f.iter().unrecognizedOpcode(&op);
+        if (!f.env().gcTypesEnabled()) {
+          return f.iter().unrecognizedOpcode(&op);
+        }
+        CHECK(EmitRefIsNull(f));
 #endif
 
       // Sign extensions
       case uint16_t(Op::I32Extend8S):
         CHECK(EmitSignExtend(f, 1, 4));
       case uint16_t(Op::I32Extend16S):
         CHECK(EmitSignExtend(f, 2, 4));
       case uint16_t(Op::I64Extend8S):