diff --git a/src/hotspot/share/opto/divnode.cpp b/src/hotspot/share/opto/divnode.cpp index 0d1337909fb70..213f2e1e9a89d 100644 --- a/src/hotspot/share/opto/divnode.cpp +++ b/src/hotspot/share/opto/divnode.cpp @@ -1198,44 +1198,76 @@ Node *ModINode::Ideal(PhaseGVN *phase, bool can_reshape) { } //------------------------------Value------------------------------------------ -const Type* ModINode::Value(PhaseGVN* phase) const { +static const Type* mod_value(const PhaseGVN* phase, const Node* in1, const Node* in2, const BasicType bt) { + assert(bt == T_INT || bt == T_LONG, "unexpected basic type"); // Either input is TOP ==> the result is TOP - const Type *t1 = phase->type( in(1) ); - const Type *t2 = phase->type( in(2) ); - if( t1 == Type::TOP ) return Type::TOP; - if( t2 == Type::TOP ) return Type::TOP; + const Type* t1 = phase->type(in1); + const Type* t2 = phase->type(in2); + if (t1 == Type::TOP) { return Type::TOP; } + if (t2 == Type::TOP) { return Type::TOP; } // 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; } + // X MOD X is 0 - if (in(1) == in(2)) { - return TypeInt::ZERO; + if (in1 == in2) { + return TypeInteger::zero(bt); } - // Either input is BOTTOM ==> the result is the local BOTTOM - const Type *bot = bottom_type(); - if( (t1 == bot) || (t2 == bot) || - (t1 == Type::BOTTOM) || (t2 == Type::BOTTOM) ) - return bot; - - const TypeInt *i1 = t1->is_int(); - const TypeInt *i2 = t2->is_int(); - if( !i1->is_con() || !i2->is_con() ) { - if( i1->_lo >= 0 && i2->_lo >= 0 ) - return TypeInt::POS; - // If both numbers are not constants, we know little. - return TypeInt::INT; - } // Mod by zero? Throw exception at runtime! - if( !i2->get_con() ) return TypeInt::POS; + if (t2 == TypeInteger::zero(bt)) { + return Type::TOP; + } - // 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; + const TypeInteger* i1 = t1->is_integer(bt); + const TypeInteger* i2 = t2->is_integer(bt); + if (i1->is_con() && i2->is_con()) { + // We must be modulo'ing 2 int constants. + // Special case: min_jlong % '-1' is UB, and e.g., x86 triggers a division error. + // Any value % -1 is 0, so we can return 0 and avoid that scenario. + if (i2->get_con_as_long(bt) == -1) { + return TypeInteger::zero(bt); + } + return TypeInteger::make(i1->get_con_as_long(bt) % i2->get_con_as_long(bt), bt); + } + // We checked that t2 is not the zero constant. Hence, at least i2->_lo or i2->_hi must be non-zero, + // and hence 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. + julong divisor_magnitude = MAX2(g_uabs(i2->lo_as_long()), g_uabs(i2->hi_as_long())); + // JVMS lrem bytecode: "the magnitude of the result is always less than the magnitude of the divisor" + // "less than" means we can subtract 1 to get an inclusive upper bound in [0, 2^31-1] or [0, 2^63-1], respectively + jlong hi = static_cast(divisor_magnitude - 1); + jlong lo = -hi; + // JVMS lrem bytecode: "the result of the remainder operation can be negative only if the dividend + // is negative and can be positive only if the dividend is positive" + // Note that with a dividend with bounds e.g. lo == -4 and hi == -1 can still result in values + // below lo; i.e., -3 % 3 == 0. + // That means we cannot restrict the bound that is closer to zero beyond knowing its sign (or zero). + if (i1->hi_as_long() <= 0) { + // all dividends are not positive, so the result is not positive + hi = 0; + // if the dividend is known to be closer to zero, use that as a lower limit + lo = MAX2(lo, i1->lo_as_long()); + } else if (i1->lo_as_long() >= 0) { + // all dividends are not negative, so the result is not negative + lo = 0; + // if the dividend is known to be closer to zero, use that as an upper limit + hi = MIN2(hi, i1->hi_as_long()); + } else { + // Mixed signs, so we don't know the sign of the result, but the result is + // either the dividend itself or a value closer to zero than the dividend, + // and it is closer to zero than the divisor. + // As we know i1->_lo < 0 and i1->_hi > 0, we can use these bounds directly. + 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); +} - return TypeInt::make( i1->get_con() % i2->get_con() ); +const Type* ModINode::Value(PhaseGVN* phase) const { + return mod_value(phase, in(1), in(2), T_INT); } //============================================================================= @@ -1464,43 +1496,7 @@ Node *ModLNode::Ideal(PhaseGVN *phase, bool can_reshape) { //------------------------------Value------------------------------------------ const Type* ModLNode::Value(PhaseGVN* phase) const { - // Either input is TOP ==> the result is TOP - const Type *t1 = phase->type( in(1) ); - const Type *t2 = phase->type( in(2) ); - if( t1 == Type::TOP ) return Type::TOP; - if( t2 == Type::TOP ) return Type::TOP; - - // We always generate the dynamic check for 0. - // 0 MOD X is 0 - if( t1 == TypeLong::ZERO ) return TypeLong::ZERO; - // X MOD X is 0 - if (in(1) == in(2)) { - return TypeLong::ZERO; - } - - // Either input is BOTTOM ==> the result is the local BOTTOM - const Type *bot = bottom_type(); - if( (t1 == bot) || (t2 == bot) || - (t1 == Type::BOTTOM) || (t2 == Type::BOTTOM) ) - return bot; - - const TypeLong *i1 = t1->is_long(); - const TypeLong *i2 = t2->is_long(); - if( !i1->is_con() || !i2->is_con() ) { - if( i1->_lo >= CONST64(0) && i2->_lo >= CONST64(0) ) - return TypeLong::POS; - // If both numbers are not constants, we know little. - return TypeLong::LONG; - } - // Mod by zero? Throw exception at runtime! - if( !i2->get_con() ) return TypeLong::POS; - - // We must be modulo'ing 2 float constants. - // Check for min_jint % '-1', result is defined to be '0'. - if( i1->get_con() == min_jlong && i2->get_con() == -1 ) - return TypeLong::ZERO; - - return TypeLong::make( i1->get_con() % i2->get_con() ); + return mod_value(phase, in(1), in(2), T_LONG); } Node *UModLNode::Ideal(PhaseGVN *phase, bool can_reshape) { diff --git a/test/hotspot/jtreg/compiler/c2/gvn/ModINodeValueTests.java b/test/hotspot/jtreg/compiler/c2/gvn/ModINodeValueTests.java new file mode 100644 index 0000000000000..7870ce1091048 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/gvn/ModINodeValueTests.java @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2025, 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.c2.gvn; + +import compiler.lib.generators.Generator; +import compiler.lib.generators.Generators; +import compiler.lib.generators.RestrictableGenerator; +import compiler.lib.ir_framework.DontCompile; +import compiler.lib.ir_framework.ForceInline; +import compiler.lib.ir_framework.IR; +import compiler.lib.ir_framework.IRNode; +import compiler.lib.ir_framework.Run; +import compiler.lib.ir_framework.Test; +import compiler.lib.ir_framework.TestFramework; +import jdk.test.lib.Asserts; + +/* + * @test + * @bug 8356813 + * @summary Test that Value method of ModINode is working as expected. + * @key randomness + * @library /test/lib / + * @run driver compiler.c2.gvn.ModINodeValueTests + */ +public class ModINodeValueTests { + private static final RestrictableGenerator INT_GEN = Generators.G.ints(); + private static final int POS_INT = INT_GEN.restricted(1, Integer.MAX_VALUE).next(); + private static final int NEG_INT = INT_GEN.restricted(Integer.MIN_VALUE, -1).next(); + + public static void main(String[] args) { + TestFramework.run(); + } + + @Run(test = { + "nonNegativeDividend", "nonNegativeDividendInRange", + "negativeDividend", "negativeDividendInRange", + "modByKnownBoundsUpper", "modByKnownBoundsUpperInRange", + "modByKnownBoundsLower", "modByKnownBoundsLowerInRange", + "modByKnownBoundsLimitedByDividendUpper", "modByKnownBoundsLimitedByDividendUpperInRange", + "modByKnownBoundsLimitedByDividendLower", "modByKnownBoundsLimitedByDividendLowerInRange", + "testRandomLimits" + }) + public void runMethod() { + int a = INT_GEN.next(); + int b = INT_GEN.next(); + + int min = Integer.MIN_VALUE; + int max = Integer.MAX_VALUE; + + assertResult(0, 0); + assertResult(a, b); + assertResult(min, min); + assertResult(max, max); + } + + @DontCompile + public void assertResult(int x, int y) { + Asserts.assertEQ(x != 0 && POS_INT % x < 0, nonNegativeDividend(x)); + Asserts.assertEQ(x != 0 && POS_INT % x <= 0, nonNegativeDividendInRange(x)); + Asserts.assertEQ(x != 0 && NEG_INT % x > 0, negativeDividend(x)); + Asserts.assertEQ(x != 0 && NEG_INT % x >= 0, negativeDividendInRange(x)); + Asserts.assertEQ(x % (((byte) y) + 129) > 255, modByKnownBoundsUpper(x, y)); + Asserts.assertEQ(x % (((byte) y) + 129) >= 255, modByKnownBoundsUpperInRange(x, y)); + Asserts.assertEQ(x % (((byte) y) + 129) < -255, modByKnownBoundsLower(x, y)); + Asserts.assertEQ(x % (((byte) y) + 129) <= -255, modByKnownBoundsLowerInRange(x, y)); + Asserts.assertEQ(((byte) x) % (((char) y) + 1) > 127, modByKnownBoundsLimitedByDividendUpper(x, y)); + Asserts.assertEQ(((byte) x) % (((char) y) + 1) >= 127, modByKnownBoundsLimitedByDividendUpperInRange(x, y)); + Asserts.assertEQ(((byte) x) % (((char) y) + 1) < -128, modByKnownBoundsLimitedByDividendLower(x, y)); + Asserts.assertEQ(((byte) x) % (((char) y) + 1) <= -128, modByKnownBoundsLimitedByDividendLowerInRange(x, y)); + + int res; + try { + res = testRandomLimitsInterpreted(x, y); + } catch (ArithmeticException _) { + try { + testRandomLimits(x, y); + Asserts.fail("Expected ArithmeticException"); + return; // unreachable + } catch (ArithmeticException _) { + return; // test succeeded, no result to assert + } + } + Asserts.assertEQ(res, testRandomLimits(x, y)); + } + + @Test + @IR(failOn = {IRNode.MOD_I, IRNode.CMP_I}) + // The sign of the result of % is the same as the sign of the dividend, + // i.e., POS_INT % x < 0 => false. + public boolean nonNegativeDividend(int x) { + return x != 0 && POS_INT % x < 0; + } + + @Test + @IR(counts = {IRNode.MOD_I, "1"}) + // The sign of the result of % is the same as the sign of the dividend, + // i.e., POS_INT % x < 0 => false. + // This uses <= to verify the % is not optimized away + public boolean nonNegativeDividendInRange(int x) { + return x != 0 && POS_INT % x <= 0; + } + + @Test + @IR(failOn = {IRNode.MOD_I, IRNode.CMP_I}) + // The sign of the result of % is the same as the sign of the dividend, + // i.e., NEG_INT % x > 0 => false. + public boolean negativeDividend(int x) { + return x != 0 && NEG_INT % x > 0; + } + + @Test + @IR(counts = {IRNode.MOD_I, "1"}) + // The sign of the result of % is the same as the sign of the dividend, + // i.e., NEG_INT % x > 0 => false. + // This uses >= to verify the % is not optimized away + public boolean negativeDividendInRange(int x) { + return x != 0 && NEG_INT % x >= 0; + } + + @Test + @IR(failOn = {IRNode.MOD_I, IRNode.CMP_I}) + // The magnitude of the result is less than the divisor. + public boolean modByKnownBoundsUpper(int x, int y) { + // d = ((byte) y) + 129 => [1, 256] divisor + // x % d => [-255, 255] + return x % (((byte) y) + 129) > 255; + } + + @Test + @IR(counts = {IRNode.MOD_I, "1"}) + // The magnitude of the result is less than the divisor. + public boolean modByKnownBoundsUpperInRange(int x, int y) { + // d = ((byte) y) + 129 => [1, 256] divisor + // x % d => [-255, 255] + // in bounds, cannot optimize + return x % (((byte) y) + 129) >= 255; + } + + @Test + @IR(failOn = {IRNode.MOD_I, IRNode.CMP_I}) + // The magnitude of the result is less than the divisor + public boolean modByKnownBoundsLower(int x, int y) { + // d = ((byte) y) + 129 => [1, 256] divisor + // x % d => [-255, 255] + return x % (((byte) y) + 129) < -255; + } + + @Test + @IR(counts = {IRNode.MOD_I, "1"}) + // The magnitude of the result is less than the divisor + public boolean modByKnownBoundsLowerInRange(int x, int y) { + // d = ((byte) y) + 129 => [1, 256] divisor + // x % d => [-255, 255] + // in bounds, cannot optimize + return x % (((byte) y) + 129) <= -255; + } + + @Test + @IR(failOn = {IRNode.MOD_I, IRNode.CMP_I}) + // The result is closer to zero than or equal to the dividend. + public boolean modByKnownBoundsLimitedByDividendUpper(int x, int y) { + // d = ((char) y) + 1 => [1, 65536] divisor + // e = ((byte) x) => [-128, 127] + // e % d => [-128, 127] + return ((byte) x) % (((char) y) + 1) > 127; + } + + @Test + @IR(counts = {IRNode.MOD_I, "1"}) + // The result is closer to zero than or equal to the dividend. + public boolean modByKnownBoundsLimitedByDividendUpperInRange(int x, int y) { + // d = ((char) y) + 1 => [1, 65536] divisor + // e = ((byte) x) => [-128, 127] + // e % d => [-128, 127] + // in bounds, cannot optimize + return ((byte) x) % (((char) y) + 1) >= 127; + } + + @Test + @IR(failOn = {IRNode.MOD_I, IRNode.CMP_I}) + // The result is closer to zero than or equal to the dividend. + public boolean modByKnownBoundsLimitedByDividendLower(int x, int y) { + // d = ((char) y) + 1 => [1, 65536] divisor + // e = ((byte) x) => [-128, 127] + // e % d => [-128, 127] + return ((byte) x) % (((char) y) + 1) < -128; + } + + @Test + @IR(counts = {IRNode.MOD_I, "1"}) + // The result is closer to zero than or equal to the dividend. + public boolean modByKnownBoundsLimitedByDividendLowerInRange(int x, int y) { + // d = ((char) y) + 1 => [1, 65536] divisor + // e = ((byte) x) => [-128, 127] + // e % d => [-128, 127] + // in bounds, cannot optimize + return ((byte) x) % (((char) y) + 1) <= -128; + } + + private static final int LIMIT_1 = INT_GEN.next(); + private static final int LIMIT_2 = INT_GEN.next(); + private static final int LIMIT_3 = INT_GEN.next(); + private static final int LIMIT_4 = INT_GEN.next(); + private static final int LIMIT_5 = INT_GEN.next(); + private static final int LIMIT_6 = INT_GEN.next(); + private static final int LIMIT_7 = INT_GEN.next(); + private static final int LIMIT_8 = INT_GEN.next(); + private static final Range RANGE_1 = Range.generate(INT_GEN); + private static final Range RANGE_2 = Range.generate(INT_GEN); + + @Test + public int testRandomLimits(int x, int y) { + x = RANGE_1.clamp(x); + y = RANGE_2.clamp(y); + int z = x % y; + + int sum = 0; + if (z < LIMIT_1) sum += 1; + if (z < LIMIT_2) sum += 2; + if (z < LIMIT_3) sum += 4; + if (z < LIMIT_4) sum += 8; + if (z > LIMIT_5) sum += 16; + if (z > LIMIT_6) sum += 32; + if (z > LIMIT_7) sum += 64; + if (z > LIMIT_8) sum += 128; + + return sum; + } + + @DontCompile + public int testRandomLimitsInterpreted(int x, int y) { + x = RANGE_1.clamp(x); + y = RANGE_2.clamp(y); + int z = x % y; + + int sum = 0; + if (z < LIMIT_1) sum += 1; + if (z < LIMIT_2) sum += 2; + if (z < LIMIT_3) sum += 4; + if (z < LIMIT_4) sum += 8; + if (z > LIMIT_5) sum += 16; + if (z > LIMIT_6) sum += 32; + if (z > LIMIT_7) sum += 64; + if (z > LIMIT_8) sum += 128; + + return sum; + } + + record Range(int lo, int hi) { + Range { + if (lo > hi) { + throw new IllegalArgumentException("lo > hi"); + } + } + + @ForceInline + int clamp(int v) { + return Math.min(hi, Math.max(v, lo)); + } + + static Range generate(Generator g) { + var a = g.next(); + var b = g.next(); + if (a > b) { + var tmp = a; + a = b; + b = tmp; + } + return new Range(a, b); + } + } +} diff --git a/test/hotspot/jtreg/compiler/c2/gvn/ModLNodeValueTests.java b/test/hotspot/jtreg/compiler/c2/gvn/ModLNodeValueTests.java new file mode 100644 index 0000000000000..a6eef6d332608 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/gvn/ModLNodeValueTests.java @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2025, 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.c2.gvn; + +import compiler.lib.generators.Generator; +import compiler.lib.generators.Generators; +import compiler.lib.ir_framework.DontCompile; +import compiler.lib.ir_framework.ForceInline; +import compiler.lib.ir_framework.IR; +import compiler.lib.ir_framework.IRNode; +import compiler.lib.ir_framework.Run; +import compiler.lib.ir_framework.Test; +import compiler.lib.ir_framework.TestFramework; +import jdk.test.lib.Asserts; + +/* + * @test + * @bug 8356813 + * @summary Test that Value method of ModLNode is working as expected. + * @key randomness + * @library /test/lib / + * @run driver compiler.c2.gvn.ModLNodeValueTests + */ +public class ModLNodeValueTests { + private static final Generator LONG_GEN = Generators.G.longs(); + private static final long POS_LONG = Generators.G.longs().restricted(1L, Long.MAX_VALUE).next(); + private static final long NEG_LONG = Generators.G.longs().restricted(Long.MIN_VALUE, -1L).next(); + + public static void main(String[] args) { + TestFramework.run(); + } + + @Run(test = { + "nonNegativeDividend", "nonNegativeDividendInRange", + "negativeDividend", "negativeDividendInRange", + "modByKnownBoundsUpper", "modByKnownBoundsUpperInRange", + "modByKnownBoundsLower", "modByKnownBoundsLowerInRange", + "modByKnownBoundsLimitedByDividendUpper", "modByKnownBoundsLimitedByDividendUpperInRange", + "modByKnownBoundsLimitedByDividendLower", "modByKnownBoundsLimitedByDividendLowerInRange", + "testRandomLimits" + }) + public void runMethod() { + long a = LONG_GEN.next(); + long b = LONG_GEN.next(); + + long min = Long.MIN_VALUE; + long max = Long.MAX_VALUE; + + assertResult(0, 0); + assertResult(a, b); + assertResult(min, min); + assertResult(max, max); + } + + @DontCompile + public void assertResult(long x, long y) { + Asserts.assertEQ(x != 0 && POS_LONG % x < 0, nonNegativeDividend(x)); + Asserts.assertEQ(x != 0 && POS_LONG % x <= 0, nonNegativeDividendInRange(x)); + Asserts.assertEQ(x != 0 && NEG_LONG % x > 0, negativeDividend(x)); + Asserts.assertEQ(x != 0 && NEG_LONG % x >= 0, negativeDividendInRange(x)); + Asserts.assertEQ(x % (((byte) y) + 129L) > 255, modByKnownBoundsUpper(x, y)); + Asserts.assertEQ(x % (((byte) y) + 129L) >= 255, modByKnownBoundsUpperInRange(x, y)); + Asserts.assertEQ(x % (((byte) y) + 129L) < -255, modByKnownBoundsLower(x, y)); + Asserts.assertEQ(x % (((byte) y) + 129L) <= -255, modByKnownBoundsLowerInRange(x, y)); + Asserts.assertEQ(((byte) x) % (((char) y) + 1L) > 127, modByKnownBoundsLimitedByDividendUpper(x, y)); + Asserts.assertEQ(((byte) x) % (((char) y) + 1L) >= 127, modByKnownBoundsLimitedByDividendUpperInRange(x, y)); + Asserts.assertEQ(((byte) x) % (((char) y) + 1L) < -128, modByKnownBoundsLimitedByDividendLower(x, y)); + Asserts.assertEQ(((byte) x) % (((char) y) + 1L) <= -128, modByKnownBoundsLimitedByDividendLowerInRange(x, y)); + + int res; + try { + res = testRandomLimitsInterpreted(x, y); + } catch (ArithmeticException _) { + try { + testRandomLimits(x, y); + Asserts.fail("Expected ArithmeticException"); + return; // unreachable + } catch (ArithmeticException _) { + return; // test succeeded, no result to assert + } + } + Asserts.assertEQ(res, testRandomLimits(x, y)); + } + + @Test + @IR(failOn = {IRNode.MOD_L, IRNode.CMP_L}) + // The sign of the result of % is the same as the sign of the dividend, + // i.e., posVal % x < 0 => false. + public boolean nonNegativeDividend(long x) { + return x != 0 && POS_LONG % x < 0; + } + + @Test + @IR(counts = {IRNode.MOD_L, "1"}) + // The sign of the result of % is the same as the sign of the dividend, + // i.e., posVal % x < 0 => false. + // This uses <= to verify the % is not optimized away + public boolean nonNegativeDividendInRange(long x) { + return x != 0 && POS_LONG % x <= 0; + } + + @Test + @IR(failOn = {IRNode.MOD_L, IRNode.CMP_L}) + // The sign of the result of % is the same as the sign of the dividend, + // i.e., negValue % x > 0 => false. + public boolean negativeDividend(long x) { + return x != 0 && NEG_LONG % x > 0; + } + + @Test + @IR(counts = {IRNode.MOD_L, "1"}) + // The sign of the result of % is the same as the sign of the dividend, + // i.e., negValue % x > 0 => false. + // This uses >= to verify the % is not optimized away + public boolean negativeDividendInRange(long x) { + return x != 0 && NEG_LONG % x >= 0; + } + + @Test + @IR(failOn = {IRNode.MOD_L, IRNode.CMP_L}) + // The magnitude of the result is less than the divisor. + public boolean modByKnownBoundsUpper(long x, long y) { + // d = ((byte) y) + 129 => [1, 256] divisor + // x % d => [-255, 255] + return x % (((byte) y) + 129L) > 255; + } + + @Test + @IR(counts = {IRNode.MOD_L, "1"}) + // The magnitude of the result is less than the divisor. + public boolean modByKnownBoundsUpperInRange(long x, long y) { + // d = ((byte) y) + 129 => [1, 256] divisor + // x % d => [-255, 255] + // in bounds, cannot optimize + return x % (((byte) y) + 129L) >= 255; + } + + @Test + @IR(failOn = {IRNode.MOD_L, IRNode.CMP_L}) + // The magnitude of the result is less than the divisor + public boolean modByKnownBoundsLower(long x, long y) { + // d = ((byte) y) + 129 => [1, 256] divisor + // x % d => [-255, 255] + return x % (((byte) y) + 129L) < -255; + } + + @Test + @IR(counts = {IRNode.MOD_L, "1"}) + // The magnitude of the result is less than the divisor + public boolean modByKnownBoundsLowerInRange(long x, long y) { + // d = ((byte) y) + 129 => [1, 256] divisor + // x % d => [-255, 255] + // in bounds, cannot optimize + return x % (((byte) y) + 129L) <= -255; + } + + @Test + @IR(failOn = {IRNode.MOD_L, IRNode.CMP_L}) + // The result is closer to zero than or equal to the dividend. + public boolean modByKnownBoundsLimitedByDividendUpper(long x, long y) { + // d = ((char) y) + 1 => [1, 65536] divisor + // e = ((byte) x) => [-128, 127] + // e % d => [-128, 127] + return ((byte) x) % (((char) y) + 1L) > 127; + } + + @Test + @IR(counts = {IRNode.MOD_L, "1"}) + // The result is closer to zero than or equal to the dividend. + public boolean modByKnownBoundsLimitedByDividendUpperInRange(long x, long y) { + // d = ((char) y) + 1 => [1, 65536] divisor + // e = ((byte) x) => [-128, 127] + // e % d => [-128, 127] + // in bounds, cannot optimize + return ((byte) x) % (((char) y) + 1L) >= 127; + } + + @Test + @IR(failOn = {IRNode.MOD_L, IRNode.CMP_L}) + // The result is closer to zero than or equal to the dividend. + public boolean modByKnownBoundsLimitedByDividendLower(long x, long y) { + // d = ((char) y) + 1 => [1, 65536] divisor + // e = ((byte) x) => [-128, 127] + // e % d => [-128, 127] + return ((byte) x) % (((char) y) + 1L) < -128; + } + + @Test + @IR(counts = {IRNode.MOD_L, "1"}) + // The result is closer to zero than or equal to the dividend. + public boolean modByKnownBoundsLimitedByDividendLowerInRange(long x, long y) { + // d = ((char) y) + 1 => [1, 65536] divisor + // e = ((byte) x) => [-128, 127] + // e % d => [-128, 127] + // in bounds, cannot optimize + return ((byte) x) % (((char) y) + 1L) <= -128; + } + + + private static final long LIMIT_1 = LONG_GEN.next(); + private static final long LIMIT_2 = LONG_GEN.next(); + private static final long LIMIT_3 = LONG_GEN.next(); + private static final long LIMIT_4 = LONG_GEN.next(); + private static final long LIMIT_5 = LONG_GEN.next(); + private static final long LIMIT_6 = LONG_GEN.next(); + private static final long LIMIT_7 = LONG_GEN.next(); + private static final long LIMIT_8 = LONG_GEN.next(); + private static final Range RANGE_1 = Range.generate(LONG_GEN); + private static final Range RANGE_2 = Range.generate(LONG_GEN); + + @Test + public int testRandomLimits(long x, long y) { + x = RANGE_1.clamp(x); + y = RANGE_2.clamp(y); + long z = x % y; + + int sum = 0; + if (z < LIMIT_1) sum += 1; + if (z < LIMIT_2) sum += 2; + if (z < LIMIT_3) sum += 4; + if (z < LIMIT_4) sum += 8; + if (z > LIMIT_5) sum += 16; + if (z > LIMIT_6) sum += 32; + if (z > LIMIT_7) sum += 64; + if (z > LIMIT_8) sum += 128; + + return sum; + } + + @DontCompile + public int testRandomLimitsInterpreted(long x, long y) { + x = RANGE_1.clamp(x); + y = RANGE_2.clamp(y); + long z = x % y; + + int sum = 0; + if (z < LIMIT_1) sum += 1; + if (z < LIMIT_2) sum += 2; + if (z < LIMIT_3) sum += 4; + if (z < LIMIT_4) sum += 8; + if (z > LIMIT_5) sum += 16; + if (z > LIMIT_6) sum += 32; + if (z > LIMIT_7) sum += 64; + if (z > LIMIT_8) sum += 128; + + return sum; + } + + record Range(long lo, long hi) { + Range { + if (lo > hi) { + throw new IllegalArgumentException("lo > hi"); + } + } + + @ForceInline + long clamp(long v) { + return Math.min(hi, Math.max(v, lo)); + } + + static Range generate(Generator g) { + var a = g.next(); + var b = g.next(); + if (a > b) { + var tmp = a; + a = b; + b = tmp; + } + return new Range(a, b); + } + } +}