diff --git a/src/hotspot/share/opto/addnode.cpp b/src/hotspot/share/opto/addnode.cpp index 8406fe8b69ea0..8112c5b86fc71 100644 --- a/src/hotspot/share/opto/addnode.cpp +++ b/src/hotspot/share/opto/addnode.cpp @@ -33,6 +33,7 @@ #include "opto/phaseX.hpp" #include "opto/subnode.hpp" #include "runtime/stubRoutines.hpp" +#include "opto/utilities/xor.hpp" // Portions of code courtesy of Clifford Click @@ -995,23 +996,9 @@ const Type* XorINode::Value(PhaseGVN* phase) const { if (in1->eqv_uncast(in2)) { return add_id(); } - // result of xor can only have bits sets where any of the - // inputs have bits set. lo can always become 0. - const TypeInt* t1i = t1->is_int(); - const TypeInt* t2i = t2->is_int(); - if ((t1i->_lo >= 0) && - (t1i->_hi > 0) && - (t2i->_lo >= 0) && - (t2i->_hi > 0)) { - // hi - set all bits below the highest bit. Using round_down to avoid overflow. - const TypeInt* t1x = TypeInt::make(0, round_down_power_of_2(t1i->_hi) + (round_down_power_of_2(t1i->_hi) - 1), t1i->_widen); - const TypeInt* t2x = TypeInt::make(0, round_down_power_of_2(t2i->_hi) + (round_down_power_of_2(t2i->_hi) - 1), t2i->_widen); - return t1x->meet(t2x); - } return AddNode::Value(phase); } - //------------------------------add_ring--------------------------------------- // Supplied function returns the sum of the inputs IN THE CURRENT RING. For // the logical operations the ring's ADD is really a logical OR function. @@ -1021,16 +1008,20 @@ const Type *XorINode::add_ring( const Type *t0, const Type *t1 ) const { const TypeInt *r0 = t0->is_int(); // Handy access const TypeInt *r1 = t1->is_int(); - // Complementing a boolean? - if( r0 == TypeInt::BOOL && ( r1 == TypeInt::ONE - || r1 == TypeInt::BOOL)) - return TypeInt::BOOL; + if (r0->is_con() && r1->is_con()) { + // compute constant result + return TypeInt::make(r0->get_con() ^ r1->get_con()); + } - if( !r0->is_con() || !r1->is_con() ) // Not constants - return TypeInt::INT; // Any integer, but still no symbols. + // At least one of the arguments is not constant - // Otherwise just XOR them bits. - return TypeInt::make( r0->get_con() ^ r1->get_con() ); + if (r0->_lo >= 0 && r1->_lo >= 0) { + // Combine [r0->_lo, r0->_hi] ^ [r0->_lo, r1->_hi] -> [0, upper_bound] + jint upper_bound = xor_upper_bound_for_ranges(r0->_hi, r1->_hi); + return TypeInt::make(0, upper_bound, MAX2(r0->_widen, r1->_widen)); + } + + return TypeInt::INT; } //============================================================================= @@ -1039,12 +1030,20 @@ const Type *XorLNode::add_ring( const Type *t0, const Type *t1 ) const { const TypeLong *r0 = t0->is_long(); // Handy access const TypeLong *r1 = t1->is_long(); - // If either input is not a constant, just return all integers. - if( !r0->is_con() || !r1->is_con() ) - return TypeLong::LONG; // Any integer, but still no symbols. + if (r0->is_con() && r1->is_con()) { + // compute constant result + return TypeLong::make(r0->get_con() ^ r1->get_con()); + } - // Otherwise just OR them bits. - return TypeLong::make( r0->get_con() ^ r1->get_con() ); + // At least one of the arguments is not constant + + if (r0->_lo >= 0 && r1->_lo >= 0) { + // Combine [r0->_lo, r0->_hi] ^ [r0->_lo, r1->_hi] -> [0, upper_bound] + julong upper_bound = xor_upper_bound_for_ranges(r0->_hi, r1->_hi); + return TypeLong::make(0, upper_bound, MAX2(r0->_widen, r1->_widen)); + } + + return TypeLong::LONG; } Node* XorLNode::Ideal(PhaseGVN* phase, bool can_reshape) { @@ -1080,19 +1079,7 @@ const Type* XorLNode::Value(PhaseGVN* phase) const { if (in1->eqv_uncast(in2)) { return add_id(); } - // result of xor can only have bits sets where any of the - // inputs have bits set. lo can always become 0. - const TypeLong* t1l = t1->is_long(); - const TypeLong* t2l = t2->is_long(); - if ((t1l->_lo >= 0) && - (t1l->_hi > 0) && - (t2l->_lo >= 0) && - (t2l->_hi > 0)) { - // hi - set all bits below the highest bit. Using round_down to avoid overflow. - const TypeLong* t1x = TypeLong::make(0, round_down_power_of_2(t1l->_hi) + (round_down_power_of_2(t1l->_hi) - 1), t1l->_widen); - const TypeLong* t2x = TypeLong::make(0, round_down_power_of_2(t2l->_hi) + (round_down_power_of_2(t2l->_hi) - 1), t2l->_widen); - return t1x->meet(t2x); - } + return AddNode::Value(phase); } diff --git a/src/hotspot/share/opto/utilities/xor.hpp b/src/hotspot/share/opto/utilities/xor.hpp new file mode 100644 index 0000000000000..20edaf0d01779 --- /dev/null +++ b/src/hotspot/share/opto/utilities/xor.hpp @@ -0,0 +1,47 @@ +#ifndef SHARE_OPTO_UTILITIES_XOR_HPP +#define SHARE_OPTO_UTILITIES_XOR_HPP + +#include "utilities/powerOfTwo.hpp" +// Code separated into its own header to allow access from GTEST + +// Given 2 non-negative values in the ranges [0, hi_0] and [0, hi_1], respectively. The bitwise +// xor of these values should also be non-negative. This method calculates an upper bound. + +// S and U type parameters correspond to the signed and unsigned +// variants of an integer to operate on. +template +static S xor_upper_bound_for_ranges(const S hi_0, const S hi_1) { + static_assert(S(-1) < S(0), "S must be signed"); + static_assert(U(-1) > U(0), "U must be unsigned"); + + assert(hi_0 >= 0, "must be non-negative"); + assert(hi_1 >= 0, "must be non-negative"); + + // x ^ y cannot have any bit set that is higher than both the highest bits set in x and y + // x cannot have any bit set that is higher than the highest bit set in hi_0 + // y cannot have any bit set that is higher than the highest bit set in hi_1 + + // We want to find a value that has all 1 bits everywhere up to and including + // the highest bits set in hi_0 as well as hi_1. For this, we can take the next + // power of 2 strictly greater than both hi values and subtract 1 from it. + + // Example 1: + // hi_0 = 5 (0b0101) hi_1=1 (0b0001) + // (5|1)+1 = 0b0110 + // round_up_pow2 = 0b1000 + // -1 = 0b0111 = max + + // Example 2 - this demonstrates need for the +1: + // hi_0 = 4 (0b0100) hi_1=4 (0b0100) + // (4|4)+1 = 0b0101 + // round_up_pow2 = 0b1000 + // -1 = 0b0111 = max + // Without the +1, round_up_pow2 would be 0b0100, resulting in 0b0011 as max + + // Note: cast to unsigned happens before +1 to avoid signed overflow, and + // round_up is safe because high bit is unset (0 <= lo <= hi) + + return round_up_power_of_2(U(hi_0 | hi_1) + 1) - 1; +} + +#endif // SHARE_OPTO_UTILITIES_XOR_HPP diff --git a/test/hotspot/gtest/opto/test_xor_node.cpp b/test/hotspot/gtest/opto/test_xor_node.cpp new file mode 100644 index 0000000000000..ff21d9c4a9007 --- /dev/null +++ b/test/hotspot/gtest/opto/test_xor_node.cpp @@ -0,0 +1,102 @@ +/* + * 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. + * + */ + +#include "unittest.hpp" +#include "opto/utilities/xor.hpp" +#include "utilities/globalDefinitions.hpp" // For jint, juint + +jint test_calc_max(const jint hi_0, const jint hi_1) { + return xor_upper_bound_for_ranges(hi_0, hi_1); +} + +jlong test_calc_max(const jlong hi_0, const jlong hi_1) { + return xor_upper_bound_for_ranges(hi_0, hi_1); +} + +template +void test_xor_bounds(S hi_0, S hi_1, S val_0, S val_1) { + ASSERT_GE(hi_0, 0); + ASSERT_GE(hi_1, 0); + + // Skip out-of-bounds values for convenience + if (val_0 > hi_0 || val_0 < S(0) || val_1 > hi_1 || val_1 < S(0)) { + return; + } + + S v = val_0 ^ val_1; + S max = test_calc_max(hi_0, hi_1); + EXPECT_LE(v, max); +} + +template +void test_sample_values(S hi_0, S hi_1) { + for (S i = 0; i <= 3; i++) { + for (S j = 0; j <= 3; j++) { + // Some bit combinations near the low and high ends of the range + test_xor_bounds(hi_0, hi_1, i, j); + test_xor_bounds(hi_0, hi_1, hi_0 - i, hi_1 - j); + } + } +} + +template +void test_in_ranges(S lo, S hi){ + ASSERT_GE(lo, 0); + ASSERT_LE(lo, hi); + + for (S hi_0 = lo; hi_0 <= hi; hi_0++) { + for (S hi_1 = hi_0; hi_1 <=hi; hi_1++) { + test_sample_values(hi_0, hi_1); + } + } +} + +template +void test_exhaustive(S limit) { + for (S hi_0 = 0; hi_0 <= limit; hi_0++) { + for (S hi_1 = hi_0; hi_1 <= limit; hi_1++) { + for (S val_0 = 0; val_0 <= hi_0; val_0++) { + for (S val_1 = val_0; val_1 <= hi_1; val_1++) { + test_xor_bounds(hi_0, hi_1, val_0, val_1); + } + } + } + } +} + +template +void exec_tests() { + S top_bit = max_power_of_2(); + S prev_bit = top_bit >> 1; + + test_exhaustive(15); + + test_in_ranges(top_bit - 1, top_bit); + test_in_ranges(prev_bit - 1, prev_bit); +} + +TEST_VM(opto, xor_max) { + exec_tests(); + exec_tests(); +} diff --git a/test/hotspot/jtreg/compiler/c2/irTests/XorINodeIdealizationTests.java b/test/hotspot/jtreg/compiler/c2/irTests/XorINodeIdealizationTests.java index f1a0320330b14..b0a1c4c9e3775 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/XorINodeIdealizationTests.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/XorINodeIdealizationTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -23,16 +23,24 @@ package compiler.c2.irTests; import jdk.test.lib.Asserts; +import compiler.lib.generators.*; import compiler.lib.ir_framework.*; +import static java.lang.Integer.MAX_VALUE; +import static java.lang.Integer.MIN_VALUE; + /* * @test - * @bug 8281453 - * @summary Convert ~x into -1-x when ~x is used in an arithmetic expression + * @bug 8281453 8347645 8261008 8267332 + * @summary Test correctness of optimizations of xor * @library /test/lib / * @run driver compiler.c2.irTests.XorINodeIdealizationTests */ public class XorINodeIdealizationTests { + private static final RestrictableGenerator G = Generators.G.ints(); + private static final int CONST_1 = G.next(); + private static final int CONST_2 = G.next(); + public static void main(String[] args) { TestFramework.run(); } @@ -42,15 +50,17 @@ public static void main(String[] args) { "test7", "test8", "test9", "test10", "test11", "test12", "test13", "test14", "test15", - "test16", "test17"}) + "test16", "test17", + "testConstXor", "testXorSelf" + }) public void runMethod() { int a = RunInfo.getRandom().nextInt(); int b = RunInfo.getRandom().nextInt(); int c = RunInfo.getRandom().nextInt(); int d = RunInfo.getRandom().nextInt(); - int min = Integer.MIN_VALUE; - int max = Integer.MAX_VALUE; + int min = MIN_VALUE; + int max = MAX_VALUE; assertResult(0, 0, 0, 0); assertResult(a, b, c, d); @@ -77,6 +87,8 @@ public void assertResult(int a, int b, int c, int d) { Asserts.assertEQ(~a , test15(a)); Asserts.assertEQ((~a + b) + (~a | c), test16(a, b, c)); Asserts.assertEQ(-2023 - a , test17(a)); + Asserts.assertEQ(CONST_1 ^ CONST_2 , testConstXor()); + Asserts.assertEQ(0 , testXorSelf(a)); } @Test @@ -217,4 +229,218 @@ public int test16(int x, int y, int z) { public int test17(int x) { return ~(x + 2022); } + + @Test + @IR(failOn = {IRNode.XOR}) + @IR(counts = {IRNode.CON_I, "1"}) + // Checks (c1 ^ c2) => c3 (constant folded) + public int testConstXor() { + return CONST_1 ^ CONST_2; + } + + @Test + @IR(failOn = {IRNode.XOR}) + @IR(counts = {IRNode.CON_I, "1"}) + // Checks (x ^ x) => c (constant folded) + public int testXorSelf(int x) { + return x ^ x; + } + + private static final boolean CONST_BOOL_1 = RunInfo.getRandom().nextBoolean(); + private static final boolean CONST_BOOL_2 = RunInfo.getRandom().nextBoolean(); + + @Run(test={ + "testConstXorBool", "testXorSelfBool", "testXorIntAsBool" + }) + public void runBooleanTests() { + int c = G.next(); + int d = G.next(); + + assertBooleanResult(true, c, d); + assertBooleanResult(false, c, d); + } + + @DontCompile + public void assertBooleanResult(boolean b, int x, int y) { + Asserts.assertEQ(CONST_BOOL_1 ^ CONST_BOOL_2, testConstXorBool()); + Asserts.assertEQ(false, testXorSelfBool(b)); + Asserts.assertEQ(true, testXorIntAsBool(x, y)); + } + + @Test + @IR(failOn = {IRNode.XOR}) + @IR(counts = {IRNode.CON_I, "1"}) + // Checks (c1 ^ c2) => c3 (constant folded) + public boolean testConstXorBool() { + return CONST_BOOL_1 ^ CONST_BOOL_2; + } + + @Test + @IR(failOn = {IRNode.XOR}) + @IR(counts = {IRNode.CON_I, "1"}) + // Checks (x ^ x) => c (constant folded) + public boolean testXorSelfBool(boolean x) { + return x ^ x; + } + + @Test + @IR(failOn = {IRNode.XOR}) + @IR(counts = {IRNode.CON_I, "1"}) + // This test explicitly checks for constant folding over ints representing booleans. + // Checks (x ^ y) => z in [0, 1] when x and y are known to be in [0, 1] (constant folded) + public boolean testXorIntAsBool(int xi, int yi) { + int xor = (xi & 1) ^ (yi & 1); + return 0 <= xor && xor <= 1; + } + + @Run(test = { + "testFoldableXor", "testFoldableXorPow2", "testUnfoldableXorPow2", + "testFoldableXorDifferingLength", "testXorMax", + "testFoldableRange","testRandomLimits" + }) + public void runRangeTests() { + int a = G.next(); + int b = G.next(); + checkXor(a, b); + + for (a = 0; a < 32; a++) { + for (b = a; b < 32; b++) { + checkXor(a, b); + checkXor(MAX_VALUE, MAX_VALUE - b); + } + } + } + + @DontCompile + public void checkXor(int a, int b) { + Asserts.assertEQ(true, testFoldableXor(a, b)); + Asserts.assertEQ(((a & 0b1000) ^ (b & 0b1000)) < 0b1000, testUnfoldableXorPow2(a, b)); + Asserts.assertEQ(true, testFoldableXorPow2(a, b)); + Asserts.assertEQ(true, testFoldableXorDifferingLength(a, b)); + Asserts.assertEQ((a & MAX_VALUE) ^ (b & 0b11), testXorMax(a, b)); + Asserts.assertEQ(testRandomLimitsInterpreted(a, b), testRandomLimits(a, b)); + Asserts.assertEQ(true, testFoldableRange(a, b)); + } + + @Test + @IR(failOn = {IRNode.XOR}) + @IR(counts = {IRNode.CON_I, "1"}) + public boolean testFoldableXorPow2(int x, int y) { + return ((x & 0b1000) ^ (y & 0b1000)) < 0b10000; + } + + @Test + @IR(counts = {IRNode.XOR, "1"}) + public boolean testUnfoldableXorPow2(int x, int y) { + return ((x & 0b1000) ^ (y & 0b1000)) < 0b1000; + } + + @Test + @IR(failOn = {IRNode.XOR}) + @IR(counts = {IRNode.CON_I, "1"}) + public boolean testFoldableXor(int x, int y) { + var xor = (x & 0b111) ^ (y & 0b100); + return xor < 0b1000; + } + + @Test + @IR(failOn = {IRNode.XOR}) + @IR(counts = {IRNode.CON_I, "1"}) + public boolean testFoldableXorDifferingLength(int x, int y) { + var xor = (x & 0b111) ^ (y & 0b11); + return xor < 0b1000; + } + + @Test + public int testXorMax(int x, int y) { + return (x & MAX_VALUE) ^ (y & 0b11); + // can't do the folding range check here since xor <= MAX_VALUE is + // constant with or without the xor + } + + private static final Range RANGE_1 = Range.generate(G.restricted(0, MAX_VALUE)); + private static final Range RANGE_2 = Range.generate(G.restricted(0, MAX_VALUE)); + private static final int UPPER_BOUND = Integer.max(0, Integer.highestOneBit(RANGE_1.hi() | RANGE_2.hi()) * 2 - 1); + + private static final int LIMIT_1 = G.next(); + private static final int LIMIT_2 = G.next(); + private static final int LIMIT_3 = G.next(); + private static final int LIMIT_4 = G.next(); + private static final int LIMIT_5 = G.next(); + private static final int LIMIT_6 = G.next(); + private static final int LIMIT_7 = G.next(); + private static final int LIMIT_8 = G.next(); + + + @Test + @IR(failOn = {IRNode.XOR}) + @IR(counts = {IRNode.CON_I, "1"}) + public boolean testFoldableRange(int x, int y) { + return (RANGE_1.clamp(x) ^ RANGE_2.clamp(y)) <= UPPER_BOUND; + } + + @Test + public int testRandomLimits(int x, int y) { + x = RANGE_1.clamp(x); + y = RANGE_2.clamp(y); + + int z = x ^ y; + // This should now have a new range, possibly some [0, max] + // Now let's test the range with some random if branches. + 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 + private int testRandomLimitsInterpreted(int x, int y) { + x = RANGE_1.clamp(x); + y = RANGE_2.clamp(y); + + var z = x ^ y; + // This should now have a new range, possibly some [0, max] + // Now let's test the range with some random if branches. + 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"); + } + } + + 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/irTests/XorLNodeIdealizationTests.java b/test/hotspot/jtreg/compiler/c2/irTests/XorLNodeIdealizationTests.java index 22f3997a15ca9..ce328f7a37b00 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/XorLNodeIdealizationTests.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/XorLNodeIdealizationTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -23,16 +23,24 @@ package compiler.c2.irTests; import jdk.test.lib.Asserts; +import compiler.lib.generators.*; import compiler.lib.ir_framework.*; +import static java.lang.Long.MAX_VALUE; +import static java.lang.Long.MIN_VALUE; + /* * @test - * @bug 8281453 - * @summary Convert ~x into -1-x when ~x is used in an arithmetic expression + * @bug 8281453 8347645 8261008 8267332 + * @summary Test correctness of optimizations of xor * @library /test/lib / * @run driver compiler.c2.irTests.XorLNodeIdealizationTests */ public class XorLNodeIdealizationTests { + private static final RestrictableGenerator G = Generators.G.longs(); + private static final long CONST_1 = G.next(); + private static final long CONST_2 = G.next(); + public static void main(String[] args) { TestFramework.run(); } @@ -42,15 +50,17 @@ public static void main(String[] args) { "test7", "test8", "test9", "test10", "test11", "test12", "test13", "test14", "test15", - "test16", "test17"}) + "test16", "test17", + "testConstXor", "testXorSelf", + }) public void runMethod() { long a = RunInfo.getRandom().nextLong(); long b = RunInfo.getRandom().nextLong(); long c = RunInfo.getRandom().nextLong(); long d = RunInfo.getRandom().nextLong(); - long min = Long.MIN_VALUE; - long max = Long.MAX_VALUE; + long min = MIN_VALUE; + long max = MAX_VALUE; assertResult(0, 0, 0, 0); assertResult(a, b, c, d); @@ -77,6 +87,8 @@ public void assertResult(long a, long b, long c, long d) { Asserts.assertEQ(~a , test15(a)); Asserts.assertEQ((~a + b) + (~a | c), test16(a, b, c)); Asserts.assertEQ(-2023 - a , test17(a)); + Asserts.assertEQ(CONST_1 ^ CONST_2 , testConstXor()); + Asserts.assertEQ(0L , testXorSelf(a)); } @Test @@ -217,4 +229,171 @@ public long test16(long x, long y, long z) { public long test17(long x) { return ~(x + 2022); } + + @Test + @IR(failOn = {IRNode.XOR}) + @IR(counts = {IRNode.CON_L, "1"}) + // Checks (c1 ^ c2) => c3 (constant folded) + public long testConstXor() { + return CONST_1 ^ CONST_2; + } + + @Test + @IR(failOn = {IRNode.XOR}) + @IR(counts = {IRNode.CON_L, "1"}) + // Checks (x ^ x) => c (constant folded) + public long testXorSelf(long x) { + return x ^ x; + } + + @Run(test = { + "testFoldableXor", "testFoldableXorPow2", "testUnfoldableXorPow2", + "testFoldableXorDifferingLength", "testXorMax", + "testFoldableRange","testRandomLimits" + }) + public void runRangeTests() { + long a = G.next(); + long b = G.next(); + checkXor(a, b); + + for (a = 0; a < 32; a++) { + for (b = a; b < 32; b++) { + checkXor(a, b); + checkXor(MAX_VALUE, MAX_VALUE - b); + } + } + } + + @DontCompile + public void checkXor(long a, long b) { + Asserts.assertEQ(true, testFoldableXor(a, b)); + Asserts.assertEQ(((a & 0b1000) ^ (b & 0b1000)) < 0b1000, testUnfoldableXorPow2(a, b)); + Asserts.assertEQ(true, testFoldableXorPow2(a, b)); + Asserts.assertEQ(true, testFoldableXorDifferingLength(a, b)); + Asserts.assertEQ((a & MAX_VALUE) ^ (b & 0b11), testXorMax(a, b)); + Asserts.assertEQ(testRandomLimitsInterpreted(a, b), testRandomLimits(a, b)); + Asserts.assertEQ(true, testFoldableRange(a, b)); + } + + @Test + @IR(failOn = {IRNode.XOR}) + @IR(counts = {IRNode.CON_I, "1"}) // boolean is a CON_I + public boolean testFoldableXorPow2(long x, long y) { + return ((x & 0b1000) ^ (y & 0b1000)) < 0b10000; + } + + @Test + @IR(counts = {IRNode.XOR, "1"}) + public boolean testUnfoldableXorPow2(long x, long y) { + return ((x & 0b1000) ^ (y & 0b1000)) < 0b1000; + } + + @Test + @IR(failOn = {IRNode.XOR}) + @IR(counts = {IRNode.CON_I, "1"}) // boolean is a CON_I + public boolean testFoldableXor(long x, long y) { + var xor = (x & 0b111) ^ (y & 0b100); + return xor < 0b1000; + } + + @Test + @IR(failOn = {IRNode.XOR}) + @IR(counts = {IRNode.CON_I, "1"}) // boolean is a CON_I + public boolean testFoldableXorDifferingLength(long x, long y) { + var xor = (x & 0b111) ^ (y & 0b11); + return xor < 0b1000; + } + + @Test + public long testXorMax(long x, long y) { + return (x & MAX_VALUE) ^ (y & 0b11); + // can't do the folding range check here since xor <= MAX_VALUE is + // constant with or without the xor + } + + private static final Range RANGE_1 = Range.generate(G.restricted(0L, MAX_VALUE)); + private static final Range RANGE_2 = Range.generate(G.restricted(0L, MAX_VALUE)); + private static final long UPPER_BOUND = Math.max(0, Long.highestOneBit(RANGE_1.hi() | RANGE_2.hi()) * 2 - 1); + + private static final long LIMIT_1 = G.next(); + private static final long LIMIT_2 = G.next(); + private static final long LIMIT_3 = G.next(); + private static final long LIMIT_4 = G.next(); + private static final long LIMIT_5 = G.next(); + private static final long LIMIT_6 = G.next(); + private static final long LIMIT_7 = G.next(); + private static final long LIMIT_8 = G.next(); + + + @Test + @IR(failOn = {IRNode.XOR}) + @IR(counts = {IRNode.CON_I, "1"}) // Boolean is CON_I + public boolean testFoldableRange(long x, long y) { + return (RANGE_1.clamp(x) ^ RANGE_2.clamp(y)) <= UPPER_BOUND; + } + + @Test + public int testRandomLimits(long x, long y) { + x = RANGE_1.clamp(x); + y = RANGE_2.clamp(y); + + var z = x ^ y; + // This should now have a new range, possibly some [0, max] + // Now let's test the range with some random if branches. + 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 + private int testRandomLimitsInterpreted(long x, long y) { + x = RANGE_1.clamp(x); + y = RANGE_2.clamp(y); + + var z = x ^ y; + // This should now have a new range, possibly some [0, max] + // Now let's test the range with some random if branches. + 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"); + } + } + + 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); + } + } }