Bug 1237058 - Nursery allocate cross-compartment wrappers; r=jonco,r=terrence
authorTerrence Cole <terrence@mozilla.com>
Mon, 04 Apr 2016 11:50:12 -0700
changeset 313525 9ff810febb0af9cf4e08ed5c1b4c902aef3f4f5f
parent 313524 af4c053741f1b847888f5566175fd4c7931e5bd7
child 313526 eab4e2f9adb418a65995058aa97cec5547454118
push id81649
push usertcole@mozilla.com
push dateMon, 12 Sep 2016 16:29:59 +0000
treeherdermozilla-inbound@9ff810febb0a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco, terrence
bugs1237058
milestone51.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1237058 - Nursery allocate cross-compartment wrappers; r=jonco,r=terrence
js/public/Proxy.h
js/public/Value.h
js/src/gc/Marking.cpp
js/src/gc/Nursery.cpp
js/src/gc/Nursery.h
js/src/gc/NurseryAwareHashMap.h
js/src/jsapi-tests/testGCMarking.cpp
js/src/jsapi-tests/testWeakMap.cpp
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jscompartmentinlines.h
js/src/jsgc.cpp
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/jswrapper.h
js/src/proxy/CrossCompartmentWrapper.cpp
js/src/proxy/Proxy.cpp
js/src/vm/Debugger.cpp
js/src/vm/ObjectGroup.h
js/src/vm/ProxyObject.cpp
js/src/vm/ProxyObject.h
--- a/js/public/Proxy.h
+++ b/js/public/Proxy.h
@@ -214,16 +214,24 @@ class JS_FRIEND_API(BaseProxyHandler)
     virtual bool finalizeInBackground(Value priv) const {
         /*
          * Called on creation of a proxy to determine whether its finalize
          * method can be finalized on the background thread.
          */
         return true;
     }
 
+    virtual bool canNurseryAllocate() const {
+        /*
+         * Nursery allocation is allowed if and only if it is safe to not
+         * run |finalize| when the ProxyObject dies.
+         */
+        return false;
+    }
+
     /* Policy enforcement methods.
      *
      * enter() allows the policy to specify whether the caller may perform |act|
      * on the proxy's |id| property. In the case when |act| is CALL, |id| is
      * generally JSID_VOID.
      *
      * The |act| parameter to enter() specifies the action being performed.
      * If |bp| is false, the method suggests that the caller throw (though it
--- a/js/public/Value.h
+++ b/js/public/Value.h
@@ -1716,16 +1716,19 @@ JS_PUBLIC_API(void) HeapValuePostBarrier
 
 template <>
 struct GCPolicy<JS::Value>
 {
     static Value initial() { return UndefinedValue(); }
     static void trace(JSTracer* trc, Value* v, const char* name) {
         js::UnsafeTraceManuallyBarrieredEdge(trc, v, name);
     }
+    static bool isTenured(const Value& thing) {
+        return !thing.isGCThing() || !IsInsideNursery(thing.toGCThing());
+    }
 };
 
 } // namespace JS
 
 namespace js {
 
 template <>
 struct BarrierMethods<JS::Value>
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -2524,21 +2524,24 @@ js::TenuringTracer::moveObjectToTenured(
     if (src->is<InlineTypedObject>()) {
         InlineTypedObject::objectMovedDuringMinorGC(this, dst, src);
     } else if (src->is<TypedArrayObject>()) {
         tenuredSize += TypedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind);
     } else if (src->is<UnboxedArrayObject>()) {
         tenuredSize += UnboxedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind);
     } else if (src->is<ArgumentsObject>()) {
         tenuredSize += ArgumentsObject::objectMovedDuringMinorGC(this, dst, src);
+    } else if (src->is<ProxyObject>()) {
+        tenuredSize += ProxyObject::objectMovedDuringMinorGC(this, dst, src);
     } else if (JSObjectMovedOp op = dst->getClass()->extObjectMovedOp()) {
         op(dst, src);
-    } else if (src->getClass()->flags & JSCLASS_SKIP_NURSERY_FINALIZE) {
-        // Objects with JSCLASS_SKIP_NURSERY_FINALIZE need to be handled above
-        // to ensure any additional nursery buffers they hold are moved.
+    } else if (src->getClass()->hasFinalize()) {
+        // Such objects need to be handled specially above to ensure any
+        // additional nursery buffers they hold are moved.
+        MOZ_RELEASE_ASSERT(CanNurseryAllocateFinalizedClass(src->getClass()));
         MOZ_CRASH("Unhandled JSCLASS_SKIP_NURSERY_FINALIZE Class");
     }
 
     return tenuredSize;
 }
 
 size_t
 js::TenuringTracer::moveSlotsToTenured(NativeObject* dst, NativeObject* src, AllocKind dstKind)
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -249,33 +249,29 @@ js::Nursery::leaveZealMode() {
 #endif // JS_GC_ZEAL
 
 JSObject*
 js::Nursery::allocateObject(JSContext* cx, size_t size, size_t numDynamic, const js::Class* clasp)
 {
     /* Ensure there's enough space to replace the contents with a RelocationOverlay. */
     MOZ_ASSERT(size >= sizeof(RelocationOverlay));
 
-    /*
-     * Classes with JSCLASS_SKIP_NURSERY_FINALIZE will not have their finalizer
-     * called if they are nursery allocated and not promoted to the tenured
-     * heap. The finalizers for these classes must do nothing except free data
-     * which was allocated via Nursery::allocateBuffer.
-     */
-    MOZ_ASSERT_IF(clasp->hasFinalize(), clasp->flags & JSCLASS_SKIP_NURSERY_FINALIZE);
+    /* Sanity check the finalizer. */
+    MOZ_ASSERT_IF(clasp->hasFinalize(), CanNurseryAllocateFinalizedClass(clasp) ||
+                                        clasp->isProxy());
 
     /* Make the object allocation. */
     JSObject* obj = static_cast<JSObject*>(allocate(size));
     if (!obj)
         return nullptr;
 
     /* If we want external slots, add them. */
     HeapSlot* slots = nullptr;
     if (numDynamic) {
-        MOZ_ASSERT(clasp->isNative());
+        MOZ_ASSERT(clasp->isNative() || clasp->isProxy());
         slots = static_cast<HeapSlot*>(allocateBuffer(cx->zone(), numDynamic * sizeof(HeapSlot)));
         if (!slots) {
             /*
              * It is safe to leave the allocated object uninitialized, since we
              * do not visit unallocated things in the nursery.
              */
             return nullptr;
         }
@@ -695,17 +691,17 @@ js::Nursery::doCollection(JSRuntime* rt,
     // objects are left to move. That is, we iterate to a fixed point.
     maybeStartProfile(ProfileKey::CollectToFP);
     collectToFixedPoint(mover, tenureCounts);
     maybeEndProfile(ProfileKey::CollectToFP);
 
     // Sweep compartments to update the array buffer object's view lists.
     maybeStartProfile(ProfileKey::SweepArrayBufferViewList);
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next())
-        c->sweepAfterMinorGC();
+        c->sweepAfterMinorGC(&mover);
     maybeEndProfile(ProfileKey::SweepArrayBufferViewList);
 
     // Update any slot or element pointers whose destination has been tenured.
     maybeStartProfile(ProfileKey::UpdateJitActivations);
     js::jit::UpdateJitActivationsForMinorGC(rt, &mover);
     forwardedBuffers.finish();
     maybeEndProfile(ProfileKey::UpdateJitActivations);
 
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -107,16 +107,30 @@ class TenuringTracer : public JSTracer
     JSObject* moveToTenured(JSObject* src);
     size_t moveObjectToTenured(JSObject* dst, JSObject* src, gc::AllocKind dstKind);
     size_t moveElementsToTenured(NativeObject* dst, NativeObject* src, gc::AllocKind dstKind);
     size_t moveSlotsToTenured(NativeObject* dst, NativeObject* src, gc::AllocKind dstKind);
 
     void traceSlots(JS::Value* vp, JS::Value* end);
 };
 
+/*
+ * Classes with JSCLASS_SKIP_NURSERY_FINALIZE or Wrapper classes with
+ * CROSS_COMPARTMENT flags will not have their finalizer called if they are
+ * nursery allocated and not promoted to the tenured heap. The finalizers for
+ * these classes must do nothing except free data which was allocated via
+ * Nursery::allocateBuffer.
+ */
+inline bool
+CanNurseryAllocateFinalizedClass(const js::Class* const clasp)
+{
+    MOZ_ASSERT(clasp->hasFinalize());
+    return clasp->flags & JSCLASS_SKIP_NURSERY_FINALIZE;
+}
+
 class Nursery
 {
   public:
     static const size_t Alignment = gc::ChunkSize;
     static const size_t ChunkShift = gc::ChunkShift;
 
     explicit Nursery(JSRuntime* rt);
     ~Nursery();
new file mode 100644
--- /dev/null
+++ b/js/src/gc/NurseryAwareHashMap.h
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef gc_NurseryAwareHashMap_h
+#define gc_NurseryAwareHashMap_h
+
+namespace js {
+
+namespace detail {
+// This class only handles the incremental case and does not deal with nursery
+// pointers. The only users should be for NurseryAwareHashMap; it is defined
+// externally because we need a GCPolicy for its use in the contained map.
+template <typename T>
+class UnsafeBareReadBarriered : public ReadBarrieredBase<T>
+{
+  public:
+    UnsafeBareReadBarriered() : ReadBarrieredBase<T>(JS::GCPolicy<T>::initial()) {}
+    MOZ_IMPLICIT UnsafeBareReadBarriered(const T& v) : ReadBarrieredBase<T>(v) {}
+    explicit UnsafeBareReadBarriered(const UnsafeBareReadBarriered& v) : ReadBarrieredBase<T>(v) {}
+    UnsafeBareReadBarriered(UnsafeBareReadBarriered&& v)
+      : ReadBarrieredBase<T>(mozilla::Forward<UnsafeBareReadBarriered<T>>(v))
+    {}
+
+    UnsafeBareReadBarriered& operator=(const UnsafeBareReadBarriered& v) {
+        this->value = v.value;
+        return *this;
+    }
+
+    UnsafeBareReadBarriered& operator=(const T& v) {
+        this->value = v;
+        return *this;
+    }
+
+    const T get() const {
+        if (!InternalBarrierMethods<T>::isMarkable(this->value))
+            return JS::GCPolicy<T>::initial();
+        this->read();
+        return this->value;
+    }
+
+    explicit operator bool() const {
+        return bool(this->value);
+    }
+
+    const T unbarrieredGet() const { return this->value; }
+    T* unsafeGet() { return &this->value; }
+    T const* unsafeGet() const { return &this->value; }
+};
+} // namespace detail
+
+// The "nursery aware" hash map is a special case of GCHashMap that is able to
+// treat nursery allocated members weakly during a minor GC: e.g. it allows for
+// nursery allocated objects to be collected during nursery GC where a normal
+// hash table treats such edges strongly.
+//
+// Doing this requires some strong constraints on what can be stored in this
+// table and how it can be accessed. At the moment, this table assumes that
+// all values contain a strong reference to the key. It also requires the
+// policy to contain an |isTenured| and |needsSweep| members, which is fairly
+// non-standard. This limits its usefulness to the CrossCompartmentMap at the
+// moment, but might serve as a useful base for other tables in future.
+template <typename Key,
+          typename Value,
+          typename HashPolicy = DefaultHasher<Key>,
+          typename AllocPolicy = TempAllocPolicy>
+class NurseryAwareHashMap
+{
+    using BarrieredValue = detail::UnsafeBareReadBarriered<Value>;
+    using MapType = GCRekeyableHashMap<Key, BarrieredValue, HashPolicy, AllocPolicy>;
+    MapType map;
+
+    // Keep a list of all keys for which JS::GCPolicy<Key>::isTenured is false.
+    // This lets us avoid a full traveral of the map on each minor GC, keeping
+    // the minor GC times proportional to the nursery heap size.
+    Vector<Key, 0, AllocPolicy> nurseryEntries;
+
+  public:
+    using Lookup = typename MapType::Lookup;
+    using Ptr = typename MapType::Ptr;
+    using Range = typename MapType::Range;
+
+    explicit NurseryAwareHashMap(AllocPolicy a = AllocPolicy()) : map(a) {}
+
+    MOZ_MUST_USE bool init(uint32_t len = 16) { return map.init(len); }
+
+    bool empty() const { return map.empty(); }
+    Ptr lookup(const Lookup& l) const { return map.lookup(l); }
+    void remove(Ptr p) { map.remove(p); }
+    Range all() const { return map.all(); }
+    struct Enum : public MapType::Enum {
+        explicit Enum(NurseryAwareHashMap& namap) : MapType::Enum(namap.map) {}
+    };
+    size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+        return map.sizeOfExcludingThis(mallocSizeOf);
+    }
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+        return map.sizeOfIncludingThis(mallocSizeOf);
+    }
+
+    MOZ_MUST_USE bool put(const Key& k, const Value& v) {
+        auto p = map.lookupForAdd(k);
+        if (p) {
+            if (!JS::GCPolicy<Key>::isTenured(k) || !JS::GCPolicy<Value>::isTenured(v)) {
+                if (!nurseryEntries.append(k))
+                    return false;
+            }
+            p->value() = v;
+            return true;
+        }
+
+        bool ok = map.add(p, k, v);
+        if (!ok)
+            return false;
+
+        if (!JS::GCPolicy<Key>::isTenured(k) || !JS::GCPolicy<Value>::isTenured(v)) {
+            if (!nurseryEntries.append(k)) {
+                map.remove(k);
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    void sweepAfterMinorGC(JSTracer* trc) {
+        for (auto& key : nurseryEntries) {
+            auto p = map.lookup(key);
+            if (!p)
+                continue;
+
+            // Drop the entry if the value is not marked.
+            if (JS::GCPolicy<BarrieredValue>::needsSweep(&p->value())) {
+                map.remove(key);
+                continue;
+            }
+
+            // Update and relocate the key, if the value is still needed.
+            //
+            // Note that this currently assumes that all Value will contain a
+            // strong reference to Key, as per its use as the
+            // CrossCompartmentWrapperMap. We may need to make the following
+            // behavior more dynamic if we use this map in other nursery-aware
+            // contexts.
+            Key copy(key);
+            mozilla::DebugOnly<bool> sweepKey = JS::GCPolicy<Key>::needsSweep(&copy);
+            MOZ_ASSERT(!sweepKey);
+            map.rekeyIfMoved(key, copy);
+        }
+        nurseryEntries.clear();
+    }
+
+    void sweep() {
+        MOZ_ASSERT(nurseryEntries.empty());
+        map.sweep();
+    }
+};
+
+} // namespace js
+
+namespace JS {
+template <typename T>
+struct GCPolicy<js::detail::UnsafeBareReadBarriered<T>>
+{
+    static void trace(JSTracer* trc, js::detail::UnsafeBareReadBarriered<T>* thingp,
+                      const char* name)
+    {
+        js::TraceEdge(trc, thingp, name);
+    }
+    static bool needsSweep(js::detail::UnsafeBareReadBarriered<T>* thingp) {
+        return js::gc::IsAboutToBeFinalized(thingp);
+    }
+};
+} // namespace JS
+
+#endif // gc_NurseryAwareHashMap_h
--- a/js/src/jsapi-tests/testGCMarking.cpp
+++ b/js/src/jsapi-tests/testGCMarking.cpp
@@ -1,21 +1,80 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jsapi.h"
+#include "jscompartment.h"
 
 #include "js/RootingAPI.h"
 #include "js/SliceBudget.h"
 #include "jsapi-tests/tests.h"
 
+static bool
+ConstructCCW(JSContext* cx, const JSClass* globalClasp,
+             JS::HandleObject global1, JS::MutableHandleObject wrapper,
+             JS::MutableHandleObject global2, JS::MutableHandleObject wrappee)
+{
+    if (!global1) {
+        fprintf(stderr, "null initial global");
+        return false;
+    }
+
+    // Define a second global in a different zone.
+    JS::CompartmentOptions options;
+    global2.set(JS_NewGlobalObject(cx, globalClasp, nullptr,
+                                   JS::FireOnNewGlobalHook, options));
+    if (!global2) {
+        fprintf(stderr, "failed to create second global");
+        return false;
+    }
+
+    // This should always be false, regardless.
+    if (global1->compartment() == global2->compartment()) {
+        fprintf(stderr, "second global claims to be in global1's compartment");
+        return false;
+    }
+
+    // This checks that the API obeys the implicit zone request.
+    if (global1->zone() == global2->zone()) {
+        fprintf(stderr, "global2 is in global1's zone");
+        return false;
+    }
+
+    // Define an object in compartment 2, that is wrapped by a CCW into compartment 1.
+    {
+        JSAutoCompartment ac(cx, global2);
+        wrappee.set(JS_NewPlainObject(cx));
+        if (wrappee->compartment() != global2->compartment()) {
+            fprintf(stderr, "wrappee in wrong compartment");
+            return false;
+        }
+    }
+
+    wrapper.set(wrappee);
+    if (!JS_WrapObject(cx, wrapper)) {
+        fprintf(stderr, "failed to wrap");
+        return false;
+    }
+    if (wrappee == wrapper) {
+        fprintf(stderr, "expected wrapping");
+        return false;
+    }
+    if (wrapper->compartment() != global1->compartment()) {
+        fprintf(stderr, "wrapper in wrong compartment");
+        return false;
+    }
+
+    return true;
+}
+
 class CCWTestTracer : public JS::CallbackTracer {
     void onChild(const JS::GCCellPtr& thing) override {
         numberOfThingsTraced++;
 
         printf("*thingp         = %p\n", thing.asCell());
         printf("*expectedThingp = %p\n", *expectedThingp);
 
         printf("kind         = %d\n", static_cast<int>(thing.kind()));
@@ -37,54 +96,191 @@ class CCWTestTracer : public JS::Callbac
         numberOfThingsTraced(0),
         expectedThingp(expectedThingp),
         expectedKind(expectedKind)
     { }
 };
 
 BEGIN_TEST(testTracingIncomingCCWs)
 {
-    // Get two globals, in two different zones.
+#ifdef JS_GC_ZEAL
+    // Disable zeal modes because this test needs to control exactly when the GC happens.
+    JS_SetGCZeal(cx, 0, 100);
+#endif
+    JS_GC(cx);
 
     JS::RootedObject global1(cx, JS::CurrentGlobalOrNull(cx));
-    CHECK(global1);
-    JS::CompartmentOptions options;
-    JS::RootedObject global2(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
-                                                    JS::FireOnNewGlobalHook, options));
-    CHECK(global2);
-    CHECK(global1->compartment() != global2->compartment());
+    JS::RootedObject wrapper(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject global2(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject wrappee(cx, JS::CurrentGlobalOrNull(cx));
+    CHECK(ConstructCCW(cx, getGlobalClass(), global1, &wrapper, &global2, &wrappee));
+    JS_GC(cx);
+    CHECK(!js::gc::IsInsideNursery(wrappee));
+    CHECK(!js::gc::IsInsideNursery(wrapper));
 
-    // Define an object in one compartment, that is wrapped by a CCW in another
-    // compartment.
-
-    JS::RootedObject obj(cx, JS_NewPlainObject(cx));
-    CHECK(obj->compartment() == global1->compartment());
-
-    JSAutoCompartment ac(cx, global2);
-    JS::RootedObject wrapper(cx, obj);
-    CHECK(JS_WrapObject(cx, &wrapper));
     JS::RootedValue v(cx, JS::ObjectValue(*wrapper));
-    CHECK(JS_SetProperty(cx, global2, "ccw", v));
+    CHECK(JS_SetProperty(cx, global1, "ccw", v));
 
     // Ensure that |TraceIncomingCCWs| finds the object wrapped by the CCW.
 
     JS::CompartmentSet compartments;
     CHECK(compartments.init());
-    CHECK(compartments.put(global1->compartment()));
+    CHECK(compartments.put(global2->compartment()));
 
-    void* thing = obj.get();
+    void* thing = wrappee.get();
     CCWTestTracer trc(cx, &thing, JS::TraceKind::Object);
     JS::TraceIncomingCCWs(&trc, compartments);
     CHECK(trc.numberOfThingsTraced == 1);
     CHECK(trc.okay);
 
     return true;
 }
 END_TEST(testTracingIncomingCCWs)
 
+static size_t
+countWrappers(JSCompartment* comp)
+{
+    size_t count = 0;
+    for (JSCompartment::WrapperEnum e(comp); !e.empty(); e.popFront())
+        ++count;
+    return count;
+}
+
+BEGIN_TEST(testDeadNurseryCCW)
+{
+#ifdef JS_GC_ZEAL
+    // Disable zeal modes because this test needs to control exactly when the GC happens.
+    JS_SetGCZeal(cx, 0, 100);
+#endif
+    JS_GC(cx);
+
+    JS::RootedObject global1(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject wrapper(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject global2(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject wrappee(cx, JS::CurrentGlobalOrNull(cx));
+    CHECK(ConstructCCW(cx, getGlobalClass(), global1, &wrapper, &global2, &wrappee));
+    CHECK(js::gc::IsInsideNursery(wrappee));
+    CHECK(js::gc::IsInsideNursery(wrapper));
+
+    // Now let the obj and wrapper die.
+    wrappee = wrapper = nullptr;
+
+    // Now a GC should clear the CCW.
+    CHECK(countWrappers(global1->compartment()) == 1);
+    cx->gc.evictNursery();
+    CHECK(countWrappers(global1->compartment()) == 0);
+
+    // Check for corruption of the CCW table by doing a full GC to force sweeping.
+    JS_GC(cx);
+
+    return true;
+}
+END_TEST(testDeadNurseryCCW)
+
+BEGIN_TEST(testLiveNurseryCCW)
+{
+#ifdef JS_GC_ZEAL
+    // Disable zeal modes because this test needs to control exactly when the GC happens.
+    JS_SetGCZeal(cx, 0, 100);
+#endif
+    JS_GC(cx);
+
+    JS::RootedObject global1(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject wrapper(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject global2(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject wrappee(cx, JS::CurrentGlobalOrNull(cx));
+    CHECK(ConstructCCW(cx, getGlobalClass(), global1, &wrapper, &global2, &wrappee));
+    CHECK(js::gc::IsInsideNursery(wrappee));
+    CHECK(js::gc::IsInsideNursery(wrapper));
+
+    // Now a GC should not kill the CCW.
+    CHECK(countWrappers(global1->compartment()) == 1);
+    cx->gc.evictNursery();
+    CHECK(countWrappers(global1->compartment()) == 1);
+
+    CHECK(!js::gc::IsInsideNursery(wrappee));
+    CHECK(!js::gc::IsInsideNursery(wrapper));
+
+    // Check for corruption of the CCW table by doing a full GC to force sweeping.
+    JS_GC(cx);
+
+    return true;
+}
+END_TEST(testLiveNurseryCCW)
+
+BEGIN_TEST(testLiveNurseryWrapperCCW)
+{
+#ifdef JS_GC_ZEAL
+    // Disable zeal modes because this test needs to control exactly when the GC happens.
+    JS_SetGCZeal(cx, 0, 100);
+#endif
+    JS_GC(cx);
+
+    JS::RootedObject global1(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject wrapper(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject global2(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject wrappee(cx, JS::CurrentGlobalOrNull(cx));
+    CHECK(ConstructCCW(cx, getGlobalClass(), global1, &wrapper, &global2, &wrappee));
+    CHECK(js::gc::IsInsideNursery(wrappee));
+    CHECK(js::gc::IsInsideNursery(wrapper));
+
+    // The wrapper contains a strong reference to the wrappee, so just dropping
+    // the reference to the wrappee will not drop the CCW table entry as long
+    // as the wrapper is held strongly. Thus, the minor collection here must
+    // tenure both the wrapper and the wrappee and keep both in the table.
+    wrappee = nullptr;
+
+    // Now a GC should not kill the CCW.
+    CHECK(countWrappers(global1->compartment()) == 1);
+    cx->gc.evictNursery();
+    CHECK(countWrappers(global1->compartment()) == 1);
+
+    CHECK(!js::gc::IsInsideNursery(wrapper));
+
+    // Check for corruption of the CCW table by doing a full GC to force sweeping.
+    JS_GC(cx);
+
+    return true;
+}
+END_TEST(testLiveNurseryWrapperCCW)
+
+BEGIN_TEST(testLiveNurseryWrappeeCCW)
+{
+#ifdef JS_GC_ZEAL
+    // Disable zeal modes because this test needs to control exactly when the GC happens.
+    JS_SetGCZeal(cx, 0, 100);
+#endif
+    JS_GC(cx);
+
+    JS::RootedObject global1(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject wrapper(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject global2(cx, JS::CurrentGlobalOrNull(cx));
+    JS::RootedObject wrappee(cx, JS::CurrentGlobalOrNull(cx));
+    CHECK(ConstructCCW(cx, getGlobalClass(), global1, &wrapper, &global2, &wrappee));
+    CHECK(js::gc::IsInsideNursery(wrappee));
+    CHECK(js::gc::IsInsideNursery(wrapper));
+
+    // Let the wrapper die. The wrapper should drop from the table when we GC,
+    // even though there are other non-cross-compartment edges to it.
+    wrapper = nullptr;
+
+    // Now a GC should not kill the CCW.
+    CHECK(countWrappers(global1->compartment()) == 1);
+    cx->gc.evictNursery();
+    CHECK(countWrappers(global1->compartment()) == 0);
+
+    CHECK(!js::gc::IsInsideNursery(wrappee));
+
+    // Check for corruption of the CCW table by doing a full GC to force sweeping.
+    JS_GC(cx);
+
+    return true;
+}
+END_TEST(testLiveNurseryWrappeeCCW)
+
 BEGIN_TEST(testIncrementalRoots)
 {
     JSRuntime* rt = cx->runtime();
 
 #ifdef JS_GC_ZEAL
     // Disable zeal modes because this test needs to control exactly when the GC happens.
     JS_SetGCZeal(cx, 0, 100);
 #endif
--- a/js/src/jsapi-tests/testWeakMap.cpp
+++ b/js/src/jsapi-tests/testWeakMap.cpp
@@ -186,16 +186,21 @@ JSObject* newCCW(JS::HandleObject source
         if (!object)
             return nullptr;
     }
     {
         JSAutoCompartment ac(cx, sourceZone);
         if (!JS_WrapObject(cx, &object))
             return nullptr;
     }
+
+    // In order to test the SCC algorithm, we need the wrapper/wrappee to be
+    // tenured.
+    cx->gc.evictNursery();
+
     return object;
 }
 
 JSObject* newDelegate()
 {
     static const js::ClassOps delegateClassOps = {
         nullptr, /* addProperty */
         nullptr, /* delProperty */
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -228,44 +228,24 @@ JSCompartment::checkWrapperMapAfterMovin
         e.front().mutableKey().applyToDebugger(CheckGCThingAfterMovingGCFunctor());
 
         WrapperMap::Ptr ptr = crossCompartmentWrappers.lookup(e.front().key());
         MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &e.front());
     }
 }
 #endif
 
-namespace {
-struct IsInsideNurseryFunctor {
-    template <class T> bool operator()(T tp) { return IsInsideNursery(*tp); }
-};
-} // namespace (anonymous)
-
 bool
 JSCompartment::putWrapper(JSContext* cx, const CrossCompartmentKey& wrapped,
                           const js::Value& wrapper)
 {
     MOZ_ASSERT(wrapped.is<JSString*>() == wrapper.isString());
     MOZ_ASSERT_IF(!wrapped.is<JSString*>(), wrapper.isObject());
 
-    /* There's no point allocating wrappers in the nursery since we will tenure them anyway. */
-    MOZ_ASSERT(!IsInsideNursery(static_cast<gc::Cell*>(wrapper.toGCThing())));
-
-    bool isNuseryKey =
-        const_cast<CrossCompartmentKey&>(wrapped).applyToWrapped(IsInsideNurseryFunctor()) ||
-        const_cast<CrossCompartmentKey&>(wrapped).applyToDebugger(IsInsideNurseryFunctor());
-
-    if (isNuseryKey && !nurseryCCKeys.append(wrapped)) {
-        ReportOutOfMemory(cx);
-        return false;
-    }
-
-    if (!crossCompartmentWrappers.put(wrapped, ReadBarriered<Value>(wrapper))) {
-        if (isNuseryKey)
-            nurseryCCKeys.popBack();
+    if (!crossCompartmentWrappers.put(wrapped, wrapper)) {
         ReportOutOfMemory(cx);
         return false;
     }
 
     return true;
 }
 
 static JSString*
@@ -601,26 +581,16 @@ JSCompartment::trace(JSTracer* trc)
 {
     savedStacks_.trace(trc);
 
     // Atoms are always tenured.
     if (!trc->runtime()->isHeapMinorCollecting())
         varNames_.trace(trc);
 }
 
-struct TraceFunctor {
-    JSTracer* trc_;
-    const char* name_;
-    TraceFunctor(JSTracer *trc, const char* name)
-      : trc_(trc), name_(name) {}
-    template <class T> void operator()(T* t) {
-        TraceManuallyBarrieredEdge(trc_, t, name_);
-    }
-};
-
 void
 JSCompartment::traceRoots(JSTracer* trc, js::gc::GCRuntime::TraceOrMarkRuntime traceOrMark)
 {
     if (objectMetadataState.is<PendingMetadata>()) {
         TraceRoot(trc,
                   &objectMetadataState.as<PendingMetadata>(),
                   "on-stack object pending metadata");
     }
@@ -682,28 +652,16 @@ JSCompartment::traceRoots(JSTracer* trc,
             TraceRoot(trc, &script, "profilingScripts");
             MOZ_ASSERT(script == r.front().key(), "const_cast is only a work-around");
         }
     }
 
     if (nonSyntacticLexicalEnvironments_)
         nonSyntacticLexicalEnvironments_->trace(trc);
 
-    // In a minor GC we need to mark nursery objects that are the targets of
-    // cross compartment wrappers.
-    if (trc->runtime()->isHeapMinorCollecting()) {
-        for (auto key : nurseryCCKeys) {
-            CrossCompartmentKey prior = key;
-            key.applyToWrapped(TraceFunctor(trc, "ccw wrapped"));
-            key.applyToDebugger(TraceFunctor(trc, "ccw debugger"));
-            crossCompartmentWrappers.rekeyIfMoved(prior, key);
-        }
-        nurseryCCKeys.clear();
-    }
-
     wasm.trace(trc);
 }
 
 void
 JSCompartment::finishRoots()
 {
     if (watchpointMap)
         watchpointMap->clear();
@@ -719,22 +677,24 @@ JSCompartment::finishRoots()
 
     clearScriptCounts();
 
     if (nonSyntacticLexicalEnvironments_)
         nonSyntacticLexicalEnvironments_->clear();
 }
 
 void
-JSCompartment::sweepAfterMinorGC()
+JSCompartment::sweepAfterMinorGC(JSTracer* trc)
 {
     globalWriteBarriered = 0;
 
     if (innerViews.needsSweepAfterMinorGC())
         innerViews.sweepAfterMinorGC();
+
+    crossCompartmentWrappers.sweepAfterMinorGC(trc);
 }
 
 void
 JSCompartment::sweepSavedStacks()
 {
     savedStacks_.sweep();
 }
 
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -12,16 +12,17 @@
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Tuple.h"
 #include "mozilla/Variant.h"
 #include "mozilla/XorShift128PlusRNG.h"
 
 #include "asmjs/WasmCompartment.h"
 #include "builtin/RegExp.h"
 #include "gc/Barrier.h"
+#include "gc/NurseryAwareHashMap.h"
 #include "gc/Zone.h"
 #include "vm/GlobalObject.h"
 #include "vm/PIC.h"
 #include "vm/SavedStacks.h"
 #include "vm/Time.h"
 
 namespace js {
 
@@ -63,17 +64,17 @@ class DtoaCache {
         this->s = s;
     }
 
 #ifdef JSGC_HASH_TABLE_CHECKS
     void checkCacheAfterMovingGC() { MOZ_ASSERT(!s || !IsForwarded(s)); }
 #endif
 };
 
-struct CrossCompartmentKey
+class CrossCompartmentKey
 {
   public:
     enum DebuggerObjectKind : uint8_t { DebuggerSource, DebuggerEnvironment, DebuggerObject,
                                         DebuggerWasmScript, DebuggerWasmSource };
     using DebuggerAndObject = mozilla::Tuple<NativeObject*, JSObject*, DebuggerObjectKind>;
     using DebuggerAndScript = mozilla::Tuple<NativeObject*, JSScript*>;
     using WrappedType = mozilla::Variant<
         JSObject*,
@@ -164,26 +165,38 @@ struct CrossCompartmentKey
         static HashNumber hash(const CrossCompartmentKey& key) {
             return key.wrapped.match(HashFunctor());
         }
 
         static bool match(const CrossCompartmentKey& l, const CrossCompartmentKey& k) {
             return l.wrapped == k.wrapped;
         }
     };
+
+    bool isTenured() const {
+        struct IsTenuredFunctor {
+            using ReturnType = bool;
+            ReturnType operator()(JSObject** tp) { return !IsInsideNursery(*tp); }
+            ReturnType operator()(JSScript** tp) { return true; }
+            ReturnType operator()(JSString** tp) { return true; }
+        };
+        return const_cast<CrossCompartmentKey*>(this)->applyToWrapped(IsTenuredFunctor());
+    }
+
     void trace(JSTracer* trc);
     bool needsSweep();
 
   private:
     CrossCompartmentKey() = delete;
     WrappedType wrapped;
 };
 
-using WrapperMap = GCRekeyableHashMap<CrossCompartmentKey, ReadBarrieredValue,
-                                      CrossCompartmentKey::Hasher, SystemAllocPolicy>;
+
+using WrapperMap = NurseryAwareHashMap<CrossCompartmentKey, JS::Value,
+                                       CrossCompartmentKey::Hasher, SystemAllocPolicy>;
 
 // We must ensure that all newly allocated JSObjects get their metadata
 // set. However, metadata builders may require the new object be in a sane
 // state (eg, have its reserved slots initialized so they can get the
 // sizeOfExcludingThis of the object). Therefore, for objects of certain
 // JSClasses (those marked with JSCLASS_DELAY_METADATA_BUILDER), it is not safe
 // for the allocation paths to call the object metadata builder
 // immediately. Instead, the JSClass-specific "constructor" C++ function up the
@@ -612,17 +625,17 @@ struct JSCompartment
      * when compacting to update cross-compartment pointers.
      */
     void traceOutgoingCrossCompartmentWrappers(JSTracer* trc);
     static void traceIncomingCrossCompartmentEdgesForZoneGC(JSTracer* trc);
 
     /* Whether to preserve JIT code on non-shrinking GCs. */
     bool preserveJitCode() { return creationOptions_.preserveJitCode(); }
 
-    void sweepAfterMinorGC();
+    void sweepAfterMinorGC(JSTracer* trc);
 
     void sweepCrossCompartmentWrappers();
     void sweepSavedStacks();
     void sweepGlobalObject(js::FreeOp* fop);
     void sweepSelfHostingScriptSource();
     void sweepJitCompartment(js::FreeOp* fop);
     void sweepRegExps();
     void sweepDebugEnvironments();
@@ -1061,9 +1074,16 @@ class MOZ_RAII AutoSuppressAllocationMet
 
     ~AutoSuppressAllocationMetadataBuilder() {
         zone->suppressAllocationMetadataBuilder = saved;
     }
 };
 
 } /* namespace js */
 
+namespace JS {
+template <>
+struct GCPolicy<js::CrossCompartmentKey> : public StructGCPolicy<js::CrossCompartmentKey> {
+    static bool isTenured(const js::CrossCompartmentKey& key) { return key.isTenured(); }
+};
+} // namespace JS
+
 #endif /* jscompartment_h */
--- a/js/src/jscompartmentinlines.h
+++ b/js/src/jscompartmentinlines.h
@@ -105,17 +105,17 @@ JSCompartment::wrap(JSContext* cx, JS::M
 #ifdef DEBUG
     JS::RootedObject cacheResult(cx);
 #endif
     JS::RootedValue v(cx, vp);
     if (js::WrapperMap::Ptr p = crossCompartmentWrappers.lookup(js::CrossCompartmentKey(v))) {
 #ifdef DEBUG
         cacheResult = &p->value().get().toObject();
 #else
-        vp.set(p->value());
+        vp.set(p->value().get());
         return true;
 #endif
     }
 
     JS::RootedObject obj(cx, &vp.toObject());
     if (!wrap(cx, &obj))
         return false;
     vp.setObject(*obj);
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -3670,17 +3670,17 @@ InCrossCompartmentMap(JSObject* src, JS:
     }
 
     /*
      * If the cross-compartment edge is caused by the debugger, then we don't
      * know the right hashtable key, so we have to iterate.
      */
     for (JSCompartment::WrapperEnum e(srccomp); !e.empty(); e.popFront()) {
         if (e.front().mutableKey().applyToWrapped(IsDestComparatorFunctor(dst)) &&
-            ToMarkable(e.front().value()) == src)
+            ToMarkable(e.front().value().unbarrieredGet()) == src)
         {
             return true;
         }
     }
 
     return false;
 }
 
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3686,18 +3686,19 @@ JSObject::allocKindForTenure(const js::N
      */
     if (is<TypedArrayObject>() && !as<TypedArrayObject>().hasBuffer()) {
         size_t nbytes = as<TypedArrayObject>().byteLength();
         if (nbytes >= TypedArrayObject::INLINE_BUFFER_LIMIT)
             return GetGCObjectKind(getClass());
         return GetBackgroundAllocKind(TypedArrayObject::AllocKindForLazyBuffer(nbytes));
     }
 
-    // Proxies have finalizers and are not nursery allocated.
-    MOZ_ASSERT(!IsProxy(this));
+    // Proxies that are CrossCompartmentWrappers may be nursery allocated.
+    if (IsProxy(this))
+        return as<ProxyObject>().allocKindForTenure();
 
     // Unboxed plain objects are sized according to the data they store.
     if (is<UnboxedPlainObject>()) {
         size_t nbytes = as<UnboxedPlainObject>().layoutDontCheckGeneration().size();
         return GetGCObjectKindForBytes(UnboxedPlainObject::offsetOfData() + nbytes);
     }
 
     // Unboxed arrays use inline data if their size is small enough.
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -1124,19 +1124,25 @@ extern const char js_lookupGetter_str[];
 extern const char js_lookupSetter_str[];
 #endif
 
 namespace js {
 
 inline gc::InitialHeap
 GetInitialHeap(NewObjectKind newKind, const Class* clasp)
 {
+    if (newKind == NurseryAllocatedProxy) {
+        MOZ_ASSERT(clasp->isProxy());
+        MOZ_ASSERT(clasp->hasFinalize());
+        MOZ_ASSERT(!CanNurseryAllocateFinalizedClass(clasp));
+        return gc::DefaultHeap;
+    }
     if (newKind != GenericObject)
         return gc::TenuredHeap;
-    if (clasp->hasFinalize() && !(clasp->flags & JSCLASS_SKIP_NURSERY_FINALIZE))
+    if (clasp->hasFinalize() && !CanNurseryAllocateFinalizedClass(clasp))
         return gc::TenuredHeap;
     return gc::DefaultHeap;
 }
 
 bool
 NewObjectWithTaggedProtoIsCachable(ExclusiveContext* cxArg, Handle<TaggedProto> proto,
                                    NewObjectKind newKind, const Class* clasp);
 
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -345,17 +345,18 @@ JSObject::create(js::ExclusiveContext* c
         MOZ_ASSERT(finalizeFlags == JSCLASS_FOREGROUND_FINALIZE ||
                    finalizeFlags == JSCLASS_BACKGROUND_FINALIZE);
         MOZ_ASSERT((finalizeFlags == JSCLASS_BACKGROUND_FINALIZE) == IsBackgroundFinalized(kind));
     } else {
         MOZ_ASSERT(finalizeFlags == 0);
     }
 
     MOZ_ASSERT_IF(clasp->hasFinalize(), heap == js::gc::TenuredHeap ||
-                                        (flags & JSCLASS_SKIP_NURSERY_FINALIZE));
+                                        CanNurseryAllocateFinalizedClass(clasp) ||
+                                        clasp->isProxy());
     MOZ_ASSERT_IF(group->hasUnanalyzedPreliminaryObjects(), heap == js::gc::TenuredHeap);
 #endif
 
     MOZ_ASSERT(!cx->compartment()->hasObjectPendingMetadata());
 
     // Non-native classes cannot have reserved slots or private data, and the
     // objects can't have any fixed slots, for compatibility with
     // GetReservedOrProxyPrivateSlot.
--- a/js/src/jswrapper.h
+++ b/js/src/jswrapper.h
@@ -210,16 +210,19 @@ class JS_FRIEND_API(CrossCompartmentWrap
     virtual bool hasInstance(JSContext* cx, HandleObject wrapper, MutableHandleValue v,
                              bool* bp) const override;
     virtual const char* className(JSContext* cx, HandleObject proxy) const override;
     virtual JSString* fun_toString(JSContext* cx, HandleObject wrapper,
                                    unsigned indent) const override;
     virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override;
     virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const override;
 
+    // Allocate CrossCompartmentWrappers in the nursery.
+    virtual bool canNurseryAllocate() const override { return true; }
+
     static const CrossCompartmentWrapper singleton;
     static const CrossCompartmentWrapper singletonWithPrototype;
 };
 
 class JS_FRIEND_API(OpaqueCrossCompartmentWrapper) : public CrossCompartmentWrapper
 {
   public:
     explicit constexpr OpaqueCrossCompartmentWrapper() : CrossCompartmentWrapper(0)
--- a/js/src/proxy/CrossCompartmentWrapper.cpp
+++ b/js/src/proxy/CrossCompartmentWrapper.cpp
@@ -468,16 +468,17 @@ js::IsCrossCompartmentWrapper(JSObject* 
 {
     return IsWrapper(obj) &&
            !!(Wrapper::wrapperHandler(obj)->flags() & Wrapper::CROSS_COMPARTMENT);
 }
 
 void
 js::NukeCrossCompartmentWrapper(JSContext* cx, JSObject* wrapper)
 {
+    MOZ_ASSERT(!IsInsideNursery(wrapper));
     MOZ_ASSERT(wrapper->is<CrossCompartmentWrapperObject>());
 
     NotifyGCNukeWrapper(wrapper);
 
     wrapper->as<ProxyObject>().nuke(&DeadObjectProxy::singleton);
 
     MOZ_ASSERT(IsDeadProxyObject(wrapper));
 }
@@ -494,16 +495,18 @@ JS_FRIEND_API(bool)
 js::NukeCrossCompartmentWrappers(JSContext* cx,
                                  const CompartmentFilter& sourceFilter,
                                  const CompartmentFilter& targetFilter,
                                  js::NukeReferencesToWindow nukeReferencesToWindow)
 {
     CHECK_REQUEST(cx);
     JSRuntime* rt = cx->runtime();
 
+    rt->gc.evictNursery(JS::gcreason::EVICT_NURSERY);
+
     // Iterate through scopes looking for system cross compartment wrappers
     // that point to an object that shares a global with obj.
 
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
         if (!sourceFilter.match(c))
             continue;
 
         // Iterate the wrappers looking for anything interesting.
@@ -537,16 +540,19 @@ js::NukeCrossCompartmentWrappers(JSConte
 // Given a cross-compartment wrapper |wobj|, update it to point to
 // |newTarget|. This recomputes the wrapper with JS_WrapValue, and thus can be
 // useful even if wrapper already points to newTarget.
 // This operation crashes on failure rather than leaving the heap in an
 // inconsistent state.
 void
 js::RemapWrapper(JSContext* cx, JSObject* wobjArg, JSObject* newTargetArg)
 {
+    MOZ_ASSERT(!IsInsideNursery(wobjArg));
+    MOZ_ASSERT(!IsInsideNursery(newTargetArg));
+
     RootedObject wobj(cx, wobjArg);
     RootedObject newTarget(cx, newTargetArg);
     MOZ_ASSERT(wobj->is<CrossCompartmentWrapperObject>());
     MOZ_ASSERT(!newTarget->is<CrossCompartmentWrapperObject>());
     JSObject* origTarget = Wrapper::wrappedObject(wobj);
     MOZ_ASSERT(origTarget);
     Value origv = ObjectValue(*origTarget);
     JSCompartment* wcompartment = wobj->compartment();
@@ -601,16 +607,19 @@ js::RemapWrapper(JSContext* cx, JSObject
 }
 
 // Remap all cross-compartment wrappers pointing to |oldTarget| to point to
 // |newTarget|. All wrappers are recomputed.
 JS_FRIEND_API(bool)
 js::RemapAllWrappersForObject(JSContext* cx, JSObject* oldTargetArg,
                               JSObject* newTargetArg)
 {
+    MOZ_ASSERT(!IsInsideNursery(oldTargetArg));
+    MOZ_ASSERT(!IsInsideNursery(newTargetArg));
+
     RootedValue origv(cx, ObjectValue(*oldTargetArg));
     RootedObject newTarget(cx, newTargetArg);
 
     AutoWrapperVector toTransplant(cx);
     if (!toTransplant.reserve(cx->runtime()->numCompartments))
         return false;
 
     for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
@@ -625,18 +634,20 @@ js::RemapAllWrappersForObject(JSContext*
 
     return true;
 }
 
 JS_FRIEND_API(bool)
 js::RecomputeWrappers(JSContext* cx, const CompartmentFilter& sourceFilter,
                       const CompartmentFilter& targetFilter)
 {
+    // Drop any nursery-allocated wrappers.
+    cx->runtime()->gc.evictNursery(JS::gcreason::EVICT_NURSERY);
+
     AutoWrapperVector toRecompute(cx);
-
     for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
         // Filter by source compartment.
         if (!sourceFilter.match(c))
             continue;
 
         // Iterate over the wrappers, filtering appropriately.
         for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) {
             // Filter out non-objects.
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -772,16 +772,17 @@ js::NewProxyObject(JSContext* cx, const 
     }
 
     return ProxyObject::New(cx, handler, priv, TaggedProto(proto_), options);
 }
 
 void
 ProxyObject::renew(JSContext* cx, const BaseProxyHandler* handler, Value priv)
 {
+    MOZ_ASSERT(!IsInsideNursery(this));
     MOZ_ASSERT_IF(IsCrossCompartmentWrapper(this), IsDeadProxyObject(this));
     MOZ_ASSERT(getClass() == &ProxyObject::proxyClass);
     MOZ_ASSERT(!IsWindowProxy(this));
     MOZ_ASSERT(hasDynamicPrototype());
 
     setHandler(handler);
     setCrossCompartmentPrivate(priv);
     setExtra(0, UndefinedValue());
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -6180,17 +6180,17 @@ Debugger::observesFrame(AbstractFramePtr
 
 bool
 Debugger::observesFrame(const FrameIter& iter) const
 {
     // Skip frames not yet fully initialized during their prologue.
     if (iter.isInterp() && iter.isFunctionFrame()) {
         const Value& thisVal = iter.interpFrame()->thisArgument();
         if (thisVal.isMagic() && thisVal.whyMagic() == JS_IS_CONSTRUCTING)
-            return false;;
+            return false;
     }
     if (iter.isWasm())
         return false;
     return observesScript(iter.script());
 }
 
 bool
 Debugger::observesScript(JSScript* script) const
@@ -9309,17 +9309,18 @@ DebuggerObject::initClass(JSContext* cx,
 
     return objectProto;
 }
 
 /* static */ DebuggerObject*
 DebuggerObject::create(JSContext* cx, HandleObject proto, HandleObject referent,
                        HandleNativeObject debugger)
 {
-  JSObject* obj = NewObjectWithGivenProto(cx, &DebuggerObject::class_, proto, TenuredObject);
+  NewObjectKind newKind = IsInsideNursery(referent) ? GenericObject : TenuredObject;
+  JSObject* obj = NewObjectWithGivenProto(cx, &DebuggerObject::class_, proto, newKind);
   if (!obj)
     return nullptr;
 
   DebuggerObject& object = obj->as<DebuggerObject>();
   object.setPrivateGCThing(referent);
   object.setReservedSlot(JSSLOT_DEBUGOBJECT_OWNER, ObjectValue(*debugger));
 
   return &object;
@@ -10448,18 +10449,18 @@ DebuggerEnvironment::initClass(JSContext
     return InitClass(cx, dbgCtor, objProto, &DebuggerEnvironment::class_, construct, 0,
                      properties_, methods_, nullptr, nullptr);
 }
 
 /* static */ DebuggerEnvironment*
 DebuggerEnvironment::create(JSContext* cx, HandleObject proto, HandleObject referent,
                             HandleNativeObject debugger)
 {
-    RootedObject obj(cx, NewObjectWithGivenProto(cx, &DebuggerEnvironment::class_, proto,
-                                                 TenuredObject));
+    NewObjectKind newKind = IsInsideNursery(referent) ? GenericObject : TenuredObject;
+    RootedObject obj(cx, NewObjectWithGivenProto(cx, &DebuggerEnvironment::class_, proto, newKind));
     if (!obj)
         return nullptr;
 
     DebuggerEnvironment& environment = obj->as<DebuggerEnvironment>();
     environment.setPrivateGCThing(referent);
     environment.setReservedSlot(OWNER_SLOT, ObjectValue(*debugger));
 
     return &environment;
--- a/js/src/vm/ObjectGroup.h
+++ b/js/src/vm/ObjectGroup.h
@@ -42,16 +42,22 @@ enum NewObjectKind {
     /*
      * Singleton objects are treated specially by the type system. This flag
      * ensures that the new object is automatically set up correctly as a
      * singleton and is allocated in the tenured heap.
      */
     SingletonObject,
 
     /*
+     * CrossCompartmentWrappers use the common Proxy class, but are allowed
+     * to have nursery lifetime.
+     */
+    NurseryAllocatedProxy,
+
+    /*
      * Objects which will not benefit from being allocated in the nursery
      * (e.g. because they are known to have a long lifetime) may be allocated
      * with this kind to place them immediately into the tenured generation.
      */
     TenuredObject
 };
 
 /*
--- a/js/src/vm/ProxyObject.cpp
+++ b/js/src/vm/ProxyObject.cpp
@@ -17,33 +17,45 @@ ProxyObject::New(JSContext* cx, const Ba
 {
     Rooted<TaggedProto> proto(cx, proto_);
 
     const Class* clasp = options.clasp();
 
     MOZ_ASSERT(isValidProxyClass(clasp));
     MOZ_ASSERT(clasp->shouldDelayMetadataBuilder());
     MOZ_ASSERT_IF(proto.isObject(), cx->compartment() == proto.toObject()->compartment());
+    MOZ_ASSERT(clasp->hasFinalize());
 
     /*
      * Eagerly mark properties unknown for proxies, so we don't try to track
      * their properties and so that we don't need to walk the compartment if
      * their prototype changes later.  But don't do this for DOM proxies,
      * because we want to be able to keep track of them in typesets in useful
      * ways.
      */
     if (proto.isObject() && !options.singleton() && !clasp->isDOMClass()) {
         RootedObject protoObj(cx, proto.toObject());
         if (!JSObject::setNewGroupUnknown(cx, clasp, protoObj))
             return nullptr;
     }
 
-    NewObjectKind newKind = options.singleton() ? SingletonObject : GenericObject;
+    // Ensure that the wrapper has the same lifetime assumptions as the
+    // wrappee. Prefer to allocate in the nursery, when possible.
+    NewObjectKind newKind = NurseryAllocatedProxy;
+    if (options.singleton()) {
+        MOZ_ASSERT(priv.isGCThing() && priv.toGCThing()->isTenured());
+        newKind = SingletonObject;
+    } else if ((priv.isGCThing() && priv.toGCThing()->isTenured()) ||
+               !handler->canNurseryAllocate() ||
+               !handler->finalizeInBackground(priv))
+    {
+        newKind = TenuredObject;
+    }
+
     gc::AllocKind allocKind = gc::GetGCObjectKind(clasp);
-
     if (handler->finalizeInBackground(priv))
         allocKind = GetBackgroundAllocKind(allocKind);
 
     AutoSetNewObjectMetadata metadata(cx);
     // Note: this will initialize the object's |data| to strange values, but we
     // will immediately overwrite those below.
     RootedObject obj(cx, NewObjectWithGivenTaggedProto(cx, clasp, proto, allocKind,
                                                        newKind));
@@ -57,16 +69,40 @@ ProxyObject::New(JSContext* cx, const Ba
 
     /* Don't track types of properties of non-DOM and non-singleton proxies. */
     if (newKind != SingletonObject && !clasp->isDOMClass())
         MarkObjectGroupUnknownProperties(cx, proxy->group());
 
     return proxy;
 }
 
+gc::AllocKind
+ProxyObject::allocKindForTenure() const
+{
+    gc::AllocKind allocKind = gc::GetGCObjectKind(group()->clasp());
+    if (data.handler->finalizeInBackground(const_cast<ProxyObject*>(this)->private_()))
+        allocKind = GetBackgroundAllocKind(allocKind);
+    return allocKind;
+}
+
+/* static */ size_t
+ProxyObject::objectMovedDuringMinorGC(TenuringTracer* trc, JSObject* dst, JSObject* src)
+{
+    ProxyObject& psrc = src->as<ProxyObject>();
+    ProxyObject& pdst = dst->as<ProxyObject>();
+
+    // We're about to sweep the nursery heap, so migrate the inline
+    // ProxyValueArray to the malloc heap if they were nursery allocated.
+    if (trc->runtime()->gc.nursery.isInside(psrc.data.values))
+        pdst.data.values = js_new<detail::ProxyValueArray>(*psrc.data.values);
+    else
+        trc->runtime()->gc.nursery.removeMallocedBuffer(psrc.data.values);
+    return sizeof(detail::ProxyValueArray);
+}
+
 void
 ProxyObject::setCrossCompartmentPrivate(const Value& priv)
 {
     *slotOfPrivate() = priv;
 }
 
 void
 ProxyObject::setSameCompartmentPrivate(const Value& priv)
--- a/js/src/vm/ProxyObject.h
+++ b/js/src/vm/ProxyObject.h
@@ -72,16 +72,19 @@ class ProxyObject : public ShapedObject
     const Value& extra(size_t n) const {
         return GetProxyExtra(const_cast<ProxyObject*>(this), n);
     }
 
     void setExtra(size_t n, const Value& extra) {
         SetProxyExtra(this, n, extra);
     }
 
+    gc::AllocKind allocKindForTenure() const;
+    static size_t objectMovedDuringMinorGC(TenuringTracer* trc, JSObject* dst, JSObject* src);
+
   private:
     GCPtrValue* slotOfExtra(size_t n) {
         MOZ_ASSERT(n < detail::PROXY_EXTRA_SLOTS);
         return reinterpret_cast<GCPtrValue*>(&detail::GetProxyDataLayout(this)->values->extraSlots[n]);
     }
 
     static bool isValidProxyClass(const Class* clasp) {
         // Since we can take classes from the outside, make sure that they