Skip to content

Prevent JIT bodies from strictly outliving methods inlined into them #7673

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

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

jdmpapin
Copy link
Contributor

This is largely in preparation for constant references, but it should also have benefits for code motion. For more detail, see the Motivation section of the doc comment for RetainedMethodSet.

Much of this commit is OMR::RetainedMethodSet, which describes a set of methods that will remain loaded under certain assumptions. This is a base class that is meant to be subclassed in any downstream project that allows methods to be dynamically unloaded.

During inlining, each TR_CallSite and TR_CallTarget will now have an associated RetainedMethodSet. Often, all methods named at call sites and all selected call targets can be established to outlive their callers, and transitively the outermost method. In this case, nothing of note happens.

However, sometimes a call site is refined based on a known object, and the refined method is not already known to outlive its caller. In this case, a nested RetainedMethodSet is created to represent the refined method (in addition to the surrounding inline context). Any further nested call sites and call targets can therefore take this refined method for granted. If the call site is actually inlined, then we record a "keepalive" for the refined method. The idea of a keepalive is that the JIT compiler should create an additional reference to prevent that method from being unloaded as long as the JIT body is still valid. In this commit, no such reference is created because we do not yet have a mechanism to do so. They will be implemented later as extra constant references. In the meantime, the JIT will simply assume that anything covered by a keepalive will remain loaded on its own. This assumption isn't great, but it's essentially the same as our existing pervasive assumption that known object indices will remain accurate.

Similarly, sometimes a call target is selected based on profiling or a single implementer, and the target method is not already known to outlive the base method named at the call site. In this case, a nested RetainedMethodSet is created to represent the target method (in addition to the surrounding inline context). Any further nested call sites and call targets can therefore take this target method for granted. If the call target is actually inlined, then we record a "bond" for the target method. The idea of a bond is to limit the lifetime of the JIT body to prevent it from strictly outliving the inlined method. This should be accomplished in a downstream project by creating an unload assumption so that when the inlined method is unloaded, the JIT body will be invalidated (similarly to preexistence).

If the dontInlineUnloadableMethods option is set, then instead of requesting bonds, inliner will refuse to inline targets that would required them. In this case, assertions check after optimization that no inlined method will be unexpectedly unloaded.

@jdmpapin
Copy link
Contributor Author

jdmpapin commented Mar 5, 2025

Updated to add a node flag wasRefinedFromKnownObject for call nodes to facilitate inlining top-level refined calls with keepalives.

@jdmpapin
Copy link
Contributor Author

Rebased onto a base commit that's compatible with the new base commit for eclipse-openj9/openj9#21216

@jdmpapin
Copy link
Contributor Author

Pushed updates in a separate commit. I intend to squash them later.

These go along with the updates that have just been pushed to eclipse-openj9/openj9#21216.

jdmpapin added 2 commits May 23, 2025 12:38
This is largely in preparation for constant references, but it should
also have benefits for code motion. For more detail, see the Motivation
section of the doc comment for RetainedMethodSet.

Much of this commit is OMR::RetainedMethodSet, which describes a set of
methods that will remain loaded under certain assumptions. This is a
base class that is meant to be subclassed in any downstream project that
allows methods to be dynamically unloaded.

During inlining, each TR_CallSite and TR_CallTarget will now have an
associated RetainedMethodSet. Often, all methods named at call sites
and all selected call targets can be established to outlive their
callers, and transitively the outermost method. In this case, nothing of
note happens.

However, sometimes a call site is refined based on a known object, and
the refined method is not already known to outlive its caller. In this
case, a nested RetainedMethodSet is created to represent the refined
method (in addition to the surrounding inline context). Any further
nested call sites and call targets can therefore take this refined
method for granted. If the call site is actually inlined, then we record
a "keepalive" for the refined method. The idea of a keepalive is that
the JIT compiler should create an additional reference to prevent that
method from being unloaded as long as the JIT body is still valid. In
this commit, no such reference is created because we do not yet have a
mechanism to do so. They will be implemented later as extra constant
references. In the meantime, the JIT will simply assume that anything
covered by a keepalive will remain loaded on its own. This assumption
isn't great, but it's essentially the same as our existing pervasive
assumption that known object indices will remain accurate.

Similarly, sometimes a call target is selected based on profiling or a
single implementer, and the target method is not already known to
outlive the base method named at the call site. In this case, a nested
RetainedMethodSet is created to represent the target method (in addition
to the surrounding inline context). Any further nested call sites and
call targets can therefore take this target method for granted. If the
call target is actually inlined, then we record a "bond" for the target
method. The idea of a bond is to limit the lifetime of the JIT body to
prevent it from strictly outliving the inlined method. This should be
accomplished in a downstream project by creating an unload assumption so
that when the inlined method is unloaded, the JIT body will be
invalidated (similarly to preexistence).

If the dontInlineUnloadableMethods option is set, then instead of
requesting bonds, inliner will refuse to inline targets that would
required them. In this case, assertions check after optimization that no
inlined method will be unexpectedly unloaded.
- Move to a single set of keepalives and a single set of bonds per tree.
  This way, keepalives and bonds don't have to bubble up toward the root
  one level at a time, which makes it easier to make call-site
  attestations non-destructive. It also means that sets that should
  generate keepalives and bonds are now allowed to do so in any order.

- Replace attestLinkedCallee...() with withLinkedCalleeAttested(). It
  now creates a child set if needed instead of mutating the receiver.
  This way, while building the tree for inlining, the set at a given
  point has contents based on the call path leading to it, independent
  of any other call sites or call targets.

- Collect information pertaining to the retained methods analysis for
  each call site during inlining. In particular, remember whether the
  call site was refined, and if so, what the refined method was, and
  also remember which call sites generated keepalives and bonds. This
  information will be needed in case the analysis is to be repeated
  later (e.g. for OpenJ9 JITServer).

- Call keepalive/bond in preorder instead of in postorder since it's now
  possible to do so.

- Assert that no bonds are created with dontInlineUnloadableMethods set.

- Add a doc comment for withKeepalivesAttested().

- Add withBondsAttested() similar to withKeepalivesAttested(). It's not
  currently used, but it will be used later for const refs.

- Allow the per-tree set of keepalives and bonds to be extended by a
  downstream project. This will make it possible to remember the
  contents of the receivers of the keepalive() and bond() calls, which
  will allow for withKeepalivesAttested() and withBondsAttested() to
  be done without needing to scan.

- When tracing the bond methods, add a project-determined note. This
  will be used by JITServer to warn that the set of bonds is imprecise
  and that the real set will be determined by the client later on.

- When checking the inlining table for dontInlineUnloadableMethods,
  don't bother calling withKeepalivesAttested() unless there are
  actually some inlined sites, and trace a blank line at the end.

- Fix a namespace end comment.

- Remove the assertion that was added to the TR_CallTarget constructor
  because an equivalent one has been added by an earlier inliner change.
@jdmpapin jdmpapin force-pushed the retained-method-set branch from a404ec3 to b1f748e Compare May 23, 2025 17:58
@jdmpapin
Copy link
Contributor Author

Rebased onto a base commit that's compatible with the new base commit for eclipse-openj9/openj9#21216.

Also corrected a slight omission in the "Update RetainedMethodSet" commit. It was supposed to delete a redundant assertion, but didn't, and now it does. An equivalent assertion was introduced earlier by #7731.

@jdmpapin jdmpapin marked this pull request as ready for review May 23, 2025 18:05
@jdmpapin jdmpapin marked this pull request as draft May 30, 2025 15:41
@jdmpapin
Copy link
Contributor Author

Draft again because of eclipse-openj9/openj9#21216

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