Bug 1487327 - Custom section for opting in to GC feature work. r=luke
authorLars T Hansen <lhansen@mozilla.com>
Fri, 31 Aug 2018 08:50:24 +0200
changeset 434743 e99f9ea180465aacbe10bca19201d54a2919e29f
parent 434742 492491501281e4c9b8d7db0f1584171420a20989
child 434744 2168cbf8a779fe9c7f9ecefb8f57d11ca3ac2fb1
push id107480
push userlhansen@mozilla.com
push dateWed, 05 Sep 2018 08:10:35 +0000
treeherdermozilla-inbound@e99f9ea18046 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1487327
milestone64.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 1487327 - Custom section for opting in to GC feature work. r=luke We introduce a new numbered section which must be the first section in the .wasm file if it is present at all. This section, GcFeatureOptIn, carries the version number of the GC feature the module wants to opt in to. During validation we signal an error if we can't satisfy the requirement; and we signal errors for all the GC feature aspects (anyref, ref, struct, and all the ref-type instructions) if no opt-in section was present. This patch does not affect how we generate code for stubs; that code is controlled by the --wasm-gc flag, as it is module-independent. This patch also does not change how we choose the compiler; that is still based on --wasm-gc. But once --wasm-gc disappears we will be using the opt-in section to control compiler selection until Ion can support the GC feature. Currently the only supported GC feature version is 1. As the engine evolves to accomodate new versions we will accept old version numbers provided the engine remains completely backward compatible. (We can also provide version-triggered backward compatibility but I question the utility.)
js/src/jit-test/lib/wasm-binary.js
js/src/jit-test/tests/wasm/binary.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-val-tracing.js
js/src/jit-test/tests/wasm/gc/anyref.js
js/src/jit-test/tests/wasm/gc/binary.js
js/src/jit-test/tests/wasm/gc/debugger.js
js/src/jit-test/tests/wasm/gc/disabled.js
js/src/jit-test/tests/wasm/gc/gc-feature-opt-in.js
js/src/jit-test/tests/wasm/gc/ref-global.js
js/src/jit-test/tests/wasm/gc/ref-restrict.js
js/src/jit-test/tests/wasm/gc/ref.js
js/src/jit-test/tests/wasm/gc/structs.js
js/src/wasm/WasmAST.h
js/src/wasm/WasmBaselineCompile.cpp
js/src/wasm/WasmBinaryConstants.h
js/src/wasm/WasmCode.cpp
js/src/wasm/WasmCode.h
js/src/wasm/WasmCompile.cpp
js/src/wasm/WasmCompile.h
js/src/wasm/WasmGenerator.cpp
js/src/wasm/WasmIonCompile.cpp
js/src/wasm/WasmModule.cpp
js/src/wasm/WasmOpIter.h
js/src/wasm/WasmStubs.cpp
js/src/wasm/WasmStubs.h
js/src/wasm/WasmTextToBinary.cpp
js/src/wasm/WasmValidate.cpp
js/src/wasm/WasmValidate.h
--- a/js/src/jit-test/lib/wasm-binary.js
+++ b/js/src/jit-test/lib/wasm-binary.js
@@ -19,16 +19,17 @@ const functionId       = 3;
 const tableId          = 4;
 const memoryId         = 5;
 const globalId         = 6;
 const exportId         = 7;
 const startId          = 8;
 const elemId           = 9;
 const codeId           = 10;
 const dataId           = 11;
+const gcFeatureOptInId = 42;
 
 // User-defined section names
 const nameName         = "name";
 
 // Name section name types
 const nameTypeModule   = 0;
 const nameTypeFunction = 1;
 const nameTypeLocal    = 2;
@@ -172,16 +173,20 @@ function moduleWithSections(sectionArray
     for (let section of sectionArray) {
         bytes.push(section.name);
         bytes.push(...varU32(section.body.length));
         bytes.push(...section.body);
     }
     return toU8(bytes);
 }
 
+function gcFeatureOptInSection(version) {
+    return { name: gcFeatureOptInId, body: [ version & 0x7F ] }
+}
+
 function sigSection(sigs) {
     var body = [];
     body.push(...varU32(sigs.length));
     for (let sig of sigs) {
         body.push(...varU32(FuncCode));
         body.push(...varU32(sig.args.length));
         for (let arg of sig.args)
             body.push(...varU32(arg));
--- a/js/src/jit-test/tests/wasm/binary.js
+++ b/js/src/jit-test/tests/wasm/binary.js
@@ -42,19 +42,19 @@ assertErrorMessage(() => wasmEval(toU8(m
 assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(globalId))), CompileError, sectionError("global"));
 assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(exportId))), CompileError, sectionError("export"));
 assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(startId))), CompileError, sectionError("start"));
 assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(elemId))), CompileError, sectionError("elem"));
 assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(codeId))), CompileError, sectionError("code"));
 assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(dataId))), CompileError, sectionError("data"));
 
 // unknown sections are unconditionally rejected
-assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(42))), CompileError, unknownSection);
-assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(42, 0))), CompileError, unknownSection);
-assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(42, 1, 0))), CompileError, unknownSection);
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(37))), CompileError, unknownSection);
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(37, 0))), CompileError, unknownSection);
+assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(37, 1, 0))), CompileError, unknownSection);
 
 // user sections have special rules
 assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0))), CompileError, sectionError("custom"));  // no length
 assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 0))), CompileError, sectionError("custom"));  // no id
 assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 0, 0))), CompileError, sectionError("custom"));  // payload too small to have id length
 assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 1, 1))), CompileError, sectionError("custom"));  // id not present
 assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 1, 1, 65))), CompileError, sectionError("custom"));  // id length doesn't fit in section
 assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 1, 0, 0))), CompileError, sectionError("custom"));  // second, unfinished custom section
--- a/js/src/jit-test/tests/wasm/gc/anyref-global-postbarrier.js
+++ b/js/src/jit-test/tests/wasm/gc/anyref-global-postbarrier.js
@@ -7,34 +7,37 @@ const { startProfiling, endProfiling, as
 // Dummy constructor.
 function Baguette(calories) {
     this.calories = calories;
 }
 
 // Ensure the baseline compiler sync's before the postbarrier.
 (function() {
     wasmEvalText(`(module
+        (gc_feature_opt_in 1)
         (global (mut anyref) (ref.null anyref))
         (func (export "f")
             get_global 0
             ref.null anyref
             set_global 0
             set_global 0
         )
     )`).exports.f();
 })();
 
 let exportsPlain = wasmEvalText(`(module
+    (gc_feature_opt_in 1)
     (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
+    (gc_feature_opt_in 1)
     (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);
 
--- a/js/src/jit-test/tests/wasm/gc/anyref-global-prebarrier.js
+++ b/js/src/jit-test/tests/wasm/gc/anyref-global-prebarrier.js
@@ -1,15 +1,16 @@
 if (!wasmGcEnabled()) {
     quit(0);
 }
 
 const { startProfiling, endProfiling, assertEqPreciseStacks, isSingleStepProfilingEnabled } = WasmHelpers;
 
 let e = wasmEvalText(`(module
+    (gc_feature_opt_in 1)
     (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
--- a/js/src/jit-test/tests/wasm/gc/anyref-val-tracing.js
+++ b/js/src/jit-test/tests/wasm/gc/anyref-val-tracing.js
@@ -1,14 +1,15 @@
 if (!wasmGcEnabled()) {
     quit(0);
 }
 
 gczeal(14, 1);
 let { exports } = wasmEvalText(`(module
+    (gc_feature_opt_in 1)
     (global $anyref (import "glob" "anyref") anyref)
     (func (export "get") (result anyref) get_global $anyref)
 )`, {
     glob: {
         anyref: { sentinel: "lol" },
     }
 });
 assertEq(exports.get().sentinel, "lol");
--- a/js/src/jit-test/tests/wasm/gc/anyref.js
+++ b/js/src/jit-test/tests/wasm/gc/anyref.js
@@ -9,63 +9,67 @@ function Baguette(calories) {
     this.calories = calories;
 }
 
 // Type checking.
 
 const { validate, CompileError } = WebAssembly;
 
 assertErrorMessage(() => wasmEvalText(`(module
+    (gc_feature_opt_in 1)
     (func (result anyref)
         i32.const 42
     )
 )`), CompileError, mismatchError('i32', 'anyref'));
 
 assertErrorMessage(() => wasmEvalText(`(module
+    (gc_feature_opt_in 1)
     (func (result anyref)
         i32.const 0
         ref.null anyref
         i32.const 42
         select
     )
 )`), CompileError, /select operand types/);
 
 assertErrorMessage(() => wasmEvalText(`(module
+    (gc_feature_opt_in 1)
     (func (result i32)
         ref.null anyref
         if
             i32.const 42
         end
     )
 )`), CompileError, mismatchError('anyref', 'i32'));
 
 
 // Basic compilation tests.
 
 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)))`,
+    "(module (gc_feature_opt_in 1) (func (drop (ref.null anyref))))",
+    "(module (gc_feature_opt_in 1) (func $test (local anyref)))",
+    "(module (gc_feature_opt_in 1) (func $test (param anyref)))",
+    "(module (gc_feature_opt_in 1) (func $test (result anyref) (ref.null anyref)))",
+    "(module (gc_feature_opt_in 1) (func $test (block anyref (unreachable)) unreachable))",
+    "(module (gc_feature_opt_in 1) (func $test (local anyref) (result i32) (ref.is_null (get_local 0))))",
+    `(module (gc_feature_opt_in 1) (import "a" "b" (param anyref)))`,
+    `(module (gc_feature_opt_in 1) (import "a" "b" (result anyref)))`,
+    `(module (gc_feature_opt_in 1) (global anyref (ref.null anyref)))`,
+    `(module (gc_feature_opt_in 1) (global (mut anyref) (ref.null anyref)))`,
 ];
 
 for (let src of simpleTests) {
     wasmEvalText(src, {a:{b(){}}});
     assertEq(validate(wasmTextToBinary(src)), true);
 }
 
 // Basic behavioral tests.
 
 let { exports } = wasmEvalText(`(module
+    (gc_feature_opt_in 1)
     (func (export "is_null") (result i32)
         ref.null anyref
         ref.is_null
     )
 
     (func $sum (result i32) (param i32)
         get_local 0
         i32.const 42
@@ -93,16 +97,17 @@ let { exports } = wasmEvalText(`(module
 
 assertEq(exports.is_null(), 1);
 assertEq(exports.is_null_spill(), 1);
 assertEq(exports.is_null_local(), 1);
 
 // Anyref param and result in wasm functions.
 
 exports = wasmEvalText(`(module
+    (gc_feature_opt_in 1)
     (func (export "is_null") (result i32) (param $ref anyref)
         get_local $ref
         ref.is_null
     )
 
     (func (export "ref_or_null") (result anyref) (param $ref anyref) (param $selector i32)
         get_local $ref
         ref.null anyref
@@ -150,31 +155,33 @@ assertEq(ref.calories, baguette.calories
 
 ref = exports.nested(baguette, 0);
 assertEq(ref, baguette);
 assertEq(ref.calories, baguette.calories);
 
 // Make sure grow-memory isn't blocked by the lack of gc.
 (function() {
     assertEq(wasmEvalText(`(module
+    (gc_feature_opt_in 1)
     (memory 0 64)
     (func (export "f") (param anyref) (result i32)
         i32.const 10
         grow_memory
         drop
         current_memory
     )
 )`).exports.f({}), 10);
 })();
 
 // More interesting use cases about control flow joins.
 
 function assertJoin(body) {
     let val = { i: -1 };
     assertEq(wasmEvalText(`(module
+        (gc_feature_opt_in 1)
         (func (export "test") (param $ref anyref) (param $i i32) (result anyref)
             ${body}
         )
     )`).exports.test(val), val);
     assertEq(val.i, -1);
 }
 
 assertJoin("(block anyref get_local $ref)");
@@ -231,16 +238,17 @@ assertJoin(`(block $out anyref (block $u
     i32.add
     tee_local $i
     br_table $unreachable $out
     ) unreachable))
 `);
 
 let x = { i: 42 }, y = { f: 53 };
 exports = wasmEvalText(`(module
+    (gc_feature_opt_in 1)
     (func (export "test") (param $lhs anyref) (param $rhs anyref) (param $i i32) (result anyref)
         get_local $lhs
         get_local $rhs
         get_local $i
         select
     )
 )`).exports;
 
@@ -285,16 +293,17 @@ let imports = {
         },
         ret() {
             return imports.myBaguette;
         }
     }
 };
 
 exports = wasmEvalText(`(module
+    (gc_feature_opt_in 1)
     (import $ret "funcs" "ret" (result anyref))
     (import $param "funcs" "param" (param anyref))
 
     (func (export "param") (param $x anyref) (param $y anyref)
         get_local $y
         get_local $x
         call $param
         call $param
@@ -313,16 +322,17 @@ imports.myBaguette = null;
 assertEq(exports.ret(), null);
 
 imports.myBaguette = new Baguette(1337);
 assertEq(exports.ret(), imports.myBaguette);
 
 // Check lazy stubs generation.
 
 exports = wasmEvalText(`(module
+    (gc_feature_opt_in 1)
     (import $mirror "funcs" "mirror" (param anyref) (result anyref))
     (import $augment "funcs" "augment" (param anyref) (result anyref))
 
     (global $count_f (mut i32) (i32.const 0))
     (global $count_g (mut i32) (i32.const 0))
 
     (func $f (param $param anyref) (result anyref)
         i32.const 1
@@ -396,38 +406,39 @@ 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 } }),
+assertErrorMessage(() => wasmEvalText(`(module (gc_feature_opt_in 1) (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) } }),
+assertErrorMessage(() => wasmEvalText(`(module (gc_feature_opt_in 1) (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: {} } }),
+assertErrorMessage(() => wasmEvalText(`(module (gc_feature_opt_in 1) (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
+    (gc_feature_opt_in 1)
     (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))
--- a/js/src/jit-test/tests/wasm/gc/binary.js
+++ b/js/src/jit-test/tests/wasm/gc/binary.js
@@ -3,17 +3,20 @@ if (!wasmGcEnabled()) {
 }
 
 load(libdir + "wasm-binary.js");
 
 const v2vSig = {args:[], ret:VoidCode};
 const v2vSigSection = sigSection([v2vSig]);
 
 function checkInvalid(body, errorMessage) {
-    assertErrorMessage(() => new WebAssembly.Module(moduleWithSections([v2vSigSection, declSection([0]), bodySection([body])])), WebAssembly.CompileError, errorMessage);
+    assertErrorMessage(() => new WebAssembly.Module(
+        moduleWithSections([gcFeatureOptInSection(1), v2vSigSection, declSection([0]), bodySection([body])])),
+                       WebAssembly.CompileError,
+                       errorMessage);
 }
 
 const invalidRefNullBody = funcBody({locals:[], body:[
     RefNull,
     RefCode,
     0x42,
 
     RefNull,
--- a/js/src/jit-test/tests/wasm/gc/debugger.js
+++ b/js/src/jit-test/tests/wasm/gc/debugger.js
@@ -1,24 +1,25 @@
 if (!wasmGcEnabled() || !wasmDebuggingIsSupported()) {
     quit(0);
 }
 
 (function() {
     let g = newGlobal();
     let dbg = new Debugger(g);
-    g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func (result anyref) (param anyref) get_local 0) (export "" 0))')));`);
+    g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (gc_feature_opt_in 1) (func (result anyref) (param anyref) get_local 0) (export "" 0))')));`);
 })();
 
 (function() {
     var g = newGlobal();
     g.parent = this;
 
     let src = `
       (module
+        (gc_feature_opt_in 1)
         (func (export "func") (result anyref) (param $ref anyref)
             get_local $ref
         )
       )
     `;
 
     g.eval(`
         var obj = { somekey: 'somevalue' };
--- a/js/src/jit-test/tests/wasm/gc/disabled.js
+++ b/js/src/jit-test/tests/wasm/gc/disabled.js
@@ -1,28 +1,49 @@
 if (wasmGcEnabled()) {
     quit();
 }
 
 const { CompileError, validate } = WebAssembly;
 
-const UNRECOGNIZED_OPCODE_OR_BAD_TYPE = /(unrecognized opcode|bad type|invalid inline block type)/;
+const UNRECOGNIZED_OPCODE_OR_BAD_TYPE = /unrecognized opcode|(Structure|reference) types not enabled|invalid inline block type/;
 
 function assertValidateError(text) {
     assertEq(validate(wasmTextToBinary(text)), false);
 }
 
 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 (gc_feature_opt_in 1) (func (drop (ref.null anyref))))",
+    "(module (gc_feature_opt_in 1) (func $test (local anyref)))",
+    "(module (gc_feature_opt_in 1) (func $test (param anyref)))",
+    "(module (gc_feature_opt_in 1) (func $test (result anyref) (ref.null anyref)))",
+    "(module (gc_feature_opt_in 1) (func $test (block anyref (unreachable)) unreachable))",
+    "(module (gc_feature_opt_in 1) (func $test (local anyref) (result i32) (ref.is_null (get_local 0))))",
+    `(module (gc_feature_opt_in 1) (import "a" "b" (param anyref)))`,
+    `(module (gc_feature_opt_in 1) (import "a" "b" (result anyref)))`,
+    `(module (gc_feature_opt_in 1) (type $s (struct)))`,
 ];
 
+// Two distinct failure modes:
+//
+// - if we have no compiled-in support for wasm-gc we'll get a syntax error when
+//   parsing the test programs that use ref types and structures.
+//
+// - if we have compiled-in support for wasm-gc, but wasm-gc is not currently
+//   enabled, we will succeed parsing but fail compilation and validation.
+//
+// But it should always be all of one or all of the other.
+
+var fail_syntax = 0;
+var fail_compile = 0;
 for (let src of simpleTests) {
-    print(src)
+    try {
+        wasmTextToBinary(src);
+    } catch (e) {
+        assertEq(e instanceof SyntaxError, true);
+        fail_syntax++;
+        continue;
+    }
     assertErrorMessage(() => wasmEvalText(src), CompileError, UNRECOGNIZED_OPCODE_OR_BAD_TYPE);
     assertValidateError(src);
+    fail_compile++;
 }
+assertEq((fail_syntax == 0) != (fail_compile == 0), true);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/gc-feature-opt-in.js
@@ -0,0 +1,120 @@
+if (!wasmGcEnabled()) {
+    quit(0);
+}
+
+// Encoding.  If the section is present it must be first.
+
+var bad_order =
+    new Uint8Array([0x00, 0x61, 0x73, 0x6d,
+                    0x01, 0x00, 0x00, 0x00,
+
+                    0x01,                   // Type section
+                    0x01,                   // Section size
+                    0x00,                   // Zero types
+
+                    0x2a,                   // GcFeatureOptIn section
+                    0x01,                   // Section size
+                    0x01]);                 // Version
+
+assertErrorMessage(() => new WebAssembly.Module(bad_order),
+                   WebAssembly.CompileError,
+                   /expected custom section/);
+
+// Version numbers.  Version 1 is good, version 2 is bad.
+
+new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (gc_feature_opt_in 1))`));
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (gc_feature_opt_in 2))`)),
+                   WebAssembly.CompileError,
+                   /unsupported version of the gc feature/);
+
+// Struct types are only available if we opt in.
+
+new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (gc_feature_opt_in 1)
+      (type (struct (field i32))))`));
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (type (struct (field i32))))`)),
+                   WebAssembly.CompileError,
+                   /Structure types not enabled/);
+
+// Parameters of ref type are only available if we opt in.
+
+new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (gc_feature_opt_in 1)
+      (type (func (param anyref))))`));
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (type (func (param anyref))))`)),
+                   WebAssembly.CompileError,
+                   /reference types not enabled/);
+
+// Ditto returns
+
+new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (gc_feature_opt_in 1)
+      (type (func (result anyref))))`));
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (type (func (result anyref))))`)),
+                   WebAssembly.CompileError,
+                   /reference types not enabled/);
+
+// Ditto locals
+
+new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (gc_feature_opt_in 1)
+      (func (result i32)
+       (local anyref)
+       (i32.const 0)))`));
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (func (result i32)
+       (local anyref)
+       (i32.const 0)))`)),
+                   WebAssembly.CompileError,
+                   /reference types not enabled/);
+
+// Ditto globals
+
+new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (gc_feature_opt_in 1)
+      (global (mut anyref) (ref.null anyref)))`));
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (global (mut anyref) (ref.null anyref)))`)),
+                   WebAssembly.CompileError,
+                   /reference types not enabled/);
+
+// Ref instructions are only available if we opt in.
+//
+// When testing these we need to avoid struct types or parameters, locals,
+// returns, or globals of ref type, or guards on those will preempt the guards
+// on the instructions.
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (func (ref.null anyref)))`)),
+                   WebAssembly.CompileError,
+                   /unrecognized opcode/);
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (func ref.is_null))`)),
+                   WebAssembly.CompileError,
+                   /unrecognized opcode/);
--- a/js/src/jit-test/tests/wasm/gc/ref-global.js
+++ b/js/src/jit-test/tests/wasm/gc/ref-global.js
@@ -2,16 +2,17 @@ if (!wasmGcEnabled())
     quit(0);
 
 // Basic private-to-module functionality.  At the moment all we have is null
 // pointers, not very exciting.
 
 {
     let bin = wasmTextToBinary(
         `(module
+          (gc_feature_opt_in 1)
 
           (type $point (struct
                         (field $x f64)
                         (field $y f64)))
 
           (global $g1 (mut (ref $point)) (ref.null (ref $point)))
           (global $g2 (mut (ref $point)) (ref.null (ref $point)))
           (global $g3 (ref $point) (ref.null (ref $point)))
@@ -37,28 +38,30 @@ if (!wasmGcEnabled())
     ins.clear();                // Should not crash
 }
 
 // We can't import a global of a reference type because we don't have a good
 // notion of structural type compatibility yet.
 {
     let bin = wasmTextToBinary(
         `(module
+          (gc_feature_opt_in 1)
           (type $box (struct (field $val i32)))
           (import "m" "g" (global (mut (ref $box)))))`);
 
     assertErrorMessage(() => new WebAssembly.Module(bin), WebAssembly.CompileError,
                        /cannot expose reference type/);
 }
 
 // We can't export a global of a reference type because we can't later import
 // it.  (Once we can export it, the value setter must also perform the necessary
 // subtype check, which implies we have some notion of exporting types, and we
 // don't have that yet.)
 {
     let bin = wasmTextToBinary(
         `(module
+          (gc_feature_opt_in 1)
           (type $box (struct (field $val i32)))
           (global $boxg (export "box") (mut (ref $box)) (ref.null (ref $box))))`);
 
     assertErrorMessage(() => new WebAssembly.Module(bin), WebAssembly.CompileError,
                        /cannot expose reference type/);
 }
--- a/js/src/jit-test/tests/wasm/gc/ref-restrict.js
+++ b/js/src/jit-test/tests/wasm/gc/ref-restrict.js
@@ -47,269 +47,302 @@ if (!wasmGcEnabled())
 function wasmCompile(text) {
     return new WebAssembly.Module(wasmTextToBinary(text));
 }
 
 // Exported function can't take ref type parameter, but anyref is OK.
 
 assertErrorMessage(() => wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (type $box (struct (field $x i32)))
       (func (export "f") (param (ref $box)) (unreachable)))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertEq(typeof wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (func (export "f") (param anyref) (unreachable)))`),
          "object");
 
 // Exported function can't return ref result, but anyref is OK.
 
 assertErrorMessage(() => wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (type $box (struct (field $x i32)))
       (func (export "f") (result (ref $box)) (ref.null (ref $box))))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertEq(typeof wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (func (export "f") (result anyref) (ref.null anyref)))`),
          "object");
 
 // Imported function can't take ref parameter, but anyref is OK.
 
 assertErrorMessage(() => wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (type $box (struct (field $x i32)))
       (import "m" "f" (param (ref $box))))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertEq(typeof wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (import "m" "f" (param anyref)))`),
          "object");
 
 // Imported function can't return ref type, but anyref is OK.
 
 assertErrorMessage(() => wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (type $box (struct (field $x i32)))
       (import "m" "f" (param i32) (result (ref $box))))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertEq(typeof wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (import "m" "f" (param i32) (result anyref)))`),
          "object");
 
 // Imported global can't be of Ref type (irrespective of mutability), though anyref is OK.
 
 assertErrorMessage(() => wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (type $box (struct (field $val i32)))
       (import "m" "g" (global (mut (ref $box)))))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertErrorMessage(() => wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (type $box (struct (field $val i32)))
       (import "m" "g" (global (ref $box))))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertEq(typeof wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (import "m" "g" (global (mut anyref))))`),
          "object");
 
 assertEq(typeof wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (import "m" "g" (global anyref)))`),
          "object");
 
 // Exported global can't be of Ref type (irrespective of mutability), though anyref is OK.
 
 assertErrorMessage(() => wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (type $box (struct (field $val i32)))
       (global $boxg (export "box") (mut (ref $box)) (ref.null (ref $box))))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertErrorMessage(() => wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (type $box (struct (field $val i32)))
       (global $boxg (export "box") (ref $box) (ref.null (ref $box))))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertEq(typeof wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (global $boxg (export "box") (mut anyref) (ref.null anyref)))`),
          "object");
 
 assertEq(typeof wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (global $boxg (export "box") anyref (ref.null anyref)))`),
          "object");
 
 // Exported table cannot reference functions that are exposed for Ref, but anyref is OK.
 
 assertErrorMessage(() => wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (type $box (struct (field $val i32)))
       (table (export "tbl") 1 anyfunc)
       (elem (i32.const 0) $f1)
       (func $f1 (param (ref $box)) (unreachable)))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertErrorMessage(() => wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (type $box (struct (field $val i32)))
       (table (export "tbl") 1 anyfunc)
       (elem (i32.const 0) $f1)
       (func $f1 (result (ref $box)) (ref.null (ref $box))))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertEq(typeof wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (table (export "tbl") 1 anyfunc)
       (elem (i32.const 0) $f1)
       (func $f1 (param anyref) (unreachable)))`),
          "object");
 
 assertEq(typeof wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (table (export "tbl") 1 anyfunc)
       (elem (i32.const 0) $f1)
       (func $f1 (result anyref) (ref.null anyref)))`),
          "object");
 
 // Imported table cannot reference functions that are exposed for Ref, though anyref is OK.
 
 assertErrorMessage(() => wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (type $box (struct (field $val i32)))
       (import "m" "tbl" (table 1 anyfunc))
       (elem (i32.const 0) $f1)
       (func $f1 (param (ref $box)) (unreachable)))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertErrorMessage(() => wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (type $box (struct (field $val i32)))
       (import "m" "tbl" (table 1 anyfunc))
       (elem (i32.const 0) $f1)
       (func $f1 (result (ref $box)) (ref.null (ref $box))))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertEq(typeof wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (import "m" "tbl" (table 1 anyfunc))
       (elem (i32.const 0) $f1)
       (func $f1 (param anyref) (unreachable)))`),
          "object");
 
 assertEq(typeof wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (import "m" "tbl" (table 1 anyfunc))
       (elem (i32.const 0) $f1)
       (func $f1 (result anyref) (ref.null anyref)))`),
          "object");
 
 // Can't call via exported table with type that is exposed for Ref, though anyref is OK.
 
 assertErrorMessage(() => wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (type $box (struct (field $val i32)))
       (type $fn (func (param (ref $box))))
       (table (export "tbl") 1 anyfunc)
       (func (param i32)
        (call_indirect $fn (ref.null (ref $box)) (get_local 0))))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertErrorMessage(() => wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (type $box (struct (field $val i32)))
       (type $fn (func (result (ref $box))))
       (table (export "tbl") 1 anyfunc)
       (func (param i32) (result (ref $box))
        (call_indirect $fn (get_local 0))))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertEq(typeof wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (type $fn (func (param anyref)))
       (table (export "tbl") 1 anyfunc)
       (func (param i32)
        (call_indirect $fn (ref.null anyref) (get_local 0))))`),
          "object");
 
 assertEq(typeof wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (type $fn (func (result anyref)))
       (table (export "tbl") 1 anyfunc)
       (func (param i32) (result anyref)
        (call_indirect $fn (get_local 0))))`),
          "object");
 
 // Can't call via imported table with type that is exposed for Ref, though anyref is OK.
 
 assertErrorMessage(() => wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (type $box (struct (field $val i32)))
       (type $fn (func (param (ref $box))))
       (import "m" "tbl" (table 1 anyfunc))
       (func (param i32)
        (call_indirect $fn (ref.null (ref $box)) (get_local 0))))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertErrorMessage(() => wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (type $box (struct (field $val i32)))
       (type $fn (func (result (ref $box))))
       (import "m" "tbl" (table 1 anyfunc))
       (func (param i32) (result (ref $box))
        (call_indirect $fn (get_local 0))))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertEq(typeof wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (type $fn (func (param anyref)))
       (import "m" "tbl" (table 1 anyfunc))
       (func (param i32)
        (call_indirect $fn (ref.null anyref) (get_local 0))))`),
          "object");
 
 assertEq(typeof wasmCompile(
     `(module
+      (gc_feature_opt_in 1)
       (type $fn (func (result anyref)))
       (import "m" "tbl" (table 1 anyfunc))
       (func (param i32) (result anyref)
        (call_indirect $fn (get_local 0))))`),
          "object");
 
 // We can call via a private table with a type that is exposed for Ref.
 
 {
     let m = wasmCompile(
         `(module
+          (gc_feature_opt_in 1)
           (type $box (struct (field $val i32)))
           (type $fn (func (param (ref $box)) (result i32)))
           (table 1 anyfunc)
           (elem (i32.const 0) $f1)
           (func $f1 (param (ref $box)) (result i32) (i32.const 37))
           (func (export "f") (param i32) (result i32)
            (call_indirect $fn (ref.null (ref $box)) (get_local 0))))`);
     let i = new WebAssembly.Instance(m).exports;
--- a/js/src/jit-test/tests/wasm/gc/ref.js
+++ b/js/src/jit-test/tests/wasm/gc/ref.js
@@ -1,18 +1,19 @@
 if (!wasmGcEnabled()) {
     assertErrorMessage(() => wasmEvalText(`(module (func (param (ref 0)) (unreachable)))`),
-                       WebAssembly.CompileError, /bad type/);
+                       WebAssembly.CompileError, /reference types not enabled/);
     quit(0);
 }
 
 // Parsing and resolving.
 
 var bin = wasmTextToBinary(
     `(module
+      (gc_feature_opt_in 1)
       (type $cons (struct
                    (field $car i32)
                    (field $cdr (ref $cons))))
 
       (type $odd (struct
                   (field $x i32)
                   (field $to_even (ref $even))))
 
@@ -62,137 +63,152 @@ var bin = wasmTextToBinary(
 // Validation
 
 assertEq(WebAssembly.validate(bin), true);
 
 // ref.is_null should work on any reference type
 
 new WebAssembly.Module(wasmTextToBinary(`
 (module
+ (gc_feature_opt_in 1)
  (type $s (struct))
  (func $null (param (ref $s)) (result i32)
    (ref.is_null (get_local 0))))
 `))
 
 // Automatic upcast to anyref
 
 new WebAssembly.Module(wasmTextToBinary(`
 (module
+ (gc_feature_opt_in 1)
  (type $s (struct (field i32)))
  (func $f (param (ref $s)) (call $g (get_local 0)))
  (func $g (param anyref) (unreachable)))
 `));
 
 // Misc failure modes
 
 assertErrorMessage(() => wasmEvalText(`
 (module
-  (func (param (ref $odd)) (unreachable)))
+ (gc_feature_opt_in 1)
+ (func (param (ref $odd)) (unreachable)))
 `),
 SyntaxError, /Type label.*not found/);
 
 // Ref type mismatch in parameter is allowed through the prefix rule
 // but not if the structs are incompatible.
 
 wasmEvalText(`
 (module
+ (gc_feature_opt_in 1)
  (type $s (struct (field i32)))
  (type $t (struct (field i32)))
  (func $f (param (ref $s)) (unreachable))
  (func $g (param (ref $t)) (call $f (get_local 0)))
 )`);
 
 assertErrorMessage(() => wasmEvalText(`
 (module
+ (gc_feature_opt_in 1)
  (type $s (struct (field i32)))
  (type $t (struct (field f32))) ;; Incompatible type
  (func $f (param (ref $s)) (unreachable))
  (func $g (param (ref $t)) (call $f (get_local 0)))
 )`),
 WebAssembly.CompileError, /expression has type ref.*but expected ref/);
 
 assertErrorMessage(() => wasmEvalText(`
 (module
+ (gc_feature_opt_in 1)
  (type $s (struct (field i32)))
  (type $t (struct (field (mut i32)))) ;; Incompatible mutability
  (func $f (param (ref $s)) (unreachable))
  (func $g (param (ref $t)) (call $f (get_local 0)))
 )`),
 WebAssembly.CompileError, /expression has type ref.*but expected ref/);
 
 // Ref type mismatch in assignment to local but the prefix rule allows
 // the assignment to succeed if the structs are the same.
 
 wasmEvalText(`
 (module
+ (gc_feature_opt_in 1)
  (type $s (struct (field i32)))
  (type $t (struct (field i32)))
  (func $f (param (ref $s)) (local (ref $t)) (set_local 1 (get_local 0))))
 `)
 
 assertErrorMessage(() => wasmEvalText(`
 (module
+ (gc_feature_opt_in 1)
  (type $s (struct (field i32)))
  (type $t (struct (field f32)))
  (func $f (param (ref $s)) (local (ref $t)) (set_local 1 (get_local 0))))
 `),
 WebAssembly.CompileError, /expression has type ref.*but expected ref/);
 
 assertErrorMessage(() => wasmEvalText(`
 (module
+ (gc_feature_opt_in 1)
  (type $s (struct (field i32)))
  (type $t (struct (field (mut i32))))
  (func $f (param (ref $s)) (unreachable))
  (func $g (param (ref $t)) (call $f (get_local 0)))
 )`),
 WebAssembly.CompileError, /expression has type ref.*but expected ref/);
 
 // Ref type mismatch in return but the prefix rule allows the return
 // to succeed if the structs are the same.
 
 wasmEvalText(`
 (module
+ (gc_feature_opt_in 1)
  (type $s (struct (field i32)))
  (type $t (struct (field i32)))
  (func $f (param (ref $s)) (result (ref $t)) (get_local 0)))
 `);
 
 assertErrorMessage(() => wasmEvalText(`
 (module
+ (gc_feature_opt_in 1)
  (type $s (struct (field i32)))
  (type $t (struct (field f32)))
  (func $f (param (ref $s)) (result (ref $t)) (get_local 0)))
 `),
 WebAssembly.CompileError, /expression has type ref.*but expected ref/);
 
 assertErrorMessage(() => wasmEvalText(`
 (module
+ (gc_feature_opt_in 1)
  (type $s (struct (field i32)))
  (type $t (struct (field (mut i32))))
  (func $f (param (ref $s)) (result (ref $t)) (get_local 0)))
 `),
 WebAssembly.CompileError, /expression has type ref.*but expected ref/);
 
 // Ref type can't reference a function type
 
 assertErrorMessage(() => wasmEvalText(`
 (module
+ (gc_feature_opt_in 1)
  (type $x (func (param i32)))
  (func $f (param (ref $x)) (unreachable)))
 `),
 SyntaxError, /Type label.*not found/);
 
 assertErrorMessage(() => wasmEvalText(`
 (module
+ (gc_feature_opt_in 1)
  (type (func (param i32)))
  (func $f (param (ref 0)) (unreachable)))
 `),
 WebAssembly.CompileError, /does not reference a struct type/);
 
 // No automatic downcast from anyref
 
 assertErrorMessage(() => wasmEvalText(`
 (module
+ (gc_feature_opt_in 1)
  (type $s (struct (field i32)))
  (func $f (param anyref) (call $g (get_local 0)))
  (func $g (param (ref $s)) (unreachable)))
 `),
 WebAssembly.CompileError, /expression has type anyref but expected ref/);
--- a/js/src/jit-test/tests/wasm/gc/structs.js
+++ b/js/src/jit-test/tests/wasm/gc/structs.js
@@ -1,16 +1,14 @@
-if (!wasmGcEnabled()) {
-    assertErrorMessage(() => wasmEvalText(`(module (type $s (struct)))`),
-                       WebAssembly.CompileError, /Structure types not enabled/);
+if (!wasmGcEnabled())
     quit();
-}
 
 var bin = wasmTextToBinary(
     `(module
+      (gc_feature_opt_in 1)
 
       (table 2 anyfunc)
       (elem (i32.const 0) $doit $doitagain)
 
       ;; Type array has a mix of types
 
       (type $f1 (func (param i32) (result i32)))
 
@@ -68,82 +66,95 @@ assertEq(ins.hello(4.0, 1), 16.0)
 
 assertEq(ins.x1(12), 36)
 assertEq(ins.x2(8), Math.PI)
 
 // The field name is optional, so this should work.
 
 wasmEvalText(`
 (module
+ (gc_feature_opt_in 1)
  (type $s (struct (field i32))))
 `)
 
 // Empty structs are OK.
 
 wasmEvalText(`
 (module
+ (gc_feature_opt_in 1)
  (type $s (struct)))
 `)
 
 // Multiply defined structures.
 
 assertErrorMessage(() => wasmEvalText(`
 (module
+ (gc_feature_opt_in 1)
  (type $s (struct (field $x i32)))
  (type $s (struct (field $y i32))))
 `),
 SyntaxError, /duplicate type name/);
 
 // Bogus type definition syntax.
 
 assertErrorMessage(() => wasmEvalText(`
 (module
+ (gc_feature_opt_in 1)
  (type $s))
 `),
 SyntaxError, /parsing wasm text/);
 
 assertErrorMessage(() => wasmEvalText(`
 (module
+ (gc_feature_opt_in 1)
  (type $s (field $x i32)))
 `),
 SyntaxError, /bad type definition/);
 
 assertErrorMessage(() => wasmEvalText(`
 (module
+ (gc_feature_opt_in 1)
  (type $s (struct (field $x i31))))
 `),
 SyntaxError, /parsing wasm text/);
 
 assertErrorMessage(() => wasmEvalText(`
 (module
+ (gc_feature_opt_in 1)
  (type $s (struct (fjeld $x i32))))
 `),
 SyntaxError, /parsing wasm text/);
 
 assertErrorMessage(() => wasmEvalText(`
 (module
+ (gc_feature_opt_in 1)
  (type $s (struct abracadabra)))
 `),
 SyntaxError, /parsing wasm text/);
 
 // Function should not reference struct type: syntactic test
 
 assertErrorMessage(() => wasmEvalText(`
 (module
+ (gc_feature_opt_in 1)
  (type $s (struct))
  (type $f (func (param i32) (result i32)))
  (func (type 0) (param i32) (result i32) (unreachable)))
 `),
 WebAssembly.CompileError, /signature index references non-signature/);
 
 // Function should not reference struct type: binary test
 
 var bad = new Uint8Array([0x00, 0x61, 0x73, 0x6d,
                           0x01, 0x00, 0x00, 0x00,
 
+                          0x2a,                   // GcFeatureOptIn section
+                          0x01,                   // Section size
+                          0x01,                   // Version
+
                           0x01,                   // Type section
                           0x03,                   // Section size
                           0x01,                   // One type
                           0x50,                   // Struct
                           0x00,                   // Zero fields
 
                           0x03,                   // Function section
                           0x02,                   // Section size
--- a/js/src/wasm/WasmAST.h
+++ b/js/src/wasm/WasmAST.h
@@ -1228,16 +1228,19 @@ class AstModule : public AstNode
 
     LifoAlloc&           lifo_;
     TypeDefVector        types_;
     FuncTypeMap          funcTypeMap_;
     ImportVector         imports_;
     NameVector           funcImportNames_;
     AstResizableVector   tables_;
     AstResizableVector   memories_;
+#ifdef ENABLE_WASM_GC
+    uint32_t             gcFeatureOptIn_;
+#endif
     ExportVector         exports_;
     Maybe<AstStartFunc>  startFunc_;
     FuncVector           funcs_;
     AstDataSegmentVector dataSegments_;
     AstElemSegmentVector elemSegments_;
     AstGlobalVector      globals_;
 
     size_t numGlobalImports_;
@@ -1246,32 +1249,44 @@ class AstModule : public AstNode
     explicit AstModule(LifoAlloc& lifo)
       : lifo_(lifo),
         types_(lifo),
         funcTypeMap_(lifo),
         imports_(lifo),
         funcImportNames_(lifo),
         tables_(lifo),
         memories_(lifo),
+#ifdef ENABLE_WASM_GC
+        gcFeatureOptIn_(0),
+#endif
         exports_(lifo),
         funcs_(lifo),
         dataSegments_(lifo),
         elemSegments_(lifo),
         globals_(lifo),
         numGlobalImports_(0)
     {}
     bool addMemory(AstName name, const Limits& memory) {
         return memories_.append(AstResizable(memory, false, name));
     }
     bool hasMemory() const {
         return !!memories_.length();
     }
     const AstResizableVector& memories() const {
         return memories_;
     }
+#ifdef ENABLE_WASM_GC
+    bool addGcFeatureOptIn(uint32_t version) {
+        gcFeatureOptIn_ = version;
+        return true;
+    }
+    uint32_t gcFeatureOptIn() const {
+        return gcFeatureOptIn_;
+    }
+#endif
     bool addTable(AstName name, const Limits& table) {
         return tables_.append(AstResizable(table, false, name));
     }
     bool hasTable() const {
         return !!tables_.length();
     }
     const AstResizableVector& tables() const {
         return tables_;
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -9962,22 +9962,22 @@ BaseCompiler::emitBody()
           // Memory Related
           case uint16_t(Op::GrowMemory):
             CHECK_NEXT(emitGrowMemory());
           case uint16_t(Op::CurrentMemory):
             CHECK_NEXT(emitCurrentMemory());
 
 #ifdef ENABLE_WASM_GC
           case uint16_t(Op::RefNull):
-            if (env_.gcTypesEnabled == HasGcTypes::False)
+            if (env_.gcTypesEnabled() == HasGcTypes::False)
                 return iter_.unrecognizedOpcode(&op);
             CHECK_NEXT(emitRefNull());
             break;
           case uint16_t(Op::RefIsNull):
-            if (env_.gcTypesEnabled == HasGcTypes::False)
+            if (env_.gcTypesEnabled() == HasGcTypes::False)
                 return iter_.unrecognizedOpcode(&op);
             CHECK_NEXT(emitConversion(emitRefIsNull, ValType::AnyRef, ValType::I32));
             break;
 #endif
 
           // "Miscellaneous" operations
           case uint16_t(Op::MiscPrefix): {
             switch (op.b1) {
@@ -10370,17 +10370,17 @@ js::wasm::BaselineCompileFunctions(const
     for (const FuncCompileInput& func : inputs) {
         Decoder d(func.begin, func.end, func.lineOrBytecode, error);
 
         // Build the local types vector.
 
         ValTypeVector locals;
         if (!locals.appendAll(env.funcTypes[func.index]->args()))
             return false;
-        if (!DecodeLocalEntries(d, env.kind, env.types, env.gcTypesEnabled, &locals))
+        if (!DecodeLocalEntries(d, env.kind, env.types, env.gcTypesEnabled(), &locals))
             return false;
 
         // One-pass baseline compilation.
 
         BaseCompiler f(env, func, locals, d, &alloc, &masm);
         if (!f.init())
             return false;
         if (!f.emitFunction())
--- a/js/src/wasm/WasmBinaryConstants.h
+++ b/js/src/wasm/WasmBinaryConstants.h
@@ -33,17 +33,20 @@ enum class SectionId
     Function                             = 3,
     Table                                = 4,
     Memory                               = 5,
     Global                               = 6,
     Export                               = 7,
     Start                                = 8,
     Elem                                 = 9,
     Code                                 = 10,
-    Data                                 = 11
+    Data                                 = 11,
+#ifdef ENABLE_WASM_GC
+    GcFeatureOptIn                       = 42 // Arbitrary, but fits in 7 bits
+#endif
 };
 
 enum class TypeCode
 {
     I32                                  = 0x7f,  // SLEB128(-0x01)
     I64                                  = 0x7e,  // SLEB128(-0x02)
     F32                                  = 0x7d,  // SLEB128(-0x03)
     F64                                  = 0x7c,  // SLEB128(-0x04)
--- a/js/src/wasm/WasmCode.cpp
+++ b/js/src/wasm/WasmCode.cpp
@@ -690,17 +690,17 @@ struct ProjectLazyFuncIndex
     uint32_t operator[](size_t index) const {
         return funcExports[index].funcIndex;
     }
 };
 
 static constexpr unsigned LAZY_STUB_LIFO_DEFAULT_CHUNK_SIZE = 8 * 1024;
 
 bool
-LazyStubTier::createMany(HasGcTypes gcTypesEnabled, const Uint32Vector& funcExportIndices,
+LazyStubTier::createMany(HasGcTypes gcTypesConfigured, const Uint32Vector& funcExportIndices,
                          const CodeTier& codeTier, size_t* stubSegmentIndex)
 {
     MOZ_ASSERT(funcExportIndices.length());
 
     LifoAlloc lifo(LAZY_STUB_LIFO_DEFAULT_CHUNK_SIZE);
     TempAllocator alloc(&lifo);
     JitContext jitContext(&alloc);
     WasmMacroAssembler masm(alloc);
@@ -714,17 +714,17 @@ LazyStubTier::createMany(HasGcTypes gcTy
     for (uint32_t funcExportIndex : funcExportIndices) {
         const FuncExport& fe = funcExports[funcExportIndex];
         numExpectedRanges += fe.funcType().temporarilyUnsupportedAnyRef() ? 1 : 2;
         void* calleePtr = moduleSegmentBase +
                           moduleRanges[fe.funcCodeRangeIndex()].funcNormalEntry();
         Maybe<ImmPtr> callee;
         callee.emplace(calleePtr, ImmPtr::NoCheckToken());
         if (!GenerateEntryStubs(masm, funcExportIndex, fe, callee, /* asmjs */ false,
-                                gcTypesEnabled, &codeRanges))
+                                gcTypesConfigured, &codeRanges))
         {
             return false;
         }
     }
     MOZ_ASSERT(codeRanges.length() == numExpectedRanges, "incorrect number of entries per function");
 
     masm.finish();
 
@@ -796,17 +796,17 @@ LazyStubTier::createMany(HasGcTypes gcTy
 bool
 LazyStubTier::createOne(uint32_t funcExportIndex, const CodeTier& codeTier)
 {
     Uint32Vector funcExportIndexes;
     if (!funcExportIndexes.append(funcExportIndex))
         return false;
 
     size_t stubSegmentIndex;
-    if (!createMany(codeTier.code().metadata().temporaryHasGcTypes, funcExportIndexes, codeTier,
+    if (!createMany(codeTier.code().metadata().temporaryGcTypesConfigured, funcExportIndexes, codeTier,
                     &stubSegmentIndex))
     {
         return false;
     }
 
     const UniqueLazyStubSegment& segment = stubSegments_[stubSegmentIndex];
     const CodeRangeVector& codeRanges = segment->codeRanges();
 
@@ -823,24 +823,24 @@ LazyStubTier::createOne(uint32_t funcExp
     const CodeRange& cr = codeRanges[codeRanges.length() - 1];
     MOZ_ASSERT(cr.isJitEntry());
 
     codeTier.code().setJitEntry(cr.funcIndex(), segment->base() + cr.begin());
     return true;
 }
 
 bool
-LazyStubTier::createTier2(HasGcTypes gcTypesEnabled, const Uint32Vector& funcExportIndices,
+LazyStubTier::createTier2(HasGcTypes gcTypesConfigured, const Uint32Vector& funcExportIndices,
                           const CodeTier& codeTier, Maybe<size_t>* outStubSegmentIndex)
 {
     if (!funcExportIndices.length())
         return true;
 
     size_t stubSegmentIndex;
-    if (!createMany(gcTypesEnabled, funcExportIndices, codeTier, &stubSegmentIndex))
+    if (!createMany(gcTypesConfigured, funcExportIndices, codeTier, &stubSegmentIndex))
         return false;
 
     outStubSegmentIndex->emplace(stubSegmentIndex);
     return true;
 }
 
 void
 LazyStubTier::setJitEntries(const Maybe<size_t>& stubSegmentIndex, const Code& code)
--- a/js/src/wasm/WasmCode.h
+++ b/js/src/wasm/WasmCode.h
@@ -404,28 +404,28 @@ typedef Vector<ExprType, 0, SystemAllocP
 // the former points to instances of the latter.  Additionally, the asm.js
 // subsystem subclasses the Metadata, adding more tier-invariant data, some of
 // which is serialized.  See AsmJS.cpp.
 
 struct MetadataCacheablePod
 {
     ModuleKind            kind;
     MemoryUsage           memoryUsage;
-    HasGcTypes            temporaryHasGcTypes;
+    HasGcTypes            temporaryGcTypesConfigured;
     uint32_t              minMemoryLength;
     uint32_t              globalDataLength;
     Maybe<uint32_t>       maxMemoryLength;
     Maybe<uint32_t>       startFuncIndex;
     Maybe<NameInBytecode> moduleName;
     bool                  filenameIsURL;
 
     explicit MetadataCacheablePod(ModuleKind kind)
       : kind(kind),
         memoryUsage(MemoryUsage::None),
-        temporaryHasGcTypes(HasGcTypes::False),
+        temporaryGcTypesConfigured(HasGcTypes::False),
         minMemoryLength(0),
         globalDataLength(0),
         filenameIsURL(false)
     {}
 };
 
 typedef uint8_t ModuleHash[8];
 
@@ -612,17 +612,17 @@ class LazyStubTier
     // Creates one lazy stub for the exported function, for which the jit entry
     // will be set to the lazily-generated one.
     bool createOne(uint32_t funcExportIndex, const CodeTier& codeTier);
 
     // Create one lazy stub for all the functions in funcExportIndices, putting
     // them in a single stub. Jit entries won't be used until
     // setJitEntries() is actually called, after the Code owner has committed
     // tier2.
-    bool createTier2(HasGcTypes gcTypesEnabled, const Uint32Vector& funcExportIndices,
+    bool createTier2(HasGcTypes gcTypesConfigured, const Uint32Vector& funcExportIndices,
                      const CodeTier& codeTier, Maybe<size_t>* stubSegmentIndex);
     void setJitEntries(const Maybe<size_t>& stubSegmentIndex, const Code& code);
 
     void addSizeOfMisc(MallocSizeOf mallocSizeOf, size_t* code, size_t* data) const;
 };
 
 // CodeTier contains all the data related to a given compilation tier. It is
 // built during module generation and then immutably stored in a Code.
--- a/js/src/wasm/WasmCompile.cpp
+++ b/js/src/wasm/WasmCompile.cpp
@@ -79,17 +79,17 @@ CompileArgs::CompileArgs(JSContext* cx, 
     bool gcEnabled = cx->options().wasmGc();
 #else
     bool gcEnabled = false;
 #endif
 
     baselineEnabled = cx->options().wasmBaseline() || gcEnabled;
     ionEnabled = cx->options().wasmIon() && !gcEnabled;
     sharedMemoryEnabled = cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled();
-    gcTypesEnabled = gcEnabled ? HasGcTypes::True : HasGcTypes::False;
+    gcTypesConfigured = gcEnabled ? HasGcTypes::True : HasGcTypes::False;
     testTiering = (cx->options().testWasmAwaitTier2() || JitOptions.wasmDelayTier2) && !gcEnabled;
 
     // Debug information such as source view or debug traps will require
     // additional memory and permanently stay in baseline code, so we try to
     // only enable it when a developer actually cares: when the debugger tab
     // is open.
     debugEnabled = cx->realm()->debuggerObservesAsmJS();
 }
@@ -462,17 +462,17 @@ wasm::CompileBuffer(const CompileArgs& a
 
     Decoder d(bytecode.bytes, 0, error, warnings);
 
     CompileMode mode;
     Tier tier;
     DebugEnabled debug;
     InitialCompileFlags(args, d, &mode, &tier, &debug);
 
-    ModuleEnvironment env(mode, tier, debug, args.gcTypesEnabled,
+    ModuleEnvironment env(mode, tier, debug, args.gcTypesConfigured,
                           args.sharedMemoryEnabled ? Shareable::True : Shareable::False);
     if (!DecodeModuleEnvironment(d, &env))
         return nullptr;
 
     ModuleGenerator mg(args, &env, nullptr, error);
     if (!mg.init())
         return nullptr;
 
@@ -488,17 +488,17 @@ wasm::CompileBuffer(const CompileArgs& a
 void
 wasm::CompileTier2(const CompileArgs& args, Module& module, Atomic<bool>* cancelled)
 {
     MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers());
 
     UniqueChars error;
     Decoder d(module.bytecode().bytes, 0, &error);
 
-    MOZ_ASSERT(args.gcTypesEnabled == HasGcTypes::False, "can't ion-compile with gc types yet");
+    MOZ_ASSERT(args.gcTypesConfigured == HasGcTypes::False, "can't ion-compile with gc types yet");
 
     ModuleEnvironment env(CompileMode::Tier2, Tier::Ion, DebugEnabled::False, HasGcTypes::False,
                           args.sharedMemoryEnabled ? Shareable::True : Shareable::False);
     if (!DecodeModuleEnvironment(d, &env))
         return;
 
     ModuleGenerator mg(args, &env, cancelled, &error);
     if (!mg.init())
@@ -617,17 +617,17 @@ wasm::CompileStreaming(const CompileArgs
     {
         Decoder d(envBytes, 0, error, warnings);
 
         CompileMode mode;
         Tier tier;
         DebugEnabled debug;
         InitialCompileFlags(args, d, &mode, &tier, &debug);
 
-        env.emplace(mode, tier, debug, args.gcTypesEnabled,
+        env.emplace(mode, tier, debug, args.gcTypesConfigured,
                     args.sharedMemoryEnabled ? Shareable::True : Shareable::False);
         if (!DecodeModuleEnvironment(d, env.ptr()))
             return nullptr;
 
         MOZ_ASSERT(d.done());
     }
 
     ModuleGenerator mg(args, env.ptr(), &cancelled, error);
--- a/js/src/wasm/WasmCompile.h
+++ b/js/src/wasm/WasmCompile.h
@@ -47,26 +47,26 @@ struct ScriptedCaller
 struct CompileArgs : ShareableBase<CompileArgs>
 {
     ScriptedCaller scriptedCaller;
     UniqueChars sourceMapURL;
     bool baselineEnabled;
     bool debugEnabled;
     bool ionEnabled;
     bool sharedMemoryEnabled;
-    HasGcTypes gcTypesEnabled;
+    HasGcTypes gcTypesConfigured;
     bool testTiering;
 
     explicit CompileArgs(ScriptedCaller&& scriptedCaller)
       : scriptedCaller(std::move(scriptedCaller)),
         baselineEnabled(false),
         debugEnabled(false),
         ionEnabled(false),
         sharedMemoryEnabled(false),
-        gcTypesEnabled(HasGcTypes::False),
+        gcTypesConfigured(HasGcTypes::False),
         testTiering(false)
     {}
 
     CompileArgs(JSContext* cx, ScriptedCaller&& scriptedCaller);
 };
 
 typedef RefPtr<CompileArgs> MutableCompileArgs;
 typedef RefPtr<const CompileArgs> SharedCompileArgs;
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -824,17 +824,17 @@ ModuleGenerator::finishMetadata(const Sh
         MOZ_ASSERT(debugTrapFarJumpOffset >= last);
         last = debugTrapFarJumpOffset;
     }
 #endif
 
     // Copy over data from the ModuleEnvironment.
 
     metadata_->memoryUsage = env_->memoryUsage;
-    metadata_->temporaryHasGcTypes = env_->gcTypesEnabled;
+    metadata_->temporaryGcTypesConfigured = env_->gcTypesConfigured;
     metadata_->minMemoryLength = env_->minMemoryLength;
     metadata_->maxMemoryLength = env_->maxMemoryLength;
     metadata_->startFuncIndex = env_->startFuncIndex;
     metadata_->moduleName = env_->moduleName;
     metadata_->tables = std::move(env_->tables);
     metadata_->globals = std::move(env_->globals);
     metadata_->funcNames = std::move(env_->funcNames);
     metadata_->customSections = std::move(env_->customSections);
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -3498,17 +3498,17 @@ wasm::IonCompileFunctions(const ModuleEn
     for (const FuncCompileInput& func : inputs) {
         Decoder d(func.begin, func.end, func.lineOrBytecode, error);
 
         // Build the local types vector.
 
         ValTypeVector locals;
         if (!locals.appendAll(env.funcTypes[func.index]->args()))
             return false;
-        if (!DecodeLocalEntries(d, env.kind, env.types, env.gcTypesEnabled, &locals))
+        if (!DecodeLocalEntries(d, env.kind, env.types, env.gcTypesEnabled(), &locals))
             return false;
 
         // Set up for Ion compilation.
 
         const JitCompileOptions options;
         MIRGraph graph(&alloc);
         CompileInfo compileInfo(locals.length());
         MIRGenerator mir(nullptr, options, &alloc, &graph, &compileInfo,
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -115,21 +115,21 @@ Module::finishTier2(const LinkData& link
                 continue;
             MOZ_ASSERT(!env2.isAsmJS(), "only wasm functions are lazily exported");
             if (!stubs1->hasStub(fe.funcIndex()))
                 continue;
             if (!funcExportIndices.emplaceBack(i))
                 return false;
         }
 
-        HasGcTypes gcTypesEnabled = code().metadata().temporaryHasGcTypes;
+        HasGcTypes gcTypesConfigured = code().metadata().temporaryGcTypesConfigured;
         const CodeTier& tier2 = code().codeTier(Tier::Ion);
 
         Maybe<size_t> stub2Index;
-        if (!stubs2->createTier2(gcTypesEnabled, funcExportIndices, tier2, &stub2Index))
+        if (!stubs2->createTier2(gcTypesConfigured, funcExportIndices, tier2, &stub2Index))
             return false;
 
         // Now that we can't fail or otherwise abort tier2, make it live.
 
         MOZ_ASSERT(!code().hasTier2());
         code().commitTier2();
 
         stubs2->setJitEntries(stub2Index, code());
--- a/js/src/wasm/WasmOpIter.h
+++ b/js/src/wasm/WasmOpIter.h
@@ -627,17 +627,17 @@ OpIter<Policy>::Unify(StackType observed
         return true;
     }
 
     if (expected == StackType::Any) {
         *result = observed;
         return true;
     }
 
-    if (env_.gcTypesEnabled == HasGcTypes::True && observed.isRefOrAnyRef() &&
+    if (env_.gcTypesEnabled() == HasGcTypes::True && observed.isRefOrAnyRef() &&
         expected.isRefOrAnyRef() && IsSubtypeOf(observed, expected))
     {
         *result = expected;
         return true;
     }
 
     return false;
 }
@@ -656,17 +656,17 @@ OpIter<Policy>::Join(StackType one, Stac
         return true;
     }
 
     if (two == StackType::Any) {
         *result = one;
         return true;
     }
 
-    if (env_.gcTypesEnabled == HasGcTypes::True && one.isRefOrAnyRef() && two.isRefOrAnyRef()) {
+    if (env_.gcTypesEnabled() == HasGcTypes::True && one.isRefOrAnyRef() && two.isRefOrAnyRef()) {
         if (IsSubtypeOf(two, one)) {
             *result = one;
             return true;
         }
 
         if (IsSubtypeOf(one, two)) {
             *result = two;
             return true;
@@ -896,22 +896,22 @@ OpIter<Policy>::readBlockType(ExprType* 
       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::Ref):
-        known = env_.gcTypesEnabled == HasGcTypes::True &&
+        known = env_.gcTypesEnabled() == HasGcTypes::True &&
                 uncheckedRefTypeIndex < MaxTypes &&
                 uncheckedRefTypeIndex < env_.types.length();
         break;
       case uint8_t(ExprType::AnyRef):
-        known = env_.gcTypesEnabled == HasGcTypes::True;
+        known = env_.gcTypesEnabled() == HasGcTypes::True;
         break;
       case uint8_t(ExprType::Limit):
         break;
     }
 
     if (!known)
         return fail("invalid inline block type");
 
--- a/js/src/wasm/WasmStubs.cpp
+++ b/js/src/wasm/WasmStubs.cpp
@@ -292,17 +292,17 @@ CallFuncExport(MacroAssembler& masm, con
 }
 
 // Generate a stub that enters wasm from a C++ caller via the native ABI. The
 // signature of the entry point is Module::ExportFuncPtr. The exported wasm
 // function has an ABI derived from its specific signature, so this function
 // must map from the ABI of ExportFuncPtr to the export's signature's ABI.
 static bool
 GenerateInterpEntry(MacroAssembler& masm, const FuncExport& fe, const Maybe<ImmPtr>& funcPtr,
-                    HasGcTypes gcTypesEnabled, Offsets* offsets)
+                    HasGcTypes gcTypesConfigured, Offsets* offsets)
 {
     AssertExpectedSP(masm);
     masm.haltingAlign(CodeAlignment);
 
     offsets->begin = masm.currentOffset();
 
     // Save the return address if it wasn't already saved by the call insn.
 #ifdef JS_USE_LINK_REGISTER
@@ -347,17 +347,17 @@ GenerateInterpEntry(MacroAssembler& masm
     arg = abi.next(MIRType::Pointer);
     if (arg.kind() == ABIArg::GPR)
         masm.movePtr(arg.gpr(), WasmTlsReg);
     else
         masm.loadPtr(Address(masm.getStackPointer(), argBase + arg.offsetFromArgBase()), WasmTlsReg);
 
 #ifdef ENABLE_WASM_GC
     WasmPush(masm, WasmTlsReg);
-    if (gcTypesEnabled == HasGcTypes::True)
+    if (gcTypesConfigured == HasGcTypes::True)
         SuppressGC(masm, 1, scratch);
 #endif
 
     // Save 'argv' on the stack so that we can recover it after the call.
     WasmPush(masm, argv);
 
     // Since we're about to dynamically align the stack, reset the frame depth
     // so we can still assert static stack depth balancing.
@@ -406,17 +406,17 @@ GenerateInterpEntry(MacroAssembler& masm
     MOZ_ASSERT(masm.framePushed() == 0);
     masm.setFramePushed(FramePushedBeforeAlign);
 
     // Recover the 'argv' pointer which was saved before aligning the stack.
     WasmPop(masm, argv);
 
 #ifdef ENABLE_WASM_GC
     WasmPop(masm, WasmTlsReg);
-    if (gcTypesEnabled == HasGcTypes::True)
+    if (gcTypesConfigured == HasGcTypes::True)
         SuppressGC(masm, -1, WasmTlsReg);
 #endif
 
     // Store the return value in argv[0].
     StoreABIReturn(masm, fe, argv);
 
     // After the ReturnReg is stored into argv[0] but before fp is clobbered by
     // the PopRegsInMask(NonVolatileRegs) below, set the return value based on
@@ -514,17 +514,17 @@ GenerateJitEntryThrow(MacroAssembler& ma
 // Generate a stub that enters wasm from a jit code caller via the jit ABI.
 //
 // ARM64 note: This does not save the PseudoStackPointer so we must be sure to
 // recompute it on every return path, be it normal return or exception return.
 // The JIT code we return to assumes it is correct.
 
 static bool
 GenerateJitEntry(MacroAssembler& masm, size_t funcExportIndex, const FuncExport& fe,
-                 const Maybe<ImmPtr>& funcPtr, HasGcTypes gcTypesEnabled, Offsets* offsets)
+                 const Maybe<ImmPtr>& funcPtr, HasGcTypes gcTypesConfigured, Offsets* offsets)
 {
     AssertExpectedSP(masm);
 
     RegisterOrSP sp = masm.getStackPointer();
 
     GenerateJitEntryPrologue(masm, offsets);
 
     // The jit caller has set up the following stack layout (sp grows to the
@@ -727,30 +727,30 @@ GenerateJitEntry(MacroAssembler& masm, s
           }
         }
     }
 
     // Setup wasm register state.
     masm.loadWasmPinnedRegsFromTls();
 
 #ifdef ENABLE_WASM_GC
-    if (gcTypesEnabled == HasGcTypes::True) {
+    if (gcTypesConfigured == HasGcTypes::True) {
         masm.storePtr(WasmTlsReg, Address(sp, savedTlsOffset));
         SuppressGC(masm, 1, ScratchIonEntry);
     }
 #endif
 
     // Call into the real function. Note that, due to the throw stub, fp, tls
     // and pinned registers may be clobbered.
     masm.assertStackAlignment(WasmStackAlignment);
     CallFuncExport(masm, fe, funcPtr);
     masm.assertStackAlignment(WasmStackAlignment);
 
 #ifdef ENABLE_WASM_GC
-    if (gcTypesEnabled == HasGcTypes::True) {
+    if (gcTypesConfigured == HasGcTypes::True) {
         masm.loadPtr(Address(sp, savedTlsOffset), WasmTlsReg);
         SuppressGC(masm, -1, WasmTlsReg);
     }
 #endif
 
     // If fp is equal to the FailFP magic value (set by the throw stub), then
     // report the exception to the JIT caller by jumping into the exception
     // stub; otherwise the FP value is still set to the parent ion frame value.
@@ -1843,32 +1843,32 @@ GenerateDebugTrapStub(MacroAssembler& ma
 
     GenerateExitEpilogue(masm, 0, ExitReason::Fixed::DebugTrap, offsets);
 
     return FinishOffsets(masm, offsets);
 }
 
 bool
 wasm::GenerateEntryStubs(MacroAssembler& masm, size_t funcExportIndex, const FuncExport& fe,
-                         const Maybe<ImmPtr>& callee, bool isAsmJS, HasGcTypes gcTypesEnabled,
+                         const Maybe<ImmPtr>& callee, bool isAsmJS, HasGcTypes gcTypesConfigured,
                          CodeRangeVector* codeRanges)
 {
     MOZ_ASSERT(!callee == fe.hasEagerStubs());
     MOZ_ASSERT_IF(isAsmJS, fe.hasEagerStubs());
 
     Offsets offsets;
-    if (!GenerateInterpEntry(masm, fe, callee, gcTypesEnabled, &offsets))
+    if (!GenerateInterpEntry(masm, fe, callee, gcTypesConfigured, &offsets))
         return false;
     if (!codeRanges->emplaceBack(CodeRange::InterpEntry, fe.funcIndex(), offsets))
         return false;
 
     if (isAsmJS || fe.funcType().temporarilyUnsupportedAnyRef())
         return true;
 
-    if (!GenerateJitEntry(masm, funcExportIndex, fe, callee, gcTypesEnabled, &offsets))
+    if (!GenerateJitEntry(masm, funcExportIndex, fe, callee, gcTypesConfigured, &offsets))
         return false;
     if (!codeRanges->emplaceBack(CodeRange::JitEntry, fe.funcIndex(), offsets))
         return false;
 
     return true;
 }
 
 bool
@@ -1909,17 +1909,17 @@ wasm::GenerateStubs(const ModuleEnvironm
     JitSpew(JitSpew_Codegen, "# Emitting wasm export stubs");
 
     Maybe<ImmPtr> noAbsolute;
     for (size_t i = 0; i < exports.length(); i++) {
         const FuncExport& fe = exports[i];
         if (!fe.hasEagerStubs())
             continue;
         if (!GenerateEntryStubs(masm, i, fe, noAbsolute, env.isAsmJS(),
-                                env.gcTypesEnabled, &code->codeRanges))
+                                env.gcTypesConfigured, &code->codeRanges))
         {
             return false;
         }
     }
 
     JitSpew(JitSpew_Codegen, "# Emitting wasm exit stubs");
 
     Offsets offsets;
--- a/js/src/wasm/WasmStubs.h
+++ b/js/src/wasm/WasmStubs.h
@@ -34,17 +34,17 @@ GenerateImportFunctions(const ModuleEnvi
 
 extern bool
 GenerateStubs(const ModuleEnvironment& env, const FuncImportVector& imports,
               const FuncExportVector& exports, CompiledCode* code);
 
 extern bool
 GenerateEntryStubs(jit::MacroAssembler& masm, size_t funcExportIndex,
                    const FuncExport& funcExport, const Maybe<jit::ImmPtr>& callee,
-                   bool isAsmJS, HasGcTypes gcTypesEnabled, CodeRangeVector* codeRanges);
+                   bool isAsmJS, HasGcTypes gcTypesConfigured, CodeRangeVector* codeRanges);
 
 // An argument that will end up on the stack according to the system ABI, to be
 // passed to GenerateDirectCallFromJit. Since the direct JIT call creates its
 // own frame, it is its responsibility to put stack arguments to their expected
 // locations; so the caller of GenerateDirectCallFromJit can put them anywhere.
 
 class JitCallStackArg
 {
--- a/js/src/wasm/WasmTextToBinary.cpp
+++ b/js/src/wasm/WasmTextToBinary.cpp
@@ -92,16 +92,19 @@ class WasmToken
         Error,
         Export,
 #ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
         ExtraConversionOpcode,
 #endif
         Field,
         Float,
         Func,
+#ifdef ENABLE_WASM_GC
+        GcFeatureOptIn,
+#endif
         GetGlobal,
         GetLocal,
         Global,
         GrowMemory,
         If,
         Import,
         Index,
         Memory,
@@ -367,16 +370,19 @@ class WasmToken
           case EndOfFile:
           case Equal:
           case End:
           case Error:
           case Export:
           case Field:
           case Float:
           case Func:
+#ifdef ENABLE_WASM_GC
+          case GcFeatureOptIn:
+#endif
           case Global:
           case Mutable:
           case Import:
           case Index:
           case Memory:
           case NegativeZero:
           case Local:
           case Module:
@@ -1165,16 +1171,20 @@ WasmTokenStream::next()
                     return WasmToken(WasmToken::UnaryOpcode, Op::F64Trunc, begin, cur_);
                 break;
             }
             break;
         }
         break;
 
       case 'g':
+#ifdef ENABLE_WASM_GC
+        if (consume(u"gc_feature_opt_in"))
+            return WasmToken(WasmToken::GcFeatureOptIn, begin, cur_);
+#endif
         if (consume(u"get_global"))
             return WasmToken(WasmToken::GetGlobal, begin, cur_);
         if (consume(u"get_local"))
             return WasmToken(WasmToken::GetLocal, begin, cur_);
         if (consume(u"global"))
             return WasmToken(WasmToken::Global, begin, cur_);
         if (consume(u"grow_memory"))
             return WasmToken(WasmToken::GrowMemory, begin, cur_);
@@ -3618,16 +3628,38 @@ ParseMemory(WasmParseContext& c, AstModu
 
     Limits memory;
     if (!ParseLimits(c, &memory, Shareable::True))
         return false;
 
     return module->addMemory(name, memory);
 }
 
+#ifdef ENABLE_WASM_GC
+// Custom section for experimental work.  The size of this section should always
+// be 1 byte, and that byte is a nonzero varint7 carrying the version number
+// being opted into.
+static bool
+ParseGcFeatureOptIn(WasmParseContext& c, AstModule* module)
+{
+    WasmToken token;
+    if (!c.ts.getIf(WasmToken::Index, &token)) {
+        c.ts.generateError(token, "GC feature version number required", c.error);
+        return false;
+    }
+
+    if (token.index() == 0 || token.index() > 127) {
+        c.ts.generateError(token, "invalid GC feature version number", c.error);
+        return false;
+    }
+
+    return module->addGcFeatureOptIn(token.index());
+}
+#endif
+
 static bool
 ParseStartFunc(WasmParseContext& c, WasmToken token, AstModule* module)
 {
     AstRef func;
     if (!c.ts.matchRef(&func, c.error))
         return false;
 
     if (!module->setStartFunc(AstStartFunc(func))) {
@@ -4055,16 +4087,23 @@ ParseModule(const char16_t* text, uintpt
                 return nullptr;
             break;
           }
           case WasmToken::Memory: {
             if (!ParseMemory(c, module))
                 return nullptr;
             break;
           }
+#ifdef ENABLE_WASM_GC
+          case WasmToken::GcFeatureOptIn: {
+            if (!ParseGcFeatureOptIn(c, module))
+                return nullptr;
+            break;
+          }
+#endif
           case WasmToken::Global: {
             if (!ParseGlobal(c, module))
                 return nullptr;
             break;
           }
           case WasmToken::Data: {
             AstDataSegment* segment = ParseDataSegment(c);
             if (!segment || !module->append(segment))
@@ -5363,16 +5402,36 @@ EncodeExpr(Encoder& e, AstExpr& expr)
 #endif
     }
     MOZ_CRASH("Bad expr kind");
 }
 
 /*****************************************************************************/
 // wasm AST binary serialization
 
+#ifdef ENABLE_WASM_GC
+static bool
+EncodeGcFeatureOptInSection(Encoder& e, AstModule& module)
+{
+    uint32_t optInVersion = module.gcFeatureOptIn();
+    if (!optInVersion)
+        return true;
+
+    size_t offset;
+    if (!e.startSection(SectionId::GcFeatureOptIn, &offset))
+        return false;
+
+    if (!e.writeVarU32(optInVersion))
+        return false;
+
+    e.finishSection(offset);
+    return true;
+}
+#endif
+
 static bool
 EncodeTypeSection(Encoder& e, AstModule& module)
 {
     if (module.types().empty())
         return true;
 
     size_t offset;
     if (!e.startSection(SectionId::Type, &offset))
@@ -5853,16 +5912,21 @@ EncodeModule(AstModule& module, Uint32Ve
     Encoder e(*bytes);
 
     if (!e.writeFixedU32(MagicNumber))
         return false;
 
     if (!e.writeFixedU32(EncodingVersion))
         return false;
 
+#ifdef ENABLE_WASM_GC
+    if (!EncodeGcFeatureOptInSection(e, module))
+        return false;
+#endif
+
     if (!EncodeTypeSection(e, module))
         return false;
 
     if (!EncodeImportSection(e, module))
         return false;
 
     if (!EncodeFunctionSection(e, module))
         return false;
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -392,22 +392,22 @@ DecodeValType(Decoder& d, ModuleKind kin
       case uint8_t(ValType::I32):
       case uint8_t(ValType::F32):
       case uint8_t(ValType::F64):
       case uint8_t(ValType::I64):
         *type = ValType(ValType::Code(uncheckedCode));
         return true;
       case uint8_t(ValType::AnyRef):
         if (gcTypesEnabled == HasGcTypes::False)
-            break;
+            return d.fail("reference types not enabled");
         *type = ValType(ValType::Code(uncheckedCode));
         return true;
       case uint8_t(ValType::Ref): {
         if (gcTypesEnabled == HasGcTypes::False)
-            break;
+            return d.fail("reference types not enabled");
         if (uncheckedRefTypeIndex >= numTypes)
             return d.fail("ref index out of range");
         // We further validate ref types in the caller.
         *type = ValType(ValType::Code(uncheckedCode), uncheckedRefTypeIndex);
         return true;
       }
       default:
         break;
@@ -858,24 +858,24 @@ DecodeFunctionBodyExprs(const ModuleEnvi
 #endif
               default:
                 return iter.unrecognizedOpcode(&op);
             }
             break;
           }
 #ifdef ENABLE_WASM_GC
           case uint16_t(Op::RefNull): {
-            if (env.gcTypesEnabled == HasGcTypes::False)
+            if (env.gcTypesEnabled() == HasGcTypes::False)
                 return iter.unrecognizedOpcode(&op);
             ValType unusedType;
             CHECK(iter.readRefNull(&unusedType));
             break;
           }
           case uint16_t(Op::RefIsNull): {
-            if (env.gcTypesEnabled == HasGcTypes::False)
+            if (env.gcTypesEnabled() == HasGcTypes::False)
                 return iter.unrecognizedOpcode(&op);
             CHECK(iter.readConversion(ValType::AnyRef, ValType::I32, &nothing));
             break;
           }
 #endif
           case uint16_t(Op::ThreadPrefix): {
 #ifdef ENABLE_WASM_THREAD_OPS
             switch (op.b1) {
@@ -1065,17 +1065,17 @@ wasm::ValidateFunctionBody(const ModuleE
     const FuncType& funcType = *env.funcTypes[funcIndex];
 
     ValTypeVector locals;
     if (!locals.appendAll(funcType.args()))
         return false;
 
     const uint8_t* bodyBegin = d.currentPosition();
 
-    if (!DecodeLocalEntries(d, ModuleKind::Wasm, env.types, env.gcTypesEnabled, &locals))
+    if (!DecodeLocalEntries(d, ModuleKind::Wasm, env.types, env.gcTypesEnabled(), &locals))
         return false;
 
     if (!DecodeFunctionBodyExprs(env, funcType, locals, bodyBegin + bodySize, &d))
         return false;
 
     return true;
 }
 
@@ -1149,34 +1149,34 @@ DecodeFuncType(Decoder& d, ModuleEnviron
     if (numArgs > MaxParams)
         return d.fail("too many arguments in signature");
 
     ValTypeVector args;
     if (!args.resize(numArgs))
         return false;
 
     for (uint32_t i = 0; i < numArgs; i++) {
-        if (!DecodeValType(d, ModuleKind::Wasm, env->types.length(), env->gcTypesEnabled, &args[i]))
+        if (!DecodeValType(d, ModuleKind::Wasm, env->types.length(), env->gcTypesEnabled(), &args[i]))
             return false;
         if (!ValidateRefType(d, typeState, args[i]))
             return false;
     }
 
     uint32_t numRets;
     if (!d.readVarU32(&numRets))
         return d.fail("bad number of function returns");
 
     if (numRets > 1)
         return d.fail("too many returns in signature");
 
     ExprType result = ExprType::Void;
 
     if (numRets == 1) {
         ValType type;
-        if (!DecodeValType(d, ModuleKind::Wasm, env->types.length(), env->gcTypesEnabled, &type))
+        if (!DecodeValType(d, ModuleKind::Wasm, env->types.length(), env->gcTypesEnabled(), &type))
             return false;
         if (!ValidateRefType(d, typeState, type))
             return false;
 
         result = ExprType(type);
     }
 
     if ((*typeState)[typeIndex] != TypeState::None)
@@ -1186,17 +1186,17 @@ DecodeFuncType(Decoder& d, ModuleEnviron
     (*typeState)[typeIndex] = TypeState::Func;
 
     return true;
 }
 
 static bool
 DecodeStructType(Decoder& d, ModuleEnvironment* env, TypeStateVector* typeState, uint32_t typeIndex)
 {
-    if (env->gcTypesEnabled == HasGcTypes::False)
+    if (env->gcTypesEnabled() == HasGcTypes::False)
         return d.fail("Structure types not enabled");
 
     uint32_t numFields;
     if (!d.readVarU32(&numFields))
         return d.fail("Bad number of fields");
 
     if (numFields > MaxStructFields)
         return d.fail("too many fields in structure");
@@ -1209,31 +1209,59 @@ DecodeStructType(Decoder& d, ModuleEnvir
 
     for (uint32_t i = 0; i < numFields; i++) {
         uint8_t flags;
         if (!d.readFixedU8(&flags))
             return d.fail("expected flag");
         if ((flags & ~uint8_t(FieldFlags::AllowedMask)) != 0)
             return d.fail("garbage flag bits");
         fields[i].isMutable = flags & uint8_t(FieldFlags::Mutable);
-        if (!DecodeValType(d, ModuleKind::Wasm, env->types.length(), env->gcTypesEnabled, &fields[i].type))
+        if (!DecodeValType(d, ModuleKind::Wasm, env->types.length(), env->gcTypesEnabled(), &fields[i].type))
             return false;
         if (!ValidateRefType(d, typeState, fields[i].type))
             return false;
     }
 
     if ((*typeState)[typeIndex] != TypeState::None && (*typeState)[typeIndex] != TypeState::ForwardStruct)
         return d.fail("struct type entry referenced as function");
 
     env->types[typeIndex] = TypeDef(StructType(std::move(fields)));
     (*typeState)[typeIndex] = TypeState::Struct;
 
     return true;
 }
 
+#ifdef ENABLE_WASM_GC
+static bool
+DecodeGCFeatureOptInSection(Decoder& d, ModuleEnvironment* env)
+{
+    MaybeSectionRange range;
+    if (!d.startSection(SectionId::GcFeatureOptIn, env, &range, "type"))
+        return false;
+    if (!range)
+        return true;
+
+    uint32_t version;
+    if (!d.readVarU32(&version))
+        return d.fail("expected gc feature version");
+
+    // For documentation of what's in the various versions, see
+    // https://github.com/lars-t-hansen/moz-gc-experiments
+    //
+    // When we evolve the engine to handle v2, we will continue to recognize v1
+    // here if v2 is fully backwards compatible with v1.
+
+    if (version != 1)
+        return d.fail("unsupported version of the gc feature");
+
+    env->gcFeatureOptIn = HasGcTypes::True;
+    return d.finishSection(*range, "gcfeatureoptin");
+}
+#endif
+
 static bool
 DecodeTypeSection(Decoder& d, ModuleEnvironment* env)
 {
     MaybeSectionRange range;
     if (!d.startSection(SectionId::Type, env, &range, "type"))
         return false;
     if (!range)
         return true;
@@ -1531,17 +1559,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, env->gcTypesEnabled, &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;
@@ -1763,21 +1791,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, env->gcTypesEnabled, &type, &isMutable))
+        if (!DecodeGlobalType(d, env->types, env->gcTypesEnabled(), &type, &isMutable))
             return false;
 
         InitExpr initializer;
-        if (!DecodeInitializerExpression(d, env->gcTypesEnabled, env->globals, type,
+        if (!DecodeInitializerExpression(d, env->gcTypesEnabled(), env->globals, type,
                                          env->types.length(), &initializer))
         {
             return false;
         }
 
         env->globals.infallibleAppend(GlobalDesc(initializer, isMutable));
     }
 
@@ -1956,17 +1984,17 @@ 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->gcTypesEnabled, env->globals, ValType::I32,
+        if (!DecodeInitializerExpression(d, env->gcTypesEnabled(), env->globals, ValType::I32,
                                          env->types.length(), &offset))
         {
             return false;
         }
 
         uint32_t numElems;
         if (!d.readVarU32(&numElems))
             return d.fail("expected segment size");
@@ -2036,16 +2064,21 @@ wasm::StartsCodeSection(const uint8_t* b
 }
 
 bool
 wasm::DecodeModuleEnvironment(Decoder& d, ModuleEnvironment* env)
 {
     if (!DecodePreamble(d))
         return false;
 
+#ifdef ENABLE_WASM_GC
+    if (!DecodeGCFeatureOptInSection(d, env))
+        return false;
+#endif
+
     if (!DecodeTypeSection(d, env))
         return false;
 
     if (!DecodeImportSection(d, env))
         return false;
 
     if (!DecodeFunctionSection(d, env))
         return false;
@@ -2143,17 +2176,17 @@ 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->gcTypesEnabled, env->globals, ValType::I32,
+        if (!DecodeInitializerExpression(d, env->gcTypesEnabled(), env->globals, ValType::I32,
                                          env->types.length(), &seg.offset))
         {
             return false;
         }
 
         if (!d.readVarU32(&seg.length))
             return d.fail("expected segment size");
 
--- a/js/src/wasm/WasmValidate.h
+++ b/js/src/wasm/WasmValidate.h
@@ -55,21 +55,40 @@ typedef Maybe<SectionRange> MaybeSection
 
 struct ModuleEnvironment
 {
     // Constant parameters for the entire compilation:
     const DebugEnabled        debug;
     const ModuleKind          kind;
     const CompileMode         mode;
     const Shareable           sharedMemoryEnabled;
-    const HasGcTypes          gcTypesEnabled;
+    // `gcTypesConfigured` reflects the value of the flags --wasm-gc and
+    // javascript.options.wasm_gc.  These flags will disappear eventually, thus
+    // allowing the removal of this variable and its replacement everywhere by
+    // the value HasGcTypes::True.
+    //
+    // For now, the value is used (a) in the value of gcTypesEnabled(), which
+    // controls whether ref types and struct types and associated instructions
+    // are accepted during validation, and (b) to control whether we emit code
+    // to suppress GC while wasm activations are on the stack.
+    const HasGcTypes          gcTypesConfigured;
     const Tier                tier;
 
     // Module fields decoded from the module environment (or initialized while
     // validating an asm.js module) and immutable during compilation:
+#ifdef ENABLE_WASM_GC
+    // `gcFeatureOptIn` reflects the presence in a module of a GcFeatureOptIn
+    // section.  This variable will be removed eventually, allowing it to be
+    // replaced everywhere by the value HasGcTypes::True.
+    //
+    // The flag is used in the value of gcTypesEnabled(), which controls whether
+    // ref types and struct types and associated instructions are accepted
+    // during validation.
+    HasGcTypes                gcFeatureOptIn;
+#endif
     MemoryUsage               memoryUsage;
     uint32_t                  minMemoryLength;
     Maybe<uint32_t>           maxMemoryLength;
     TypeDefVector             types;
     FuncTypeWithIdPtrVector   funcTypes;
     Uint32Vector              funcImportGlobalDataOffsets;
     GlobalDescVector          globals;
     TableDescVector           tables;
@@ -91,18 +110,21 @@ struct ModuleEnvironment
                                DebugEnabled debug,
                                HasGcTypes hasGcTypes,
                                Shareable sharedMemoryEnabled,
                                ModuleKind kind = ModuleKind::Wasm)
       : debug(debug),
         kind(kind),
         mode(mode),
         sharedMemoryEnabled(sharedMemoryEnabled),
-        gcTypesEnabled(hasGcTypes),
+        gcTypesConfigured(hasGcTypes),
         tier(tier),
+#ifdef ENABLE_WASM_GC
+        gcFeatureOptIn(HasGcTypes::False),
+#endif
         memoryUsage(MemoryUsage::None),
         minMemoryLength(0)
     {}
 
     size_t numTables() const {
         return tables.length();
     }
     size_t numTypes() const {
@@ -112,16 +134,23 @@ struct ModuleEnvironment
         return funcTypes.length();
     }
     size_t numFuncImports() const {
         return funcImportGlobalDataOffsets.length();
     }
     size_t numFuncDefs() const {
         return funcTypes.length() - funcImportGlobalDataOffsets.length();
     }
+    HasGcTypes gcTypesEnabled() const {
+#ifdef ENABLE_WASM_GC
+        if (gcTypesConfigured == HasGcTypes::True)
+            return gcFeatureOptIn;
+#endif
+        return HasGcTypes::False;
+    }
     bool usesMemory() const {
         return memoryUsage != MemoryUsage::None;
     }
     bool usesSharedMemory() const {
         return memoryUsage == MemoryUsage::Shared;
     }
     bool isAsmJS() const {
         return kind == ModuleKind::AsmJS;
@@ -324,16 +353,17 @@ class Encoder
 
     // A "section" is a contiguous range of bytes that stores its own size so
     // that it may be trivially skipped without examining the contents. Sections
     // require backpatching since the size of the section is only known at the
     // end while the size's varU32 must be stored at the beginning. Immediately
     // after the section length is the string id of the section.
 
     MOZ_MUST_USE bool startSection(SectionId id, size_t* offset) {
+        MOZ_ASSERT(uint32_t(id) < 128);
         return writeVarU32(uint32_t(id)) &&
                writePatchableVarU32(offset);
     }
     void finishSection(size_t offset) {
         return patchVarU32(offset, bytes_.length() - offset - varU32ByteLength(offset));
     }
 };