### Mismatching Stack Sizes During Late Inlining
The re-enabled assert and some tests in `TestNullableInlineTypes` fail with a 
stack mismatch assert when trying to combine exception states when replacing 
the non-static `CallStaticJava` node in `GraphKit::replace_call()` for a just 
late inlined call with the actual result . In `test84()`, for example, we have 
one exception state with the JVM expression stack pointer (later referred to as 
just "stack pointer") set to 1 and the other set to 0. This means that we throw 
from two different paths with unequal stack sizes which is a problem.

### Reason for Stack Size Mismatch
At the time of the assertion failure, we compare two states from two exception 
safepoints:
1. `builtin_throw()` from a null check from an ordinary field access in the 
callee (currently being late inlined):
https://github.com/openjdk/valhalla/blob/4c23f0ed5d6a89cd1e7ab8550b367bca296a9f30/src/hotspot/share/opto/graphKit.cpp#L1543-L1544
We clear the stack due to not having an exception handler:
https://github.com/openjdk/valhalla/blob/931493bdedd35731569225e1a5c644fbeeea6372/src/hotspot/share/opto/graphKit.cpp#L614-L619
2. `builtin_throw()` from the receiver null check before the non-static call to 
the callee (i.e. still in the caller). We have an exception handler and thus do 
not clear the stack as for `1.`. We have all the arguments on the stack:
https://github.com/openjdk/valhalla/blob/4c23f0ed5d6a89cd1e7ab8550b367bca296a9f30/src/hotspot/share/opto/graphKit.hpp#L692-L694

This explains the different stack sizes.

When we have an exception handler in the callee, we would already handle the 
exception states in `Parse::do_exceptions()` and do not need to combine them in 
the caller after the callee was late inlined.

### Why Is this not Causing more Problems?
This seems to be quite a common case to have different stack size, so why don't 
we hit more problems? First, we notice that when we normally add a new 
exception state, we combine states only when stack sizes are equal:
https://github.com/openjdk/valhalla/blob/931493bdedd35731569225e1a5c644fbeeea6372/src/hotspot/share/opto/graphKit.cpp#L304-L308

We only combine exception states with different stack sizes when calling 
`combine_exception_states()` from `combine_and_pop_all_exception_states()`. 
This happens at two places:
- `Compile::rethrow_exceptions()`: When we still have exceptions states when 
calling this method, it means that there is no exception handler in the current 
method. Therefore, the stack pointer should have been cleared as in `1.` above.
- `GraphKit::replace_call()`: We just late inlined a call from which there are 
exception states to be combined (i.e. the callee had no handler). The stack 
pointer should be cleared as in `1.` and set to zero. `rethrow_exceptions()` 
was already called at this point. As a result, there are no exceptions left in 
the caller to handle and we only combine the callee related exception states. 
Since we already handle the callee exceptions with `do_exceptions()` when 
having a handler or reset the stack pointer to zero otherwise, it looks like we 
should be fine. But there is a problem with the receiver null check just before 
the late inlined call: We have all the arguments on the stack and adjust the 
stack size accordingly. When now having an additional exception in the callee 
without an exception handler (i.e. stack size set to zero), we combine a 
non-zero stack size with a zero stack size and hit the assertion failure. 

### Valhalla Related Issue
Why aren't we seeing the assertion failure above also in mainline? There were 
similar issues in the past but they all were fixed (e.g. 
[JDK-8275638](https://bugs.openjdk.org/browse/JDK-8275638) which added the 
stack clearing when we don't have any exception handler). But this bug here is 
specific to value classes: We have a bug in checking the null marker of a value 
object receiver.

In parsing, we already emit a receiver null check for the call to be inlined 
later. When later late inlining the call, we emit another receiver null check 
(the one that is causing the assertion failure above) which normally gets 
folded because we already statically know that the receiver is non-null (we 
already added a receiver null check in parsing and now know that the receiver 
is non-null). But that's not always happening for value object receivers. We 
have this graph:

<img width="575" height="274" alt="image" 
src="https://github.com/user-attachments/assets/2b7e1904-7a47-4dc2-a209-43499b33248e";
 />

The receiver `20403 InlineType` is actually `NonNull` (i.e. null marker is one) 
but we do the following:
https://github.com/openjdk/valhalla/blob/703ac967a48926c145cef69945dfc8fc7fc3be01/src/hotspot/share/opto/graphKit.cpp#L1372-L1378

For `InlineType` oop inputs, we walk up the oop chain until we hit a 
non-inline-type oop input and only then look at the null marker. In the failing 
case, we walk one `InlineType` up and then have a phi for the null marker and 
thus wrongly conclude that the receiver `20403 InlineType` is maybe null. We 
should have directly checked the null marker of the receiver instead of first 
walking up the oop inputs which is exactly what I propose as a fix in this 
patch. This lets the receiver null check be folded and avoids the problematic 
exception state.

Note that it is unusual to have another `InlineType` as an oop input. But this 
is possible with late inlining and returning a value object.

Thanks,
Christian

---------
- [X] I confirm that I make this contribution in accordance with the [OpenJDK 
Interim AI Policy](https://openjdk.org/legal/ai).

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

Commit messages:
 - fix

Changes: https://git.openjdk.org/valhalla/pull/2486/files
  Webrev: https://webrevs.openjdk.org/?repo=valhalla&pr=2486&range=00
  Issue: https://bugs.openjdk.org/browse/JDK-8325632
  Stats: 10 lines in 2 files changed: 0 ins; 7 del; 3 mod
  Patch: https://git.openjdk.org/valhalla/pull/2486.diff
  Fetch: git fetch https://git.openjdk.org/valhalla.git pull/2486/head:pull/2486

PR: https://git.openjdk.org/valhalla/pull/2486

Reply via email to