Implement JIT optimization for MethodHandle.linkToNative calls#23617
Implement JIT optimization for MethodHandle.linkToNative calls#23617nbhuiyan wants to merge 2 commits intoeclipse-openj9:masterfrom
Conversation
673b4f7 to
a710d84
Compare
|
@0xdaryl @vijaysun-omr @hzongaro @zl-wang FYI @TobiAjila Please confirm if there are any planned VM refactoring work that will require changes in how the linkToNative optimization is implemented. |
|
Some thoughts. Of course, you might have already considered some or all of this. It's not obvious to me that we want to refine AFAICT from a quick look, a case where the inlining kicks in is one where during inlining we could determine the MH but not the native entry point. If we were to leave the I also worry that inlining is overkill if the goal is just to avoid a J2I transition. Inlining might add a lot of code to the IL for the benefit of a call that's necessarily going to be pretty slow anyway. For the case where we fail to do direct JNI, might it be better to do a direct call transformation in recognized call transformer, like for |
|
i haven't read the design yet, but agreed with @jdmpapin from the outset. for the best performance, staying the IL at a level, as long as it allows the codegen realizing (by symRef name recognition or otherwise) to go JNI-Direct. of course, the real callee first needs to pass through some limitation tests (afaik, the only criteria is the number of pass-down arguments doesn't exceed 32 or something like that). if it fails the test, i guessed the existing approach still applies (i.e. through libffi). one of the future optimizations possible is to be able to query if the callee is suitable for fast-JNI. if there are upCall(s) involved, the other optimization in the future is to cache upCall thunk (to be used by multiple threads in multiple scopes for example). no need to (re)generate it per thread per call-site per scope (i.e. inefficient). |
|
or, ascertaining/investigating: this particular linkToNative situation can always assume to be fast-JNI (i.e. don't build call-out frame, don't drop VMAccess, no GC allowed, don't kill preserved GPRs, don't need to tear down JNI frame -- ref pool left-behind, and no need to check for pending throws). |
We cant assume this, we need to operate with the same JNI assumptions where the JNI frame is built and VM access is dropped. That being said it is possible to allow fastJNI like behaviour where we dont need to drop VMAccess in some cases, however, this is beyond what we initially discussed. See #23631 |
a710d84 to
4cb5015
Compare
todo: commit message needs updating to reflect new changes
This commit implements partial support for optimizing MethodHandle.linkToNative
invocations by transforming them into direct native function calls, bypassing
the J2I transition overhead. Currently, only primitive argument and return
types are supported (no structs/arrays).
The linkToNative polymorphic signature method is used by the Foreign Function
& Memory API (Project Panama) to invoke native functions through MethodHandles.
At the bytecode level, linkToNative receives:
- arg0: native function address as a MemorySegment
- arg1: appendix object from the invoke cache
- args 2..N-1: primitive arguments for the native function
- argN: NativeMethodHandle object
Without this optimization, such invocations would be dispatched through the
interpreter, which has a lot more overhead.
Implementation Details:
1. Inlining Support Through InterpreterEmulator Enhancements:
- Added handling of linkToNative in InterpreterEmulator
- Extracts native entry point, MemberName, and appendix from the
NativeMethodHandle's invoke cache array
- Reconstructs the operand stack by removing the NativeMethodHandle and
inserting the native entry point and appendix in the correct positions
- Establishes const provenance relationships for known objects
2. MethodHandleTransformer:
- New method: process_java_lang_invoke_MethodHandle_linkToNative() to
handle MethodHandle.linkToNative transformation.
- Validates that the native function address is a known constant
- Extracts the raw function pointer from the NativeMemorySegmentImpl
- Validates and transforms the method signature:
* Strips the MemorySegment, appendix, and NativeMethodHandle parameters
* Ensures only primitive types remain (rejects arrays and references)
* Constructs a new signature with only primitive parameters and return
- Creates a new resolved method with the transformed signature
- Marks the call as a direct native call (canDirectNativeCall=true)
- Adjusts the call node's children to match the new signature
3. Platform-Specific JNI Linkage Updates:
- x86-64 (AMD64JNILinkage.cpp):
* Disables JNIEnv* passing and reference wrapping
* Fixes argument cleanup calculation to account for the base argument size
* Adds logging for debugging argument size calculations
- Z (S390PrivateLinkage.cpp): Disables JNIEnv* passing and
JNI reference frame collapsing
- aarch64 (ARM64JNILinkage.cpp): Disables JNIEnv* passing and reference
wrapping for linkToNative calls
- Power (PPCJNILinkage.cpp): Same adjustments as aarch64
- i386: as this platform is only used for Java 8, no changes necessary for
linkToNative, which was introduced in Java 16
4. CodeGen (common):
- Modified lowerTreeIfNeeded() to skip dummy null argument insertion for
linkToNative calls that have been prepared for direct JNI dispatch
- Uses isPreparedForDirectJNI() flag to distinguish transformed calls
5. New Recognized Method (and Class):
- Added java_lang_invoke_NativeMethodHandle_internalNativeEntryPoint to the
recognized methods enum (required clang-format assisted reformatting)
6. Method Metadata:
- Added linkToNativeJNITargetAddress field to TR_ResolvedJ9Method
- Added setter/getter methods for the native target address
- Modified startAddressForJNIMethod() to return the stored target address
for linkToNative calls
- TR_ResolvedJ9JITServerMethod's functions isJNINative and
startAddressForJNIMethod checks linkToNativeJniTargetAddress field
With the above changes implemented, we achieve the following optimizations
for linkToNative calls that consist of primitive-only args and return types:
- Eliminate J2I transition overhead for eligible linkToNative invocations
- Remove JNI frame creation and teardown overhead that's part of the linkToNative
INL function in the interpreter
- Bypass JNI exception checking
Co-authored-by: Devin Papineau <devin@ajdmp.ca>
Signed-off-by: Nazim Bhuiyan <nubhuiyan@ibm.com>
4cb5015 to
5e329a6
Compare
5e329a6 to
9644d1c
Compare
Note: description currently not up to date with changes in the implementation. This will be updated prior to being marked ready for review.
This commit implements partial support for optimizing MethodHandle.linkToNative invocations by transforming them into direct native function calls, bypassing the J2I transition overhead. Currently, only primitive argument and return types are supported (no structs/arrays).
The linkToNative polymorphic signature method is used by the Foreign Function & Memory API (Project Panama) to invoke native functions through MethodHandles. At the bytecode level, linkToNative receives:
Without this optimization, such invocations would be dispatched through the interpreter, which has a lot more overhead.
Implementation Details:
Inlining Support Through InterpreterEmulator Enhancements:
MethodHandleTransformer:
Platform-Specific JNI Linkage Updates:
CodeGen (common):
New Recognized Method (and Class):
Method Metadata:
With the above changes implemented, we achieve the following optimizations for linkToNative calls that consist of primitive-only args and return types:
This is built on top of @jdmpapin's prototype implementation. Currently in WIP state to complete further testing and performance analysis on all platforms, and some refactoring work in the interpreter.
Commit/PR message composed using IBM Bob.