Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/source/scriptinterface/ScriptContext.cpp
Show All 19 Lines | |||||
#include "ScriptContext.h" | #include "ScriptContext.h" | ||||
#include "ps/GameSetup/Config.h" | #include "ps/GameSetup/Config.h" | ||||
#include "ps/Profile.h" | #include "ps/Profile.h" | ||||
#include "scriptinterface/ScriptEngine.h" | #include "scriptinterface/ScriptEngine.h" | ||||
#include "scriptinterface/ScriptInterface.h" | #include "scriptinterface/ScriptInterface.h" | ||||
void GCSliceCallbackHook(JSRuntime* UNUSED(rt), JS::GCProgress progress, const JS::GCDescription& UNUSED(desc)) | void GCSliceCallbackHook(JSContext* UNUSED(cx), JS::GCProgress progress, const JS::GCDescription& UNUSED(desc)) | ||||
{ | { | ||||
/* | /* | ||||
* During non-incremental GC, the GC is bracketed by JSGC_CYCLE_BEGIN/END | * During non-incremental GC, the GC is bracketed by JSGC_CYCLE_BEGIN/END | ||||
* callbacks. During an incremental GC, the sequence of callbacks is as | * callbacks. During an incremental GC, the sequence of callbacks is as | ||||
* follows: | * follows: | ||||
* JSGC_CYCLE_BEGIN, JSGC_SLICE_END (first slice) | * JSGC_CYCLE_BEGIN, JSGC_SLICE_END (first slice) | ||||
* JSGC_SLICE_BEGIN, JSGC_SLICE_END (second slice) | * JSGC_SLICE_BEGIN, JSGC_SLICE_END (second slice) | ||||
* ... | * ... | ||||
Show All 27 Lines | void GCSliceCallbackHook(JSContext* UNUSED(cx), JS::GCProgress progress, const JS::GCDescription& UNUSED(desc)) | ||||
} | } | ||||
// The following code can be used to print some information aobut garbage collection | // The following code can be used to print some information aobut garbage collection | ||||
// Search for "Nonincremental reason" if there are problems running GC incrementally. | // Search for "Nonincremental reason" if there are problems running GC incrementally. | ||||
#if 0 | #if 0 | ||||
if (progress == JS::GCProgress::GC_CYCLE_BEGIN) | if (progress == JS::GCProgress::GC_CYCLE_BEGIN) | ||||
printf("starting cycle ===========================================\n"); | printf("starting cycle ===========================================\n"); | ||||
const char16_t* str = desc.formatMessage(rt); | const char16_t* str = desc.formatMessage(cx); | ||||
int len = 0; | int len = 0; | ||||
for(int i = 0; i < 10000; i++) | for(int i = 0; i < 10000; i++) | ||||
{ | { | ||||
len++; | len++; | ||||
if(!str[i]) | if(!str[i]) | ||||
break; | break; | ||||
} | } | ||||
Show All 17 Lines | |||||
ScriptContext::ScriptContext(int contextSize, int heapGrowthBytesGCTrigger): | ScriptContext::ScriptContext(int contextSize, int heapGrowthBytesGCTrigger): | ||||
m_LastGCBytes(0), | m_LastGCBytes(0), | ||||
m_LastGCCheck(0.0f), | m_LastGCCheck(0.0f), | ||||
m_HeapGrowthBytesGCTrigger(heapGrowthBytesGCTrigger), | m_HeapGrowthBytesGCTrigger(heapGrowthBytesGCTrigger), | ||||
m_ContextSize(contextSize) | m_ContextSize(contextSize) | ||||
{ | { | ||||
ENSURE(ScriptEngine::IsInitialised() && "The ScriptEngine must be initialized before constructing any ScriptContexts!"); | ENSURE(ScriptEngine::IsInitialised() && "The ScriptEngine must be initialized before constructing any ScriptContexts!"); | ||||
m_rt = JS_NewRuntime(contextSize, JS::DefaultNurseryBytes, nullptr); | m_cx = JS_NewContext(contextSize, JS::DefaultNurseryBytes, nullptr); | ||||
ENSURE(m_rt); // TODO: error handling | ENSURE(m_cx); // TODO: error handling | ||||
ENSURE(JS::InitSelfHostedCode(m_cx)); | |||||
JS::SetGCSliceCallback(m_rt, GCSliceCallbackHook); | JS::SetGCSliceCallback(m_cx, GCSliceCallbackHook); | ||||
JS_SetGCParameter(m_rt, JSGC_MAX_MALLOC_BYTES, m_ContextSize); | JS_SetGCParameter(m_cx, JSGC_MAX_MALLOC_BYTES, m_ContextSize); | ||||
JS_SetGCParameter(m_rt, JSGC_MAX_BYTES, m_ContextSize); | JS_SetGCParameter(m_cx, JSGC_MAX_BYTES, m_ContextSize); | ||||
JS_SetGCParameter(m_rt, JSGC_MODE, JSGC_MODE_INCREMENTAL); | JS_SetGCParameter(m_cx, JSGC_MODE, JSGC_MODE_INCREMENTAL); | ||||
// The whole heap-growth mechanism seems to work only for non-incremental GCs. | // The whole heap-growth mechanism seems to work only for non-incremental GCs. | ||||
// We disable it to make it more clear if full GCs happen triggered by this JSAPI internal mechanism. | // We disable it to make it more clear if full GCs happen triggered by this JSAPI internal mechanism. | ||||
JS_SetGCParameter(m_rt, JSGC_DYNAMIC_HEAP_GROWTH, false); | JS_SetGCParameter(m_cx, JSGC_DYNAMIC_HEAP_GROWTH, false); | ||||
JS_SetErrorReporter(m_rt, ScriptException::ErrorReporter); | |||||
m_cx = JS_NewContext(m_rt, STACK_CHUNK_SIZE); | JS_SetOffthreadIonCompilationEnabled(m_cx, true); | ||||
ENSURE(m_cx); // TODO: error handling | |||||
JS_SetOffthreadIonCompilationEnabled(m_rt, true); | |||||
// For GC debugging: | // For GC debugging: | ||||
// JS_SetGCZeal(m_cx, 2, JS_DEFAULT_ZEAL_FREQ); | // JS_SetGCZeal(m_cx, 2, JS_DEFAULT_ZEAL_FREQ); | ||||
JS_SetContextPrivate(m_cx, nullptr); | JS_SetContextPrivate(m_cx, nullptr); | ||||
JS_SetGlobalJitCompilerOption(m_rt, JSJITCOMPILER_ION_ENABLE, 1); | JS_SetGlobalJitCompilerOption(m_cx, JSJITCOMPILER_ION_ENABLE, 1); | ||||
JS_SetGlobalJitCompilerOption(m_rt, JSJITCOMPILER_BASELINE_ENABLE, 1); | JS_SetGlobalJitCompilerOption(m_cx, JSJITCOMPILER_BASELINE_ENABLE, 1); | ||||
JS::RuntimeOptionsRef(m_cx) | JS::ContextOptionsRef(m_cx) | ||||
.setExtraWarnings(true) | .setExtraWarnings(true) | ||||
.setWerror(false) | .setWerror(false) | ||||
.setStrictMode(true); | .setStrictMode(true); | ||||
ScriptEngine::GetSingleton().RegisterContext(m_cx); | ScriptEngine::GetSingleton().RegisterContext(m_cx); | ||||
} | } | ||||
ScriptContext::~ScriptContext() | ScriptContext::~ScriptContext() | ||||
{ | { | ||||
ENSURE(ScriptEngine::IsInitialised() && "The ScriptEngine must be active (initialized and not yet shut down) when destroying a ScriptContext!"); | ENSURE(ScriptEngine::IsInitialised() && "The ScriptEngine must be active (initialized and not yet shut down) when destroying a ScriptContext!"); | ||||
JS_DestroyContext(m_cx); | JS_DestroyContext(m_cx); | ||||
JS_DestroyRuntime(m_rt); | |||||
ScriptEngine::GetSingleton().UnRegisterContext(m_cx); | ScriptEngine::GetSingleton().UnRegisterContext(m_cx); | ||||
} | } | ||||
void ScriptContext::RegisterCompartment(JSCompartment* cmpt) | void ScriptContext::RegisterCompartment(JSCompartment* cmpt) | ||||
{ | { | ||||
ENSURE(cmpt); | ENSURE(cmpt); | ||||
m_Compartments.push_back(cmpt); | m_Compartments.push_back(cmpt); | ||||
} | } | ||||
void ScriptContext::UnRegisterCompartment(JSCompartment* cmpt) | void ScriptContext::UnRegisterCompartment(JSCompartment* cmpt) | ||||
{ | { | ||||
m_Compartments.remove(cmpt); | m_Compartments.remove(cmpt); | ||||
} | } | ||||
#define GC_DEBUG_PRINT 0 | #define GC_DEBUG_PRINT 0 | ||||
void ScriptContext::MaybeIncrementalGC(double delay) | void ScriptContext::MaybeIncrementalGC(double delay) | ||||
{ | { | ||||
PROFILE2("MaybeIncrementalGC"); | PROFILE2("MaybeIncrementalGC"); | ||||
if (JS::IsIncrementalGCEnabled(m_rt)) | if (JS::IsIncrementalGCEnabled(m_cx)) | ||||
{ | { | ||||
// The idea is to get the heap size after a completed GC and trigger the next GC when the heap size has | // The idea is to get the heap size after a completed GC and trigger the next GC when the heap size has | ||||
// reached m_LastGCBytes + X. | // reached m_LastGCBytes + X. | ||||
// In practice it doesn't quite work like that. When the incremental marking is completed, the sweeping kicks in. | // In practice it doesn't quite work like that. When the incremental marking is completed, the sweeping kicks in. | ||||
// The sweeping actually frees memory and it does this in a background thread (if JS_USE_HELPER_THREADS is set). | // The sweeping actually frees memory and it does this in a background thread (if JS_USE_HELPER_THREADS is set). | ||||
// While the sweeping is happening we already run scripts again and produce new garbage. | // While the sweeping is happening we already run scripts again and produce new garbage. | ||||
const int GCSliceTimeBudget = 30; // Milliseconds an incremental slice is allowed to run | const int GCSliceTimeBudget = 30; // Milliseconds an incremental slice is allowed to run | ||||
// Have a minimum time in seconds to wait between GC slices and before starting a new GC to distribute the GC | // Have a minimum time in seconds to wait between GC slices and before starting a new GC to distribute the GC | ||||
// load and to hopefully make it unnoticeable for the player. This value should be high enough to distribute | // load and to hopefully make it unnoticeable for the player. This value should be high enough to distribute | ||||
// the load well enough and low enough to make sure we don't run out of memory before we can start with the | // the load well enough and low enough to make sure we don't run out of memory before we can start with the | ||||
// sweeping. | // sweeping. | ||||
if (timer_Time() - m_LastGCCheck < delay) | if (timer_Time() - m_LastGCCheck < delay) | ||||
return; | return; | ||||
m_LastGCCheck = timer_Time(); | m_LastGCCheck = timer_Time(); | ||||
int gcBytes = JS_GetGCParameter(m_rt, JSGC_BYTES); | int gcBytes = JS_GetGCParameter(m_cx, JSGC_BYTES); | ||||
#if GC_DEBUG_PRINT | #if GC_DEBUG_PRINT | ||||
std::cout << "gcBytes: " << gcBytes / 1024 << " KB" << std::endl; | std::cout << "gcBytes: " << gcBytes / 1024 << " KB" << std::endl; | ||||
#endif | #endif | ||||
if (m_LastGCBytes > gcBytes || m_LastGCBytes == 0) | if (m_LastGCBytes > gcBytes || m_LastGCBytes == 0) | ||||
{ | { | ||||
#if GC_DEBUG_PRINT | #if GC_DEBUG_PRINT | ||||
printf("Setting m_LastGCBytes: %d KB \n", gcBytes / 1024); | printf("Setting m_LastGCBytes: %d KB \n", gcBytes / 1024); | ||||
#endif | #endif | ||||
m_LastGCBytes = gcBytes; | m_LastGCBytes = gcBytes; | ||||
} | } | ||||
// Run an additional incremental GC slice if the currently running incremental GC isn't over yet | // Run an additional incremental GC slice if the currently running incremental GC isn't over yet | ||||
// ... or | // ... or | ||||
// start a new incremental GC if the JS heap size has grown enough for a GC to make sense | // start a new incremental GC if the JS heap size has grown enough for a GC to make sense | ||||
if (JS::IsIncrementalGCInProgress(m_rt) || (gcBytes - m_LastGCBytes > m_HeapGrowthBytesGCTrigger)) | if (JS::IsIncrementalGCInProgress(m_cx) || (gcBytes - m_LastGCBytes > m_HeapGrowthBytesGCTrigger)) | ||||
{ | { | ||||
#if GC_DEBUG_PRINT | #if GC_DEBUG_PRINT | ||||
if (JS::IsIncrementalGCInProgress(m_rt)) | if (JS::IsIncrementalGCInProgress(m_cx)) | ||||
printf("An incremental GC cycle is in progress. \n"); | printf("An incremental GC cycle is in progress. \n"); | ||||
else | else | ||||
printf("GC needed because JSGC_BYTES - m_LastGCBytes > m_HeapGrowthBytesGCTrigger \n" | printf("GC needed because JSGC_BYTES - m_LastGCBytes > m_HeapGrowthBytesGCTrigger \n" | ||||
" JSGC_BYTES: %d KB \n m_LastGCBytes: %d KB \n m_HeapGrowthBytesGCTrigger: %d KB \n", | " JSGC_BYTES: %d KB \n m_LastGCBytes: %d KB \n m_HeapGrowthBytesGCTrigger: %d KB \n", | ||||
gcBytes / 1024, | gcBytes / 1024, | ||||
m_LastGCBytes / 1024, | m_LastGCBytes / 1024, | ||||
m_HeapGrowthBytesGCTrigger / 1024); | m_HeapGrowthBytesGCTrigger / 1024); | ||||
#endif | #endif | ||||
// A hack to make sure we never exceed the context size because we can't collect the memory | // A hack to make sure we never exceed the context size because we can't collect the memory | ||||
// fast enough. | // fast enough. | ||||
if (gcBytes > m_ContextSize / 2) | if (gcBytes > m_ContextSize / 2) | ||||
{ | { | ||||
if (JS::IsIncrementalGCInProgress(m_rt)) | if (JS::IsIncrementalGCInProgress(m_cx)) | ||||
{ | { | ||||
#if GC_DEBUG_PRINT | #if GC_DEBUG_PRINT | ||||
printf("Finishing incremental GC because gcBytes > m_ContextSize / 2. \n"); | printf("Finishing incremental GC because gcBytes > m_ContextSize / 2. \n"); | ||||
#endif | #endif | ||||
PrepareCompartmentsForIncrementalGC(); | PrepareCompartmentsForIncrementalGC(); | ||||
JS::FinishIncrementalGC(m_rt, JS::gcreason::REFRESH_FRAME); | JS::FinishIncrementalGC(m_cx, JS::gcreason::REFRESH_FRAME); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
if (gcBytes > m_ContextSize * 0.75) | if (gcBytes > m_ContextSize * 0.75) | ||||
{ | { | ||||
ShrinkingGC(); | ShrinkingGC(); | ||||
#if GC_DEBUG_PRINT | #if GC_DEBUG_PRINT | ||||
printf("Running shrinking GC because gcBytes > m_ContextSize * 0.75. \n"); | printf("Running shrinking GC because gcBytes > m_ContextSize * 0.75. \n"); | ||||
#endif | #endif | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
#if GC_DEBUG_PRINT | #if GC_DEBUG_PRINT | ||||
printf("Running full GC because gcBytes > m_ContextSize / 2. \n"); | printf("Running full GC because gcBytes > m_ContextSize / 2. \n"); | ||||
#endif | #endif | ||||
JS_GC(m_rt); | JS_GC(m_cx); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
#if GC_DEBUG_PRINT | #if GC_DEBUG_PRINT | ||||
if (!JS::IsIncrementalGCInProgress(m_rt)) | if (!JS::IsIncrementalGCInProgress(m_cx)) | ||||
printf("Starting incremental GC \n"); | printf("Starting incremental GC \n"); | ||||
else | else | ||||
printf("Running incremental GC slice \n"); | printf("Running incremental GC slice \n"); | ||||
#endif | #endif | ||||
PrepareCompartmentsForIncrementalGC(); | PrepareCompartmentsForIncrementalGC(); | ||||
if (!JS::IsIncrementalGCInProgress(m_rt)) | if (!JS::IsIncrementalGCInProgress(m_cx)) | ||||
JS::StartIncrementalGC(m_rt, GC_NORMAL, JS::gcreason::REFRESH_FRAME, GCSliceTimeBudget); | JS::StartIncrementalGC(m_cx, GC_NORMAL, JS::gcreason::REFRESH_FRAME, GCSliceTimeBudget); | ||||
else | else | ||||
JS::IncrementalGCSlice(m_rt, JS::gcreason::REFRESH_FRAME, GCSliceTimeBudget); | JS::IncrementalGCSlice(m_cx, JS::gcreason::REFRESH_FRAME, GCSliceTimeBudget); | ||||
} | } | ||||
m_LastGCBytes = gcBytes; | m_LastGCBytes = gcBytes; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
void ScriptContext::ShrinkingGC() | void ScriptContext::ShrinkingGC() | ||||
{ | { | ||||
JS_SetGCParameter(m_rt, JSGC_MODE, JSGC_MODE_COMPARTMENT); | JS_SetGCParameter(m_cx, JSGC_MODE, JSGC_MODE_ZONE); | ||||
JS::PrepareForFullGC(m_rt); | JS::PrepareForFullGC(m_cx); | ||||
JS::GCForReason(m_rt, GC_SHRINK, JS::gcreason::REFRESH_FRAME); | JS::GCForReason(m_cx, GC_SHRINK, JS::gcreason::REFRESH_FRAME); | ||||
JS_SetGCParameter(m_rt, JSGC_MODE, JSGC_MODE_INCREMENTAL); | JS_SetGCParameter(m_cx, JSGC_MODE, JSGC_MODE_INCREMENTAL); | ||||
} | } | ||||
void ScriptContext::PrepareCompartmentsForIncrementalGC() const | void ScriptContext::PrepareCompartmentsForIncrementalGC() const | ||||
{ | { | ||||
for (JSCompartment* const& cmpt : m_Compartments) | for (JSCompartment* const& cmpt : m_Compartments) | ||||
JS::PrepareZoneForGC(js::GetCompartmentZone(cmpt)); | JS::PrepareZoneForGC(js::GetCompartmentZone(cmpt)); | ||||
} | } |
Wildfire Games · Phabricator