8356813: Improve Mod(I|L)Node::Value

Reviewed-by: epeter, qamai
This commit is contained in:
Hannes Greule 2025-09-16 12:33:32 +00:00
parent ca89cd06d3
commit c7f014ed49
3 changed files with 645 additions and 65 deletions

View File

@ -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<jlong>(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) {

View File

@ -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<Integer> 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<Integer> g) {
var a = g.next();
var b = g.next();
if (a > b) {
var tmp = a;
a = b;
b = tmp;
}
return new Range(a, b);
}
}
}

View File

@ -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> 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<Long> g) {
var a = g.next();
var b = g.next();
if (a > b) {
var tmp = a;
a = b;
b = tmp;
}
return new Range(a, b);
}
}
}