RFR: 8299089: Instrument global jni handles with tag to make them distinguishable

Axel Boldt-Christmas aboldtch at openjdk.org
Wed Dec 21 12:31:49 UTC 2022


On Tue, 20 Dec 2022 11:44:33 GMT, Axel Boldt-Christmas <aboldtch at openjdk.org> wrote:

> Weak global jni handles are tagged so the GC can distinguish them when resolving the object. Today there is no cheap way of distinguishing global jni handles from local jni handles. For generational ZGC the OopStorage handles and the thread local handles semantical difference requires the handles to be distinguishable. 
> 
> This enhancements instruments the jni handles with a global tag similarly to the jweak tag. 
> 
> Note: 
>  * the s390 implementation has minimal changes and requires improvement.
>  * There is room for enchantment here to create the same abstraction that ppc uses for all platforms, i.e. move the resolve_jobject from the MacroAssembler to the BarrierSetAssembler which allows for more optimised code for GCs that can treat local and global handles the same. 
> 
> Testing: GHA. Oracle supported platforms tier1-3. Will run higher tiers. Has also been tested on the generational branch of ZGC for over three months. Requires testing on non Oracle platforms.

The new mechanism for distinguishing global jni handles is the same as the old mechanism for distinguishing weak global jni handles. That is a tag bit in the lower address bits. 

The actual tagging when creating a new global handle is negligible as creating a handle will be dominated by allocating in OopStorage and memory accesses. 

However the biggest change will be to code which resolves jobjects, both in generated code and in the runtime. The change essentially boils down to the following:

if (is_jweak_tagged(handle)) { 
     result = NativeAccess<ON_PHANTOM_OOP_REF>::oop_load(jweak_ptr(handle));
+  } else if (is_global_tagged(handle)) {
+    result = NativeAccess<>::oop_load(global_ptr(handle));
   } else {
-    result = NativeAccess<>::oop_load(jobject_ptr(handle));
+    result = *jobject_ptr(handle);
} 

where `is_*_tagged(handle)` checks the tag and `*_ptr(handle)` untags / removes the tag bits. 

As mentioned in the PR there is some room of improvement here. For example for G1 `NativeAccess<>::oop_load(untagged_ptr(handle));` is equivalent to `*untagged_ptr(handle)` because they are strong and needs no barriers. For the runtime code I do not believe that it matters but the generated code (in this case for G1) can be generated to be almost equivalent to the current generation with the only change being clearing the tag bits. This is already implemented for ppc. But I thought this could be for future RFE (adding `resolve_jobject` to `BarrierSetAssembler` and specialise for GCs), while this PR focuses on making the baseline code actually respect the semantics of our storage areas and their respective access invariants. 

There are also improvements to be made when we generate code from a context where we know that the jobject will be a global handle (upcall stub is one example). This is already done in the JFR write checkpoint epilogue and `LibraryCallKit::inline_native_getEventWriter` code, where resolving the jobject is simply:
```c++
assert(is_global_tagged(handle), "");
return NativeAccess<>::oop_load(global_ptr(handle));


The change to jniFastGetField will only ever see a difference if some platform has different performance characteristics when clearing the lowest bit vs clearing the two lowest bits. (Which I highly doubt)

Regardless I will be running performance testing over the holidays.

-------------

PR: https://git.openjdk.org/jdk/pull/11740


More information about the hotspot-dev mailing list