aarch64: Concurrent class unloading, nmethod barriers, ZGC
Erik Österlund
erik.osterlund at oracle.com
Thu Jan 9 22:35:12 UTC 2020
Hi Stuart and Andrew,
Right, when it comes to native wrappers, we do inject entry barriers for
that on x86. The main reason for that is that I am allergic to "special"
nmethods that you have to remember work differently all the time. We
have too many of them. The only nmethod that regrettably does not have
entry barriers is the method handle intrinsic. That seems fine but I'm
not quite happy about it.
Other than that, we also do need the barriers for correctness. Last time
I thought about that, I recall there were a few problematic hypothetical
situations I wanted to avoid. For example, consider the following
obscure race condition (suitable beverage while reading advised):
1. Load abstract class A with non-static method foo.
2. Load class B, inheriting from A, from a separate class loader,
overriding foo with a native method (that gets a native wrapper).
3. JIT nmethod with a virtual call to A.foo. The compiler will with CHA
decide that there is only a single concrete foo implementation in the
system (B::foo), due to there being a single implementation of A, which
turns out to be our native wrapper. When this happens an optimized
virtual call is generated with a direct call emitted (originally
pointing at a resolution stub for the very first call), but the holder
oop of B (it's class loader) is not inserted to the oop section.
Instead, an entry is added in the dependency context to keep track of
this nmethod so the caller nmethod (calling the native wrapper) can get
deoptimized if the unique callee for A assumption changes.
4. Call the JIT-compiled call of A.foo with an instance of B, resolve it
and patch the direct call to the native wrapper (B.foo *verified* entry,
due to being an optimized virtual call).
5. Release the reference to the class loader of B, and wait until the
class loader dies, and hence B dies.
6. Before concurrent class unloading kicks in (concurrently) and walks
dependency contexts of dead things to invalidate them (which would
invalidate the caller nmethod), load a class C also inheriting from A
and overriding a concrete implementation of foo. When loading that
class, the dependency context walk for invalidating e.g. CHA
inconsistencies skip over the is_unloading() nmethods (including the
native wrapper), due to race conditions that ended up giving that
responsibility to the concurrent GC thread (which has not gotten to it yet).
7. Reuse the same JIT-compiled virtual call of A.foo but pass in a new
instance of C. The state of the callsite is now a direct call to B.foo,
and it's about to get deoptimized, but isn't yet. But B.foo
is_unloading() because B is dead, making the one oop of the native
wrapper (the holder oop of B) dead, and hence the native wrapper
is_unloading().
Now in this scenario, without an nmethod entry barrier, we can end up
calling a dead method. The nmethod entry barrier guards that by
enforcing the invariant that we can't enter dead nmethods.
Hope this makes sense and helps understanding why the native wrapper
ought to have an entry barrier.
Thanks,
/Erik
On 2020-01-09 17:02, Stuart Monteith wrote:
> Thank you Andrew, that compiles and runs without error - the
> deoptimize method is definitely being provoked. and continues without
> apparent problems.
>
> I've been trying to insert constants, and the issue you mention is
> tripped when we enter a native method wrapper. Eric can perhaps
> correct me, but I presume we might have to deoptimise a native method
> if it was overriding a JIT-compiled method and it is subsequently been
> unloaded.
>
> In x86 it is inserted here:
> http://hg.openjdk.java.net/jdk/jdk/file/6d23020e3da0/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp#l2204
> on aarch64 I added the change in our generate_native_wrapper.
>
>
> BR,
> Stuart
>
>
> On Wed, 8 Jan 2020 at 15:37, Andrew Haley <aph at redhat.com> wrote:
>> On 1/8/20 2:23 PM, Stuart Monteith wrote:
>>> I see there is LIR_Assembler::int_constant, which is only for C1, the
>>> equivalent is MacroAssembler::ldr_constant, which uses an
>>> InternalAddress.
>> There is MacroAssembler::int_constant(n). It is there, and it returns an
>> address that you can use with ADR and/or LDR . It won't work with a native
>> method because they have no constant pool (int_constant() will return NULL)
>> but I don't think you need barriers for native methods.
>>
>> (Um, perhaps you do, for synchronized ones? They have a reference to a class.)
>>
>> Anyway, this is your patch with a working (probably) deoptimize handler:
>>
>> http://cr.openjdk.java.net/~aph/aarch64-jdk-nmethod-barriers-3.patch
>>
>> --
>> Andrew Haley (he/him)
>> Java Platform Lead Engineer
>> Red Hat UK Ltd. <https://www.redhat.com>
>> https://keybase.io/andrewhaley
>> EAC8 43EB D3EF DB98 CC77 2FAD A5CD 6035 332F A671
>>
More information about the hotspot-gc-dev
mailing list