1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "js/GlobalObject.h"
6 #include "jsapi-tests/tests.h"
10 static const unsigned BufSize
= 20;
11 static unsigned FinalizeCalls
= 0;
12 static JSFinalizeStatus StatusBuffer
[BufSize
];
14 BEGIN_TEST(testGCFinalizeCallback
) {
15 AutoGCParameter
param1(cx
, JSGC_INCREMENTAL_GC_ENABLED
, true);
16 AutoGCParameter
param2(cx
, JSGC_PER_ZONE_GC_ENABLED
, true);
18 /* Full GC, non-incremental. */
21 CHECK(cx
->runtime()->gc
.isFullGc());
22 CHECK(checkSingleGroup());
23 CHECK(checkFinalizeStatus());
25 /* Full GC, incremental. */
27 JS::PrepareForFullGC(cx
);
28 SliceBudget
startBudget(TimeBudget(1000000));
29 JS::StartIncrementalGC(cx
, JS::GCOptions::Normal
, JS::GCReason::API
,
31 while (cx
->runtime()->gc
.isIncrementalGCInProgress()) {
32 JS::PrepareForFullGC(cx
);
33 SliceBudget
budget(TimeBudget(1000000));
34 JS::IncrementalGCSlice(cx
, JS::GCReason::API
, budget
);
36 CHECK(!cx
->runtime()->gc
.isIncrementalGCInProgress());
37 CHECK(cx
->runtime()->gc
.isFullGc());
38 CHECK(checkMultipleGroups());
39 CHECK(checkFinalizeStatus());
42 // Bug 1377593 - the below tests want to control how many zones are GC'ing,
43 // and some zeal modes will convert them into all-zones GCs.
44 JS_SetGCZeal(cx
, 0, 0);
47 JS::RootedObject
global1(cx
, createTestGlobal());
48 JS::RootedObject
global2(cx
, createTestGlobal());
49 JS::RootedObject
global3(cx
, createTestGlobal());
54 /* Zone GC, non-incremental, single zone. */
56 JS::PrepareZoneForGC(cx
, global1
->zone());
57 JS::NonIncrementalGC(cx
, JS::GCOptions::Normal
, JS::GCReason::API
);
58 CHECK(!cx
->runtime()->gc
.isFullGc());
59 CHECK(checkSingleGroup());
60 CHECK(checkFinalizeStatus());
62 /* Zone GC, non-incremental, multiple zones. */
64 JS::PrepareZoneForGC(cx
, global1
->zone());
65 JS::PrepareZoneForGC(cx
, global2
->zone());
66 JS::PrepareZoneForGC(cx
, global3
->zone());
67 JS::NonIncrementalGC(cx
, JS::GCOptions::Normal
, JS::GCReason::API
);
68 CHECK(!cx
->runtime()->gc
.isFullGc());
69 CHECK(checkSingleGroup());
70 CHECK(checkFinalizeStatus());
72 /* Zone GC, incremental, single zone. */
74 JS::PrepareZoneForGC(cx
, global1
->zone());
75 SliceBudget
budget(TimeBudget(1000000));
76 JS::StartIncrementalGC(cx
, JS::GCOptions::Normal
, JS::GCReason::API
, budget
);
77 while (cx
->runtime()->gc
.isIncrementalGCInProgress()) {
78 JS::PrepareZoneForGC(cx
, global1
->zone());
79 budget
= SliceBudget(TimeBudget(1000000));
80 JS::IncrementalGCSlice(cx
, JS::GCReason::API
, budget
);
82 CHECK(!cx
->runtime()->gc
.isIncrementalGCInProgress());
83 CHECK(!cx
->runtime()->gc
.isFullGc());
84 CHECK(checkSingleGroup());
85 CHECK(checkFinalizeStatus());
87 /* Zone GC, incremental, multiple zones. */
89 JS::PrepareZoneForGC(cx
, global1
->zone());
90 JS::PrepareZoneForGC(cx
, global2
->zone());
91 JS::PrepareZoneForGC(cx
, global3
->zone());
92 budget
= SliceBudget(TimeBudget(1000000));
93 JS::StartIncrementalGC(cx
, JS::GCOptions::Normal
, JS::GCReason::API
, budget
);
94 while (cx
->runtime()->gc
.isIncrementalGCInProgress()) {
95 JS::PrepareZoneForGC(cx
, global1
->zone());
96 JS::PrepareZoneForGC(cx
, global2
->zone());
97 JS::PrepareZoneForGC(cx
, global3
->zone());
98 budget
= SliceBudget(TimeBudget(1000000));
99 JS::IncrementalGCSlice(cx
, JS::GCReason::API
, budget
);
101 CHECK(!cx
->runtime()->gc
.isIncrementalGCInProgress());
102 CHECK(!cx
->runtime()->gc
.isFullGc());
103 CHECK(checkMultipleGroups());
104 CHECK(checkFinalizeStatus());
108 /* Full GC with reset due to new zone, becoming zone GC. */
111 JS_SetGCZeal(cx
, 9, 1000000);
112 JS::PrepareForFullGC(cx
);
113 budget
= SliceBudget(WorkBudget(1));
114 cx
->runtime()->gc
.startDebugGC(JS::GCOptions::Normal
, budget
);
115 CHECK(cx
->runtime()->gc
.state() == gc::State::Mark
);
116 CHECK(cx
->runtime()->gc
.isFullGc());
118 JS::RootedObject
global4(cx
, createTestGlobal());
119 budget
= SliceBudget(WorkBudget(1));
120 cx
->runtime()->gc
.debugGCSlice(budget
);
121 while (cx
->runtime()->gc
.isIncrementalGCInProgress()) {
122 cx
->runtime()->gc
.debugGCSlice(budget
);
124 CHECK(!cx
->runtime()->gc
.isIncrementalGCInProgress());
125 CHECK(checkSingleGroup());
126 CHECK(checkFinalizeStatus());
128 JS_SetGCZeal(cx
, 0, 0);
133 * Make some use of the globals here to ensure the compiler doesn't optimize
134 * them away in release builds, causing the zones to be collected and
137 CHECK(JS_IsGlobalObject(global1
));
138 CHECK(JS_IsGlobalObject(global2
));
139 CHECK(JS_IsGlobalObject(global3
));
144 JSObject
* createTestGlobal() {
145 JS::RealmOptions options
;
146 return JS_NewGlobalObject(cx
, getGlobalClass(), nullptr,
147 JS::FireOnNewGlobalHook
, options
);
150 virtual bool init() override
{
151 if (!JSAPIRuntimeTest::init()) {
155 JS_AddFinalizeCallback(cx
, FinalizeCallback
, nullptr);
159 virtual void uninit() override
{
160 JS_RemoveFinalizeCallback(cx
, FinalizeCallback
);
161 JSAPIRuntimeTest::uninit();
164 bool checkSingleGroup() {
165 CHECK(FinalizeCalls
< BufSize
);
166 CHECK(FinalizeCalls
== 4);
170 bool checkMultipleGroups() {
171 CHECK(FinalizeCalls
< BufSize
);
172 CHECK(FinalizeCalls
% 3 == 1);
173 CHECK((FinalizeCalls
- 1) / 3 > 1);
177 bool checkFinalizeStatus() {
179 * The finalize callback should be called twice for each sweep group
180 * finalized, with status JSFINALIZE_GROUP_START and JSFINALIZE_GROUP_END,
181 * and then once more with JSFINALIZE_COLLECTION_END.
184 for (unsigned i
= 0; i
< FinalizeCalls
- 1; i
+= 3) {
185 CHECK(StatusBuffer
[i
] == JSFINALIZE_GROUP_PREPARE
);
186 CHECK(StatusBuffer
[i
+ 1] == JSFINALIZE_GROUP_START
);
187 CHECK(StatusBuffer
[i
+ 2] == JSFINALIZE_GROUP_END
);
190 CHECK(StatusBuffer
[FinalizeCalls
- 1] == JSFINALIZE_COLLECTION_END
);
195 static void FinalizeCallback(JS::GCContext
* gcx
, JSFinalizeStatus status
,
197 if (FinalizeCalls
< BufSize
) {
198 StatusBuffer
[FinalizeCalls
] = status
;
202 END_TEST(testGCFinalizeCallback
)