Bug 1233862 - Add a WeakCache builtin to automatically manage sweeping; r=sfink
authorTerrence Cole <terrence@mozilla.com>
Fri, 18 Dec 2015 14:50:20 -0800
changeset 293322 58c36d9ae2af72144b7dd995b4f2344f543c0f7d
parent 293321 b4b843abf463252b8177c12268b19c9ade761224
child 293323 b23a6286c125783b582eb59967ab5e574133478a
push id18749
push usercbook@mozilla.com
push dateFri, 15 Apr 2016 12:01:19 +0000
treeherderfx-team@8f7045b63b07 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfink
bugs1233862
milestone48.0a1
Bug 1233862 - Add a WeakCache builtin to automatically manage sweeping; r=sfink
js/public/GCHashTable.h
js/public/GCPolicyAPI.h
js/public/SweepingAPI.h
js/public/TracingAPI.h
js/src/gc/Tracer.h
js/src/gc/Zone.cpp
js/src/gc/Zone.h
js/src/jsapi-tests/moz.build
js/src/jsapi-tests/testGCWeakCache.cpp
js/src/jsgc.cpp
js/src/moz.build
js/src/vm/ObjectGroup.h
--- a/js/public/GCHashTable.h
+++ b/js/public/GCHashTable.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GCHashTable_h
 #define GCHashTable_h
 
 #include "js/GCPolicyAPI.h"
 #include "js/HashTable.h"
 #include "js/RootingAPI.h"
+#include "js/SweepingAPI.h"
 #include "js/TracingAPI.h"
 
 namespace js {
 
 // Define a reasonable default GC policy for GC-aware Maps.
 template <typename Key, typename Value>
 struct DefaultMapSweepPolicy {
     static bool needsSweep(Key* key, Value* value) {
@@ -209,16 +210,21 @@ class MutableHandleBase<GCHashMap<A,B,C,
   : public MutableGCHashMapOperations<JS::MutableHandle<GCHashMap<A,B,C,D,E>>, A,B,C,D,E>
 {};
 
 template <typename A, typename B, typename C, typename D, typename E>
 class HandleBase<GCHashMap<A,B,C,D,E>>
   : public GCHashMapOperations<JS::Handle<GCHashMap<A,B,C,D,E>>, A,B,C,D,E>
 {};
 
+template <typename A, typename B, typename C, typename D, typename E>
+class WeakCacheBase<GCHashMap<A,B,C,D,E>>
+  : public MutableGCHashMapOperations<JS::WeakCache<GCHashMap<A,B,C,D,E>>, A,B,C,D,E>
+{};
+
 // A GCHashSet is a HashSet with an additional trace method that knows
 // be traced to be kept alive will generally want to use this GCHashSet
 // specializeation in lieu of HashSet.
 //
 // Most types of GC pointers can be traced with no extra infrastructure. For
 // structs and non-gc-pointer members, ensure that there is a specialization of
 // GCPolicy<T> with an appropriate trace method available to handle the custom
 // type. Generic helpers can be found in js/public/TracingAPI.h.
@@ -272,17 +278,17 @@ class GCHashSetOperations
 {
     using Set = GCHashSet<Args...>;
     using Lookup = typename Set::Lookup;
     using Ptr = typename Set::Ptr;
     using AddPtr = typename Set::AddPtr;
     using Range = typename Set::Range;
     using Enum = typename Set::Enum;
 
-    const Set& set() const { return static_cast<const Outer*>(this)->extract(); }
+    const Set& set() const { return static_cast<const Outer*>(this)->get(); }
 
   public:
     bool initialized() const                   { return set().initialized(); }
     Ptr lookup(const Lookup& l) const          { return set().lookup(l); }
     AddPtr lookupForAdd(const Lookup& l) const { return set().lookupForAdd(l); }
     Range all() const                          { return set().all(); }
     bool empty() const                         { return set().empty(); }
     uint32_t count() const                     { return set().count(); }
@@ -296,17 +302,17 @@ class MutableGCHashSetOperations
 {
     using Set = GCHashSet<Args...>;
     using Lookup = typename Set::Lookup;
     using Ptr = typename Set::Ptr;
     using AddPtr = typename Set::AddPtr;
     using Range = typename Set::Range;
     using Enum = typename Set::Enum;
 
-    Set& set() { return static_cast<Outer*>(this)->extract(); }
+    Set& set() { return static_cast<Outer*>(this)->get(); }
 
   public:
     bool init(uint32_t len = 16) { return set().init(len); }
     void clear()                 { set().clear(); }
     void finish()                { set().finish(); }
     void remove(Ptr p)           { set().remove(p); }
     void remove(const Lookup& l) { set().remove(l); }
 
@@ -335,44 +341,31 @@ class MutableGCHashSetOperations
         return set().putNew(l, mozilla::Forward<TInput>(t));
     }
 };
 
 template <typename T, typename HP, typename AP>
 class RootedBase<GCHashSet<T, HP, AP>>
   : public MutableGCHashSetOperations<JS::Rooted<GCHashSet<T, HP, AP>>, T, HP, AP>
 {
-    using Set = GCHashSet<T, HP, AP>;
-
-    friend class GCHashSetOperations<JS::Rooted<Set>, T, HP, AP>;
-    const Set& extract() const { return *static_cast<const JS::Rooted<Set>*>(this)->address(); }
-
-    friend class MutableGCHashSetOperations<JS::Rooted<Set>, T, HP, AP>;
-    Set& extract() { return *static_cast<JS::Rooted<Set>*>(this)->address(); }
 };
 
 template <typename T, typename HP, typename AP>
 class MutableHandleBase<GCHashSet<T, HP, AP>>
   : public MutableGCHashSetOperations<JS::MutableHandle<GCHashSet<T, HP, AP>>, T, HP, AP>
 {
-    using Set = GCHashSet<T, HP, AP>;
-
-    friend class GCHashSetOperations<JS::MutableHandle<Set>, T, HP, AP>;
-    const Set& extract() const {
-        return *static_cast<const JS::MutableHandle<Set>*>(this)->address();
-    }
-
-    friend class MutableGCHashSetOperations<JS::MutableHandle<Set>, T, HP, AP>;
-    Set& extract() { return *static_cast<JS::MutableHandle<Set>*>(this)->address(); }
 };
 
 template <typename T, typename HP, typename AP>
 class HandleBase<GCHashSet<T, HP, AP>>
   : public GCHashSetOperations<JS::Handle<GCHashSet<T, HP, AP>>, T, HP, AP>
 {
-    using Set = GCHashSet<T, HP, AP>;
-    friend class GCHashSetOperations<JS::Handle<Set>, T, HP, AP>;
-    const Set& extract() const { return *static_cast<const JS::Handle<Set>*>(this)->address(); }
+};
+
+template <typename T, typename HP, typename AP>
+class WeakCacheBase<GCHashSet<T, HP, AP>>
+  : public MutableGCHashSetOperations<JS::WeakCache<GCHashSet<T, HP, AP>>, T, HP, AP>
+{
 };
 
 } /* namespace js */
 
 #endif /* GCHashTable_h */
--- a/js/public/GCPolicyAPI.h
+++ b/js/public/GCPolicyAPI.h
@@ -64,16 +64,20 @@ struct StructGCPolicy
     static T initial() {
         return T();
     }
 
     static void trace(JSTracer* trc, T* tp, const char* name) {
         tp->trace(trc);
     }
 
+    static void sweep(T* tp) {
+        return tp->sweep();
+    }
+
     static bool needsSweep(T* tp) {
         return tp->needsSweep();
     }
 };
 
 // The default GC policy attempts to defer to methods on the underlying type.
 // Most C++ structures that contain a default constructor, a trace function and
 // a sweep function will work out of the box with Rooted, Handle, GCVector,
@@ -93,19 +97,16 @@ template <> struct GCPolicy<uint64_t> : 
 template <typename T>
 struct GCPointerPolicy
 {
     static T initial() { return nullptr; }
     static void trace(JSTracer* trc, T* vp, const char* name) {
         if (*vp)
             js::UnsafeTraceManuallyBarrieredEdge(trc, vp, name);
     }
-    static bool needsSweep(T* vp) {
-        return js::gc::EdgeNeedsSweep(vp);
-    }
 };
 template <> struct GCPolicy<JS::Symbol*> : public GCPointerPolicy<JS::Symbol*> {};
 template <> struct GCPolicy<JSAtom*> : public GCPointerPolicy<JSAtom*> {};
 template <> struct GCPolicy<JSFunction*> : public GCPointerPolicy<JSFunction*> {};
 template <> struct GCPolicy<JSObject*> : public GCPointerPolicy<JSObject*> {};
 template <> struct GCPolicy<JSScript*> : public GCPointerPolicy<JSScript*> {};
 template <> struct GCPolicy<JSString*> : public GCPointerPolicy<JSString*> {};
 
new file mode 100644
--- /dev/null
+++ b/js/public/SweepingAPI.h
@@ -0,0 +1,62 @@
+/* -*- 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 js_SweepingAPI_h
+#define js_SweepingAPI_h
+
+#include "js/HeapAPI.h"
+
+namespace js {
+template <typename T>
+class WeakCacheBase {};
+} // namespace js
+
+namespace JS {
+template <typename T> class WeakCache;
+
+namespace shadow {
+JS_PUBLIC_API(void)
+RegisterWeakCache(JS::Zone* zone, JS::WeakCache<void*>* cachep);
+} // namespace shadow
+
+// A WeakCache stores the given Sweepable container and links itself into a
+// list of such caches that are swept during each GC.
+template <typename T>
+class WeakCache : public js::WeakCacheBase<T>,
+                  private mozilla::LinkedListElement<WeakCache<T>>
+{
+    friend class mozilla::LinkedListElement<WeakCache<T>>;
+    friend class mozilla::LinkedList<WeakCache<T>>;
+
+    WeakCache(const WeakCache&) = delete;
+
+    using SweepFn = void (*)(T*);
+    SweepFn sweeper;
+    T cache;
+
+  public:
+    template <typename U>
+    WeakCache(Zone* zone, U&& initial)
+      : cache(mozilla::Forward<U>(initial))
+    {
+        sweeper = js::GCPolicy<T>::sweep;
+        shadow::RegisterWeakCache(zone, reinterpret_cast<WeakCache<void*>*>(this));
+    }
+    WeakCache(WeakCache&& other)
+      : sweeper(other.sweeper),
+        cache(mozilla::Forward<T>(other.cache))
+    {
+    }
+
+    const T& get() const { return cache; }
+    T& get() { return cache; }
+
+    void sweep() { sweeper(&cache); }
+};
+
+} // namespace JS
+
+#endif // js_SweepingAPI_h
--- a/js/public/TracingAPI.h
+++ b/js/public/TracingAPI.h
@@ -342,15 +342,16 @@ namespace js {
 //
 // This method does not check if |*edgep| is non-null before tracing through
 // it, so callers must check any nullable pointer before calling this method.
 template <typename T>
 extern JS_PUBLIC_API(void)
 UnsafeTraceManuallyBarrieredEdge(JSTracer* trc, T* edgep, const char* name);
 
 namespace gc {
+// Return true if the given edge is not live and is about to be swept.
 template <typename T>
 extern JS_PUBLIC_API(bool)
 EdgeNeedsSweep(JS::Heap<T>* edgep);
 } // namespace gc
 } // namespace js
 
 #endif /* js_TracingAPI_h */
--- a/js/src/gc/Tracer.h
+++ b/js/src/gc/Tracer.h
@@ -5,17 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef js_Tracer_h
 #define js_Tracer_h
 
 #include "jsfriendapi.h"
 
 #include "gc/Barrier.h"
-#include "js/GCHashTable.h"
 
 namespace js {
 
 // Internal Tracing API
 //
 // Tracing is an abstract visitation of each edge in a JS heap graph.[1] The
 // most common (and performance sensitive) use of this infrastructure is for GC
 // "marking" as part of the mark-and-sweep collector; however, this
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -418,8 +418,14 @@ ZoneList::removeFront()
 }
 
 void
 ZoneList::clear()
 {
     while (!isEmpty())
         removeFront();
 }
+
+JS_PUBLIC_API(void)
+JS::shadow::RegisterWeakCache(JS::Zone* zone, WeakCache<void*>* cachep)
+{
+    zone->registerWeakCache(cachep);
+}
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -304,16 +304,22 @@ struct Zone : public JS::shadow::Zone,
     typedef js::Vector<js::gc::Cell*, 0, js::SystemAllocPolicy> GrayRootVector;
     GrayRootVector gcGrayRoots;
 
     // This zone's weak edges found via graph traversal during marking,
     // preserved for re-scanning during sweeping.
     using WeakEdges = js::Vector<js::gc::TenuredCell**, 0, js::SystemAllocPolicy>;
     WeakEdges gcWeakRefs;
 
+    // List of non-ephemeron weak containers to sweep during beginSweepingZoneGroup.
+    mozilla::LinkedList<WeakCache<void*>> weakCaches_;
+    void registerWeakCache(WeakCache<void*>* cachep) {
+        weakCaches_.insertBack(cachep);
+    }
+
     /*
      * Mapping from not yet marked keys to a vector of all values that the key
      * maps to in any live weak map.
      */
     js::gc::WeakKeyTable gcWeakKeys;
 
     // A set of edges from this zone to other zones.
     //
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -40,16 +40,17 @@ UNIFIED_SOURCES += [
     'testGCChunkPool.cpp',
     'testGCExactRooting.cpp',
     'testGCFinalizeCallback.cpp',
     'testGCHeapPostBarriers.cpp',
     'testGCMarking.cpp',
     'testGCOutOfMemory.cpp',
     'testGCStoreBufferRemoval.cpp',
     'testGCUniqueId.cpp',
+    'testGCWeakCache.cpp',
     'testGCWeakRef.cpp',
     'testGetPropertyDescriptor.cpp',
     'testHashTable.cpp',
     'testIndexToString.cpp',
     'testIntern.cpp',
     'testIntlAvailableLocales.cpp',
     'testIntString.cpp',
     'testIntTypesABI.cpp',
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testGCWeakCache.cpp
@@ -0,0 +1,92 @@
+/* -*- 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 "gc/Policy.h"
+#include "js/GCHashTable.h"
+#include "js/RootingAPI.h"
+#include "js/SweepingAPI.h"
+
+#include "jsapi-tests/tests.h"
+
+// Exercise WeakCache<GCHashSet>.
+BEGIN_TEST(testWeakCacheSet)
+{
+    // Create two objects tenured and two in the nursery. If zeal is on,
+    // this may fail and we'll get more tenured objects. That's fine:
+    // the test will continue to work, it will just not test as much.
+    JS::RootedObject tenured1(cx, JS_NewPlainObject(cx));
+    JS::RootedObject tenured2(cx, JS_NewPlainObject(cx));
+    JS_GC(rt);
+    JS::RootedObject nursery1(cx, JS_NewPlainObject(cx));
+    JS::RootedObject nursery2(cx, JS_NewPlainObject(cx));
+
+    using ObjectSet = js::GCHashSet<JS::Heap<JSObject*>, js::MovableCellHasher<JS::Heap<JSObject*>>>;
+    using Cache = JS::WeakCache<ObjectSet>;
+    auto cache = Cache(JS::GetObjectZone(tenured1), ObjectSet(cx));
+    CHECK(cache.init());
+
+    cache.put(tenured1);
+    cache.put(tenured2);
+    cache.put(nursery1);
+    cache.put(nursery2);
+
+    // Verify relocation and that we don't sweep too aggressively.
+    JS_GC(rt);
+    CHECK(cache.has(tenured1));
+    CHECK(cache.has(tenured2));
+    CHECK(cache.has(nursery1));
+    CHECK(cache.has(nursery2));
+
+    // Unroot two entries and verify that they get removed.
+    tenured2 = nursery2 = nullptr;
+    JS_GC(rt);
+    CHECK(cache.has(tenured1));
+    CHECK(cache.has(nursery1));
+    CHECK(cache.count() == 2);
+
+    return true;
+}
+END_TEST(testWeakCacheSet)
+
+// Exercise WeakCache<GCHashMap>.
+BEGIN_TEST(testWeakCacheMap)
+{
+    // Create two objects tenured and two in the nursery. If zeal is on,
+    // this may fail and we'll get more tenured objects. That's fine:
+    // the test will continue to work, it will just not test as much.
+    JS::RootedObject tenured1(cx, JS_NewPlainObject(cx));
+    JS::RootedObject tenured2(cx, JS_NewPlainObject(cx));
+    JS_GC(rt);
+    JS::RootedObject nursery1(cx, JS_NewPlainObject(cx));
+    JS::RootedObject nursery2(cx, JS_NewPlainObject(cx));
+
+    using ObjectMap = js::GCHashMap<JS::Heap<JSObject*>, uint32_t,
+                                    js::MovableCellHasher<JS::Heap<JSObject*>>>;
+    using Cache = JS::WeakCache<ObjectMap>;
+    auto cache = Cache(JS::GetObjectZone(tenured1), ObjectMap(cx));
+    CHECK(cache.init());
+
+    cache.put(tenured1, 1);
+    cache.put(tenured2, 2);
+    cache.put(nursery1, 3);
+    cache.put(nursery2, 4);
+
+    JS_GC(rt);
+    CHECK(cache.has(tenured1));
+    CHECK(cache.has(tenured2));
+    CHECK(cache.has(nursery1));
+    CHECK(cache.has(nursery2));
+
+    tenured2 = nursery2 = nullptr;
+    JS_GC(rt);
+    CHECK(cache.has(tenured1));
+    CHECK(cache.has(nursery1));
+    CHECK(cache.count() == 2);
+
+    return true;
+}
+END_TEST(testWeakCacheMap)
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2443,16 +2443,18 @@ GCRuntime::sweepTypesAfterCompacting(Zon
 void
 GCRuntime::sweepZoneAfterCompacting(Zone* zone)
 {
     MOZ_ASSERT(zone->isCollecting());
     FreeOp* fop = rt->defaultFreeOp();
     sweepTypesAfterCompacting(zone);
     zone->sweepBreakpoints(fop);
     zone->sweepWeakMaps();
+    for (auto* cache : zone->weakCaches_)
+        cache->sweep();
 
     for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) {
         c->sweepInnerViews();
         c->sweepBaseShapeTable();
         c->sweepInitialShapeTable();
         c->objectGroups.sweep(fop);
         c->sweepRegExps();
         c->sweepSavedStacks();
@@ -5155,16 +5157,19 @@ GCRuntime::beginSweepingZoneGroup()
         /* Clear all weakrefs that point to unmarked things. */
         for (auto edge : zone->gcWeakRefs) {
             /* Edges may be present multiple times, so may already be nulled. */
             if (*edge && IsAboutToBeFinalizedDuringSweep(**edge))
                 *edge = nullptr;
         }
         zone->gcWeakRefs.clear();
 
+        for (JS::WeakCache<void*>* cache : zone->weakCaches_)
+            cache->sweep();
+
         /* No need to look up any more weakmap keys from this zone group. */
         AutoEnterOOMUnsafeRegion oomUnsafe;
         if (!zone->gcWeakKeys.clear())
             oomUnsafe.crash("clearing weak keys in beginSweepingZoneGroup()");
     }
 
     FreeOp fop(rt);
     SweepAtomsTask sweepAtomsTask(rt);
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -28,17 +28,17 @@ with Files('gc/**'):
     BUG_COMPONENT = component_gc
 with Files('jit/**'):
     BUG_COMPONENT = component_jit
 
 # File-specific metadata
 for gcfile in ['jsgc*', 'devtools/rootAnalysis', 'devtools/gc-ubench', 'devtools/gctrace']:
     with Files(gcfile):
         BUG_COMPONENT = component_gc
-for header in ('GCAnnotations.h', 'GCAPI.h', 'HeapAPI.h', 'RootingAPI.h', 'SliceBudget.h', 'TraceKind.h', 'TracingAPI.h', 'WeakMapPtr.h'):
+for header in ('GCAnnotations.h', 'GCAPI.h', 'HeapAPI.h', 'RootingAPI.h', 'SliceBudget.h', 'SweepingAPI.h', 'TraceKind.h', 'TracingAPI.h', 'WeakMapPtr.h'):
     with Files('../public/' + header):
         BUG_COMPONENT = component_gc
 
 for stlfile in ['jsarray.*', 'jsbool*', 'jsdate.*', 'jsnum.*', 'json.*', 'jsstr.*']:
     with Files(stlfile):
         BUG_COMPONENT = component_stl
 
 with Files('builtin/Intl*'):
@@ -129,16 +129,17 @@ EXPORTS.js += [
     '../public/Principals.h',
     '../public/ProfilingFrameIterator.h',
     '../public/ProfilingStack.h',
     '../public/Proxy.h',
     '../public/RequiredDefines.h',
     '../public/RootingAPI.h',
     '../public/SliceBudget.h',
     '../public/StructuredClone.h',
+    '../public/SweepingAPI.h',
     '../public/TraceKind.h',
     '../public/TracingAPI.h',
     '../public/TrackedOptimizationInfo.h',
     '../public/TypeDecls.h',
     '../public/UbiNode.h',
     '../public/UbiNodeBreadthFirst.h',
     '../public/UbiNodeCensus.h',
     '../public/UbiNodeDominatorTree.h',
--- a/js/src/vm/ObjectGroup.h
+++ b/js/src/vm/ObjectGroup.h
@@ -7,16 +7,17 @@
 #ifndef vm_ObjectGroup_h
 #define vm_ObjectGroup_h
 
 #include "jsbytecode.h"
 #include "jsfriendapi.h"
 
 #include "ds/IdValuePair.h"
 #include "gc/Barrier.h"
+#include "js/GCHashTable.h"
 #include "vm/TaggedProto.h"
 #include "vm/TypeInference.h"
 
 namespace js {
 
 class TypeDescr;
 class UnboxedLayout;