Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "js/GlobalObject.h"
#include "jsapi-tests/tests.h"
using namespace js;
using namespace JS;
static const unsigned BufSize = 20;
static unsigned FinalizeCalls = 0;
static JSFinalizeStatus StatusBuffer[BufSize];
BEGIN_TEST(testGCFinalizeCallback) {
AutoGCParameter param1(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
AutoGCParameter param2(cx, JSGC_PER_ZONE_GC_ENABLED, true);
/* Full GC, non-incremental. */
FinalizeCalls = 0;
JS_GC(cx);
CHECK(cx->runtime()->gc.isFullGc());
CHECK(checkSingleGroup());
CHECK(checkFinalizeStatus());
/* Full GC, incremental. */
FinalizeCalls = 0;
JS::PrepareForFullGC(cx);
SliceBudget startBudget(TimeBudget(1000000));
JS::StartIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API,
startBudget);
while (cx->runtime()->gc.isIncrementalGCInProgress()) {
JS::PrepareForFullGC(cx);
SliceBudget budget(TimeBudget(1000000));
JS::IncrementalGCSlice(cx, JS::GCReason::API, budget);
}
CHECK(!cx->runtime()->gc.isIncrementalGCInProgress());
CHECK(cx->runtime()->gc.isFullGc());
CHECK(checkMultipleGroups());
CHECK(checkFinalizeStatus());
#ifdef JS_GC_ZEAL
// Bug 1377593 - the below tests want to control how many zones are GC'ing,
// and some zeal modes will convert them into all-zones GCs.
JS::SetGCZeal(cx, 0, 0);
#endif
JS::RootedObject global1(cx, createTestGlobal());
JS::RootedObject global2(cx, createTestGlobal());
JS::RootedObject global3(cx, createTestGlobal());
CHECK(global1);
CHECK(global2);
CHECK(global3);
/* Zone GC, non-incremental, single zone. */
FinalizeCalls = 0;
JS::PrepareZoneForGC(cx, global1->zone());
JS::NonIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API);
CHECK(!cx->runtime()->gc.isFullGc());
CHECK(checkSingleGroup());
CHECK(checkFinalizeStatus());
/* Zone GC, non-incremental, multiple zones. */
FinalizeCalls = 0;
JS::PrepareZoneForGC(cx, global1->zone());
JS::PrepareZoneForGC(cx, global2->zone());
JS::PrepareZoneForGC(cx, global3->zone());
JS::NonIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API);
CHECK(!cx->runtime()->gc.isFullGc());
CHECK(checkSingleGroup());
CHECK(checkFinalizeStatus());
/* Zone GC, incremental, single zone. */
FinalizeCalls = 0;
JS::PrepareZoneForGC(cx, global1->zone());
SliceBudget budget(TimeBudget(1000000));
JS::StartIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API, budget);
while (cx->runtime()->gc.isIncrementalGCInProgress()) {
JS::PrepareZoneForGC(cx, global1->zone());
budget = SliceBudget(TimeBudget(1000000));
JS::IncrementalGCSlice(cx, JS::GCReason::API, budget);
}
CHECK(!cx->runtime()->gc.isIncrementalGCInProgress());
CHECK(!cx->runtime()->gc.isFullGc());
CHECK(checkSingleGroup());
CHECK(checkFinalizeStatus());
/* Zone GC, incremental, multiple zones. */
FinalizeCalls = 0;
JS::PrepareZoneForGC(cx, global1->zone());
JS::PrepareZoneForGC(cx, global2->zone());
JS::PrepareZoneForGC(cx, global3->zone());
budget = SliceBudget(TimeBudget(1000000));
JS::StartIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API, budget);
while (cx->runtime()->gc.isIncrementalGCInProgress()) {
JS::PrepareZoneForGC(cx, global1->zone());
JS::PrepareZoneForGC(cx, global2->zone());
JS::PrepareZoneForGC(cx, global3->zone());
budget = SliceBudget(TimeBudget(1000000));
JS::IncrementalGCSlice(cx, JS::GCReason::API, budget);
}
CHECK(!cx->runtime()->gc.isIncrementalGCInProgress());
CHECK(!cx->runtime()->gc.isFullGc());
CHECK(checkMultipleGroups());
CHECK(checkFinalizeStatus());
#ifdef JS_GC_ZEAL
/* Full GC with reset due to new zone, becoming zone GC. */
FinalizeCalls = 0;
JS::SetGCZeal(cx, 9, 1000000);
JS::PrepareForFullGC(cx);
budget = SliceBudget(WorkBudget(1));
cx->runtime()->gc.startDebugGC(JS::GCOptions::Normal, budget);
CHECK(cx->runtime()->gc.state() == gc::State::Mark);
CHECK(cx->runtime()->gc.isFullGc());
JS::RootedObject global4(cx, createTestGlobal());
budget = SliceBudget(WorkBudget(1));
cx->runtime()->gc.debugGCSlice(budget);
while (cx->runtime()->gc.isIncrementalGCInProgress()) {
cx->runtime()->gc.debugGCSlice(budget);
}
CHECK(!cx->runtime()->gc.isIncrementalGCInProgress());
CHECK(checkSingleGroup());
CHECK(checkFinalizeStatus());
JS::SetGCZeal(cx, 0, 0);
#endif
/*
* Make some use of the globals here to ensure the compiler doesn't optimize
* them away in release builds, causing the zones to be collected and
* the test to fail.
*/
CHECK(JS_IsGlobalObject(global1));
CHECK(JS_IsGlobalObject(global2));
CHECK(JS_IsGlobalObject(global3));
return true;
}
JSObject* createTestGlobal() {
JS::RealmOptions options;
return JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
JS::FireOnNewGlobalHook, options);
}
virtual bool init() override {
if (!JSAPIRuntimeTest::init()) {
return false;
}
JS_AddFinalizeCallback(cx, FinalizeCallback, nullptr);
return true;
}
virtual void uninit() override {
JS_RemoveFinalizeCallback(cx, FinalizeCallback);
JSAPIRuntimeTest::uninit();
}
bool checkSingleGroup() {
CHECK(FinalizeCalls < BufSize);
CHECK(FinalizeCalls == 4);
return true;
}
bool checkMultipleGroups() {
CHECK(FinalizeCalls < BufSize);
CHECK(FinalizeCalls % 3 == 1);
CHECK((FinalizeCalls - 1) / 3 > 1);
return true;
}
bool checkFinalizeStatus() {
/*
* The finalize callback should be called twice for each sweep group
* finalized, with status JSFINALIZE_GROUP_START and JSFINALIZE_GROUP_END,
* and then once more with JSFINALIZE_COLLECTION_END.
*/
for (unsigned i = 0; i < FinalizeCalls - 1; i += 3) {
CHECK(StatusBuffer[i] == JSFINALIZE_GROUP_PREPARE);
CHECK(StatusBuffer[i + 1] == JSFINALIZE_GROUP_START);
CHECK(StatusBuffer[i + 2] == JSFINALIZE_GROUP_END);
}
CHECK(StatusBuffer[FinalizeCalls - 1] == JSFINALIZE_COLLECTION_END);
return true;
}
static void FinalizeCallback(JS::GCContext* gcx, JSFinalizeStatus status,
void* data) {
if (FinalizeCalls < BufSize) {
StatusBuffer[FinalizeCalls] = status;
}
++FinalizeCalls;
}
END_TEST(testGCFinalizeCallback)