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_GCAPI_h
8 #define js_GCAPI_h
9
10 #include "mozilla/UniquePtr.h"
11 #include "mozilla/Vector.h"
12
13 #include "js/HeapAPI.h"
14
15 namespace js {
16 namespace gc {
17 class GCRuntime;
18 } // namespace gc
19 namespace gcstats {
20 struct Statistics;
21 } // namespace gcstats
22 } // namespace js
23
24 typedef enum JSGCMode {
25 /** Perform only global GCs. */
26 JSGC_MODE_GLOBAL = 0,
27
28 /** Perform per-compartment GCs until too much garbage has accumulated. */
29 JSGC_MODE_COMPARTMENT = 1,
30
31 /**
32 * Collect in short time slices rather than all at once. Implies
33 * JSGC_MODE_COMPARTMENT.
34 */
35 JSGC_MODE_INCREMENTAL = 2
36 } JSGCMode;
37
38 /**
39 * Kinds of js_GC invocation.
40 */
41 typedef enum JSGCInvocationKind {
42 /* Normal invocation. */
43 GC_NORMAL = 0,
44
45 /* Minimize GC triggers and release empty GC chunks right away. */
46 GC_SHRINK = 1
47 } JSGCInvocationKind;
48
49 namespace JS {
50
51 using mozilla::UniquePtr;
52
53 #define GCREASONS(D) \
54 /* Reasons internal to the JS engine */ \
55 D(API) \
56 D(EAGER_ALLOC_TRIGGER) \
57 D(DESTROY_RUNTIME) \
58 D(DESTROY_CONTEXT) \
59 D(LAST_DITCH) \
60 D(TOO_MUCH_MALLOC) \
61 D(ALLOC_TRIGGER) \
62 D(DEBUG_GC) \
63 D(COMPARTMENT_REVIVED) \
64 D(RESET) \
65 D(OUT_OF_NURSERY) \
66 D(EVICT_NURSERY) \
67 D(FULL_STORE_BUFFER) \
68 D(SHARED_MEMORY_LIMIT) \
69 D(PERIODIC_FULL_GC) \
70 D(INCREMENTAL_TOO_SLOW) \
71 D(ABORT_GC) \
72 \
73 /* These are reserved for future use. */ \
74 D(RESERVED0) \
75 D(RESERVED1) \
76 D(RESERVED2) \
77 D(RESERVED3) \
78 D(RESERVED4) \
79 D(RESERVED5) \
80 D(RESERVED6) \
81 D(RESERVED7) \
82 D(RESERVED8) \
83 D(RESERVED9) \
84 D(RESERVED10) \
85 D(RESERVED11) \
86 D(RESERVED12) \
87 D(RESERVED13) \
88 D(RESERVED14) \
89 D(RESERVED15) \
90 \
91 /* Reasons from Firefox */ \
92 D(DOM_WINDOW_UTILS) \
93 D(COMPONENT_UTILS) \
94 D(MEM_PRESSURE) \
95 D(CC_WAITING) \
96 D(CC_FORCED) \
97 D(LOAD_END) \
98 D(POST_COMPARTMENT) \
99 D(PAGE_HIDE) \
100 D(NSJSCONTEXT_DESTROY) \
101 D(SET_NEW_DOCUMENT) \
102 D(SET_DOC_SHELL) \
103 D(DOM_UTILS) \
104 D(DOM_IPC) \
105 D(DOM_WORKER) \
106 D(INTER_SLICE_GC) \
107 D(REFRESH_FRAME) \
108 D(FULL_GC_TIMER) \
109 D(SHUTDOWN_CC) \
110 D(FINISH_LARGE_EVALUATE) \
111 D(USER_INACTIVE) \
112 D(XPCONNECT_SHUTDOWN)
113
114 namespace gcreason {
115
116 /* GCReasons will end up looking like JSGC_MAYBEGC */
117 enum Reason {
118 #define MAKE_REASON(name) name,
119 GCREASONS(MAKE_REASON)
120 #undef MAKE_REASON
121 NO_REASON,
122 NUM_REASONS,
123
124 /*
125 * For telemetry, we want to keep a fixed max bucket size over time so we
126 * don't have to switch histograms. 100 is conservative; as of this writing
127 * there are 52. But the cost of extra buckets seems to be low while the
128 * cost of switching histograms is high.
129 */
130 NUM_TELEMETRY_REASONS = 100
131 };
132
133 } /* namespace gcreason */
134
135 /*
136 * Zone GC:
137 *
138 * SpiderMonkey's GC is capable of performing a collection on an arbitrary
139 * subset of the zones in the system. This allows an embedding to minimize
140 * collection time by only collecting zones that have run code recently,
141 * ignoring the parts of the heap that are unlikely to have changed.
142 *
143 * When triggering a GC using one of the functions below, it is first necessary
144 * to select the zones to be collected. To do this, you can call
145 * PrepareZoneForGC on each zone, or you can call PrepareForFullGC to select
146 * all zones. Failing to select any zone is an error.
147 */
148
149 /**
150 * Schedule the given zone to be collected as part of the next GC.
151 */
152 extern JS_PUBLIC_API(void)
153 PrepareZoneForGC(Zone* zone);
154
155 /**
156 * Schedule all zones to be collected in the next GC.
157 */
158 extern JS_PUBLIC_API(void)
159 PrepareForFullGC(JSRuntime* rt);
160
161 /**
162 * When performing an incremental GC, the zones that were selected for the
163 * previous incremental slice must be selected in subsequent slices as well.
164 * This function selects those slices automatically.
165 */
166 extern JS_PUBLIC_API(void)
167 PrepareForIncrementalGC(JSRuntime* rt);
168
169 /**
170 * Returns true if any zone in the system has been scheduled for GC with one of
171 * the functions above or by the JS engine.
172 */
173 extern JS_PUBLIC_API(bool)
174 IsGCScheduled(JSRuntime* rt);
175
176 /**
177 * Undoes the effect of the Prepare methods above. The given zone will not be
178 * collected in the next GC.
179 */
180 extern JS_PUBLIC_API(void)
181 SkipZoneForGC(Zone* zone);
182
183 /*
184 * Non-Incremental GC:
185 *
186 * The following functions perform a non-incremental GC.
187 */
188
189 /**
190 * Performs a non-incremental collection of all selected zones.
191 *
192 * If the gckind argument is GC_NORMAL, then some objects that are unreachable
193 * from the program may still be alive afterwards because of internal
194 * references; if GC_SHRINK is passed then caches and other temporary references
195 * to objects will be cleared and all unreferenced objects will be removed from
196 * the system.
197 */
198 extern JS_PUBLIC_API(void)
199 GCForReason(JSRuntime* rt, JSGCInvocationKind gckind, gcreason::Reason reason);
200
201 /*
202 * Incremental GC:
203 *
204 * Incremental GC divides the full mark-and-sweep collection into multiple
205 * slices, allowing client JavaScript code to run between each slice. This
206 * allows interactive apps to avoid long collection pauses. Incremental GC does
207 * not make collection take less time, it merely spreads that time out so that
208 * the pauses are less noticable.
209 *
210 * For a collection to be carried out incrementally the following conditions
211 * must be met:
212 * - The collection must be run by calling JS::IncrementalGC() rather than
213 * JS_GC().
214 * - The GC mode must have been set to JSGC_MODE_INCREMENTAL with
215 * JS_SetGCParameter().
216 *
217 * Note: Even if incremental GC is enabled and working correctly,
218 * non-incremental collections can still happen when low on memory.
219 */
220
221 /**
222 * Begin an incremental collection and perform one slice worth of work. When
223 * this function returns, the collection may not be complete.
224 * IncrementalGCSlice() must be called repeatedly until
225 * !IsIncrementalGCInProgress(rt).
226 *
227 * Note: SpiderMonkey's GC is not realtime. Slices in practice may be longer or
228 * shorter than the requested interval.
229 */
230 extern JS_PUBLIC_API(void)
231 StartIncrementalGC(JSRuntime* rt, JSGCInvocationKind gckind, gcreason::Reason reason,
232 int64_t millis = 0);
233
234 /**
235 * Perform a slice of an ongoing incremental collection. When this function
236 * returns, the collection may not be complete. It must be called repeatedly
237 * until !IsIncrementalGCInProgress(rt).
238 *
239 * Note: SpiderMonkey's GC is not realtime. Slices in practice may be longer or
240 * shorter than the requested interval.
241 */
242 extern JS_PUBLIC_API(void)
243 IncrementalGCSlice(JSRuntime* rt, gcreason::Reason reason, int64_t millis = 0);
244
245 /**
246 * If IsIncrementalGCInProgress(rt), this call finishes the ongoing collection
247 * by performing an arbitrarily long slice. If !IsIncrementalGCInProgress(rt),
248 * this is equivalent to GCForReason. When this function returns,
249 * IsIncrementalGCInProgress(rt) will always be false.
250 */
251 extern JS_PUBLIC_API(void)
252 FinishIncrementalGC(JSRuntime* rt, gcreason::Reason reason);
253
254 /**
255 * If IsIncrementalGCInProgress(rt), this call aborts the ongoing collection and
256 * performs whatever work needs to be done to return the collector to its idle
257 * state. This may take an arbitrarily long time. When this function returns,
258 * IsIncrementalGCInProgress(rt) will always be false.
259 */
260 extern JS_PUBLIC_API(void)
261 AbortIncrementalGC(JSRuntime* rt);
262
263 namespace dbg {
264
265 // The `JS::dbg::GarbageCollectionEvent` class is essentially a view of the
266 // `js::gcstats::Statistics` data without the uber implementation-specific bits.
267 // It should generally be palatable for web developers.
268 class GarbageCollectionEvent
269 {
270 // The major GC number of the GC cycle this data pertains to.
271 uint64_t majorGCNumber_;
272
273 // Reference to a non-owned, statically allocated C string. This is a very
274 // short reason explaining why a GC was triggered.
275 const char* reason;
276
277 // Reference to a nullable, non-owned, statically allocated C string. If the
278 // collection was forced to be non-incremental, this is a short reason of
279 // why the GC could not perform an incremental collection.
280 const char* nonincrementalReason;
281
282 // Represents a single slice of a possibly multi-slice incremental garbage
283 // collection.
284 struct Collection {
285 double startTimestamp;
286 double endTimestamp;
287 };
288
289 // The set of garbage collection slices that made up this GC cycle.
290 mozilla::Vector<Collection> collections;
291
292 GarbageCollectionEvent(const GarbageCollectionEvent& rhs) = delete;
293 GarbageCollectionEvent& operator=(const GarbageCollectionEvent& rhs) = delete;
294
295 public:
GarbageCollectionEvent(uint64_t majorGCNum)296 explicit GarbageCollectionEvent(uint64_t majorGCNum)
297 : majorGCNumber_(majorGCNum)
298 , reason(nullptr)
299 , nonincrementalReason(nullptr)
300 , collections()
301 { }
302
303 using Ptr = UniquePtr<GarbageCollectionEvent, DeletePolicy<GarbageCollectionEvent>>;
304 static Ptr Create(JSRuntime* rt, ::js::gcstats::Statistics& stats, uint64_t majorGCNumber);
305
306 JSObject* toJSObject(JSContext* cx) const;
307
majorGCNumber()308 uint64_t majorGCNumber() const { return majorGCNumber_; }
309 };
310
311 } // namespace dbg
312
313 enum GCProgress {
314 /*
315 * During non-incremental GC, the GC is bracketed by JSGC_CYCLE_BEGIN/END
316 * callbacks. During an incremental GC, the sequence of callbacks is as
317 * follows:
318 * JSGC_CYCLE_BEGIN, JSGC_SLICE_END (first slice)
319 * JSGC_SLICE_BEGIN, JSGC_SLICE_END (second slice)
320 * ...
321 * JSGC_SLICE_BEGIN, JSGC_CYCLE_END (last slice)
322 */
323
324 GC_CYCLE_BEGIN,
325 GC_SLICE_BEGIN,
326 GC_SLICE_END,
327 GC_CYCLE_END
328 };
329
JS_PUBLIC_API(GCDescription)330 struct JS_PUBLIC_API(GCDescription) {
331 bool isCompartment_;
332 JSGCInvocationKind invocationKind_;
333 gcreason::Reason reason_;
334
335 GCDescription(bool isCompartment, JSGCInvocationKind kind, gcreason::Reason reason)
336 : isCompartment_(isCompartment), invocationKind_(kind), reason_(reason) {}
337
338 char16_t* formatSliceMessage(JSRuntime* rt) const;
339 char16_t* formatSummaryMessage(JSRuntime* rt) const;
340 char16_t* formatJSON(JSRuntime* rt, uint64_t timestamp) const;
341
342 JS::dbg::GarbageCollectionEvent::Ptr toGCEvent(JSRuntime* rt) const;
343 };
344
345 typedef void
346 (* GCSliceCallback)(JSRuntime* rt, GCProgress progress, const GCDescription& desc);
347
348 /**
349 * The GC slice callback is called at the beginning and end of each slice. This
350 * callback may be used for GC notifications as well as to perform additional
351 * marking.
352 */
353 extern JS_PUBLIC_API(GCSliceCallback)
354 SetGCSliceCallback(JSRuntime* rt, GCSliceCallback callback);
355
356 /**
357 * Incremental GC defaults to enabled, but may be disabled for testing or in
358 * embeddings that have not yet implemented barriers on their native classes.
359 * There is not currently a way to re-enable incremental GC once it has been
360 * disabled on the runtime.
361 */
362 extern JS_PUBLIC_API(void)
363 DisableIncrementalGC(JSRuntime* rt);
364
365 /**
366 * Returns true if incremental GC is enabled. Simply having incremental GC
367 * enabled is not sufficient to ensure incremental collections are happening.
368 * See the comment "Incremental GC" above for reasons why incremental GC may be
369 * suppressed. Inspection of the "nonincremental reason" field of the
370 * GCDescription returned by GCSliceCallback may help narrow down the cause if
371 * collections are not happening incrementally when expected.
372 */
373 extern JS_PUBLIC_API(bool)
374 IsIncrementalGCEnabled(JSRuntime* rt);
375
376 /**
377 * Returns true while an incremental GC is ongoing, both when actively
378 * collecting and between slices.
379 */
380 extern JS_PUBLIC_API(bool)
381 IsIncrementalGCInProgress(JSRuntime* rt);
382
383 /*
384 * Returns true when writes to GC things must call an incremental (pre) barrier.
385 * This is generally only true when running mutator code in-between GC slices.
386 * At other times, the barrier may be elided for performance.
387 */
388 extern JS_PUBLIC_API(bool)
389 IsIncrementalBarrierNeeded(JSRuntime* rt);
390
391 extern JS_PUBLIC_API(bool)
392 IsIncrementalBarrierNeeded(JSContext* cx);
393
394 /*
395 * Notify the GC that a reference to a GC thing is about to be overwritten.
396 * These methods must be called if IsIncrementalBarrierNeeded.
397 */
398 extern JS_PUBLIC_API(void)
399 IncrementalReferenceBarrier(GCCellPtr thing);
400
401 extern JS_PUBLIC_API(void)
402 IncrementalValueBarrier(const Value& v);
403
404 extern JS_PUBLIC_API(void)
405 IncrementalObjectBarrier(JSObject* obj);
406
407 /**
408 * Returns true if the most recent GC ran incrementally.
409 */
410 extern JS_PUBLIC_API(bool)
411 WasIncrementalGC(JSRuntime* rt);
412
413 /*
414 * Generational GC:
415 *
416 * Note: Generational GC is not yet enabled by default. The following class
417 * is non-functional unless SpiderMonkey was configured with
418 * --enable-gcgenerational.
419 */
420
421 /** Ensure that generational GC is disabled within some scope. */
JS_PUBLIC_API(AutoDisableGenerationalGC)422 class JS_PUBLIC_API(AutoDisableGenerationalGC)
423 {
424 js::gc::GCRuntime* gc;
425
426 public:
427 explicit AutoDisableGenerationalGC(JSRuntime* rt);
428 ~AutoDisableGenerationalGC();
429 };
430
431 /**
432 * Returns true if generational allocation and collection is currently enabled
433 * on the given runtime.
434 */
435 extern JS_PUBLIC_API(bool)
436 IsGenerationalGCEnabled(JSRuntime* rt);
437
438 /**
439 * Returns the GC's "number". This does not correspond directly to the number
440 * of GCs that have been run, but is guaranteed to be monotonically increasing
441 * with GC activity.
442 */
443 extern JS_PUBLIC_API(size_t)
444 GetGCNumber();
445
446 /**
447 * The GC does not immediately return the unused memory freed by a collection
448 * back to the system incase it is needed soon afterwards. This call forces the
449 * GC to return this memory immediately.
450 */
451 extern JS_PUBLIC_API(void)
452 ShrinkGCBuffers(JSRuntime* rt);
453
454 /**
455 * Assert if a GC occurs while this class is live. This class does not disable
456 * the static rooting hazard analysis.
457 */
JS_PUBLIC_API(AutoAssertOnGC)458 class JS_PUBLIC_API(AutoAssertOnGC)
459 {
460 #ifdef DEBUG
461 js::gc::GCRuntime* gc;
462 size_t gcNumber;
463
464 public:
465 AutoAssertOnGC();
466 explicit AutoAssertOnGC(JSRuntime* rt);
467 ~AutoAssertOnGC();
468
469 static void VerifyIsSafeToGC(JSRuntime* rt);
470 #else
471 public:
472 AutoAssertOnGC() {}
473 explicit AutoAssertOnGC(JSRuntime* rt) {}
474 ~AutoAssertOnGC() {}
475
476 static void VerifyIsSafeToGC(JSRuntime* rt) {}
477 #endif
478 };
479
480 /**
481 * Assert if an allocation of a GC thing occurs while this class is live. This
482 * class does not disable the static rooting hazard analysis.
483 */
JS_PUBLIC_API(AutoAssertNoAlloc)484 class JS_PUBLIC_API(AutoAssertNoAlloc)
485 {
486 #ifdef JS_DEBUG
487 js::gc::GCRuntime* gc;
488
489 public:
490 AutoAssertNoAlloc() : gc(nullptr) {}
491 explicit AutoAssertNoAlloc(JSRuntime* rt);
492 void disallowAlloc(JSRuntime* rt);
493 ~AutoAssertNoAlloc();
494 #else
495 public:
496 AutoAssertNoAlloc() {}
497 explicit AutoAssertNoAlloc(JSRuntime* rt) {}
498 void disallowAlloc(JSRuntime* rt) {}
499 #endif
500 };
501
502 /**
503 * Disable the static rooting hazard analysis in the live region and assert if
504 * any allocation that could potentially trigger a GC occurs while this guard
505 * object is live. This is most useful to help the exact rooting hazard analysis
506 * in complex regions, since it cannot understand dataflow.
507 *
508 * Note: GC behavior is unpredictable even when deterministic and is generally
509 * non-deterministic in practice. The fact that this guard has not
510 * asserted is not a guarantee that a GC cannot happen in the guarded
511 * region. As a rule, anyone performing a GC unsafe action should
512 * understand the GC properties of all code in that region and ensure
513 * that the hazard analysis is correct for that code, rather than relying
514 * on this class.
515 */
JS_PUBLIC_API(AutoSuppressGCAnalysis)516 class JS_PUBLIC_API(AutoSuppressGCAnalysis) : public AutoAssertNoAlloc
517 {
518 public:
519 AutoSuppressGCAnalysis() : AutoAssertNoAlloc() {}
520 explicit AutoSuppressGCAnalysis(JSRuntime* rt) : AutoAssertNoAlloc(rt) {}
521 };
522
523 /**
524 * Assert that code is only ever called from a GC callback, disable the static
525 * rooting hazard analysis and assert if any allocation that could potentially
526 * trigger a GC occurs while this guard object is live.
527 *
528 * This is useful to make the static analysis ignore code that runs in GC
529 * callbacks.
530 */
JS_PUBLIC_API(AutoAssertGCCallback)531 class JS_PUBLIC_API(AutoAssertGCCallback) : public AutoSuppressGCAnalysis
532 {
533 public:
534 explicit AutoAssertGCCallback(JSObject* obj);
535 };
536
537 /**
538 * Place AutoCheckCannotGC in scopes that you believe can never GC. These
539 * annotations will be verified both dynamically via AutoAssertOnGC, and
540 * statically with the rooting hazard analysis (implemented by making the
541 * analysis consider AutoCheckCannotGC to be a GC pointer, and therefore
542 * complain if it is live across a GC call.) It is useful when dealing with
543 * internal pointers to GC things where the GC thing itself may not be present
544 * for the static analysis: e.g. acquiring inline chars from a JSString* on the
545 * heap.
546 */
JS_PUBLIC_API(AutoCheckCannotGC)547 class JS_PUBLIC_API(AutoCheckCannotGC) : public AutoAssertOnGC
548 {
549 public:
550 AutoCheckCannotGC() : AutoAssertOnGC() {}
551 explicit AutoCheckCannotGC(JSRuntime* rt) : AutoAssertOnGC(rt) {}
552 };
553
554 /**
555 * Unsets the gray bit for anything reachable from |thing|. |kind| should not be
556 * JS::TraceKind::Shape. |thing| should be non-null.
557 */
558 extern JS_FRIEND_API(bool)
559 UnmarkGrayGCThingRecursively(GCCellPtr thing);
560
561 } /* namespace JS */
562
563 namespace js {
564 namespace gc {
565
566 static MOZ_ALWAYS_INLINE void
ExposeGCThingToActiveJS(JS::GCCellPtr thing)567 ExposeGCThingToActiveJS(JS::GCCellPtr thing)
568 {
569 /*
570 * GC things residing in the nursery cannot be gray: they have no mark bits.
571 * All live objects in the nursery are moved to tenured at the beginning of
572 * each GC slice, so the gray marker never sees nursery things.
573 */
574 if (IsInsideNursery(thing.asCell()))
575 return;
576 JS::shadow::Runtime* rt = detail::GetGCThingRuntime(thing.unsafeAsUIntPtr());
577 if (IsIncrementalBarrierNeededOnTenuredGCThing(rt, thing))
578 JS::IncrementalReferenceBarrier(thing);
579 else if (JS::GCThingIsMarkedGray(thing))
580 JS::UnmarkGrayGCThingRecursively(thing);
581 }
582
583 static MOZ_ALWAYS_INLINE void
MarkGCThingAsLive(JSRuntime * aRt,JS::GCCellPtr thing)584 MarkGCThingAsLive(JSRuntime* aRt, JS::GCCellPtr thing)
585 {
586 JS::shadow::Runtime* rt = JS::shadow::Runtime::asShadowRuntime(aRt);
587 /*
588 * Any object in the nursery will not be freed during any GC running at that time.
589 */
590 if (IsInsideNursery(thing.asCell()))
591 return;
592 if (IsIncrementalBarrierNeededOnTenuredGCThing(rt, thing))
593 JS::IncrementalReferenceBarrier(thing);
594 }
595
596 } /* namespace gc */
597 } /* namespace js */
598
599 namespace JS {
600
601 /*
602 * This should be called when an object that is marked gray is exposed to the JS
603 * engine (by handing it to running JS code or writing it into live JS
604 * data). During incremental GC, since the gray bits haven't been computed yet,
605 * we conservatively mark the object black.
606 */
607 static MOZ_ALWAYS_INLINE void
ExposeObjectToActiveJS(JSObject * obj)608 ExposeObjectToActiveJS(JSObject* obj)
609 {
610 js::gc::ExposeGCThingToActiveJS(GCCellPtr(obj));
611 }
612
613 static MOZ_ALWAYS_INLINE void
ExposeScriptToActiveJS(JSScript * script)614 ExposeScriptToActiveJS(JSScript* script)
615 {
616 js::gc::ExposeGCThingToActiveJS(GCCellPtr(script));
617 }
618
619 /*
620 * If a GC is currently marking, mark the string black.
621 */
622 static MOZ_ALWAYS_INLINE void
MarkStringAsLive(Zone * zone,JSString * string)623 MarkStringAsLive(Zone* zone, JSString* string)
624 {
625 JSRuntime* rt = JS::shadow::Zone::asShadowZone(zone)->runtimeFromMainThread();
626 js::gc::MarkGCThingAsLive(rt, GCCellPtr(string));
627 }
628
629 /*
630 * Internal to Firefox.
631 *
632 * Note: this is not related to the PokeGC in nsJSEnvironment.
633 */
634 extern JS_FRIEND_API(void)
635 PokeGC(JSRuntime* rt);
636
637 /*
638 * Internal to Firefox.
639 */
640 extern JS_FRIEND_API(void)
641 NotifyDidPaint(JSRuntime* rt);
642
643 } /* namespace JS */
644
645 #endif /* js_GCAPI_h */
646