Skip to content

Track loaded classes with valid class chains - #20516

Merged
mpirvu merged 1 commit into
eclipse-openj9:masterfrom
cjjdespres:track-class-offsets
Nov 11, 2024
Merged

Track loaded classes with valid class chains#20516
mpirvu merged 1 commit into
eclipse-openj9:masterfrom
cjjdespres:track-class-offsets

Conversation

@cjjdespres

@cjjdespres cjjdespres commented Nov 5, 2024

Copy link
Copy Markdown
Contributor

The new TR_AOTDependencyTable tracks all loaded classes whose class chains match what is stored in the local SCC. In future, it will also be responsible for tracking potential AOT loads; it will use this offset map to detect when all the dependencies of the stored AOT compilation have been satisfied and reduce the count of the associated method to induce a load in response.

This class tracking is disabled by default. It can be controlled with the option -XX:[+|-]TrackAOTDependencies.

Related: #20529

@cjjdespres
cjjdespres requested a review from dsouzai as a code owner November 5, 2024 18:31
Comment on lines +44 to +57
* must have identical chains). The ROM classes of these classes must have been
* shared when they were loaded in order to be tracked.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't necessary for AOT dependency tracking to track classes whose ROM classes weren't in the SCC when they were loaded - if they weren't, then the only way compiled code in the SCC could reference them is if this JVM was responsible for compiling that code (and remembering the class during the JVM run). If that's the case, we don't need to track the dependencies of that code anyway.

I could have the table include all classes by updating the table in TR_J9SharedCache::rememberClass(), but there was one potential race that I was concerned about, where we remember a class that's loaded but not initialized at the same time as the class initialization hook is triggered for that class. We might end up not notifying the table of the initialization properly in that case. I mention this because if the _offsetMap here is used to look up candidate classes during relocation in case looking them up in a class loader fails, then we might want freshly-remembered classes in the table to improve that fallback. That's an optimization, though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant to say class chains, not ROM classes above. I could also rememberClass() on class load instead of using classMatchesCachedVersion(), or track every shared class. I believe either would also work.

@cjjdespres

Copy link
Copy Markdown
Contributor Author

Attn @mpirvu.

@mpirvu

mpirvu commented Nov 5, 2024

Copy link
Copy Markdown
Contributor

Since you added new files, they need to be added the makefile system.

@mpirvu mpirvu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall it looks good. I only have some small comments/suggestions.
Have you considered what happens if we run out of memory during the allocations of the new data structures?

Comment thread runtime/compiler/control/HookedByTheJit.cpp Outdated
Comment thread runtime/compiler/env/DependencyTable.cpp
Comment thread runtime/compiler/env/DependencyTable.cpp
Comment thread runtime/compiler/env/DependencyTable.cpp Outdated
Comment thread runtime/compiler/env/DependencyTable.cpp Outdated
Comment thread runtime/compiler/env/DependencyTable.cpp Outdated
if (!_sharedCache->isClassInSharedCache(freshClass, &freshClassOffset) && !_sharedCache->isClassInSharedCache(oldClass, &oldClassOffset))
return;

if (oldClassOffset != freshClassOffset)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For class redefinition, do both the oldClass and the fresh class go through the class loading process?

Also, doesn't the oldClass need to be eliminated from the OffsetEntry associated with its romClass?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it would be cleaner if redefined class invalidation went through classLoadEvent (or some common method). The code in invalidateRedefinedClass should currently implement the same process that classLoadEvent does, because class initialization is only relevant for updating method dependencies.

I do classList.push_front(classInfo) right after collecting the subclasses of the old class, so the loop just below this should end up removing oldClass from its OffsetEntry.

Comment thread runtime/compiler/env/DependencyTable.cpp
@cjjdespres

Copy link
Copy Markdown
Contributor Author

I have thought about failure to allocate a bit, but I haven't addressed it in the code. I should.

If we have a failure to allocate, then the _offsetMap will start to become inaccurate and newly-initialized methods will no longer be able to tell if their dependencies have been satisfied already. If there is a failure to allocate, the best course of action might be to deactivate the table entirely.

@mpirvu

mpirvu commented Nov 7, 2024

Copy link
Copy Markdown
Contributor

If there is a failure to allocate, the best course of action might be to deactivate the table entirely.

I agree. If there is little memory in the system, performance tweaks are the last thing we need to worry about.

@dsouzai dsouzai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had some questions below

Comment thread runtime/compiler/env/DependencyTable.hpp
Comment thread runtime/compiler/control/HookedByTheJit.cpp Outdated
Comment thread runtime/compiler/env/SharedCache.hpp Outdated
Comment on lines +112 to +140
// In a class redefinition event, an old class is replaced by a fresh class. If
// the ROM class offset changed as a result, it and all its subclasses that
// formerly had valid chains will now be guaranteed not to match, so the entries
// for these must be removed. If the new offset is valid, any class that didn't
// have an entry should be rechecked.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment says that if the ROM class offset changes, the entries must be removed. However, further down you have

   if (oldClassOffset != freshClassOffset)
      return;

which means that if the offset changed, you don't do anything, which contradicts the comment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, if the offset is different, then you definitely need to update the table. Otherwise, the table will have an entry with the oldClassOffset as the key but the freshClass as the value (because the freshClass is redefined in place). You may not need to recheck the subclass though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo introduced when I rewrote a bit of this. It should be ==. If the offsets are the same then the validation status remains unchanged.

return NULL;

auto c_it = it->second._loadedClasses.begin();
if (c_it == it->second._loadedClasses.end())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it not possible to do if (it->second._loadedClasses.empty()) here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible, but I'll be changing this so I iterate over all the loaded classes anyway, in case the first ones found are not initialized.

Comment thread runtime/compiler/env/DependencyTable.cpp
@mpirvu

mpirvu commented Nov 7, 2024

Copy link
Copy Markdown
Contributor

I think that TR_AOTDependencyTable::invalidateUnloadedClass(TR_OpaqueClassBlock *clazz) is supposed to be used in class unload hooks, but I don't see that. Similar for redefinition. Is that supposed to be done in a separate PR?

Comment thread runtime/compiler/env/DependencyTable.cpp Outdated
@cjjdespres
cjjdespres force-pushed the track-class-offsets branch 2 times, most recently from 345cd52 to f5fdde4 Compare November 7, 2024 16:56
@cjjdespres

Copy link
Copy Markdown
Contributor Author

I think that TR_AOTDependencyTable::invalidateUnloadedClass(TR_OpaqueClassBlock *clazz) is supposed to be used in class unload hooks, but I don't see that. Similar for redefinition. Is that supposed to be done in a separate PR?

Yes, they should be there.

I could add a replacement for PersistentUnordered* if they aren't supported by the compiler. Or, I could just disable the feature on those platforms and come back to it later. Do you happen to remember which build compilers don't support them?

if (!_sharedCache->isClassInSharedCache(freshClass, &freshClassOffset) && !_sharedCache->isClassInSharedCache(oldClass, &oldClassOffset))
return;

if (oldClassOffset == freshClassOffset)

@dsouzai dsouzai Nov 7, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the offsets are the same, do you need to add oldClass into the table? I guess if something needs the class chain validation status, it will use the new class, but is there anything that might still use the old class to look up the status? Even if something does, does it matter if it can't find it? I'm gonna guess the answer is no, but I figure it's worth pointing out just in case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The oldClass would have been tracked already in classLoadEvent. I actually need to invalidate the oldClass and then register that freshClass was loaded. Should be fixed.

@cjjdespres
cjjdespres force-pushed the track-class-offsets branch 2 times, most recently from a6b88f6 to 2f4d248 Compare November 7, 2024 19:34
* 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
*******************************************************************************/

#if !defined(PERSISTENT_COLLECTIONS_UNSUPPORTED)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I suppose the tradeoff is that you'd have to guard all places that include the header appropriately as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just pushed to include DependencyTable.cpp conditionally in the CMakeLists.txt, but I don't think the way I did it works. I think I'd need to define a feature flag so that DependencyTable.cpp is conditionally included in common.mk.ftl as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reverted that.

@cjjdespres
cjjdespres force-pushed the track-class-offsets branch 2 times, most recently from c6a92d7 to c74edbb Compare November 7, 2024 21:27
@cjjdespres

Copy link
Copy Markdown
Contributor Author

I think that should be it. I've added classLoadEventAtOffset() and invalidateClassAtOffset() functions that the class load, unload, and redefinition hooks can all use.

Comment thread runtime/compiler/env/DependencyTable.cpp
Comment thread runtime/compiler/env/DependencyTable.hpp Outdated
Comment thread runtime/compiler/env/DependencyTable.cpp
Comment thread runtime/compiler/env/DependencyTable.cpp Outdated
@mpirvu

mpirvu commented Nov 8, 2024

Copy link
Copy Markdown
Contributor

jenkins compile all JDK8

@mpirvu

mpirvu commented Nov 8, 2024

Copy link
Copy Markdown
Contributor

AIX compile error:

  In file included from /home/jenkins/workspace/Build_JDK8_ppc64_aix_Personal/openj9/runtime/compiler/control/DLLMain.cpp:27:
10:11:25  /home/jenkins/workspace/Build_JDK8_ppc64_aix_Personal/openj9/runtime/compiler/env/DependencyTable.hpp:37:59: error: expected class member or base class name
10:11:25     TR_AOTDependencyTable(TR_J9SharedCache *sharedCache) : {}

@cjjdespres

Copy link
Copy Markdown
Contributor Author

The explicit constructor definition can just be removed.

@mpirvu

mpirvu commented Nov 8, 2024

Copy link
Copy Markdown
Contributor

jenkins compile all JDK8

The new TR_AOTDependencyTable tracks all loaded classes whose class
chains match what is stored in the local SCC. In future, it will also be
responsible for tracking potential AOT loads; it will use this offset
map to detect when all the dependencies of the stored AOT compilation
have been satisfied and reduce the count of the associated method to
induce a load in response.

This class tracking is disabled by default. It can be controlled with
the option -XX:[+|-]TrackAOTDependencies.

Signed-off-by: Christian Despres <despresc@ibm.com>
@cjjdespres

cjjdespres commented Nov 8, 2024

Copy link
Copy Markdown
Contributor Author

Latest failure was due to the fact that the start of DependencyTable.cpp looked like:

#if !defined(PERSISTENT_COLLECTIONS_UNSUPPORTED)

#include "control/CompilationThread.hpp"
#include "env/DependencyTable.hpp"
#include "env/J9SharedCache.hpp"
#include "env/PersistentCHTable.hpp"

and since the include for env/DependencyTable.hpp came after the !defined, the symbol was was always undefined.

@mpirvu

mpirvu commented Nov 8, 2024

Copy link
Copy Markdown
Contributor

jenkins compile aix JDK8

@mpirvu

mpirvu commented Nov 8, 2024

Copy link
Copy Markdown
Contributor

jenkins compile all JDK8

@mpirvu

mpirvu commented Nov 9, 2024

Copy link
Copy Markdown
Contributor

jenkins test sanity all jdk21

@mpirvu
mpirvu merged commit 3cac37b into eclipse-openj9:master Nov 11, 2024
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.

3 participants