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 331164 58c36d9ae2af72144b7dd995b4f2344f543c0f7d
parent 331163 b4b843abf463252b8177c12268b19c9ade761224
child 331165 b23a6286c125783b582eb59967ab5e574133478a
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfink
bugs1233862
milestone48.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 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;