|
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 js_Tracer_h |
|
8 #define js_Tracer_h |
|
9 |
|
10 #include "mozilla/DebugOnly.h" |
|
11 |
|
12 #include "js/GCAPI.h" |
|
13 #include "js/SliceBudget.h" |
|
14 #include "js/TracingAPI.h" |
|
15 |
|
16 namespace js { |
|
17 class GCMarker; |
|
18 class ObjectImpl; |
|
19 namespace gc { |
|
20 class ArenaHeader; |
|
21 } |
|
22 namespace jit { |
|
23 class JitCode; |
|
24 } |
|
25 namespace types { |
|
26 class TypeObject; |
|
27 } |
|
28 |
|
29 static const size_t NON_INCREMENTAL_MARK_STACK_BASE_CAPACITY = 4096; |
|
30 static const size_t INCREMENTAL_MARK_STACK_BASE_CAPACITY = 32768; |
|
31 |
|
32 /* |
|
33 * When the native stack is low, the GC does not call JS_TraceChildren to mark |
|
34 * the reachable "children" of the thing. Rather the thing is put aside and |
|
35 * JS_TraceChildren is called later with more space on the C stack. |
|
36 * |
|
37 * To implement such delayed marking of the children with minimal overhead for |
|
38 * the normal case of sufficient native stack, the code adds a field per arena. |
|
39 * The field markingDelay->link links all arenas with delayed things into a |
|
40 * stack list with the pointer to stack top in GCMarker::unmarkedArenaStackTop. |
|
41 * GCMarker::delayMarkingChildren adds arenas to the stack as necessary while |
|
42 * markDelayedChildren pops the arenas from the stack until it empties. |
|
43 */ |
|
44 class MarkStack |
|
45 { |
|
46 friend class GCMarker; |
|
47 |
|
48 uintptr_t *stack_; |
|
49 uintptr_t *tos_; |
|
50 uintptr_t *end_; |
|
51 |
|
52 // The capacity we start with and reset() to. |
|
53 size_t baseCapacity_; |
|
54 size_t maxCapacity_; |
|
55 |
|
56 public: |
|
57 MarkStack(size_t maxCapacity) |
|
58 : stack_(nullptr), |
|
59 tos_(nullptr), |
|
60 end_(nullptr), |
|
61 baseCapacity_(0), |
|
62 maxCapacity_(maxCapacity) |
|
63 {} |
|
64 |
|
65 ~MarkStack() { |
|
66 js_free(stack_); |
|
67 } |
|
68 |
|
69 size_t capacity() { return end_ - stack_; } |
|
70 |
|
71 ptrdiff_t position() const { return tos_ - stack_; } |
|
72 |
|
73 void setStack(uintptr_t *stack, size_t tosIndex, size_t capacity) { |
|
74 stack_ = stack; |
|
75 tos_ = stack + tosIndex; |
|
76 end_ = stack + capacity; |
|
77 } |
|
78 |
|
79 bool init(JSGCMode gcMode); |
|
80 |
|
81 void setBaseCapacity(JSGCMode mode); |
|
82 size_t maxCapacity() const { return maxCapacity_; } |
|
83 void setMaxCapacity(size_t maxCapacity); |
|
84 |
|
85 bool push(uintptr_t item) { |
|
86 if (tos_ == end_) { |
|
87 if (!enlarge(1)) |
|
88 return false; |
|
89 } |
|
90 JS_ASSERT(tos_ < end_); |
|
91 *tos_++ = item; |
|
92 return true; |
|
93 } |
|
94 |
|
95 bool push(uintptr_t item1, uintptr_t item2, uintptr_t item3) { |
|
96 uintptr_t *nextTos = tos_ + 3; |
|
97 if (nextTos > end_) { |
|
98 if (!enlarge(3)) |
|
99 return false; |
|
100 nextTos = tos_ + 3; |
|
101 } |
|
102 JS_ASSERT(nextTos <= end_); |
|
103 tos_[0] = item1; |
|
104 tos_[1] = item2; |
|
105 tos_[2] = item3; |
|
106 tos_ = nextTos; |
|
107 return true; |
|
108 } |
|
109 |
|
110 bool isEmpty() const { |
|
111 return tos_ == stack_; |
|
112 } |
|
113 |
|
114 uintptr_t pop() { |
|
115 JS_ASSERT(!isEmpty()); |
|
116 return *--tos_; |
|
117 } |
|
118 |
|
119 void reset(); |
|
120 |
|
121 /* Grow the stack, ensuring there is space for at least count elements. */ |
|
122 bool enlarge(unsigned count); |
|
123 |
|
124 void setGCMode(JSGCMode gcMode); |
|
125 |
|
126 size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; |
|
127 }; |
|
128 |
|
129 class GCMarker : public JSTracer |
|
130 { |
|
131 public: |
|
132 explicit GCMarker(JSRuntime *rt); |
|
133 bool init(JSGCMode gcMode); |
|
134 |
|
135 void setMaxCapacity(size_t maxCap) { stack.setMaxCapacity(maxCap); } |
|
136 size_t maxCapacity() const { return stack.maxCapacity(); } |
|
137 |
|
138 void start(); |
|
139 void stop(); |
|
140 void reset(); |
|
141 |
|
142 void pushObject(ObjectImpl *obj) { |
|
143 pushTaggedPtr(ObjectTag, obj); |
|
144 } |
|
145 |
|
146 void pushType(types::TypeObject *type) { |
|
147 pushTaggedPtr(TypeTag, type); |
|
148 } |
|
149 |
|
150 void pushJitCode(jit::JitCode *code) { |
|
151 pushTaggedPtr(JitCodeTag, code); |
|
152 } |
|
153 |
|
154 uint32_t getMarkColor() const { |
|
155 return color; |
|
156 } |
|
157 |
|
158 /* |
|
159 * Care must be taken changing the mark color from gray to black. The cycle |
|
160 * collector depends on the invariant that there are no black to gray edges |
|
161 * in the GC heap. This invariant lets the CC not trace through black |
|
162 * objects. If this invariant is violated, the cycle collector may free |
|
163 * objects that are still reachable. |
|
164 */ |
|
165 void setMarkColorGray() { |
|
166 JS_ASSERT(isDrained()); |
|
167 JS_ASSERT(color == gc::BLACK); |
|
168 color = gc::GRAY; |
|
169 } |
|
170 |
|
171 void setMarkColorBlack() { |
|
172 JS_ASSERT(isDrained()); |
|
173 JS_ASSERT(color == gc::GRAY); |
|
174 color = gc::BLACK; |
|
175 } |
|
176 |
|
177 inline void delayMarkingArena(gc::ArenaHeader *aheader); |
|
178 void delayMarkingChildren(const void *thing); |
|
179 void markDelayedChildren(gc::ArenaHeader *aheader); |
|
180 bool markDelayedChildren(SliceBudget &budget); |
|
181 bool hasDelayedChildren() const { |
|
182 return !!unmarkedArenaStackTop; |
|
183 } |
|
184 |
|
185 bool isDrained() { |
|
186 return isMarkStackEmpty() && !unmarkedArenaStackTop; |
|
187 } |
|
188 |
|
189 bool drainMarkStack(SliceBudget &budget); |
|
190 |
|
191 /* |
|
192 * Gray marking must be done after all black marking is complete. However, |
|
193 * we do not have write barriers on XPConnect roots. Therefore, XPConnect |
|
194 * roots must be accumulated in the first slice of incremental GC. We |
|
195 * accumulate these roots in the each compartment's gcGrayRoots vector and |
|
196 * then mark them later, after black marking is complete for each |
|
197 * compartment. This accumulation can fail, but in that case we switch to |
|
198 * non-incremental GC. |
|
199 */ |
|
200 bool hasBufferedGrayRoots() const; |
|
201 void startBufferingGrayRoots(); |
|
202 void endBufferingGrayRoots(); |
|
203 void resetBufferedGrayRoots(); |
|
204 void markBufferedGrayRoots(JS::Zone *zone); |
|
205 |
|
206 static void GrayCallback(JSTracer *trc, void **thing, JSGCTraceKind kind); |
|
207 |
|
208 void setGCMode(JSGCMode mode) { stack.setGCMode(mode); } |
|
209 |
|
210 size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; |
|
211 |
|
212 /* This is public exclusively for ScanRope. */ |
|
213 MarkStack stack; |
|
214 |
|
215 private: |
|
216 #ifdef DEBUG |
|
217 void checkZone(void *p); |
|
218 #else |
|
219 void checkZone(void *p) {} |
|
220 #endif |
|
221 |
|
222 /* |
|
223 * We use a common mark stack to mark GC things of different types and use |
|
224 * the explicit tags to distinguish them when it cannot be deduced from |
|
225 * the context of push or pop operation. |
|
226 */ |
|
227 enum StackTag { |
|
228 ValueArrayTag, |
|
229 ObjectTag, |
|
230 TypeTag, |
|
231 XmlTag, |
|
232 SavedValueArrayTag, |
|
233 JitCodeTag, |
|
234 LastTag = JitCodeTag |
|
235 }; |
|
236 |
|
237 static const uintptr_t StackTagMask = 7; |
|
238 static_assert(StackTagMask >= uintptr_t(LastTag), "The tag mask must subsume the tags."); |
|
239 static_assert(StackTagMask <= gc::CellMask, "The tag mask must be embeddable in a Cell*."); |
|
240 |
|
241 void pushTaggedPtr(StackTag tag, void *ptr) { |
|
242 checkZone(ptr); |
|
243 uintptr_t addr = reinterpret_cast<uintptr_t>(ptr); |
|
244 JS_ASSERT(!(addr & StackTagMask)); |
|
245 if (!stack.push(addr | uintptr_t(tag))) |
|
246 delayMarkingChildren(ptr); |
|
247 } |
|
248 |
|
249 void pushValueArray(JSObject *obj, void *start, void *end) { |
|
250 checkZone(obj); |
|
251 |
|
252 JS_ASSERT(start <= end); |
|
253 uintptr_t tagged = reinterpret_cast<uintptr_t>(obj) | GCMarker::ValueArrayTag; |
|
254 uintptr_t startAddr = reinterpret_cast<uintptr_t>(start); |
|
255 uintptr_t endAddr = reinterpret_cast<uintptr_t>(end); |
|
256 |
|
257 /* |
|
258 * Push in the reverse order so obj will be on top. If we cannot push |
|
259 * the array, we trigger delay marking for the whole object. |
|
260 */ |
|
261 if (!stack.push(endAddr, startAddr, tagged)) |
|
262 delayMarkingChildren(obj); |
|
263 } |
|
264 |
|
265 bool isMarkStackEmpty() { |
|
266 return stack.isEmpty(); |
|
267 } |
|
268 |
|
269 bool restoreValueArray(JSObject *obj, void **vpp, void **endp); |
|
270 void saveValueRanges(); |
|
271 inline void processMarkStackTop(SliceBudget &budget); |
|
272 void processMarkStackOther(uintptr_t tag, uintptr_t addr); |
|
273 |
|
274 void appendGrayRoot(void *thing, JSGCTraceKind kind); |
|
275 |
|
276 /* The color is only applied to objects and functions. */ |
|
277 uint32_t color; |
|
278 |
|
279 /* Pointer to the top of the stack of arenas we are delaying marking on. */ |
|
280 js::gc::ArenaHeader *unmarkedArenaStackTop; |
|
281 |
|
282 /* Count of arenas that are currently in the stack. */ |
|
283 mozilla::DebugOnly<size_t> markLaterArenas; |
|
284 |
|
285 enum GrayBufferState { |
|
286 GRAY_BUFFER_UNUSED, |
|
287 GRAY_BUFFER_OK, |
|
288 GRAY_BUFFER_FAILED |
|
289 }; |
|
290 GrayBufferState grayBufferState; |
|
291 |
|
292 /* Assert that start and stop are called with correct ordering. */ |
|
293 mozilla::DebugOnly<bool> started; |
|
294 }; |
|
295 |
|
296 void |
|
297 SetMarkStackLimit(JSRuntime *rt, size_t limit); |
|
298 |
|
299 } /* namespace js */ |
|
300 |
|
301 /* |
|
302 * Macro to test if a traversal is the marking phase of the GC. |
|
303 */ |
|
304 #define IS_GC_MARKING_TRACER(trc) \ |
|
305 ((trc)->callback == nullptr || (trc)->callback == GCMarker::GrayCallback) |
|
306 |
|
307 #endif /* js_Tracer_h */ |