Questions about oop handling for Panama upcalls.

Jorn Vernee jorn.vernee at oracle.com
Tue Nov 16 17:51:02 UTC 2021


Hi,

For panama-foreign upcalls we spin our own upcall stubs that wrap a 
method handle VM entry for the actual upcall. I want to make sure I have 
the oop handling correct on this.

We receive a list of arguments from native code (all primitives, so no 
oops to handle there), and then prefix that list with a MethodHandle 
oop, before calling into the MH's VM entry. The MH oop can be stored in 
three different places:

1. The MH oop is stored in a global JNI handle, and then resolved right 
before the upcall [1].
2. The MH oop is then stored in the first argument register j_rarg0 for 
the call.
3. During a deopt of the callee, the deoptimization code spills the 
receiver (MH oop) into the frame of the upcall stub. (looks like the 
extending of the frame that happens for instance in c2i adapters doesn't 
make room for the receiver?).

I don't think I need to do anything else for 1., but for 2. and 3. there 
is currently no handling. I wanted to ask how those cases should be 
handled, if at all.

I think 2. could in theory be addressed by implementing 
CodeBlob::preserve_callee_argument_oops. Though, it has been working 
fine so far without this, so I'm wondering if this is even needed. Is 
the caller or callee responsible for handling argument oops (seems to be 
caller, from looking at CompiledMethod::preserve_callee_argument_oops)? 
Or does the caller just handle the receiver if there is one (since deopt 
spills that into the callers frame)? The oop offset is passed to an 
OopClosure in CompiledArgumentOopFinder::handle_oop_offset as an oop* 
[2]. Does the argument register get spilled somewhere and the oop needs 
to be patched in place at that address (by the OopClosure)? Or is this 
just used to mark the oop as alive? (in the latter case, the JNI global 
should be enough I think).

I think 3. could be handled with an OopMap entry at the frame offset 
where the receiver is spilled during a deopt of the callee? Should it be 
an oop or a narrowOop, or does it depend on VM settings? FWIW, the deopt 
code always seems to need a machine word (64-bits) to do the spilling, 
so I think it's an oop? Do I need to zero out that part of the frame 
when allocating the frame so that the GC doesn't mistake some garbage 
that's in there for an oop?

I have a POC patch here for reference [3], that implements the 2 things 
above. This passes our test suite, but I'm not sure about the 
correctness. Looking at what JNI does for upcalls [4], I don't see how 
e.g. the receiver argument that is put on the stack is handled, or what 
happens when the callee deopts (though I think it would just overwrite 
the value on the stack that's there already, since JNI always seems to 
do interpreted calls, where we do compiled calls).  But, JNI/the call 
stub might be special cased elsewhere...

Also, the oop is briefly stored in rscratch1 when resolving. I'm 
interested to know when the GC can look at the frame and register state, 
especially with concurrent GCs in mind. I'm assuming it's only during 
the call to the MH VM entry (but the existence of frame::safe_for_sender 
makes me less sure)? AFAIK the call counts as a safepoint (with oop map 
for it typically stored at the return offset). At this safepoint, the 
oop can only be stored at one of the 3 places listed at the start.

Thanks,
Jorn

[1] : 
https://github.com/openjdk/panama-foreign/blob/foreign-jextract/src/hotspot/cpu/x86/universalUpcallHandler_x86_64.cpp#L412-L416
[2] : 
https://github.com/openjdk/jdk/blob/master/src/hotspot/share/runtime/frame.cpp#L939-L946
[3] : 
https://github.com/openjdk/panama-foreign/compare/foreign-memaccess+abi...JornVernee:Deopt_Crash
[4] : 
https://github.com/openjdk/jdk/blob/master/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp#L339



More information about the hotspot-dev mailing list