-
Notifications
You must be signed in to change notification settings - Fork 721
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Track methods with AOT bodies with dependencies #20599
base: master
Are you sure you want to change the base?
Track methods with AOT bodies with dependencies #20599
Conversation
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 <despresc@ibm.com>
Attn @mpirvu. This is the warm run side of the dependency tracking. |
* 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The original documentation said the first entry was the total number of entries, but I actually had the dependencies set up similarly to the well-known classes chain, where the first entry is the number of subsequent entries.
{ | ||
for (auto &entry: waitingMethods) | ||
{ | ||
if (entry->second._remainingDependencies) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be entry->second._remainingDependencies == 1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was trying really hard to understand the initial code :-)
if (isClassLoad) | ||
entry->_loadedClasses.insert(ramClass); | ||
{ | ||
checkForSatisfaction(offsetEntry->_waitingLoadMethods); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should only be run if there have been no previous loads for this offset
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean that the code needs some modification?
auto entry = getOffsetEntry(offset, isClassLoad); | ||
TR_ASSERT(entry || !isClassLoad, "Class %p offset %lu initialized without loading"); | ||
auto offsetEntry = getOffsetEntry(offset, isClassLoad); | ||
TR_ASSERT(offsetEntry || !isClassLoad, "Class %p offset %lu initialized without loading", ramClass, offset); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't accurate - I check that ramClass
matches its cached version for loads only, so if this isn't a load, I need to check that ramClass
is in offsetEntry->_loadedClasses
. If it isn't, then it was intentionally not added when it was loaded.
@@ -8253,6 +8254,9 @@ TR::CompilationInfoPerThreadBase::compile(J9VMThread * vmThread, | |||
vmThread->omrVMThread->vmState = J9VMSTATE_JIT | J9VMSTATE_MINOR; | |||
vmThread->jitMethodToBeCompiled = method; | |||
|
|||
if (auto dependencyTable = getCompilationInfo()->getPersistentInfo()->getAOTDependencyTable()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we please add a comment for piece of code. I was trying to guess what the code is trying to do without reading the implementation, but I wasn't able to come up with anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, it stops tracking for the method that gets compiled.
int32_t scount = optionsAOT->getInitialSCount(); | ||
uint16_t newScount = 0; | ||
if (sc && sc->isHint(method, TR_HintFailedValidation, &newScount)) | ||
if (auto dependencyTable = compInfo->getPersistentInfo()->getAOTDependencyTable()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add as a TODO: if the user specified a count in subset {method}(count), we should obey that and not use the dependency tracking
TR_VerboseLog::writeLineLocked(TR_Vlog_SCHINTS,"Found hint in sc, increase scount to: %d, wanted scount: %d", scount, newScount); | ||
bool dependenciesSatisfied = false; | ||
if (dependencyTable->trackMethod(vmThread, method, romMethod, dependenciesSatisfied)) | ||
count = dependenciesSatisfied ? 0 : 3003; // TODO: figure out what this should be |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If dependencies are not satisfied, then we should probably use
getCount(romMethod, optionsJIT, optionsAOT)
If you think this is too little, we could go for 3000 (there is a define for it)
PersistentUnorderedSet<J9Class *> _loadedClasses; | ||
// Methods waiting for a class with this chain offset to be loaded |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"with this chain offset" or "with this ROMClass offset"?
{ | ||
for (auto &entry: waitingMethods) | ||
{ | ||
if (entry->second._remainingDependencies) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was trying really hard to understand the initial code :-)
} | ||
|
||
J9Class * | ||
TR_AOTDependencyTable::findCandidateForDependency(PersistentUnorderedSet<J9Class *> &loadedClasses, bool needsInitialization) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
loadedClasses can be const
if (isClassLoad) | ||
entry->_loadedClasses.insert(ramClass); | ||
{ | ||
checkForSatisfaction(offsetEntry->_waitingLoadMethods); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean that the code needs some modification?
if (isClassLoad) | ||
entry->_loadedClasses.insert(ramClass); | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this code run before the "if (isClassInitialization.." at line 207? I have seen somewhere in the code that we can have both class load and class init events at the same time. For those cases we ensure that we have our class added to _loadedClasses before calling findCandidateForDependency()
uintptr_t chainOffset = decodeDependencyOffset(methodDependencies[i], needsInitialization); | ||
uintptr_t offset = _sharedCache->startingROMClassOffsetOfClassChain(_sharedCache->pointerFromOffsetInSharedCache(chainOffset)); | ||
auto entry = getOffsetEntry(offset, true); | ||
if (needsInitialization) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of these dependencies may already be satisfied. Why do we add to _waitingInitMethods and _waitingLoadMethods in this case?
else | ||
o_it->second._waitingLoadMethods.erase(entry); | ||
|
||
eraseOffsetEntryIfEmpty(&o_it->second, offset); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of passing &o_it->second
, we could pass a const reference o_it->second
.
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.
Related: #20529