Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
michael@0 | 2 | * vim: set ts=8 sts=4 et sw=4 tw=99: |
michael@0 | 3 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | #ifndef jsweakmap_h |
michael@0 | 8 | #define jsweakmap_h |
michael@0 | 9 | |
michael@0 | 10 | #include "jscompartment.h" |
michael@0 | 11 | #include "jsfriendapi.h" |
michael@0 | 12 | #include "jsobj.h" |
michael@0 | 13 | |
michael@0 | 14 | #include "gc/Marking.h" |
michael@0 | 15 | #include "js/HashTable.h" |
michael@0 | 16 | |
michael@0 | 17 | namespace js { |
michael@0 | 18 | |
michael@0 | 19 | // A subclass template of js::HashMap whose keys and values may be garbage-collected. When |
michael@0 | 20 | // a key is collected, the table entry disappears, dropping its reference to the value. |
michael@0 | 21 | // |
michael@0 | 22 | // More precisely: |
michael@0 | 23 | // |
michael@0 | 24 | // A WeakMap entry is collected if and only if either the WeakMap or the entry's key |
michael@0 | 25 | // is collected. If an entry is not collected, it remains in the WeakMap and it has a |
michael@0 | 26 | // strong reference to the value. |
michael@0 | 27 | // |
michael@0 | 28 | // You must call this table's 'trace' method when the object of which it is a part is |
michael@0 | 29 | // reached by the garbage collection tracer. Once a table is known to be live, the |
michael@0 | 30 | // implementation takes care of the iterative marking needed for weak tables and removing |
michael@0 | 31 | // table entries when collection is complete. |
michael@0 | 32 | |
michael@0 | 33 | // The value for the next pointer for maps not in the map list. |
michael@0 | 34 | static WeakMapBase * const WeakMapNotInList = reinterpret_cast<WeakMapBase *>(1); |
michael@0 | 35 | |
michael@0 | 36 | typedef Vector<WeakMapBase *, 0, SystemAllocPolicy> WeakMapVector; |
michael@0 | 37 | |
michael@0 | 38 | // Common base class for all WeakMap specializations. The collector uses this to call |
michael@0 | 39 | // their markIteratively and sweep methods. |
michael@0 | 40 | class WeakMapBase { |
michael@0 | 41 | public: |
michael@0 | 42 | WeakMapBase(JSObject *memOf, JSCompartment *c); |
michael@0 | 43 | virtual ~WeakMapBase(); |
michael@0 | 44 | |
michael@0 | 45 | void trace(JSTracer *tracer) { |
michael@0 | 46 | if (IS_GC_MARKING_TRACER(tracer)) { |
michael@0 | 47 | // We don't do anything with a WeakMap at trace time. Rather, we wait until as |
michael@0 | 48 | // many keys as possible have been marked, and add ourselves to the list of |
michael@0 | 49 | // known-live WeakMaps to be scanned in the iterative marking phase, by |
michael@0 | 50 | // markAllIteratively. |
michael@0 | 51 | JS_ASSERT(tracer->eagerlyTraceWeakMaps() == DoNotTraceWeakMaps); |
michael@0 | 52 | |
michael@0 | 53 | // Add ourselves to the list if we are not already in the list. We can already |
michael@0 | 54 | // be in the list if the weak map is marked more than once due delayed marking. |
michael@0 | 55 | if (next == WeakMapNotInList) { |
michael@0 | 56 | next = compartment->gcWeakMapList; |
michael@0 | 57 | compartment->gcWeakMapList = this; |
michael@0 | 58 | } |
michael@0 | 59 | } else { |
michael@0 | 60 | // If we're not actually doing garbage collection, the keys won't be marked |
michael@0 | 61 | // nicely as needed by the true ephemeral marking algorithm --- custom tracers |
michael@0 | 62 | // such as the cycle collector must use their own means for cycle detection. |
michael@0 | 63 | // So here we do a conservative approximation: pretend all keys are live. |
michael@0 | 64 | if (tracer->eagerlyTraceWeakMaps() == DoNotTraceWeakMaps) |
michael@0 | 65 | return; |
michael@0 | 66 | |
michael@0 | 67 | nonMarkingTraceValues(tracer); |
michael@0 | 68 | if (tracer->eagerlyTraceWeakMaps() == TraceWeakMapKeysValues) |
michael@0 | 69 | nonMarkingTraceKeys(tracer); |
michael@0 | 70 | } |
michael@0 | 71 | } |
michael@0 | 72 | |
michael@0 | 73 | // Garbage collector entry points. |
michael@0 | 74 | |
michael@0 | 75 | // Check all weak maps in a compartment that have been marked as live in this garbage |
michael@0 | 76 | // collection, and mark the values of all entries that have become strong references |
michael@0 | 77 | // to them. Return true if we marked any new values, indicating that we need to make |
michael@0 | 78 | // another pass. In other words, mark my marked maps' marked members' mid-collection. |
michael@0 | 79 | static bool markCompartmentIteratively(JSCompartment *c, JSTracer *tracer); |
michael@0 | 80 | |
michael@0 | 81 | // Remove entries whose keys are dead from all weak maps in a compartment marked as |
michael@0 | 82 | // live in this garbage collection. |
michael@0 | 83 | static void sweepCompartment(JSCompartment *c); |
michael@0 | 84 | |
michael@0 | 85 | // Trace all delayed weak map bindings. Used by the cycle collector. |
michael@0 | 86 | static void traceAllMappings(WeakMapTracer *tracer); |
michael@0 | 87 | |
michael@0 | 88 | bool isInList() { return next != WeakMapNotInList; } |
michael@0 | 89 | void check() { JS_ASSERT(!isInList()); } |
michael@0 | 90 | |
michael@0 | 91 | // Remove everything from the weak map list for a compartment. |
michael@0 | 92 | static void resetCompartmentWeakMapList(JSCompartment *c); |
michael@0 | 93 | |
michael@0 | 94 | // Save the live weak map list for a compartment, appending the data to a vector. |
michael@0 | 95 | static bool saveCompartmentWeakMapList(JSCompartment *c, WeakMapVector &vector); |
michael@0 | 96 | |
michael@0 | 97 | // Restore live weak map lists for multiple compartments from a vector. |
michael@0 | 98 | static void restoreCompartmentWeakMapLists(WeakMapVector &vector); |
michael@0 | 99 | |
michael@0 | 100 | // Remove a weakmap from the live weakmaps list |
michael@0 | 101 | static void removeWeakMapFromList(WeakMapBase *weakmap); |
michael@0 | 102 | |
michael@0 | 103 | protected: |
michael@0 | 104 | // Instance member functions called by the above. Instantiations of WeakMap override |
michael@0 | 105 | // these with definitions appropriate for their Key and Value types. |
michael@0 | 106 | virtual void nonMarkingTraceKeys(JSTracer *tracer) = 0; |
michael@0 | 107 | virtual void nonMarkingTraceValues(JSTracer *tracer) = 0; |
michael@0 | 108 | virtual bool markIteratively(JSTracer *tracer) = 0; |
michael@0 | 109 | virtual void sweep() = 0; |
michael@0 | 110 | virtual void traceMappings(WeakMapTracer *tracer) = 0; |
michael@0 | 111 | |
michael@0 | 112 | // Object that this weak map is part of, if any. |
michael@0 | 113 | JSObject *memberOf; |
michael@0 | 114 | |
michael@0 | 115 | // Compartment that this weak map is part of. |
michael@0 | 116 | JSCompartment *compartment; |
michael@0 | 117 | |
michael@0 | 118 | private: |
michael@0 | 119 | // Link in a list of WeakMaps to mark iteratively and sweep in this garbage |
michael@0 | 120 | // collection, headed by JSCompartment::gcWeakMapList. The last element of |
michael@0 | 121 | // the list has nullptr as its next. Maps not in the list have |
michael@0 | 122 | // WeakMapNotInList as their next. We must distinguish these cases to |
michael@0 | 123 | // avoid creating infinite lists when a weak map gets traced twice due to |
michael@0 | 124 | // delayed marking. |
michael@0 | 125 | WeakMapBase *next; |
michael@0 | 126 | }; |
michael@0 | 127 | |
michael@0 | 128 | template <class Key, class Value, |
michael@0 | 129 | class HashPolicy = DefaultHasher<Key> > |
michael@0 | 130 | class WeakMap : public HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy>, public WeakMapBase |
michael@0 | 131 | { |
michael@0 | 132 | public: |
michael@0 | 133 | typedef HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy> Base; |
michael@0 | 134 | typedef typename Base::Enum Enum; |
michael@0 | 135 | typedef typename Base::Lookup Lookup; |
michael@0 | 136 | typedef typename Base::Range Range; |
michael@0 | 137 | |
michael@0 | 138 | explicit WeakMap(JSContext *cx, JSObject *memOf = nullptr) |
michael@0 | 139 | : Base(cx->runtime()), WeakMapBase(memOf, cx->compartment()) { } |
michael@0 | 140 | |
michael@0 | 141 | private: |
michael@0 | 142 | bool markValue(JSTracer *trc, Value *x) { |
michael@0 | 143 | if (gc::IsMarked(x)) |
michael@0 | 144 | return false; |
michael@0 | 145 | gc::Mark(trc, x, "WeakMap entry value"); |
michael@0 | 146 | JS_ASSERT(gc::IsMarked(x)); |
michael@0 | 147 | return true; |
michael@0 | 148 | } |
michael@0 | 149 | |
michael@0 | 150 | void nonMarkingTraceKeys(JSTracer *trc) { |
michael@0 | 151 | for (Enum e(*this); !e.empty(); e.popFront()) { |
michael@0 | 152 | Key key(e.front().key()); |
michael@0 | 153 | gc::Mark(trc, &key, "WeakMap entry key"); |
michael@0 | 154 | if (key != e.front().key()) |
michael@0 | 155 | entryMoved(e, key); |
michael@0 | 156 | } |
michael@0 | 157 | } |
michael@0 | 158 | |
michael@0 | 159 | void nonMarkingTraceValues(JSTracer *trc) { |
michael@0 | 160 | for (Range r = Base::all(); !r.empty(); r.popFront()) |
michael@0 | 161 | gc::Mark(trc, &r.front().value(), "WeakMap entry value"); |
michael@0 | 162 | } |
michael@0 | 163 | |
michael@0 | 164 | bool keyNeedsMark(JSObject *key) { |
michael@0 | 165 | if (JSWeakmapKeyDelegateOp op = key->getClass()->ext.weakmapKeyDelegateOp) { |
michael@0 | 166 | JSObject *delegate = op(key); |
michael@0 | 167 | /* |
michael@0 | 168 | * Check if the delegate is marked with any color to properly handle |
michael@0 | 169 | * gray marking when the key's delegate is black and the map is |
michael@0 | 170 | * gray. |
michael@0 | 171 | */ |
michael@0 | 172 | return delegate && gc::IsObjectMarked(&delegate); |
michael@0 | 173 | } |
michael@0 | 174 | return false; |
michael@0 | 175 | } |
michael@0 | 176 | |
michael@0 | 177 | bool keyNeedsMark(gc::Cell *cell) { |
michael@0 | 178 | return false; |
michael@0 | 179 | } |
michael@0 | 180 | |
michael@0 | 181 | bool markIteratively(JSTracer *trc) { |
michael@0 | 182 | bool markedAny = false; |
michael@0 | 183 | for (Enum e(*this); !e.empty(); e.popFront()) { |
michael@0 | 184 | /* If the entry is live, ensure its key and value are marked. */ |
michael@0 | 185 | Key key(e.front().key()); |
michael@0 | 186 | if (gc::IsMarked(const_cast<Key *>(&key))) { |
michael@0 | 187 | if (markValue(trc, &e.front().value())) |
michael@0 | 188 | markedAny = true; |
michael@0 | 189 | if (e.front().key() != key) |
michael@0 | 190 | entryMoved(e, key); |
michael@0 | 191 | } else if (keyNeedsMark(key)) { |
michael@0 | 192 | gc::Mark(trc, &e.front().value(), "WeakMap entry value"); |
michael@0 | 193 | gc::Mark(trc, &key, "proxy-preserved WeakMap entry key"); |
michael@0 | 194 | if (e.front().key() != key) |
michael@0 | 195 | entryMoved(e, key); |
michael@0 | 196 | markedAny = true; |
michael@0 | 197 | } |
michael@0 | 198 | key.unsafeSet(nullptr); |
michael@0 | 199 | } |
michael@0 | 200 | return markedAny; |
michael@0 | 201 | } |
michael@0 | 202 | |
michael@0 | 203 | void sweep() { |
michael@0 | 204 | /* Remove all entries whose keys remain unmarked. */ |
michael@0 | 205 | for (Enum e(*this); !e.empty(); e.popFront()) { |
michael@0 | 206 | Key k(e.front().key()); |
michael@0 | 207 | if (gc::IsAboutToBeFinalized(&k)) |
michael@0 | 208 | e.removeFront(); |
michael@0 | 209 | else if (k != e.front().key()) |
michael@0 | 210 | entryMoved(e, k); |
michael@0 | 211 | } |
michael@0 | 212 | /* |
michael@0 | 213 | * Once we've swept, all remaining edges should stay within the |
michael@0 | 214 | * known-live part of the graph. |
michael@0 | 215 | */ |
michael@0 | 216 | assertEntriesNotAboutToBeFinalized(); |
michael@0 | 217 | } |
michael@0 | 218 | |
michael@0 | 219 | /* memberOf can be nullptr, which means that the map is not part of a JSObject. */ |
michael@0 | 220 | void traceMappings(WeakMapTracer *tracer) { |
michael@0 | 221 | for (Range r = Base::all(); !r.empty(); r.popFront()) { |
michael@0 | 222 | gc::Cell *key = gc::ToMarkable(r.front().key()); |
michael@0 | 223 | gc::Cell *value = gc::ToMarkable(r.front().value()); |
michael@0 | 224 | if (key && value) { |
michael@0 | 225 | tracer->callback(tracer, memberOf, |
michael@0 | 226 | key, gc::TraceKind(r.front().key()), |
michael@0 | 227 | value, gc::TraceKind(r.front().value())); |
michael@0 | 228 | } |
michael@0 | 229 | } |
michael@0 | 230 | } |
michael@0 | 231 | |
michael@0 | 232 | /* Rekey an entry when moved, ensuring we do not trigger barriers. */ |
michael@0 | 233 | void entryMoved(Enum &eArg, const Key &k) { |
michael@0 | 234 | typedef typename HashMap<typename Unbarriered<Key>::type, |
michael@0 | 235 | typename Unbarriered<Value>::type, |
michael@0 | 236 | typename Unbarriered<HashPolicy>::type, |
michael@0 | 237 | RuntimeAllocPolicy>::Enum UnbarrieredEnum; |
michael@0 | 238 | UnbarrieredEnum &e = reinterpret_cast<UnbarrieredEnum &>(eArg); |
michael@0 | 239 | e.rekeyFront(reinterpret_cast<const typename Unbarriered<Key>::type &>(k)); |
michael@0 | 240 | } |
michael@0 | 241 | |
michael@0 | 242 | protected: |
michael@0 | 243 | void assertEntriesNotAboutToBeFinalized() { |
michael@0 | 244 | #if DEBUG |
michael@0 | 245 | for (Range r = Base::all(); !r.empty(); r.popFront()) { |
michael@0 | 246 | Key k(r.front().key()); |
michael@0 | 247 | JS_ASSERT(!gc::IsAboutToBeFinalized(&k)); |
michael@0 | 248 | JS_ASSERT(!gc::IsAboutToBeFinalized(&r.front().value())); |
michael@0 | 249 | JS_ASSERT(k == r.front().key()); |
michael@0 | 250 | } |
michael@0 | 251 | #endif |
michael@0 | 252 | } |
michael@0 | 253 | }; |
michael@0 | 254 | |
michael@0 | 255 | } /* namespace js */ |
michael@0 | 256 | |
michael@0 | 257 | extern JSObject * |
michael@0 | 258 | js_InitWeakMapClass(JSContext *cx, js::HandleObject obj); |
michael@0 | 259 | |
michael@0 | 260 | #endif /* jsweakmap_h */ |