Bug 1293127 - Mark CCW keys that have nursery pointers explicitly rather than using the generic buffer r=terrence
authorJon Coppeard <jcoppeard@mozilla.com>
Fri, 19 Aug 2016 16:56:25 +0100
changeset 310339 520e4b9d3ed0990d85665e82027c752516a482c2
parent 310338 c330ce7c763ced88a84cd36d315984de8dff7b06
child 310340 32b100ffa607020d78092a1c0c50b574cc5cec0d
push id80814
push userjcoppeard@mozilla.com
push dateFri, 19 Aug 2016 16:02:01 +0000
treeherdermozilla-inbound@520e4b9d3ed0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersterrence
bugs1293127
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 1293127 - Mark CCW keys that have nursery pointers explicitly rather than using the generic buffer r=terrence
js/src/jit-test/tests/gc/bug-1293127.js
js/src/jscompartment.cpp
js/src/jscompartment.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/gc/bug-1293127.js
@@ -0,0 +1,10 @@
+// Test that we can create 1000 cross compartment wrappers to nursery objects
+// without trigger a minor GC.
+let g = newGlobal();
+evalcx("function f(x) { return {x: x}; }", g);
+gc();
+let initial = gcparam("gcNumber");
+for (let i = 0; i < 1000; i++)
+    g.f(i);
+let final = gcparam("gcNumber");
+assertEq(initial, final);
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -204,54 +204,16 @@ JSCompartment::ensureJitCompartmentExist
         js_delete(jitCompartment_);
         jitCompartment_ = nullptr;
         return false;
     }
 
     return true;
 }
 
-/*
- * This class is used to add a post barrier on the crossCompartmentWrappers map,
- * as the key is calculated based on objects which may be moved by generational
- * GC.
- */
-class WrapperMapRef : public BufferableRef
-{
-    WrapperMap* map;
-    CrossCompartmentKey key;
-
-  public:
-    WrapperMapRef(WrapperMap* map, const CrossCompartmentKey& key)
-      : map(map), key(key) {}
-
-    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 trace(JSTracer* trc) override {
-        CrossCompartmentKey prior = key;
-        key.applyToWrapped(TraceFunctor(trc, "ccw wrapped"));
-        key.applyToDebugger(TraceFunctor(trc, "ccw debugger"));
-        if (key == prior)
-            return;
-
-        /* Look for the original entry, which might have been removed. */
-        WrapperMap::Ptr p = map->lookup(prior);
-        if (!p)
-            return;
-
-        /* Rekey the entry. */
-        map->rekeyAs(prior, key, key);
-    }
-};
-
 #ifdef JSGC_HASH_TABLE_CHECKS
 namespace {
 struct CheckGCThingAfterMovingGCFunctor {
     template <class T> void operator()(T* t) { CheckGCThingAfterMovingGC(*t); }
 };
 } // namespace (anonymous)
 
 void
@@ -283,26 +245,30 @@ JSCompartment::putWrapper(JSContext* cx,
                           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())));
 
-    if (!crossCompartmentWrappers.put(wrapped, ReadBarriered<Value>(wrapper))) {
+    bool isNuseryKey =
+        const_cast<CrossCompartmentKey&>(wrapped).applyToWrapped(IsInsideNurseryFunctor()) ||
+        const_cast<CrossCompartmentKey&>(wrapped).applyToDebugger(IsInsideNurseryFunctor());
+
+    if (isNuseryKey && !nurseryCCKeys.append(wrapped)) {
         ReportOutOfMemory(cx);
         return false;
     }
 
-    if (const_cast<CrossCompartmentKey&>(wrapped).applyToWrapped(IsInsideNurseryFunctor()) ||
-        const_cast<CrossCompartmentKey&>(wrapped).applyToDebugger(IsInsideNurseryFunctor()))
-    {
-        WrapperMapRef ref(&crossCompartmentWrappers, wrapped);
-        cx->runtime()->gc.storeBuffer.putGeneric(ref);
+    if (!crossCompartmentWrappers.put(wrapped, ReadBarriered<Value>(wrapper))) {
+        if (isNuseryKey)
+            nurseryCCKeys.popBack();
+        ReportOutOfMemory(cx);
+        return false;
     }
 
     return true;
 }
 
 static JSString*
 CopyStringPure(JSContext* cx, JSString* str)
 {
@@ -623,16 +589,26 @@ JSCompartment::traceIncomingCrossCompart
 }
 
 void
 JSCompartment::trace(JSTracer* trc)
 {
     savedStacks_.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");
     }
@@ -694,16 +670,28 @@ JSCompartment::traceRoots(JSTracer* trc,
             TraceRoot(trc, &script, "profilingScripts");
             MOZ_ASSERT(script == r.front().key(), "const_cast is only a work-around");
         }
     }
 
     if (nonSyntacticLexicalScopes_)
         nonSyntacticLexicalScopes_->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::sweepAfterMinorGC()
 {
     globalWriteBarriered = 0;
 
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -410,16 +410,19 @@ struct JSCompartment
 
   private:
     const js::AllocationMetadataBuilder *allocationMetadataBuilder;
 
     js::SavedStacks              savedStacks_;
 
     js::WrapperMap               crossCompartmentWrappers;
 
+    using CCKeyVector = mozilla::Vector<js::CrossCompartmentKey, 0, js::SystemAllocPolicy>;
+    CCKeyVector                  nurseryCCKeys;
+
   public:
     /* Last time at which an animation was played for a global in this compartment. */
     int64_t                      lastAnimationTime;
 
     js::RegExpCompartment        regExps;
 
     /*
      * For generational GC, record whether a write barrier has added this