Merge mozilla-central to autoland. a=merge CLOSED TREE
authorCiure Andrei <aciure@mozilla.com>
Mon, 03 Sep 2018 18:44:13 +0300
changeset 482849 658c3662b01c215899d53c75b34a3a80f7e624f2
parent 482848 5254a8f44df4ac705bf05fe55476f476a37bf8b9 (current diff)
parent 482815 df61bdb0bc83acc349a7a283f7add907e6207bb7 (diff)
child 482850 416ff8fc97606ecdd0865d8cb6b216648f76fe90
push id232
push userfmarier@mozilla.com
push dateWed, 05 Sep 2018 20:45:54 +0000
reviewersmerge
milestone63.0a1
Merge mozilla-central to autoland. a=merge CLOSED TREE
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -3493,17 +3493,17 @@ def CreateBindingJSObject(descriptor, pr
     objDecl = "BindingJSObjectCreator<%s> creator(aCx);\n" % descriptor.nativeType
 
     # We don't always need to root obj, but there are a variety
     # of cases where we do, so for simplicity, just always root it.
     if descriptor.proxy:
         if descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
             create = dedent(
                 """
-                MOZ_ASSERT(aObject->mExpandoAndGeneration.expando.isUndefined());
+                aObject->mExpandoAndGeneration.expando.setUndefined();
                 JS::Rooted<JS::Value> expandoValue(aCx, JS::PrivateValue(&aObject->mExpandoAndGeneration));
                 creator.CreateProxyObject(aCx, &sClass.mBase, DOMProxyHandler::getInstance(),
                                           proto, aObject, expandoValue, aReflector);
                 """)
         else:
             create = dedent(
                 """
                 creator.CreateProxyObject(aCx, &sClass.mBase, DOMProxyHandler::getInstance(),
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1586,16 +1586,17 @@ RepresentativeStringArray(JSContext* cx,
     if (!JSString::fillWithRepresentatives(cx, array.as<ArrayObject>()))
         return false;
 
     args.rval().setObject(*array);
     return true;
 }
 
 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+
 static bool
 OOMThreadTypes(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().setInt32(js::THREAD_TYPE_MAX);
     return true;
 }
 
@@ -1685,128 +1686,110 @@ static size_t
 CountCompartments(JSContext* cx)
 {
     size_t count = 0;
     for (auto zone : cx->runtime()->gc.zones())
         count += zone->compartments().length();
     return count;
 }
 
-static bool
-OOMTest(JSContext* cx, unsigned argc, Value* vp)
+// Iterative failure testing: test a function by simulating failures at indexed
+// locations throughout the normal execution path and checking that the
+// resulting state of the environment is consistent with the error result.
+//
+// For example, trigger OOM at every allocation point and test that the function
+// either recovers and succeeds or raises an exception and fails.
+
+struct MOZ_STACK_CLASS IterativeFailureTestParams
 {
-    CallArgs args = CallArgsFromVp(argc, vp);
-
-    if (args.length() < 1 || args.length() > 2) {
-        JS_ReportErrorASCII(cx, "oomTest() takes between 1 and 2 arguments.");
-        return false;
-    }
-
-    if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
-        JS_ReportErrorASCII(cx, "The first argument to oomTest() must be a function.");
-        return false;
-    }
-
-    if (args.length() == 2 && !args[1].isBoolean()) {
-        JS_ReportErrorASCII(cx, "The optional second argument to oomTest() must be a boolean.");
-        return false;
-    }
-
-    if (!CheckCanSimulateOOM(cx))
-        return false;
-
-    bool expectExceptionOnFailure = true;
-    if (args.length() == 2)
-        expectExceptionOnFailure = args[1].toBoolean();
-
-    // There are some places where we do fail without raising an exception, so
-    // we can't expose this to the fuzzers by default.
-    if (fuzzingSafe)
-        expectExceptionOnFailure = false;
-
-    if (disableOOMFunctions) {
-        args.rval().setUndefined();
+    explicit IterativeFailureTestParams(JSContext* cx)
+      : testFunction(cx)
+    {}
+
+    RootedFunction testFunction;
+    unsigned threadStart = 0;
+    unsigned threadEnd = 0;
+    bool expectExceptionOnFailure = false;
+    bool verbose = false;
+};
+
+struct IterativeFailureSimulator
+{
+    virtual void setup(JSContext* cx) {}
+    virtual void teardown(JSContext* cx) {}
+    virtual void startSimulating(unsigned iteration, unsigned thread) = 0;
+    virtual bool stopSimulating() = 0;
+    virtual void cleanup(JSContext* cx) {}
+};
+
+bool
+RunIterativeFailureTest(JSContext* cx, const IterativeFailureTestParams& params,
+                        IterativeFailureSimulator& simulator)
+{
+    if (disableOOMFunctions)
         return true;
-    }
-
-    RootedFunction function(cx, &args[0].toObject().as<JSFunction>());
-
-    bool verbose = EnvVarIsDefined("OOM_VERBOSE");
-
-    unsigned threadStart = oom::FirstThreadTypeToTest;
-    unsigned threadEnd = oom::LastThreadTypeToTest;
-
-    // Test a single thread type if specified by the OOM_THREAD environment variable.
-    int threadOption = 0;
-    if (EnvVarAsInt("OOM_THREAD", &threadOption)) {
-        if (threadOption < oom::FirstThreadTypeToTest || threadOption > oom::LastThreadTypeToTest) {
-            JS_ReportErrorASCII(cx, "OOM_THREAD value out of range.");
-            return false;
-        }
-
-        threadStart = threadOption;
-        threadEnd = threadOption + 1;
-    }
-
+
+    // Disallow nested tests.
     if (cx->runningOOMTest) {
-        JS_ReportErrorASCII(cx, "Nested call to oomTest() is not allowed.");
+        JS_ReportErrorASCII(cx, "Nested call to iterative failure test is not allowed.");
         return false;
     }
     cx->runningOOMTest = true;
 
     MOZ_ASSERT(!cx->isExceptionPending());
     cx->runtime()->hadOutOfMemory = false;
 
-    size_t compartmentCount = CountCompartments(cx);
-
 #ifdef JS_GC_ZEAL
     JS_SetGCZeal(cx, 0, JS_DEFAULT_ZEAL_FREQ);
 #endif
 
-    for (unsigned thread = threadStart; thread < threadEnd; thread++) {
-        if (verbose)
+    size_t compartmentCount = CountCompartments(cx);
+
+    simulator.setup(cx);
+
+    for (unsigned thread = params.threadStart; thread < params.threadEnd; thread++) {
+        if (params.verbose)
             fprintf(stderr, "thread %d\n", thread);
 
-        unsigned allocation = 1;
-        bool handledOOM;
+        unsigned iteration = 1;
+        bool failureWasSimulated;
         do {
-            if (verbose)
-                fprintf(stderr, "  allocation %d\n", allocation);
+            if (params.verbose)
+                fprintf(stderr, "  iteration %d\n", iteration);
 
             MOZ_ASSERT(!cx->isExceptionPending());
             MOZ_ASSERT(!cx->runtime()->hadOutOfMemory);
 
-            js::oom::SimulateOOMAfter(allocation, thread, false);
+            simulator.startSimulating(iteration, thread);
 
             RootedValue result(cx);
-            bool ok = JS_CallFunction(cx, cx->global(), function,
+            bool ok = JS_CallFunction(cx, cx->global(), params.testFunction,
                                       HandleValueArray::empty(), &result);
 
-            handledOOM = js::oom::HadSimulatedOOM();
-            js::oom::ResetSimulatedOOM();
+            failureWasSimulated = simulator.stopSimulating();
 
             MOZ_ASSERT_IF(ok, !cx->isExceptionPending());
 
             if (ok) {
                 MOZ_ASSERT(!cx->isExceptionPending(),
                            "Thunk execution succeeded but an exception was raised - "
                            "missing error check?");
-            } else if (expectExceptionOnFailure) {
+            } else if (params.expectExceptionOnFailure) {
                 MOZ_ASSERT(cx->isExceptionPending(),
                            "Thunk execution failed but no exception was raised - "
                            "missing call to js::ReportOutOfMemory()?");
             }
 
             // Note that it is possible that the function throws an exception
-            // unconnected to OOM, in which case we ignore it. More correct
-            // would be to have the caller pass some kind of exception
-            // specification and to check the exception against it.
+            // unconnected to the simulated failure, in which case we ignore
+            // it. More correct would be to have the caller pass some kind of
+            // exception specification and to check the exception against it.
 
             cx->clearPendingException();
-            cx->runtime()->hadOutOfMemory = false;
+            simulator.cleanup(cx);
 
             // Some tests create a new compartment or zone on every
             // iteration. Our GC is triggered by GC allocations and not by
             // number of compartments or zones, so these won't normally get
             // cleaned up. The check here stops some tests running out of
             // memory.
             if (CountCompartments(cx) > compartmentCount + 100) {
                 JS_GC(cx);
@@ -1818,302 +1801,189 @@ OOMTest(JSContext* cx, unsigned argc, Va
             TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
             if (logger->enabled()) {
                 while (logger->enabled())
                     logger->disable();
                 logger->enable(cx);
             }
 #endif
 
-            allocation++;
-        } while (handledOOM);
-
-        if (verbose) {
-            fprintf(stderr, "  finished after %d allocations\n", allocation - 2);
-        }
-    }
+            iteration++;
+        } while (failureWasSimulated);
+
+        if (params.verbose)
+            fprintf(stderr, "  finished after %d iterations\n", iteration - 2);
+    }
+
+    simulator.teardown(cx);
 
     cx->runningOOMTest = false;
-    args.rval().setUndefined();
     return true;
 }
 
-static bool
-StackTest(JSContext* cx, unsigned argc, Value* vp)
+bool
+ParseIterativeFailureTestParams(JSContext* cx, const CallArgs& args,
+                                IterativeFailureTestParams* params)
 {
-    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(params);
 
     if (args.length() < 1 || args.length() > 2) {
-        JS_ReportErrorASCII(cx, "stackTest() takes between 1 and 2 arguments.");
+        JS_ReportErrorASCII(cx, "function takes between 1 and 2 arguments.");
         return false;
     }
 
     if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
-        JS_ReportErrorASCII(cx, "The first argument to stackTest() must be a function.");
-        return false;
-    }
-
-    if (args.length() == 2 && !args[1].isBoolean()) {
-        JS_ReportErrorASCII(cx, "The optional second argument to stackTest() must be a boolean.");
-        return false;
-    }
-
-    bool expectExceptionOnFailure = true;
-    if (args.length() == 2)
-        expectExceptionOnFailure = args[1].toBoolean();
+        JS_ReportErrorASCII(cx, "The first argument must be the function to test.");
+        return false;
+    }
+    params->testFunction = &args[0].toObject().as<JSFunction>();
 
     // There are some places where we do fail without raising an exception, so
     // we can't expose this to the fuzzers by default.
-    if (fuzzingSafe)
-        expectExceptionOnFailure = false;
-
-    if (disableOOMFunctions) {
-        args.rval().setUndefined();
-        return true;
-    }
-
-    RootedFunction function(cx, &args[0].toObject().as<JSFunction>());
-
-    bool verbose = EnvVarIsDefined("OOM_VERBOSE");
-
-    unsigned threadStart = oom::FirstThreadTypeToTest;
-    unsigned threadEnd = oom::LastThreadTypeToTest;
+    params->expectExceptionOnFailure = !fuzzingSafe;
+    if (args.length() == 2) {
+        if (!args[1].isBoolean()) {
+            JS_ReportErrorASCII(cx, "The optional second argument must be a boolean.");
+            return false;
+        }
+        params->expectExceptionOnFailure = args[1].toBoolean();
+    }
+
+    // Test all threads by default.
+    params->threadStart = oom::FirstThreadTypeToTest;
+    params->threadEnd = oom::LastThreadTypeToTest;
 
     // Test a single thread type if specified by the OOM_THREAD environment variable.
     int threadOption = 0;
     if (EnvVarAsInt("OOM_THREAD", &threadOption)) {
         if (threadOption < oom::FirstThreadTypeToTest || threadOption > oom::LastThreadTypeToTest) {
             JS_ReportErrorASCII(cx, "OOM_THREAD value out of range.");
             return false;
         }
 
-        threadStart = threadOption;
-        threadEnd = threadOption + 1;
-    }
-
-    if (cx->runningOOMTest) {
-        JS_ReportErrorASCII(cx, "Nested call to oomTest() or stackTest() is not allowed.");
-        return false;
-    }
-    cx->runningOOMTest = true;
-
-    MOZ_ASSERT(!cx->isExceptionPending());
-
-    size_t compartmentCount = CountCompartments(cx);
-
-#ifdef JS_GC_ZEAL
-    JS_SetGCZeal(cx, 0, JS_DEFAULT_ZEAL_FREQ);
-#endif
-
-    for (unsigned thread = threadStart; thread < threadEnd; thread++) {
-        if (verbose)
-            fprintf(stderr, "thread %d\n", thread);
-
-        unsigned check = 1;
-        bool handledOOM;
-        do {
-            if (verbose)
-                fprintf(stderr, "  check %d\n", check);
-
-            MOZ_ASSERT(!cx->isExceptionPending());
-
-            js::oom::SimulateStackOOMAfter(check, thread, false);
-
-            RootedValue result(cx);
-            bool ok = JS_CallFunction(cx, cx->global(), function,
-                                      HandleValueArray::empty(), &result);
-
-            handledOOM = js::oom::HadSimulatedStackOOM();
-            js::oom::ResetSimulatedStackOOM();
-
-            MOZ_ASSERT_IF(ok, !cx->isExceptionPending());
-
-            if (ok) {
-                MOZ_ASSERT(!cx->isExceptionPending(),
-                           "Thunk execution succeeded but an exception was raised - "
-                           "missing error check?");
-            } else if (expectExceptionOnFailure) {
-                MOZ_ASSERT(cx->isExceptionPending(),
-                           "Thunk execution failed but no exception was raised - "
-                           "missing call to js::ReportOutOfMemory()?");
-            }
-
-            // Note that it is possible that the function throws an exception
-            // unconnected to OOM, in which case we ignore it. More correct
-            // would be to have the caller pass some kind of exception
-            // specification and to check the exception against it.
-
-            cx->clearPendingException();
-
-            // Some tests create a new compartment or zone on every
-            // iteration. Our GC is triggered by GC allocations and not by
-            // number of compartments or zones, so these won't normally get
-            // cleaned up. The check here stops some tests running out of
-            // memory.
-            if (CountCompartments(cx) > compartmentCount + 100) {
-                JS_GC(cx);
-                compartmentCount = CountCompartments(cx);
-            }
-
-#ifdef JS_TRACE_LOGGING
-            // Reset the TraceLogger state if enabled.
-            TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
-            if (logger->enabled()) {
-                while (logger->enabled())
-                    logger->disable();
-                logger->enable(cx);
-            }
-#endif
-
-            check++;
-        } while (handledOOM);
-
-        if (verbose) {
-            fprintf(stderr, "  finished after %d checks\n", check - 2);
-        }
-    }
-
-    cx->runningOOMTest = false;
+        params->threadStart = threadOption;
+        params->threadEnd = threadOption + 1;
+    }
+
+    params->verbose = EnvVarIsDefined("OOM_VERBOSE");
+
+    return true;
+}
+
+struct OOMSimulator : public IterativeFailureSimulator
+{
+    void startSimulating(unsigned i, unsigned thread) override {
+        js::oom::SimulateOOMAfter(i, thread, false);
+    }
+
+    bool stopSimulating() override {
+        bool handledOOM = js::oom::HadSimulatedOOM();
+        js::oom::ResetSimulatedOOM();
+        return handledOOM;
+    }
+
+    void cleanup(JSContext* cx) override {
+        cx->runtime()->hadOutOfMemory = false;
+    }
+};
+
+static bool
+OOMTest(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    IterativeFailureTestParams params(cx);
+    if (!ParseIterativeFailureTestParams(cx, args, &params))
+        return false;
+
+    OOMSimulator simulator;
+    if (!RunIterativeFailureTest(cx, params, simulator))
+        return false;
+
     args.rval().setUndefined();
     return true;
 }
 
+struct StackOOMSimulator : public IterativeFailureSimulator
+{
+    void startSimulating(unsigned i, unsigned thread) override {
+        js::oom::SimulateStackOOMAfter(i, thread, false);
+    }
+
+    bool stopSimulating() override {
+        bool handledOOM = js::oom::HadSimulatedStackOOM();
+        js::oom::ResetSimulatedStackOOM();
+        return handledOOM;
+    }
+};
+
 static bool
-FailingInterruptCallback(JSContext* cx)
+StackTest(JSContext* cx, unsigned argc, Value* vp)
 {
-    return false;
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    IterativeFailureTestParams params(cx);
+    if (!ParseIterativeFailureTestParams(cx, args, &params))
+        return false;
+
+    StackOOMSimulator simulator;
+    if (!RunIterativeFailureTest(cx, params, simulator))
+        return false;
+
+    args.rval().setUndefined();
+    return true;
 }
 
+struct FailingIterruptSimulator : public IterativeFailureSimulator
+{
+    JSInterruptCallback* prevEnd = nullptr;
+
+    static bool
+    failingInterruptCallback(JSContext* cx) {
+        return false;
+    }
+
+    void setup(JSContext* cx) override {
+        prevEnd = cx->interruptCallbacks().end();
+        JS_AddInterruptCallback(cx, failingInterruptCallback);
+    }
+
+    void teardown(JSContext* cx) override {
+        cx->interruptCallbacks().erase(prevEnd, cx->interruptCallbacks().end());
+    }
+
+    void startSimulating(unsigned i, unsigned thread) override {
+        js::oom::SimulateInterruptAfter(i, thread, false);
+    }
+
+    bool stopSimulating() override {
+        bool handledInterrupt = js::oom::HadSimulatedInterrupt();
+        js::oom::ResetSimulatedInterrupt();
+        return handledInterrupt;
+    }
+};
+
 static bool
 InterruptTest(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    if (args.length() < 1 || args.length() > 2) {
-        JS_ReportErrorASCII(cx, "interruptTest() takes exactly 1 argument.");
-        return false;
-    }
-
-    if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
-        JS_ReportErrorASCII(cx, "The argument to interruptTest() must be a function.");
-        return false;
-    }
-
-    if (disableOOMFunctions) {
-        args.rval().setUndefined();
-        return true;
-    }
-
-    RootedFunction function(cx, &args[0].toObject().as<JSFunction>());
-
-    bool verbose = EnvVarIsDefined("OOM_VERBOSE");
-
-    unsigned threadStart = oom::FirstThreadTypeToTest;
-    unsigned threadEnd = oom::LastThreadTypeToTest;
-
-    // Test a single thread type if specified by the OOM_THREAD environment variable.
-    int threadOption = 0;
-    if (EnvVarAsInt("OOM_THREAD", &threadOption)) {
-        if (threadOption < oom::FirstThreadTypeToTest || threadOption > oom::LastThreadTypeToTest) {
-            JS_ReportErrorASCII(cx, "OOM_THREAD value out of range.");
-            return false;
-        }
-
-        threadStart = threadOption;
-        threadEnd = threadOption + 1;
-    }
-
-    if (cx->runningOOMTest) {
-        JS_ReportErrorASCII(cx, "Nested call to oomTest(), stackTest() or interruptTest() is not allowed.");
-        return false;
-    }
-    cx->runningOOMTest = true;
-
-    MOZ_ASSERT(!cx->isExceptionPending());
-
-    size_t compartmentCount = CountCompartments(cx);
-
-#ifdef JS_GC_ZEAL
-    JS_SetGCZeal(cx, 0, JS_DEFAULT_ZEAL_FREQ);
-#endif
-
-    JSInterruptCallback *prevEnd = cx->interruptCallbacks().end();
-    JS_AddInterruptCallback(cx, FailingInterruptCallback);
-
-    for (unsigned thread = threadStart; thread < threadEnd; thread++) {
-        if (verbose)
-            fprintf(stderr, "thread %d\n", thread);
-
-        unsigned check = 1;
-        bool handledInterrupt;
-        do {
-            if (verbose)
-                fprintf(stderr, "  check %d\n", check);
-
-            MOZ_ASSERT(!cx->isExceptionPending());
-
-            js::oom::SimulateInterruptAfter(check, thread, false);
-
-            RootedValue result(cx);
-            bool ok = JS_CallFunction(cx, cx->global(), function,
-                                      HandleValueArray::empty(), &result);
-
-            handledInterrupt = js::oom::HadSimulatedInterrupt();
-            js::oom::ResetSimulatedInterrupt();
-
-            MOZ_ASSERT_IF(ok, !cx->isExceptionPending());
-
-            if (ok) {
-                MOZ_ASSERT(!cx->isExceptionPending(),
-                           "Thunk execution succeeded but an exception was raised - "
-                           "missing error check?");
-            }
-
-            // Note that it is possible that the function throws an exception
-            // unconnected to OOM, in which case we ignore it. More correct
-            // would be to have the caller pass some kind of exception
-            // specification and to check the exception against it.
-
-            cx->clearPendingException();
-
-            // Some tests create a new compartment or zone on every
-            // iteration. Our GC is triggered by GC allocations and not by
-            // number of compartments or zones, so these won't normally get
-            // cleaned up. The check here stops some tests running out of
-            // memory.
-            if (CountCompartments(cx) > compartmentCount + 100) {
-                JS_GC(cx);
-                compartmentCount = CountCompartments(cx);
-            }
-
-#ifdef JS_TRACE_LOGGING
-            // Reset the TraceLogger state if enabled.
-            TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
-            if (logger->enabled()) {
-                while (logger->enabled())
-                    logger->disable();
-                logger->enable(cx);
-            }
-#endif
-
-            check++;
-        } while (handledInterrupt);
-
-        if (verbose) {
-            fprintf(stderr, "  finished after %d checks\n", check - 2);
-        }
-    }
-
-    // Clear any interrupt callbacks we added within this function
-    cx->interruptCallbacks().erase(prevEnd, cx->interruptCallbacks().end());
-    cx->runningOOMTest = false;
+    IterativeFailureTestParams params(cx);
+    if (!ParseIterativeFailureTestParams(cx, args, &params))
+        return false;
+
+    FailingIterruptSimulator simulator;
+    if (!RunIterativeFailureTest(cx, params, simulator))
+        return false;
+
     args.rval().setUndefined();
     return true;
 }
-#endif
+
+#endif // defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
 
 static bool
 SettlePromiseNow(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (!args.requireAtLeast(cx, "settlePromiseNow", 1))
         return false;
     if (!args[0].isObject() || !args[0].toObject().is<PromiseObject>()) {
@@ -5415,16 +5285,17 @@ static const JSFunctionSpecWithHelp Test
 "  Ensures str is a flat (null-terminated) string and returns it."),
 
     JS_FN_HELP("representativeStringArray", RepresentativeStringArray, 0, 0,
 "representativeStringArray()",
 "  Returns an array of strings that represent the various internal string\n"
 "  types and character encodings."),
 
 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+
     JS_FN_HELP("oomThreadTypes", OOMThreadTypes, 0, 0,
 "oomThreadTypes()",
 "  Get the number of thread types that can be used as an argument for\n"
 "  oomAfterAllocations() and oomAtAllocation()."),
 
     JS_FN_HELP("oomAfterAllocations", OOMAfterAllocations, 2, 0,
 "oomAfterAllocations(count [,threadType])",
 "  After 'count' js_malloc memory allocations, fail every following allocation\n"
@@ -5455,17 +5326,18 @@ static const JSFunctionSpecWithHelp Test
 "stackTest(function, [expectExceptionOnFailure = true])",
 "  This function behaves exactly like oomTest with the difference that\n"
 "  instead of simulating regular OOM conditions, it simulates the engine\n"
 "  running out of stack space (failing recursion check)."),
 
     JS_FN_HELP("interruptTest", InterruptTest, 0, 0,
 "interruptTest(function)",
 "  This function simulates interrupts similar to how oomTest simulates OOM conditions."),
-#endif
+
+#endif // defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
 
     JS_FN_HELP("newRope", NewRope, 3, 0,
 "newRope(left, right[, options])",
 "  Creates a rope with the given left/right strings.\n"
 "  Available options:\n"
 "    nursery: bool - force the string to be created in/out of the nursery, if possible.\n"),
 
     JS_FN_HELP("settlePromiseNow", SettlePromiseNow, 1, 0,
--- 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,28 @@
 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|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)))`,
 ];
 
 for (let src of simpleTests) {
     print(src)
     assertErrorMessage(() => wasmEvalText(src), CompileError, UNRECOGNIZED_OPCODE_OR_BAD_TYPE);
     assertValidateError(src);
 }
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,19 @@
 if (!wasmGcEnabled()) {
-    assertErrorMessage(() => wasmEvalText(`(module (type $s (struct)))`),
+    assertErrorMessage(() => wasmEvalText(`(module
+                                            (gc_feature_opt_in 1)
+                                            (type $s (struct)))`),
                        WebAssembly.CompileError, /Structure types not enabled/);
     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 +71,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
@@ -623,17 +623,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);
@@ -647,17 +647,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();
 
@@ -729,17 +729,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();
 
@@ -756,24 +756,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
@@ -363,28 +363,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];
 
@@ -571,17 +571,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
@@ -91,17 +91,17 @@ CompileArgs::initFromContext(JSContext* 
     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();
 
@@ -427,17 +427,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;
 
@@ -453,17 +453,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())
@@ -582,17 +582,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
@@ -41,27 +41,27 @@ struct CompileArgs : ShareableBase<Compi
 {
     Assumptions assumptions;
     ScriptedCaller scriptedCaller;
     UniqueChars sourceMapURL;
     bool baselineEnabled;
     bool debugEnabled;
     bool ionEnabled;
     bool sharedMemoryEnabled;
-    HasGcTypes gcTypesEnabled;
+    HasGcTypes gcTypesConfigured;
     bool testTiering;
 
     CompileArgs(Assumptions&& assumptions, ScriptedCaller&& scriptedCaller)
       : assumptions(std::move(assumptions)),
         scriptedCaller(std::move(scriptedCaller)),
         baselineEnabled(false),
         debugEnabled(false),
         ionEnabled(false),
         sharedMemoryEnabled(false),
-        gcTypesEnabled(HasGcTypes::False),
+        gcTypesConfigured(HasGcTypes::False),
         testTiering(false)
     {}
 
     // If CompileArgs is constructed without arguments, initFromContext() must
     // be called to complete initialization.
     CompileArgs() = default;
     bool initFromContext(JSContext* cx, ScriptedCaller&& scriptedCaller);
 };
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -827,17 +827,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
@@ -249,21 +249,21 @@ Module::finishTier2(UniqueLinkDataTier l
                 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,18 @@ WasmTokenStream::next()
                     return WasmToken(WasmToken::UnaryOpcode, Op::F64Trunc, begin, cur_);
                 break;
             }
             break;
         }
         break;
 
       case 'g':
+        if (consume(u"gc_feature_opt_in"))
+            return WasmToken(WasmToken::GcFeatureOptIn, begin, cur_);
         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 +3626,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 +4085,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 +5400,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 +5910,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();
     }
+#ifdef ENABLE_WASM_GC
+    HasGcTypes gcTypesEnabled() const {
+        if (gcTypesConfigured == HasGcTypes::True)
+            return gcFeatureOptIn;
+        return HasGcTypes::False;
+    }
+#endif
     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));
     }
 };
 
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -973,57 +973,65 @@ def build_docker_worker_payload(config, 
 
     check_caches_are_volumes(task)
 
 
 @payload_builder('generic-worker')
 def build_generic_worker_payload(config, task, task_def):
     worker = task['worker']
 
+    task_def['payload'] = {
+        'command': worker['command'],
+        'maxRunTime': worker['max-run-time'],
+    }
+
+    env = worker.get('env', {})
+
+    if task.get('needs-sccache'):
+        env['USE_SCCACHE'] = '1'
+        # Disable sccache idle shutdown.
+        env['SCCACHE_IDLE_TIMEOUT'] = '0'
+    else:
+        env['SCCACHE_DISABLE'] = '1'
+
+    if env:
+        task_def['payload']['env'] = env
+
     artifacts = []
 
     for artifact in worker.get('artifacts', []):
         a = {
             'path': artifact['path'],
             'type': artifact['type'],
-            'expires': task_def['expires'],  # always expire with the task
         }
         if 'name' in artifact:
             a['name'] = artifact['name']
         artifacts.append(a)
 
+    if artifacts:
+        task_def['payload']['artifacts'] = artifacts
+
     # Need to copy over mounts, but rename keys to respect naming convention
     #   * 'cache-name' -> 'cacheName'
     #   * 'task-id'    -> 'taskId'
     # All other key names are already suitable, and don't need renaming.
     mounts = deepcopy(worker.get('mounts', []))
     for mount in mounts:
         if 'cache-name' in mount:
             mount['cacheName'] = mount.pop('cache-name')
         if 'content' in mount:
             if 'task-id' in mount['content']:
                 mount['content']['taskId'] = mount['content'].pop('task-id')
 
-    task_def['payload'] = {
-        'command': worker['command'],
-        'artifacts': artifacts,
-        'env': worker.get('env', {}),
-        'mounts': mounts,
-        'maxRunTime': worker['max-run-time'],
-        'osGroups': worker.get('os-groups', []),
-    }
+    if mounts:
+        task_def['payload']['mounts'] = mounts
 
-    if task.get('needs-sccache'):
-        worker['env']['USE_SCCACHE'] = '1'
-        # Disable sccache idle shutdown.
-        worker['env']['SCCACHE_IDLE_TIMEOUT'] = '0'
-    else:
-        worker['env']['SCCACHE_DISABLE'] = '1'
+    if worker.get('os-groups', []):
+        task_def['payload']['osGroups'] = worker['os-groups']
 
-    # currently only support one feature (chain of trust) but this will likely grow
     features = {}
 
     if worker.get('chain-of-trust'):
         features['chainOfTrust'] = True
 
     if worker.get('taskcluster-proxy'):
         features['taskclusterProxy'] = True
 
@@ -1360,16 +1368,19 @@ def set_defaults(config, tasks):
             worker.setdefault('volumes', [])
             worker.setdefault('env', {})
             if 'caches' in worker:
                 for c in worker['caches']:
                     c.setdefault('skip-untrusted', False)
         elif worker['implementation'] == 'generic-worker':
             worker.setdefault('env', {})
             worker.setdefault('os-groups', [])
+            if worker['os-groups'] and worker['os'] != 'windows':
+                raise Exception('os-groups feature of generic-worker is only supported on '
+                                'Windows, not on {}'.format(worker['os']))
             worker.setdefault('chain-of-trust', False)
         elif worker['implementation'] in (
             'scriptworker-signing', 'beetmover', 'beetmover-push-to-release', 'beetmover-maven',
         ):
             worker.setdefault('max-run-time', 600)
         elif worker['implementation'] == 'push-apk':
             worker.setdefault('commit', False)
 
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -477005,65 +477005,65 @@
    "f6ac0ef7e8f6e192c9c26239884bf92b13cf67b8",
    "support"
   ],
   "css/CSS2/floats-clear/support/test-tr.png": [
    "59843ae54b64f6ce4f7e616d4be491c911ea84cf",
    "support"
   ],
   "css/CSS2/floats/float-nowrap-1-notref.html": [
-   "90eb8ef0501371ed2bac0d9a5d834f389f6df7cc",
+   "540c8048af61a2c7804d99ff14c3a2bf1f87e6ad",
    "support"
   ],
   "css/CSS2/floats/float-nowrap-1.html": [
-   "e9bd0db6fec826573204e1e1b0a0a96ca0db8204",
+   "656b9398e6b771b5cf0545a1a40bcb36a115301b",
    "reftest_node"
   ],
   "css/CSS2/floats/float-nowrap-2.html": [
-   "a20ebee2e17969ebc2a3cc4c564268b2d9b460c7",
+   "fc8e11fadc5e66bc49bbddea99aeed0c4019bec0",
    "reftest"
   ],
   "css/CSS2/floats/float-nowrap-3-ref.html": [
-   "a7d1a0b3a5d054a5d729d6c39e05a2226d023698",
+   "e7556a213662453f22cdcbee2adf3b21e00d6ce6",
    "reftest_node"
   ],
   "css/CSS2/floats/float-nowrap-3.html": [
-   "68be2ca43749459ef5e89ee5e4e5413e208fdc97",
+   "dbc643c8ca97230ab252769b4278065aec844160",
    "reftest"
   ],
   "css/CSS2/floats/float-nowrap-4-ref.html": [
-   "bcc45773c03ba4014569442509b82beac79ff1c3",
+   "a8c7b060819839dd1e7107a327dfbf32fb1ea794",
    "support"
   ],
   "css/CSS2/floats/float-nowrap-4.html": [
-   "3df6dcc3a8fdd9397322f2bc2ce182a1b66da9af",
+   "bf11e6e31c416e85580bc4792b2b3503dd87f99b",
    "reftest_node"
   ],
   "css/CSS2/floats/float-nowrap-5-ref.html": [
-   "61ec5b6e4a4413eedeedf452f49200dac57d1efc",
+   "7f400841037cfb79bafa2c41d3834cdd942445f4",
    "support"
   ],
   "css/CSS2/floats/float-nowrap-5.html": [
-   "e3dd4f2092e13f5326f2724ae24e63d418d17930",
+   "f4403cfb838126b726ef70906d2f8df7bb733f48",
    "reftest"
   ],
   "css/CSS2/floats/float-nowrap-6.html": [
-   "480421e65b0d537835899bc0ad24cc6abe6f2c2e",
+   "7c6bf9c0db6a112e364760b3320dcff3265be5c8",
    "reftest"
   ],
   "css/CSS2/floats/float-nowrap-7.html": [
-   "81f43c1770f07113e685395143b331fcaf4c79a1",
+   "bef462d995608e13ae943329022220c55a1f315e",
    "reftest"
   ],
   "css/CSS2/floats/float-nowrap-8.html": [
-   "bd066e4f94566395a78e7ac757c91f611257dfa9",
+   "8c2653193d643a452a416f9e41eb8d8397ab0058",
    "reftest"
   ],
   "css/CSS2/floats/float-nowrap-9.html": [
-   "f16e798ab48ffbdf25e25b50d33791503dd299f2",
+   "b4a6b2923097f9f9957d940db42d335b22e73def",
    "reftest"
   ],
   "css/CSS2/floats/floated-table-wider-than-specified.html": [
    "f93d50e43dd3eb49d5c8964200b7fe4ebb5bd6c8",
    "reftest"
   ],
   "css/CSS2/floats/floats-in-table-caption-001-ref.html": [
    "bf02b993c455a7630c4508b7bb5e0efdebde5f47",
@@ -650105,17 +650105,17 @@
    "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
    "support"
   ],
   "webaudio/the-audio-api/the-audioparam-interface/audioparam-connect-audioratesignal.html": [
    "517d64f3dbb69e610a5ed17475c45f530f749a42",
    "testharness"
   ],
   "webaudio/the-audio-api/the-audioparam-interface/audioparam-exceptional-values.html": [
-   "c928b3dc165a298dc5ddfd9b7b885c2e7b9c501d",
+   "982731d33843544d2f70af72a51e9bd878bbf486",
    "testharness"
   ],
   "webaudio/the-audio-api/the-audioparam-interface/audioparam-exponentialRampToValueAtTime.html": [
    "bec4c1286b4aaf7ba1b7aa7209ff41dad6c02dbd",
    "testharness"
   ],
   "webaudio/the-audio-api/the-audioparam-interface/audioparam-large-endtime.html": [
    "d8f38eeba0857a352badbdbeef3eca8bf9a6f087",
@@ -650133,17 +650133,17 @@
    "faf00c007b375a365bbb6b00e4051d44852d0fdc",
    "testharness"
   ],
   "webaudio/the-audio-api/the-audioparam-interface/audioparam-setValueAtTime.html": [
    "ab2edfd009f1320fdb8e0a49dffb984baed36f05",
    "testharness"
   ],
   "webaudio/the-audio-api/the-audioparam-interface/audioparam-setValueCurve-exceptions.html": [
-   "31405ebfcd2aa90bc8be13bd3fe856b1f6e60601",
+   "16975f1c7c8f8079916e4fd4f166b63ad9fa686a",
    "testharness"
   ],
   "webaudio/the-audio-api/the-audioparam-interface/audioparam-setValueCurveAtTime.html": [
    "de8406244bc386981c0527629e3685f250c6a659",
    "testharness"
   ],
   "webaudio/the-audio-api/the-audioparam-interface/audioparam-summingjunction.html": [
    "9084942f7032cd00d9d57b8d4d1f6ba2742ab57f",
@@ -650153,17 +650153,17 @@
    "73892dd845887d731779b3794a14df3f6bd36cba",
    "support"
   ],
   "webaudio/the-audio-api/the-audioparam-interface/automation-rate.html": [
    "a3c789e2f2cbdb31e540bf5cc58850786b4ed73b",
    "testharness"
   ],
   "webaudio/the-audio-api/the-audioparam-interface/event-insertion.html": [
-   "eab77c494d1161083f1fe73376492b177355995f",
+   "688d0478235e202859c7939eef65ad383f7a4f36",
    "testharness"
   ],
   "webaudio/the-audio-api/the-audioparam-interface/k-rate-audioworklet.https.html": [
    "e891da6da2b02924aac74b9e850677dc080b57b0",
    "testharness"
   ],
   "webaudio/the-audio-api/the-audioparam-interface/k-rate-biquad.html": [
    "85ae4f175fe182a4bf993443163dea14f00fa203",
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/compat/webkit-appearance.html.ini
@@ -0,0 +1,2 @@
+[webkit-appearance.html]
+  prefs: [layout.css.webkit-appearance.enabled:true]
--- a/testing/webdriver/src/actions.rs
+++ b/testing/webdriver/src/actions.rs
@@ -199,26 +199,24 @@ where
 }
 
 fn deserialize_to_option_i64<'de, D>(deserializer: D) -> Result<Option<i64>, D::Error>
 where
     D: Deserializer<'de>,
 {
     Option::deserialize(deserializer)?
         .ok_or_else(|| de::Error::custom("invalid type: null, expected i64"))
-        .map(|v: i64| Some(v))
 }
 
 fn deserialize_to_option_u64<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
 where
     D: Deserializer<'de>,
 {
     Option::deserialize(deserializer)?
         .ok_or_else(|| de::Error::custom("invalid type: null, expected i64"))
-        .map(|v: u64| Some(v))
 }
 
 #[cfg(test)]
 mod test {
     use super::*;
     use serde_json;
     use test::{check_deserialize, check_serialize_deserialize};
 
--- a/testing/webdriver/src/capabilities.rs
+++ b/testing/webdriver/src/capabilities.rs
@@ -1,12 +1,11 @@
 use common::MAX_SAFE_INTEGER;
 use error::{ErrorStatus, WebDriverError, WebDriverResult};
 use serde_json::{Map, Value};
-use std::convert::From;
 use url::Url;
 
 pub type Capabilities = Map<String, Value>;
 
 /// Trait for objects that can be used to inspect browser capabilities
 ///
 /// The main methods in this trait are called with a Capabilites object
 /// resulting from a full set of potential capabilites for the session.  Given
@@ -140,17 +139,17 @@ impl SpecNewSessionParameters {
                 }
                 "pageLoadStrategy" => SpecNewSessionParameters::validate_page_load_strategy(value)?,
                 "proxy" => SpecNewSessionParameters::validate_proxy(value)?,
                 "timeouts" => SpecNewSessionParameters::validate_timeouts(value)?,
                 "unhandledPromptBehavior" => {
                     SpecNewSessionParameters::validate_unhandled_prompt_behaviour(value)?
                 }
                 x => {
-                    if !x.contains(":") {
+                    if !x.contains(':') {
                         return Err(WebDriverError::new(
                             ErrorStatus::InvalidArgument,
                             format!(
                                 "{} is not the name of a known capability or extension capability",
                                 x
                             ),
                         ));
                     } else {
@@ -206,20 +205,22 @@ impl SpecNewSessionParameters {
                             ErrorStatus::InvalidArgument,
                             format!("proxyType is not a string: {}", value),
                         ))
                     }
                 },
 
                 "proxyAutoconfigUrl" => match value.as_str() {
                     Some(x) => {
-                        Url::parse(x).or(Err(WebDriverError::new(
-                            ErrorStatus::InvalidArgument,
-                            format!("proxyAutoconfigUrl is not a valid URL: {}", x),
-                        )))?;
+                        Url::parse(x).or_else(|_| {
+                            Err(WebDriverError::new(
+                                ErrorStatus::InvalidArgument,
+                                format!("proxyAutoconfigUrl is not a valid URL: {}", x),
+                            ))
+                        })?;
                     }
                     None => {
                         return Err(WebDriverError::new(
                             ErrorStatus::InvalidArgument,
                             "proxyAutoconfigUrl is not a string",
                         ))
                     }
                 },
@@ -269,37 +270,41 @@ impl SpecNewSessionParameters {
                     format!("noProxy is not an array: {}", value),
                 ))
             }
         }
 
         Ok(())
     }
 
-    /// Validate whether a named capability is JSON value is a string containing a host
-    /// and possible port
+    /// Validate whether a named capability is JSON value is a string
+    /// containing a host and possible port
     fn validate_host(value: &Value, entry: &str) -> WebDriverResult<()> {
         match value.as_str() {
             Some(host) => {
                 if host.contains("://") {
                     return Err(WebDriverError::new(
                         ErrorStatus::InvalidArgument,
                         format!("{} must not contain a scheme: {}", entry, host),
                     ));
                 }
 
                 // Temporarily add a scheme so the host can be parsed as URL
-                let s = String::from(format!("http://{}", host));
-                let url = Url::parse(s.as_str()).or(Err(WebDriverError::new(
-                    ErrorStatus::InvalidArgument,
-                    format!("{} is not a valid URL: {}", entry, host),
-                )))?;
+                let url = Url::parse(&format!("http://{}", host)).or_else(|_| {
+                    Err(WebDriverError::new(
+                        ErrorStatus::InvalidArgument,
+                        format!("{} is not a valid URL: {}", entry, host),
+                    ))
+                })?;
 
-                if url.username() != "" || url.password() != None || url.path() != "/"
-                    || url.query() != None || url.fragment() != None
+                if url.username() != ""
+                    || url.password() != None
+                    || url.path() != "/"
+                    || url.query() != None
+                    || url.fragment() != None
                 {
                     return Err(WebDriverError::new(
                         ErrorStatus::InvalidArgument,
                         format!("{} is not of the form host[:port]: {}", entry, host),
                     ));
                 }
             }
 
@@ -483,34 +488,34 @@ impl CapabilitiesMatching for SpecNewSes
                             if !browser_capabilities
                                 .accept_proxy(&proxy, merged)
                                 .unwrap_or(false)
                             {
                                 return None;
                             }
                         }
                         name => {
-                            if name.contains(":") {
+                            if name.contains(':') {
                                 if !browser_capabilities
                                     .accept_custom(name, value, merged)
                                     .unwrap_or(false)
                                 {
                                     return None;
                                 }
                             } else {
                                 // Accept the capability
                             }
                         }
                     }
                 }
 
-                return Some(merged);
+                Some(merged)
             })
             .next()
-            .map(|x| x.clone());
+            .cloned();
         Ok(selected)
     }
 }
 
 #[derive(Debug, PartialEq, Serialize, Deserialize)]
 pub struct LegacyNewSessionParameters {
     #[serde(default = "Capabilities::default")]
     pub desired: Capabilities,
--- a/testing/webdriver/src/command.rs
+++ b/testing/webdriver/src/command.rs
@@ -95,17 +95,17 @@ impl<U: WebDriverExtensionRoute> WebDriv
     ) -> WebDriverMessage<U> {
         WebDriverMessage {
             session_id,
             command,
         }
     }
 
     pub fn from_http(
-        match_type: Route<U>,
+        match_type: &Route<U>,
         params: &Captures,
         raw_body: &str,
         requires_body: bool,
     ) -> WebDriverResult<WebDriverMessage<U>> {
         let session_id = WebDriverMessage::<U>::get_session_id(params);
         let body_data = WebDriverMessage::<U>::decode_body(raw_body, requires_body)?;
         let command = match match_type {
             Route::NewSession => WebDriverCommand::NewSession(serde_json::from_str(raw_body)?),
@@ -521,33 +521,29 @@ fn deserialize_to_u64<'de, D>(deserializ
 where
     D: Deserializer<'de>,
 {
     let opt = Option::deserialize(deserializer)?.map(|value: f64| value);
     let value = match opt {
         Some(n) => {
             if n < 0.0 || n.fract() != 0.0 {
                 return Err(de::Error::custom(format!(
-                    "'{}' is not a positive Integer",
+                    "{} is not a positive Integer",
                     n
                 )));
             }
             if (n as u64) > MAX_SAFE_INTEGER {
                 return Err(de::Error::custom(format!(
-                    "'{}' is greater than maximum safe integer",
+                    "{} is greater than maximum safe integer",
                     n
                 )));
             }
             Some(n as u64)
         }
-        None => {
-            return Err(de::Error::custom(format!(
-                "'null' is not a positive Integer"
-            )));
-        }
+        None => return Err(de::Error::custom("null is not a positive integer")),
     };
 
     Ok(value)
 }
 
 /// A top-level browsing context’s window rect is a dictionary of the
 /// [`screenX`], [`screenY`], `width`, and `height` attributes of the
 /// `WindowProxy`.
--- a/testing/webdriver/src/common.rs
+++ b/testing/webdriver/src/common.rs
@@ -1,15 +1,15 @@
 use serde::ser::{Serialize, Serializer};
 
 pub static ELEMENT_KEY: &'static str = "element-6066-11e4-a52e-4f735466cecf";
 pub static FRAME_KEY: &'static str = "frame-075b-4da1-b6ba-e579c2d3230a";
 pub static WINDOW_KEY: &'static str = "window-fcc6-11e5-b4f8-330a88ab9d7f";
 
-pub static MAX_SAFE_INTEGER: u64 = 9007199254740991;
+pub static MAX_SAFE_INTEGER: u64 = 9_007_199_254_740_991;
 
 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
 pub struct Cookie {
     pub name: String,
     pub value: String,
     pub path: Option<String>,
     pub domain: Option<String>,
     #[serde(default)]
--- a/testing/webdriver/src/httpapi.rs
+++ b/testing/webdriver/src/httpapi.rs
@@ -318,39 +318,39 @@ impl<U: WebDriverExtensionRoute> Request
         let path_regexp = RequestMatcher::<U>::compile_path(path);
         RequestMatcher {
             method,
             path_regexp,
             match_type,
         }
     }
 
-    pub fn get_match<'t>(&'t self, method: Method, path: &'t str) -> (bool, Option<Captures>) {
+    pub fn get_match<'t>(&'t self, method: &Method, path: &'t str) -> (bool, Option<Captures>) {
         let captures = self.path_regexp.captures(path);
         (method == self.method, captures)
     }
 
     fn compile_path(path: &str) -> Regex {
         let mut rv = String::new();
         rv.push_str("^");
         let components = path.split('/');
         for component in components {
-            if component.starts_with("{") {
-                if !component.ends_with("}") {
+            if component.starts_with('{') {
+                if !component.ends_with('}') {
                     panic!("Invalid url pattern")
                 }
                 rv.push_str(&format!("(?P<{}>[^/]+)/", &component[1..component.len() - 1])[..]);
             } else {
                 rv.push_str(&format!("{}/", component)[..]);
             }
         }
-        //Remove the trailing /
+        // Remove the trailing /
         rv.pop();
         rv.push_str("$");
-        //This will fail at runtime if the regexp is invalid
+        // This will fail at runtime if the regexp is invalid
         Regex::new(&rv[..]).unwrap()
     }
 }
 
 #[derive(Debug)]
 pub struct WebDriverHttpApi<U: WebDriverExtensionRoute> {
     routes: Vec<(Method, RequestMatcher<U>)>,
 }
@@ -374,28 +374,28 @@ impl<U: WebDriverExtensionRoute> WebDriv
 
     fn add(&mut self, method: Method, path: &str, match_type: Route<U>) {
         let http_matcher = RequestMatcher::new(method.clone(), path, match_type);
         self.routes.push((method, http_matcher));
     }
 
     pub fn decode_request(
         &self,
-        method: Method,
+        method: &Method,
         path: &str,
         body: &str,
     ) -> WebDriverResult<WebDriverMessage<U>> {
         let mut error = ErrorStatus::UnknownPath;
         for &(ref match_method, ref matcher) in self.routes.iter() {
             if method == *match_method {
-                let (method_match, captures) = matcher.get_match(method.clone(), path);
+                let (method_match, captures) = matcher.get_match(method, path);
                 if captures.is_some() {
                     if method_match {
                         return WebDriverMessage::from_http(
-                            matcher.match_type.clone(),
+                            &matcher.match_type,
                             &captures.unwrap(),
                             body,
                             method == Method::POST,
                         );
                     } else {
                         error = ErrorStatus::UnknownMethod;
                     }
                 }
--- a/testing/webdriver/src/server.rs
+++ b/testing/webdriver/src/server.rs
@@ -58,17 +58,17 @@ impl<T: WebDriverHandler<U>, U: WebDrive
     fn new(handler: T) -> Dispatcher<T, U> {
         Dispatcher {
             handler,
             session: None,
             extension_type: PhantomData,
         }
     }
 
-    fn run(&mut self, msg_chan: Receiver<DispatchMessage<U>>) {
+    fn run(&mut self, msg_chan: &Receiver<DispatchMessage<U>>) {
         loop {
             match msg_chan.recv() {
                 Ok(DispatchMessage::HandleWebDriver(msg, resp_chan)) => {
                     let resp = match self.check_session(&msg) {
                         Ok(_) => self.handler.handle_command(&self.session, msg),
                         Err(e) => Err(e),
                     };
 
@@ -169,67 +169,62 @@ impl<U: WebDriverExtensionRoute> HttpHan
     }
 }
 
 impl<U: WebDriverExtensionRoute + 'static> Service for HttpHandler<U> {
     type ReqBody = Body;
     type ResBody = Body;
 
     type Error = hyper::Error;
-    type Future = Box<future::Future<Item=Response<Self::ResBody>, Error=hyper::Error> + Send>;
+    type Future = Box<future::Future<Item = Response<Self::ResBody>, Error = hyper::Error> + Send>;
 
     fn call(&mut self, req: Request<Self::ReqBody>) -> Self::Future {
         let uri = req.uri().clone();
         let method = req.method().clone();
         let api = self.api.clone();
         let chan = self.chan.clone();
 
         Box::new(req.into_body().concat2().and_then(move |body| {
             let body = String::from_utf8(body.to_vec()).unwrap();
             debug!("-> {} {} {}", method, uri, body);
 
             let msg_result = {
                 // The fact that this locks for basically the whole request doesn't
                 // matter as long as we are only handling one request at a time.
                 match api.lock() {
-                    Ok(ref api) => api.decode_request(method, &uri.path(), &body[..]),
-                    Err(_) => panic!("Something terrible happened"),
+                    Ok(ref api) => api.decode_request(&method, &uri.path(), &body[..]),
+                    Err(e) => panic!("Error decoding request: {:?}", e),
                 }
             };
 
             let (status, resp_body) = match msg_result {
                 Ok(message) => {
                     let (send_res, recv_res) = channel();
                     match chan.lock() {
                         Ok(ref c) => {
-                            let res =
-                                c.send(DispatchMessage::HandleWebDriver(message, send_res));
+                            let res = c.send(DispatchMessage::HandleWebDriver(message, send_res));
                             match res {
                                 Ok(x) => x,
-                                Err(_) => {
-                                    panic!("Something terrible happened");
-                                }
+                                Err(e) => panic!("Error: {:?}", e),
                             }
                         }
                         Err(e) => panic!("Error reading response: {:?}", e),
                     }
 
                     match recv_res.recv() {
                         Ok(data) => match data {
                             Ok(response) => {
                                 (StatusCode::OK, serde_json::to_string(&response).unwrap())
                             }
-                            Err(err) => {
-                                (err.http_status(), serde_json::to_string(&err).unwrap())
-                            }
+                            Err(e) => (e.http_status(), serde_json::to_string(&e).unwrap()),
                         },
                         Err(e) => panic!("Error reading response: {:?}", e),
                     }
                 }
-                Err(err) => (err.http_status(), serde_json::to_string(&err).unwrap()),
+                Err(e) => (e.http_status(), serde_json::to_string(&e).unwrap()),
             };
 
             debug!("<- {} {}", status, resp_body);
 
             let response = Response::builder()
                 .status(status)
                 .header(http::header::CONTENT_TYPE, "application/json; charset=utf8")
                 .header(http::header::CACHE_CONTROL, "no-cache")
@@ -284,13 +279,13 @@ where
             });
 
         rt.block_on(fut).unwrap();
     })?;
 
     let builder = thread::Builder::new().name("webdriver dispatcher".to_string());
     builder.spawn(move || {
         let mut dispatcher = Dispatcher::new(handler);
-        dispatcher.run(msg_recv);
+        dispatcher.run(&msg_recv);
     })?;
 
     Ok(Listener { _guard: Some(handle), socket: addr })
 }