Skip to content

Commit d22f5c5

Browse files
committed
8386591: C2: wrong result because of broken truncation check in CountedLoopConverter::TruncatedIncrement::build
Reviewed-by: roland, kvn, qamai
1 parent 2d005ac commit d22f5c5

3 files changed

Lines changed: 133 additions & 11 deletions

File tree

src/hotspot/share/opto/loopnode.cpp

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3129,8 +3129,12 @@ Node* LoopLimitNode::Identity(PhaseGVN* phase) {
31293129
return this;
31303130
}
31313131

3132-
// Match increment with optional truncation:
3133-
// CHAR: (i+1)&0x7fff, BYTE: ((i+1)<<8)>>8, or SHORT: ((i+1)<<16)>>16
3132+
// CHAR: (i+1)&0x7fff Note: does NOT work for char cast (0xffff)
3133+
// BYTE: ((i+1)<<8)>>8 Note: does NOT work for byte cast (<< 24 >> 24)
3134+
// SHORT: ((i+1)<<16)>>16
3135+
//
3136+
// Note: in the future, we should fix both the BYTE and the CHAR case,
3137+
// to allow proper optimization of byte/char cast truncation.
31343138
void CountedLoopConverter::TruncatedIncrement::build(Node* expr) {
31353139
_is_valid = false;
31363140

@@ -3146,15 +3150,19 @@ void CountedLoopConverter::TruncatedIncrement::build(Node* expr) {
31463150
const TypeInteger* trunc_t = TypeInteger::bottom(_bt);
31473151

31483152
if (_bt == T_INT) {
3149-
// Try to strip (n1 & M) or (n1 << N >> N) from n1.
31503153
if (n1op == Op_AndI &&
3151-
n1->in(2)->is_Con() &&
3152-
n1->in(2)->bottom_type()->is_int()->get_con() == 0x7fff) {
3153-
// %%% This check should match any mask of 2**K-1.
3154-
t1 = n1;
3155-
n1 = t1->in(1);
3156-
n1op = n1->Opcode();
3157-
trunc_t = TypeInt::CHAR;
3154+
n1->in(2)->is_Con()) {
3155+
// Unsigned truncation.
3156+
// Pattern: ((i+1) & mask)
3157+
jint mask = n1->in(2)->bottom_type()->is_int()->get_con();
3158+
switch (mask) {
3159+
case 0x7fff: // Unsigned 15-bit truncation. For historical reasons.
3160+
t1 = n1;
3161+
n1 = t1->in(1);
3162+
n1op = n1->Opcode();
3163+
trunc_t = TypeInt::make_unsigned(0, mask, 0);
3164+
break;
3165+
}
31583166
} else if (n1op == Op_RShiftI &&
31593167
n1->in(1) != nullptr &&
31603168
n1->in(1)->Opcode() == Op_LShiftI &&

src/hotspot/share/opto/loopnode.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2105,7 +2105,6 @@ class CountedLoopConverter {
21052105
bool is_valid() const { return _is_valid; }
21062106
Node* incr() const { return _incr; }
21072107

2108-
// Optional truncation for: CHAR: (i+1)&0x7fff, BYTE: ((i+1)<<8)>>8, or SHORT: ((i+1)<<16)>>16
21092108
Node* outer_trunc() const { return _outer_trunc; } // the outermost truncating node (either the & or the final >>)
21102109
Node* inner_trunc() const { return _inner_trunc; } // the inner truncating node, if applicable (the << in a <</>> pair)
21112110
const TypeInteger* trunc_type() const { return _trunc_type; }
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
package compiler.loopopts;
25+
26+
/*
27+
* @test
28+
* @bug 8386591
29+
* @summary Test case for TruncatedIncrement::build /
30+
* CountedLoopConverter::has_truncation_wrap where we got wrong
31+
* results, because we confused "& 0x7fff" as range [0..65535]
32+
* instead of [0..32767].
33+
* @library /test/lib /
34+
* @run main/othervm -Xcomp
35+
* -XX:-TieredCompilation
36+
* -XX:CompileCommand=compileonly,${test.main.class}::test*
37+
* ${test.main.class}
38+
* @run main ${test.main.class}
39+
*/
40+
41+
public class TestTruncationWrapBadCharWrap {
42+
interface TestMethod {
43+
int call();
44+
}
45+
46+
public static void main(String[] args) {
47+
int failures = 0;
48+
49+
failures += run("test1", () -> test1(), 1402);
50+
failures += run("test2", () -> test2(), 2037);
51+
failures += run("test3", () -> test3(), 171761184);
52+
53+
if (failures > 0) {
54+
throw new RuntimeException("failures: " + failures);
55+
}
56+
}
57+
58+
static int run(String name, TestMethod t, int expected) {
59+
for (int i = 0; i < 10_000; i++) {
60+
int result = t.call();
61+
if (result != expected) {
62+
System.out.println(name + " wrong result: " + result + " vs " + expected);
63+
return 1;
64+
}
65+
}
66+
return 0;
67+
}
68+
69+
70+
static int test1() {
71+
int sum = 0;
72+
// The entry value is outside the range [0..32767], but inside [0..65535].
73+
int i = (char)38405;
74+
while (32 < i) {
75+
sum++;
76+
// Ignoring truncation would require values to be in range [0..32767].
77+
// But unfortunately, we classified this as CHAR, and checked for [0..65535].
78+
i = (i - 4) & 0x7fff;
79+
}
80+
return sum;
81+
}
82+
83+
static int test2() {
84+
int sum = 0;
85+
// We have 32767 - 32758 = 9 < 48, so the limit is too close to the wrap
86+
// limit, and wrap is possible. But since 0x7fff got mapped to CHAR,
87+
// we accidentally checked 65535 - 32758 < 48, and conclude wrap is not
88+
// possible.
89+
for (int i = 519; i < 32758; i = (i + 48) & 0x7fff) {
90+
sum++;
91+
}
92+
return sum;
93+
}
94+
95+
static int opaqueCounter;
96+
97+
static boolean opaqueCheck() {
98+
return opaqueCounter++ > 10448;
99+
}
100+
101+
static int test3() {
102+
opaqueCounter = 0;
103+
int sum = 0;
104+
int i;
105+
// Similar as with test2:
106+
// We should check 32767 - 32766 = 1 < 50, so wrap possible. But we
107+
// wrongly classified it as CHAR and checked 65535 - 32766 < 50, and
108+
// concluded there is no wrap.
109+
for (i = 2046; i <= 32766; i = (i + 50) & 0x7fff) {
110+
sum += i + 1;
111+
if (opaqueCheck()) { break; }
112+
}
113+
return sum + i;
114+
}
115+
}

0 commit comments

Comments
 (0)