Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/openj9
Path: blob/master/doc/compiler/aot/InlinedMethods.md
6000 views

Overview

AOT compilations support inlining of methods. However, ensuring validation and relocation is a non-trivial process with many subtleties. The relocation records related to the inlined methods are different from the other records in that they do many things - they perform validation, relocation of the inlined table entry in the J9JITExceptionTable metadata structure, as well as relocation of guards.

This document will explain how an inlined site is validated, as well what what all is necessary as part of the relocation. Note, inlined method relocations still follow the high level process described by the Intro to Ahead Of Time Compilation blog series, which this doc assumes the reader has already read.

Introduction

There are four general kinds of inlined method relocations:

  • Inlined Methods

  • Inlined Methods with NOP Guards

  • Profiled Inlined Methods

  • Profiled Inlined Methods with Guards

All relocations are used to validate the inlined method. However, the relocations for methods with guards are used to relocate the associated inlined table entry in the metadata as well as the guards for the inlined sites, whereas the rest are only used to relocate the associated inlined table entry. Profiled Inlined Methods are methods that were inlined that were not named by the caller. For example, consider the following scenario:

class base { void foo(); } class child_one extends base { void foo(); } child class_two extends base { void foo(); } ... void some_method(base b) { b.foo(); }

Because b is of type base, the foo that gets executed could either be the implementation in base or child_one or child_two. If the inliner decided to pick base.foo() because that is what was named in some_method, then the relocation would be the Inlined Method variant. However, if the inliner decided to inline child_two.foo() based on profiling information, the relocation would be the Profiled Inlined Method variant.

Generating Guards & External Relocations

During an AOT compilation, i.e. when the code is first generated, external relocation are generated at various points. Once all of these external relocations are added to a list, the inlined method external relocations are then added in reverse order; because entries are added to the list at the front, an external relocation associated with the last inlined site index is added first, followed by the second last, continuing to the first. This ensures that the first inlined method external relocation in the list is associated with the first inlined site.

The reason for ensuring this is because validating/materializing values for subsequent inlined methods may depend on the previous entry. For example, if foo() calls bar() calls baz(), and bar and baz are inlined (first bar would be inlined, then baz), baz is named in bar's constant pool, and bar is named in foo's constant pool. Therefore, in order to materialize the J9Method of baz, one first needs the J9Method of bar, which first requires the J9Method of foo.

If the inlined method has a guard assocated with it, then the Inlined Method with NOP Guard / Profiled Inlined Method with Guard relocations are generated. If not, then the Inlined Method / Profiled Inlined Method relocations are generated. Additionally, unless the SVM is enabled, the inlined method external relocations are the last entries added to the list; if the SVM is enabled, the SVM validation records are added next - part of the validation that would've been done by the inlined method validation procedure is delegated to the SVM.

Validating Inlined Sites

The way an inlined method is validated depends on whether it was profiled or not.

Inlined Methods with or without NOP Guards

The process starts in TR_RelocationRecordInlinedMethod::preparePrivateData. First TR_RelocationRecordInlinedMethod::inlinedSiteValid is called, which

  1. Gets the J9Method of the caller. For the very first inlined site, this will be the method being compiled; for the rest it will be from one of the previously validated/relocated inlined sites.

  2. Checks if the method has been unloaded.

  3. Materializes the J9Method of the inlined method:

    • If the SVM is enabled the method ID in the relocation record is used.

    • If the SVM is disabled, getMethodFromCP is called which is a virtual method that has different implementations depending on the kind of inlined method (special, static, virtual, interface).

  4. Checks if the inlined site can be activated because of debug or other JVM restrictions.

  5. Checks if J9ROMClass of the J9Method from step 3 matches the J9ROMClass in the SCC based on the offset stored in the relocation record.

If everything succeeds, inlinedSiteValid returns true. The success or failure is cached in reloPrivateData->_failValidation.

Profiled Inlined Methods with or without Guards

The process occurs in TR_RelocationRecordProfiledInlinedMethod::preparePrivateData which

  1. Materializes the class of the inlined method:

    • If the SVM is enabled, the class ID in the relocation record is used.

    • If the SVM is disabled, the process described here is used, specifically the section about using the class chain of the first class loaded by the classloader that loaded the class of the inlined method.

  2. Validates the Class Chain of the class from 1.

  3. Materializes the inlined method from the class from 1 using the method index stored in the relocation record.

  4. Checks if the inlined site can be activated because of debug or other JVM restrictions.

The success or failure is then cached in reloPrivateData->_failValidation.

Relocating the Inlining Table in the J9JITExceptionTable

In both variants of validating the inlined sites, the final task if everything succeeded is to relocate the entry in the inlining table in the J9JITExceptionTable. TR_RelocationRecordInlinedMethod::fixInlinedSiteInfo is called, which relocates the entry in the table, and registers an assumption to patch the entry in the table should the class of the method get unloaded.

Relocating (Activating / Invalidating) Guards

The way a guard is relocated depends on whether it is a NOP guard or not. Inlined Profiled Methods do not use NOP guards; Inlined Methods do use NOP guards.

Inlined Methods with NOP Guards

If reloPrivateData->_failValidation is true, then if the SVM is enabled the AOT load is aborted; if the SVM is disabled then TR_RelocationRecordNopGuard::invalidateGuard is called, which patches the guard to branch to the slow path. If reloPrivateData->_failValidation is false, then TR_RelocationRecordNopGuard::activateGuard is called, which

  1. Calls createAssumptions, which will add assumptions appropriate for the kind of the inlined method (virtual, interface).

  2. Registers a Class Redefinition assumption against the class of the inlined method.

Profiled Inlined Methods with Guards

If reloPrivateData->_failValidation is true, then if the SVM is enabled the AOT load is aborted. If false, nothing is done here. Because these guards are not NOP guards, but explicit tests, the materialization of the pointer which is put into a register to be used in the comparison instruction is relocated in a separate relocation record (either a TR_RelocationRecordClassPointer or TR_RelocationRecordMethodPointer relocation record).

Subtleties

This section goes through some subtleties that were either glossed over above, or are not immediately obvious.

Inlined Method Validations/Relocations have to happen first

If the SVM is enabled, then the SVM validations are performed first. However, regardless of whether the SVM is enabled or not, the inlined method validations/relocations have to happen before the other relocations. This is because many relocation records will store a CP index and an inlined site index; this means that in order to materialize the value, the relocation infrastructure needs to look in the constant pool of the class of the method inlined at the specified inlined site. Therefore, the inlining table in the J9JITExceptionTable must hold valid entries by this point.

Aborting compilation with SVM enabled for an invalid inlined site

If the SVM is enabled, if all SVM validations pass then it is (almost) not possible for any of the various tests for both Inlined Method and Profiled Inlined Method to fail; the exception is if inlinedSiteCanBeActivated returns false which can happen because of debug or other JVM restrictions.

Because the SVM enables far more optimization, it is not immediately obvious whether it is safe to execute the code. Without validating an inlined method, it is not clear if some other part of the code's functional correctness depends on the inlined method being the same in the second run. As such, it is better to just take the conservative approach of failing the AOT load rather than execute a potentially incorrect body.

Class Initialization

There isn't a need to verify whether the class of an inlined method has been initialized. The reason is because for most inlined methods, in order to execute it, the receiver object must exist; instantiating the receiver initializes the class including its superclass(es) and interface class(es) it implements (if the interface has default/static methods, static fields, or when reflection is used). Therefore, even if the class isn't initialized at relocation time, the class will be initialized at runtime.

Note, this logic does not apply static methods. However, it does not matter as all of the VM APIs used to materialize the J9Method (in the case of Inlined Method relocations) or the J9Class (in the case of Profiled Inlined Method relocations) do a compile time resolve and also check if the class is initialized. Therefore, there is no need to explicitly check for initialization; if the API returns a non-NULL result, the class must be initialized.

Class Redefinition Assumptions for J9JITExceptionTable Inlining Table Entries

There is no need to register Class Redefinitions assumptions against entries in the inlining table. This is because although a redefined class will have the same J9Class pointer, the J9Method pointers of the redefined class will be different. As Class Redefinition semantics require that new invocations result in execution of the redefined method, the inlining table entry must remain unchanged in case there are threads that are still executing the old inlined bodies (perhaps because they haven't reached a point where they can transition to the new code).