-
Notifications
You must be signed in to change notification settings - Fork 6.2k
8356813: Improve Mod(I|L)Node::Value #25254
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
👋 Welcome back hgreule! A progress list of the required criteria for merging this PR into |
|
@SirYwell 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: 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 497 new commits pushed to the
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 |
Webrevs
|
Can we return Besides, #17508 should be merged right after JDK-25 folk, do you want to wait for it first? |
That should work too and might be more intuitive. I assume there also isn't much benefit in constant-folding users of the mod if the mod is known to fail (which seems to be the only benefit of not returning TOP?).
We can wait if it makes sense to do the unsigned variants here too, but I'm also fine with doing it separately. |
mhaessig
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for working on this, @SirYwell. I especially like the citations directly from the spec to motivate and justify the optimizations.
I commented only on the int side of things, but the comments apply equally to the long changes.
You exclude zero from the dividend magnitude only based on the constant check. That is not correct. You have to check the range as well to exclude zero. Hence, it would also be nice to have test cases where the value is known to be in a given range in the ideal graph. To get such a value, you can call array.length(), which is always >=0, or use Parse::sharpen_type_after_if():
jdk/src/hotspot/share/opto/parse2.cpp
Lines 1772 to 1794 in effe40a
| switch (btest) { | |
| case BoolTest::eq: // Constant test? | |
| { | |
| const Type* tboth = tcon->join_speculative(tval); | |
| if (tboth == tval) break; // Nothing to gain. | |
| if (tcon->isa_int()) { | |
| ccast = new CastIINode(control(), val, tboth); | |
| } else if (tcon == TypePtr::NULL_PTR) { | |
| // Cast to null, but keep the pointer identity temporarily live. | |
| ccast = new CastPPNode(control(), val, tboth); | |
| } else { | |
| const TypeF* tf = tcon->isa_float_constant(); | |
| const TypeD* td = tcon->isa_double_constant(); | |
| // Exclude tests vs float/double 0 as these could be | |
| // either +0 or -0. Just because you are equal to +0 | |
| // doesn't mean you ARE +0! | |
| // Note, following code also replaces Long and Oop values. | |
| if ((!tf || tf->_f != 0.0) && | |
| (!td || td->_d != 0.0)) | |
| cast = con; // Replace non-constant val by con. | |
| } | |
| } | |
| break; |
eme64
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@SirYwell Thanks for looking into this, that looks promising!
I have two bigger comments:
- Could we unify the L and I code, either using C++ templating or
BasicType? It would reduce code duplication. - Can we have some tests where the input ranges are random as well, and where we check the output ranges with some comparisons?
Copied from the code comment:
Nice work with the examples you already have, and randomizing some of it!
I would like to see one more generalized test.
- compute
res = lhs % rhs- Truncate both
lhsandrhswith randomly produced bounds from Generators, like this:lhs = Math.max(lo, Math.min(hi, lhs)).- Below, add all sorts of comparisons with random constants, like this:
if (res < CON) { sum += 1; }. If the output range is wrong, this could wrongly constant fold, and allow us to catch that.Then fuzz the generated method a few times with random inputs for
lhsandrhs, and check that thesumandresvalue are the same for compiled and interpreted code.I hope that makes sense :)
This is currently my best method to check if ranges are correct, and I think it is quite important because often tests are only written with constants in mind, but less so with ranges, and then we mess up the ranges because it is just too tricky.This is an example, where I asked someone to try this out as well:
https://github.com/openjdk/jdk/pull/23089/files#diff-12bebea175a260a6ab62c22a3681ccae0c3d9027900d2fdbd8c5e856ae7d1123R404-R422
|
Thanks @eme64. I unified the code now using I'll also look into your suggestions for the tests, thanks for the input there. |
|
@eme64 I merged master and hopefully addressed your latest comments. Now that we have #17508 integrated, I could also directly update the unsigned variant, but I'm also fine with doing that separately. WDYT? I also checked the constant folding part again (or generally whenever the RHS is a constant), these code paths are indeed not used by PhaseGVN directly (but by PhaseCCP and PhaseIdealLoop). That makes it a bit difficult to test that part properly. |
src/hotspot/share/opto/divnode.cpp
Outdated
| // We must be modulo'ing 2 int constants. | ||
| // Check for min_jlong % '-1', result is defined to be '0' | ||
| // We don't need to check for min_jint % '-1' as its result is defined when using jlong. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems both cases are "defined"... so it sounds a little strange when you say ... as its result is defined when using jlong. Both are "defined", it would be nice if you said explicitly "how" they are defined.
But wait... how does this work. We used to do the same trick above for min_jint when using Jint, correct?
// We must be modulo'ing 2 float constants.
// Check for min_jint % '-1', result is defined to be '0'.
if( i1->get_con() == min_jint && i2->get_con() == -1 )
return TypeInt::ZERO;
Is this case here really handling that? It doesn't look like it.
Do we have tests for all these cases?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, seems we have discussed this before... Maybe it is best to just keep the old behavior and do the test for min_jint as well if we have T_INT. I'd rather be safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can add min_jint as a special case again. But I just had a different idea, as x % -1 == 0 for any x, I could also generalize the check and only test for -1. WDYT?
src/hotspot/share/opto/divnode.cpp
Outdated
| // The magnitude of the divisor is in range [1, 2^31] or [1, 2^63], depending on the BasicType. | ||
| // We know it isn't 0 as we handled that above. | ||
| // That means at least one value is nonzero, so its absolute value is bigger than zero. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm actually struggling to follow this here. Can you define "magnitude" for the reader? Maybe there is some JVMS definition you can mention. And which "value" are you refering to, that is nonzero here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // The magnitude of the divisor is in range [1, 2^31] or [1, 2^63], depending on the BasicType. | |
| // We know it isn't 0 as we handled that above. | |
| // That means at least one value is nonzero, so its absolute value is bigger than zero. | |
| // We checked that t2 is not the zero constant. Hence at least i2->_lo or i2->_hi must be non-zero, | |
| // and hence its its absoute value is bigger than zero. Hence, the magnitude of the divisor (i.e. the | |
| // largest absolute value for any value in i2) must be in the range [1, 2^31] or [1, 2^63], depending | |
| // on the BasicType. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Magnitude is what the JVMS uses, that's why I used it. But I like your suggested wording, I'll adapt it.
Let's keep the patch as it is. With #17508 we will have to also probably refactor and add more tests, if we want to do any unsigned and known-bit optimizations. @SirYwell Thanks for the updates, I had a few more comments, but we are getting there :) |
Thanks for the review :)
Yes,
https://gist.github.com/SirYwell/151a48c90d12593bf500028389bdd07c this is an example. (Currently, we don't detect patterns like |
|
@eme64 gentle ping in case you missed my latest changes :) |
|
I also filed https://bugs.openjdk.org/browse/JDK-8366815 now regarding the early transformation of div/mod by constants. |
|
Thanks for filing the issue! I left some comments there. We could delay div/mod by constants to after loop opts. And we could even optimize div/mod in loops that have loop-invariant divisor ;) |
|
@SirYwell The changes look good to me, thanks for working on this! I'll now run some internal testing, before approving. Please ping me again in 24h if I don't report back by then :) |
eme64
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests pass, approved 😊
@merykitty @mhaessig your turn 😉
merykitty
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice consolidation also. I have only some small style suggestion.
src/hotspot/share/opto/divnode.cpp
Outdated
| // Mod by zero? Throw exception at runtime! | ||
| if( !i2->get_con() ) return TypeInt::POS; | ||
| if (t2 == TypeInteger::zero(bt)) { | ||
| return TypeInt::TOP; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TypeInt::TOP is actually Type::TOP
src/hotspot/share/opto/divnode.cpp
Outdated
| lo = MAX2(lo, i1->lo_as_long()); | ||
| hi = MIN2(hi, i1->hi_as_long()); | ||
| } | ||
| return TypeInteger::make(lo, hi, MAX2(i1->_widen,i2->_widen), bt); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small style: space after comma.
src/hotspot/share/opto/divnode.cpp
Outdated
| return TypeInt::ZERO; | ||
| const TypeInteger* i1 = t1->isa_integer(bt); | ||
| const TypeInteger* i2 = t2->isa_integer(bt); | ||
| if (i1 == nullptr || i2 == nullptr) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If they are not TOP here, isa_integer should never return nullptr, it's better to do an assert here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess using is_integer directly might make sense then?
|
@merykitty thanks, I hopefully addressed your comments :) @eme64 do you want to re-run the tests once again? |
|
@SirYwell Launching tests 🚀 |
|
I noticed one parameter was unused, I removed it now. This shouldn't affect testing I guess. |
eme64
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Testing looks good. Minor changes should be ok, as long as GitHub Actions passes.
Thanks for all the work @SirYwell !
|
Thanks @eme64! Do I need another re-approval from @merykitty or are we ready to integrate? |
|
@SirYwell @merykitty Let's give him 24h. If he does not respond, you can integrate in my opinion. |
|
Thanks everyone for the patience and the reviews :) /integrate |
|
Going to push as commit c7f014e.
Your commit was automatically rebased without conflicts. |
| // We always generate the dynamic check for 0. | ||
| // 0 MOD X is 0 | ||
| if( t1 == TypeInt::ZERO ) return TypeInt::ZERO; | ||
| if (t1 == TypeInteger::zero(bt)) { return t1; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the culprit for JDK-8356813 is this place. We need to check for the divisor being a constant 0 and return Type::TOP before this check and the check below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I already worked a bit on it, see https://github.com/SirYwell/jdk/tree/fix/mod-not-monotonic but I didn't have time to create a PR yet.
This change improves the precision of the
Mod(I|L)Node::Value()functions.I reordered the structure a bit. First, we handle constants, afterwards, we handle ranges. The bottom checks seem to be excessive (
Type::BOTTOMis covered by usingisa_(int|long)(), the local bottom is just the full range). Given we can even give reasonable bounds if only one input has any bounds, we don't want to return early.The changes after that are commented. Please let me know if the explanations are good, or if you have any suggestions.
Monotonicity
Before, a 0 divisor resulted in
Type(Int|Long)::POS. Initially I wanted to keep it this way, but that violates monotonicity during PhaseCCP. As an example, if we see a 0 divisor first and a 3 afterwards, we might try to go from>=0to-2..2, but the meet of these would be>=-2rather than-2..2. UsingType(Int|Long)::ZEROinstead (zero is always in the resulting value if we cover a range).Testing
I added tests for cases around the relevant bounds. I also ran tier1, tier2, and tier3 but didn't see any related failures after addressing the monotonicity problem described above (I'm having a few unrelated failures on my system currently, so separate testing would be appreciated in case I missed something).
Please review and let me know what you think.
Other
The
UMod(I|L)Nodes were adjusted to be more in line with its signed variants. This change diverges them again, but similar improvements could be made after #17508.During experimenting with these changes, I stumbled upon a few things that aren't directly related to this change, but might be worth to further look into:
Mod(I|L)Nodewith more but less expensive nodes in::Ideal(). Type analysis for these nodes combined is less precise, means we miss potential cases were this would help e.g., removing range checks. Would it make sense to delay the replacement?char. I noticed that method parameters of sub-int integer types all fall back toTypeInt::INT. This seems to be an intentional change of 200784d. The bug report is private, so I can't really judge if that part is necessary, but it seems odd.Progress
Issue
Reviewers
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/25254/head:pull/25254$ git checkout pull/25254Update a local copy of the PR:
$ git checkout pull/25254$ git pull https://git.openjdk.org/jdk.git pull/25254/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 25254View PR using the GUI difftool:
$ git pr show -t 25254Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/25254.diff
Using Webrev
Link to Webrev Comment