diff --git a/src/hotspot/share/opto/loopnode.cpp b/src/hotspot/share/opto/loopnode.cpp index 7c62e313803b3..7c9bef84d3a9b 100644 --- a/src/hotspot/share/opto/loopnode.cpp +++ b/src/hotspot/share/opto/loopnode.cpp @@ -3155,6 +3155,13 @@ void CountedLoopConverter::TruncatedIncrement::build(Node* expr) { n1 = t1->in(1); n1op = n1->Opcode(); trunc_t = TypeInt::CHAR; + // TODO: fix bug + // Problem: we accept mask 0x7fff which produces range [0..32767]. + // But we return CHAR, which will check for wrap in [0..65535]. + // This leads to wrong results, because we ignore wrap. + // Matching 0x7fff is also a bit silly. I would assume users are + // more likely to want to use "(char)" cast truncation, which + // would lead to a 0xffff mask, with range [0..65535]. } else if (n1op == Op_RShiftI && n1->in(1) != nullptr && n1->in(1)->Opcode() == Op_LShiftI && @@ -3171,6 +3178,15 @@ void CountedLoopConverter::TruncatedIncrement::build(Node* expr) { trunc_t = TypeInt::SHORT; } else if (shift == 8) { trunc_t = TypeInt::BYTE; + // TODO: maybe fix missing optimization + // shift == 8 means this pattern: + // ((x << 8) >> 8), which only removes the uppermost + // 8 bits, so leaves a 24-bit value. But we probably + // wanted to match ((x << 24) >> 24), which would be + // 8-bit signed truncation. Since we return BYTE, we + // will apply strict signed-bit overflow checks, so + // this just means we get missing optimizations, we + // don't actually manage to allow "(byte)" cast truncation. } } } diff --git a/test/hotspot/jtreg/compiler/loopopts/TestTruncationWrapBadCharWrap.java b/test/hotspot/jtreg/compiler/loopopts/TestTruncationWrapBadCharWrap.java new file mode 100644 index 0000000000000..435eafdc6b3ec --- /dev/null +++ b/test/hotspot/jtreg/compiler/loopopts/TestTruncationWrapBadCharWrap.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.loopopts; + +/* + * @test + * @bug 8386591 + * @summary Test case for TruncatedIncrement::build / + * CountedLoopConverter::has_truncation_wrap where we got wrong + * results, because we confused "& 0x7fff" as range [0..65535] + * instead of [0..32767]. + * @library /test/lib / + * @run main/othervm -Xcomp + * -XX:-TieredCompilation + * -XX:CompileCommand=compileonly,${test.main.class}::test* + * ${test.main.class} + * @run main ${test.main.class} + */ + +public class TestTruncationWrapBadCharWrap { + interface TestMethod { + int call(); + } + + public static void main(String[] args) { + int failures = 0; + + failures += run("test1", () -> test1(), 1402); + failures += run("test2", () -> test2(), 2037); + failures += run("test3", () -> test3(), 171761184); + + if (failures > 0) { + throw new RuntimeException("failures: " + failures); + } + } + + static int run(String name, TestMethod t, int expected) { + for (int i = 0; i < 10_000; i++) { + int result = t.call(); + if (result != expected) { + System.out.println(name + " wrong result: " + result + " vs " + expected); + return 1; + } + } + return 0; + } + + + static int test1() { + int sum = 0; + // The entry value is outside the range [0..32767], but inside [0..65535]. + int i = (char)38405; + while (32 < i) { + sum++; + // Ignoring truncation would require values to be in range [0..32767]. + // But unfortunately, we classify this as CHAR, and check for [0..65535]. + i = (i - 4) & 0x7fff; + } + return sum; + } + + static int test2() { + int sum = 0; + // We have 32767 - 32758 = 9 < 48, so the limit is too close to the wrap + // limit, and wrap is possible. But since 0x7fff gets mapped to CHAR, + // we accidentally check 65535 - 32758 < 48, and conclude wrap is not + // possible. + for (int i = 519; i < 32758; i = (i + 48) & 0x7fff) { + sum++; + } + return sum; + } + + static int opaqueCounter; + + static boolean opaqueCheck() { + return opaqueCounter++ > 10448; + } + + static int test3() { + opaqueCounter = 0; + int sum = 0; + int i; + // Similar as with test2: + // We should check 32767 - 32766 = 1 < 50, so wrap possible. But we + // wrongly classify it as CHAR and check 65535 - 32766 < 50, and + // conclude there is no wrap. + for (i = 2046; i <= 32766; i = (i + 50) & 0x7fff) { + sum += i + 1; + if (opaqueCheck()) { break; } + } + return sum + i; + } +}