From 9ca5d97a890b08f95d3c67f8a6996d6e1368020a Mon Sep 17 00:00:00 2001 From: Christian Despres Date: Thu, 14 Nov 2024 16:38:10 -0500 Subject: [PATCH] Track methods with AOT bodies with dependencies When a method is initialized in jitHookInitializeSendTarget, the SCC will be checked to see if it has a stored AOT body that was compiled with tracked dependencies. If it does, and all of them are satisfied, the method will be given an initial count of 0. If not all of the dependencies are satisfied, the method will be tracked by the TR_DependencyTable until the dependencies are satisfied, at which point its count will be set to zero, triggering an AOT load on its next invocation. Signed-off-by: Christian Despres --- .../compiler/control/CompilationThread.cpp | 6 + runtime/compiler/control/DLLMain.cpp | 2 + runtime/compiler/control/HookedByTheJit.cpp | 39 ++- runtime/compiler/env/DependencyTable.cpp | 251 +++++++++++++++++- runtime/compiler/env/DependencyTable.hpp | 103 ++++++- runtime/compiler/env/J9SharedCache.cpp | 20 +- runtime/compiler/env/J9SharedCache.hpp | 14 +- 7 files changed, 396 insertions(+), 39 deletions(-) diff --git a/runtime/compiler/control/CompilationThread.cpp b/runtime/compiler/control/CompilationThread.cpp index 902593b910e..362a7dc1f8a 100644 --- a/runtime/compiler/control/CompilationThread.cpp +++ b/runtime/compiler/control/CompilationThread.cpp @@ -59,6 +59,7 @@ #include "env/StackMemoryRegion.hpp" #include "env/jittypes.h" #include "env/ClassTableCriticalSection.hpp" +#include "env/DependencyTable.hpp" #include "env/PersistentCHTable.hpp" #include "env/VMAccessCriticalSection.hpp" #include "env/VerboseLog.hpp" @@ -8253,6 +8254,11 @@ TR::CompilationInfoPerThreadBase::compile(J9VMThread * vmThread, vmThread->omrVMThread->vmState = J9VMSTATE_JIT | J9VMSTATE_MINOR; vmThread->jitMethodToBeCompiled = method; + // If method is being compiled, then the dependency table no longer needs to + // track it. Let the table know. + if (auto dependencyTable = getCompilationInfo()->getPersistentInfo()->getAOTDependencyTable()) + dependencyTable->methodWillBeCompiled(method); + try { TR::RawAllocator rawAllocator(vmThread->javaVM); diff --git a/runtime/compiler/control/DLLMain.cpp b/runtime/compiler/control/DLLMain.cpp index ad9b464e958..0f0cd1f63e0 100644 --- a/runtime/compiler/control/DLLMain.cpp +++ b/runtime/compiler/control/DLLMain.cpp @@ -526,6 +526,8 @@ IDATA J9VMDllMain(J9JavaVM* vm, IDATA stage, void * reserved) persistentInfo->setAOTDependencyTable(dependencyTable); } #endif /* !defined(PERSISTENT_COLLECTIONS_UNSUPPORTED) */ + if (!persistentInfo->getAOTDependencyTable()) + persistentInfo->setTrackAOTDependencies(false); } } else diff --git a/runtime/compiler/control/HookedByTheJit.cpp b/runtime/compiler/control/HookedByTheJit.cpp index 966c91dca83..c9ed30fb342 100644 --- a/runtime/compiler/control/HookedByTheJit.cpp +++ b/runtime/compiler/control/HookedByTheJit.cpp @@ -566,20 +566,37 @@ static void jitHookInitializeSendTarget(J9HookInterface * * hook, UDATA eventNum #endif /* defined(J9VM_OPT_CRIU_SUPPORT) */ ) { - int32_t scount = optionsAOT->getInitialSCount(); - uint16_t newScount = 0; - if (sc && sc->isHint(method, TR_HintFailedValidation, &newScount)) + if (auto dependencyTable = compInfo->getPersistentInfo()->getAOTDependencyTable()) { - if ((scount == TR_QUICKSTART_INITIAL_SCOUNT) || (scount == TR_INITIAL_SCOUNT)) - { // If scount is not user specified (coarse way due to info being lost from options parsing) - // TODO: Is casting the best thing to do here? - scount= std::min(getCount(romMethod, optionsJIT, optionsAOT), static_cast(newScount) ); // Find what would've been normal count for this method and - // make sure new scount isn't larger than that - if (optionsAOT->getVerboseOption(TR_VerboseSCHints) || optionsJIT->getVerboseOption(TR_VerboseSCHints)) - TR_VerboseLog::writeLineLocked(TR_Vlog_SCHINTS,"Found hint in sc, increase scount to: %d, wanted scount: %d", scount, newScount); + bool dependenciesSatisfied = false; + // TODO: Obey user counts if they are set for this method + // + // TODO: The initial count for unsatisfied dependencies should + // be revisited. It can likely be lower, particularly if AOT + // loads are suppressed if dependencies are unsatisfied, + // though remember that counts can decrease faster in the warm + // run due to sampling. + if (dependencyTable->trackMethod(vmThread, method, romMethod, dependenciesSatisfied)) + count = dependenciesSatisfied ? 0 : TR_DEFAULT_INITIAL_COUNT; + } + + if (count == -1) + { + int32_t scount = optionsAOT->getInitialSCount(); + uint16_t newScount = 0; + if (sc && sc->isHint(method, TR_HintFailedValidation, &newScount)) + { + if ((scount == TR_QUICKSTART_INITIAL_SCOUNT) || (scount == TR_INITIAL_SCOUNT)) + { // If scount is not user specified (coarse way due to info being lost from options parsing) + // TODO: Is casting the best thing to do here? + scount= std::min(getCount(romMethod, optionsJIT, optionsAOT), static_cast(newScount) ); // Find what would've been normal count for this method and + // make sure new scount isn't larger than that + if (optionsAOT->getVerboseOption(TR_VerboseSCHints) || optionsJIT->getVerboseOption(TR_VerboseSCHints)) + TR_VerboseLog::writeLineLocked(TR_Vlog_SCHINTS,"Found hint in sc, increase scount to: %d, wanted scount: %d", scount, newScount); + } } + count = scount; } - count = scount; compInfo->incrementNumMethodsFoundInSharedCache(); } // AOT Body not in SCC, so scount was not set diff --git a/runtime/compiler/env/DependencyTable.cpp b/runtime/compiler/env/DependencyTable.cpp index 6569df4aa0f..5f5c1cfefe4 100644 --- a/runtime/compiler/env/DependencyTable.cpp +++ b/runtime/compiler/env/DependencyTable.cpp @@ -20,7 +20,7 @@ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 OR GPL-2.0-only WITH OpenJDK-assembly-exception-1.0 *******************************************************************************/ - +#include "control/CompilationRuntime.hpp" #include "control/CompilationThread.hpp" #include "env/DependencyTable.hpp" #include "env/J9SharedCache.hpp" @@ -32,9 +32,126 @@ TR_AOTDependencyTable::TR_AOTDependencyTable(TR_J9SharedCache *sharedCache) : _isActive(true), _sharedCache(sharedCache), _tableMonitor(TR::Monitor::create("JIT-AOTDependencyTableMonitor")), - _offsetMap(decltype(_offsetMap)::allocator_type(TR::Compiler->persistentAllocator())) + _offsetMap(decltype(_offsetMap)::allocator_type(TR::Compiler->persistentAllocator())), + _methodMap(decltype(_methodMap)::allocator_type(TR::Compiler->persistentAllocator())), + _pendingLoads(decltype(_pendingLoads)::allocator_type(TR::Compiler->persistentAllocator())) { } +bool +TR_AOTDependencyTable::trackMethod(J9VMThread *vmThread, J9Method *method, J9ROMMethod *romMethod, bool &dependenciesSatisfied) + { + const uintptr_t *methodDependencies = NULL; + if (!_sharedCache->methodHasAOTBodyWithDependencies(vmThread, romMethod, methodDependencies)) + return false; + + if (!methodDependencies) + { + dependenciesSatisfied = true; + return true; + } + + OMR::CriticalSection cs(_tableMonitor); + if (!isActive()) + return false; + + try + { + uintptr_t totalDependencies = *methodDependencies; + uintptr_t numberRemainingDependencies = totalDependencies; + + auto m_it = _methodMap.insert({method, {0, methodDependencies}}); + auto methodEntry = &(*m_it.first); + + for (size_t i = 1; i <= totalDependencies; ++i) + { + bool needsInitialization = false; + uintptr_t chainOffset = decodeDependencyOffset(methodDependencies[i], needsInitialization); + uintptr_t offset = _sharedCache->startingROMClassOffsetOfClassChain(_sharedCache->pointerFromOffsetInSharedCache(chainOffset)); + auto entry = getOffsetEntry(offset, true); + if (needsInitialization) + entry->_waitingInitMethods.insert(methodEntry); + else + entry->_waitingLoadMethods.insert(methodEntry); + + if (findCandidateForDependency(entry->_loadedClasses, needsInitialization)) + numberRemainingDependencies -= 1; + } + + if (numberRemainingDependencies == 0) + { + stopTracking(methodEntry); + dependenciesSatisfied = true; + } + else + { + methodEntry->second._remainingDependencies = numberRemainingDependencies; + } + } + catch (std::exception&) + { + deactivateTable(); + return false; + } + + return true; + } + +void +TR_AOTDependencyTable::methodWillBeCompiled(J9Method *method) + { + OMR::CriticalSection cs(_tableMonitor); + if (!isActive()) + return; + + // TODO: For now we simply stop tracking method if for some reason a + // compilation is triggered for it, but if the compilation in question is an + // AOT load we might consider preventing the load from taking place (by + // increasing the counts and continuing to track the method, or marking the + // method as ineligible for loads and giving up on tracking it). + stopTracking(method); + } + +void +TR_AOTDependencyTable::stopTracking(MethodEntryRef entry) + { + auto methodEntry = entry->second; + auto dependencyChain = methodEntry._dependencyChain; + auto dependencyChainLength = *dependencyChain; + + for (size_t i = 1; i <= dependencyChainLength; ++i) + { + bool needsInitialization = false; + uintptr_t chainOffset = decodeDependencyOffset(dependencyChain[i], needsInitialization); + uintptr_t offset = _sharedCache->startingROMClassOffsetOfClassChain(_sharedCache->pointerFromOffsetInSharedCache(chainOffset)); + + auto o_it = _offsetMap.find(offset); + + if (needsInitialization) + o_it->second._waitingInitMethods.erase(entry); + else + o_it->second._waitingLoadMethods.erase(entry); + + eraseOffsetEntryIfEmpty(o_it->second, offset); + } + + _methodMap.erase(entry->first); + } + +void +TR_AOTDependencyTable::stopTracking(J9Method *method) + { + auto entry = _methodMap.find(method); + if (entry != _methodMap.end()) + stopTracking(&*entry); + } + +void +TR_AOTDependencyTable::eraseOffsetEntryIfEmpty(const OffsetEntry &entry, uintptr_t offset) + { + if (entry._loadedClasses.empty() && entry._waitingInitMethods.empty() && entry._waitingLoadMethods.empty()) + _offsetMap.erase(offset); + } + void TR_AOTDependencyTable::classLoadEvent(TR_OpaqueClassBlock *clazz, bool isClassLoad, bool isClassInitialization) { @@ -60,17 +177,65 @@ TR_AOTDependencyTable::classLoadEvent(TR_OpaqueClassBlock *clazz, bool isClassLo catch (std::exception&) { deactivateTable(); + return; + } + + resolvePendingLoads(); + } + +void +TR_AOTDependencyTable::registerSatisfaction(PersistentUnorderedSet waitingMethods) + { + for (auto &entry: waitingMethods) + { + if (entry->second._remainingDependencies == 1) + _pendingLoads.insert(entry); + else + --entry->second._remainingDependencies; } } void TR_AOTDependencyTable::classLoadEventAtOffset(J9Class *ramClass, uintptr_t offset, bool isClassLoad, bool isClassInitialization) { - auto entry = getOffsetEntry(offset, isClassLoad); - TR_ASSERT(entry || !isClassLoad, "Class %p offset %lu initialized without loading"); + auto offsetEntry = getOffsetEntry(offset, isClassLoad); + // We only need to check for chain validity on load, because for + // initialization (that isn't a simultaneous load) we can simply check to see + // if ramClass is already tracked and abort the update if not. + if (!offsetEntry) + return; + if (!isClassLoad && (offsetEntry->_loadedClasses.find(ramClass) == offsetEntry->_loadedClasses.end())) + return; + + // Check for dependency satisfaction if this is the first class initialized + // for this offset. + if (isClassInitialization) + { + // TODO: need to confirm that ramClass->initializeStatus will never itself + // be J9ClassInitSucceeded, in which case the loop below can be replaced + // with !findCandidateForDependency() + bool existingInit = false; + for (const auto& entry: offsetEntry->_loadedClasses) + { + if ((J9ClassInitSucceeded == entry->initializeStatus) && (entry != ramClass)) + { + existingInit = true; + break; + } + } + if (!existingInit) + registerSatisfaction(offsetEntry->_waitingInitMethods); + } + + // Track the class, and also check for dependency satisfaction if this is the + // first class loaded for this offset if (isClassLoad) - entry->_loadedClasses.insert(ramClass); + { + if (!findCandidateForDependency(offsetEntry->_loadedClasses, false)) + registerSatisfaction(offsetEntry->_waitingLoadMethods); + offsetEntry->_loadedClasses.insert(ramClass); + } } OffsetEntry * @@ -83,7 +248,9 @@ TR_AOTDependencyTable::getOffsetEntry(uintptr_t offset, bool create) if (create) { PersistentUnorderedSet loadedClasses(PersistentUnorderedSet::allocator_type(TR::Compiler->persistentAllocator())); - return &(*_offsetMap.insert(it, {offset, {loadedClasses}})).second; + PersistentUnorderedSet waitingLoadMethods(PersistentUnorderedSet::allocator_type(TR::Compiler->persistentAllocator())); + PersistentUnorderedSet waitingInitMethods(PersistentUnorderedSet::allocator_type(TR::Compiler->persistentAllocator())); + return &(*_offsetMap.insert(it, {offset, {loadedClasses, waitingLoadMethods, waitingInitMethods}})).second; } return NULL; @@ -101,7 +268,19 @@ TR_AOTDependencyTable::invalidateUnloadedClass(TR_OpaqueClassBlock *clazz) if (!isActive()) return; - invalidateClassAtOffset((J9Class *)clazz, classOffset); + auto ramClass = (J9Class *)clazz; + if (invalidateClassAtOffset(ramClass, classOffset)) + invalidateMethodsOfClass(ramClass); + } + +void +TR_AOTDependencyTable::registerDissatisfaction(PersistentUnorderedSet waitingMethods) + { + for (auto& entry: waitingMethods) + { + ++entry->second._remainingDependencies; + _pendingLoads.erase(entry); + } } bool @@ -111,13 +290,33 @@ TR_AOTDependencyTable::invalidateClassAtOffset(J9Class *ramClass, uintptr_t romC if (entry) { entry->_loadedClasses.erase(ramClass); + + // Update the waiting methods if the removal of ramClass caused a + // dependency to become unsatisfied if (entry->_loadedClasses.empty()) - _offsetMap.erase(romClassOffset); + { + registerDissatisfaction(entry->_waitingLoadMethods); + registerDissatisfaction(entry->_waitingInitMethods); + eraseOffsetEntryIfEmpty(*entry, romClassOffset); + } + else if (!findCandidateForDependency(entry->_loadedClasses, true)) + { + registerDissatisfaction(entry->_waitingInitMethods); + } + return true; } + return false; } +void +TR_AOTDependencyTable::invalidateMethodsOfClass(J9Class *ramClass) + { + for (uint32_t i = 0; i < ramClass->romClass->romMethodCount; i++) + stopTracking(&ramClass->ramMethods[i]); + } + // If an entry exists for a class, remove it. Otherwise, if we should // revalidate, add an entry if the class has a valid chain. void @@ -159,6 +358,7 @@ TR_AOTDependencyTable::invalidateRedefinedClass(TR_PersistentCHTable *table, TR_ // old and fresh class pointers. if (invalidateClassAtOffset((J9Class *)oldClass, oldClassOffset)) { + invalidateMethodsOfClass((J9Class *)oldClass); auto freshRamClass = (J9Class *)freshClass; bool initialized = J9ClassInitSucceeded == freshRamClass->initializeStatus; classLoadEventAtOffset(freshRamClass, freshClassOffset, true, initialized); @@ -167,8 +367,10 @@ TR_AOTDependencyTable::invalidateRedefinedClass(TR_PersistentCHTable *table, TR_ catch (std::exception&) { deactivateTable(); + return; } + resolvePendingLoads(); return; } @@ -186,6 +388,9 @@ TR_AOTDependencyTable::invalidateRedefinedClass(TR_PersistentCHTable *table, TR_ try { + // Invalidate the methods of oldClass first, so _pendingLoads doesn't have + // to be cleared of invalidated method entries + invalidateMethodsOfClass((J9Class *)oldClass); for (auto iter = classList.begin(); iter != classList.end(); iter++) { auto clazz = (J9Class *)(*iter)->getClassId(); @@ -199,6 +404,22 @@ TR_AOTDependencyTable::invalidateRedefinedClass(TR_PersistentCHTable *table, TR_ { deactivateTable(); } + + resolvePendingLoads(); + } + +void +TR_AOTDependencyTable::resolvePendingLoads() + { + for (auto& entry: _pendingLoads) + { + auto method = entry->first; + auto count = TR::CompilationInfo::getInvocationCount(method); + while ((count > 0) && !TR::CompilationInfo::setInvocationCount(method, count, 0)) + count = TR::CompilationInfo::getInvocationCount(method); + stopTracking(entry); + } + _pendingLoads.clear(); } TR_OpaqueClassBlock * @@ -213,10 +434,16 @@ TR_AOTDependencyTable::findClassCandidate(uintptr_t romClassOffset) if (it == _offsetMap.end()) return NULL; - for (const auto& clazz: it->second._loadedClasses) + return (TR_OpaqueClassBlock *)findCandidateForDependency(it->second._loadedClasses, true); + } + +J9Class * +TR_AOTDependencyTable::findCandidateForDependency(const PersistentUnorderedSet &loadedClasses, bool needsInitialization) + { + for (const auto& clazz: loadedClasses) { - if (J9ClassInitSucceeded == clazz->initializeStatus) - return (TR_OpaqueClassBlock *)clazz; + if (!needsInitialization || (J9ClassInitSucceeded == clazz->initializeStatus)) + return clazz; } return NULL; @@ -226,6 +453,8 @@ void TR_AOTDependencyTable::deactivateTable() { _offsetMap.clear(); + _methodMap.clear(); + _pendingLoads.clear(); setInactive(); } diff --git a/runtime/compiler/env/DependencyTable.hpp b/runtime/compiler/env/DependencyTable.hpp index 40dd5921354..293465a4790 100644 --- a/runtime/compiler/env/DependencyTable.hpp +++ b/runtime/compiler/env/DependencyTable.hpp @@ -34,6 +34,8 @@ class TR_J9SharedCache; class TR_AOTDependencyTable { public: + bool trackMethod(J9VMThread *vmThread, J9Method *method, J9ROMMethod *romMethod, bool &dependenciesSatisfied) { return false; } + void methodIsBeingCompiled(J9Method *method) {} void classLoadEvent(TR_OpaqueClassBlock *ramClass, bool isClassLoad, bool isClassInitialization) {} void invalidateUnloadedClass(TR_OpaqueClassBlock *ramClass) {} void invalidateRedefinedClass(TR_PersistentCHTable *table, TR_J9VMBase *fej9, TR_OpaqueClassBlock *oldClass, TR_OpaqueClassBlock *freshClass) {} @@ -42,9 +44,26 @@ class TR_AOTDependencyTable #else +struct MethodEntry + { + // The number of dependencies left to be satisfied + uintptr_t _remainingDependencies; + // The dependency chain stored in the local SCC (used to stop tracking a + // method) + const uintptr_t *_dependencyChain; + }; + +typedef std::pair* MethodEntryRef; + struct OffsetEntry { + // Classes that have been loaded and have a particular valid class chain + // offset PersistentUnorderedSet _loadedClasses; + // Methods waiting for a class with this offset to be loaded + PersistentUnorderedSet _waitingLoadMethods; + // Methods waiting for a class with this offset to be initialized + PersistentUnorderedSet _waitingInitMethods; }; /** @@ -70,34 +89,101 @@ class TR_AOTDependencyTable public: TR_PERSISTENT_ALLOC(TR_Memory::PersistentCHTable) TR_AOTDependencyTable(TR_J9SharedCache *sharedCache); - // Update the table in response to a class load or initialization event. The - // isClassInitialization parameter is currently unused. + + // If the given method has an AOT body with stored dependencies in the local + // SCC, trackMethod() will determine how many are currently satisfied. If all + // are, dependenciesSatisfied will be set to true. Otherwise, the method and + // its dependencies will be tracked until all the dependencies are satisfied, + // at which point the method's count will be reduced to zero. + // + // Returns true if the method had stored dependencies and either they were + // all satisfied immediately, or the method was successfully tracked. + bool trackMethod(J9VMThread *vmThread, J9Method *method, J9ROMMethod *romMethod, bool &dependenciesSatisfied); + + // Inform the dependency table that a method is being compiled, so it can + // stop tracking the method. Will invalidate the MethodEntryRef pointer for + // method, if it was being tracked. + void methodWillBeCompiled(J9Method *method); + + // Update the table in response to a class load or initialization event. void classLoadEvent(TR_OpaqueClassBlock *ramClass, bool isClassLoad, bool isClassInitialization); - // Invalidate an unloaded class + // Invalidate an unloaded class. Will invalidate the MethodEntryRef for every + // RAM method of ramClass. void invalidateUnloadedClass(TR_OpaqueClassBlock *ramClass); - // Invalidate a redefined class + // Invalidate a redefined class. Will invalidate the MethodEntryRef for every + // RAM method of ramClass. void invalidateRedefinedClass(TR_PersistentCHTable *table, TR_J9VMBase *fej9, TR_OpaqueClassBlock *oldClass, TR_OpaqueClassBlock *freshClass); // Given a ROM class offset, return an initialized class with a valid class // chain starting with that offset. TR_OpaqueClassBlock *findClassCandidate(uintptr_t offset); + static uintptr_t decodeDependencyOffset(uintptr_t offset) + { + return offset | 1; + } + static uintptr_t decodeDependencyOffset(uintptr_t offset, bool &needsInitialization) + { + needsInitialization = (offset & 1) == 1; + return decodeDependencyOffset(offset); + } + static uintptr_t encodeDependencyOffset(uintptr_t offset, bool needsInitialization) + { + return needsInitialization ? offset : (offset & ~1); + } + private: bool isActive() const { return _isActive; } void setInactive() { _isActive = false; } + // Deallocate the internal structures of the table and mark the table as // inactive. Must be called with the _tableMonitor in hand. void deactivateTable(); + // Register a class load event for ramClass at offset. If any methods had + // their dependencies satisfied by this event, they will be added to + // methodsToQueue. void classLoadEventAtOffset(J9Class *ramClass, uintptr_t offset, bool isClassLoad, bool isClassInitialization); + // Invalidate a class with a particular ROM class offset. Returns false if - // the class wasn't tracked. + // the class wasn't tracked. If pendingMethodQueue is not NULL, we will also + // remove all methods whose dependencies became unsatisfied from + // pendingMethodQueue. bool invalidateClassAtOffset(J9Class *ramClass, uintptr_t romClassOffset); + + // Invalidate any tracked methods of ramClass. This will invalidate the + // MethodEntryRef of those methods. When registering a class redefinition + // event, this is best called before any revalidation occurs, so that stale + // method entry pointers are not collected in _pendingLoads. + void invalidateMethodsOfClass(J9Class *ramClass); + void recheckSubclass(J9Class *ramClass, uintptr_t offset, bool shouldRevalidate); + + // Get the OffsetEntry corresponding to offset. If create is true this will + // never return a NULL entry (but it may throw). OffsetEntry *getOffsetEntry(uintptr_t offset, bool create); + J9Class *findCandidateForDependency(const PersistentUnorderedSet &loadedClasses, bool needsInitialization); + + // Stop tracking the given method. This will invalidate the MethodEntryRef + // for the method. + void stopTracking(MethodEntryRef entry); + void stopTracking(J9Method *method); + + // Queue and clear the _pendingLoads, and remove those methods from tracking. + // Must be called at the end of any dependency table operation that could + // have led to a pending load being registered. + void resolvePendingLoads(); + + // Erase the given entry at offset if the entry is empty. This will + // invalidate the pointer to OffsetEntry. + void eraseOffsetEntryIfEmpty(const OffsetEntry &entry, uintptr_t offset); + + void registerSatisfaction(PersistentUnorderedSet waitingMethods); + void registerDissatisfaction(PersistentUnorderedSet waitingMethods); + // Initially true, and set to false if there is a failure to allocate. bool _isActive; @@ -111,6 +197,13 @@ class TR_AOTDependencyTable // works because there can be at most one class chain in the SCC whose entry // is a particular ROM class offset. PersistentUnorderedMap _offsetMap; + + // A map from methods to their tracking information in the dependency table + PersistentUnorderedMap _methodMap; + + // Any pending method loads that were triggered by the current dependency + // table transaction + PersistentUnorderedSet _pendingLoads; }; #endif /* defined(PERSISTENT_COLLECTIONS_UNSUPPORTED) */ diff --git a/runtime/compiler/env/J9SharedCache.cpp b/runtime/compiler/env/J9SharedCache.cpp index b68cc812269..4833fa373e2 100644 --- a/runtime/compiler/env/J9SharedCache.cpp +++ b/runtime/compiler/env/J9SharedCache.cpp @@ -986,11 +986,16 @@ TR_J9SharedCache::isPtrToROMClassesSectionInSharedCache(void *ptr, uintptr_t *ca J9ROMClass * TR_J9SharedCache::startingROMClassOfClassChain(UDATA *classChain) { - UDATA lengthInBytes = classChain[0]; - TR_ASSERT_FATAL(lengthInBytes >= 2 * sizeof (UDATA), "class chain is too short!"); + return romClassFromOffsetInSharedCache(startingROMClassOffsetOfClassChain(classChain)); + } - UDATA romClassOffset = classChain[1]; - return romClassFromOffsetInSharedCache(romClassOffset); +uintptr_t +TR_J9SharedCache::startingROMClassOffsetOfClassChain(void *chain) + { + auto classChain = (uintptr_t *)chain; + uintptr_t lengthInBytes = classChain[0]; + TR_ASSERT_FATAL(lengthInBytes >= 2 * sizeof (UDATA), "class chain is too short!"); + return classChain[1]; } // convert an offset into a string of 8 characters @@ -1555,7 +1560,7 @@ TR_J9SharedCache::buildAOTMethodDependenciesKey(uintptr_t offset, char *buffer, cursor += dependencyKeyPrefixLength; convertUnsignedOffsetToASCII(offset, cursor); - keyLength = (cursor - buffer) + _numDigitsForCacheOffsets + 1; // NULL terminator not included in _numDigitsForCacheOffsets + keyLength = (cursor - buffer) + _numDigitsForCacheOffsets; } const void * @@ -1613,8 +1618,9 @@ TR_J9SharedCache::methodHasAOTBodyWithDependencies(J9VMThread *vmThread, J9ROMMe dataDescriptor.address = NULL; TR_J9VMBase *fej9 = (TR_J9VMBase *)(fe()); - if (sharedCacheConfig()->findSharedData(vmThread, key, keyLength, J9SHR_DATA_TYPE_JITHINT, FALSE, &dataDescriptor, NULL) > 0) - methodDependencies = (uintptr_t *)dataDescriptor.address; + sharedCacheConfig()->findSharedData(vmThread, key, keyLength, J9SHR_DATA_TYPE_JITHINT, FALSE, &dataDescriptor, NULL); + methodDependencies = (uintptr_t *)dataDescriptor.address; + return true; #else return false; diff --git a/runtime/compiler/env/J9SharedCache.hpp b/runtime/compiler/env/J9SharedCache.hpp index ebfd6d4f56a..0a56952d579 100644 --- a/runtime/compiler/env/J9SharedCache.hpp +++ b/runtime/compiler/env/J9SharedCache.hpp @@ -368,6 +368,7 @@ class TR_J9SharedCache : public TR_SharedCache virtual bool isOffsetOfPtrToROMClassesSectionInSharedCache(uintptr_t offset, void **ptr = NULL); J9ROMClass *startingROMClassOfClassChain(UDATA *classChain); + uintptr_t startingROMClassOffsetOfClassChain(void *chain); virtual uintptr_t getClassChainOffsetIdentifyingLoader(TR_OpaqueClassBlock *clazz, uintptr_t **classChain = NULL); @@ -421,11 +422,14 @@ class TR_J9SharedCache : public TR_SharedCache /** * \brief Store the dependencies of an AOT method in the SCC * - * The dependencies of an AOT method are encoded as an array of uintptr_t values. The first entry is the number - * of elements in the entire array. The subsequent entries are encoded offsets to the class chains of classes that - * need to be loaded or initialized before the compiled body can be loaded. If the class must be initialized, the - * entry will be the offset itself (which necessarily has a set low bit), and if the class only needs to be loaded - * it will be the offset with the low bit cleared. * + * The dependencies of an AOT method are encoded as an array of uintptr_t + * values. The first entry is the number of entries after the first, the + * number of dependencies in the array. The subsequent entries are encoded + * offsets to the class chains of classes that need to be loaded or + * initialized before the compiled body can be loaded. If the class must be + * initialized, the entry will be the offset itself (which necessarily has a + * set low bit), and if the class only needs to be loaded it will be the + * offset with the low bit cleared. * * \param[in] vmThread VM thread * \param[in] methodDependencies The dependencies of the AOT compilation