Skip to content
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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

cjjdespres
Copy link
Contributor

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

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>
@cjjdespres
Copy link
Contributor Author

Attn @mpirvu. This is the warm run side of the dependency tracking.

@mpirvu mpirvu self-assigned this Nov 15, 2024
* 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
Copy link
Contributor Author

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)
Copy link
Contributor Author

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

Copy link
Contributor

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);
Copy link
Contributor Author

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

Copy link
Contributor

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);
Copy link
Contributor Author

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())
Copy link
Contributor

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.

Copy link
Contributor

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())
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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)
Copy link
Contributor

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)
Copy link
Contributor

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);
Copy link
Contributor

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);
{
Copy link
Contributor

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)
Copy link
Contributor

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);
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants