diff --git a/src/hotspot/share/opto/mulnode.cpp b/src/hotspot/share/opto/mulnode.cpp index 1f22c60832388..ad98fda025f07 100644 --- a/src/hotspot/share/opto/mulnode.cpp +++ b/src/hotspot/share/opto/mulnode.cpp @@ -594,6 +594,69 @@ const Type* MulHiValue(const Type *t1, const Type *t2, const Type *bot) { return TypeLong::LONG; } +template +static const IntegerType* and_value(const IntegerType* r0, const IntegerType* r1) { + typedef typename IntegerType::NativeType NativeType; + static_assert(std::is_signed::value, "Native type of IntegerType must be signed!"); + + int widen = MAX2(r0->_widen, r1->_widen); + + // If both types are constants, we can calculate a constant result. + if (r0->is_con() && r1->is_con()) { + return IntegerType::make(r0->get_con() & r1->get_con()); + } + + // If both ranges are positive, the result will range from 0 up to the hi value of the smaller range. The minimum + // of the two constrains the upper bound because any higher value in the other range will see all zeroes, so it will be masked out. + if (r0->_lo >= 0 && r1->_lo >= 0) { + return IntegerType::make(0, MIN2(r0->_hi, r1->_hi), widen); + } + + // If only one range is positive, the result will range from 0 up to that range's maximum value. + // For the operation 'x & C' where C is a positive constant, the result will be in the range [0..C]. With that observation, + // we can say that for any integer c such that 0 <= c <= C will also be in the range [0..C]. Therefore, 'x & [c..C]' + // where c >= 0 will be in the range [0..C]. + if (r0->_lo >= 0) { + return IntegerType::make(0, r0->_hi, widen); + } + + if (r1->_lo >= 0) { + return IntegerType::make(0, r1->_hi, widen); + } + + // At this point, all positive ranges will have already been handled, so the only remaining cases will be negative ranges + // and constants. + + assert(r0->_lo < 0 && r1->_lo < 0, "positive ranges should already be handled!"); + + // As two's complement means that both numbers will start with leading 1s, the lower bound of both ranges will contain + // the common leading 1s of both minimum values. In order to count them with count_leading_zeros, the bits are inverted. + NativeType sel_val = ~MIN2(r0->_lo, r1->_lo); + + NativeType min; + if (sel_val == 0) { + // Since count_leading_zeros is undefined at 0, we short-circuit the condition where both ranges have a minimum of -1. + min = -1; + } else { + // To get the number of bits to shift, we count the leading 0-bits and then subtract one, as the sign bit is already set. + int shift_bits = count_leading_zeros(sel_val) - 1; + min = std::numeric_limits::min() >> shift_bits; + } + + NativeType max; + if (r0->_hi < 0 && r1->_hi < 0) { + // If both ranges are negative, then the same optimization as both positive ranges will apply, and the smaller hi + // value will mask off any bits set by higher values. + max = MIN2(r0->_hi, r1->_hi); + } else { + // In the case of ranges that cross zero, negative values can cause the higher order bits to be set, so the maximum + // positive value can be as high as the larger hi value. + max = MAX2(r0->_hi, r1->_hi); + } + + return IntegerType::make(min, max, widen); +} + //============================================================================= //------------------------------mul_ring--------------------------------------- // Supplied function returns the product of the inputs IN THE CURRENT RING. @@ -601,29 +664,10 @@ const Type* MulHiValue(const Type *t1, const Type *t2, const Type *bot) { // This also type-checks the inputs for sanity. Guaranteed never to // be passed a TOP or BOTTOM type, these are filtered out by pre-check. const Type *AndINode::mul_ring( const Type *t0, const Type *t1 ) const { - const TypeInt *r0 = t0->is_int(); // Handy access - const TypeInt *r1 = t1->is_int(); - int widen = MAX2(r0->_widen,r1->_widen); - - // If either input is a constant, might be able to trim cases - if( !r0->is_con() && !r1->is_con() ) - return TypeInt::INT; // No constants to be had - - // Both constants? Return bits - if( r0->is_con() && r1->is_con() ) - return TypeInt::make( r0->get_con() & r1->get_con() ); - - if( r0->is_con() && r0->get_con() > 0 ) - return TypeInt::make(0, r0->get_con(), widen); + const TypeInt* r0 = t0->is_int(); + const TypeInt* r1 = t1->is_int(); - if( r1->is_con() && r1->get_con() > 0 ) - return TypeInt::make(0, r1->get_con(), widen); - - if( r0 == TypeInt::BOOL || r1 == TypeInt::BOOL ) { - return TypeInt::BOOL; - } - - return TypeInt::INT; // No constants to be had + return and_value(r0, r1); } const Type* AndINode::Value(PhaseGVN* phase) const { @@ -751,25 +795,10 @@ Node *AndINode::Ideal(PhaseGVN *phase, bool can_reshape) { // This also type-checks the inputs for sanity. Guaranteed never to // be passed a TOP or BOTTOM type, these are filtered out by pre-check. const Type *AndLNode::mul_ring( const Type *t0, const Type *t1 ) const { - const TypeLong *r0 = t0->is_long(); // Handy access - const TypeLong *r1 = t1->is_long(); - int widen = MAX2(r0->_widen,r1->_widen); - - // If either input is a constant, might be able to trim cases - if( !r0->is_con() && !r1->is_con() ) - return TypeLong::LONG; // No constants to be had - - // Both constants? Return bits - if( r0->is_con() && r1->is_con() ) - return TypeLong::make( r0->get_con() & r1->get_con() ); - - if( r0->is_con() && r0->get_con() > 0 ) - return TypeLong::make(CONST64(0), r0->get_con(), widen); - - if( r1->is_con() && r1->get_con() > 0 ) - return TypeLong::make(CONST64(0), r1->get_con(), widen); + const TypeLong* r0 = t0->is_long(); + const TypeLong* r1 = t1->is_long(); - return TypeLong::LONG; // No constants to be had + return and_value(r0, r1); } const Type* AndLNode::Value(PhaseGVN* phase) const { diff --git a/test/hotspot/jtreg/compiler/c2/irTests/AndINodeIdealizationTests.java b/test/hotspot/jtreg/compiler/c2/irTests/AndINodeIdealizationTests.java index f20c28e321db1..1e90c22ddabc3 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/AndINodeIdealizationTests.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/AndINodeIdealizationTests.java @@ -27,7 +27,8 @@ /* * @test - * @bug 8297384 + * @bug 8297384 8335444 + * @key randomness * @summary Test that Ideal transformations of AndINode* are being performed as expected. * @library /test/lib / * @run driver compiler.c2.irTests.AndINodeIdealizationTests @@ -38,7 +39,7 @@ public static void main(String[] args) { TestFramework.run(); } - @Run(test = { "test1", "test2" }) + @Run(test = { "test1", "test2", "test3", "test4", "test5", "test6", "test7", "test8", "test9", "test10" }) public void runMethod() { int a = RunInfo.getRandom().nextInt(); int b = RunInfo.getRandom().nextInt(); @@ -47,7 +48,12 @@ public void runMethod() { int max = Integer.MAX_VALUE; assertResult(0, 0); + assertResult(10, 20); + assertResult(10, -20); + assertResult(-10, 20); + assertResult(-10, -20); assertResult(a, b); + assertResult(b, a); assertResult(min, min); assertResult(max, max); } @@ -56,6 +62,14 @@ public void runMethod() { public void assertResult(int a, int b) { Asserts.assertEQ((0 - a) & 1, test1(a)); Asserts.assertEQ((~a) & (~b), test2(a, b)); + Asserts.assertEQ((a & 15) >= 0, test3(a, b)); + Asserts.assertEQ((a & 15) > 15, test4(a, b)); + Asserts.assertEQ((a & (b >>> 1)) >= 0, test5(a, b)); + Asserts.assertEQ((a & (b >>> 30)) > 3, test6(a, b)); + Asserts.assertEQ(((byte)a & -8) >= -128, test7(a, b)); + Asserts.assertEQ(((byte)a & -8) <= 127, test8(a, b)); + Asserts.assertEQ(((a & 255) & (char)b) > 255, test9(a, b)); + Asserts.assertEQ((((a & 1) - 3) & ((b & 2) - 10)) > -8, test10(a, b)); } @Test @@ -74,4 +88,60 @@ public int test1(int x) { public int test2(int a, int b) { return (~a) & (~b); } + + @Test + @IR(failOn = { IRNode.AND }) + // Checks a & 15 => [0, 15] + public boolean test3(int a, int b) { + return (a & 15) >= 0; + } + + @Test + @IR(failOn = { IRNode.AND }) + // Checks a & 15 => [0, 15] + public boolean test4(int a, int b) { + return (a & 15) > 15; + } + + @Test + @IR(failOn = { IRNode.AND }) + // Checks a & [0, int_max] => [0, int_max] + public boolean test5(int a, int b) { + return (a & (b >>> 1)) >= 0; + } + + @Test + @IR(failOn = { IRNode.AND }) + // Checks a & [0, 3] => [0, 3] + public boolean test6(int a, int b) { + return (a & (b >>> 30)) > 3; + } + + @Test + @IR(failOn = { IRNode.AND }) + // Checks [-128, 127] & -8 => [-128, 127] + public boolean test7(int a, int b) { + return ((byte)a & -8) >= -128; + } + + @Test + @IR(failOn = { IRNode.AND }) + // Checks [-128, 127] & -8 => [-128, 127] + public boolean test8(int a, int b) { + return ((byte)a & -8) <= 127; + } + + @Test + @IR(failOn = { IRNode.AND }) + // Checks that [0, 255] & [0, 65535] => [0, 255] + public boolean test9(int a, int b) { + return ((a & 255) & (char)b) > 255; + } + + @Test + @IR(failOn = { IRNode.AND }) + // Checks that [-3, -2] & [-10, -8] => [-16, -8] + public boolean test10(int a, int b) { + return (((a & 1) - 3) & ((b & 2) - 10)) > -8; + } } diff --git a/test/hotspot/jtreg/compiler/c2/irTests/AndLNodeIdealizationTests.java b/test/hotspot/jtreg/compiler/c2/irTests/AndLNodeIdealizationTests.java index 9aa1b62be9743..a32172b20153a 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/AndLNodeIdealizationTests.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/AndLNodeIdealizationTests.java @@ -27,7 +27,8 @@ /* * @test - * @bug 8322589 + * @bug 8322589 8335444 + * @key randomness * @summary Test that Ideal transformations of AndLNode* are being performed as expected. * @library /test/lib / * @run driver compiler.c2.irTests.AndLNodeIdealizationTests @@ -38,7 +39,7 @@ public static void main(String[] args) { TestFramework.run(); } - @Run(test = { "test1" }) + @Run(test = { "test1", "test2", "test3", "test4", "test5", "test6", "test7", "test8", "test9" }) public void runMethod() { long a = RunInfo.getRandom().nextLong(); long b = RunInfo.getRandom().nextLong(); @@ -47,7 +48,12 @@ public void runMethod() { long max = Long.MAX_VALUE; assertResult(0, 0); + assertResult(10, 20); + assertResult(10, -20); + assertResult(-10, 20); + assertResult(-10, -20); assertResult(a, b); + assertResult(b, a); assertResult(min, min); assertResult(max, max); } @@ -55,6 +61,14 @@ public void runMethod() { @DontCompile public void assertResult(long a, long b) { Asserts.assertEQ((~a) & (~b), test1(a, b)); + Asserts.assertEQ((a & 15) >= 0, test2(a, b)); + Asserts.assertEQ((a & 15) > 15, test3(a, b)); + Asserts.assertEQ((a & (b >>> 1)) >= 0, test4(a, b)); + Asserts.assertEQ((a & (b >>> 62)) > 3, test5(a, b)); + Asserts.assertEQ(((byte)a & -8L) >= -128, test6(a, b)); + Asserts.assertEQ(((byte)a & -8L) <= 127, test7(a, b)); + Asserts.assertEQ(((a & 255) & (char)b) > 255, test8(a, b)); + Asserts.assertEQ((((a & 1) - 3) & ((b & 2) - 10)) > -8, test9(a, b)); } @Test @@ -65,4 +79,60 @@ public void assertResult(long a, long b) { public long test1(long a, long b) { return (~a) & (~b); } + + @Test + @IR(failOn = { IRNode.AND }) + // Checks a & 15 => [0, 15] + public boolean test2(long a, long b) { + return (a & 15) >= 0; + } + + @Test + @IR(failOn = { IRNode.AND }) + // Checks a & 15 => [0, 15] + public boolean test3(long a, long b) { + return (a & 15) > 15; + } + + @Test + @IR(failOn = { IRNode.AND }) + // Checks a & [0, long_max] => [0, long_max] + public boolean test4(long a, long b) { + return (a & (b >>> 1)) >= 0; + } + + @Test + @IR(failOn = { IRNode.AND }) + // Checks a & [0, 3] => [0, 3] + public boolean test5(long a, long b) { + return (a & (b >>> 62)) > 3; + } + + @Test + @IR(failOn = { IRNode.AND }) + // Checks [-128, 127] & -8 => [-128, 127] + public boolean test6(long a, long b) { + return ((byte)a & -8L) >= -128; + } + + @Test + @IR(failOn = { IRNode.AND }) + // Checks [-128, 127] & -8 => [-128, 127] + public boolean test7(long a, long b) { + return ((byte)a & -8L) <= 127; + } + + @Test + @IR(failOn = { IRNode.AND }) + // Checks that [0, 255] & [0, 65535] => [0, 255] + public boolean test8(long a, long b) { + return ((a & 255) & (char)b) > 255; + } + + @Test + @IR(failOn = { IRNode.AND }) + // Checks that [-3, -2] & [-10, -8] => [-16, -8] + public boolean test9(long a, long b) { + return (((a & 1) - 3) & ((b & 2) - 10)) > -8; + } } diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/BasicBooleanOpTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/BasicBooleanOpTest.java index d1a61538f2e7e..bb6185177e75f 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/BasicBooleanOpTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/BasicBooleanOpTest.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2022, 2023, Arm Limited. All rights reserved. + * Copyright (c) 2024, 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 @@ -71,12 +72,9 @@ public boolean[] vectorNot() { } @Test - @IR(applyIfCPUFeature = {"asimd", "true"}, - counts = {IRNode.AND_VB, ">0"}) - @IR(applyIfCPUFeatureAnd = {"avx512f", "false", "sse2", "true"}, + @IR(applyIfCPUFeatureOr = {"asimd", "true", "sse2", "true"}, + phase = CompilePhase.BEFORE_MACRO_EXPANSION, counts = {IRNode.AND_VB, ">0"}) - @IR(applyIfCPUFeature = {"avx512f", "true"}, - counts = {IRNode.MACRO_LOGIC_V, ">0"}) public boolean[] vectorAnd() { boolean[] res = new boolean[SIZE]; for (int i = 0; i < SIZE; i++) { diff --git a/test/micro/org/openjdk/bench/vm/compiler/TypeVectorOperations.java b/test/micro/org/openjdk/bench/vm/compiler/TypeVectorOperations.java index 9d4f62604409a..a3e4445664ce9 100644 --- a/test/micro/org/openjdk/bench/vm/compiler/TypeVectorOperations.java +++ b/test/micro/org/openjdk/bench/vm/compiler/TypeVectorOperations.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2024, 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 @@ -38,6 +38,9 @@ public abstract class TypeVectorOperations { @Param({"512", /* "1024", */ "2048"}) public int COUNT; + private boolean[] boolsA; + private boolean[] boolsB; + private boolean[] resZ; private byte[] bytesA; private byte[] bytesB; private byte[] resB; @@ -58,6 +61,9 @@ public abstract class TypeVectorOperations { @Setup public void init() { + boolsA = new boolean[COUNT]; + boolsB = new boolean[COUNT]; + resZ = new boolean[COUNT]; bytesA = new byte[COUNT]; bytesB = new byte[COUNT]; resB = new byte[COUNT]; @@ -73,6 +79,8 @@ public void init() { resF = new float[COUNT]; for (int i = 0; i < COUNT; i++) { + boolsA[i] = r.nextBoolean(); + boolsB[i] = r.nextBoolean(); shorts[i] = (short) r.nextInt(Short.MAX_VALUE + 1); ints[i] = r.nextInt(); longs[i] = r.nextLong(); @@ -366,6 +374,13 @@ public void convertS2L() { } } + @Benchmark + public void andZ() { + for (int i = 0; i < COUNT; i++) { + resZ[i] = boolsA[i] & boolsB[i]; + } + } + @Benchmark @Fork(jvmArgsPrepend = {"-XX:+UseCMoveUnconditionally", "-XX:+UseVectorCmov"}) public void cmoveD() {