Bug 1546138 - Baldr: add FuncRef to ValType (r=lth)
☠☠ backed out by e5562f9f81ca ☠ ☠
authorLuke Wagner <luke@mozilla.com>
Mon, 29 Apr 2019 19:13:55 -0500
changeset 532321 da9544b976b137d7c95d37d8bb695a712abd8af4
parent 532320 77be2a5365737eca914cfcf06129cf21f4024c6c
child 532322 b60f1ed65b1aa52865eac106b6f9838352811d9e
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslth
bugs1546138
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1546138 - Baldr: add FuncRef to ValType (r=lth) Differential Revision: https://phabricator.services.mozilla.com/D28724 *** Bug 1546138 - Baldr: remove (ref T) special case from Val union (r=lth) Differential Revision: https://phabricator.services.mozilla.com/D29298
js/src/jit-test/tests/wasm/funcref.js
js/src/jit-test/tests/wasm/tables.js
js/src/jit/CodeGenerator.cpp
js/src/jit/MCallOptimize.cpp
js/src/js.msg
js/src/wasm/AsmJS.cpp
js/src/wasm/WasmAST.h
js/src/wasm/WasmBaselineCompile.cpp
js/src/wasm/WasmBuiltins.cpp
js/src/wasm/WasmFrameIter.cpp
js/src/wasm/WasmInstance.cpp
js/src/wasm/WasmInstance.h
js/src/wasm/WasmIonCompile.cpp
js/src/wasm/WasmJS.cpp
js/src/wasm/WasmJS.h
js/src/wasm/WasmModule.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
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/funcref.js
@@ -0,0 +1,142 @@
+const {Module,Instance,Global,RuntimeError} = WebAssembly;
+
+const badWasmFunc = /can only pass WebAssembly exported functions to funcref/;
+const typeErr = /type mismatch/;
+
+
+// Validation:
+
+wasmEvalText(`(module (func (local anyref funcref) (local.set 0 (local.get 1))))`);
+wasmEvalText(`(module (func (local funcref funcref) (local.set 0 (local.get 1))))`);
+wasmEvalText(`(module (func (local funcref) (local.set 0 (ref.null))))`);
+wasmFailValidateText(`(module (func (local funcref anyref) (local.set 0 (local.get 1))))`, typeErr);
+wasmEvalText(`(module (global (mut funcref) (ref.null)) (func (param funcref) (global.set 0 (local.get 0))))`);
+wasmEvalText(`(module (global (mut anyref) (ref.null)) (func (param funcref) (global.set 0 (local.get 0))))`);
+wasmFailValidateText(`(module (global (mut funcref) (ref.null)) (func (param anyref) (global.set 0 (local.get 0))))`, typeErr);
+wasmEvalText(`(module (func (param funcref)) (func (param funcref) (call 0 (local.get 0))))`);
+wasmEvalText(`(module (func (param anyref)) (func (param funcref) (call 0 (local.get 0))))`);
+wasmFailValidateText(`(module (func (param funcref)) (func (param anyref) (call 0 (local.get 0))))`, typeErr);
+wasmEvalText(`(module (func (param funcref) (result funcref) (block funcref (local.get 0) (br 0))))`);
+wasmEvalText(`(module (func (param funcref) (result anyref) (block anyref (local.get 0) (br 0))))`);
+wasmFailValidateText(`(module (func (param anyref) (result anyref) (block funcref (local.get 0) (br 0))))`, typeErr);
+wasmEvalText(`(module (func (param funcref funcref) (result funcref) (select (local.get 0) (local.get 1) (i32.const 0))))`);
+wasmEvalText(`(module (func (param anyref funcref) (result anyref) (select (local.get 0) (local.get 1) (i32.const 0))))`);
+wasmEvalText(`(module (func (param funcref anyref) (result anyref) (select (local.get 0) (local.get 1) (i32.const 0))))`);
+wasmFailValidateText(`(module (func (param anyref funcref) (result funcref) (select (local.get 0) (local.get 1) (i32.const 0))))`, typeErr);
+wasmFailValidateText(`(module (func (param funcref anyref) (result funcref) (select (local.get 0) (local.get 1) (i32.const 0))))`, typeErr);
+
+
+// Runtime:
+
+var m = new Module(wasmTextToBinary(`(module (func (export "wasmFun")))`));
+const wasmFun1 = new Instance(m).exports.wasmFun;
+const wasmFun2 = new Instance(m).exports.wasmFun;
+const wasmFun3 = new Instance(m).exports.wasmFun;
+
+var run = wasmEvalText(`(module
+    (global (mut funcref) (ref.null))
+    (func (param $x funcref) (param $test i32) (result funcref)
+      local.get $x
+      global.get 0
+      local.get $test
+      select
+    )
+    (func (export "run") (param $a funcref) (param $b funcref) (param $c funcref) (param $test1 i32) (param $test2 i32) (result funcref)
+      local.get $a
+      global.set 0
+      block funcref
+        local.get $b
+        local.get $test1
+        br_if 0
+        drop
+        local.get $c
+      end
+      local.get $test2
+      call 0
+    )
+)`).exports.run;
+assertEq(run(wasmFun1, wasmFun2, wasmFun3, false, false), wasmFun1);
+assertEq(run(wasmFun1, wasmFun2, wasmFun3, true, false), wasmFun1);
+assertEq(run(wasmFun1, wasmFun2, wasmFun3, true, true), wasmFun2);
+assertEq(run(wasmFun1, wasmFun2, wasmFun3, false, true), wasmFun3);
+
+var run = wasmEvalText(`(module
+  (type $t0 (func (param anyref) (result anyref)))
+  (type $t1 (func (param funcref) (result anyref)))
+  (type $t2 (func (param anyref) (result funcref)))
+  (type $t3 (func (param funcref funcref) (result funcref)))
+  (func $f0 (type $t0) ref.null)
+  (func $f1 (type $t1) ref.null)
+  (func $f2 (type $t2) ref.null)
+  (func $f3 (type $t3) ref.null)
+  (table funcref (elem $f0 $f1 $f2 $f3))
+  (func (export "run") (param i32 i32) (result anyref)
+    block $b3 block $b2 block $b1 block $b0
+      local.get 0
+      br_table $b0 $b1 $b2 $b3
+    end $b0
+      ref.null
+      local.get 1
+      call_indirect $t0
+      return
+    end $b1
+      ref.null
+      local.get 1
+      call_indirect $t1
+      return
+    end $b2
+      ref.null
+      local.get 1
+      call_indirect $t2
+      return
+    end $b3
+      ref.null
+      ref.null
+      local.get 1
+      call_indirect $t3
+      return
+  )
+)`).exports.run;
+
+for (var i = 0; i < 4; i++) {
+  for (var j = 0; j < 4; j++) {
+    if (i == j)
+      assertEq(run(i, j), null);
+    else
+      assertErrorMessage(() => run(i, j), RuntimeError, /indirect call signature mismatch/);
+  }
+}
+
+
+// JS API:
+
+const wasmFun = wasmEvalText(`(module (func (export "x")))`).exports.x;
+
+var run = wasmEvalText(`(module (func (export "run") (param funcref) (result funcref) (local.get 0)))`).exports.run;
+assertEq(run(wasmFun), wasmFun);
+assertEq(run(null), null);
+assertErrorMessage(() => run(() => {}), TypeError, badWasmFunc);
+
+var importReturnValue;
+var importFun = () => importReturnValue;
+var run = wasmEvalText(`(module (func (import "" "i") (result funcref)) (func (export "run") (result funcref) (call 0)))`, {'':{i:importFun}}).exports.run;
+importReturnValue = wasmFun;
+assertEq(run(), wasmFun);
+importReturnValue = null;
+assertEq(run(), null);
+importReturnValue = undefined;
+assertErrorMessage(() => run(), TypeError, badWasmFunc);
+importReturnValue = () => {};
+assertErrorMessage(() => run(), TypeError, badWasmFunc);
+
+var g = new Global({value:'funcref', mutable:true}, wasmFun);
+assertEq(g.value, wasmFun);
+g.value = null;
+assertEq(g.value, null);
+Math.sin();
+assertErrorMessage(() => g.value = () => {}, TypeError, badWasmFunc);
+var g = new Global({value:'funcref', mutable:true}, null);
+assertEq(g.value, null);
+g.value = wasmFun;
+assertEq(g.value, wasmFun);
+assertErrorMessage(() => new Global({value:'funcref'}, () => {}), TypeError, badWasmFunc);
--- a/js/src/jit-test/tests/wasm/tables.js
+++ b/js/src/jit-test/tests/wasm/tables.js
@@ -1,15 +1,17 @@
 const Module = WebAssembly.Module;
 const Instance = WebAssembly.Instance;
 const Table = WebAssembly.Table;
 const Memory = WebAssembly.Memory;
 const LinkError = WebAssembly.LinkError;
 const RuntimeError = WebAssembly.RuntimeError;
 
+const badFuncRefError = /can only pass WebAssembly exported functions to funcref/;
+
 var callee = i => `(func $f${i} (result i32) (i32.const ${i}))`;
 
 wasmFailValidateText(`(module (elem (i32.const 0) $f0) ${callee(0)})`, /elem segment requires a table section/);
 wasmFailValidateText(`(module (table 10 funcref) (elem (i32.const 0) 0))`, /table element out of range/);
 wasmFailValidateText(`(module (table 10 funcref) (func) (elem (i32.const 0) 0 1))`, /table element out of range/);
 wasmFailValidateText(`(module (table 10 funcref) (func) (elem (f32.const 0) 0) ${callee(0)})`, /type mismatch/);
 
 assertErrorMessage(() => wasmEvalText(`(module (table 10 funcref) (elem (i32.const 10) $f0) ${callee(0)})`), LinkError, /elem segment does not fit/);
@@ -117,18 +119,18 @@ tbl.set(1, e2.g);
 tbl.set(2, e3.h);
 var e4 = wasmEvalText(`(module (import "a" "b" (table 3 funcref)) ${caller})`, {a:{b:tbl}}).exports;
 assertEq(e4.call(0), 42);
 assertErrorMessage(() => e4.call(1), RuntimeError, /indirect call signature mismatch/);
 assertEq(e4.call(2), 13);
 
 var asmjsFun = (function() { "use asm"; function f() {} return f })();
 assertEq(isAsmJSFunction(asmjsFun), isAsmJSCompilationAvailable());
-assertErrorMessage(() => tbl.set(0, asmjsFun), TypeError, /can only assign WebAssembly exported functions/);
-assertErrorMessage(() => tbl.grow(1, asmjsFun), TypeError, /bad initializer to funcref table/);
+assertErrorMessage(() => tbl.set(0, asmjsFun), TypeError, badFuncRefError);
+assertErrorMessage(() => tbl.grow(1, asmjsFun), TypeError, badFuncRefError);
 
 var m = new Module(wasmTextToBinary(`(module
     (type $i2i (func (param i32) (result i32)))
     (import "a" "mem" (memory 1))
     (import "a" "tbl" (table 10 funcref))
     (import $imp "a" "imp" (result i32))
     (func $call (param $i i32) (result i32)
         (i32.add
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -13913,16 +13913,17 @@ void CodeGenerator::emitIonToWasmCallBas
       case wasm::ValType::I32:
       case wasm::ValType::F32:
       case wasm::ValType::F64:
         argMir = ToMIRType(sig.args()[i]);
         break;
       case wasm::ValType::I64:
       case wasm::ValType::Ref:
       case wasm::ValType::AnyRef:
+      case wasm::ValType::FuncRef:
         // 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);
@@ -13971,16 +13972,17 @@ void CodeGenerator::emitIonToWasmCallBas
       MOZ_ASSERT(ToFloatRegister(lir->output()) == ReturnFloat32Reg);
       break;
     case wasm::ExprType::F64:
       MOZ_ASSERT(lir->mir()->type() == MIRType::Double);
       MOZ_ASSERT(ToFloatRegister(lir->output()) == ReturnDoubleReg);
       break;
     case wasm::ExprType::Ref:
     case wasm::ExprType::AnyRef:
+    case wasm::ExprType::FuncRef:
     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");
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -4267,16 +4267,17 @@ IonBuilder::InliningResult IonBuilder::i
       case wasm::ValType::F32:
         conversion = MToFloat32::New(alloc(), arg);
         break;
       case wasm::ValType::F64:
         conversion = MToDouble::New(alloc(), arg);
         break;
       case wasm::ValType::I64:
       case wasm::ValType::AnyRef:
+      case wasm::ValType::FuncRef:
       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);
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -396,28 +396,27 @@ MSG_DEF(JSMSG_WASM_INT_DIVIDE_BY_ZERO, 0
 MSG_DEF(JSMSG_WASM_OUT_OF_BOUNDS,      0, JSEXN_WASMRUNTIMEERROR, "index out of bounds")
 MSG_DEF(JSMSG_WASM_UNALIGNED_ACCESS,   0, JSEXN_WASMRUNTIMEERROR, "unaligned memory access")
 MSG_DEF(JSMSG_WASM_WAKE_OVERFLOW,      0, JSEXN_WASMRUNTIMEERROR, "too many woken agents")
 MSG_DEF(JSMSG_WASM_DROPPED_DATA_SEG,   0, JSEXN_WASMRUNTIMEERROR, "use of dropped data segment")
 MSG_DEF(JSMSG_WASM_DROPPED_ELEM_SEG,   0, JSEXN_WASMRUNTIMEERROR, "use of dropped element segment")
 MSG_DEF(JSMSG_WASM_DEREF_NULL,         0, JSEXN_WASMRUNTIMEERROR, "dereferencing null pointer")
 MSG_DEF(JSMSG_WASM_BAD_RANGE ,         2, JSEXN_RANGEERR,    "bad {0} {1}")
 MSG_DEF(JSMSG_WASM_BAD_GROW,           1, JSEXN_RANGEERR,    "failed to grow {0}")
-MSG_DEF(JSMSG_WASM_BAD_TBL_GROW_INIT,  1, JSEXN_TYPEERR,     "bad initializer to {0} table")
 MSG_DEF(JSMSG_WASM_TABLE_OUT_OF_BOUNDS, 0, JSEXN_RANGEERR,   "table index out of bounds")
 MSG_DEF(JSMSG_WASM_BAD_UINT32,         2, JSEXN_TYPEERR,     "bad {0} {1}")
 MSG_DEF(JSMSG_WASM_BAD_BUF_ARG,        0, JSEXN_TYPEERR,     "first argument must be an ArrayBuffer or typed array object")
 MSG_DEF(JSMSG_WASM_BAD_MOD_ARG,        0, JSEXN_TYPEERR,     "first argument must be a WebAssembly.Module")
 MSG_DEF(JSMSG_WASM_BAD_BUF_MOD_ARG,    0, JSEXN_TYPEERR,     "first argument must be a WebAssembly.Module, ArrayBuffer or typed array object")
 MSG_DEF(JSMSG_WASM_BAD_DESC_ARG,       1, JSEXN_TYPEERR,     "first argument must be a {0} descriptor")
 MSG_DEF(JSMSG_WASM_BAD_ELEMENT,        0, JSEXN_TYPEERR,     "\"element\" property of table descriptor must be \"funcref\"")
 MSG_DEF(JSMSG_WASM_BAD_ELEMENT_GENERALIZED, 0, JSEXN_TYPEERR, "\"element\" property of table descriptor must be \"funcref\" or \"anyref\"")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_ARG,     0, JSEXN_TYPEERR,     "second argument must be an object")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_FIELD,   1, JSEXN_TYPEERR,     "import object field '{0}' is not an Object")
-MSG_DEF(JSMSG_WASM_BAD_TABLE_VALUE,    0, JSEXN_TYPEERR,     "can only assign WebAssembly exported functions to Table")
+MSG_DEF(JSMSG_WASM_BAD_FUNCREF_VALUE,  0, JSEXN_TYPEERR,     "can only pass WebAssembly exported functions to funcref")
 MSG_DEF(JSMSG_WASM_BAD_I64_TYPE,       0, JSEXN_TYPEERR,     "cannot pass i64 to or from JS")
 MSG_DEF(JSMSG_WASM_BAD_GLOBAL_TYPE,    0, JSEXN_TYPEERR,     "bad type for a WebAssembly.Global")
 MSG_DEF(JSMSG_WASM_NO_TRANSFER,        0, JSEXN_TYPEERR,     "cannot transfer WebAssembly/asm.js ArrayBuffer")
 MSG_DEF(JSMSG_WASM_TEXT_FAIL,          1, JSEXN_SYNTAXERR,   "wasm text error: {0}")
 MSG_DEF(JSMSG_WASM_MISSING_MAXIMUM,    0, JSEXN_TYPEERR,     "'shared' is true but maximum is not specified")
 MSG_DEF(JSMSG_WASM_GLOBAL_IMMUTABLE,   0, JSEXN_TYPEERR,     "can't set value of immutable global")
 
 // Proxy
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -6557,17 +6557,18 @@ static bool ValidateGlobalVariable(JSCon
           if (!ToNumber(cx, v, &d)) {
             return false;
           }
           val->emplace(d);
           return true;
         }
         case ValType::Ref:
         case ValType::NullRef:
-        case ValType::AnyRef: {
+        case ValType::AnyRef:
+        case ValType::FuncRef: {
           MOZ_CRASH("not available in asm.js");
         }
       }
     }
   }
 
   MOZ_CRASH("unreachable");
 }
--- a/js/src/wasm/WasmAST.h
+++ b/js/src/wasm/WasmAST.h
@@ -122,19 +122,21 @@ class AstValType {
       which_ = IsValType;
       type_ = ValType(ValType::Ref, ref.index());
     } else {
       which_ = IsAstRef;
       ref_ = ref;
     }
   }
 
-  bool isRefType() const {
+#ifdef ENABLE_WASM_GC
+  bool isNarrowType() const {
     return code() == ValType::AnyRef || code() == ValType::Ref;
   }
+#endif
 
   bool isValid() const { return !(which_ == IsValType && !type_.isValid()); }
 
   bool isResolved() const { return which_ == IsValType; }
 
   AstRef& asRef() { return ref_; }
 
   void resolve() {
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -1051,16 +1051,17 @@ void BaseLocalIter::settle() {
   MOZ_ASSERT(argsIter_.done());
   if (index_ < locals_.length()) {
     switch (locals_[index_].code()) {
       case ValType::I32:
       case ValType::I64:
       case ValType::F32:
       case ValType::F64:
       case ValType::Ref:
+      case ValType::FuncRef:
       case ValType::AnyRef:
         // TODO/AnyRef-boxing: With boxed immediates and strings, the
         // debugger must be made aware that AnyRef != Pointer.
         ASSERT_ANYREF_IS_JSOBJECT;
         mirType_ = ToMIRType(locals_[index_]);
         frameOffset_ = pushLocal(MIRTypeToSize(mirType_));
         break;
       case ValType::NullRef:
@@ -2778,16 +2779,17 @@ class BaseCompiler final : public BaseCo
   void maybeReserveJoinRegI(ExprType type) {
     switch (type.code()) {
       case ExprType::I32:
         needI32(joinRegI32_);
         break;
       case ExprType::I64:
         needI64(joinRegI64_);
         break;
+      case ExprType::FuncRef:
       case ExprType::AnyRef:
       case ExprType::NullRef:
       case ExprType::Ref:
         needRef(joinRegPtr_);
         break;
       default:;
     }
   }
@@ -2795,16 +2797,17 @@ class BaseCompiler final : public BaseCo
   void maybeUnreserveJoinRegI(ExprType type) {
     switch (type.code()) {
       case ExprType::I32:
         freeI32(joinRegI32_);
         break;
       case ExprType::I64:
         freeI64(joinRegI64_);
         break;
+      case ExprType::FuncRef:
       case ExprType::AnyRef:
       case ExprType::NullRef:
       case ExprType::Ref:
         freeRef(joinRegPtr_);
         break;
       default:;
     }
   }
@@ -2820,16 +2823,17 @@ class BaseCompiler final : public BaseCo
       case ExprType::F32:
         needF32(joinRegF32_);
         break;
       case ExprType::F64:
         needF64(joinRegF64_);
         break;
       case ExprType::Ref:
       case ExprType::NullRef:
+      case ExprType::FuncRef:
       case ExprType::AnyRef:
         needRef(joinRegPtr_);
         break;
       default:
         break;
     }
   }
 
@@ -2844,16 +2848,17 @@ class BaseCompiler final : public BaseCo
       case ExprType::F32:
         freeF32(joinRegF32_);
         break;
       case ExprType::F64:
         freeF64(joinRegF64_);
         break;
       case ExprType::Ref:
       case ExprType::NullRef:
+      case ExprType::FuncRef:
       case ExprType::AnyRef:
         freeRef(joinRegPtr_);
         break;
       default:
         break;
     }
   }
 
@@ -3773,16 +3778,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::FuncRef:
       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");
@@ -3811,16 +3817,17 @@ class BaseCompiler final : public BaseCo
         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::FuncRef:
       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");
@@ -4234,16 +4241,17 @@ class BaseCompiler final : public BaseCo
         break;
       case ExprType::F64:
         masm.storeDouble(RegF64(ReturnDoubleReg), resultsAddress);
         break;
       case ExprType::F32:
         masm.storeFloat32(RegF32(ReturnFloat32Reg), resultsAddress);
         break;
       case ExprType::Ref:
+      case ExprType::FuncRef:
       case ExprType::AnyRef:
         masm.storePtr(RegPtr(ReturnReg), resultsAddress);
         break;
       case ExprType::NullRef:
       default:
         MOZ_CRASH("Function return type");
     }
   }
@@ -4264,16 +4272,17 @@ class BaseCompiler final : public BaseCo
         break;
       case ExprType::F64:
         masm.loadDouble(resultsAddress, RegF64(ReturnDoubleReg));
         break;
       case ExprType::F32:
         masm.loadFloat32(resultsAddress, RegF32(ReturnFloat32Reg));
         break;
       case ExprType::Ref:
+      case ExprType::FuncRef:
       case ExprType::AnyRef:
         masm.loadPtr(resultsAddress, RegPtr(ReturnReg));
         break;
       case ExprType::NullRef:
       default:
         MOZ_CRASH("Function return type");
     }
   }
@@ -4576,16 +4585,17 @@ class BaseCompiler final : public BaseCo
           }
 #endif
           case ABIArg::Uninitialized:
             MOZ_CRASH("Uninitialized ABIArg kind");
         }
         break;
       }
       case ValType::Ref:
+      case ValType::FuncRef:
       case ValType::AnyRef: {
         ABIArg argLoc = call->abi.next(MIRType::RefOrNull);
         if (argLoc.kind() == ABIArg::Stack) {
           ScratchPtr scratch(*this);
           loadRef(arg, scratch);
           masm.storePtr(scratch, Address(masm.getStackPointer(),
                                          argLoc.offsetFromArgBase()));
         } else {
@@ -8548,16 +8558,17 @@ void BaseCompiler::doReturn(ExprType typ
     case ExprType::F32: {
       RegF32 rv = popF32(RegF32(ReturnFloat32Reg));
       returnCleanup(popStack);
       freeF32(rv);
       break;
     }
     case ExprType::Ref:
     case ExprType::NullRef:
+    case ExprType::FuncRef:
     case ExprType::AnyRef: {
       RegPtr rv = popRef(RegPtr(ReturnReg));
       returnCleanup(popStack);
       freeRef(rv);
       break;
     }
     default: {
       MOZ_CRASH("Function return type");
@@ -8990,16 +9001,17 @@ bool BaseCompiler::emitGetLocal() {
       break;
     case ValType::F64:
       pushLocalF64(slot);
       break;
     case ValType::F32:
       pushLocalF32(slot);
       break;
     case ValType::Ref:
+    case ValType::FuncRef:
     case ValType::AnyRef:
       pushLocalRef(slot);
       break;
     case ValType::NullRef:
     default:
       MOZ_CRASH("Local variable type");
   }
 
@@ -9054,16 +9066,17 @@ bool BaseCompiler::emitSetOrTeeLocal(uin
       if (isSetLocal) {
         freeF32(rv);
       } else {
         pushF32(rv);
       }
       break;
     }
     case ValType::Ref:
+    case ValType::FuncRef:
     case ValType::AnyRef: {
       RegPtr rv = popRef();
       syncLocal(slot);
       fr.storeLocalPtr(rv, localFromSlot(slot, MIRType::RefOrNull));
       if (isSetLocal) {
         freeRef(rv);
       } else {
         pushRef(rv);
@@ -9119,22 +9132,22 @@ bool BaseCompiler::emitGetGlobal() {
         break;
       case ValType::F32:
         pushF32(value.f32());
         break;
       case ValType::F64:
         pushF64(value.f64());
         break;
       case ValType::Ref:
+      case ValType::FuncRef:
+      case ValType::AnyRef:
+        pushRef(intptr_t(value.ref().forCompiledCode()));
+        break;
       case ValType::NullRef:
-        pushRef(intptr_t(value.ref()));
-        break;
-      case ValType::AnyRef:
-        pushRef(intptr_t(value.anyref().forCompiledCode()));
-        break;
+        MOZ_CRASH("NullRef not expressible");
       default:
         MOZ_CRASH("Global constant type");
     }
     return true;
   }
 
   switch (global.type().code()) {
     case ValType::I32: {
@@ -9161,16 +9174,17 @@ bool BaseCompiler::emitGetGlobal() {
     case ValType::F64: {
       RegF64 rv = needF64();
       ScratchI32 tmp(*this);
       masm.loadDouble(addressOfGlobalVar(global, tmp), rv);
       pushF64(rv);
       break;
     }
     case ValType::Ref:
+    case ValType::FuncRef:
     case ValType::AnyRef: {
       RegPtr rv = needRef();
       ScratchI32 tmp(*this);
       masm.loadPtr(addressOfGlobalVar(global, tmp), rv);
       pushRef(rv);
       break;
     }
     case ValType::NullRef:
@@ -9220,16 +9234,17 @@ bool BaseCompiler::emitSetGlobal() {
     case ValType::F64: {
       RegF64 rv = popF64();
       ScratchI32 tmp(*this);
       masm.storeDouble(rv, addressOfGlobalVar(global, tmp));
       freeF64(rv);
       break;
     }
     case ValType::Ref:
+    case ValType::FuncRef:
     case ValType::AnyRef: {
       RegPtr valueAddr(PreBarrierReg);
       needRef(valueAddr);
       {
         ScratchI32 tmp(*this);
         masm.computeEffectiveAddress(addressOfGlobalVar(global, tmp),
                                      valueAddr);
       }
@@ -9627,16 +9642,17 @@ bool BaseCompiler::emitSelect() {
       moveF64(rs, r);
       masm.bind(&done);
       freeF64(rs);
       pushF64(r);
       break;
     }
     case ValType::Ref:
     case ValType::NullRef:
+    case ValType::FuncRef:
     case ValType::AnyRef: {
       RegPtr r, rs;
       pop2xRef(&r, &rs);
       emitBranchPerform(&b);
       moveRef(rs, r);
       masm.bind(&done);
       freeRef(rs);
       pushRef(r);
@@ -10540,16 +10556,17 @@ bool BaseCompiler::emitStructNew() {
       }
       case ValType::F64: {
         RegF64 r = popF64();
         masm.storeDouble(r, Address(rdata, offs));
         freeF64(r);
         break;
       }
       case ValType::Ref:
+      case ValType::FuncRef:
       case ValType::AnyRef: {
         RegPtr value = popRef();
         masm.storePtr(value, Address(rdata, offs));
 
         // A write barrier is needed here for the extremely unlikely case
         // that the object is allocated in the tenured area - a result of
         // a GC artifact.
 
@@ -10657,16 +10674,17 @@ bool BaseCompiler::emitStructGet() {
     }
     case ValType::F64: {
       RegF64 r = needF64();
       masm.loadDouble(Address(rp, offs), r);
       pushF64(r);
       break;
     }
     case ValType::Ref:
+    case ValType::FuncRef:
     case ValType::AnyRef: {
       RegPtr r = needRef();
       masm.loadPtr(Address(rp, offs), r);
       pushRef(r);
       break;
     }
     case ValType::NullRef: {
       MOZ_CRASH("NullRef not expressible");
@@ -10718,16 +10736,17 @@ bool BaseCompiler::emitStructSet() {
       break;
     case ValType::F32:
       rf = popF32();
       break;
     case ValType::F64:
       rd = popF64();
       break;
     case ValType::Ref:
+    case ValType::FuncRef:
     case ValType::AnyRef:
       rr = popRef();
       break;
     case ValType::NullRef:
       MOZ_CRASH("NullRef not expressible");
     default:
       MOZ_CRASH("Unexpected field type");
   }
@@ -10761,16 +10780,17 @@ bool BaseCompiler::emitStructSet() {
       break;
     }
     case ValType::F64: {
       masm.storeDouble(rd, Address(rp, offs));
       freeF64(rd);
       break;
     }
     case ValType::Ref:
+    case ValType::FuncRef:
     case ValType::AnyRef: {
       masm.computeEffectiveAddress(Address(rp, offs), valueAddr);
       // emitBarrieredStore consumes valueAddr
       if (!emitBarrieredStore(Some(rp), valueAddr, rr,
                               structType.fields_[fieldIndex].type)) {
         return false;
       }
       freeRef(rr);
@@ -10797,16 +10817,20 @@ bool BaseCompiler::emitStructNarrow() {
   if (!iter_.readStructNarrow(&inputType, &outputType, &nothing)) {
     return false;
   }
 
   if (deadCode_) {
     return true;
   }
 
+  // Currently not supported by struct.narrow validation.
+  MOZ_ASSERT(inputType != ValType::FuncRef);
+  MOZ_ASSERT(outputType != ValType::FuncRef);
+
   // AnyRef -> AnyRef is a no-op, just leave the value on the stack.
 
   if (inputType == ValType::AnyRef && outputType == ValType::AnyRef) {
     return true;
   }
 
   RegPtr rp = popRef();
 
--- a/js/src/wasm/WasmBuiltins.cpp
+++ b/js/src/wasm/WasmBuiltins.cpp
@@ -614,16 +614,19 @@ void* wasm::AddressOf(SymbolicAddress im
       *abiType = Args_General4;
       return FuncCast(Instance::callImport_i32, *abiType);
     case SymbolicAddress::CallImport_I64:
       *abiType = Args_General4;
       return FuncCast(Instance::callImport_i64, *abiType);
     case SymbolicAddress::CallImport_F64:
       *abiType = Args_General4;
       return FuncCast(Instance::callImport_f64, *abiType);
+    case SymbolicAddress::CallImport_FuncRef:
+      *abiType = Args_General4;
+      return FuncCast(Instance::callImport_funcref, *abiType);
     case SymbolicAddress::CallImport_AnyRef:
       *abiType = Args_General4;
       return FuncCast(Instance::callImport_anyref, *abiType);
     case SymbolicAddress::CoerceInPlace_ToInt32:
       *abiType = Args_General1;
       return FuncCast(CoerceInPlace_ToInt32, *abiType);
     case SymbolicAddress::CoerceInPlace_ToNumber:
       *abiType = Args_General1;
@@ -832,16 +835,17 @@ bool wasm::NeedsBuiltinThunk(SymbolicAdd
   switch (sym) {
     case SymbolicAddress::HandleDebugTrap:  // GenerateDebugTrapStub
     case SymbolicAddress::HandleThrow:      // GenerateThrowStub
     case SymbolicAddress::HandleTrap:       // GenerateTrapExit
     case SymbolicAddress::CallImport_Void:  // GenerateImportInterpExit
     case SymbolicAddress::CallImport_I32:
     case SymbolicAddress::CallImport_I64:
     case SymbolicAddress::CallImport_F64:
+    case SymbolicAddress::CallImport_FuncRef:
     case SymbolicAddress::CallImport_AnyRef:
     case SymbolicAddress::CoerceInPlace_ToInt32:  // GenerateImportJitExit
     case SymbolicAddress::CoerceInPlace_ToNumber:
 #if defined(JS_CODEGEN_MIPS32)
     case SymbolicAddress::js_jit_gAtomic64Lock:
 #endif
 #ifdef WASM_CODEGEN_DEBUG
     case SymbolicAddress::PrintI32:
--- a/js/src/wasm/WasmFrameIter.cpp
+++ b/js/src/wasm/WasmFrameIter.cpp
@@ -1256,16 +1256,17 @@ static const char* ThunkedNativeToDescri
   switch (func) {
     case SymbolicAddress::HandleDebugTrap:
     case SymbolicAddress::HandleThrow:
     case SymbolicAddress::HandleTrap:
     case SymbolicAddress::CallImport_Void:
     case SymbolicAddress::CallImport_I32:
     case SymbolicAddress::CallImport_I64:
     case SymbolicAddress::CallImport_F64:
+    case SymbolicAddress::CallImport_FuncRef:
     case SymbolicAddress::CallImport_AnyRef:
     case SymbolicAddress::CoerceInPlace_ToInt32:
     case SymbolicAddress::CoerceInPlace_ToNumber:
       MOZ_ASSERT(!NeedsBuiltinThunk(func),
                  "not in sync with NeedsBuiltinThunk");
       break;
     case SymbolicAddress::ToInt32:
       return "call to asm.js native ToInt32 coercion (in wasm)";
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -126,16 +126,17 @@ bool Instance::callImport(JSContext* cx,
         args[i].set(Int32Value(*(int32_t*)&argv[i]));
         break;
       case ValType::F32:
         args[i].set(JS::CanonicalizedDoubleValue(*(float*)&argv[i]));
         break;
       case ValType::F64:
         args[i].set(JS::CanonicalizedDoubleValue(*(double*)&argv[i]));
         break;
+      case ValType::FuncRef:
       case ValType::AnyRef: {
         args[i].set(UnboxAnyRef(AnyRef::fromCompiledCode(*(void**)&argv[i])));
         break;
       }
       case ValType::Ref:
         MOZ_CRASH("temporarily unsupported Ref type in callImport");
       case ValType::I64:
         MOZ_CRASH("unhandled type in callImport");
@@ -214,17 +215,17 @@ bool Instance::callImport(JSContext* cx,
         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::FuncRef:
       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)) {
@@ -304,16 +305,34 @@ Instance::callImport_anyref(Instance* in
   RootedAnyRef result(cx, AnyRef::null());
   if (!BoxAnyRef(cx, rval, &result)) {
     return false;
   }
   *(void**)argv = result.get().forCompiledCode();
   return true;
 }
 
+/* static */ int32_t /* 0 to signal trap; 1 to signal OK */
+Instance::callImport_funcref(Instance* instance, int32_t funcImportIndex,
+                             int32_t argc, uint64_t* argv) {
+  JSContext* cx = TlsContext.get();
+  RootedValue rval(cx);
+  if (!instance->callImport(cx, funcImportIndex, argc, argv, &rval)) {
+    return false;
+  }
+
+  RootedFunction fun(cx);
+  if (!CheckFuncRefValue(cx, rval, &fun)) {
+    return false;
+  }
+
+  *(void**)argv = fun;
+  return true;
+}
+
 /* static */ uint32_t /* infallible */
 Instance::memoryGrow_i32(Instance* instance, uint32_t delta) {
   MOZ_ASSERT(!instance->isAsmJS());
 
   JSContext* cx = TlsContext.get();
   RootedWasmMemoryObject memory(cx, instance->memory_);
 
   uint32_t ret = WasmMemoryObject::grow(memory, delta, cx);
@@ -1054,17 +1073,17 @@ Instance::structNarrow(Instance* instanc
 // Note, dst must point into nonmoveable storage that is not in the nursery,
 // this matters for the write barriers.  Furthermore, for pointer types the
 // current value of *dst must be null so that only a post-barrier is required.
 //
 // Regarding the destination not being in the nursery, we have these cases.
 // 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.ref_/anyref_ may point to a
+// - WasmInstanceObjects are always tenured and u.ref_ 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.
 //
 // - WasmGlobalObjects are always tenured, and they have a Cell field, so a
 //   post-barrier may be needed for the same reason as above.
 
 void CopyValPostBarriered(uint8_t* dst, const Val& src) {
   switch (src.type().code()) {
@@ -1083,43 +1102,32 @@ void CopyValPostBarriered(uint8_t* dst, 
       memcpy(dst, &x, sizeof(x));
       break;
     }
     case ValType::F64: {
       double x = src.f64();
       memcpy(dst, &x, sizeof(x));
       break;
     }
+    case ValType::Ref:
+    case ValType::FuncRef:
     case ValType::AnyRef: {
       // TODO/AnyRef-boxing: With boxed immediates and strings, the write
       // barrier is going to have to be more complicated.
       ASSERT_ANYREF_IS_JSOBJECT;
       MOZ_ASSERT(*(void**)dst == nullptr,
                  "should be null so no need for a pre-barrier");
-      AnyRef x = src.anyref();
-      memcpy(dst, x.asJSObjectAddress(), sizeof(x));
+      AnyRef x = src.ref();
+      memcpy(dst, x.asJSObjectAddress(), sizeof(*x.asJSObjectAddress()));
       if (!x.isNull()) {
         JSObject::writeBarrierPost((JSObject**)dst, nullptr, x.asJSObject());
       }
       break;
     }
-    case ValType::Ref: {
-      MOZ_ASSERT(*(JSObject**)dst == nullptr,
-                 "should be null so no need for a pre-barrier");
-      JSObject* x = src.ref();
-      memcpy(dst, &x, sizeof(x));
-      if (x) {
-        JSObject::writeBarrierPost((JSObject**)dst, nullptr, x);
-      }
-      break;
-    }
     case ValType::NullRef: {
-      break;
-    }
-    default: {
       MOZ_CRASH("unexpected Val type");
     }
   }
 }
 
 Instance::Instance(JSContext* cx, Handle<WasmInstanceObject*> object,
                    SharedCode code, UniqueTlsData tlsDataIn,
                    HandleWasmMemoryObject memory, SharedTableVector&& tables,
@@ -1397,23 +1405,23 @@ void Instance::tracePrivate(JSTracer* tr
     TraceNullableEdge(trc, &funcImportTls(fi).fun, "wasm import");
   }
 
   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.
+    // Indirect reference globals get traced by the owning WebAssembly.Global.
     if (!global.type().isReference() || global.isConstant() ||
         global.isIndirect()) {
       continue;
     }
     GCPtrObject* obj = (GCPtrObject*)(globalData() + global.offset());
-    TraceNullableEdge(trc, obj, "wasm ref/anyref global");
+    TraceNullableEdge(trc, obj, "wasm reference-typed global");
   }
 
   TraceNullableEdge(trc, &memory_, "wasm buffer");
   structTypeDescrs_.trace(trc);
 }
 
 void Instance::trace(JSTracer* trc) {
   // Technically, instead of having this method, the caller could use
@@ -1651,90 +1659,98 @@ bool Instance::callExport(JSContext* cx,
   // stored in the first element of the array (which, therefore, must have
   // length >= 1).
   Vector<ExportArg, 8> exportArgs(cx);
   if (!exportArgs.resize(Max<size_t>(1, funcType->args().length()))) {
     return false;
   }
 
   ASSERT_ANYREF_IS_JSOBJECT;
-  Rooted<GCVector<JSObject*, 8, SystemAllocPolicy>> anyrefs(cx);
+  Rooted<GCVector<JSObject*, 8, SystemAllocPolicy>> refs(cx);
 
   DebugCodegen(DebugChannel::Function, "wasm-function[%d]; arguments ",
                funcIndex);
   RootedValue v(cx);
   for (size_t i = 0; i < funcType->args().length(); ++i) {
     v = i < args.length() ? args[i] : UndefinedValue();
     switch (funcType->arg(i).code()) {
       case ValType::I32:
         if (!ToInt32(cx, v, (int32_t*)&exportArgs[i])) {
-          DebugCodegen(DebugChannel::Function, "call to ToInt32 failed!\n");
           return false;
         }
         DebugCodegen(DebugChannel::Function, "i32(%d) ",
                      *(int32_t*)&exportArgs[i]);
         break;
       case ValType::I64:
         MOZ_CRASH("unexpected i64 flowing into callExport");
       case ValType::F32:
         if (!RoundFloat32(cx, v, (float*)&exportArgs[i])) {
-          DebugCodegen(DebugChannel::Function,
-                       "call to RoundFloat32 failed!\n");
           return false;
         }
         DebugCodegen(DebugChannel::Function, "f32(%f) ",
                      *(float*)&exportArgs[i]);
         break;
       case ValType::F64:
         if (!ToNumber(cx, v, (double*)&exportArgs[i])) {
-          DebugCodegen(DebugChannel::Function, "call to ToNumber failed!\n");
           return false;
         }
         DebugCodegen(DebugChannel::Function, "f64(%lf) ",
                      *(double*)&exportArgs[i]);
         break;
       case ValType::Ref:
         MOZ_CRASH("temporarily unsupported Ref type in callExport");
+      case ValType::FuncRef: {
+        RootedFunction fun(cx);
+        if (!CheckFuncRefValue(cx, v, &fun)) {
+          return false;
+        }
+        // Store in rooted array until no more GC is possible.
+        ASSERT_ANYREF_IS_JSOBJECT;
+        if (!refs.emplaceBack(fun)) {
+          return false;
+        }
+        DebugCodegen(DebugChannel::Function, "ptr(#%d) ",
+                     int(refs.length() - 1));
+        break;
+      }
       case ValType::AnyRef: {
         RootedAnyRef ar(cx, AnyRef::null());
         if (!BoxAnyRef(cx, v, &ar)) {
-          DebugCodegen(DebugChannel::Function, "call to BoxAnyRef failed!\n");
           return false;
         }
-        // We'll copy the value into the arguments array just before the call;
-        // for now tuck the value away in a rooted array.
+        // Store in rooted array until no more GC is possible.
         ASSERT_ANYREF_IS_JSOBJECT;
-        if (!anyrefs.emplaceBack(ar.get().asJSObject())) {
+        if (!refs.emplaceBack(ar.get().asJSObject())) {
           return false;
         }
         DebugCodegen(DebugChannel::Function, "ptr(#%d) ",
-                     int(anyrefs.length() - 1));
+                     int(refs.length() - 1));
         break;
       }
       case ValType::NullRef: {
         MOZ_CRASH("NullRef not expressible");
       }
     }
   }
 
   DebugCodegen(DebugChannel::Function, "\n");
 
   // Copy over reference values from the rooted array, if any.
-  if (anyrefs.length() > 0) {
+  if (refs.length() > 0) {
     DebugCodegen(DebugChannel::Function, "; ");
     size_t nextRef = 0;
     for (size_t i = 0; i < funcType->args().length(); ++i) {
       if (funcType->arg(i).isReference()) {
         ASSERT_ANYREF_IS_JSOBJECT;
-        *(void**)&exportArgs[i] = (void*)anyrefs[nextRef++];
+        *(void**)&exportArgs[i] = (void*)refs[nextRef++];
         DebugCodegen(DebugChannel::Function, "ptr(#%d) = %p ", int(nextRef - 1),
                      *(void**)&exportArgs[i]);
       }
     }
-    anyrefs.clear();
+    refs.clear();
   }
 
   {
     JitActivation activation(cx);
 
     // Call the per-exported-function trampoline created by GenerateEntry.
     auto funcPtr = JS_DATA_TO_FUNC_PTR(ExportFuncPtr, interpEntry);
     if (!CALL_GENERATED_2(funcPtr, exportArgs.begin(), tlsData())) {
@@ -1778,16 +1794,17 @@ bool Instance::callExport(JSContext* cx,
       DebugCodegen(DebugChannel::Function, "f32(%f)", *(float*)retAddr);
       break;
     case ExprType::F64:
       args.rval().set(NumberValue(*(double*)retAddr));
       DebugCodegen(DebugChannel::Function, "f64(%lf)", *(double*)retAddr);
       break;
     case ExprType::Ref:
       MOZ_CRASH("temporarily unsupported Ref type in callExport");
+    case ExprType::FuncRef:
     case ExprType::AnyRef:
       args.rval().set(UnboxAnyRef(AnyRef::fromCompiledCode(*(void**)retAddr)));
       DebugCodegen(DebugChannel::Function, "ptr(%p)", *(void**)retAddr);
       break;
     case ExprType::NullRef:
       MOZ_CRASH("NullRef not expressible");
     case ExprType::Limit:
       MOZ_CRASH("Limit");
--- a/js/src/wasm/WasmInstance.h
+++ b/js/src/wasm/WasmInstance.h
@@ -177,16 +177,17 @@ class Instance {
 
  public:
   // Functions to be called directly from wasm code.
   static int32_t callImport_void(Instance*, int32_t, int32_t, uint64_t*);
   static int32_t callImport_i32(Instance*, int32_t, int32_t, uint64_t*);
   static int32_t callImport_i64(Instance*, int32_t, int32_t, uint64_t*);
   static int32_t callImport_f64(Instance*, int32_t, int32_t, uint64_t*);
   static int32_t callImport_anyref(Instance*, int32_t, int32_t, uint64_t*);
+  static int32_t callImport_funcref(Instance*, int32_t, int32_t, uint64_t*);
   static uint32_t memoryGrow_i32(Instance* instance, uint32_t delta);
   static uint32_t memorySize_i32(Instance* instance);
   static int32_t wait_i32(Instance* instance, uint32_t byteOffset,
                           int32_t value, int64_t timeout);
   static int32_t wait_i64(Instance* instance, uint32_t byteOffset,
                           int64_t value, int64_t timeout);
   static int32_t wake(Instance* instance, uint32_t byteOffset, int32_t count);
   static int32_t memCopy(Instance* instance, uint32_t destByteOffset,
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -178,16 +178,17 @@ class FunctionCompiler {
           break;
         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::FuncRef:
         case ValType::AnyRef:
           ins = MWasmNullConstant::New(alloc());
           break;
         case ValType::NullRef:
           MOZ_CRASH("NullRef not expressible");
       }
 
       curBlock_->add(ins);
@@ -2178,18 +2179,19 @@ 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::FuncRef:
     case ValType::AnyRef:
-      MOZ_ASSERT(value.anyref().isNull());
+      MOZ_ASSERT(value.ref().isNull());
       result = f.nullRefConstant();
       break;
     default:
       MOZ_CRASH("unexpected type in EmitGetGlobal");
   }
 
   f.iter().setResult(result);
   return true;
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -173,22 +173,30 @@ static bool ToWebAssemblyValue(JSContext
     case ValType::F64: {
       double d;
       if (!ToNumber(cx, v, &d)) {
         return false;
       }
       val.set(Val(d));
       return true;
     }
+    case ValType::FuncRef: {
+      RootedFunction fun(cx);
+      if (!CheckFuncRefValue(cx, v, &fun)) {
+        return false;
+      }
+      val.set(Val(ValType::FuncRef, AnyRef::fromJSObject(fun)));
+      return true;
+    }
     case ValType::AnyRef: {
       RootedAnyRef tmp(cx, AnyRef::null());
       if (!BoxAnyRef(cx, v, &tmp)) {
         return false;
       }
-      val.set(Val(tmp));
+      val.set(Val(ValType::AnyRef, tmp));
       return true;
     }
     case ValType::Ref:
     case ValType::NullRef:
     case ValType::I64: {
       break;
     }
   }
@@ -198,18 +206,19 @@ static bool ToWebAssemblyValue(JSContext
 static Value ToJSValue(const Val& val) {
   switch (val.type().code()) {
     case ValType::I32:
       return Int32Value(val.i32());
     case ValType::F32:
       return DoubleValue(JS::CanonicalizeNaN(double(val.f32())));
     case ValType::F64:
       return DoubleValue(JS::CanonicalizeNaN(val.f64()));
+    case ValType::FuncRef:
     case ValType::AnyRef:
-      return UnboxAnyRef(val.anyref());
+      return UnboxAnyRef(val.ref());
     case ValType::Ref:
     case ValType::NullRef:
     case ValType::I64:
       break;
   }
   MOZ_CRASH("unexpected type when translating to a JS value");
 }
 
@@ -1531,16 +1540,39 @@ WasmFunctionScope* WasmInstanceObject::g
 
   return funcScope;
 }
 
 bool wasm::IsWasmExportedFunction(JSFunction* fun) {
   return fun->kind() == JSFunction::Wasm;
 }
 
+bool wasm::CheckFuncRefValue(JSContext* cx, HandleValue v,
+                             MutableHandleFunction fun) {
+  if (v.isNull()) {
+    MOZ_ASSERT(!fun);
+    return true;
+  }
+
+  if (v.isObject()) {
+    JSObject& obj = v.toObject();
+    if (obj.is<JSFunction>()) {
+      JSFunction* f = &obj.as<JSFunction>();
+      if (IsWasmExportedFunction(f)) {
+        fun.set(f);
+        return true;
+      }
+    }
+  }
+
+  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+                           JSMSG_WASM_BAD_FUNCREF_VALUE);
+  return false;
+}
+
 Instance& wasm::ExportedFunctionToInstance(JSFunction* fun) {
   return ExportedFunctionToInstanceObject(fun)->instance();
 }
 
 WasmInstanceObject* wasm::ExportedFunctionToInstanceObject(JSFunction* fun) {
   MOZ_ASSERT(fun->kind() == JSFunction::Wasm ||
              fun->kind() == JSFunction::AsmJS);
   const Value& v = fun->getExtendedSlot(FunctionExtended::WASM_INSTANCE_SLOT);
@@ -2129,16 +2161,23 @@ bool WasmTableObject::getImpl(JSContext*
 /* static */
 bool WasmTableObject::get(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   return CallNonGenericMethod<IsTable, getImpl>(cx, args);
 }
 
 static void TableFunctionFill(JSContext* cx, Table* table, HandleFunction value,
                               uint32_t index, uint32_t limit) {
+  if (!value) {
+    while (index < limit) {
+      table->setNull(index++);
+    }
+    return;
+  }
+
   RootedWasmInstanceObject instanceObj(cx,
                                        ExportedFunctionToInstanceObject(value));
   uint32_t funcIndex = ExportedFunctionToFuncIndex(value);
 
 #ifdef DEBUG
   RootedFunction f(cx);
   MOZ_ASSERT(instanceObj->getExportedFunction(cx, instanceObj, funcIndex, &f));
   MOZ_ASSERT(value == f);
@@ -2150,30 +2189,16 @@ static void TableFunctionFill(JSContext*
   const CodeRange& codeRange =
       metadata.codeRange(metadata.lookupFuncExport(funcIndex));
   void* code = instance.codeBase(tier) + codeRange.funcTableEntry();
   while (index < limit) {
     table->setFuncRef(index++, code, &instance);
   }
 }
 
-static bool IsWasmExportedFunction(const Value& v, MutableHandleFunction f) {
-  if (!v.isObject()) {
-    return false;
-  }
-
-  JSObject& obj = v.toObject();
-  if (!obj.is<JSFunction>() || !IsWasmExportedFunction(&obj.as<JSFunction>())) {
-    return false;
-  }
-
-  f.set(&obj.as<JSFunction>());
-  return true;
-}
-
 /* static */
 bool WasmTableObject::setImpl(JSContext* cx, const CallArgs& args) {
   RootedWasmTableObject tableObj(
       cx, &args.thisv().toObject().as<WasmTableObject>());
   Table& table = tableObj->table();
 
   if (!args.requireAtLeast(cx, "WebAssembly.Table.set", 2)) {
     return false;
@@ -2182,30 +2207,23 @@ bool WasmTableObject::setImpl(JSContext*
   uint32_t index;
   if (!ToTableIndex(cx, args.get(0), table, "set index", &index)) {
     return false;
   }
 
   RootedValue fillValue(cx, args[1]);
   switch (table.kind()) {
     case TableKind::FuncRef: {
-      RootedFunction value(cx);
-      if (!IsWasmExportedFunction(fillValue, &value) && !fillValue.isNull()) {
-        JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
-                                 JSMSG_WASM_BAD_TABLE_VALUE);
+      RootedFunction fun(cx);
+      if (!CheckFuncRefValue(cx, fillValue, &fun)) {
         return false;
       }
-
-      if (value) {
-        MOZ_ASSERT(index < MaxTableLength);
-        static_assert(MaxTableLength < UINT32_MAX, "Invariant");
-        TableFunctionFill(cx, &table, value, index, index + 1);
-      } else {
-        table.setNull(index);
-      }
+      MOZ_ASSERT(index < MaxTableLength);
+      static_assert(MaxTableLength < UINT32_MAX, "Invariant");
+      TableFunctionFill(cx, &table, fun, index, index + 1);
       break;
     }
     case TableKind::AnyRef: {
       RootedAnyRef tmp(cx, AnyRef::null());
       if (!BoxAnyRef(cx, fillValue, &tmp)) {
         return false;
       }
       table.setAnyRef(index, tmp);
@@ -2256,30 +2274,29 @@ bool WasmTableObject::growImpl(JSContext
 
   MOZ_ASSERT(delta <= MaxTableLength);              // grow() should ensure this
   MOZ_ASSERT(oldLength <= MaxTableLength - delta);  // ditto
 
   static_assert(MaxTableLength < UINT32_MAX, "Invariant");
 
   switch (table->table().kind()) {
     case TableKind::FuncRef: {
-      RootedFunction value(cx);
       if (fillValue.isNull()) {
 #ifdef DEBUG
         for (uint32_t index = oldLength; index < oldLength + delta; index++) {
           MOZ_ASSERT(table->table().getFuncRef(index).code == nullptr);
         }
 #endif
-      } else if (IsWasmExportedFunction(fillValue, &value)) {
-        TableFunctionFill(cx, &table->table(), value, oldLength,
+      } else {
+        RootedFunction fun(cx);
+        if (!CheckFuncRefValue(cx, fillValue, &fun)) {
+          return false;
+        }
+        TableFunctionFill(cx, &table->table(), fun, oldLength,
                           oldLength + delta);
-      } else {
-        JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
-                                 JSMSG_WASM_BAD_TBL_GROW_INIT, "funcref");
-        return false;
       }
       break;
     }
     case TableKind::AnyRef: {
       RootedAnyRef tmp(cx, AnyRef::null());
       if (!BoxAnyRef(cx, fillValue, &tmp)) {
         return false;
       }
@@ -2348,24 +2365,24 @@ void WasmGlobalObject::trace(JSTracer* t
   WasmGlobalObject* global = reinterpret_cast<WasmGlobalObject*>(obj);
   if (global->isNewborn()) {
     // This can happen while we're allocating the object, in which case
     // every single slot of the object is not defined yet. In particular,
     // there's nothing to trace yet.
     return;
   }
   switch (global->type().code()) {
+    case ValType::FuncRef:
     case ValType::AnyRef:
-      if (!global->cell()->anyref.isNull()) {
+      if (!global->cell()->ref.isNull()) {
         // TODO/AnyRef-boxing: With boxed immediates and strings, the write
         // barrier is going to have to be more complicated.
         ASSERT_ANYREF_IS_JSOBJECT;
-        TraceManuallyBarrieredEdge(trc,
-                                   global->cell()->anyref.asJSObjectAddress(),
-                                   "wasm anyref global");
+        TraceManuallyBarrieredEdge(trc, global->cell()->ref.asJSObjectAddress(),
+                                   "wasm reference-typed global");
       }
       break;
     case ValType::I32:
     case ValType::F32:
     case ValType::I64:
     case ValType::F64:
       break;
     case ValType::Ref:
@@ -2417,32 +2434,32 @@ WasmGlobalObject* WasmGlobalObject::crea
       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->ref, "value should be null already");
-      break;
+    case ValType::FuncRef:
     case ValType::AnyRef:
-      MOZ_ASSERT(cell->anyref.isNull(), "no prebarriers needed");
-      cell->anyref = val.anyref();
-      if (!cell->anyref.isNull()) {
+      MOZ_ASSERT(cell->ref.isNull(), "no prebarriers needed");
+      cell->ref = val.ref();
+      if (!cell->ref.isNull()) {
         // TODO/AnyRef-boxing: With boxed immediates and strings, the write
         // barrier is going to have to be more complicated.
         ASSERT_ANYREF_IS_JSOBJECT;
-        JSObject::writeBarrierPost(&cell->anyref, nullptr,
-                                   cell->anyref.asJSObject());
+        JSObject::writeBarrierPost(cell->ref.asJSObjectAddress(), nullptr,
+                                   cell->ref.asJSObject());
       }
       break;
     case ValType::Ref:
       MOZ_CRASH("Ref NYI");
+    case ValType::NullRef:
+      MOZ_CRASH("NullRef not expressible");
   }
 
   obj->initReservedSlot(TYPE_SLOT,
                         Int32Value(int32_t(val.type().bitsUnsafe())));
   obj->initReservedSlot(MUTABLE_SLOT, JS::BooleanValue(isMutable));
   obj->initReservedSlot(CELL_SLOT, PrivateValue(cell));
 
   MOZ_ASSERT(!obj->isNewborn());
@@ -2500,16 +2517,19 @@ bool WasmGlobalObject::construct(JSConte
     // initializing value.
     globalType = ValType::I64;
   } else if (StringEqualsAscii(typeLinearStr, "f32")) {
     globalType = ValType::F32;
   } else if (StringEqualsAscii(typeLinearStr, "f64")) {
     globalType = ValType::F64;
 #ifdef ENABLE_WASM_REFTYPES
   } else if (HasReftypesSupport(cx) &&
+             StringEqualsAscii(typeLinearStr, "funcref")) {
+    globalType = ValType::FuncRef;
+  } else if (HasReftypesSupport(cx) &&
              StringEqualsAscii(typeLinearStr, "anyref")) {
     globalType = ValType::AnyRef;
 #endif
   } else {
     JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                              JSMSG_WASM_BAD_GLOBAL_TYPE);
     return false;
   }
@@ -2528,29 +2548,32 @@ bool WasmGlobalObject::construct(JSConte
       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::FuncRef:
+      globalVal = Val(ValType::FuncRef, AnyRef::null());
+      break;
     case ValType::AnyRef:
-      globalVal = Val(AnyRef::null());
+      globalVal = Val(ValType::AnyRef, AnyRef::null());
       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() ||
-      (args.length() >= 2 && globalType == ValType::AnyRef)) {
+      (args.length() >= 2 && globalType.isReference())) {
     if (!ToWebAssemblyValue(cx, globalType, valueVal, &globalVal)) {
       return false;
     }
   }
 
   WasmGlobalObject* global = WasmGlobalObject::create(cx, globalVal, isMutable);
   if (!global) {
     return false;
@@ -2565,16 +2588,17 @@ static bool IsGlobal(HandleValue v) {
 }
 
 /* static */
 bool WasmGlobalObject::valueGetterImpl(JSContext* cx, const CallArgs& args) {
   switch (args.thisv().toObject().as<WasmGlobalObject>().type().code()) {
     case ValType::I32:
     case ValType::F32:
     case ValType::F64:
+    case ValType::FuncRef:
     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:
@@ -2622,27 +2646,28 @@ bool WasmGlobalObject::valueSetterImpl(J
       cell->i32 = val.get().i32();
       break;
     case ValType::F32:
       cell->f32 = val.get().f32();
       break;
     case ValType::F64:
       cell->f64 = val.get().f64();
       break;
+    case ValType::FuncRef:
     case ValType::AnyRef: {
-      AnyRef prevPtr = cell->anyref;
+      AnyRef prevPtr = cell->ref;
       // TODO/AnyRef-boxing: With boxed immediates and strings, the write
       // barrier is going to have to be more complicated.
       ASSERT_ANYREF_IS_JSOBJECT;
       JSObject::writeBarrierPre(prevPtr.asJSObject());
-      cell->anyref = val.get().anyref();
-      if (!cell->anyref.isNull()) {
-        JSObject::writeBarrierPost(cell->anyref.asJSObjectAddress(),
+      cell->ref = val.get().ref();
+      if (!cell->ref.isNull()) {
+        JSObject::writeBarrierPost(cell->ref.asJSObjectAddress(),
                                    prevPtr.asJSObject(),
-                                   cell->anyref.asJSObject());
+                                   cell->ref.asJSObject());
       }
       break;
     }
     case ValType::I64:
       MOZ_CRASH("unexpected i64 when setting global's value");
     case ValType::Ref:
       MOZ_CRASH("Ref NYI");
     case ValType::NullRef:
@@ -2688,18 +2713,21 @@ void WasmGlobalObject::val(MutableHandle
       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::FuncRef:
+      outval.set(Val(ValType::FuncRef, cell->ref));
+      return;
     case ValType::AnyRef:
-      outval.set(Val(cell->anyref));
+      outval.set(Val(ValType::AnyRef, cell->ref));
       return;
     case ValType::Ref:
       MOZ_CRASH("Ref NYI");
     case ValType::NullRef:
       MOZ_CRASH("NullRef not expressible");
   }
   MOZ_CRASH("unexpected Global type");
 }
--- a/js/src/wasm/WasmJS.h
+++ b/js/src/wasm/WasmJS.h
@@ -97,23 +97,24 @@ MOZ_MUST_USE bool DeserializeModule(JSCo
 
 // A WebAssembly "Exported Function" is the spec name for the JS function
 // objects created to wrap wasm functions. This predicate returns false
 // for asm.js functions which are semantically just normal JS functions
 // (even if they are implemented via wasm under the hood). The accessor
 // functions for extracting the instance and func-index of a wasm function
 // can be used for both wasm and asm.js, however.
 
-extern bool IsWasmExportedFunction(JSFunction* fun);
+bool IsWasmExportedFunction(JSFunction* fun);
+bool CheckFuncRefValue(JSContext* cx, HandleValue v, MutableHandleFunction fun);
 
-extern Instance& ExportedFunctionToInstance(JSFunction* fun);
-extern WasmInstanceObject* ExportedFunctionToInstanceObject(JSFunction* fun);
-extern uint32_t ExportedFunctionToFuncIndex(JSFunction* fun);
+Instance& ExportedFunctionToInstance(JSFunction* fun);
+WasmInstanceObject* ExportedFunctionToInstanceObject(JSFunction* fun);
+uint32_t ExportedFunctionToFuncIndex(JSFunction* fun);
 
-extern bool IsSharedWasmMemoryObject(JSObject* obj);
+bool IsSharedWasmMemoryObject(JSObject* obj);
 
 }  // namespace wasm
 
 // The class of the WebAssembly global namespace object.
 
 extern const Class WebAssemblyClass;
 
 JSObject* InitWebAssemblyClass(JSContext* cx, Handle<GlobalObject*> global);
@@ -171,18 +172,17 @@ class WasmGlobalObject : public NativeOb
  public:
   // For exposed globals the Cell holds the value of the global; the
   // instance's global area holds a pointer to the Cell.
   union Cell {
     int32_t i32;
     int64_t i64;
     float f32;
     double f64;
-    JSObject* ref;  // Note, this breaks an abstraction boundary
-    wasm::AnyRef anyref;
+    wasm::AnyRef ref;
     Cell() : i64(0) {}
     ~Cell() {}
   };
 
   static const unsigned RESERVED_SLOTS = 3;
   static const Class class_;
   static const JSPropertySpec properties[];
   static const JSFunctionSpec methods[];
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -1223,16 +1223,17 @@ static bool MakeStructField(JSContext* c
     case ValType::F64:
       t = GlobalObject::getOrCreateScalarTypeDescr(cx, cx->global(),
                                                    Scalar::Float64);
       break;
     case ValType::Ref:
       t = GlobalObject::getOrCreateReferenceTypeDescr(
           cx, cx->global(), ReferenceType::TYPE_OBJECT);
       break;
+    case ValType::FuncRef:
     case ValType::AnyRef:
       t = GlobalObject::getOrCreateReferenceTypeDescr(
           cx, cx->global(), ReferenceType::TYPE_WASM_ANYREF);
       break;
     default:
       MOZ_CRASH("Bad field type");
   }
   MOZ_ASSERT(t != nullptr);
--- a/js/src/wasm/WasmOpIter.h
+++ b/js/src/wasm/WasmOpIter.h
@@ -41,16 +41,17 @@ class StackType {
 #ifdef DEBUG
   bool isValidCode() {
     switch (UnpackTypeCodeType(tc_)) {
       case TypeCode::I32:
       case TypeCode::I64:
       case TypeCode::F32:
       case TypeCode::F64:
       case TypeCode::AnyRef:
+      case TypeCode::FuncRef:
       case TypeCode::Ref:
       case TypeCode::NullRef:
       case TypeCode::Limit:
         return true;
       default:
         return false;
     }
   }
@@ -59,16 +60,17 @@ class StackType {
  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),
+    FuncRef = uint8_t(ValType::FuncRef),
     Ref = uint8_t(ValType::Ref),
     NullRef = uint8_t(ValType::NullRef),
 
     TVar = uint8_t(TypeCode::Limit),
   };
 
   StackType() : tc_(InvalidPackedTypeCode()) {}
 
@@ -78,34 +80,26 @@ class StackType {
 
   explicit StackType(const ValType& t) : tc_(t.packed()) {}
 
   PackedTypeCode packed() const { return tc_; }
 
   Code code() const { return Code(UnpackTypeCodeType(tc_)); }
 
   uint32_t refTypeIndex() const { return UnpackTypeCodeIndex(tc_); }
-
   bool isRef() const { return UnpackTypeCodeType(tc_) == TypeCode::Ref; }
 
-  bool isReference() const {
-    TypeCode tc = UnpackTypeCodeType(tc_);
-    return tc == TypeCode::Ref || tc == TypeCode::AnyRef ||
-           tc == TypeCode::NullRef;
-  }
+  bool isReference() const { return IsReferenceType(tc_); }
 
   bool operator==(const StackType& that) const { return tc_ == that.tc_; }
-
   bool operator!=(const StackType& that) const { return tc_ != that.tc_; }
-
   bool operator==(Code that) const {
     MOZ_ASSERT(that != Code::Ref);
     return code() == that;
   }
-
   bool operator!=(Code that) const { return !(*this == that); }
 };
 
 static inline ValType NonTVarToValType(StackType type) {
   MOZ_ASSERT(type != StackType::TVar);
   return ValType(type.packed());
 }
 
@@ -760,16 +754,17 @@ inline bool OpIter<Policy>::readBlockTyp
   switch (uncheckedCode) {
     case uint8_t(ExprType::Void):
     case uint8_t(ExprType::I32):
     case uint8_t(ExprType::I64):
     case uint8_t(ExprType::F32):
     case uint8_t(ExprType::F64):
       known = true;
       break;
+    case uint8_t(ExprType::FuncRef):
     case uint8_t(ExprType::AnyRef):
 #ifdef ENABLE_WASM_REFTYPES
       known = true;
 #endif
       break;
     case uint8_t(ExprType::Ref):
       known = env_.gcTypesEnabled() && uncheckedRefTypeIndex < MaxTypes &&
               uncheckedRefTypeIndex < env_.types.length();
--- a/js/src/wasm/WasmStubs.cpp
+++ b/js/src/wasm/WasmStubs.cpp
@@ -307,16 +307,17 @@ static void StoreABIReturn(MacroAssemble
       masm.canonicalizeFloat(ReturnFloat32Reg);
       masm.storeFloat32(ReturnFloat32Reg, Address(argv, 0));
       break;
     case ExprType::F64:
       masm.canonicalizeDouble(ReturnDoubleReg);
       masm.storeDouble(ReturnDoubleReg, Address(argv, 0));
       break;
     case ExprType::Ref:
+    case ExprType::FuncRef:
     case ExprType::AnyRef:
       masm.storePtr(ReturnReg, Address(argv, 0));
       break;
     case ExprType::NullRef:
       MOZ_CRASH("NullRef not expressible");
     case ExprType::Limit:
       MOZ_CRASH("Limit");
   }
@@ -895,20 +896,19 @@ static bool GenerateJitEntry(MacroAssemb
     case ExprType::F64: {
       masm.canonicalizeDouble(ReturnDoubleReg);
       GenPrintF64(DebugChannel::Function, masm, ReturnDoubleReg);
       ScratchDoubleScope fpscratch(masm);
       masm.boxDouble(ReturnDoubleReg, JSReturnOperand, fpscratch);
       break;
     }
     case ExprType::Ref:
-      MOZ_CRASH("return ref in jitentry NYI");
-      break;
+    case ExprType::FuncRef:
     case ExprType::AnyRef:
-      MOZ_CRASH("return anyref in jitentry NYI");
+      MOZ_CRASH("returning reference 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");
   }
@@ -1146,16 +1146,17 @@ void wasm::GenerateDirectCallFromJit(Mac
       masm.canonicalizeFloat(ReturnFloat32Reg);
       GenPrintF32(DebugChannel::Function, masm, ReturnFloat32Reg);
       break;
     case wasm::ExprType::F64:
       masm.canonicalizeDouble(ReturnDoubleReg);
       GenPrintF64(DebugChannel::Function, masm, ReturnDoubleReg);
       break;
     case wasm::ExprType::Ref:
+    case wasm::ExprType::FuncRef:
     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");
   }
@@ -1547,16 +1548,24 @@ static bool GenerateImportInterpExit(Mac
       masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
       masm.loadDouble(argv, ReturnDoubleReg);
       GenPrintf(DebugChannel::Import, masm, "wasm-import[%u]; returns ",
                 funcImportIndex);
       GenPrintF64(DebugChannel::Import, masm, ReturnDoubleReg);
       break;
     case ExprType::Ref:
       MOZ_CRASH("No Ref support here yet");
+    case ExprType::FuncRef:
+      masm.call(SymbolicAddress::CallImport_FuncRef);
+      masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
+      masm.loadPtr(argv, ReturnReg);
+      GenPrintf(DebugChannel::Import, masm, "wasm-import[%u]; returns ",
+                funcImportIndex);
+      GenPrintPtr(DebugChannel::Import, masm, ReturnReg);
+      break;
     case ExprType::AnyRef:
       masm.call(SymbolicAddress::CallImport_AnyRef);
       masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
       masm.loadPtr(argv, ReturnReg);
       GenPrintf(DebugChannel::Import, masm, "wasm-import[%u]; returns ",
                 funcImportIndex);
       GenPrintPtr(DebugChannel::Import, masm, ReturnReg);
       break;
@@ -1748,20 +1757,19 @@ static bool GenerateImportJitExit(MacroA
       masm.convertValueToFloat(JSReturnOperand, ReturnFloat32Reg, &oolConvert);
       GenPrintF32(DebugChannel::Import, masm, ReturnFloat32Reg);
       break;
     case ExprType::F64:
       masm.convertValueToDouble(JSReturnOperand, ReturnDoubleReg, &oolConvert);
       GenPrintF64(DebugChannel::Import, masm, ReturnDoubleReg);
       break;
     case ExprType::Ref:
-      MOZ_CRASH("ref returned by import (jit exit) NYI");
-      break;
+    case ExprType::FuncRef:
     case ExprType::AnyRef:
-      MOZ_CRASH("anyref returned by import (jit exit) NYI");
+      MOZ_CRASH("reference returned by import (jit exit) NYI");
       break;
     case ExprType::NullRef:
       MOZ_CRASH("NullRef not expressible");
     case ExprType::Limit:
       MOZ_CRASH("Limit");
   }
 
   GenPrintf(DebugChannel::Import, masm, "\n");
--- a/js/src/wasm/WasmTextToBinary.cpp
+++ b/js/src/wasm/WasmTextToBinary.cpp
@@ -85,17 +85,16 @@ class WasmToken {
     EndOfFile,
     Equal,
     Error,
     Export,
     ExtraConversionOpcode,
     Field,
     Float,
     Func,
-    FuncRef,
 #ifdef ENABLE_WASM_GC
     GcFeatureOptIn,
 #endif
     GetGlobal,
     GetLocal,
     Global,
     If,
     Import,
@@ -371,17 +370,16 @@ class WasmToken {
       case EndOfFile:
       case Equal:
       case End:
       case Error:
       case Export:
       case Field:
       case Float:
       case Func:
-      case FuncRef:
 #ifdef ENABLE_WASM_GC
       case GcFeatureOptIn:
 #endif
       case Global:
       case Mutable:
       case Import:
       case Index:
       case Memory:
@@ -949,17 +947,17 @@ WasmToken WasmTokenStream::next() {
     case '9':
       return literal(begin);
 
     case 'a':
       if (consume(u"align")) {
         return WasmToken(WasmToken::Align, begin, cur_);
       }
       if (consume(u"anyfunc")) {
-        return WasmToken(WasmToken::FuncRef, begin, cur_);
+        return WasmToken(WasmToken::ValueType, ValType::FuncRef, begin, cur_);
       }
       if (consume(u"anyref")) {
         return WasmToken(WasmToken::ValueType, ValType::AnyRef, begin, cur_);
       }
       if (consume(u"atomic.")) {
         if (consume(u"wake") || consume(u"notify")) {
           return WasmToken(WasmToken::Wake, ThreadOp::Wake, begin, cur_);
         }
@@ -1032,17 +1030,17 @@ WasmToken WasmTokenStream::next() {
       break;
 
     case 'f':
       if (consume(u"field")) {
         return WasmToken(WasmToken::Field, begin, cur_);
       }
 
       if (consume(u"funcref")) {
-        return WasmToken(WasmToken::FuncRef, begin, cur_);
+        return WasmToken(WasmToken::ValueType, ValType::FuncRef, begin, cur_);
       }
 
       if (consume(u"func")) {
         return WasmToken(WasmToken::Func, begin, cur_);
       }
 
       if (consume(u"f32")) {
         if (!consume(u".")) {
@@ -3968,27 +3966,27 @@ static AstExpr* ParseStructSet(WasmParse
 }
 
 static AstExpr* ParseStructNarrow(WasmParseContext& c, bool inParens) {
   AstValType inputType;
   if (!ParseValType(c, &inputType)) {
     return nullptr;
   }
 
-  if (!inputType.isRefType()) {
+  if (!inputType.isNarrowType()) {
     c.ts.generateError(c.ts.peek(), "struct.narrow requires ref type", c.error);
     return nullptr;
   }
 
   AstValType outputType;
   if (!ParseValType(c, &outputType)) {
     return nullptr;
   }
 
-  if (!outputType.isRefType()) {
+  if (!outputType.isNarrowType()) {
     c.ts.generateError(c.ts.peek(), "struct.narrow requires ref type", c.error);
     return nullptr;
   }
 
   AstExpr* ptr = ParseExpr(c, inParens);
   if (!ptr) {
     return nullptr;
   }
@@ -4714,30 +4712,29 @@ static bool ParseGlobalType(WasmParseCon
     return false;
   }
 
   return true;
 }
 
 static bool ParseElemType(WasmParseContext& c, TableKind* tableKind) {
   WasmToken token;
-  if (c.ts.getIf(WasmToken::FuncRef, &token)) {
-    *tableKind = TableKind::FuncRef;
-    return true;
-  }
+  if (c.ts.getIf(WasmToken::ValueType, &token)) {
+    if (token.valueType() == ValType::FuncRef) {
+      *tableKind = TableKind::FuncRef;
+      return true;
+    }
 #ifdef ENABLE_WASM_REFTYPES
-  if (c.ts.getIf(WasmToken::ValueType, &token) &&
-      token.valueType() == ValType::AnyRef) {
-    *tableKind = TableKind::AnyRef;
-    return true;
+    if (token.valueType() == ValType::AnyRef) {
+      *tableKind = TableKind::AnyRef;
+      return true;
+    }
+#endif
   }
   c.ts.generateError(token, "'funcref' or 'anyref' required", c.error);
-#else
-  c.ts.generateError(token, "'funcref' required", c.error);
-#endif
   return false;
 }
 
 static bool ParseTableSig(WasmParseContext& c, Limits* table,
                           TableKind* tableKind) {
   return ParseLimits(c, table, Shareable::False) && ParseElemType(c, tableKind);
 }
 
--- a/js/src/wasm/WasmTypes.cpp
+++ b/js/src/wasm/WasmTypes.cpp
@@ -73,38 +73,33 @@ Val::Val(const LitVal& val) {
       return;
     case ValType::I64:
       u.i64_ = val.i64();
       return;
     case ValType::F64:
       u.f64_ = val.f64();
       return;
     case ValType::Ref:
+    case ValType::FuncRef:
+    case ValType::AnyRef:
       u.ref_ = val.ref();
       return;
-    case ValType::AnyRef:
-      u.anyref_ = val.anyref();
-      return;
     case ValType::NullRef:
       break;
   }
   MOZ_CRASH();
 }
 
 void Val::trace(JSTracer* trc) {
-  if (type_.isValid()) {
-    if (type_.isRef() && u.ref_) {
-      TraceManuallyBarrieredEdge(trc, &u.ref_, "wasm ref/anyref global");
-    } else if (type_ == ValType::AnyRef && !u.anyref_.isNull()) {
-      // TODO/AnyRef-boxing: With boxed immediates and strings, the write
-      // barrier is going to have to be more complicated.
-      ASSERT_ANYREF_IS_JSOBJECT;
-      TraceManuallyBarrieredEdge(trc, u.anyref_.asJSObjectAddress(),
-                                 "wasm ref/anyref global");
-    }
+  if (type_.isValid() && type_.isReference() && !u.ref_.isNull()) {
+    // TODO/AnyRef-boxing: With boxed immediates and strings, the write
+    // barrier is going to have to be more complicated.
+    ASSERT_ANYREF_IS_JSOBJECT;
+    TraceManuallyBarrieredEdge(trc, u.ref_.asJSObjectAddress(),
+                               "wasm reference-typed global");
   }
 }
 
 void AnyRef::trace(JSTracer* trc) {
   if (value_) {
     TraceManuallyBarrieredEdge(trc, &value_, "wasm anyref referent");
   }
 }
@@ -268,16 +263,17 @@ static const unsigned sMaxTypes =
     (sTotalBits - sTagBits - sReturnBit - sLengthBits) / sTypeBits;
 
 static bool IsImmediateType(ValType vt) {
   switch (vt.code()) {
     case ValType::I32:
     case ValType::I64:
     case ValType::F32:
     case ValType::F64:
+    case ValType::FuncRef:
     case ValType::AnyRef:
       return true;
     case ValType::NullRef:
     case ValType::Ref:
       return false;
   }
   MOZ_CRASH("bad ValType");
 }
@@ -288,18 +284,20 @@ static unsigned EncodeImmediateType(ValT
     case ValType::I32:
       return 0;
     case ValType::I64:
       return 1;
     case ValType::F32:
       return 2;
     case ValType::F64:
       return 3;
+    case ValType::FuncRef:
+      return 4;
     case ValType::AnyRef:
-      return 4;
+      return 5;
     case ValType::NullRef:
     case ValType::Ref:
       break;
   }
   MOZ_CRASH("bad ValType");
 }
 
 /* static */
@@ -719,16 +717,17 @@ void DebugFrame::updateReturnJSValue() {
       cachedReturnJSValue_.setDouble(JS::CanonicalizeNaN(resultF32_));
       break;
     case ExprType::F64:
       cachedReturnJSValue_.setDouble(JS::CanonicalizeNaN(resultF64_));
       break;
     case ExprType::Ref:
       cachedReturnJSValue_ = ObjectOrNullValue((JSObject*)resultRef_);
       break;
+    case ExprType::FuncRef:
     case ExprType::AnyRef:
       cachedReturnJSValue_ = UnboxAnyRef(resultAnyRef_);
       break;
     default:
       MOZ_CRASH("result type");
   }
 }
 
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -250,16 +250,22 @@ static inline TypeCode UnpackTypeCodeTyp
   return TypeCode(uint32_t(ptc) & 255);
 }
 
 static inline uint32_t UnpackTypeCodeIndex(PackedTypeCode ptc) {
   MOZ_ASSERT(UnpackTypeCodeType(ptc) == TypeCode::Ref);
   return uint32_t(ptc) >> 8;
 }
 
+static inline bool IsReferenceType(PackedTypeCode ptc) {
+  TypeCode tc = UnpackTypeCodeType(ptc);
+  return tc == TypeCode::Ref || tc == TypeCode::AnyRef ||
+         tc == TypeCode::FuncRef || tc == TypeCode::NullRef;
+}
+
 // The ExprType represents the type of a WebAssembly expression or return value
 // and may either be a ValType or void.
 //
 // (Soon, expression types will be generalized to a list of ValType and this
 // class will go away, replaced, wherever it is used, by a varU32 + list of
 // ValType.)
 
 class ValType;
@@ -270,16 +276,17 @@ 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::FuncRef:
       case TypeCode::NullRef:
       case TypeCode::Ref:
       case TypeCode::BlockVoid:
       case TypeCode::Limit:
         return true;
       default:
         return false;
     }
@@ -290,16 +297,17 @@ class ExprType {
   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),
+    FuncRef = uint8_t(TypeCode::FuncRef),
     NullRef = uint8_t(TypeCode::NullRef),
     Ref = uint8_t(TypeCode::Ref),
 
     Limit = uint8_t(TypeCode::Limit)
   };
 
   ExprType() : tc_() {}
 
@@ -316,42 +324,33 @@ class ExprType {
 
   explicit ExprType(PackedTypeCode ptc) : tc_(ptc) {
     MOZ_ASSERT(isValidCode());
   }
 
   explicit inline ExprType(const ValType& t);
 
   PackedTypeCode packed() const { return tc_; }
-
   PackedTypeCode* packedPtr() { return &tc_; }
 
   Code code() const { return Code(UnpackTypeCodeType(tc_)); }
 
+  bool isValid() const { return IsValid(tc_); }
+
   uint32_t refTypeIndex() const { return UnpackTypeCodeIndex(tc_); }
-
-  bool isValid() const { return IsValid(tc_); }
-
   bool isRef() const { return UnpackTypeCodeType(tc_) == TypeCode::Ref; }
 
-  bool isReference() const {
-    TypeCode tc = UnpackTypeCodeType(tc_);
-    return tc == TypeCode::Ref || tc == TypeCode::AnyRef ||
-           tc == TypeCode::NullRef;
-  }
+  bool isReference() const { return IsReferenceType(tc_); }
 
   bool operator==(const ExprType& that) const { return tc_ == that.tc_; }
-
   bool operator!=(const ExprType& that) const { return tc_ != that.tc_; }
-
   bool operator==(Code that) const {
     MOZ_ASSERT(that != Code::Ref);
     return code() == that;
   }
-
   bool operator!=(Code that) const { return !(*this == that); }
 };
 
 // The ValType represents the storage type of a WebAssembly location, whether
 // parameter, local, or global.
 
 class ValType {
   PackedTypeCode tc_;
@@ -359,16 +358,17 @@ 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::FuncRef:
       case TypeCode::NullRef:
       case TypeCode::Ref:
         return true;
       default:
         return false;
     }
   }
 #endif
@@ -376,16 +376,17 @@ class ValType {
  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),
+    FuncRef = uint8_t(TypeCode::FuncRef),
     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());
@@ -426,37 +427,29 @@ class ValType {
   }
 
   PackedTypeCode packed() const { return tc_; }
 
   uint32_t bitsUnsafe() const { return PackedTypeCodeToBits(tc_); }
 
   Code code() const { return Code(UnpackTypeCodeType(tc_)); }
 
+  bool isValid() const { return IsValid(tc_); }
+
   uint32_t refTypeIndex() const { return UnpackTypeCodeIndex(tc_); }
-
-  bool isValid() const { return IsValid(tc_); }
-
   bool isRef() const { return UnpackTypeCodeType(tc_) == TypeCode::Ref; }
 
-  bool isReference() const {
-    TypeCode tc = UnpackTypeCodeType(tc_);
-    return tc == TypeCode::Ref || tc == TypeCode::AnyRef ||
-           tc == TypeCode::NullRef;
-  }
+  bool isReference() const { return IsReferenceType(tc_); }
 
   bool operator==(const ValType& that) const { return tc_ == that.tc_; }
-
   bool operator!=(const ValType& that) const { return tc_ != that.tc_; }
-
   bool operator==(Code that) const {
     MOZ_ASSERT(that != Code::Ref);
     return code() == that;
   }
-
   bool operator!=(Code that) const { return !(*this == that); }
 };
 
 // The dominant use of this data type is for locals and args, and profiling
 // with ZenGarden and Tanks suggests an initial size of 16 minimises heap
 // allocation, both in terms of blocks and bytes.
 typedef Vector<ValType, 16, SystemAllocPolicy> ValTypeVector;
 
@@ -466,16 +459,17 @@ static inline unsigned 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::FuncRef:
     case ValType::NullRef:
     case ValType::Ref:
       return sizeof(intptr_t);
   }
   MOZ_CRASH("Invalid ValType");
 }
 
 static inline jit::MIRType ToMIRType(ValType vt) {
@@ -485,16 +479,17 @@ static inline jit::MIRType ToMIRType(Val
     case ValType::I64:
       return jit::MIRType::Int64;
     case ValType::F32:
       return jit::MIRType::Float32;
     case ValType::F64:
       return jit::MIRType::Double;
     case ValType::Ref:
     case ValType::AnyRef:
+    case ValType::FuncRef:
     case ValType::NullRef:
       return jit::MIRType::RefOrNull;
   }
   MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("bad type");
 }
 
 static inline bool IsNumberType(ValType vt) { return !vt.isReference(); }
 
@@ -522,16 +517,18 @@ static inline const char* ToCString(Expr
     case ExprType::I64:
       return "i64";
     case ExprType::F32:
       return "f32";
     case ExprType::F64:
       return "f64";
     case ExprType::AnyRef:
       return "anyref";
+    case ExprType::FuncRef:
+      return "funcref";
     case ExprType::NullRef:
       return "nullref";
     case ExprType::Ref:
       return "ref";
     case ExprType::Limit:;
   }
   MOZ_CRASH("bad expression type");
 }
@@ -697,40 +694,33 @@ class LitVal {
  protected:
   ValType type_;
   union U {
     U() : i32_(0) {}
     uint32_t i32_;
     uint64_t i64_;
     float f32_;
     double f64_;
-    JSObject* ref_;  // Note, this breaks an abstraction boundary
-    AnyRef anyref_;
+    AnyRef ref_;
   } u;
 
  public:
   LitVal() : type_(), u{} {}
 
   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(AnyRef any) : type_(ValType::AnyRef) {
+  explicit LitVal(ValType type, AnyRef any) : type_(type) {
+    MOZ_ASSERT(type.isReference());
     MOZ_ASSERT(any.isNull(),
                "use Val for non-nullptr ref types to get tracing");
-    u.anyref_ = any;
-  }
-
-  explicit LitVal(ValType refType, JSObject* ref) : type_(refType) {
-    MOZ_ASSERT(refType.isRef());
-    MOZ_ASSERT(ref == nullptr,
-               "use Val for non-nullptr ref types to get tracing");
-    u.ref_ = ref;
+    u.ref_ = any;
   }
 
   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_;
@@ -742,42 +732,38 @@ class LitVal {
   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* ref() const {
-    MOZ_ASSERT(type_.isRef());
+  AnyRef ref() const {
+    MOZ_ASSERT(type_.isReference());
     return u.ref_;
   }
-  AnyRef anyref() const {
-    MOZ_ASSERT(type_ == ValType::AnyRef);
-    return u.anyref_;
-  }
 };
 
 // A Val is a LitVal that can contain (non-null) pointers to GC things. All Vals
 // must be stored in Rooteds so that their trace() methods are called during
 // stack marking. Vals do not implement barriers and thus may not be stored on
 // the heap.
 
 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(AnyRef val) : LitVal(AnyRef::null()) { u.anyref_ = val; }
-  explicit Val(ValType type, JSObject* obj) : LitVal(type, (JSObject*)nullptr) {
-    u.ref_ = obj;
+  explicit Val(ValType type, AnyRef val) : LitVal(type, AnyRef::null()) {
+    MOZ_ASSERT(type.isReference());
+    u.ref_ = val;
   }
   void trace(JSTracer* trc);
 };
 
 typedef Rooted<Val> RootedVal;
 typedef Handle<Val> HandleVal;
 typedef MutableHandle<Val> MutableHandleVal;
 
@@ -1858,16 +1844,17 @@ enum class SymbolicAddress {
   HandleDebugTrap,
   HandleThrow,
   HandleTrap,
   ReportInt64JSCall,
   CallImport_Void,
   CallImport_I32,
   CallImport_I64,
   CallImport_F64,
+  CallImport_FuncRef,
   CallImport_AnyRef,
   CoerceInPlace_ToInt32,
   CoerceInPlace_ToNumber,
   CoerceInPlace_JitEntry,
   DivI64,
   UDivI64,
   ModI64,
   UModI64,
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -1311,16 +1311,17 @@ static bool DecodeStructType(Decoder& d,
         offset = layout.addScalar(Scalar::Float32);
         break;
       case ValType::F64:
         offset = layout.addScalar(Scalar::Float64);
         break;
       case ValType::Ref:
         offset = layout.addReference(ReferenceType::TYPE_OBJECT);
         break;
+      case ValType::FuncRef:
       case ValType::AnyRef:
         offset = layout.addReference(ReferenceType::TYPE_WASM_ANYREF);
         break;
       default:
         MOZ_CRASH("Unknown type");
     }
     if (!offset.isValid()) {
       return d.fail("Object too large");
@@ -1597,16 +1598,17 @@ static bool DecodeTableTypeAndLimits(Dec
 }
 
 static bool GlobalIsJSCompatible(Decoder& d, ValType type, bool isMutable) {
   switch (type.code()) {
     case ValType::I32:
     case ValType::F32:
     case ValType::F64:
     case ValType::I64:
+    case ValType::FuncRef:
     case ValType::AnyRef:
       break;
 #ifdef WASM_PRIVATE_REFTYPES
     case ValType::Ref:
       return d.fail("cannot expose reference type");
 #endif
     default:
       return d.fail("unexpected variable type in global import/export");
@@ -1932,24 +1934,18 @@ static bool DecodeInitializerExpression(
       *init = InitExpr(LitVal(f64));
       break;
     }
     case uint16_t(Op::RefNull): {
       if (!expected.isReference()) {
         return d.fail(
             "type mismatch: initializer type and expected type don't match");
       }
-      if (expected == ValType::AnyRef) {
-        *init = InitExpr(LitVal(AnyRef::null()));
-      } else {
-        if (!env->gcTypesEnabled()) {
-          return d.fail("unexpected initializer expression");
-        }
-        *init = InitExpr(LitVal(expected, nullptr));
-      }
+      MOZ_ASSERT_IF(expected.isRef(), env->gcTypesEnabled());
+      *init = InitExpr(LitVal(expected, AnyRef::null()));
       break;
     }
     case uint16_t(Op::GetGlobal): {
       uint32_t i;
       const GlobalDescVector& globals = env->globals;
       if (!d.readVarU32(&i)) {
         return d.fail(
             "failed to read global.get index in initializer expression");
--- a/js/src/wasm/WasmValidate.h
+++ b/js/src/wasm/WasmValidate.h
@@ -606,16 +606,17 @@ class Decoder {
     switch (code) {
       case uint8_t(ValType::I32):
       case uint8_t(ValType::F32):
       case uint8_t(ValType::F64):
       case uint8_t(ValType::I64):
         *type = ValType::Code(code);
         return true;
 #ifdef ENABLE_WASM_REFTYPES
+      case uint8_t(ValType::FuncRef):
       case uint8_t(ValType::AnyRef):
         *type = ValType::Code(code);
         return true;
 #  ifdef ENABLE_WASM_GC
       case uint8_t(ValType::Ref): {
         if (!gcTypesEnabled) {
           return fail("(ref T) types not enabled");
         }