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