8276162: Optimise unsigned comparison pattern

Reviewed-by: thartmann, kvn
This commit is contained in:
MeryKitty 2021-11-16 14:09:53 +00:00 committed by Tobias Hartmann
parent 9a9a157a7d
commit f3eb5014aa
3 changed files with 521 additions and 5 deletions

View file

@ -1480,13 +1480,15 @@ Node *BoolNode::Ideal(PhaseGVN *phase, bool can_reshape) {
return NULL;
}
const int cmp1_op = cmp1->Opcode();
const int cmp2_op = cmp2->Opcode();
// Constant on left?
Node *con = cmp1;
uint op2 = cmp2->Opcode();
// Move constants to the right of compare's to canonicalize.
// Do not muck with Opaque1 nodes, as this indicates a loop
// guard that cannot change shape.
if( con->is_Con() && !cmp2->is_Con() && op2 != Op_Opaque1 &&
if( con->is_Con() && !cmp2->is_Con() && cmp2_op != Op_Opaque1 &&
// Because of NaN's, CmpD and CmpF are not commutative
cop != Op_CmpD && cop != Op_CmpF &&
// Protect against swapping inputs to a compare when it is used by a
@ -1504,7 +1506,7 @@ Node *BoolNode::Ideal(PhaseGVN *phase, bool can_reshape) {
// Change "bool eq/ne (cmp (and X 16) 16)" into "bool ne/eq (cmp (and X 16) 0)".
if (cop == Op_CmpI &&
(_test._test == BoolTest::eq || _test._test == BoolTest::ne) &&
cmp1->Opcode() == Op_AndI && cmp2->Opcode() == Op_ConI &&
cmp1_op == Op_AndI && cmp2_op == Op_ConI &&
cmp1->in(2)->Opcode() == Op_ConI) {
const TypeInt *t12 = phase->type(cmp2)->isa_int();
const TypeInt *t112 = phase->type(cmp1->in(2))->isa_int();
@ -1518,7 +1520,7 @@ Node *BoolNode::Ideal(PhaseGVN *phase, bool can_reshape) {
// Same for long type: change "bool eq/ne (cmp (and X 16) 16)" into "bool ne/eq (cmp (and X 16) 0)".
if (cop == Op_CmpL &&
(_test._test == BoolTest::eq || _test._test == BoolTest::ne) &&
cmp1->Opcode() == Op_AndL && cmp2->Opcode() == Op_ConL &&
cmp1_op == Op_AndL && cmp2_op == Op_ConL &&
cmp1->in(2)->Opcode() == Op_ConL) {
const TypeLong *t12 = phase->type(cmp2)->isa_long();
const TypeLong *t112 = phase->type(cmp1->in(2))->isa_long();
@ -1529,10 +1531,41 @@ Node *BoolNode::Ideal(PhaseGVN *phase, bool can_reshape) {
}
}
// Change "cmp (add X min_jint) (add Y min_jint)" into "cmpu X Y"
// and "cmp (add X min_jint) c" into "cmpu X (c + min_jint)"
if (cop == Op_CmpI &&
cmp1_op == Op_AddI &&
phase->type(cmp1->in(2)) == TypeInt::MIN) {
if (cmp2_op == Op_ConI) {
Node* ncmp2 = phase->intcon(java_add(cmp2->get_int(), min_jint));
Node* ncmp = phase->transform(new CmpUNode(cmp1->in(1), ncmp2));
return new BoolNode(ncmp, _test._test);
} else if (cmp2_op == Op_AddI &&
phase->type(cmp2->in(2)) == TypeInt::MIN) {
Node* ncmp = phase->transform(new CmpUNode(cmp1->in(1), cmp2->in(1)));
return new BoolNode(ncmp, _test._test);
}
}
// Change "cmp (add X min_jlong) (add Y min_jlong)" into "cmpu X Y"
// and "cmp (add X min_jlong) c" into "cmpu X (c + min_jlong)"
if (cop == Op_CmpL &&
cmp1_op == Op_AddL &&
phase->type(cmp1->in(2)) == TypeLong::MIN) {
if (cmp2_op == Op_ConL) {
Node* ncmp2 = phase->longcon(java_add(cmp2->get_long(), min_jlong));
Node* ncmp = phase->transform(new CmpULNode(cmp1->in(1), ncmp2));
return new BoolNode(ncmp, _test._test);
} else if (cmp2_op == Op_AddL &&
phase->type(cmp2->in(2)) == TypeLong::MIN) {
Node* ncmp = phase->transform(new CmpULNode(cmp1->in(1), cmp2->in(1)));
return new BoolNode(ncmp, _test._test);
}
}
// Change "bool eq/ne (cmp (xor X 1) 0)" into "bool ne/eq (cmp X 0)".
// The XOR-1 is an idiom used to flip the sense of a bool. We flip the
// test instead.
int cmp1_op = cmp1->Opcode();
const TypeInt* cmp2_type = phase->type(cmp2)->isa_int();
if (cmp2_type == NULL) return NULL;
Node* j_xor = cmp1;

View file

@ -0,0 +1,345 @@
/*
* Copyright (c) 2021, 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.irTests;
import jdk.test.lib.Asserts;
import compiler.lib.ir_framework.*;
/*
* @test
* @bug 8276162
* @summary Test that unsigned comparison transformation works as intended.
* @library /test/lib /
* @run driver compiler.c2.irTests.TestUnsignedComparison
*/
public class TestUnsignedComparison {
private static final String CMP_REGEX = "(\\d+(\\s){2}(" + "Cmp(I|L)" + ".*)+(\\s){2}===.*)";
private static final String CMPU_REGEX = "(\\d+(\\s){2}(" + "Cmp(U|UL)" + ".*)+(\\s){2}===.*)";
private static final String ADD_REGEX = "(\\d+(\\s){2}(" + "Add(I|L)" + ".*)+(\\s){2}===.*)";
private static final int INT_MIN = Integer.MIN_VALUE;
private static final long LONG_MIN = Long.MIN_VALUE;
// Integers are sorted in unsignedly increasing order
private static final int[] INT_DATA = {
0,
1,
2,
3,
0x8000_0000,
0x8000_0001,
0x8000_0002,
0x8000_0003,
0xFFFF_FFFE,
0xFFFF_FFFF,
};
// Longs are sorted in unsignedly increasing order
private static final long[] LONG_DATA = {
0L,
1L,
2L,
3L,
0x00000000_80000000L,
0x00000000_FFFFFFFFL,
0x00000001_00000000L,
0x80000000_00000000L,
0x80000000_00000001L,
0x80000000_00000002L,
0x80000000_00000003L,
0x80000000_80000000L,
0xFFFFFFFF_FFFFFFFEL,
0xFFFFFFFF_FFFFFFFFL,
};
// Constants to compare against, add MIN_VALUE beforehand for convenience
private static final int CONST_INDEX = 6;
private static final int INT_CONST = INT_DATA[CONST_INDEX] + INT_MIN;
private static final long LONG_CONST = LONG_DATA[CONST_INDEX] + LONG_MIN;
public static void main(String[] args) {
TestFramework framework = new TestFramework();
framework.start();
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testIntVarEQ(int x, int y) {
return x + INT_MIN == y + INT_MIN;
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testIntVarNE(int x, int y) {
return x + INT_MIN != y + INT_MIN;
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testIntVarLT(int x, int y) {
return x + INT_MIN < y + INT_MIN;
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testIntVarLE(int x, int y) {
return x + INT_MIN <= y + INT_MIN;
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testIntVarGT(int x, int y) {
return x + INT_MIN > y + INT_MIN;
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testIntVarGE(int x, int y) {
return x + INT_MIN >= y + INT_MIN;
}
@Run(test = {"testIntVarEQ", "testIntVarNE",
"testIntVarLT", "testIntVarLE",
"testIntVarGT", "testIntVarGE"})
public void checkTestIntVar() {
// Verify the transformation "cmp (add X min_jint) (add Y min_jint)"
// to "cmpu X Y"
for (int i = 0; i < INT_DATA.length; i++) {
for (int j = 0; j < INT_DATA.length; j++) {
Asserts.assertEquals(testIntVarEQ(INT_DATA[i], INT_DATA[j]),
i == j);
Asserts.assertEquals(testIntVarNE(INT_DATA[i], INT_DATA[j]),
i != j);
Asserts.assertEquals(testIntVarLT(INT_DATA[i], INT_DATA[j]),
i < j);
Asserts.assertEquals(testIntVarLE(INT_DATA[i], INT_DATA[j]),
i <= j);
Asserts.assertEquals(testIntVarGT(INT_DATA[i], INT_DATA[j]),
i > j);
Asserts.assertEquals(testIntVarGE(INT_DATA[i], INT_DATA[j]),
i >= j);
}
}
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testIntConEQ(int x) {
return x + INT_MIN == INT_CONST;
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testIntConNE(int x) {
return x + INT_MIN != INT_CONST;
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testIntConLT(int x) {
return x + INT_MIN < INT_CONST;
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testIntConLE(int x) {
return x + INT_MIN <= INT_CONST;
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testIntConGT(int x) {
return x + INT_MIN > INT_CONST;
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testIntConGE(int x) {
return x + INT_MIN >= INT_CONST;
}
@Run(test = {"testIntConEQ", "testIntConNE",
"testIntConLT", "testIntConLE",
"testIntConGT", "testIntConGE"})
public void checkTestIntCon() {
// Verify the transformation "cmp (add X min_jint) c"
// to "cmpu X (c + min_jint)"
for (int i = 0; i < INT_DATA.length; i++) {
Asserts.assertEquals(testIntConEQ(INT_DATA[i]),
i == CONST_INDEX);
Asserts.assertEquals(testIntConNE(INT_DATA[i]),
i != CONST_INDEX);
Asserts.assertEquals(testIntConLT(INT_DATA[i]),
i < CONST_INDEX);
Asserts.assertEquals(testIntConLE(INT_DATA[i]),
i <= CONST_INDEX);
Asserts.assertEquals(testIntConGT(INT_DATA[i]),
i > CONST_INDEX);
Asserts.assertEquals(testIntConGE(INT_DATA[i]),
i >= CONST_INDEX);
}
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testLongVarEQ(long x, long y) {
return x + LONG_MIN == y + LONG_MIN;
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testLongVarNE(long x, long y) {
return x + LONG_MIN != y + LONG_MIN;
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testLongVarLT(long x, long y) {
return x + LONG_MIN < y + LONG_MIN;
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testLongVarLE(long x, long y) {
return x + LONG_MIN <= y + LONG_MIN;
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testLongVarGT(long x, long y) {
return x + LONG_MIN > y + LONG_MIN;
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testLongVarGE(long x, long y) {
return x + LONG_MIN >= y + LONG_MIN;
}
@Run(test = {"testLongVarEQ", "testLongVarNE",
"testLongVarLT", "testLongVarLE",
"testLongVarGT", "testLongVarGE"})
public void checkTestLongVar() {
// Verify the transformation "cmp (add X min_jlong) (add Y min_jlong)"
// to "cmpu X Y"
for (int i = 0; i < LONG_DATA.length; i++) {
for (int j = 0; j < LONG_DATA.length; j++) {
Asserts.assertEquals(testLongVarEQ(LONG_DATA[i], LONG_DATA[j]),
i == j);
Asserts.assertEquals(testLongVarNE(LONG_DATA[i], LONG_DATA[j]),
i != j);
Asserts.assertEquals(testLongVarLT(LONG_DATA[i], LONG_DATA[j]),
i < j);
Asserts.assertEquals(testLongVarLE(LONG_DATA[i], LONG_DATA[j]),
i <= j);
Asserts.assertEquals(testLongVarGT(LONG_DATA[i], LONG_DATA[j]),
i > j);
Asserts.assertEquals(testLongVarGE(LONG_DATA[i], LONG_DATA[j]),
i >= j);
}
}
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testLongConEQ(long x) {
return x + LONG_MIN == LONG_CONST;
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testLongConNE(long x) {
return x + LONG_MIN != LONG_CONST;
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testLongConLT(long x) {
return x + LONG_MIN < LONG_CONST;
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testLongConLE(long x) {
return x + LONG_MIN <= LONG_CONST;
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testLongConGT(long x) {
return x + LONG_MIN > LONG_CONST;
}
@Test
@IR(failOn = {CMP_REGEX, ADD_REGEX})
@IR(counts = {CMPU_REGEX, "1"})
public boolean testLongConGE(long x) {
return x + LONG_MIN >= LONG_CONST;
}
@Run(test = {"testLongConEQ", "testLongConNE",
"testLongConLT", "testLongConLE",
"testLongConGT", "testLongConGE"})
public void checkTestLongConGE() {
// Verify the transformation "cmp (add X min_jlong) c"
// to "cmpu X (c + min_jlong)"
for (int i = 0; i < LONG_DATA.length; i++) {
Asserts.assertEquals(testLongConEQ(LONG_DATA[i]),
i == CONST_INDEX);
Asserts.assertEquals(testLongConNE(LONG_DATA[i]),
i != CONST_INDEX);
Asserts.assertEquals(testLongConLT(LONG_DATA[i]),
i < CONST_INDEX);
Asserts.assertEquals(testLongConLE(LONG_DATA[i]),
i <= CONST_INDEX);
Asserts.assertEquals(testLongConGT(LONG_DATA[i]),
i > CONST_INDEX);
Asserts.assertEquals(testLongConGE(LONG_DATA[i]),
i >= CONST_INDEX);
}
}
}

View file

@ -0,0 +1,138 @@
/*
* Copyright (c) 2021, 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 org.openjdk.bench.vm.compiler;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.infra.Blackhole;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(2)
@State(Scope.Thread)
public class UnsignedComparison {
private static final int ITERATIONS = 1000;
private static final int CONST_OPERAND = 4;
private static final int INT_MIN = Integer.MIN_VALUE;
private static final long LONG_MIN = Long.MIN_VALUE;
int arg0 = 0, arg1 = 4;
@Setup(Level.Invocation)
public void toggle() {
arg0 = (arg0 + 1) & 7;
}
@Benchmark
public void intVarDirect(Blackhole bh) {
for (int i = 0; i < ITERATIONS; i++) {
bh.consume(arg0 + INT_MIN < arg1 + INT_MIN);
}
}
@Benchmark
public void intVarLibLT(Blackhole bh) {
for (int i = 0; i < ITERATIONS; i++) {
bh.consume(Integer.compareUnsigned(arg0, arg1) < 0);
}
}
@Benchmark
public void intVarLibGT(Blackhole bh) {
for (int i = 0; i < ITERATIONS; i++) {
bh.consume(Integer.compareUnsigned(arg0, arg1) > 0);
}
}
@Benchmark
public void intConDirect(Blackhole bh) {
for (int i = 0; i < ITERATIONS; i++) {
bh.consume(arg0 + INT_MIN < CONST_OPERAND + INT_MIN);
}
}
@Benchmark
public void intConLibLT(Blackhole bh) {
for (int i = 0; i < ITERATIONS; i++) {
bh.consume(Integer.compareUnsigned(arg0, CONST_OPERAND) < 0);
}
}
@Benchmark
public void intConLibGT(Blackhole bh) {
for (int i = 0; i < ITERATIONS; i++) {
bh.consume(Integer.compareUnsigned(arg0, CONST_OPERAND) > 0);
}
}
@Benchmark
public void longVarDirect(Blackhole bh) {
for (int i = 0; i < ITERATIONS; i++) {
bh.consume(arg0 + LONG_MIN < arg1 + LONG_MIN);
}
}
@Benchmark
public void longVarLibLT(Blackhole bh) {
for (int i = 0; i < ITERATIONS; i++) {
bh.consume(Long.compareUnsigned(arg0, arg1) < 0);
}
}
@Benchmark
public void longVarLibGT(Blackhole bh) {
for (int i = 0; i < ITERATIONS; i++) {
bh.consume(Long.compareUnsigned(arg0, arg1) > 0);
}
}
@Benchmark
public void longConDirect(Blackhole bh) {
for (int i = 0; i < ITERATIONS; i++) {
bh.consume(arg0 + LONG_MIN < CONST_OPERAND + LONG_MIN);
}
}
@Benchmark
public void longConLibLT(Blackhole bh) {
for (int i = 0; i < ITERATIONS; i++) {
bh.consume(Long.compareUnsigned(arg0, CONST_OPERAND) < 0);
}
}
@Benchmark
public void longConLibGT(Blackhole bh) {
for (int i = 0; i < ITERATIONS; i++) {
bh.consume(Long.compareUnsigned(arg0, CONST_OPERAND) > 0);
}
}
}