On Thu, 5 Nov 2026 15:17:04 GMT, Quan Anh Mai <[email protected]> wrote:
> ## Problem
>
> When C2 parses a variable, it tries to be smart about the type of that
> variable. If a method parameter declares that it is of type `A`, and `A` is
> an abstract class that only has a single concrete subclass `B`, then C2
> eagerly assumes that the parameter must be of type `B`. This property, in the
> presence of concurrent class loading/unloading, does not always hold, as the
> VM can always load another concrete subclass `C` of `A` that is unrelated to
> `B`. To avoid incorrect execution arised from this issue, C2 relies on
> `dependencies`, which encodes various kinds of assumptions the compiler made.
> Among those, we have an assumption called `unique_concrete_subclass`, which
> will be invalidated when `C` is loaded, triggering the deoptimization and
> invalidation of the aforementioned compiled method.
>
> However, this mechanism is both inadequate and not timely. The main issue is
> that the concurrent load of `C` may not only happen after the compilation has
> finished, but also during the compilation process. This leads to CHA
> computation giving contradictory results when called at different time in the
> compilation process. There are 2 examples which were reported:
>
> 1.
>
> if (b) {
> A a1 = ...;
> } else {
> C a2 = ...;
> }
> A a = Phi(a1, a2);
>
> If `A a = Phi(a1, a2)` is parsed before `C a2 = ...`, and `C` has not been
> loaded then, the compiler will assume that `a` must be of type `B`. This
> leads to a `Phi` of type `B` with one of its inputs being a node of type `C`
> which is unrelated. This graph shape is nonsense, and all kinds of issues can
> appear from various optimization passes which do not expect such a
> conflicting graph.
>
> 2.
>
> A a = call();
>
> If when `a` is parsed, `C` has not been loaded, the compiler will assume `a`
> is of type `B`. However, if `call` is late-inlined, and it turns out that the
> returned value is of type `C`. All uses of `a` will suddenly have their input
> changed from a `B` to a `C`, which may also lead to unexpected failures.
>
> ## Overview of this PR
>
> This PR tries to detect CHA inconsistencies, and triggers a compilation
> failure when such a paradox happens. The core invariant that needs holding is
> that, given type `X` and `Y`, if `X` is a subtype of `Y`, then the result of
> narrowing `X` should also be a subtype of the result of narrowing `Y`. This
> invariant does not need to hold in the VM, it only needs to hold in the local
> `ciEnv`, which is the local view of the compiler with respect to the class
> hierarchy.
>
> In some...
src/hotspot/share/code/dependencies.cpp line 114:
> 112: void Dependencies::assert_unique_implementor(ciInstanceKlass* ctxk,
> ciInstanceKlass* uniqk) {
> 113: check_ctxk(ctxk);
> 114: check_unique_implementor(ctxk, uniqk);
I don't think this check is ensured, so I removed it.
src/hotspot/share/opto/parse1.cpp line 2511:
> 2509: return vt;
> 2510: } else {
> 2511: PhiNode* phi = new PhiNode(region, t);
The changes in this file are to revert
[JDK-8387328](https://bugs.openjdk.org/browse/JDK-8387328). I tried inserting
an assert here that `o->bottom_type()` must be a subset of `t`, but there are
some issues with method handle intrinsics that prevent this. To be specific, I
find that a method is supposed to return `BoundedMethodHandle`, but the
`ProjNode` is an `Object`.
src/hotspot/share/opto/phaseX.cpp line 1248:
> 1246: static bool klass_bottom_type_invalidated(const Node* n, const Type*
> new_type) {
> 1247: // Concurrent class loading can invalidate a klass bottom type.
> 1248: const Type* bt = n->bottom_type();
The changes in this file and `phaseX.hpp` are to revert
[JDK-8393482](https://bugs.openjdk.org/browse/JDK-8393482).
-------------
PR Review Comment: https://git.openjdk.org/jdk/pull/33753#discussion_r4528391440
PR Review Comment: https://git.openjdk.org/jdk/pull/33753#discussion_r4528375809
PR Review Comment: https://git.openjdk.org/jdk/pull/33753#discussion_r4528381483