#include "j9.h"
#include "jni.h"
#include "j9port.h"
#include "jvminit.h"
#include "modronopt.h"
#include "gcchk.h"
#include <string.h>
#include "CheckBase.hpp"
#include "CheckEngine.hpp"
#include "CheckError.hpp"
#include "CheckReporterTTY.hpp"
#include "GCExtensions.hpp"
#include "ModronTypes.hpp"
#include "ObjectModel.hpp"
#include "CycleState.hpp"
extern "C" {
void hookGcCycleStart(J9HookInterface** hook, UDATA eventNum, void* eventData, void* userData);
void hookGcCycleEnd(J9HookInterface** hook, UDATA eventNum, void* eventData, void* userData);
#if (defined(J9VM_GC_MODRON_SCAVENGER))
void hookScavengerBackOut(J9HookInterface** hook, UDATA eventNum, void* eventData, void* userData);
#endif
#if defined(J9VM_GC_GENERATIONAL)
void hookRememberedSetOverflow(J9HookInterface** hook, UDATA eventNum, void* eventData, void* userData);
#endif
void hookInvokeGCCheck(J9HookInterface** hook, UDATA eventNum, void* eventData, void* userData);
static IDATA OnLoad(J9JavaVM * javaVM, const char *options);
static IDATA OnUnload(J9JavaVM * javaVM);
IDATA
J9VMDllMain(J9JavaVM* vm, IDATA stage, void* reserved)
{
if (stage == ALL_VM_ARGS_CONSUMED) {
const char* options = "";
IDATA xcheckGCIndex = FIND_AND_CONSUME_ARG( OPTIONAL_LIST_MATCH, "-Xcheck:gc", NULL );
if (xcheckGCIndex >= 0) {
GET_OPTION_VALUE(xcheckGCIndex, ':', &options);
options = strchr(options, ':');
if (options == NULL) {
options = "";
} else {
options++;
}
}
return OnLoad(vm, options);
} else if (stage == LIBRARIES_ONUNLOAD) {
return OnUnload(vm);
} else {
return J9VMDLLMAIN_OK;
}
}
static IDATA
OnLoad(J9JavaVM *javaVM, const char *options)
{
GCCHK_Extensions *extensions;
GC_CheckReporter *reporter;
J9HookInterface** mmPrivateHooks = J9_HOOK_INTERFACE((MM_GCExtensions::getExtensions(javaVM))->privateHookInterface);
J9HookInterface** mmOmrHooks = J9_HOOK_INTERFACE((MM_GCExtensions::getExtensions(javaVM))->omrHookInterface);
PORT_ACCESS_FROM_JAVAVM(javaVM);
MM_Forge *forge = MM_GCExtensions::getExtensions(javaVM)->getForge();
if(!strcmp(options, "help")) {
GC_CheckCycle::printHelp(PORTLIB);
return J9VMDLLMAIN_SILENT_EXIT_VM;
}
extensions = (GCCHK_Extensions *)forge->allocate(sizeof(GCCHK_Extensions), MM_AllocationCategory::DIAGNOSTIC, J9_GET_CALLSITE());
if (!extensions) {
goto error_no_memory;
}
memset(extensions, 0, sizeof(GCCHK_Extensions));
(MM_GCExtensions::getExtensions(javaVM))->gcchkExtensions = extensions;
reporter = GC_CheckReporterTTY::newInstance(javaVM);
if (!reporter) {
goto error_no_memory;
}
extensions->checkEngine = (void *)GC_CheckEngine::newInstance(javaVM, reporter);
if (!extensions->checkEngine) {
goto error_no_memory;
}
extensions->checkCycle = (void *)GC_CheckCycle::newInstance(javaVM, (GC_CheckEngine *)extensions->checkEngine, options);
if(!extensions->checkCycle) {
goto error_no_memory;
}
if(!(((GC_CheckCycle *)extensions->checkCycle)->getMiscFlags() & J9MODRON_GCCHK_MANUAL)) {
(*mmOmrHooks)->J9HookRegisterWithCallSite(mmOmrHooks, J9HOOK_MM_OMR_GC_CYCLE_START, hookGcCycleStart, OMR_GET_CALLSITE(), NULL);
(*mmOmrHooks)->J9HookRegisterWithCallSite(mmOmrHooks, J9HOOK_MM_OMR_GC_CYCLE_END, hookGcCycleEnd, OMR_GET_CALLSITE(), NULL);
#if defined(J9VM_GC_MODRON_SCAVENGER)
(*mmPrivateHooks)->J9HookRegisterWithCallSite(mmPrivateHooks, J9HOOK_MM_PRIVATE_SCAVENGER_BACK_OUT, hookScavengerBackOut, OMR_GET_CALLSITE(), NULL);
#endif
#if defined(J9VM_GC_GENERATIONAL)
(*mmPrivateHooks)->J9HookRegisterWithCallSite(mmPrivateHooks, J9HOOK_MM_PRIVATE_REMEMBEREDSET_OVERFLOW, hookRememberedSetOverflow, OMR_GET_CALLSITE(), NULL);
#endif
}
(*mmPrivateHooks)->J9HookRegisterWithCallSite(mmPrivateHooks, J9HOOK_MM_PRIVATE_INVOKE_GC_CHECK, hookInvokeGCCheck, OMR_GET_CALLSITE(), NULL);
javaVM->requiredDebugAttributes |= J9VM_DEBUG_ATTRIBUTE_ALLOW_USER_HEAP_WALK;
if (((GC_CheckCycle *)extensions->checkCycle)->getMiscFlags() & J9MODRON_GCCHK_VERBOSE) {
j9tty_printf(PORTLIB, "<gc check installed>\n");
}
return J9VMDLLMAIN_OK;
error_no_memory:
if (extensions) {
if (extensions->checkEngine) {
((GC_CheckEngine *)extensions->checkEngine)->kill();
} else if (reporter) {
reporter->kill();
}
if(extensions->checkCycle) {
((GC_CheckCycle *)extensions->checkCycle)->kill();
}
forge->free(extensions);
((MM_GCExtensions *)javaVM->gcExtensions)->gcchkExtensions = NULL;
}
return J9VMDLLMAIN_FAILED;
}
static IDATA
OnUnload(J9JavaVM *javaVM )
{
GCCHK_Extensions *extensions = (GCCHK_Extensions *)(MM_GCExtensions::getExtensions(javaVM))->gcchkExtensions;
MM_Forge *forge = MM_GCExtensions::getExtensions(javaVM)->getForge();
if(extensions) {
((GC_CheckEngine *)extensions->checkEngine)->kill();
((GC_CheckCycle *)extensions->checkCycle)->kill();
forge->free(extensions);
((MM_GCExtensions *)javaVM->gcExtensions)->gcchkExtensions = NULL;
}
return J9VMDLLMAIN_OK;
}
#if (defined(J9VM_GC_MODRON_SCAVENGER))
bool
excludeLocalGc(J9JavaVM *javaVM)
{
MM_GCExtensions *gcExtensions = MM_GCExtensions::getExtensions(javaVM);
GCCHK_Extensions *extensions = (GCCHK_Extensions *)gcExtensions->gcchkExtensions;
GC_CheckEngine *gcCheck = (GC_CheckEngine *)extensions->checkEngine;
GC_CheckCycle *cycle = (GC_CheckCycle *)extensions->checkCycle;
bool ret = false;
if ((cycle->getMiscFlags() & J9MODRON_GCCHK_SUPPRESS_LOCAL) ||
((cycle->getMiscFlags() & J9MODRON_GCCHK_REMEMBEREDSET_OVERFLOW) && (!gcCheck->_rsOverflowState))) {
return true;
}
if (gcExtensions->isConcurrentScavengerEnabled() && gcExtensions->isScavengerBackOutFlagRaised()) {
return true;
}
if ((cycle->getMiscFlags() & J9MODRON_GCCHK_SCAVENGER_BACKOUT) && (!gcCheck->_scavengerBackout)) {
return true;
}
if (cycle->getMiscFlags() & J9MODRON_GCCHK_LOCAL_INTERVAL) {
if ((extensions->localGcCount % extensions->localGcInterval) == 0) {
return false;
} else {
ret = true;
}
}
if (cycle->getMiscFlags() & J9MODRON_GCCHK_INTERVAL) {
return ( ((extensions->globalGcCount + extensions->localGcCount) % extensions->gcInterval) != 0 );
}
if (cycle->getMiscFlags() & J9MODRON_GCCHK_START_INDEX) {
return ((extensions->globalGcCount + extensions->localGcCount) < extensions->gcStartIndex);
}
return ret;
}
void
hookScavengerBackOut(J9HookInterface** hook, UDATA eventNum, void* eventData, void* userData)
{
MM_ScavengerBackOutEvent* event = (MM_ScavengerBackOutEvent*)eventData;
bool value = (event->value == TRUE);
GCCHK_Extensions *extensions = (GCCHK_Extensions *)(MM_GCExtensions::getExtensions(event->omrVM))->gcchkExtensions;
GC_CheckEngine *checkEngine = (GC_CheckEngine *)extensions->checkEngine;
GC_CheckCycle *cycle = (GC_CheckCycle *)extensions->checkCycle;
if (cycle->getMiscFlags() & J9MODRON_GCCHK_SCAVENGER_BACKOUT) {
checkEngine->_scavengerBackout = value;
}
}
#endif
bool
excludeGlobalGc(J9VMThread* vmThread)
{
J9JavaVM *javaVM = vmThread->javaVM;
MM_GCExtensions *gcExtensions = MM_GCExtensions::getExtensions(javaVM);
GCCHK_Extensions *extensions = (GCCHK_Extensions *)gcExtensions->gcchkExtensions;
GC_CheckCycle *cycle = (GC_CheckCycle *)extensions->checkCycle;
GC_CheckEngine *gcCheck = (GC_CheckEngine *)extensions->checkEngine;
bool ret = false;
UDATA gcCount;
if (gcExtensions->isConcurrentScavengerEnabled() && gcExtensions->isScavengerBackOutFlagRaised() && (OMRVMSTATE_GC_CHECK_BEFORE_GC == vmThread->omrVMThread->vmState)) {
return true;
}
if ((cycle->getMiscFlags() & J9MODRON_GCCHK_SUPPRESS_GLOBAL) ||
(cycle->getMiscFlags() & J9MODRON_GCCHK_REMEMBEREDSET_OVERFLOW)) {
return true;
}
if (cycle->getMiscFlags() & J9MODRON_GCCHK_SCAVENGER_BACKOUT) {
if (!(gcExtensions->isConcurrentScavengerEnabled() && gcCheck->_scavengerBackout)) {
return true;
}
}
if (cycle->getMiscFlags() & J9MODRON_GCCHK_GLOBAL_INTERVAL) {
if ( (extensions->globalGcCount % extensions->globalGcInterval) == 0 ) {
return false;
} else {
ret = true;
}
}
gcCount = extensions->globalGcCount;
#if defined(J9VM_GC_MODRON_SCAVENGER)
gcCount += extensions->localGcCount;
#endif
if (cycle->getMiscFlags() & J9MODRON_GCCHK_INTERVAL) {
return ((gcCount % extensions->gcInterval) != 0 );
}
if (cycle->getMiscFlags() & J9MODRON_GCCHK_START_INDEX) {
return (gcCount < extensions->gcStartIndex);
}
return ret;
}
void
hookGcCycleStart(J9HookInterface** hook, UDATA eventNum, void* eventData, void* userData)
{
MM_GCCycleStartEvent* event = (MM_GCCycleStartEvent*)eventData;
J9VMThread* vmThread = (J9VMThread*)MM_EnvironmentBase::getEnvironment(event->omrVMThread)->getLanguageVMThread();
J9JavaVM *javaVM = vmThread->javaVM;
GCCHK_Extensions *extensions = (GCCHK_Extensions *)(MM_GCExtensions::getExtensions(javaVM))->gcchkExtensions;
GC_CheckCycle *cycle = (GC_CheckCycle *)extensions->checkCycle;
PORT_ACCESS_FROM_JAVAVM(javaVM);
UDATA oldVMState = vmThread->omrVMThread->vmState;
vmThread->omrVMThread->vmState = OMRVMSTATE_GC_CHECK_BEFORE_GC;
if (OMR_GC_CYCLE_TYPE_GLOBAL == event->cycleType) {
++extensions->globalGcCount;
if (!excludeGlobalGc(vmThread)) {
if (cycle->getMiscFlags() & J9MODRON_GCCHK_VERBOSE) {
j9tty_printf(PORTLIB, "<gc check: start verifying slots before global gc (%zu)>\n", extensions->globalGcCount);
}
cycle->run(invocation_global_start);
if (cycle->getMiscFlags() & J9MODRON_GCCHK_VERBOSE) {
j9tty_printf(PORTLIB, "<gc check: finished verifying slots before global gc (%zu)>\n", extensions->globalGcCount);
}
}
}
#if defined(J9VM_GC_MODRON_SCAVENGER)
else if (OMR_GC_CYCLE_TYPE_SCAVENGE == event->cycleType) {
++extensions->localGcCount;
if (!excludeLocalGc(javaVM)) {
if (cycle->getMiscFlags() & J9MODRON_GCCHK_VERBOSE) {
j9tty_printf(PORTLIB, "<gc check: start verifying slots before local gc (%zu)>\n", extensions->localGcCount);
}
cycle->run(invocation_local_start);
if (cycle->getMiscFlags() & J9MODRON_GCCHK_VERBOSE) {
j9tty_printf(PORTLIB, "<gc check: finished verifying slots before local gc (%zu)>\n", extensions->localGcCount);
}
}
}
#endif
else {
++extensions->globalGcCount;
if (!excludeGlobalGc(vmThread)) {
if (cycle->getMiscFlags() & J9MODRON_GCCHK_VERBOSE) {
j9tty_printf(PORTLIB, "<gc check: start verifying slots before default gc (%zu)>\n", extensions->globalGcCount);
}
cycle->run(invocation_global_start);
if (cycle->getMiscFlags() & J9MODRON_GCCHK_VERBOSE) {
j9tty_printf(PORTLIB, "<gc check: finished verifying slots before default gc (%zu)>\n", extensions->globalGcCount);
}
}
}
vmThread->omrVMThread->vmState = oldVMState;
}
void
hookGcCycleEnd(J9HookInterface** hook, UDATA eventNum, void* eventData, void* userData)
{
MM_GCCycleEndEvent* event = (MM_GCCycleEndEvent*)eventData;
J9VMThread* vmThread = (J9VMThread*)MM_EnvironmentBase::getEnvironment(event->omrVMThread)->getLanguageVMThread();
J9JavaVM *javaVM = vmThread->javaVM;
GCCHK_Extensions *extensions = (GCCHK_Extensions *)(MM_GCExtensions::getExtensions(javaVM))->gcchkExtensions;
GC_CheckCycle *cycle = (GC_CheckCycle *)extensions->checkCycle;
PORT_ACCESS_FROM_JAVAVM(javaVM);
UDATA oldVMState = vmThread->omrVMThread->vmState;
vmThread->omrVMThread->vmState = OMRVMSTATE_GC_CHECK_AFTER_GC;
if (OMR_GC_CYCLE_TYPE_GLOBAL == event->cycleType) {
if (!excludeGlobalGc(vmThread)) {
if (cycle->getMiscFlags() & J9MODRON_GCCHK_VERBOSE) {
j9tty_printf(PORTLIB, "<gc check: start verifying slots after global gc (%zu)>\n", extensions->globalGcCount);
}
cycle->run(invocation_global_end);
if (cycle->getMiscFlags() & J9MODRON_GCCHK_VERBOSE) {
j9tty_printf(PORTLIB, "<gc check: finished verifying slots after global gc (%zu)>\n", extensions->globalGcCount);
}
}
}
#if defined(J9VM_GC_MODRON_SCAVENGER)
else if (OMR_GC_CYCLE_TYPE_SCAVENGE == event->cycleType) {
if (!excludeLocalGc(javaVM)) {
if (cycle->getMiscFlags() & J9MODRON_GCCHK_VERBOSE) {
j9tty_printf(PORTLIB, "<gc check: start verifying slots after local gc (%zu)>\n", extensions->localGcCount);
}
cycle->run(invocation_local_end);
if (cycle->getMiscFlags() & J9MODRON_GCCHK_VERBOSE) {
j9tty_printf(PORTLIB, "<gc check: finished verifying slots after local gc (%zu)>\n", extensions->localGcCount);
}
}
}
#endif
else {
if (!excludeGlobalGc(vmThread)) {
if (cycle->getMiscFlags() & J9MODRON_GCCHK_VERBOSE) {
j9tty_printf(PORTLIB, "<gc check: start verifying slots after default gc (%zu)>\n", extensions->globalGcCount);
}
cycle->run(invocation_global_end);
if (cycle->getMiscFlags() & J9MODRON_GCCHK_VERBOSE) {
j9tty_printf(PORTLIB, "<gc check: finished verifying slots after default gc (%zu)>\n", extensions->globalGcCount);
}
}
}
vmThread->omrVMThread->vmState = oldVMState;
}
#if defined(J9VM_GC_GENERATIONAL)
void
hookRememberedSetOverflow(J9HookInterface** hook, UDATA eventNum, void* eventData, void* userData)
{
MM_RememberedSetOverflowEvent* event = (MM_RememberedSetOverflowEvent*)eventData;
GCCHK_Extensions *extensions = (GCCHK_Extensions *)(MM_GCExtensions::getExtensions(event->currentThread))->gcchkExtensions;
GC_CheckEngine *checkEngine = (GC_CheckEngine *)extensions->checkEngine;
GC_CheckCycle *cycle = (GC_CheckCycle *)extensions->checkCycle;
if (cycle->getMiscFlags() & J9MODRON_GCCHK_REMEMBEREDSET_OVERFLOW) {
checkEngine->_rsOverflowState = MM_GCExtensions::getExtensions(event->currentThread)->isRememberedSetInOverflowState();
}
}
#endif
void
hookInvokeGCCheck(J9HookInterface** hook, UDATA eventNum, void* eventData, void* userData)
{
MM_InvokeGCCheckEvent* event = (MM_InvokeGCCheckEvent*)eventData;
J9JavaVM *javaVM = static_cast<J9JavaVM*>(event->omrVM->_language_vm);
GCCHK_Extensions *extensions = (GCCHK_Extensions *)((MM_GCExtensions *)javaVM->gcExtensions)->gcchkExtensions;
GC_CheckEngine *checkEngine = (GC_CheckEngine *)extensions->checkEngine;
GC_CheckCycle *cycle;
if (checkEngine) {
cycle = GC_CheckCycle::newInstance(javaVM, checkEngine, event->options, event->invocationNumber);
if(cycle) {
cycle->run(invocation_manual);
cycle->kill();
}
}
}
}