RFR: 8370502: C2: segfault while adding node to IGVN worklist [v3]

Kerem Kat krk at openjdk.org
Thu Nov 27 17:51:12 UTC 2025


On Thu, 20 Nov 2025 16:30:10 GMT, Manuel Hässig <mhaessig at openjdk.org> wrote:

>> Kerem Kat has updated the pull request with a new target base due to a merge or a rebase. The incremental webrev excludes the unrelated changes brought in by the merge/rebase. The pull request contains seven additional commits since the last revision:
>> 
>>  - rename test file
>>  - Merge branch 'master' into fix-c2-segfault-unlocknode
>>  - fix test spacing
>>  - Update src/hotspot/share/opto/macro.cpp
>>    
>>    Co-authored-by: Manuel Hässig <manuel at haessig.org>
>>  - Update src/hotspot/share/opto/macro.cpp
>>    
>>    Co-authored-by: Manuel Hässig <manuel at haessig.org>
>>  - copyright format fix?
>>  - 8370502: C2: segfault while adding node to IGVN worklist
>
> Thank you for working on this, @krk. And nice job reducing the test further!
> 
> I have a few questions and style comments below.

Thank you for the comments. To answer three remaining comments from @mhaessig, @dean-long and @TobiHartmann about the same subject:

`CallNode::extract_projections` sets `fallthrough_memproj` to `null` when the node has `outcnt == 0`. It does not assert on this because `do_asserts = false` is passed.

Regarding `mem` vs. `fallthrough_memproj`, replacing `fallthrough_memproj` with `mem` appears to change the semantics from memory outputs to memory inputs.

Nothing after unlock (exit from the synchronized block) uses memory, as `a[i] =0` would throw (`a` is a zero-length array), making the rest of the `test` method unreachable.

This raises the question: why is `synchronized` reachable at all, as it is also after `a[i] = 0`.

About the invariant, I have found mixed null checks of `fallthrough_memproj`:


## Analysis of all reads of `fallthrough_memproj`

### Does null-check

* `PhaseMacroExpand::expand_unlock_node` checks now with the current PR.
* `ArrayCopyNode::finish_transform`
* `ShenandoahBarrierC2Support::find_bottom_mem`
* `GraphKit::replace_call`
* `PhaseMacroExpand::expand_allocate_common`
* `PhaseMacroExpand::yank_alloc_node`
* `PhaseMacroExpand::eliminate_locking_node`
* `StringConcat::eliminate_call`

#### (checks equality with a non-null `Node*`)

* `MemoryGraphFixer::get_ctrl`
* `CallGenerator::do_late_inline_helper`

### No check, but `do_asserts = true`

* `PhaseMacroExpand::process_users_of_allocation` asserts for `callprojs`, checks for null for `_callprojs`.

### No check

* `PhaseMacroExpand::expand_lock_node`
* `PhaseMacroExpand::generate_arraycopy`
* `PhaseMacroExpand::generate_slow_arraycopy`
* `PhaseMacroExpand::expand_arraycopy_node`

## To summarize

1. If `do_asserts = false` is passed to `CallNode::extract_projections`, it means `fallthrough_memproj` (and some others) may be `null`. After this, `fallthrough_memproj` should be null-checked. Currently, only code that needs null-checking is in `PhaseMacroExpand` class.
2. Why is `synchronized` reachable in the repro at all, as it is also after `a[i] = 0`.

Based on this analysis, I propose creating new issues for these two points and keep the current PR focused on `PhaseMacroExpand::expand_unlock_node`.

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

PR Comment: https://git.openjdk.org/jdk/pull/28432#issuecomment-3586912195


More information about the hotspot-compiler-dev mailing list