Bug 1450261: Implement support of anyref in wasm globals; r=lth
☠☠ backed out by 82e8c7cfd346 ☠ ☠
authorBenjamin Bouvier <benj@benj.me>
Tue, 27 Mar 2018 15:40:13 +0200
changeset 479540 1478fb0f82debdec6e748e0e2b605b4e17d5f88d
parent 479517 99371dbf539290e70252f91cfa910930f3c66974
child 479541 ddbbcea98cdecbebb985afaa65225e6395a6a494
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslth
bugs1450261
milestone63.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 1450261: Implement support of anyref in wasm globals; r=lth
js/src/gc/Nursery.h
js/src/jit-test/tests/wasm/gc/anyref-global-object.js
js/src/jit-test/tests/wasm/gc/anyref-global-postbarrier.js
js/src/jit-test/tests/wasm/gc/anyref-global-prebarrier.js
js/src/jit-test/tests/wasm/gc/anyref.js
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/WasmJS.cpp
js/src/wasm/WasmJS.h
js/src/wasm/WasmTypes.cpp
js/src/wasm/WasmTypes.h
js/src/wasm/WasmValidate.cpp
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -49,18 +49,16 @@ class PlainObject;
 class NativeObject;
 class Nursery;
 struct NurseryChunk;
 class HeapSlot;
 class JSONPrinter;
 class MapObject;
 class SetObject;
 
-void SetGCZeal(JSRuntime*, uint8_t, uint32_t);
-
 namespace gc {
 class AutoMaybeStartBackgroundAllocation;
 class AutoTraceSession;
 struct Cell;
 class MinorCollectionTracer;
 class RelocationOverlay;
 struct TenureCountCache;
 enum class AllocKind : uint8_t;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/anyref-global-object.js
@@ -0,0 +1,94 @@
+if (!wasmGcEnabled() || typeof WebAssembly.Global !== 'function') {
+    quit(0);
+}
+
+// Dummy object.
+function Baguette(calories) {
+    this.calories = calories;
+}
+
+assertEq(new WebAssembly.Global({value: "anyref"}) instanceof WebAssembly.Global, true);
+
+(function() {
+    // Test initialization without a value.
+    let g = new WebAssembly.Global({value: "anyref"});
+    assertEq(g.value, null);
+    assertErrorMessage(() => g.value = 42, TypeError, /immutable global/);
+})();
+
+(function() {
+    // Test initialization with a value.
+    let g = new WebAssembly.Global({value: "anyref"}, null);
+    assertEq(g.value, null);
+    assertErrorMessage(() => g.value = 42, TypeError, /immutable global/);
+
+    let obj = {};
+    g = new WebAssembly.Global({value: "anyref"}, obj);
+    assertEq(g.value, obj);
+    assertErrorMessage(() => g.value = 42, TypeError, /immutable global/);
+
+    g = new WebAssembly.Global({value: "anyref"}, 1337);
+    assertEq(g.value instanceof Number, true);
+    assertEq(+g.value, 1337);
+
+    g = new WebAssembly.Global({value: "anyref"}, 13.37);
+    assertEq(g.value instanceof Number, true);
+    assertEq(+g.value, 13.37);
+
+    g = new WebAssembly.Global({value: "anyref"}, "string");
+    assertEq(g.value instanceof String, true);
+    assertEq(g.value.toString(), "string");
+
+    g = new WebAssembly.Global({value: "anyref"}, true);
+    assertEq(g.value instanceof Boolean, true);
+    assertEq(!!g.value, true);
+
+    g = new WebAssembly.Global({value: "anyref"}, Symbol("status"));
+    assertEq(g.value instanceof Symbol, true);
+    assertEq(g.value.toString(), "Symbol(status)");
+
+    assertErrorMessage(() => new WebAssembly.Global({value: "anyref"}, undefined),
+                       TypeError,
+                       "can't convert undefined to object");
+})();
+
+(function() {
+    // Test mutable property and assignment.
+    let g = new WebAssembly.Global({value: "anyref", mutable: true}, null);
+    assertEq(g.value, null);
+
+    let obj = { x: 42 };
+    g.value = obj;
+    assertEq(g.value, obj);
+    assertEq(g.value.x, 42);
+
+    obj = null;
+    assertEq(g.value.x, 42);
+
+    let otherObj = { y : 35 };
+    g.value = otherObj;
+    assertEq(g.value, otherObj);
+})();
+
+(function() {
+    // Test tracing.
+    let nom = new Baguette(1);
+    let g = new WebAssembly.Global({value: "anyref"}, nom);
+    nom = null;
+    gc();
+    assertEq(g.value.calories, 1);
+})();
+
+var global = new WebAssembly.Global({ value: "anyref", mutable: true }, null);
+
+// GCZeal mode 2 implies that every allocation (second parameter = every single
+// allocation) will trigger a full GC.
+gczeal(2, 1);
+
+{
+    let nomnom = new Baguette(42);
+    global.value = nomnom;
+    nomnom = null;
+}
+new Baguette();
+assertEq(global.value.calories, 42);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/anyref-global-postbarrier.js
@@ -0,0 +1,72 @@
+if (!wasmGcEnabled()) {
+    quit(0);
+}
+
+const { startProfiling, endProfiling, assertEqPreciseStacks, isSingleStepProfilingEnabled } = WasmHelpers;
+
+// Dummy constructor.
+function Baguette(calories) {
+    this.calories = calories;
+}
+
+let exportsPlain = wasmEvalText(`(module
+    (global i32 (i32.const 42))
+    (global $g (mut anyref) (ref.null anyref))
+    (func (export "set") (param anyref) get_local 0 set_global $g)
+    (func (export "get") (result anyref) get_global $g)
+)`).exports;
+
+let exportsObj = wasmEvalText(`(module
+    (global $g (export "g") (mut anyref) (ref.null anyref))
+    (func (export "set") (param anyref) get_local 0 set_global $g)
+    (func (export "get") (result anyref) get_global $g)
+)`).exports;
+
+// 7 => Generational GC zeal.
+gczeal(7, 1);
+
+for (var i = 0; i < 100; i++) {
+    new Baguette(i);
+}
+
+function test(exports) {
+    // Test post-write barrier in wasm code.
+    {
+        let nomnom = new Baguette(15);
+        exports.set(nomnom);
+        nomnom = null;
+    }
+    new Baguette();
+    assertEq(exports.get().calories, 15);
+}
+
+test(exportsPlain);
+test(exportsObj);
+
+// Test stacks reported in profiling mode in a separate way, to not perturb
+// the behavior of the tested functions.
+if (!isSingleStepProfilingEnabled)
+    quit(0);
+
+enableGeckoProfiling();
+
+const EXPECTED_STACKS = [
+    ['', '!>', '0,!>', '<,0,!>', 'GC postbarrier,0,!>', '<,0,!>', '0,!>', '!>', ''],
+    ['', '!>', '0,!>', '!>', ''],
+];
+
+function testStacks(exports) {
+    // Test post-write barrier in wasm code.
+    {
+        let nomnom = new Baguette(15);
+        startProfiling();
+        exports.set(nomnom);
+        assertEqPreciseStacks(endProfiling(), EXPECTED_STACKS);
+        nomnom = null;
+    }
+    new Baguette();
+    assertEq(exports.get().calories, 15);
+}
+
+testStacks(exportsPlain);
+testStacks(exportsObj);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/anyref-global-prebarrier.js
@@ -0,0 +1,39 @@
+if (!wasmGcEnabled()) {
+    quit(0);
+}
+
+const { startProfiling, endProfiling, assertEqPreciseStacks, isSingleStepProfilingEnabled } = WasmHelpers;
+
+let e = wasmEvalText(`(module
+    (global $g (mut anyref) (ref.null anyref))
+    (func (export "set") (param anyref) get_local 0 set_global $g)
+)`).exports;
+
+let obj = { field: null };
+
+// GCZeal mode 4 implies that prebarriers are being verified at many
+// locations in the interpreter, during interrupt checks, etc. It can be ultra
+// slow, so disable it with gczeal(0) when it's not strictly needed.
+gczeal(4, 1);
+e.set(obj);
+e.set(null);
+gczeal(0);
+
+if (!isSingleStepProfilingEnabled) {
+    quit(0);
+}
+
+enableGeckoProfiling();
+startProfiling();
+gczeal(4, 1);
+e.set(obj);
+gczeal(0);
+assertEqPreciseStacks(endProfiling(), [['', '!>', '0,!>', '!>', '']]);
+
+startProfiling();
+gczeal(4, 1);
+e.set(null);
+gczeal(0);
+
+// We're losing stack info in the prebarrier code.
+assertEqPreciseStacks(endProfiling(), [['', '!>', '0,!>', '', '0,!>', '!>', '']]);
--- a/js/src/jit-test/tests/wasm/gc/anyref.js
+++ b/js/src/jit-test/tests/wasm/gc/anyref.js
@@ -44,16 +44,18 @@ let simpleTests = [
     "(module (func (drop (ref.null anyref))))",
     "(module (func $test (local anyref)))",
     "(module (func $test (param anyref)))",
     "(module (func $test (result anyref) (ref.null anyref)))",
     "(module (func $test (block anyref (unreachable)) unreachable))",
     "(module (func $test (local anyref) (result i32) (ref.is_null (get_local 0))))",
     `(module (import "a" "b" (param anyref)))`,
     `(module (import "a" "b" (result anyref)))`,
+    `(module (global anyref (ref.null anyref)))`,
+    `(module (global (mut anyref) (ref.null anyref)))`,
 ];
 
 for (let src of simpleTests) {
     wasmEvalText(src, {a:{b(){}}});
     assertEq(validate(wasmTextToBinary(src)), true);
 }
 
 // Basic behavioral tests.
@@ -389,8 +391,83 @@ assertEq(exports.count_f(), 1);
 assertEq(exports.count_g(), 1);
 
 x = { i: 23 };
 assertEq(exports.table.get(3)(x), x);
 assertEq(x.i, 24);
 assertEq(x.newProp, "hello");
 assertEq(exports.count_f(), 1);
 assertEq(exports.count_g(), 1);
+
+// Globals.
+
+// Anyref globals in wasm modules.
+
+assertErrorMessage(() => wasmEvalText(`(module (global (import "glob" "anyref") anyref))`, { glob: { anyref: 42 } }),
+    WebAssembly.LinkError,
+    /import object field 'anyref' is not a Object-or-null/);
+
+assertErrorMessage(() => wasmEvalText(`(module (global (import "glob" "anyref") anyref))`, { glob: { anyref: new WebAssembly.Global({ value: 'i32' }, 42) } }),
+    WebAssembly.LinkError,
+    /imported global type mismatch/);
+
+assertErrorMessage(() => wasmEvalText(`(module (global (import "glob" "i32") i32))`, { glob: { i32: {} } }),
+    WebAssembly.LinkError,
+    /import object field 'i32' is not a Number/);
+
+imports = {
+    constants: {
+        imm_null: null,
+        imm_bread: new Baguette(321),
+        mut_null: new WebAssembly.Global({ value: "anyref", mutable: true }, null),
+        mut_bread: new WebAssembly.Global({ value: "anyref", mutable: true }, new Baguette(123))
+    }
+};
+
+exports = wasmEvalText(`(module
+    (global $g_imp_imm_null  (import "constants" "imm_null") anyref)
+    (global $g_imp_imm_bread (import "constants" "imm_bread") anyref)
+
+    (global $g_imp_mut_null   (import "constants" "mut_null") (mut anyref))
+    (global $g_imp_mut_bread  (import "constants" "mut_bread") (mut anyref))
+
+    (global $g_imm_null     anyref (ref.null anyref))
+    (global $g_imm_getglob  anyref (get_global $g_imp_imm_bread))
+    (global $g_mut         (mut anyref) (ref.null anyref))
+
+    (func (export "imm_null")      (result anyref) get_global $g_imm_null)
+    (func (export "imm_getglob")   (result anyref) get_global $g_imm_getglob)
+
+    (func (export "imp_imm_null")  (result anyref) get_global $g_imp_imm_null)
+    (func (export "imp_imm_bread") (result anyref) get_global $g_imp_imm_bread)
+    (func (export "imp_mut_null")  (result anyref) get_global $g_imp_mut_null)
+    (func (export "imp_mut_bread") (result anyref) get_global $g_imp_mut_bread)
+
+    (func (export "set_imp_null")  (param anyref) get_local 0 set_global $g_imp_mut_null)
+    (func (export "set_imp_bread") (param anyref) get_local 0 set_global $g_imp_mut_bread)
+
+    (func (export "set_mut") (param anyref) get_local 0 set_global $g_mut)
+    (func (export "get_mut") (result anyref) get_global $g_mut)
+)`, imports).exports;
+
+assertEq(exports.imp_imm_null(), imports.constants.imm_null);
+assertEq(exports.imp_imm_bread(), imports.constants.imm_bread);
+
+assertEq(exports.imm_null(), null);
+assertEq(exports.imm_getglob(), imports.constants.imm_bread);
+
+assertEq(exports.imp_mut_null(), imports.constants.mut_null.value);
+assertEq(exports.imp_mut_bread(), imports.constants.mut_bread.value);
+
+let brandNewBaguette = new Baguette(1000);
+exports.set_imp_null(brandNewBaguette);
+assertEq(exports.imp_mut_null(), brandNewBaguette);
+assertEq(exports.imp_mut_bread(), imports.constants.mut_bread.value);
+
+exports.set_imp_bread(null);
+assertEq(exports.imp_mut_null(), brandNewBaguette);
+assertEq(exports.imp_mut_bread(), null);
+
+assertEq(exports.get_mut(), null);
+let glutenFreeBaguette = new Baguette("calories-free bread");
+exports.set_mut(glutenFreeBaguette);
+assertEq(exports.get_mut(), glutenFreeBaguette);
+assertEq(exports.get_mut().calories, "calories-free bread");
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -134,16 +134,17 @@
 # include "jit/mips32/Assembler-mips32.h"
 #endif
 #if defined(JS_CODEGEN_MIPS64)
 # include "jit/mips-shared/Assembler-mips-shared.h"
 # include "jit/mips64/Assembler-mips64.h"
 #endif
 
 #include "wasm/WasmGenerator.h"
+#include "wasm/WasmInstance.h"
 #include "wasm/WasmOpIter.h"
 #include "wasm/WasmSignalHandlers.h"
 #include "wasm/WasmValidate.h"
 
 #include "jit/MacroAssembler-inl.h"
 
 using mozilla::DebugOnly;
 using mozilla::FloorLog2;
@@ -4390,17 +4391,16 @@ class BaseCompiler final : public BaseCo
 
     //////////////////////////////////////////////////////////////////////
     //
     // Global variable access.
 
     Address addressOfGlobalVar(const GlobalDesc& global, RegI32 tmp)
     {
         uint32_t globalToTlsOffset = offsetof(TlsData, globalArea) + global.offset();
-
         masm.loadWasmTlsRegFromFrame(tmp);
         if (global.isIndirect()) {
             masm.loadPtr(Address(tmp, globalToTlsOffset), tmp);
             return Address(tmp, 0);
         }
         return Address(tmp, globalToTlsOffset);
     }
 
@@ -5716,16 +5716,91 @@ class BaseCompiler final : public BaseCo
     void branchTo(Assembler::Condition c, RegI64 lhs, RegI64 rhs, Label* l) {
         masm.branch64(c, lhs, rhs, l);
     }
 
     void branchTo(Assembler::Condition c, RegI64 lhs, Imm64 rhs, Label* l) {
         masm.branch64(c, lhs, rhs, l);
     }
 
+#ifdef ENABLE_WASM_GC
+    // The following couple of functions emit a GC pre-write barrier. This is
+    // needed when we replace a member field with a new value, and the previous
+    // field value might have no other referents. The field might belong to an
+    // object or be a stack slot or a register or a heap allocated value.
+    //
+    // let obj = { field: previousValue };
+    // obj.field = newValue; // previousValue must be marked with a pre-barrier.
+    //
+    // Implementing a pre-barrier looks like this:
+    // - call `testNeedPreBarrier` with a fresh label.
+    // - user code must put the address of the field we're about to clobber in
+    // PreBarrierReg (to avoid explicit pushing/popping).
+    // - call `emitPreBarrier`, which binds the label.
+
+    void testNeedPreBarrier(Label* skipBarrier) {
+        MOZ_ASSERT(!skipBarrier->used());
+        MOZ_ASSERT(!skipBarrier->bound());
+
+        // If no incremental GC has started, we don't need the barrier.
+        ScratchPtr scratch(*this);
+        masm.loadWasmTlsRegFromFrame(scratch);
+        masm.loadPtr(Address(scratch, offsetof(TlsData, addressOfNeedsIncrementalBarrier)), scratch);
+        masm.branchTest32(Assembler::Zero, Address(scratch, 0), Imm32(0x1), skipBarrier);
+    }
+
+    void emitPreBarrier(RegPtr valueAddr, Label* skipBarrier) {
+        MOZ_ASSERT(valueAddr == PreBarrierReg);
+
+        // If the previous value is null, we don't need the barrier.
+        ScratchPtr scratch(*this);
+        masm.loadPtr(Address(valueAddr, 0), scratch);
+        masm.branchTestPtr(Assembler::Zero, scratch, scratch, skipBarrier);
+
+        // Call the barrier. This assumes PreBarrierReg contains the address of
+        // the stored value.
+        masm.loadWasmTlsRegFromFrame(scratch);
+        masm.loadPtr(Address(scratch, offsetof(TlsData, instance)), scratch);
+        masm.loadPtr(Address(scratch, Instance::offsetOfPreBarrierCode()), scratch);
+        masm.call(scratch);
+
+        masm.bind(skipBarrier);
+    }
+
+    // This emits a GC post-write barrier. This is needed to ensure that the GC
+    // is aware of slots of tenured things containing references to nursery
+    // values. Pass None for object when the field's owner object is known to
+    // be tenured or heap-allocated.
+
+    void emitPostBarrier(const Maybe<RegPtr>& object, RegPtr setValue, PostBarrierArg arg) {
+        Label skipBarrier;
+
+        // If the set value is null, no barrier.
+        masm.branchTestPtr(Assembler::Zero, setValue, setValue, &skipBarrier);
+
+        RegPtr scratch = needRef();
+        if (object) {
+            // If the object value isn't tenured, no barrier.
+            masm.branchPtrInNurseryChunk(Assembler::Equal, *object, scratch, &skipBarrier);
+        }
+
+        // If the set value is tenured, no barrier.
+        masm.branchPtrInNurseryChunk(Assembler::NotEqual, setValue, scratch, &skipBarrier);
+
+        freeRef(scratch);
+
+        // Need a barrier.
+        uint32_t bytecodeOffset = iter_.lastOpcodeOffset();
+        pushI32(arg.rawPayload());
+        emitInstanceCall(bytecodeOffset, SigPI_, ExprType::Void, SymbolicAddress::PostBarrier);
+
+        masm.bind(&skipBarrier);
+    }
+#endif
+
     // Emit a conditional branch that optionally and optimally cleans up the CPU
     // stack before we branch.
     //
     // Cond is either Assembler::Condition or Assembler::DoubleCondition.
     //
     // Lhs is RegI32, RegI64, or RegF32, or RegF64.
     //
     // Rhs is either the same as Lhs, or an immediate expression compatible with
@@ -8262,16 +8337,19 @@ BaseCompiler::emitGetGlobal()
             pushI64(value.i64());
             break;
           case ValType::F32:
             pushF32(value.f32());
             break;
           case ValType::F64:
             pushF64(value.f64());
             break;
+          case ValType::AnyRef:
+            pushRef(value.ptr());
+            break;
           default:
             MOZ_CRASH("Global constant type");
         }
         return true;
     }
 
     switch (global.type().code()) {
       case ValType::I32: {
@@ -8297,16 +8375,23 @@ BaseCompiler::emitGetGlobal()
       }
       case ValType::F64: {
         RegF64 rv = needF64();
         ScratchI32 tmp(*this);
         masm.loadDouble(addressOfGlobalVar(global, tmp), rv);
         pushF64(rv);
         break;
       }
+      case ValType::AnyRef: {
+        RegPtr rv = needRef();
+        ScratchI32 tmp(*this);
+        masm.loadPtr(addressOfGlobalVar(global, tmp), rv);
+        pushRef(rv);
+        break;
+      }
       default:
         MOZ_CRASH("Global variable type");
         break;
     }
     return true;
 }
 
 bool
@@ -8346,16 +8431,43 @@ BaseCompiler::emitSetGlobal()
       }
       case ValType::F64: {
         RegF64 rv = popF64();
         ScratchI32 tmp(*this);
         masm.storeDouble(rv, addressOfGlobalVar(global, tmp));
         freeF64(rv);
         break;
       }
+#ifdef ENABLE_WASM_GC
+      case ValType::AnyRef: {
+        Label skipBarrier;
+        testNeedPreBarrier(&skipBarrier);
+
+        RegPtr valueAddr(PreBarrierReg);
+        needRef(valueAddr);
+        {
+            ScratchI32 tmp(*this);
+            masm.computeEffectiveAddress(addressOfGlobalVar(global, tmp), valueAddr);
+        }
+        emitPreBarrier(valueAddr, &skipBarrier);
+        freeRef(valueAddr);
+
+        RegPtr rv = popRef();
+        {
+            // Actual store.
+            ScratchI32 tmp(*this);
+            masm.storePtr(rv, addressOfGlobalVar(global, tmp));
+        }
+
+        emitPostBarrier(Nothing(), rv, PostBarrierArg::Global(id));
+
+        freeRef(rv);
+        break;
+      }
+#endif
       default:
         MOZ_CRASH("Global variable type");
         break;
     }
     return true;
 }
 
 // Bounds check elimination.
--- a/js/src/wasm/WasmBuiltins.cpp
+++ b/js/src/wasm/WasmBuiltins.cpp
@@ -672,16 +672,20 @@ AddressOf(SymbolicAddress imm, ABIFuncti
         *abiType = Args_General3;
         return FuncCast(Instance::wake, *abiType);
       case SymbolicAddress::MemCopy:
         *abiType = Args_General4;
         return FuncCast(Instance::memCopy, *abiType);
       case SymbolicAddress::MemFill:
         *abiType = Args_General4;
         return FuncCast(Instance::memFill, *abiType);
+      case SymbolicAddress::PostBarrier:
+        *abiType = Args_General2;
+        static_assert(sizeof(PostBarrierArg) == sizeof(uint32_t), "passed arg is a u32");
+        return FuncCast(Instance::postBarrier, *abiType);
 #if defined(JS_CODEGEN_MIPS32)
       case SymbolicAddress::js_jit_gAtomic64Lock:
         return &js::jit::gAtomic64Lock;
 #endif
       case SymbolicAddress::Limit:
         break;
     }
 
@@ -750,16 +754,17 @@ wasm::NeedsBuiltinThunk(SymbolicAddress 
       case SymbolicAddress::CurrentMemory:
       case SymbolicAddress::WaitI32:
       case SymbolicAddress::WaitI64:
       case SymbolicAddress::Wake:
       case SymbolicAddress::CoerceInPlace_JitEntry:
       case SymbolicAddress::ReportInt64JSCall:
       case SymbolicAddress::MemCopy:
       case SymbolicAddress::MemFill:
+      case SymbolicAddress::PostBarrier:
         return true;
       case SymbolicAddress::Limit:
         break;
     }
 
     MOZ_CRASH("unexpected symbolic address");
 }
 
--- a/js/src/wasm/WasmFrameIter.cpp
+++ b/js/src/wasm/WasmFrameIter.cpp
@@ -1262,16 +1262,18 @@ ThunkedNativeToDescription(SymbolicAddre
       case SymbolicAddress::CoerceInPlace_JitEntry:
         return "out-of-line coercion for jit entry arguments (in wasm)";
       case SymbolicAddress::ReportInt64JSCall:
         return "jit call to int64 wasm function";
       case SymbolicAddress::MemCopy:
         return "call to native memory.copy function";
       case SymbolicAddress::MemFill:
         return "call to native memory.fill function";
+      case SymbolicAddress::PostBarrier:
+        return "call to native GC postbarrier (in wasm)";
 #if defined(JS_CODEGEN_MIPS32)
       case SymbolicAddress::js_jit_gAtomic64Lock:
         MOZ_CRASH();
 #endif
       case SymbolicAddress::Limit:
         break;
     }
     return "?";
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -20,16 +20,17 @@
 
 #include "jit/AtomicOperations.h"
 #include "jit/BaselineJIT.h"
 #include "jit/InlinableNatives.h"
 #include "jit/JitCommon.h"
 #include "wasm/WasmBuiltins.h"
 #include "wasm/WasmModule.h"
 
+#include "gc/StoreBuffer-inl.h"
 #include "vm/ArrayBufferObject-inl.h"
 #include "vm/JSObject-inl.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 using mozilla::BitwiseCast;
 
@@ -479,16 +480,38 @@ Instance::memFill(Instance* instance, ui
 
     }
 
     JSContext* cx = TlsContext.get();
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_OUT_OF_BOUNDS);
     return -1;
 }
 
+/* static */ void
+Instance::postBarrier(Instance* instance, PostBarrierArg arg)
+{
+    gc::Cell** cell = nullptr;
+    switch (arg.type()) {
+      case PostBarrierArg::Type::Global: {
+        const GlobalDesc& global = instance->metadata().globals[arg.globalIndex()];
+        MOZ_ASSERT(!global.isConstant());
+        MOZ_ASSERT(global.type().isRefOrAnyRef());
+        uint8_t* globalAddr = instance->globalData() + global.offset();
+        if (global.isIndirect())
+            globalAddr = *(uint8_t**)globalAddr;
+        MOZ_ASSERT(*(JSObject**)globalAddr, "shouldn't call postbarrier if null");
+        cell = (gc::Cell**) globalAddr;
+        break;
+      }
+    }
+
+    MOZ_ASSERT(cell);
+    TlsContext.get()->runtime()->gc.storeBuffer().putCell(cell);
+}
+
 Instance::Instance(JSContext* cx,
                    Handle<WasmInstanceObject*> object,
                    SharedCode code,
                    UniqueDebugState debug,
                    UniqueTlsData tlsDataIn,
                    HandleWasmMemoryObject memory,
                    SharedTableVector&& tables,
                    Handle<FunctionVector> funcImports,
@@ -513,16 +536,20 @@ Instance::Instance(JSContext* cx,
 #ifndef WASM_HUGE_MEMORY
     tlsData()->boundsCheckLimit = memory ? memory->buffer().wasmBoundsCheckLimit() : 0;
 #endif
     tlsData()->instance = this;
     tlsData()->realm = realm_;
     tlsData()->cx = cx;
     tlsData()->resetInterrupt(cx);
     tlsData()->jumpTable = code_->tieringJumpTable();
+#ifdef ENABLE_WASM_GC
+    tlsData()->addressOfNeedsIncrementalBarrier =
+        (uint8_t*)cx->compartment()->zone()->addressOfNeedsIncrementalBarrier();
+#endif
 
     Tier callerTier = code_->bestTier();
 
     for (size_t i = 0; i < metadata(callerTier).funcImports.length(); i++) {
         HandleFunction f = funcImports[i];
         const FuncImport& fi = metadata(callerTier).funcImports[i];
         FuncImportTls& import = funcImportTls(fi);
         if (!isAsmJS() && IsExportedWasmFunction(f)) {
@@ -636,16 +663,17 @@ Instance::init(JSContext* cx)
         }
     }
 
     JitRuntime* jitRuntime = cx->runtime()->getJitRuntime(cx);
     if (!jitRuntime)
         return false;
     jsJitArgsRectifier_ = jitRuntime->getArgumentsRectifier();
     jsJitExceptionHandler_ = jitRuntime->getExceptionTail();
+    preBarrierCode_ = jitRuntime->preBarrier(MIRType::Object);
     return true;
 }
 
 Instance::~Instance()
 {
     realm_->wasm.unregisterInstance(*this);
 
     const FuncImportVector& funcImports = metadata(code().stableTier()).funcImports;
@@ -703,16 +731,26 @@ Instance::tracePrivate(JSTracer* trc)
     // OK to just do one tier here; though the tiers have different funcImports
     // tables, they share the tls object.
     for (const FuncImport& fi : metadata(code().stableTier()).funcImports)
         TraceNullableEdge(trc, &funcImportTls(fi).obj, "wasm import");
 
     for (const SharedTable& table : tables_)
         table->trace(trc);
 
+#ifdef ENABLE_WASM_GC
+    for (const GlobalDesc& global : code().metadata().globals) {
+        // Indirect anyref global get traced by the owning WebAssembly.Global.
+        if (global.type() != ValType::AnyRef || global.isConstant() || global.isIndirect())
+            continue;
+        GCPtrObject* obj = (GCPtrObject*)(globalData() + global.offset());
+        TraceNullableEdge(trc, obj, "wasm anyref global");
+    }
+#endif
+
     TraceNullableEdge(trc, &memory_, "wasm buffer");
 }
 
 void
 Instance::trace(JSTracer* trc)
 {
     // Technically, instead of having this method, the caller could use
     // Instance::object() to get the owning WasmInstanceObject to mark,
--- a/js/src/wasm/WasmInstance.h
+++ b/js/src/wasm/WasmInstance.h
@@ -42,16 +42,19 @@ namespace wasm {
 // their code.
 
 class Instance
 {
     JS::Realm* const                realm_;
     ReadBarrieredWasmInstanceObject object_;
     jit::TrampolinePtr              jsJitArgsRectifier_;
     jit::TrampolinePtr              jsJitExceptionHandler_;
+#ifdef ENABLE_WASM_GC
+    jit::TrampolinePtr              preBarrierCode_;
+#endif
     const SharedCode                code_;
     const UniqueDebugState          debug_;
     const UniqueTlsData             tlsData_;
     GCPtrWasmMemoryObject           memory_;
     SharedTableVector               tables_;
     bool                            enterFrameTrapsEnabled_;
 
     // Internal helpers:
@@ -103,16 +106,21 @@ class Instance
 #endif
 
     static constexpr size_t offsetOfJSJitArgsRectifier() {
         return offsetof(Instance, jsJitArgsRectifier_);
     }
     static constexpr size_t offsetOfJSJitExceptionHandler() {
         return offsetof(Instance, jsJitExceptionHandler_);
     }
+#ifdef ENABLE_WASM_GC
+    static constexpr size_t offsetOfPreBarrierCode() {
+        return offsetof(Instance, preBarrierCode_);
+    }
+#endif
 
     // This method returns a pointer to the GC object that owns this Instance.
     // Instances may be reached via weak edges (e.g., Compartment::instances_)
     // so this perform a read-barrier on the returned object unless the barrier
     // is explicitly waived.
 
     WasmInstanceObject* object() const;
     WasmInstanceObject* objectUnbarriered() const;
@@ -166,16 +174,17 @@ class Instance
     static int32_t callImport_ref(Instance*, int32_t, int32_t, uint64_t*);
     static uint32_t growMemory_i32(Instance* instance, uint32_t delta);
     static uint32_t currentMemory_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, uint32_t srcByteOffset, uint32_t len);
     static int32_t memFill(Instance* instance, uint32_t byteOffset, uint32_t value, uint32_t len);
+    static void postBarrier(Instance* instance, PostBarrierArg arg);
 };
 
 typedef UniquePtr<Instance> UniqueInstance;
 
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_instance_h
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -129,32 +129,47 @@ ToWebAssemblyValue(JSContext* cx, ValTyp
       }
       case ValType::F64: {
         double d;
         if (!ToNumber(cx, v, &d))
             return false;
         *val = Val(d);
         return true;
       }
+      case ValType::AnyRef: {
+        if (v.isNull()) {
+            *val = Val(ValType::AnyRef, nullptr);
+        } else {
+            JSObject* obj = ToObject(cx, v);
+            if (!obj)
+                return false;
+            *val = Val(ValType::AnyRef, obj);
+        }
+        return true;
+      }
       default: {
         MOZ_CRASH("unexpected import value type, caller must guard");
       }
     }
 }
 
 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::AnyRef:
+        if (!val.ptr())
+            return NullValue();
+        return ObjectValue(*(JSObject*)val.ptr());
       default:
         MOZ_CRASH("unexpected type when translating to a JS value");
     }
 }
 
 // ============================================================================
 // Imports
 
@@ -261,32 +276,38 @@ GetImports(JSContext* cx,
                 }
 
                 if (globalObjs.length() <= index && !globalObjs.resize(index + 1)) {
                     ReportOutOfMemory(cx);
                     return false;
                 }
                 globalObjs[index] = obj;
                 val = obj->val();
-            } else
-            if (v.isNumber()) {
+            } else {
+                if (IsNumberType(global.type())) {
+                    if (!v.isNumber())
+                        return ThrowBadImportType(cx, import.field.get(), "Number");
+                } else {
+                    MOZ_ASSERT(global.type().isRefOrAnyRef());
+                    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;
                 }
 
                 if (global.isMutable()) {
                     JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_MUT_LINK);
                     return false;
                 }
 
                 if (!ToWebAssemblyValue(cx, global.type(), v, &val))
                     return false;
-            } else {
-                return ThrowBadImportType(cx, import.field.get(), "Number");
             }
 
             if (!globalImportValues->append(val))
                 return false;
 
             break;
           }
         }
@@ -2097,56 +2118,72 @@ const ClassOps WasmGlobalObject::classOp
     nullptr, /* enumerate */
     nullptr, /* newEnumerate */
     nullptr, /* resolve */
     nullptr, /* mayResolve */
     WasmGlobalObject::finalize,
     nullptr, /* call */
     nullptr, /* hasInstance */
     nullptr, /* construct */
-    nullptr  /* trace */
+    WasmGlobalObject::trace
 };
 
 const Class WasmGlobalObject::class_ =
 {
     "WebAssembly.Global",
     JSCLASS_HAS_RESERVED_SLOTS(WasmGlobalObject::RESERVED_SLOTS) |
     JSCLASS_BACKGROUND_FINALIZE,
     &WasmGlobalObject::classOps_
 };
 
 /* static */ void
+WasmGlobalObject::trace(JSTracer* trc, JSObject* obj)
+{
+    WasmGlobalObject* global = reinterpret_cast<WasmGlobalObject*>(obj);
+    switch (global->type().code()) {
+      case ValType::AnyRef:
+        TraceNullableEdge(trc, &global->cell()->ptr, "wasm anyref global");
+        break;
+      default:
+        break;
+    }
+}
+
+/* static */ void
 WasmGlobalObject::finalize(FreeOp*, JSObject* obj)
 {
     WasmGlobalObject* global = reinterpret_cast<WasmGlobalObject*>(obj);
     js_delete(global->cell());
 }
 
 /* static */ WasmGlobalObject*
 WasmGlobalObject::create(JSContext* cx, const Val& val, bool isMutable)
 {
     UniquePtr<Cell> cell = js::MakeUnique<Cell>();
     if (!cell)
         return nullptr;
 
     switch (val.type().code()) {
-      case ValType::I32: cell->i32 = val.i32(); break;
-      case ValType::I64: cell->i64 = val.i64(); break;
-      case ValType::F32: cell->f32 = val.f32(); break;
-      case ValType::F64: cell->f64 = val.f64(); break;
-      default:           MOZ_CRASH();
+      case ValType::I32:    cell->i32 = val.i32(); break;
+      case ValType::I64:    cell->i64 = val.i64(); break;
+      case ValType::F32:    cell->f32 = val.f32(); break;
+      case ValType::F64:    cell->f64 = val.f64(); break;
+      case ValType::AnyRef: cell->ptr = (JSObject*)val.ptr(); break;
+      default:              MOZ_CRASH();
     }
 
     RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmGlobal).toObject());
 
     AutoSetNewObjectMetadata metadata(cx);
     RootedWasmGlobalObject obj(cx, NewObjectWithGivenProto<WasmGlobalObject>(cx, proto));
     if (!obj)
         return nullptr;
 
+    MOZ_ASSERT(obj->isTenured(), "assumed by set_global post barriers");
+
     obj->initReservedSlot(TYPE_SLOT, Int32Value(int32_t(val.type().bitsUnsafe())));
     obj->initReservedSlot(MUTABLE_SLOT, JS::BooleanValue(isMutable));
     obj->initReservedSlot(CELL_SLOT, PrivateValue(cell.release()));
 
     return obj;
 }
 
 /* static */ bool
@@ -2185,41 +2222,45 @@ WasmGlobalObject::construct(JSContext* c
     } else if (args.length() == 1 && StringEqualsAscii(typeLinearStr, "i64")) {
         // For the time being, i64 is allowed only if there is not an
         // initializing value.
         globalType = ValType::I64;
     } else if (StringEqualsAscii(typeLinearStr, "f32")) {
         globalType = ValType::F32;
     } else if (StringEqualsAscii(typeLinearStr, "f64")) {
         globalType = ValType::F64;
+#ifdef ENABLE_WASM_GC
+    } else if (cx->options().wasmGc() && StringEqualsAscii(typeLinearStr, "anyref")) {
+        globalType = ValType::AnyRef;
+#endif
     } else {
         JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GLOBAL_TYPE);
         return false;
     }
 
     RootedValue mutableVal(cx);
     if (!JS_GetProperty(cx, obj, "mutable", &mutableVal))
         return false;
 
     bool isMutable = ToBoolean(mutableVal);
 
     // Extract the initial value, or provide a suitable default.
     // Guard against control flow mistakes below failing to set |globalVal|.
     Val globalVal = Val(uint32_t(0));
     if (args.length() >= 2) {
         RootedValue valueVal(cx, args.get(1));
-
         if (!ToWebAssemblyValue(cx, globalType, valueVal, &globalVal))
             return false;
     } else {
         switch (globalType.code()) {
-          case ValType::I32: /* set above */               break;
+          case ValType::I32:    /* set above */ 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::F32:    globalVal = Val(float(0.0)); break;
+          case ValType::F64:    globalVal = Val(double(0.0)); break;
+          case ValType::AnyRef: globalVal = Val(ValType::AnyRef, nullptr); break;
           default: MOZ_CRASH();
         }
     }
 
     WasmGlobalObject* global = WasmGlobalObject::create(cx, globalVal, isMutable);
     if (!global)
         return false;
 
@@ -2235,16 +2276,17 @@ 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::AnyRef:
         args.rval().set(args.thisv().toObject().as<WasmGlobalObject>().value());
         return true;
       case ValType::I64:
         JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_I64_TYPE);
         return false;
       default:
         MOZ_CRASH();
     }
@@ -2272,20 +2314,21 @@ WasmGlobalObject::valueSetterImpl(JSCont
     }
 
     Val val;
     if (!ToWebAssemblyValue(cx, global->type(), args.get(0), &val))
         return false;
 
     Cell* cell = global->cell();
     switch (global->type().code()) {
-      case ValType::I32: cell->i32 = val.i32(); break;
-      case ValType::F32: cell->f32 = val.f32(); break;
-      case ValType::F64: cell->f64 = val.f64(); break;
-      default:           MOZ_CRASH();
+      case ValType::I32:    cell->i32 = val.i32(); break;
+      case ValType::F32:    cell->f32 = val.f32(); break;
+      case ValType::F64:    cell->f64 = val.f64(); break;
+      case ValType::AnyRef: cell->ptr = (JSObject*)val.ptr(); break;
+      default:              MOZ_CRASH();
     }
 
     args.rval().setUndefined();
     return true;
 }
 
 /* static */ bool
 WasmGlobalObject::valueSetter(JSContext* cx, unsigned argc, Value* vp)
@@ -2323,21 +2366,22 @@ WasmGlobalObject::isMutable() const
 }
 
 Val
 WasmGlobalObject::val() const
 {
     Cell* cell = this->cell();
     Val val;
     switch (type().code()) {
-      case ValType::I32: val = Val(uint32_t(cell->i32)); break;
-      case ValType::I64: val = Val(uint64_t(cell->i64)); break;
-      case ValType::F32: val = Val(cell->f32); break;
-      case ValType::F64: val = Val(cell->f64); break;
-      default:           MOZ_CRASH();
+      case ValType::I32:    val = Val(uint32_t(cell->i32)); break;
+      case ValType::I64:    val = Val(uint64_t(cell->i64)); break;
+      case ValType::F32:    val = Val(cell->f32); break;
+      case ValType::F64:    val = Val(cell->f64); break;
+      case ValType::AnyRef: val = Val(ValType::AnyRef, (void*)cell->ptr); break;
+      default:              MOZ_CRASH();
     }
     return val;
 }
 
 Value
 WasmGlobalObject::value() const
 {
     // ToJSValue crashes on I64; this is desirable.
--- a/js/src/wasm/WasmJS.h
+++ b/js/src/wasm/WasmJS.h
@@ -121,30 +121,35 @@ class WasmModuleObject : public NativeOb
 class WasmGlobalObject : public NativeObject
 {
     static const unsigned TYPE_SLOT = 0;
     static const unsigned MUTABLE_SLOT = 1;
     static const unsigned CELL_SLOT = 2;
 
     static const ClassOps classOps_;
     static void finalize(FreeOp*, JSObject* obj);
+    static void trace(JSTracer* trc, JSObject* obj);
 
     static bool valueGetterImpl(JSContext* cx, const CallArgs& args);
     static bool valueGetter(JSContext* cx, unsigned argc, Value* vp);
     static bool valueSetterImpl(JSContext* cx, const CallArgs& args);
     static bool valueSetter(JSContext* cx, unsigned argc, Value* vp);
 
   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;
+        int32_t     i32;
+        int64_t     i64;
+        float       f32;
+        double      f64;
+        GCPtrObject ptr;
+
+        Cell() : i64(0) {}
+        ~Cell() {}
     };
 
     static const unsigned RESERVED_SLOTS = 3;
     static const Class class_;
     static const JSPropertySpec properties[];
     static const JSFunctionSpec methods[];
     static const JSFunctionSpec static_methods[];
     static bool construct(JSContext*, unsigned, Value*);
--- a/js/src/wasm/WasmTypes.cpp
+++ b/js/src/wasm/WasmTypes.cpp
@@ -78,19 +78,20 @@ Val::writePayload(uint8_t* dst) const
       case ValType::F32x4:
       case ValType::B8x16:
       case ValType::B16x8:
       case ValType::B32x4:
         memcpy(dst, &u, jit::Simd128DataSize);
         return;
       case ValType::Ref:
       case ValType::AnyRef:
-        // TODO
-        MOZ_CRASH("writing imported value of Ref/AnyRef in global NYI");
+        memcpy(dst, &u.ptr_, sizeof(intptr_t));
+        return;
     }
+    MOZ_CRASH("unexpected Val type");
 }
 
 bool
 wasm::IsRoundingFunction(SymbolicAddress callee, jit::RoundingMode* mode)
 {
     switch (callee) {
       case SymbolicAddress::FloorD:
       case SymbolicAddress::FloorF:
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -502,17 +502,17 @@ SizeOf(ValType vt)
       case ValType::I32x4:
       case ValType::F32x4:
       case ValType::B8x16:
       case ValType::B16x8:
       case ValType::B32x4:
         return 16;
       case ValType::AnyRef:
       case ValType::Ref:
-        MOZ_CRASH("unexpected ref/anyref");
+        return sizeof(intptr_t);
     }
     MOZ_CRASH("Invalid ValType");
 }
 
 static inline bool
 IsSimdType(ValType vt)
 {
     switch (vt.code()) {
@@ -780,27 +780,33 @@ class Val
         uint32_t i32_;
         uint64_t i64_;
         float f32_;
         double f64_;
         I8x16 i8x16_;
         I16x8 i16x8_;
         I32x4 i32x4_;
         F32x4 f32x4_;
+        intptr_t ptr_;
     } u;
 
   public:
     Val() = default;
 
     explicit Val(uint32_t i32) : type_(ValType::I32) { u.i32_ = i32; }
     explicit Val(uint64_t i64) : type_(ValType::I64) { u.i64_ = i64; }
 
     explicit Val(float f32) : type_(ValType::F32) { u.f32_ = f32; }
     explicit Val(double f64) : type_(ValType::F64) { u.f64_ = f64; }
 
+    explicit Val(ValType refType, void* ptr) : type_(refType) {
+        MOZ_ASSERT(refType.isRefOrAnyRef());
+        u.ptr_ = intptr_t(ptr);
+    }
+
     explicit Val(const I8x16& i8x16, ValType type = ValType::I8x16) : type_(type) {
         MOZ_ASSERT(type_ == ValType::I8x16 || type_ == ValType::B8x16);
         memcpy(u.i8x16_, i8x16, sizeof(u.i8x16_));
     }
     explicit Val(const I16x8& i16x8, ValType type = ValType::I16x8) : type_(type) {
         MOZ_ASSERT(type_ == ValType::I16x8 || type_ == ValType::B16x8);
         memcpy(u.i16x8_, i16x8, sizeof(u.i16x8_));
     }
@@ -815,16 +821,17 @@ class Val
     ValType type() const { return type_; }
     bool isSimd() const { return IsSimdType(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_; }
+    intptr_t ptr() const { MOZ_ASSERT(type_.isRefOrAnyRef()); return u.ptr_; }
 
     const I8x16& i8x16() const {
         MOZ_ASSERT(type_ == ValType::I8x16 || type_ == ValType::B8x16);
         return u.i8x16_;
     }
     const I16x8& i16x8() const {
         MOZ_ASSERT(type_ == ValType::I16x8 || type_ == ValType::B16x8);
         return u.i16x8_;
@@ -987,17 +994,17 @@ class InitExpr
 
     bool isVal() const { return kind() == Kind::Constant; }
     Val val() const { MOZ_ASSERT(isVal()); return u.val_; }
 
     uint32_t globalIndex() const { MOZ_ASSERT(kind() == Kind::GetGlobal); return u.global.index_; }
 
     ValType type() const {
         switch (kind()) {
-          case Kind::Constant: return u.val_.type();
+          case Kind::Constant:  return u.val_.type();
           case Kind::GetGlobal: return u.global.type_;
         }
         MOZ_CRASH("unexpected initExpr type");
     }
 };
 
 // CacheableChars is used to cacheably store UniqueChars.
 
@@ -1120,17 +1127,18 @@ class GlobalDesc
             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)
+    explicit GlobalDesc(ValType type, bool isMutable, uint32_t importIndex,
+                        ModuleKind kind = ModuleKind::Wasm)
       : kind_(GlobalKind::Import)
     {
         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;
@@ -1937,16 +1945,17 @@ enum class SymbolicAddress
     Int64ToDouble,
     GrowMemory,
     CurrentMemory,
     WaitI32,
     WaitI64,
     Wake,
     MemCopy,
     MemFill,
+    PostBarrier,
 #if defined(JS_CODEGEN_MIPS32)
     js_jit_gAtomic64Lock,
 #endif
     Limit
 };
 
 bool
 IsRoundingFunction(SymbolicAddress callee, jit::RoundingMode* mode);
@@ -2058,16 +2067,20 @@ struct TlsData
     // Usually equal to cx->stackLimitForJitCode(JS::StackForUntrustedScript),
     // but can be racily set to trigger immediate trap as an opportunity to
     // CheckForInterrupt without an additional branch.
     Atomic<uintptr_t, mozilla::Relaxed> stackLimit;
 
     // Set to 1 when wasm should call CheckForInterrupt.
     Atomic<uint32_t, mozilla::Relaxed> interrupt;
 
+#ifdef ENABLE_WASM_GC
+    uint8_t* addressOfNeedsIncrementalBarrier;
+#endif
+
     // Methods to set, test and clear the above two fields. Both interrupt
     // fields are Relaxed and so no consistency/ordering can be assumed.
     void setInterrupt();
     bool isInterrupted() const;
     void resetInterrupt(JSContext* cx);
 
     // Pointer that should be freed (due to padding before the TlsData).
     void* allocatedBase;
@@ -2514,12 +2527,54 @@ class DebugFrame
 
     // DebugFrames are aligned to 8-byte aligned, allowing them to be placed in
     // an AbstractFramePtr.
 
     static const unsigned Alignment = 8;
     static void alignmentStaticAsserts();
 };
 
+# ifdef ENABLE_WASM_GC
+// A packed format for an argument to the Instance::postBarrier function.
+class PostBarrierArg
+{
+  public:
+    enum class Type {
+        Global = 0x0,
+        Last = Global
+    };
+
+  private:
+    uint32_t type_: 1;
+    uint32_t payload_: 31;
+
+    PostBarrierArg(uint32_t payload, Type type)
+      : type_(uint32_t(type)),
+        payload_(payload)
+    {
+        MOZ_ASSERT(payload < (UINT32_MAX >> 1));
+        MOZ_ASSERT(uint32_t(type) <= uint32_t(Type::Last));
+    }
+
+  public:
+    static PostBarrierArg Global(uint32_t globalIndex) {
+        return PostBarrierArg(globalIndex, Type::Global);
+    }
+
+    Type type() const {
+        MOZ_ASSERT(type_ <= uint32_t(Type::Last));
+        return Type(type_);
+    }
+    uint32_t globalIndex() const {
+        MOZ_ASSERT(type() == Type::Global);
+        return payload_;
+    }
+
+    uint32_t rawPayload() const {
+        return (payload_ << 1) | type_;
+    }
+};
+# endif
+
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_types_h
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -1393,29 +1393,30 @@ DecodeTableLimits(Decoder& d, TableDescV
 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::AnyRef:
         break;
       default:
         return d.fail("unexpected variable type in global import/export");
     }
 
     return true;
 }
 
 static bool
-DecodeGlobalType(Decoder& d, const TypeDefVector& types, ValType* type, bool* isMutable)
+DecodeGlobalType(Decoder& d, const TypeDefVector& types, HasGcTypes gcTypesEnabled, ValType* type,
+                 bool* isMutable)
 {
-    // No gc types in globals at the moment.
-    if (!DecodeValType(d, ModuleKind::Wasm, types.length(), HasGcTypes::False, type))
+    if (!DecodeValType(d, ModuleKind::Wasm, types.length(), gcTypesEnabled, type))
         return false;
     if (!ValidateRefType(d, types, *type))
         return false;
 
     uint8_t flags;
     if (!d.readFixedU8(&flags))
         return d.fail("expected global flags");
 
@@ -1504,17 +1505,17 @@ DecodeImport(Decoder& d, ModuleEnvironme
       case DefinitionKind::Memory: {
         if (!DecodeMemoryLimits(d, env))
             return false;
         break;
       }
       case DefinitionKind::Global: {
         ValType type;
         bool isMutable;
-        if (!DecodeGlobalType(d, env->types, &type, &isMutable))
+        if (!DecodeGlobalType(d, env->types, env->gcTypesEnabled, &type, &isMutable))
             return false;
         if (!GlobalIsJSCompatible(d, type, isMutable))
             return false;
         if (!env->globals.append(GlobalDesc(type, isMutable, env->globals.length())))
             return false;
         if (env->globals.length() > MaxGlobals)
             return d.fail("too many globals");
         break;
@@ -1632,18 +1633,18 @@ DecodeMemorySection(Decoder& d, ModuleEn
         if (!DecodeMemoryLimits(d, env))
             return false;
     }
 
     return d.finishSection(*range, "memory");
 }
 
 static bool
-DecodeInitializerExpression(Decoder& d, const GlobalDescVector& globals, ValType expected,
-                            InitExpr* init)
+DecodeInitializerExpression(Decoder& d, HasGcTypes gcTypesEnabled, const GlobalDescVector& globals,
+                            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): {
         int32_t i32;
@@ -1668,16 +1669,28 @@ DecodeInitializerExpression(Decoder& d, 
       }
       case uint16_t(Op::F64Const): {
         double f64;
         if (!d.readFixedF64(&f64))
             return d.fail("failed to read initializer f64 expression");
         *init = InitExpr(Val(f64));
         break;
       }
+      case uint16_t(Op::RefNull): {
+        if (gcTypesEnabled == HasGcTypes::False)
+            return d.fail("unexpected initializer expression");
+        uint8_t valType;
+        uint32_t unusedRefTypeIndex;
+        if (!d.readValType(&valType, &unusedRefTypeIndex))
+            return false;
+        if (valType != uint8_t(ValType::AnyRef))
+            return d.fail("expected anyref as type for ref.null");
+        *init = InitExpr(Val(ValType::AnyRef, nullptr));
+        break;
+      }
       case uint16_t(Op::GetGlobal): {
         uint32_t i;
         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");
@@ -1718,21 +1731,21 @@ DecodeGlobalSection(Decoder& d, ModuleEn
         return d.fail("too many globals");
 
     if (!env->globals.reserve(numGlobals.value()))
         return false;
 
     for (uint32_t i = 0; i < numDefs; i++) {
         ValType type;
         bool isMutable;
-        if (!DecodeGlobalType(d, env->types, &type, &isMutable))
+        if (!DecodeGlobalType(d, env->types, env->gcTypesEnabled, &type, &isMutable))
             return false;
 
         InitExpr initializer;
-        if (!DecodeInitializerExpression(d, env->globals, type, &initializer))
+        if (!DecodeInitializerExpression(d, env->gcTypesEnabled, env->globals, type, &initializer))
             return false;
 
         env->globals.infallibleAppend(GlobalDesc(initializer, isMutable));
     }
 
     return d.finishSection(*range, "global");
 }
 
@@ -1903,17 +1916,18 @@ DecodeElemSection(Decoder& d, ModuleEnvi
         if (!d.readVarU32(&tableIndex))
             return d.fail("expected table index");
 
         MOZ_ASSERT(env->tables.length() <= 1);
         if (tableIndex >= env->tables.length())
             return d.fail("table index out of range");
 
         InitExpr offset;
-        if (!DecodeInitializerExpression(d, env->globals, ValType::I32, &offset))
+        if (!DecodeInitializerExpression(d, env->gcTypesEnabled, env->globals, ValType::I32,
+                                         &offset))
             return false;
 
         uint32_t numElems;
         if (!d.readVarU32(&numElems))
             return d.fail("expected segment size");
 
         if (numElems > MaxTableInitialLength)
             return d.fail("too many table elements");
@@ -2073,17 +2087,18 @@ DecodeDataSection(Decoder& d, ModuleEnvi
 
         if (linearMemoryIndex != 0)
             return d.fail("linear memory index must currently be 0");
 
         if (!env->usesMemory())
             return d.fail("data segment requires a memory section");
 
         DataSegment seg;
-        if (!DecodeInitializerExpression(d, env->globals, ValType::I32, &seg.offset))
+        if (!DecodeInitializerExpression(d, env->gcTypesEnabled, env->globals, ValType::I32,
+                                         &seg.offset))
             return false;
 
         if (!d.readVarU32(&seg.length))
             return d.fail("expected segment size");
 
         if (seg.length > MaxMemoryInitialPages * PageSize)
             return d.fail("segment size too big");