8335444: Generalize implementation of AndNode mul_ring

Reviewed-by: chagedorn, qamai, dfenacci
This commit is contained in:
Jasmine Karthikeyan 2024-09-10 16:52:59 +00:00
parent 33525226b9
commit 92431049fd
5 changed files with 232 additions and 50 deletions

View File

@ -594,6 +594,69 @@ const Type* MulHiValue(const Type *t1, const Type *t2, const Type *bot) {
return TypeLong::LONG;
}
template<typename IntegerType>
static const IntegerType* and_value(const IntegerType* r0, const IntegerType* r1) {
typedef typename IntegerType::NativeType NativeType;
static_assert(std::is_signed<NativeType>::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<NativeType>::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);
const TypeInt* r0 = t0->is_int();
const TypeInt* r1 = t1->is_int();
// 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);
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<TypeInt>(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);
const TypeLong* r0 = t0->is_long();
const TypeLong* r1 = t1->is_long();
// 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);
return TypeLong::LONG; // No constants to be had
return and_value<TypeLong>(r0, r1);
}
const Type* AndLNode::Value(PhaseGVN* phase) const {

View File

@ -27,7 +27,8 @@ import compiler.lib.ir_framework.*;
/*
* @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 class AndINodeIdealizationTests {
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 class AndINodeIdealizationTests {
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 class AndINodeIdealizationTests {
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 class AndINodeIdealizationTests {
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;
}
}

View File

@ -27,7 +27,8 @@ import compiler.lib.ir_framework.*;
/*
* @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 class AndLNodeIdealizationTests {
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 class AndLNodeIdealizationTests {
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 class AndLNodeIdealizationTests {
@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 class AndLNodeIdealizationTests {
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;
}
}

View File

@ -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 class BasicBooleanOpTest extends VectorizationTestRunner {
}
@Test
@IR(applyIfCPUFeature = {"asimd", "true"},
@IR(applyIfCPUFeatureOr = {"asimd", "true", "sse2", "true"},
phase = CompilePhase.BEFORE_MACRO_EXPANSION,
counts = {IRNode.AND_VB, ">0"})
@IR(applyIfCPUFeatureAnd = {"avx512f", "false", "sse2", "true"},
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++) {

View File

@ -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 abstract class TypeVectorOperations {
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 abstract class TypeVectorOperations {
}
}
@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() {