Bug 1505774 - Introduce nullref type. r=luke
authorLars T Hansen <lhansen@mozilla.com>
Tue, 06 Nov 2018 14:50:59 +0100
changeset 507189 5219f57277c4b6b76b61d435227bd666afee59ea
parent 507188 f03c9e85b401ed8c488f5e98f90e50451c1cb1cb
child 507190 89e474b0a492ba2748e3bd6d7eb851a33c786a72
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1505774
milestone65.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 1505774 - Introduce nullref type. r=luke Mostly this is straightforward: add NullRef in the various type enums and make sure we handle it everywhere (sometimes it's valid; other times it's a fatal error because it should not appear in that context). Some type calculus code had to move out of WasmOpIter and into ModuleEnvironment in order to be available from the decoder generally. Also, since NullRef is not an expressed type but only the type of null constants in the type checker until we know better, we have to be careful to avoid using it as the value type of null values. The text syntax for a null constant in the s-expression form is "(ref.null)", with the parens required. In the stacky syntax the parens are not required. The encoding of ref.null is now incompatible with our old encoding, so there's a required update to the gc-feature opt in version (to version 2), code tagged with version 1 will no longer run but there's a sensible error message printed for that.
js/src/jit/CodeGenerator.cpp
js/src/jit/MCallOptimize.cpp
js/src/wasm/AsmJS.cpp
js/src/wasm/WasmAST.h
js/src/wasm/WasmBaselineCompile.cpp
js/src/wasm/WasmConstants.h
js/src/wasm/WasmInstance.cpp
js/src/wasm/WasmIonCompile.cpp
js/src/wasm/WasmJS.cpp
js/src/wasm/WasmOpIter.h
js/src/wasm/WasmStubs.cpp
js/src/wasm/WasmTextToBinary.cpp
js/src/wasm/WasmTypes.cpp
js/src/wasm/WasmTypes.h
js/src/wasm/WasmValidate.cpp
js/src/wasm/WasmValidate.h
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -13932,16 +13932,18 @@ CodeGenerator::emitIonToWasmCallBase(LIo
             argMir = ToMIRType(sig.args()[i]);
             break;
           case wasm::ValType::I64:
           case wasm::ValType::Ref:
           case wasm::ValType::AnyRef:
             // Don't forget to trace GC type arguments in TraceJitExitFrames
             // when they're enabled.
             MOZ_CRASH("unexpected argument type when calling from ion to wasm");
+          case wasm::ValType::NullRef:
+            MOZ_CRASH("NullRef not expressible");
         }
 
         ABIArg arg = abi.next(argMir);
         switch (arg.kind()) {
           case ABIArg::GPR:
           case ABIArg::FPU: {
             MOZ_ASSERT(ToAnyRegister(lir->getOperand(i)) == arg.reg());
             stackArgs.infallibleEmplaceBack(wasm::JitCallStackArg());
@@ -13988,16 +13990,18 @@ CodeGenerator::emitIonToWasmCallBase(LIo
         MOZ_ASSERT(ToFloatRegister(lir->output()) == ReturnDoubleReg);
         break;
       case wasm::ExprType::Ref:
       case wasm::ExprType::AnyRef:
       case wasm::ExprType::I64:
         // Don't forget to trace GC type return value in TraceJitExitFrames
         // when they're enabled.
         MOZ_CRASH("unexpected return type when calling from ion to wasm");
+      case wasm::ExprType::NullRef:
+        MOZ_CRASH("NullRef not expressible");
       case wasm::ExprType::Limit:
         MOZ_CRASH("Limit");
     }
 
     bool profilingEnabled = isProfilerInstrumentationEnabled();
     WasmInstanceObject* instObj = lir->mir()->instanceObject();
 
     bool wasmGcEnabled = false;
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -4108,16 +4108,18 @@ IonBuilder::inlineWasmCall(CallInfo& cal
             break;
           case wasm::ValType::F64:
             conversion = MToDouble::New(alloc(), arg);
             break;
           case wasm::ValType::I64:
           case wasm::ValType::AnyRef:
           case wasm::ValType::Ref:
             MOZ_CRASH("impossible per above check");
+          case wasm::ValType::NullRef:
+            MOZ_CRASH("NullRef not expressible");
         }
 
         current->add(conversion);
         call->initArg(i, conversion);
     }
 
     current->push(call);
     current->add(call);
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -6388,16 +6388,17 @@ ValidateGlobalVariable(JSContext* cx, co
             double d;
             if (!ToNumber(cx, v, &d)) {
                 return false;
             }
             val->emplace(d);
             return true;
           }
           case ValType::Ref:
+          case ValType::NullRef:
           case ValType::AnyRef: {
             MOZ_CRASH("not available in asm.js");
           }
         }
       }
     }
 
     MOZ_CRASH("unreachable");
--- a/js/src/wasm/WasmAST.h
+++ b/js/src/wasm/WasmAST.h
@@ -1782,25 +1782,21 @@ class AstExtraConversionOperator final :
     {}
 
     MiscOp op() const { return op_; }
     AstExpr* operand() const { return operand_; }
 };
 
 class AstRefNull final : public AstExpr
 {
-    AstValType refType_;
   public:
     static const AstExprKind Kind = AstExprKind::RefNull;
-    explicit AstRefNull(AstValType refType)
-      : AstExpr(Kind, ExprType::Limit), refType_(refType)
+    AstRefNull()
+      : AstExpr(Kind, ExprType::Limit)
     {}
-    AstValType& baseType() {
-        return refType_;
-    }
 };
 
 // This is an artificial AST node which can fill operand slots in an AST
 // constructed from parsing or decoding stack-machine code that doesn't have
 // an inherent AST structure.
 class AstPop final : public AstExpr
 {
   public:
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -1082,16 +1082,17 @@ BaseLocalIter::settle()
           case ValType::I64:
           case ValType::F32:
           case ValType::F64:
           case ValType::Ref:
           case ValType::AnyRef:
             mirType_ = ToMIRType(locals_[index_]);
             frameOffset_ = pushLocal(MIRTypeToSize(mirType_));
             break;
+          case ValType::NullRef:
           default:
             MOZ_CRASH("Compiler bug: Unexpected local type");
         }
         return;
     }
 
     done_ = true;
 }
@@ -2200,32 +2201,34 @@ class BaseCompiler final : public BaseCo
         switch (type.code()) {
           case ExprType::I32:
             needI32(joinRegI32_);
             break;
           case ExprType::I64:
             needI64(joinRegI64_);
             break;
           case ExprType::AnyRef:
+          case ExprType::NullRef:
           case ExprType::Ref:
             needRef(joinRegPtr_);
             break;
           default:;
         }
     }
 
     void maybeUnreserveJoinRegI(ExprType type) {
         switch (type.code()) {
           case ExprType::I32:
             freeI32(joinRegI32_);
             break;
           case ExprType::I64:
             freeI64(joinRegI64_);
             break;
           case ExprType::AnyRef:
+          case ExprType::NullRef:
           case ExprType::Ref:
             freeRef(joinRegPtr_);
             break;
           default:;
         }
     }
 
     void maybeReserveJoinReg(ExprType type) {
@@ -2238,16 +2241,17 @@ class BaseCompiler final : public BaseCo
             break;
           case ExprType::F32:
             needF32(joinRegF32_);
             break;
           case ExprType::F64:
             needF64(joinRegF64_);
             break;
           case ExprType::Ref:
+          case ExprType::NullRef:
           case ExprType::AnyRef:
             needRef(joinRegPtr_);
             break;
           default:
             break;
         }
     }
 
@@ -2261,16 +2265,17 @@ class BaseCompiler final : public BaseCo
             break;
           case ExprType::F32:
             freeF32(joinRegF32_);
             break;
           case ExprType::F64:
             freeF64(joinRegF64_);
             break;
           case ExprType::Ref:
+          case ExprType::NullRef:
           case ExprType::AnyRef:
             freeRef(joinRegPtr_);
             break;
           default:
             break;
         }
     }
 
@@ -3228,16 +3233,17 @@ class BaseCompiler final : public BaseCo
           }
           case ExprType::F32: {
             DebugOnly<Stk::Kind> k(stk_.back().kind());
             MOZ_ASSERT(k == Stk::RegisterF32 || k == Stk::ConstF32 || k == Stk::MemF32 ||
                        k == Stk::LocalF32);
             return Some(AnyReg(popF32(joinRegF32_)));
           }
           case ExprType::Ref:
+          case ExprType::NullRef:
           case ExprType::AnyRef: {
             DebugOnly<Stk::Kind> k(stk_.back().kind());
             MOZ_ASSERT(k == Stk::RegisterRef || k == Stk::ConstRef || k == Stk::MemRef ||
                        k == Stk::LocalRef);
             return Some(AnyReg(popRef(joinRegPtr_)));
           }
           default: {
             MOZ_CRASH("Compiler bug: unexpected expression type");
@@ -3265,16 +3271,17 @@ class BaseCompiler final : public BaseCo
             MOZ_ASSERT(isAvailableF32(joinRegF32_));
             needF32(joinRegF32_);
             return Some(AnyReg(joinRegF32_));
           case ExprType::F64:
             MOZ_ASSERT(isAvailableF64(joinRegF64_));
             needF64(joinRegF64_);
             return Some(AnyReg(joinRegF64_));
           case ExprType::Ref:
+          case ExprType::NullRef:
           case ExprType::AnyRef:
             MOZ_ASSERT(isAvailableRef(joinRegPtr_));
             needRef(joinRegPtr_);
             return Some(AnyReg(joinRegPtr_));
           case ExprType::Void:
             return Nothing();
           default:
             MOZ_CRASH("Compiler bug: unexpected type");
@@ -3561,16 +3568,17 @@ class BaseCompiler final : public BaseCo
             break;
           case ExprType::F32:
             masm.storeFloat32(RegF32(ReturnFloat32Reg), resultsAddress);
             break;
           case ExprType::Ref:
           case ExprType::AnyRef:
             masm.storePtr(RegPtr(ReturnReg), resultsAddress);
             break;
+          case ExprType::NullRef:
           default:
             MOZ_CRASH("Function return type");
         }
     }
 
     void restoreResult() {
         MOZ_ASSERT(env_.debugEnabled());
         size_t debugFrameOffset = masm.framePushed() - DebugFrame::offsetOfFrame();
@@ -3589,16 +3597,17 @@ class BaseCompiler final : public BaseCo
             break;
           case ExprType::F32:
             masm.loadFloat32(resultsAddress, RegF32(ReturnFloat32Reg));
             break;
           case ExprType::Ref:
           case ExprType::AnyRef:
             masm.loadPtr(resultsAddress, RegPtr(ReturnReg));
             break;
+          case ExprType::NullRef:
           default:
             MOZ_CRASH("Function return type");
         }
     }
 
     bool endFunction() {
         // Always branch to returnLabel_.
         masm.breakpoint();
@@ -3879,16 +3888,18 @@ class BaseCompiler final : public BaseCo
                 ScratchPtr scratch(*this);
                 loadRef(arg, scratch);
                 masm.storePtr(scratch, Address(masm.getStackPointer(), argLoc.offsetFromArgBase()));
             } else {
                 loadRef(arg, RegPtr(argLoc.gpr()));
             }
             break;
           }
+          case ValType::NullRef:
+            MOZ_CRASH("NullRef not expressible");
           default:
             MOZ_CRASH("Function argument type");
         }
     }
 
     void callDefinition(uint32_t funcIndex, const FunctionCall& call)
     {
         CallSiteDesc desc(call.lineOrBytecode, CallSiteDesc::Func);
@@ -7441,17 +7452,17 @@ BaseCompiler::sniffConditionalControlCmp
     // reserved join register and the lhs and rhs operands require six, but we
     // only have five.
     if (operandType == ValType::I64) {
         return false;
     }
 #endif
 
     // No optimization for pointer compares yet.
-    if (operandType.isRefOrAnyRef()) {
+    if (operandType.isReference()) {
         return false;
     }
 
     OpBytes op;
     iter_.peekOp(&op);
     switch (op.b0) {
       case uint16_t(Op::BrIf):
       case uint16_t(Op::If):
@@ -8061,16 +8072,17 @@ BaseCompiler::doReturn(ExprType type, bo
       }
       case ExprType::F32: {
         RegF32 rv = popF32(RegF32(ReturnFloat32Reg));
         returnCleanup(popStack);
         freeF32(rv);
         break;
       }
       case ExprType::Ref:
+      case ExprType::NullRef:
       case ExprType::AnyRef: {
         RegPtr rv = popRef(RegPtr(ReturnReg));
         returnCleanup(popStack);
         freeRef(rv);
         break;
       }
       default: {
         MOZ_CRASH("Function return type");
@@ -8140,16 +8152,18 @@ BaseCompiler::pushReturnedIfNonVoid(cons
         break;
       }
       case ExprType::Ref:
       case ExprType::AnyRef: {
         RegPtr rv = captureReturnedRef();
         pushRef(rv);
         break;
       }
+      case ExprType::NullRef:
+        MOZ_CRASH("NullRef not expressible");
       default:
         MOZ_CRASH("Function return type");
     }
 }
 
 // For now, always sync() at the beginning of the call to easily save live
 // values.
 //
@@ -8486,16 +8500,17 @@ BaseCompiler::emitGetLocal()
         break;
       case ValType::F32:
         pushLocalF32(slot);
         break;
       case ValType::Ref:
       case ValType::AnyRef:
         pushLocalRef(slot);
         break;
+      case ValType::NullRef:
       default:
         MOZ_CRASH("Local variable type");
     }
 
     return true;
 }
 
 template<bool isSetLocal>
@@ -8559,16 +8574,17 @@ BaseCompiler::emitSetOrTeeLocal(uint32_t
         fr.storeLocalPtr(rv, localFromSlot(slot, MIRType::Pointer));
         if (isSetLocal) {
             freeRef(rv);
         } else {
             pushRef(rv);
         }
         break;
       }
+      case ValType::NullRef:
       default:
         MOZ_CRASH("Local variable type");
     }
 
     return true;
 }
 
 bool
@@ -8618,16 +8634,17 @@ BaseCompiler::emitGetGlobal()
             break;
           case ValType::F32:
             pushF32(value.f32());
             break;
           case ValType::F64:
             pushF64(value.f64());
             break;
           case ValType::Ref:
+          case ValType::NullRef:
           case ValType::AnyRef:
             pushRef(intptr_t(value.ptr()));
             break;
           default:
             MOZ_CRASH("Global constant type");
         }
         return true;
     }
@@ -8664,16 +8681,18 @@ BaseCompiler::emitGetGlobal()
       case ValType::Ref:
       case ValType::AnyRef: {
         RegPtr rv = needRef();
         ScratchI32 tmp(*this);
         masm.loadPtr(addressOfGlobalVar(global, tmp), rv);
         pushRef(rv);
         break;
       }
+      case ValType::NullRef:
+        MOZ_CRASH("NullRef not expressible");
       default:
         MOZ_CRASH("Global variable type");
         break;
     }
     return true;
 }
 
 bool
@@ -8728,16 +8747,18 @@ BaseCompiler::emitSetGlobal()
             ScratchI32 tmp(*this);
             masm.computeEffectiveAddress(addressOfGlobalVar(global, tmp), valueAddr);
         }
         RegPtr rv = popRef();
         emitBarrieredStore(Nothing(), valueAddr, rv); // Consumes valueAddr
         freeRef(rv);
         break;
       }
+      case ValType::NullRef:
+        MOZ_CRASH("NullRef not expressible");
       default:
         MOZ_CRASH("Global variable type");
         break;
     }
     return true;
 }
 
 // Bounds check elimination.
@@ -9126,16 +9147,17 @@ BaseCompiler::emitSelect()
         emitBranchPerform(&b);
         moveF64(rs, r);
         masm.bind(&done);
         freeF64(rs);
         pushF64(r);
         break;
       }
       case ValType::Ref:
+      case ValType::NullRef:
       case ValType::AnyRef: {
         RegPtr r, rs;
         pop2xRef(&r, &rs);
         emitBranchPerform(&b);
         moveRef(rs, r);
         masm.bind(&done);
         freeRef(rs);
         pushRef(r);
@@ -9328,18 +9350,17 @@ BaseCompiler::emitCurrentMemory()
     // infallible
     emitInstanceCall(lineOrBytecode, SigP_, ExprType::I32, SymbolicAddress::CurrentMemory);
     return true;
 }
 
 bool
 BaseCompiler::emitRefNull()
 {
-    ValType type;
-    if (!iter_.readRefNull(&type)) {
+    if (!iter_.readRefNull()) {
         return false;
     }
 
     if (deadCode_) {
         return true;
     }
 
     pushRef(NULLREF_VALUE);
@@ -10037,16 +10058,18 @@ BaseCompiler::emitStructNew()
             popRef(rp);                 // Restore rp
             if (!structType.isInline_) {
                 masm.loadPtr(Address(rp, OutlineTypedObject::offsetOfData()), rdata);
             }
 
             masm.bind(&skipBarrier);
             break;
           }
+          case ValType::NullRef:
+            MOZ_CRASH("NullRef not expressible");
           default: {
             MOZ_CRASH("Unexpected field type");
           }
         }
     }
 
     if (!structType.isInline_) {
         freeRef(rdata);
@@ -10112,18 +10135,21 @@ BaseCompiler::emitStructGet()
       }
       case ValType::Ref:
       case ValType::AnyRef: {
           RegPtr r = needRef();
           masm.loadPtr(Address(rp, offs), r);
           pushRef(r);
           break;
       }
+      case ValType::NullRef: {
+        MOZ_CRASH("NullRef not expressible");
+      }
       default: {
-          MOZ_CRASH("Unexpected field type");
+        MOZ_CRASH("Unexpected field type");
       }
     }
 
     freeRef(rp);
 
     return true;
 }
 
@@ -10147,17 +10173,17 @@ BaseCompiler::emitStructSet()
     RegI64 rl;
     RegF32 rf;
     RegF64 rd;
     RegPtr rr;
 
     // Reserve this register early if we will need it so that it is not taken by
     // rr or rp.
     RegPtr valueAddr;
-    if (structType.fields_[fieldIndex].type.isRefOrAnyRef()) {
+    if (structType.fields_[fieldIndex].type.isReference()) {
         valueAddr = RegPtr(PreBarrierReg);
         needRef(valueAddr);
     }
 
     switch (structType.fields_[fieldIndex].type.code()) {
       case ValType::I32:
         ri = popI32();
         break;
@@ -10169,16 +10195,18 @@ BaseCompiler::emitStructSet()
         break;
       case ValType::F64:
         rd = popF64();
         break;
       case ValType::Ref:
       case ValType::AnyRef:
         rr = popRef();
         break;
+      case ValType::NullRef:
+        MOZ_CRASH("NullRef not expressible");
       default:
         MOZ_CRASH("Unexpected field type");
     }
 
     RegPtr rp = popRef();
 
     Label ok;
     masm.branchTestPtr(Assembler::NonZero, rp, rp, &ok);
@@ -10207,21 +10235,25 @@ BaseCompiler::emitStructSet()
         break;
       }
       case ValType::F64: {
         masm.storeDouble(rd, Address(rp, offs));
         freeF64(rd);
         break;
       }
       case ValType::Ref:
-      case ValType::AnyRef:
+      case ValType::AnyRef: {
         masm.computeEffectiveAddress(Address(rp, offs), valueAddr);
         emitBarrieredStore(Some(rp), valueAddr, rr);// Consumes valueAddr
         freeRef(rr);
         break;
+      }
+      case ValType::NullRef: {
+        MOZ_CRASH("NullRef not expressible");
+      }
       default: {
         MOZ_CRASH("Unexpected field type");
       }
     }
 
     freeRef(rp);
 
     return true;
--- a/js/src/wasm/WasmConstants.h
+++ b/js/src/wasm/WasmConstants.h
@@ -62,16 +62,19 @@ enum class TypeCode
     Func                                 = 0x60,  // SLEB128(-0x20)
 
     // Type constructor for structure types - unofficial
     Struct                               = 0x50,  // SLEB128(-0x30)
 
     // Special code representing the block signature ()->()
     BlockVoid                            = 0x40,  // SLEB128(-0x40)
 
+    // Type designator for null - unofficial, will not appear in the binary format
+    NullRef                              = 0x39,
+
     Limit                                = 0x80
 };
 
 enum class FuncTypeIdDescKind
 {
     None,
     Immediate,
     Global
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -135,16 +135,18 @@ Instance::callImport(JSContext* cx, uint
             break;
           case ValType::Ref:
           case ValType::AnyRef: {
             args[i].set(ObjectOrNullValue(*(JSObject**)&argv[i]));
             break;
           }
           case ValType::I64:
             MOZ_CRASH("unhandled type in callImport");
+          case ValType::NullRef:
+            MOZ_CRASH("NullRef not expressible");
         }
     }
 
     FuncImportTls& import = funcImportTls(fi);
     RootedFunction importFun(cx, import.fun);
     MOZ_ASSERT(cx->realm() == importFun->realm());
 
     RootedValue fval(cx, ObjectValue(*importFun));
@@ -199,22 +201,23 @@ Instance::callImport(JSContext* cx, uint
     }
 
     const ValTypeVector& importArgs = fi.funcType().args();
 
     size_t numKnownArgs = Min(importArgs.length(), importFun->nargs());
     for (uint32_t i = 0; i < numKnownArgs; i++) {
         TypeSet::Type type = TypeSet::UnknownType();
         switch (importArgs[i].code()) {
-          case ValType::I32:    type = TypeSet::Int32Type(); break;
-          case ValType::F32:    type = TypeSet::DoubleType(); break;
-          case ValType::F64:    type = TypeSet::DoubleType(); break;
-          case ValType::Ref:    MOZ_CRASH("case guarded above");
-          case ValType::AnyRef: MOZ_CRASH("case guarded above");
-          case ValType::I64:    MOZ_CRASH("NYI");
+          case ValType::I32:     type = TypeSet::Int32Type(); break;
+          case ValType::F32:     type = TypeSet::DoubleType(); break;
+          case ValType::F64:     type = TypeSet::DoubleType(); break;
+          case ValType::Ref:     MOZ_CRASH("case guarded above");
+          case ValType::AnyRef:  MOZ_CRASH("case guarded above");
+          case ValType::I64:     MOZ_CRASH("NYI");
+          case ValType::NullRef: MOZ_CRASH("NullRef not expressible");
         }
         if (!TypeScript::ArgTypes(script, i)->hasType(type)) {
             return true;
         }
     }
 
     // These arguments will be filled with undefined at runtime by the
     // arguments rectifier: check that the imported function can handle
@@ -1088,17 +1091,17 @@ Instance::tracePrivate(JSTracer* trc)
     }
 
     for (const SharedTable& table : tables_) {
         table->trace(trc);
     }
 
     for (const GlobalDesc& global : code().metadata().globals) {
         // Indirect anyref global get traced by the owning WebAssembly.Global.
-        if (!global.type().isRefOrAnyRef() || global.isConstant() || global.isIndirect()) {
+        if (!global.type().isReference() || global.isConstant() || global.isIndirect()) {
             continue;
         }
         GCPtrObject* obj = (GCPtrObject*)(globalData() + global.offset());
         TraceNullableEdge(trc, obj, "wasm ref/anyref global");
     }
 
     TraceNullableEdge(trc, &memory_, "wasm buffer");
     structTypeDescrs_.trace(trc);
@@ -1199,16 +1202,19 @@ Instance::callExport(JSContext* cx, uint
             break;
           case ValType::Ref:
           case ValType::AnyRef: {
             if (!ToRef(cx, v, &exportArgs[i])) {
                 return false;
             }
             break;
           }
+          case ValType::NullRef: {
+            MOZ_CRASH("NullRef not expressible");
+          }
         }
     }
 
     {
         JitActivation activation(cx);
 
         void* callee;
         if (func.hasEagerStubs()) {
@@ -1256,16 +1262,18 @@ Instance::callExport(JSContext* cx, uint
       case ExprType::F64:
         args.rval().set(NumberValue(*(double*)retAddr));
         break;
       case ExprType::Ref:
       case ExprType::AnyRef:
         retObj = *(JSObject**)retAddr;
         expectsObject = true;
         break;
+      case ExprType::NullRef:
+        MOZ_CRASH("NullRef not expressible");
       case ExprType::Limit:
         MOZ_CRASH("Limit");
     }
 
     if (expectsObject) {
         args.rval().set(ObjectOrNullValue(retObj));
     } else if (retObj) {
         args.rval().set(ObjectValue(*retObj));
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -223,16 +223,18 @@ class FunctionCompiler
                 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");
                 break;
+              case ValType::NullRef:
+                MOZ_CRASH("NullRef not expressible");
             }
 
             curBlock_->add(ins);
             curBlock_->initSlot(info().localSlot(i), ins);
             if (!mirGen_.ensureBallast()) {
                 return false;
             }
         }
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -155,28 +155,29 @@ ToWebAssemblyValue(JSContext* cx, ValTyp
         if (!ToNumber(cx, v, &d)) {
             return false;
         }
         val.set(Val(d));
         return true;
       }
       case ValType::AnyRef: {
         if (v.isNull()) {
-            val.set(Val(nullptr));
+            val.set(Val(targetType, nullptr));
         } else {
             JSObject* obj = ToObject(cx, v);
             if (!obj) {
                 return false;
             }
             MOZ_ASSERT(obj->compartment() == cx->compartment());
-            val.set(Val(obj));
+            val.set(Val(targetType, obj));
         }
         return true;
       }
       case ValType::Ref:
+      case ValType::NullRef:
       case ValType::I64: {
         break;
       }
     }
     MOZ_CRASH("unexpected import value type, caller must guard");
 }
 
 static Value
@@ -190,16 +191,17 @@ ToJSValue(const Val& val)
       case ValType::F64:
         return DoubleValue(JS::CanonicalizeNaN(val.f64()));
       case ValType::AnyRef:
         if (!val.ptr()) {
             return NullValue();
         }
         return ObjectValue(*(JSObject*)val.ptr());
       case ValType::Ref:
+      case ValType::NullRef:
       case ValType::I64:
         break;
     }
     MOZ_CRASH("unexpected type when translating to a JS value");
 }
 
 // ============================================================================
 // Imports
@@ -331,17 +333,17 @@ GetImports(JSContext* cx,
                 globalObjs[index] = obj;
                 obj->val(&val);
             } else {
                 if (IsNumberType(global.type())) {
                     if (!v.isNumber()) {
                         return ThrowBadImportType(cx, import.field.get(), "Number");
                     }
                 } else {
-                    MOZ_ASSERT(global.type().isRefOrAnyRef());
+                    MOZ_ASSERT(global.type().isReference());
                     if (!v.isNull() && !v.isObject()) {
                         return ThrowBadImportType(cx, import.field.get(), "Object-or-null");
                     }
                 }
 
                 if (global.type() == ValType::I64) {
                     JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_I64_LINK);
                     return false;
@@ -2429,16 +2431,18 @@ WasmGlobalObject::trace(JSTracer* trc, J
         break;
       case ValType::I32:
       case ValType::F32:
       case ValType::I64:
       case ValType::F64:
         break;
       case ValType::Ref:
         MOZ_CRASH("Ref NYI");
+      case ValType::NullRef:
+        MOZ_CRASH("NullRef not expressible");
     }
 }
 
 /* static */ void
 WasmGlobalObject::finalize(FreeOp*, JSObject* obj)
 {
     WasmGlobalObject* global = reinterpret_cast<WasmGlobalObject*>(obj);
     if (!global->isNewborn()) {
@@ -2478,16 +2482,19 @@ WasmGlobalObject::create(JSContext* cx, 
         cell->i64 = val.i64();
         break;
       case ValType::F32:
         cell->f32 = val.f32();
         break;
       case ValType::F64:
         cell->f64 = val.f64();
         break;
+      case ValType::NullRef:
+        MOZ_ASSERT(!cell->ptr, "value should be null already");
+        break;
       case ValType::AnyRef:
         MOZ_ASSERT(!cell->ptr, "no prebarriers needed");
         cell->ptr = val.ptr();
         if (cell->ptr) {
             JSObject::writeBarrierPost(&cell->ptr, nullptr, cell->ptr);
         }
         break;
       case ValType::Ref:
@@ -2571,18 +2578,19 @@ WasmGlobalObject::construct(JSContext* c
     RootedVal globalVal(cx);
 
     // Initialize with default value.
     switch (globalType.code()) {
       case ValType::I32:    globalVal = Val(uint32_t(0)); break;
       case ValType::I64:    globalVal = Val(uint64_t(0)); break;
       case ValType::F32:    globalVal = Val(float(0.0));  break;
       case ValType::F64:    globalVal = Val(double(0.0)); break;
-      case ValType::AnyRef: globalVal = Val(nullptr);     break;
+      case ValType::AnyRef: globalVal = Val(ValType::AnyRef, nullptr); break;
       case ValType::Ref:    MOZ_CRASH("Ref NYI");
+      case ValType::NullRef:MOZ_CRASH("NullRef not expressible");
     }
 
     // Override with non-undefined value, if provided.
     RootedValue valueVal(cx, args.get(1));
     if (!valueVal.isUndefined()) {
         if (!ToWebAssemblyValue(cx, globalType, valueVal, &globalVal)) {
             return false;
         }
@@ -2613,16 +2621,18 @@ WasmGlobalObject::valueGetterImpl(JSCont
       case ValType::AnyRef:
         args.rval().set(args.thisv().toObject().as<WasmGlobalObject>().value(cx));
         return true;
       case ValType::I64:
         JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_I64_TYPE);
         return false;
       case ValType::Ref:
         MOZ_CRASH("Ref NYI");
+      case ValType::NullRef:
+        MOZ_CRASH("NullRef not expressible");
     }
     MOZ_CRASH();
 }
 
 /* static */ bool
 WasmGlobalObject::valueGetter(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
@@ -2671,16 +2681,18 @@ WasmGlobalObject::valueSetterImpl(JSCont
             JSObject::writeBarrierPost(&cell->ptr, prevPtr, cell->ptr);
         }
         break;
       }
       case ValType::I64:
         MOZ_CRASH("unexpected i64 when setting global's value");
       case ValType::Ref:
         MOZ_CRASH("Ref NYI");
+      case ValType::NullRef:
+        MOZ_CRASH("NullRef not expressible");
     }
 
     args.rval().setUndefined();
     return true;
 }
 
 /* static */ bool
 WasmGlobalObject::valueSetter(JSContext* cx, unsigned argc, Value* vp)
@@ -2721,18 +2733,19 @@ void
 WasmGlobalObject::val(MutableHandleVal outval) const
 {
     Cell* cell = this->cell();
     switch (type().code()) {
       case ValType::I32:    outval.set(Val(uint32_t(cell->i32))); return;
       case ValType::I64:    outval.set(Val(uint64_t(cell->i64))); return;
       case ValType::F32:    outval.set(Val(cell->f32));           return;
       case ValType::F64:    outval.set(Val(cell->f64));           return;
-      case ValType::AnyRef: outval.set(Val(cell->ptr));           return;
+      case ValType::AnyRef: outval.set(Val(ValType::AnyRef, cell->ptr)); return;
       case ValType::Ref:    MOZ_CRASH("Ref NYI");
+      case ValType::NullRef:MOZ_CRASH("NullRef not expressible");
     }
     MOZ_CRASH("unexpected Global type");
 }
 
 Value
 WasmGlobalObject::value(JSContext* cx) const
 {
     // ToJSValue crashes on I64; this is desirable.
--- a/js/src/wasm/WasmOpIter.h
+++ b/js/src/wasm/WasmOpIter.h
@@ -48,35 +48,37 @@ class StackType
     bool isValidCode() {
         switch (UnpackTypeCodeType(tc_)) {
           case TypeCode::I32:
           case TypeCode::I64:
           case TypeCode::F32:
           case TypeCode::F64:
           case TypeCode::AnyRef:
           case TypeCode::Ref:
+          case TypeCode::NullRef:
           case TypeCode::Limit:
             return true;
           default:
             return false;
         }
     }
 #endif
 
   public:
     enum Code {
-        I32    = uint8_t(ValType::I32),
-        I64    = uint8_t(ValType::I64),
-        F32    = uint8_t(ValType::F32),
-        F64    = uint8_t(ValType::F64),
-
-        AnyRef = uint8_t(ValType::AnyRef),
-        Ref    = uint8_t(ValType::Ref),
-
-        Any    = uint8_t(TypeCode::Limit),
+        I32     = uint8_t(ValType::I32),
+        I64     = uint8_t(ValType::I64),
+        F32     = uint8_t(ValType::F32),
+        F64     = uint8_t(ValType::F64),
+
+        AnyRef  = uint8_t(ValType::AnyRef),
+        Ref     = uint8_t(ValType::Ref),
+        NullRef = uint8_t(ValType::NullRef),
+
+        TVar    = uint8_t(TypeCode::Limit),
     };
 
     StackType() : tc_(InvalidPackedTypeCode()) {}
 
     MOZ_IMPLICIT StackType(Code c)
       : tc_(PackTypeCode(TypeCode(c)))
     {
         MOZ_ASSERT(isValidCode());
@@ -97,19 +99,19 @@ class StackType
     uint32_t refTypeIndex() const {
         return UnpackTypeCodeIndex(tc_);
     }
 
     bool isRef() const {
         return UnpackTypeCodeType(tc_) == TypeCode::Ref;
     }
 
-    bool isRefOrAnyRef() const {
+    bool isReference() const {
         TypeCode tc = UnpackTypeCodeType(tc_);
-        return tc == TypeCode::Ref || tc == TypeCode::AnyRef;
+        return tc == TypeCode::Ref || tc == TypeCode::AnyRef || tc == TypeCode::NullRef;
     }
 
     bool operator ==(const StackType& that) const {
         return tc_ == that.tc_;
     }
 
     bool operator !=(const StackType& that) const {
         return tc_ != that.tc_;
@@ -123,17 +125,17 @@ class StackType
     bool operator !=(Code that) const {
         return !(*this == that);
     }
 };
 
 static inline ValType
 NonAnyToValType(StackType type)
 {
-    MOZ_ASSERT(type != StackType::Any);
+    MOZ_ASSERT(type != StackType::TVar);
     return ValType(type.packed());
 }
 
 #ifdef DEBUG
 // Families of opcodes that share a signature and validation logic.
 enum class OpKind {
     Block,
     Loop,
@@ -290,17 +292,17 @@ class ControlStackEntry<Nothing>
 
 template <typename Value>
 class TypeAndValue
 {
     StackType type_;
     Value value_;
 
   public:
-    TypeAndValue() : type_(StackType::Any), value_() {}
+    TypeAndValue() : type_(StackType::TVar), value_() {}
     explicit TypeAndValue(StackType type)
       : type_(type), value_()
     {}
     explicit TypeAndValue(ValType type)
       : type_(StackType(type)), value_()
     {}
     TypeAndValue(StackType type, Value value)
       : type_(type), value_(value)
@@ -324,17 +326,17 @@ class TypeAndValue
 
 // Specialization for when there is no additional data needed.
 template <>
 class TypeAndValue<Nothing>
 {
     StackType type_;
 
   public:
-    TypeAndValue() : type_(StackType::Any) {}
+    TypeAndValue() : type_(StackType::TVar) {}
     explicit TypeAndValue(StackType type) : type_(type) {}
     explicit TypeAndValue(ValType type) : type_(StackType(type)) {}
     TypeAndValue(StackType type, Nothing value) : type_(type) {}
     TypeAndValue(ValType type, Nothing value) : type_(StackType(type)) {}
 
     StackType type() const { return type_; }
     StackType& typeRef() { return type_; }
     Nothing value() const { return Nothing(); }
@@ -434,19 +436,16 @@ class MOZ_STACK_CLASS OpIter : private P
         valueStack_.infallibleAppend(tv);
     }
 
     void afterUnconditionalBranch() {
         valueStack_.shrinkTo(controlStack_.back().valueStackStart());
         controlStack_.back().setPolymorphicBase();
     }
 
-    inline bool IsPrefixOf(StackType a, StackType b);
-    inline bool IsSubtypeOf(StackType one, StackType two);
-    inline bool Unify(StackType observed, StackType expected, StackType* result);
     inline bool Join(StackType one, StackType two, StackType* result);
 
   public:
     typedef Vector<Value, 8, SystemAllocPolicy> ValueVector;
 
 #ifdef DEBUG
     explicit OpIter(const ModuleEnvironment& env, Decoder& decoder,
                     ExclusiveDeferredValidationState& dvs)
@@ -539,17 +538,17 @@ class MOZ_STACK_CLASS OpIter : private P
     MOZ_MUST_USE bool readTeeLocal(const ValTypeVector& locals, uint32_t* id, Value* value);
     MOZ_MUST_USE bool readGetGlobal(uint32_t* id);
     MOZ_MUST_USE bool readSetGlobal(uint32_t* id, Value* value);
     MOZ_MUST_USE bool readTeeGlobal(uint32_t* id, Value* value);
     MOZ_MUST_USE bool readI32Const(int32_t* i32);
     MOZ_MUST_USE bool readI64Const(int64_t* i64);
     MOZ_MUST_USE bool readF32Const(float* f32);
     MOZ_MUST_USE bool readF64Const(double* f64);
-    MOZ_MUST_USE bool readRefNull(ValType* type);
+    MOZ_MUST_USE bool readRefNull();
     MOZ_MUST_USE bool readCall(uint32_t* calleeIndex, ValueVector* argValues);
     MOZ_MUST_USE bool readCallIndirect(uint32_t* funcTypeIndex, uint32_t* tableIndex, Value* callee,
                                        ValueVector* argValues);
     MOZ_MUST_USE bool readOldCallDirect(uint32_t numFuncImports, uint32_t* funcIndex,
                                         ValueVector* argValues);
     MOZ_MUST_USE bool readOldCallIndirect(uint32_t* funcTypeIndex, Value* callee, ValueVector* argValues);
     MOZ_MUST_USE bool readWake(LinearMemoryAddress<Value>* addr, Value* count);
     MOZ_MUST_USE bool readWait(LinearMemoryAddress<Value>* addr,
@@ -626,86 +625,40 @@ class MOZ_STACK_CLASS OpIter : private P
     // end of the function body.
     bool controlStackEmpty() const {
         return controlStack_.empty();
     }
 };
 
 template <typename Policy>
 inline bool
-OpIter<Policy>::IsPrefixOf(StackType a, StackType b)
-{
-    const StructType& other = env_.types[a.refTypeIndex()].structType();
-    return env_.types[b.refTypeIndex()].structType().hasPrefix(other);
-}
-
-template <typename Policy>
-inline bool
-OpIter<Policy>::IsSubtypeOf(StackType one, StackType two)
-{
-    MOZ_ASSERT(one.isRefOrAnyRef());
-    MOZ_ASSERT(two.isRefOrAnyRef());
-    return one == two || two == StackType::AnyRef || (one.isRef() && IsPrefixOf(two, one));
-}
-
-template <typename Policy>
-inline bool
-OpIter<Policy>::Unify(StackType observed, StackType expected, StackType* result)
-{
-    if (MOZ_LIKELY(observed == expected)) {
-        *result = observed;
-        return true;
-    }
-
-    if (observed == StackType::Any) {
-        *result = expected;
-        return true;
-    }
-
-    if (expected == StackType::Any) {
-        *result = observed;
-        return true;
-    }
-
-    if (env_.gcTypesEnabled() == HasGcTypes::True && observed.isRefOrAnyRef() &&
-        expected.isRefOrAnyRef() && IsSubtypeOf(observed, expected))
-    {
-        *result = expected;
-        return true;
-    }
-
-    return false;
-}
-
-template <typename Policy>
-inline bool
 OpIter<Policy>::Join(StackType one, StackType two, StackType* result)
 {
     if (MOZ_LIKELY(one == two)) {
         *result = one;
         return true;
     }
 
-    if (one == StackType::Any) {
+    if (one == StackType::TVar) {
         *result = two;
         return true;
     }
 
-    if (two == StackType::Any) {
+    if (two == StackType::TVar) {
         *result = one;
         return true;
     }
 
-    if (env_.gcTypesEnabled() == HasGcTypes::True && one.isRefOrAnyRef() && two.isRefOrAnyRef()) {
-        if (IsSubtypeOf(two, one)) {
+    if (env_.gcTypesEnabled() == HasGcTypes::True && one.isReference() && two.isReference()) {
+        if (env_.isRefSubtypeOf(NonAnyToValType(two), NonAnyToValType(one))) {
             *result = one;
             return true;
         }
 
-        if (IsSubtypeOf(one, two)) {
+        if (env_.isRefSubtypeOf(NonAnyToValType(one), NonAnyToValType(two))) {
             *result = two;
             return true;
         }
 
         // No subtyping relations between the two types.
         *result = StackType::AnyRef;
         return true;
     }
@@ -741,29 +694,29 @@ OpIter<Policy>::fail_ctx(const char* fmt
     if (!error) {
         return false;
     }
     return fail(error.get());
 }
 
 // This function pops exactly one value from the stack, yielding Any types in
 // various cases and therefore making it the caller's responsibility to do the
-// right thing for StackType::Any. Prefer (pop|top)WithType.
+// right thing for StackType::TVar. Prefer (pop|top)WithType.
 template <typename Policy>
 inline bool
 OpIter<Policy>::popAnyType(StackType* type, Value* value)
 {
     ControlStackEntry<ControlItem>& block = controlStack_.back();
 
     MOZ_ASSERT(valueStack_.length() >= block.valueStackStart());
     if (MOZ_UNLIKELY(valueStack_.length() == block.valueStackStart())) {
         // If the base of this block's stack is polymorphic, then we can pop a
         // dummy value of any type; it won't be used since we're in unreachable code.
         if (block.polymorphicBase()) {
-            *type = StackType::Any;
+            *type = StackType::TVar;
             *value = Value();
 
             // Maintain the invariant that, after a pop, there is always memory
             // reserved to push a value infallibly.
             return valueStack_.reserve(valueStack_.length() + 1);
         }
 
         if (valueStack_.empty()) {
@@ -817,19 +770,26 @@ OpIter<Policy>::popWithType(StackType ex
         if (valueStack_.empty()) {
             return fail("popping value from empty stack");
         }
         return fail("popping value from outside block");
     }
 
     TypeAndValue<Value> tv = valueStack_.popCopy();
 
-    StackType _;
-    if (MOZ_UNLIKELY(!Unify(tv.type(), expectedType, &_))) {
-        return typeMismatch(tv.type(), expectedType);
+    StackType observedType = tv.type();
+    if (!(MOZ_LIKELY(observedType == expectedType) ||
+          observedType == StackType::TVar ||
+          expectedType == StackType::TVar ||
+          (env_.gcTypesEnabled() == HasGcTypes::True &&
+           observedType.isReference() && expectedType.isReference() &&
+           env_.isRefSubtypeOf(NonAnyToValType(observedType),
+                               NonAnyToValType(expectedType)))))
+    {
+        return typeMismatch(observedType, expectedType);
     }
 
     *value = tv.value();
     return true;
 }
 
 // This function pops as many types from the stack as determined by the given
 // signature. Currently, all signatures are limited to 0 or 1 types, with
@@ -873,18 +833,29 @@ OpIter<Policy>::topWithType(ValType expe
         if (valueStack_.empty()) {
             return fail("reading value from empty stack");
         }
         return fail("reading value from outside block");
     }
 
     TypeAndValue<Value>& tv = valueStack_.back();
 
-    if (MOZ_UNLIKELY(!Unify(tv.type(), StackType(expectedType), &tv.typeRef()))) {
-        return typeMismatch(tv.type(), StackType(expectedType));
+    StackType observed = tv.type();
+    StackType expected = StackType(expectedType);
+
+    if (!MOZ_UNLIKELY(observed == expected)) {
+        if (observed == StackType::TVar ||
+            (env_.gcTypesEnabled() == HasGcTypes::True &&
+             observed.isReference() && expected.isReference() &&
+             env_.isRefSubtypeOf(NonAnyToValType(observed), expectedType)))
+        {
+            tv.typeRef() = expected;
+        } else {
+            return typeMismatch(observed, expected);
+        }
     }
 
     *value = tv.value();
     return true;
 }
 
 template <typename Policy>
 inline bool
@@ -1702,24 +1673,21 @@ OpIter<Policy>::readF64Const(double* f64
     MOZ_ASSERT(Classify(op_) == OpKind::F64);
 
     return readFixedF64(f64) &&
            push(ValType::F64);
 }
 
 template <typename Policy>
 inline bool
-OpIter<Policy>::readRefNull(ValType* type)
+OpIter<Policy>::readRefNull()
 {
     MOZ_ASSERT(Classify(op_) == OpKind::RefNull);
-    if (!readReferenceType(type, "ref.null")) {
-        return false;
-    }
-
-    return push(StackType(*type));
+
+    return push(StackType(ValType::NullRef));
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::readReferenceType(ValType* type, const char* context)
 {
     uint8_t code;
     uint32_t refTypeIndex;
--- a/js/src/wasm/WasmStubs.cpp
+++ b/js/src/wasm/WasmStubs.cpp
@@ -175,16 +175,18 @@ StoreABIReturn(MacroAssembler& masm, con
       case ExprType::F64:
         masm.canonicalizeDouble(ReturnDoubleReg);
         masm.storeDouble(ReturnDoubleReg, Address(argv, 0));
         break;
       case ExprType::Ref:
       case ExprType::AnyRef:
         masm.storePtr(ReturnReg, Address(argv, 0));
         break;
+      case ExprType::NullRef:
+        MOZ_CRASH("NullRef not expressible");
       case ExprType::Limit:
         MOZ_CRASH("Limit");
     }
 }
 
 #if defined(JS_CODEGEN_ARM)
 // The ARM system ABI also includes d15 & s31 in the non volatile float registers.
 // Also exclude lr (a.k.a. r14) as we preserve it manually.
@@ -792,16 +794,18 @@ GenerateJitEntry(MacroAssembler& masm, s
       case ExprType::Ref:
         MOZ_CRASH("return ref in jitentry NYI");
         break;
       case ExprType::AnyRef:
         MOZ_CRASH("return anyref in jitentry NYI");
         break;
       case ExprType::I64:
         MOZ_CRASH("unexpected return type when calling from ion to wasm");
+      case ExprType::NullRef:
+        MOZ_CRASH("NullRef not expressible");
       case ExprType::Limit:
         MOZ_CRASH("Limit");
     }
 
     MOZ_ASSERT(masm.framePushed() == 0);
 #ifdef JS_CODEGEN_ARM64
     masm.loadPtr(Address(sp, 0), lr);
     masm.addToStackPtr(Imm32(8));
@@ -1004,16 +1008,18 @@ wasm::GenerateDirectCallFromJit(MacroAss
         break;
       case wasm::ExprType::F64:
         masm.canonicalizeDouble(ReturnDoubleReg);
         break;
       case wasm::ExprType::Ref:
       case wasm::ExprType::AnyRef:
       case wasm::ExprType::I64:
         MOZ_CRASH("unexpected return type when calling from ion to wasm");
+      case wasm::ExprType::NullRef:
+        MOZ_CRASH("NullRef not expressible");
       case wasm::ExprType::Limit:
         MOZ_CRASH("Limit");
     }
 
     // Free args + frame descriptor.
     masm.leaveExitFrame(bytesNeeded + ExitFrameLayout::Size());
 
     // If we pushed it, free FramePointer.
@@ -1344,16 +1350,18 @@ GenerateImportInterpExit(MacroAssembler&
         masm.loadDouble(argv, ReturnDoubleReg);
         break;
       case ExprType::Ref:
       case ExprType::AnyRef:
         masm.call(SymbolicAddress::CallImport_Ref);
         masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
         masm.loadPtr(argv, ReturnReg);
         break;
+      case ExprType::NullRef:
+        MOZ_CRASH("NullRef not expressible");
       case ExprType::Limit:
         MOZ_CRASH("Limit");
     }
 
     // The native ABI preserves the TLS, heap and global registers since they
     // are non-volatile.
     MOZ_ASSERT(NonVolatileRegs.has(WasmTlsReg));
 #if defined(JS_CODEGEN_X64) || \
@@ -1517,16 +1525,18 @@ GenerateImportJitExit(MacroAssembler& ma
         masm.convertValueToDouble(JSReturnOperand, ReturnDoubleReg, &oolConvert);
         break;
       case ExprType::Ref:
         MOZ_CRASH("ref returned by import (jit exit) NYI");
         break;
       case ExprType::AnyRef:
         MOZ_CRASH("anyref returned by import (jit exit) NYI");
         break;
+      case ExprType::NullRef:
+        MOZ_CRASH("NullRef not expressible");
       case ExprType::Limit:
         MOZ_CRASH("Limit");
     }
 
     Label done;
     masm.bind(&done);
 
     GenerateJitExitEpilogue(masm, masm.framePushed(), offsets);
--- a/js/src/wasm/WasmTextToBinary.cpp
+++ b/js/src/wasm/WasmTextToBinary.cpp
@@ -3875,29 +3875,17 @@ ParseStructNarrow(WasmParseContext& c, b
 
     return new(c.lifo) AstStructNarrow(inputType, outputType, ptr);
 }
 #endif
 
 static AstExpr*
 ParseRefNull(WasmParseContext& c)
 {
-    WasmToken token;
-    AstValType vt;
-
-    if (!ParseValType(c, &vt)) {
-        return nullptr;
-    }
-
-    if (!vt.isRefType()) {
-        c.ts.generateError(token, "ref.null requires ref type", c.error);
-        return nullptr;
-    }
-
-    return new(c.lifo) AstRefNull(vt);
+    return new(c.lifo) AstRefNull();
 }
 
 static AstExpr*
 ParseExprBody(WasmParseContext& c, WasmToken token, bool inParens)
 {
     if (!CheckRecursionLimitDontReport(c.stackLimit)) {
         return nullptr;
     }
@@ -5777,17 +5765,17 @@ ResolveStructNarrow(Resolver& r, AstStru
 
     return ResolveExpr(r, s.ptr());
 }
 #endif
 
 static bool
 ResolveRefNull(Resolver& r, AstRefNull& s)
 {
-    return ResolveType(r, s.baseType());
+    return true;
 }
 
 static bool
 ResolveExpr(Resolver& r, AstExpr& expr)
 {
     switch (expr.kind()) {
       case AstExprKind::Nop:
       case AstExprKind::Pop:
@@ -6731,18 +6719,17 @@ EncodeStructNarrow(Encoder& e, AstStruct
     }
     return true;
 }
 #endif
 
 static bool
 EncodeRefNull(Encoder& e, AstRefNull& s)
 {
-    return e.writeOp(Op::RefNull) &&
-           e.writeValType(s.baseType().type());
+    return e.writeOp(Op::RefNull);
 }
 
 static bool
 EncodeExpr(Encoder& e, AstExpr& expr)
 {
     switch (expr.kind()) {
       case AstExprKind::Pop:
         return true;
--- a/js/src/wasm/WasmTypes.cpp
+++ b/js/src/wasm/WasmTypes.cpp
@@ -64,16 +64,17 @@ Val::Val(const LitVal& val)
 {
     type_ = val.type();
     switch (type_.code()) {
       case ValType::I32: u.i32_ = val.i32(); return;
       case ValType::F32: u.f32_ = val.f32(); return;
       case ValType::I64: u.i64_ = val.i64(); return;
       case ValType::F64: u.f64_ = val.f64(); return;
       case ValType::Ref:
+      case ValType::NullRef:
       case ValType::AnyRef: u.ptr_ = val.ptr(); return;
     }
     MOZ_CRASH();
 }
 
 void
 Val::writePayload(uint8_t* dst) const
 {
@@ -82,16 +83,17 @@ Val::writePayload(uint8_t* dst) const
       case ValType::F32:
         memcpy(dst, &u.i32_, sizeof(u.i32_));
         return;
       case ValType::I64:
       case ValType::F64:
         memcpy(dst, &u.i64_, sizeof(u.i64_));
         return;
       case ValType::Ref:
+      case ValType::NullRef:
       case ValType::AnyRef:
         MOZ_ASSERT(*(JSObject**)dst == nullptr, "should be null so no need for a pre-barrier");
         memcpy(dst, &u.ptr_, sizeof(JSObject*));
         // Either the written location is in the global data section in the
         // WasmInstanceObject, or the Cell of a WasmGlobalObject:
         // - WasmInstanceObjects are always tenured and u.ptr_ may point to a
         // nursery object, so we need a post-barrier since the global data of
         // an instance is effectively a field of the WasmInstanceObject.
@@ -103,17 +105,17 @@ Val::writePayload(uint8_t* dst) const
         return;
     }
     MOZ_CRASH("unexpected Val type");
 }
 
 void
 Val::trace(JSTracer* trc)
 {
-    if (type_.isValid() && type_.isRefOrAnyRef() && u.ptr_) {
+    if (type_.isValid() && type_.isReference() && u.ptr_) {
         TraceManuallyBarrieredEdge(trc, &u.ptr_, "wasm ref/anyref global");
     }
 }
 
 bool
 wasm::IsRoundingFunction(SymbolicAddress callee, jit::RoundingMode* mode)
 {
     switch (callee) {
@@ -195,16 +197,17 @@ IsImmediateType(ValType vt)
 {
     switch (vt.code()) {
       case ValType::I32:
       case ValType::I64:
       case ValType::F32:
       case ValType::F64:
       case ValType::AnyRef:
         return true;
+      case ValType::NullRef:
       case ValType::Ref:
         return false;
     }
     MOZ_CRASH("bad ValType");
 }
 
 static unsigned
 EncodeImmediateType(ValType vt)
@@ -216,16 +219,17 @@ EncodeImmediateType(ValType vt)
       case ValType::I64:
         return 1;
       case ValType::F32:
         return 2;
       case ValType::F64:
         return 3;
       case ValType::AnyRef:
         return 4;
+      case ValType::NullRef:
       case ValType::Ref:
         break;
     }
     MOZ_CRASH("bad ValType");
 }
 
 /* static */ bool
 FuncTypeIdDesc::isGlobal(const FuncType& funcType)
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -288,38 +288,40 @@ class ExprType
 #ifdef DEBUG
     bool isValidCode() {
         switch (UnpackTypeCodeType(tc_)) {
           case TypeCode::I32:
           case TypeCode::I64:
           case TypeCode::F32:
           case TypeCode::F64:
           case TypeCode::AnyRef:
+          case TypeCode::NullRef:
           case TypeCode::Ref:
           case TypeCode::BlockVoid:
           case TypeCode::Limit:
             return true;
           default:
             return false;
         }
     }
 #endif
 
   public:
     enum Code {
-        Void   = uint8_t(TypeCode::BlockVoid),
-
-        I32    = uint8_t(TypeCode::I32),
-        I64    = uint8_t(TypeCode::I64),
-        F32    = uint8_t(TypeCode::F32),
-        F64    = uint8_t(TypeCode::F64),
-        AnyRef = uint8_t(TypeCode::AnyRef),
-        Ref    = uint8_t(TypeCode::Ref),
-
-        Limit  = uint8_t(TypeCode::Limit)
+        Void    = uint8_t(TypeCode::BlockVoid),
+
+        I32     = uint8_t(TypeCode::I32),
+        I64     = uint8_t(TypeCode::I64),
+        F32     = uint8_t(TypeCode::F32),
+        F64     = uint8_t(TypeCode::F64),
+        AnyRef  = uint8_t(TypeCode::AnyRef),
+        NullRef = uint8_t(TypeCode::NullRef),
+        Ref     = uint8_t(TypeCode::Ref),
+
+        Limit   = uint8_t(TypeCode::Limit)
     };
 
     ExprType() : tc_() {}
 
     ExprType(const ExprType& that) : tc_(that.tc_) {}
 
     MOZ_IMPLICIT ExprType(Code c)
       : tc_(PackTypeCode(TypeCode(c)))
@@ -360,19 +362,19 @@ class ExprType
     bool isValid() const {
         return IsValid(tc_);
     }
 
     bool isRef() const {
         return UnpackTypeCodeType(tc_) == TypeCode::Ref;
     }
 
-    bool isRefOrAnyRef() const {
+    bool isReference() const {
         TypeCode tc = UnpackTypeCodeType(tc_);
-        return tc == TypeCode::Ref || tc == TypeCode::AnyRef;
+        return tc == TypeCode::Ref || tc == TypeCode::AnyRef || tc == TypeCode::NullRef;
     }
 
     bool operator ==(const ExprType& that) const {
         return tc_ == that.tc_;
     }
 
     bool operator !=(const ExprType& that) const {
         return tc_ != that.tc_;
@@ -398,33 +400,35 @@ class ValType
 #ifdef DEBUG
     bool isValidCode() {
         switch (UnpackTypeCodeType(tc_)) {
           case TypeCode::I32:
           case TypeCode::I64:
           case TypeCode::F32:
           case TypeCode::F64:
           case TypeCode::AnyRef:
+          case TypeCode::NullRef:
           case TypeCode::Ref:
             return true;
           default:
             return false;
         }
     }
 #endif
 
   public:
     enum Code {
-        I32    = uint8_t(TypeCode::I32),
-        I64    = uint8_t(TypeCode::I64),
-        F32    = uint8_t(TypeCode::F32),
-        F64    = uint8_t(TypeCode::F64),
-
-        AnyRef = uint8_t(TypeCode::AnyRef),
-        Ref    = uint8_t(TypeCode::Ref),
+        I32     = uint8_t(TypeCode::I32),
+        I64     = uint8_t(TypeCode::I64),
+        F32     = uint8_t(TypeCode::F32),
+        F64     = uint8_t(TypeCode::F64),
+
+        AnyRef  = uint8_t(TypeCode::AnyRef),
+        NullRef = uint8_t(TypeCode::NullRef),
+        Ref     = uint8_t(TypeCode::Ref),
     };
 
     ValType() : tc_(InvalidPackedTypeCode()) {}
 
     MOZ_IMPLICIT ValType(Code c)
       : tc_(PackTypeCode(TypeCode(c)))
     {
         MOZ_ASSERT(isValidCode());
@@ -471,19 +475,19 @@ class ValType
     bool isValid() const {
         return IsValid(tc_);
     }
 
     bool isRef() const {
         return UnpackTypeCodeType(tc_) == TypeCode::Ref;
     }
 
-    bool isRefOrAnyRef() const {
+    bool isReference() const {
         TypeCode tc = UnpackTypeCodeType(tc_);
-        return tc == TypeCode::Ref || tc == TypeCode::AnyRef;
+        return tc == TypeCode::Ref || tc == TypeCode::AnyRef || tc == TypeCode::NullRef;
     }
 
     bool operator ==(const ValType& that) const {
         return tc_ == that.tc_;
     }
 
     bool operator !=(const ValType& that) const {
         return tc_ != that.tc_;
@@ -509,40 +513,42 @@ SizeOf(ValType vt)
     switch (vt.code()) {
       case ValType::I32:
       case ValType::F32:
         return 4;
       case ValType::I64:
       case ValType::F64:
         return 8;
       case ValType::AnyRef:
+      case ValType::NullRef:
       case ValType::Ref:
         return sizeof(intptr_t);
     }
     MOZ_CRASH("Invalid ValType");
 }
 
 static inline jit::MIRType
 ToMIRType(ValType vt)
 {
     switch (vt.code()) {
-      case ValType::I32:    return jit::MIRType::Int32;
-      case ValType::I64:    return jit::MIRType::Int64;
-      case ValType::F32:    return jit::MIRType::Float32;
-      case ValType::F64:    return jit::MIRType::Double;
-      case ValType::Ref:    return jit::MIRType::Pointer;
-      case ValType::AnyRef: return jit::MIRType::Pointer;
+      case ValType::I32:     return jit::MIRType::Int32;
+      case ValType::I64:     return jit::MIRType::Int64;
+      case ValType::F32:     return jit::MIRType::Float32;
+      case ValType::F64:     return jit::MIRType::Double;
+      case ValType::Ref:     return jit::MIRType::Pointer;
+      case ValType::AnyRef:  return jit::MIRType::Pointer;
+      case ValType::NullRef: return jit::MIRType::Pointer;
     }
     MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("bad type");
 }
 
 static inline bool
 IsNumberType(ValType vt)
 {
-    return !vt.isRefOrAnyRef();
+    return !vt.isReference();
 }
 
 // ExprType utilities
 
 inline
 ExprType::ExprType(const ValType& t)
   : tc_(t.packed())
 {}
@@ -571,16 +577,17 @@ ToCString(ExprType type)
 {
     switch (type.code()) {
       case ExprType::Void:    return "void";
       case ExprType::I32:     return "i32";
       case ExprType::I64:     return "i64";
       case ExprType::F32:     return "f32";
       case ExprType::F64:     return "f64";
       case ExprType::AnyRef:  return "anyref";
+      case ExprType::NullRef: return "nullref";
       case ExprType::Ref:     return "ref";
       case ExprType::Limit:;
     }
     MOZ_CRASH("bad expression type");
 }
 
 static inline const char*
 ToCString(ValType type)
@@ -703,29 +710,30 @@ class LitVal
 
     explicit LitVal(uint32_t i32) : type_(ValType::I32) { u.i32_ = i32; }
     explicit LitVal(uint64_t i64) : type_(ValType::I64) { u.i64_ = i64; }
 
     explicit LitVal(float f32) : type_(ValType::F32) { u.f32_ = f32; }
     explicit LitVal(double f64) : type_(ValType::F64) { u.f64_ = f64; }
 
     explicit LitVal(ValType refType, JSObject* ptr) : type_(refType) {
-        MOZ_ASSERT(refType.isRefOrAnyRef());
+        MOZ_ASSERT(refType.isReference());
+        MOZ_ASSERT(refType != ValType::NullRef);
         MOZ_ASSERT(ptr == nullptr, "use Val for non-nullptr ref types to get tracing");
         u.ptr_ = ptr;
     }
 
     ValType type() const { return type_; }
     static constexpr size_t sizeofLargestValue() { return sizeof(u); }
 
     uint32_t i32() const { MOZ_ASSERT(type_ == ValType::I32); return u.i32_; }
     uint64_t i64() const { MOZ_ASSERT(type_ == ValType::I64); return u.i64_; }
     const float& f32() const { MOZ_ASSERT(type_ == ValType::F32); return u.f32_; }
     const double& f64() const { MOZ_ASSERT(type_ == ValType::F64); return u.f64_; }
-    JSObject* ptr() const { MOZ_ASSERT(type_.isRefOrAnyRef()); return u.ptr_; }
+    JSObject* ptr() const { MOZ_ASSERT(type_.isReference()); return u.ptr_; }
 };
 
 typedef Vector<LitVal, 0, SystemAllocPolicy> LitValVector;
 
 // A Val is a LitVal that can contain pointers to JSObjects, thanks to their
 // trace implementation. Since a Val is able to store a pointer to a JSObject,
 // it needs to be traced during compilation in case the pointee is moved.
 // The classic shorthands for Rooted things are defined after this class, for
@@ -735,17 +743,17 @@ class MOZ_NON_PARAM Val : public LitVal
 {
   public:
     Val() : LitVal() {}
     explicit Val(const LitVal& val);
     explicit Val(uint32_t i32)  : LitVal(i32) {}
     explicit Val(uint64_t i64)  : LitVal(i64) {}
     explicit Val(float f32)     : LitVal(f32) {}
     explicit Val(double f64)    : LitVal(f64) {}
-    explicit Val(JSObject* obj) : LitVal(ValType::AnyRef, nullptr) { u.ptr_ = obj; }
+    explicit Val(ValType type, JSObject* obj) : LitVal(type, nullptr) { u.ptr_ = obj; }
     void writePayload(uint8_t* dst) const;
     void trace(JSTracer* trc);
 };
 
 typedef Rooted<Val> RootedVal;
 typedef Handle<Val> HandleVal;
 typedef MutableHandle<Val> MutableHandleVal;
 
@@ -804,21 +812,21 @@ class FuncType
         for (ValType arg : args()) {
             if (arg == ValType::I64) {
                 return true;
             }
         }
         return false;
     }
     bool temporarilyUnsupportedAnyRef() const {
-        if (ret().isRefOrAnyRef()) {
+        if (ret().isReference()) {
             return true;
         }
         for (ValType arg : args()) {
-            if (arg.isRefOrAnyRef()) {
+            if (arg.isReference()) {
                 return true;
             }
         }
         return false;
     }
 #ifdef WASM_PRIVATE_REFTYPES
     bool exposesRef() const {
         for (const ValType& arg : args()) {
@@ -1053,31 +1061,33 @@ class GlobalDesc
     bool isWasm() const { return !isConstant() && u.var.isWasm_; }
 
   public:
     GlobalDesc() = default;
 
     explicit GlobalDesc(InitExpr initial, bool isMutable, ModuleKind kind = ModuleKind::Wasm)
       : kind_((isMutable || !initial.isVal()) ? GlobalKind::Variable : GlobalKind::Constant)
     {
+        MOZ_ASSERT(initial.type() != ValType::NullRef);
         if (isVariable()) {
             u.var.val.initial_ = initial;
             u.var.isMutable_ = isMutable;
             u.var.isWasm_ = kind == Wasm;
             u.var.isExport_ = false;
             u.var.offset_ = UINT32_MAX;
         } else {
             u.cst_ = initial.val();
         }
     }
 
     explicit GlobalDesc(ValType type, bool isMutable, uint32_t importIndex,
                         ModuleKind kind = ModuleKind::Wasm)
       : kind_(GlobalKind::Import)
     {
+        MOZ_ASSERT(type != ValType::NullRef);
         u.var.val.import.type_ = type;
         u.var.val.import.index_ = importIndex;
         u.var.isMutable_ = isMutable;
         u.var.isWasm_ = kind == Wasm;
         u.var.isExport_ = false;
         u.var.offset_ = UINT32_MAX;
     }
 
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -1000,18 +1000,17 @@ DecodeFunctionBodyExprs(const ModuleEnvi
             }
             CHECK(iter.readComparison(ValType::AnyRef, &nothing, &nothing));
             break;
           }
           case uint16_t(Op::RefNull): {
             if (env.gcTypesEnabled() == HasGcTypes::False) {
                 return iter.unrecognizedOpcode(&op);
             }
-            ValType unusedType;
-            CHECK(iter.readRefNull(&unusedType));
+            CHECK(iter.readRefNull());
             break;
           }
           case uint16_t(Op::RefIsNull): {
             if (env.gcTypesEnabled() == HasGcTypes::False) {
                 return iter.unrecognizedOpcode(&op);
             }
             CHECK(iter.readConversion(ValType::AnyRef, ValType::I32, &nothing));
             break;
@@ -1447,24 +1446,28 @@ DecodeGCFeatureOptInSection(Decoder& d, 
     if (!d.readVarU32(&version)) {
         return d.fail("expected gc feature version");
     }
 
     // For documentation of what's in the various versions, see
     // https://github.com/lars-t-hansen/moz-gc-experiments
     //
     // Version 1 is complete.
-    // Version 2 is in progress, currently backward compatible with version 1.
+    // Version 2 is in progress.
 
     switch (version) {
       case 1:
+        return d.fail("Wasm GC feature version 1 is no longer supported by this engine.\n"
+                      "The current version is 2, which is not backward-compatible:\n"
+                      " - The old encoding of ref.null is no longer accepted.");
       case 2:
         break;
       default:
-        return d.fail("unsupported version of the gc feature");
+        return d.fail("The specified Wasm GC feature version is unknown.\n"
+                      "The current version is 2.");
     }
 
     env->gcFeatureOptIn = HasGcTypes::True;
     return d.finishSection(*range, "gcfeatureoptin");
 }
 #endif
 
 static bool
@@ -1974,18 +1977,17 @@ DecodeMemorySection(Decoder& d, ModuleEn
             return false;
         }
     }
 
     return d.finishSection(*range, "memory");
 }
 
 static bool
-DecodeInitializerExpression(Decoder& d, HasGcTypes gcTypesEnabled, const GlobalDescVector& globals,
-                            ValType expected, uint32_t numTypes, InitExpr* init)
+DecodeInitializerExpression(Decoder& d, ModuleEnvironment* env, ValType expected, InitExpr* init)
 {
     OpBytes op;
     if (!d.readOp(&op)) {
         return d.fail("failed to read initializer type");
     }
 
     switch (op.b0) {
       case uint16_t(Op::I32Const): {
@@ -2016,48 +2018,48 @@ DecodeInitializerExpression(Decoder& d, 
         double f64;
         if (!d.readFixedF64(&f64)) {
             return d.fail("failed to read initializer f64 expression");
         }
         *init = InitExpr(LitVal(f64));
         break;
       }
       case uint16_t(Op::RefNull): {
-        if (gcTypesEnabled == HasGcTypes::False) {
+        if (env->gcTypesEnabled() == HasGcTypes::False) {
             return d.fail("unexpected initializer expression");
         }
-        uint8_t valType;
-        uint32_t refTypeIndex;
-        if (!d.readValType(&valType, &refTypeIndex)) {
-            return false;
+        if (!expected.isReference()) {
+            return d.fail("type mismatch: initializer type and expected type don't match");
         }
-        if (valType == uint8_t(ValType::AnyRef)) {
-            *init = InitExpr(LitVal(ValType::AnyRef, nullptr));
-        } else if (valType == uint8_t(ValType::Ref)) {
-            if (refTypeIndex >= numTypes) {
-                return d.fail("invalid reference type for ref.null");
-            }
-            *init = InitExpr(LitVal(ValType(ValType::Ref, refTypeIndex), nullptr));
-        } else {
-            return d.fail("expected anyref/ref as type for ref.null");
-        }
+        *init = InitExpr(LitVal(expected, nullptr));
         break;
       }
       case uint16_t(Op::GetGlobal): {
         uint32_t i;
+        const GlobalDescVector& globals = env->globals;
         if (!d.readVarU32(&i)) {
             return d.fail("failed to read get_global index in initializer expression");
         }
         if (i >= globals.length()) {
             return d.fail("global index out of range in initializer expression");
         }
         if (!globals[i].isImport() || globals[i].isMutable()) {
             return d.fail("initializer expression must reference a global immutable import");
         }
-        *init = InitExpr(i, globals[i].type());
+        if (expected.isReference()) {
+            if (!(env->gcTypesEnabled() == HasGcTypes::True &&
+                  globals[i].type().isReference() &&
+                  env->isRefSubtypeOf(globals[i].type(), expected)))
+            {
+                return d.fail("type mismatch: initializer type and expected type don't match");
+            }
+            *init = InitExpr(i, expected);
+        } else {
+            *init = InitExpr(i, globals[i].type());
+        }
         break;
       }
       default: {
         return d.fail("unexpected initializer expression");
       }
     }
 
     if (expected != init->type()) {
@@ -2101,19 +2103,17 @@ DecodeGlobalSection(Decoder& d, ModuleEn
     for (uint32_t i = 0; i < numDefs; i++) {
         ValType type;
         bool isMutable;
         if (!DecodeGlobalType(d, env->types, env->gcTypesEnabled(), &type, &isMutable)) {
             return false;
         }
 
         InitExpr initializer;
-        if (!DecodeInitializerExpression(d, env->gcTypesEnabled(), env->globals, type,
-                                         env->types.length(), &initializer))
-        {
+        if (!DecodeInitializerExpression(d, env, type, &initializer)) {
             return false;
         }
 
         env->globals.infallibleAppend(GlobalDesc(initializer, isMutable));
     }
 
     return d.finishSection(*range, "global");
 }
@@ -2355,19 +2355,17 @@ DecodeElemSection(Decoder& d, ModuleEnvi
         }
 
         seg->tableIndex = tableIndex;
 
         if (initializerKind == InitializerKind::Active ||
             initializerKind == InitializerKind::ActiveWithIndex)
         {
             InitExpr offset;
-            if (!DecodeInitializerExpression(d, env->gcTypesEnabled(), env->globals, ValType::I32,
-                                             env->types.length(), &offset))
-            {
+            if (!DecodeInitializerExpression(d, env, ValType::I32, &offset)) {
                 return false;
             }
             seg->offsetIfActive.emplace(offset);
         }
 
         uint32_t numElems;
         if (!d.readVarU32(&numElems)) {
             return d.fail("expected segment size");
@@ -2614,19 +2612,17 @@ DecodeDataSection(Decoder& d, ModuleEnvi
             }
         }
 
         DataSegmentEnv seg;
         if (initializerKind == InitializerKind::Active ||
             initializerKind == InitializerKind::ActiveWithIndex)
         {
             InitExpr segOffset;
-            if (!DecodeInitializerExpression(d, env->gcTypesEnabled(), env->globals, ValType::I32,
-                                             env->types.length(), &segOffset))
-            {
+            if (!DecodeInitializerExpression(d, env, ValType::I32, &segOffset)) {
                 return false;
             }
             seg.offsetIfActive.emplace(segOffset);
         }
 
         if (!d.readVarU32(&seg.length)) {
             return d.fail("expected segment size");
         }
--- a/js/src/wasm/WasmValidate.h
+++ b/js/src/wasm/WasmValidate.h
@@ -247,16 +247,29 @@ struct ModuleEnvironment
         return kind == ModuleKind::AsmJS;
     }
     bool debugEnabled() const {
         return compilerEnv->debug() == DebugEnabled::True;
     }
     bool funcIsImport(uint32_t funcIndex) const {
         return funcIndex < funcImportGlobalDataOffsets.length();
     }
+    bool isRefSubtypeOf(ValType one, ValType two) const {
+        MOZ_ASSERT(one.isReference());
+        MOZ_ASSERT(two.isReference());
+        MOZ_ASSERT(gcTypesEnabled() == HasGcTypes::True);
+        return one == two || two == ValType::AnyRef || one == ValType::NullRef ||
+               (one.isRef() && two.isRef() && isStructPrefixOf(two, one));
+    }
+
+  private:
+    bool isStructPrefixOf(ValType a, ValType b) const {
+        const StructType& other = types[a.refTypeIndex()].structType();
+        return types[b.refTypeIndex()].structType().hasPrefix(other);
+    }
 };
 
 // The Encoder class appends bytes to the Bytes object it is given during
 // construction. The client is responsible for the Bytes's lifetime and must
 // keep the Bytes alive as long as the Encoder is used.
 
 class Encoder
 {