Skip to content

8385855: C2: IfNode::filtered_int_type should only allow CmpI#31395

Closed
eme64 wants to merge 45 commits into
openjdk:masterfrom
eme64:JDK-8385855-filtered_int_type-CmpU
Closed

8385855: C2: IfNode::filtered_int_type should only allow CmpI#31395
eme64 wants to merge 45 commits into
openjdk:masterfrom
eme64:JDK-8385855-filtered_int_type-CmpU

Conversation

@eme64

@eme64 eme64 commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Don't be fooled by the many changed lines: it is mostly testing

TLDR: The new assert revealed a bug, so I upgraded it to a condition.

Added lots of testing:

  • Regression testing: for assert and wrong result cases reported by this issue (reproduces at least down to 6u, so broken since a long time).
  • IR tests checking that subword iv cases get recognized as CountedLoop. Because the patterns are very brittle, and the optimization does not recognize all patterns it probably was intended for, I have lots of negative examples too. But at least the cases I do now have IR rules for will not deteriorate further.

Fuzzer test: moved to #31501, which covers CountedLoopConverter::has_truncation_wrap.

I originally also developed a template fuzzer for the optimization, but then during CI, discovered more bugs with it. So I decided to split the fuzzer test into a separate RFE, to avoid issues when backporting this fix to JDK27.

  • Fuzzer: covers patterns around CountedLoopConverter::has_truncation_wrap, with subword iv loops.

Bug 1: The Fuzzer found another bug in the same general code region:
JDK-8386482 C2 CountedLoopConverter::filtered_type_from_dominators: assert(_base == Int) failed: Not an Int
PR: #31481

Bug 2: Yet another bug found by the attached fuzzer:
JDKJDK-8386591 C2: wrong result because of broken truncation check in CountedLoopConverter::TruncatedIncrement::build


Details

This was filed as a regression of #28819, but I only added an assert there, to check that IfNode::filtered_int_type does not operate on anything else than CmpI. I added that assert with high but not perfect confidence, and no test failed, so I assumed the assert should be correct.

But now we found a reproducer where we get to IfNode::filtered_int_type from CountedLoopConverter::has_truncation_wrap. So we tripped the new assert. But it turns out that there was a bug here even before I added the assert, the assert just revealed that bug. We can get wrong results, because IfNode::filtered_int_type works with signed int ranges, and so if we use it on a CmpU, the type range will be incorrect.

CountedLoopConverter::has_truncation_wrap looks at loops that truncate the iv, i.e. cast it to byte/char/short. If we can prove that the iv never would have gone out of that range, we can drop the truncation, and make it a regular int-counted-loop, i.e. a CountedLoop. Loops that keep the truncation cannot be CountedLoop.
Specifically, we look before the loop, to see if the init value is in the truncated range. For this, we look for conditions with CmpI (or as before this bug also CmpU), and use IfNode::filtered_int_type to constrain the type of the init value using the comparison. But if we did this for a CmpU, and the type info was wrong, we might have concluded that the values are inside the truncated range, when they could actually go outside: wrong result!

I'm now even more sure that only CmpI should be allowed in IfNode::filtered_int_type, because it only handles signed ranges.


Reflection on the Optimization

The fix is conservative, and now only allows CmpI. There is a risk that IfNode::filtered_int_type allowed other shapes to optimize, where the optimization accidentally was not incorrect. But we now know that it is only provable correct for CmpI, and provably incorrect for some other cases (regression examples for CmpU).

To be honest, the CountedLoopConverter::has_truncation_wrap seems like it has deteriorated over time, and is not super general. It has some strange limitations:

  • It is quite shape-sensitive. Loop-peeling, do-while vs while, pre-loop checks, != exit checks instead of <, ... they all can make otherwise similar Java loops look different to C2. We don't recognize all shapes that could be turned into a CountedLoop.
  • TruncatedIncrement::build only recognizes a subset of truncation patterns, there are some original bugs and some patterns seem to have deteriorated over time.
    • missing: byte cast lowered through <<24 >>24, char cast lowered through & 0xffff.
    • suspicious: shift == 8 is ((x << 8) >> 8) is signed 24-bit truncation. But the implementation maps to BYTE. This looks like an original "bug", which does not create any real issues, just means we miss optimizations for byte. We should probably match shift == 24 instead, which would be <<24 >>24.
    • Some of these patterns are also idealized by new optimizations in LShiftINode::Ideal, and so they are not matched any more,
    • For char, we recognize & 0x7fff, and not the actual char truncation (& 0xffff). So we don't actually turn (char) truncation into CountedLoops. Actually, this is a bug, see: JDK-8386597.
    • Negative stride cases are not symmetric to positive stride cases, we do allow more positive cases. One example is in is_infinite_loop: if (limit_t->hi_as_long() > incr_t->hi_as_long()) { only makes sense for positive strides. I'm fairly sure this does not lead to bugs, but again: missed optimizations.

My conclusion: CountedLoopConverter::has_truncation_wrap is VERY brittle, and has deteriorated over time. I'm also not sure how important loops with byte, char and short iv really are. So maybe this is not so bad. Maybe we could even remove the optimization, and would not suffer any regressions. I welcome discussion here, but propose to fix the issue here anyway, as proposed. After all the assert is a JDK27 regression and should get fixed quickly.



Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed (2 reviews required, with at least 1 Reviewer, 1 Author)

Issue

  • JDK-8385855: C2: IfNode::filtered_int_type should only allow CmpI (Bug - P3)(⚠️ The fixVersion in this issue is [27] but the fixVersion in .jcheck/conf is 28, a new backport will be created when this pr is integrated.)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/31395/head:pull/31395
$ git checkout pull/31395

Update a local copy of the PR:
$ git checkout pull/31395
$ git pull https://git.openjdk.org/jdk.git pull/31395/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 31395

View PR using the GUI difftool:
$ git pr show -t 31395

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/31395.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper

bridgekeeper Bot commented Jun 5, 2026

Copy link
Copy Markdown

👋 Welcome back epeter! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk

openjdk Bot commented Jun 5, 2026

Copy link
Copy Markdown

@eme64 This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8385855: C2: IfNode::filtered_int_type should only allow CmpI

Reviewed-by: kvn, qamai, mchevalier

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 12 new commits pushed to the master branch:

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the master branch, type /integrate in a new comment.

@openjdk openjdk Bot changed the title JDK-8385855 8385855: C2: assert(cmp->Opcode() == Op_CmpI) failed: signed comparison required Jun 5, 2026
@openjdk

openjdk Bot commented Jun 5, 2026

Copy link
Copy Markdown

@eme64 The following label will be automatically applied to this pull request:

  • hotspot-compiler

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

@openjdk

openjdk Bot commented Jun 5, 2026

Copy link
Copy Markdown

The total number of required reviews for this PR has been set to 2 based on the presence of this label: hotspot-compiler. This can be overridden with the /reviewers command.

@eme64 eme64 changed the title 8385855: C2: assert(cmp->Opcode() == Op_CmpI) failed: signed comparison required 8385855: C2: IfNode::filtered_int_type should only allow CmpI Jun 11, 2026
@eme64 eme64 marked this pull request as ready for review June 11, 2026 12:47
@openjdk openjdk Bot added the rfr Pull request is ready for review label Jun 11, 2026
@mlbridge

mlbridge Bot commented Jun 11, 2026

Copy link
Copy Markdown

Webrevs

@vnkozlov vnkozlov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

Comment thread src/hotspot/share/opto/ifnode.cpp Outdated
// Val is always the lhs of the comparision: val <test> cmp2
if (cmp->in(1) == val) {
assert(cmp->Opcode() == Op_CmpI, "signed comparison required");
if (cmp->Opcode() != Op_CmpI) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will be a little more logical to have this just below const CmpNode* cmp = bol->in(1)->as_Cmp() instead of below the if.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively,

if (cmp->Opcode() == Op_CmpI && cmp->in(1) == val)

since not entering all the ifs there, is returning nullptr, and not doing anything.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, I'll go with Marc's suggestion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marc-chevalier @merykitty Can you re-review, please?

@merykitty merykitty left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

@openjdk openjdk Bot added the ready Pull request is ready to be integrated label Jun 12, 2026
@eme64

eme64 commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Just found another failure with the attached fuzzer:
JDKJDK-8386591 C2: wrong result because of broken truncation check in CountedLoopConverter::TruncatedIncrement::build

@openjdk openjdk Bot removed the ready Pull request is ready to be integrated label Jun 12, 2026
@eme64

eme64 commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

@merykitty @marc-chevalier @vnkozlov Ok, I decided, I'm moving the fuzzer test to a separate RFE: #31501

This allows us to fix the other two bugs, before integrating the fuzzer:

  • JDK-8386482 C2 CountedLoopConverter::filtered_type_from_dominators: assert(_base == Int) failed: Not an Int
  • JDK-8386591 C2: wrong result because of broken truncation check in CountedLoopConverter::TruncatedIncrement::build

(and there could be more bugs hiding here)

The IR test should be sufficient to give us some basic coverage, especially to ensure the optimization still works. Here, we just restrict to CmpU (and forbid CmpU), which is clearly the right thing to do, as the code can so far only handle signed-int ranges.

I'll need your re-approval to integrate this to JDK28, and then I'll backport to JDK27.

@merykitty merykitty left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your explanation, I think that is reasonable.

@vnkozlov vnkozlov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good.

@openjdk openjdk Bot added the ready Pull request is ready to be integrated label Jun 12, 2026
@eme64

eme64 commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

@marc-chevalier @vnkozlov @merykitty Thanks for the reviews, suggestions and approvals!

/integrate

@openjdk

openjdk Bot commented Jun 15, 2026

Copy link
Copy Markdown

Going to push as commit 841e288.
Since your change was applied there have been 22 commits pushed to the master branch:

Your commit was automatically rebased without conflicts.

@openjdk openjdk Bot added the integrated Pull request has been integrated label Jun 15, 2026
@openjdk openjdk Bot closed this Jun 15, 2026
@openjdk openjdk Bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review labels Jun 15, 2026
@openjdk

openjdk Bot commented Jun 15, 2026

Copy link
Copy Markdown

@eme64 Pushed as commit 841e288.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

@eme64

eme64 commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

/backport jdk:jdk27

@openjdk

openjdk Bot commented Jun 15, 2026

Copy link
Copy Markdown

@eme64 the backport was successfully created on the branch backport-eme64-841e2882-jdk27 in my personal fork of openjdk/jdk. To create a pull request with this backport targeting openjdk/jdk:jdk27, just click the following link:

➡️ Create pull request

The title of the pull request is automatically filled in correctly and below you find a suggestion for the pull request body:

Hi all,

This pull request contains a backport of commit 841e2882 from the openjdk/jdk repository.

The commit being backported was authored by Emanuel Peter on 15 Jun 2026 and was reviewed by Vladimir Kozlov, Quan Anh Mai and Marc Chevalier.

Thanks!

If you need to update the source branch of the pull then run the following commands in a local clone of your personal fork of openjdk/jdk:

$ git fetch https://github.com/openjdk-bots/jdk.git backport-eme64-841e2882-jdk27:backport-eme64-841e2882-jdk27
$ git checkout backport-eme64-841e2882-jdk27
# make changes
$ git add paths/to/changed/files
$ git commit --message 'Describe additional changes made'
$ git push https://github.com/openjdk-bots/jdk.git backport-eme64-841e2882-jdk27

⚠️ @eme64 You are not yet a collaborator in my fork openjdk-bots/jdk. An invite will be sent out and you need to accept it before you can proceed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hotspot-compiler [email protected] integrated Pull request has been integrated

Development

Successfully merging this pull request may close these issues.

4 participants