203 lines
6.3 KiB
C++
203 lines
6.3 KiB
C++
/* 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)
|