From bdcac98673a2250f608bdf244e17578eecb30fbe Mon Sep 17 00:00:00 2001 From: Marc Chevalier Date: Tue, 25 Mar 2025 10:15:55 +0000 Subject: [PATCH] 8347459: C2: missing transformation for chain of shifts/multiplications by constants Reviewed-by: dfenacci, epeter --- src/hotspot/share/opto/memnode.cpp | 216 ++++++++++++++++-- src/hotspot/share/opto/memnode.hpp | 2 +- src/hotspot/share/opto/mulnode.cpp | 57 +++++ .../share/utilities/globalDefinitions.hpp | 8 + .../irTests/LShiftINodeIdealizationTests.java | 138 ++++++++++- .../irTests/LShiftLNodeIdealizationTests.java | 122 +++++++++- 6 files changed, 526 insertions(+), 17 deletions(-) diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index 7a285ee0722..a19dbd664eb 100644 --- a/src/hotspot/share/opto/memnode.cpp +++ b/src/hotspot/share/opto/memnode.cpp @@ -3606,20 +3606,208 @@ Node *StoreNode::Ideal_masked_input(PhaseGVN *phase, uint mask) { //------------------------------Ideal_sign_extended_input---------------------- // Check for useless sign-extension before a partial-word store -// (StoreB ... (RShiftI _ (LShiftI _ valIn conIL ) conIR) ) -// If (conIL == conIR && conIR <= num_bits) this simplifies to -// (StoreB ... (valIn) ) -Node *StoreNode::Ideal_sign_extended_input(PhaseGVN *phase, int num_bits) { - Node *val = in(MemNode::ValueIn); - if( val->Opcode() == Op_RShiftI ) { - const TypeInt *t = phase->type( val->in(2) )->isa_int(); - if( t && t->is_con() && (t->get_con() <= num_bits) ) { - Node *shl = val->in(1); - if( shl->Opcode() == Op_LShiftI ) { - const TypeInt *t2 = phase->type( shl->in(2) )->isa_int(); - if( t2 && t2->is_con() && (t2->get_con() == t->get_con()) ) { - set_req_X(MemNode::ValueIn, shl->in(1), phase); - return this; +// (StoreB ... (RShiftI _ (LShiftI _ v conIL) conIR)) +// If (conIL == conIR && conIR <= num_rejected_bits) this simplifies to +// (StoreB ... (v)) +// If (conIL > conIR) under some conditions, it can be simplified into +// (StoreB ... (LShiftI _ v (conIL - conIR))) +// This case happens when the value of the store was itself a left shift, that +// gets merged into the inner left shift of the sign-extension. For instance, +// if we have +// array_of_shorts[0] = (short)(v << 2) +// We get a structure such as: +// (StoreB ... (RShiftI _ (LShiftI _ (LShiftI _ v 2) 16) 16)) +// that is simplified into +// (StoreB ... (RShiftI _ (LShiftI _ v 18) 16)). +// It is thus useful to handle cases where conIL > conIR. But this simplification +// does not always hold. Let's see in which cases it's valid. +// +// Let's assume we have the following 32 bits integer v: +// +----------------------------------+ +// | v[0..31] | +// +----------------------------------+ +// 31 0 +// that will be stuffed in 8 bits byte after a shift left and a shift right of +// potentially different magnitudes. +// We denote num_rejected_bits the number of bits of the discarded part. In this +// case, num_rejected_bits == 24. +// +// Statement (proved further below in case analysis): +// Given: +// - 0 <= conIL < BitsPerJavaInteger (no wrap in shift, enforced by maskShiftAmount) +// - 0 <= conIR < BitsPerJavaInteger (no wrap in shift, enforced by maskShiftAmount) +// - conIL >= conIR +// - num_rejected_bits >= conIR +// Then this form: +// (RShiftI _ (LShiftI _ v conIL) conIR) +// can be replaced with this form: +// (LShiftI _ v (conIL-conIR)) +// +// Note: We only have to show that the non-rejected lowest bits (8 bits for byte) +// have to be correct, as the higher bits are rejected / truncated by the store. +// +// The hypotheses +// 0 <= conIL < BitsPerJavaInteger +// 0 <= conIR < BitsPerJavaInteger +// are ensured by maskShiftAmount (called from ::Ideal of shift nodes). Indeed, +// (v << 31) << 2 must be simplified into 0, not into v << 33 (which is equivalent +// to v << 1). +// +// +// If you don't like case analysis, jump after the conclusion. +// ### Case 1 : conIL == conIR +// ###### Case 1.1: conIL == conIR == num_rejected_bits +// If we do the shift left then right by 24 bits, we get: +// after: v << 24 +// +---------+------------------------+ +// | v[0..7] | 0 | +// +---------+------------------------+ +// 31 24 23 0 +// after: (v << 24) >> 24 +// +------------------------+---------+ +// | sign bit | v[0..7] | +// +------------------------+---------+ +// 31 8 7 0 +// The non-rejected bits (bits kept by the store, that is the 8 lower bits of the +// result) are the same before and after, so, indeed, simplifying is correct. + +// ###### Case 1.2: conIL == conIR < num_rejected_bits +// If we do the shift left then right by 22 bits, we get: +// after: v << 22 +// +---------+------------------------+ +// | v[0..9] | 0 | +// +---------+------------------------+ +// 31 22 21 0 +// after: (v << 22) >> 22 +// +------------------------+---------+ +// | sign bit | v[0..9] | +// +------------------------+---------+ +// 31 10 9 0 +// The non-rejected bits are the 8 lower bits of v. The bits 8 and 9 of v are still +// present in (v << 22) >> 22 but will be dropped by the store. The simplification is +// still correct. + +// ###### But! Case 1.3: conIL == conIR > num_rejected_bits +// If we do the shift left then right by 26 bits, we get: +// after: v << 26 +// +---------+------------------------+ +// | v[0..5] | 0 | +// +---------+------------------------+ +// 31 26 25 0 +// after: (v << 26) >> 26 +// +------------------------+---------+ +// | sign bit | v[0..5] | +// +------------------------+---------+ +// 31 6 5 0 +// The non-rejected bits are made of +// - 0-5 => the bits 0 to 5 of v +// - 6-7 => the sign bit of v[0..5] (that is v[5]) +// Simplifying this as v is not correct. +// The condition conIR <= num_rejected_bits is indeed necessary in Case 1 +// +// ### Case 2: conIL > conIR +// ###### Case 2.1: num_rejected_bits == conIR +// We take conIL == 26 for this example. +// after: v << 26 +// +---------+------------------------+ +// | v[0..5] | 0 | +// +---------+------------------------+ +// 31 26 25 0 +// after: (v << 26) >> 24 +// +------------------+---------+-----+ +// | sign bit | v[0..5] | 0 | +// +------------------+---------+-----+ +// 31 8 7 2 1 0 +// The non-rejected bits are the 8 lower ones of (v << conIL - conIR). +// The bits 6 and 7 of v have been thrown away after the shift left. +// The simplification is still correct. +// +// ###### Case 2.2: num_rejected_bits > conIR. +// Let's say conIL == 26 and conIR == 22. +// after: v << 26 +// +---------+------------------------+ +// | v[0..5] | 0 | +// +---------+------------------------+ +// 31 26 25 0 +// after: (v << 26) >> 22 +// +------------------+---------+-----+ +// | sign bit | v[0..5] | 0 | +// +------------------+---------+-----+ +// 31 10 9 4 3 0 +// The bits non-rejected by the store are exactly the 8 lower ones of (v << (conIL - conIR)): +// - 0-3 => 0 +// - 4-7 => bits 0 to 3 of v +// The simplification is still correct. +// The bits 4 and 5 of v are still present in (v << (conIL - conIR)) but they don't +// matter as they are not in the 8 lower bits: they will be cut out by the store. +// +// ###### But! Case 2.3: num_rejected_bits < conIR. +// Let's see that this case is not as easy to simplify. +// Let's say conIL == 28 and conIR == 26. +// after: v << 28 +// +---------+------------------------+ +// | v[0..3] | 0 | +// +---------+------------------------+ +// 31 28 27 0 +// after: (v << 28) >> 26 +// +------------------+---------+-----+ +// | sign bit | v[0..3] | 0 | +// +------------------+---------+-----+ +// 31 6 5 2 1 0 +// The non-rejected bits are made of +// - 0-1 => 0 +// - 2-5 => the bits 0 to 3 of v +// - 6-7 => the sign bit of v[0..3] (that is v[3]) +// Simplifying this as (v << 2) is not correct. +// The condition conIR <= num_rejected_bits is indeed necessary in Case 2. +// +// ### Conclusion: +// Our hypotheses are indeed sufficient: +// - 0 <= conIL < BitsPerJavaInteger +// - 0 <= conIR < BitsPerJavaInteger +// - conIL >= conIR +// - num_rejected_bits >= conIR +// +// ### A rationale without case analysis: +// After the shift left, conIL upper bits of v are discarded and conIL lower bit +// zeroes are added. After the shift right, conIR lower bits of the previous result +// are discarded. If conIL >= conIR, we discard only the zeroes we made up during +// the shift left, but if conIL < conIR, then we discard also lower bits of v. But +// the point of the simplification is to get an expression of the form +// (v << (conIL - conIR)). This expression discard only higher bits of v, thus the +// simplification is not correct if conIL < conIR. +// +// Moreover, after the shift right, the higher bit of (v << conIL) is repeated on the +// conIR higher bits of ((v << conIL) >> conIR), it's the sign-extension. If +// conIR > num_rejected_bits, then at least one artificial copy of this sign bit will +// be in the window of the store. Thus ((v << conIL) >> conIR) is not equivalent to +// (v << (conIL-conIR)) if conIR > num_rejected_bits. +// +// We do not treat the case conIL < conIR here since the point of this function is +// to skip sign-extensions (that is conIL == conIR == num_rejected_bits). The need +// of treating conIL > conIR comes from the cases where the sign-extended value is +// also left-shift expression. Computing the sign-extension of a right-shift expression +// doesn't yield a situation such as +// (StoreB ... (RShiftI _ (LShiftI _ v conIL) conIR)) +// where conIL < conIR. +Node* StoreNode::Ideal_sign_extended_input(PhaseGVN* phase, int num_rejected_bits) { + Node* shr = in(MemNode::ValueIn); + if (shr->Opcode() == Op_RShiftI) { + const TypeInt* conIR = phase->type(shr->in(2))->isa_int(); + if (conIR != nullptr && conIR->is_con() && conIR->get_con() >= 0 && conIR->get_con() < BitsPerJavaInteger && conIR->get_con() <= num_rejected_bits) { + Node* shl = shr->in(1); + if (shl->Opcode() == Op_LShiftI) { + const TypeInt* conIL = phase->type(shl->in(2))->isa_int(); + if (conIL != nullptr && conIL->is_con() && conIL->get_con() >= 0 && conIL->get_con() < BitsPerJavaInteger) { + if (conIL->get_con() == conIR->get_con()) { + set_req_X(MemNode::ValueIn, shl->in(1), phase); + return this; + } + if (conIL->get_con() > conIR->get_con()) { + Node* new_shl = phase->transform(new LShiftINode(shl->in(1), phase->intcon(conIL->get_con() - conIR->get_con()))); + set_req_X(MemNode::ValueIn, new_shl, phase); + return this; + } } } } diff --git a/src/hotspot/share/opto/memnode.hpp b/src/hotspot/share/opto/memnode.hpp index b19f7dda989..eca891bebbb 100644 --- a/src/hotspot/share/opto/memnode.hpp +++ b/src/hotspot/share/opto/memnode.hpp @@ -579,7 +579,7 @@ protected: virtual bool depends_only_on_test() const { return false; } Node *Ideal_masked_input (PhaseGVN *phase, uint mask); - Node *Ideal_sign_extended_input(PhaseGVN *phase, int num_bits); + Node* Ideal_sign_extended_input(PhaseGVN* phase, int num_rejected_bits); public: // We must ensure that stores of object references will be visible diff --git a/src/hotspot/share/opto/mulnode.cpp b/src/hotspot/share/opto/mulnode.cpp index b1a9219e113..e149662e199 100644 --- a/src/hotspot/share/opto/mulnode.cpp +++ b/src/hotspot/share/opto/mulnode.cpp @@ -970,6 +970,43 @@ static int maskShiftAmount(PhaseGVN* phase, Node* shiftNode, int nBits) { return 0; } +// Called with +// outer_shift = (_ << con0) +// We are looking for the pattern: +// outer_shift = ((X << con1) << con0) +// we denote inner_shift the nested expression (X << con1) +// +// con0 and con1 are both in [0..nbits), as they are computed by maskShiftAmount. +// +// There are 2 cases: +// if con0 + con1 >= nbits => 0 +// if con0 + con1 < nbits => X << (con1 + con0) +static Node* collapse_nested_shift_left(PhaseGVN* phase, Node* outer_shift, int con0, BasicType bt) { + assert(bt == T_LONG || bt == T_INT, "Unexpected type"); + int nbits = static_cast(bits_per_java_integer(bt)); + Node* inner_shift = outer_shift->in(1); + if (inner_shift->Opcode() != Op_LShift(bt)) { + return nullptr; + } + + int con1 = maskShiftAmount(phase, inner_shift, nbits); + if (con1 == 0) { // Either non-const, or actually 0 (up to mask) and then delegated to Identity() + return nullptr; + } + + if (con0 + con1 >= nbits) { + // While it might be tempting to use + // phase->zerocon(bt); + // it would be incorrect: zerocon caches nodes, while Ideal is only allowed + // to return a new node, this or nullptr, but not an old (cached) node. + return ConNode::make(TypeInteger::zero(bt)); + } + + // con0 + con1 < nbits ==> actual shift happens now + Node* con0_plus_con1 = phase->intcon(con0 + con1); + return LShiftNode::make(inner_shift->in(1), con0_plus_con1, bt); +} + //------------------------------Identity--------------------------------------- Node* LShiftINode::Identity(PhaseGVN* phase) { int count = 0; @@ -983,6 +1020,9 @@ Node* LShiftINode::Identity(PhaseGVN* phase) { //------------------------------Ideal------------------------------------------ // If the right input is a constant, and the left input is an add of a // constant, flatten the tree: (X+con1)< X< X << (con1 + con2) Node *LShiftINode::Ideal(PhaseGVN *phase, bool can_reshape) { int con = maskShiftAmount(phase, this, BitsPerJavaInteger); if (con == 0) { @@ -1095,6 +1135,13 @@ Node *LShiftINode::Ideal(PhaseGVN *phase, bool can_reshape) { phase->type(add1->in(2)) == TypeInt::make( bits_mask ) ) return new LShiftINode( add1->in(1), in(2) ); + // Performs: + // (X << con1) << con2 ==> X << (con1 + con2) + Node* doubleShift = collapse_nested_shift_left(phase, this, con, T_INT); + if (doubleShift != nullptr) { + return doubleShift; + } + return nullptr; } @@ -1159,6 +1206,9 @@ Node* LShiftLNode::Identity(PhaseGVN* phase) { //------------------------------Ideal------------------------------------------ // If the right input is a constant, and the left input is an add of a // constant, flatten the tree: (X+con1)< X< X << (con1 + con2) Node *LShiftLNode::Ideal(PhaseGVN *phase, bool can_reshape) { int con = maskShiftAmount(phase, this, BitsPerJavaLong); if (con == 0) { @@ -1271,6 +1321,13 @@ Node *LShiftLNode::Ideal(PhaseGVN *phase, bool can_reshape) { phase->type(add1->in(2)) == TypeLong::make( bits_mask ) ) return new LShiftLNode( add1->in(1), in(2) ); + // Performs: + // (X << con1) << con2 ==> X << (con1 + con2) + Node* doubleShift = collapse_nested_shift_left(phase, this, con, T_LONG); + if (doubleShift != nullptr) { + return doubleShift; + } + return nullptr; } diff --git a/src/hotspot/share/utilities/globalDefinitions.hpp b/src/hotspot/share/utilities/globalDefinitions.hpp index 4dc8ebfef4a..95ebc77d1c9 100644 --- a/src/hotspot/share/utilities/globalDefinitions.hpp +++ b/src/hotspot/share/utilities/globalDefinitions.hpp @@ -797,6 +797,14 @@ inline jlong min_signed_integer(BasicType bt) { return min_jlong; } +inline uint bits_per_java_integer(BasicType bt) { + if (bt == T_INT) { + return BitsPerJavaInteger; + } + assert(bt == T_LONG, "int or long only"); + return BitsPerJavaLong; +} + // Auxiliary math routines // least common multiple extern size_t lcm(size_t a, size_t b); diff --git a/test/hotspot/jtreg/compiler/c2/irTests/LShiftINodeIdealizationTests.java b/test/hotspot/jtreg/compiler/c2/irTests/LShiftINodeIdealizationTests.java index b9e1590443c..0b2a87fb2c5 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/LShiftINodeIdealizationTests.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/LShiftINodeIdealizationTests.java @@ -25,6 +25,8 @@ package compiler.c2.irTests; import jdk.test.lib.Asserts; import compiler.lib.ir_framework.*; +import static compiler.lib.generators.Generators.G; + /* * @test * @bug 8297384 8303238 @@ -33,11 +35,26 @@ import compiler.lib.ir_framework.*; * @run driver compiler.c2.irTests.LShiftINodeIdealizationTests */ public class LShiftINodeIdealizationTests { + private static final int CON0 = G.ints().next(); + private static final int CON1 = G.ints().next(); + public static void main(String[] args) { TestFramework.run(); } - @Run(test = { "test1", "test2", "test3", "test4", "test5", "test6", "test7", "test8" }) + @Run(test = {"test1", "test2", "test3", "test4", "test5", "test6", "test7", "test8", + "testDoubleShift1", + "testDoubleShift2", + "testDoubleShift3", + "testDoubleShift4", + "testDoubleShift5", + "testDoubleShift6", + "testDoubleShift7", + "testDoubleShift8", + "testDoubleShift9", + "testDoubleShiftSliceAndStore", + "testRandom", + }) public void runMethod() { int a = RunInfo.getRandom().nextInt(); int b = RunInfo.getRandom().nextInt(); @@ -66,6 +83,8 @@ public class LShiftINodeIdealizationTests { Asserts.assertEQ((a >>> 8) << 4, test6(a)); Asserts.assertEQ(((a >> 4) & 0xFF) << 8, test7(a)); Asserts.assertEQ(((a >>> 4) & 0xFF) << 8, test8(a)); + + assertDoubleShiftResult(a); } @Test @@ -131,4 +150,121 @@ public class LShiftINodeIdealizationTests { public int test8(int x) { return ((x >>> 4) & 0xFF) << 8; } + + @DontCompile + public void assertDoubleShiftResult(int a) { + Asserts.assertEQ((a << 2) << 3, testDoubleShift1(a)); + Asserts.assertEQ(a << 5, testDoubleShift1(a)); + + Asserts.assertEQ(((a << 2) << 3) << 1, testDoubleShift2(a)); + Asserts.assertEQ(a << 6, testDoubleShift2(a)); + + Asserts.assertEQ((a << 31) << 1, testDoubleShift3(a)); + Asserts.assertEQ(0, testDoubleShift3(a)); + + Asserts.assertEQ((a << 1) << 31, testDoubleShift4(a)); + Asserts.assertEQ(0, testDoubleShift4(a)); + + Asserts.assertEQ(((a << 30) << 1) << 1, testDoubleShift5(a)); + Asserts.assertEQ(0, testDoubleShift5(a)); + + Asserts.assertEQ((a * 4) << 3, testDoubleShift6(a)); + Asserts.assertEQ(a << 5, testDoubleShift6(a)); + + Asserts.assertEQ((a << 3) * 4, testDoubleShift7(a)); + Asserts.assertEQ(a << 5, testDoubleShift7(a)); + + Asserts.assertEQ(a << 33, testDoubleShift8(a)); + Asserts.assertEQ(a << 1, testDoubleShift8(a)); + + Asserts.assertEQ((a << 30) << 3, testDoubleShift9(a)); + Asserts.assertEQ(0, testDoubleShift9(a)); + + short[] arr = new short[1]; + arr[0] = (short)a; + Asserts.assertEQ((short)(a << 3), testDoubleShiftSliceAndStore(arr)[0]); + + Asserts.assertEQ((a << CON0) << CON1, testRandom(a)); + } + + @Test + @IR(counts = {IRNode.LSHIFT, "1"}) + // Checks (x << 2) << 3 => x << 5 + public int testDoubleShift1(int x) { + return (x << 2) << 3; + } + + @Test + @IR(counts = {IRNode.LSHIFT, "1"}) + // Checks ((x << 2) << 3) << 1 => x << 6 + public int testDoubleShift2(int x) { + return ((x << 2) << 3) << 1; + } + + @Test + @IR(failOn = {IRNode.LSHIFT}) + // Checks (x << 31) << 1 => 0 + public int testDoubleShift3(int x) { + return (x << 31) << 1; + } + + @Test + @IR(failOn = {IRNode.LSHIFT}) + // Checks (x << 1) << 31 => 0 + public int testDoubleShift4(int x) { + return (x << 1) << 31; + } + + @Test + @IR(failOn = {IRNode.LSHIFT}) + // Checks ((x << 30) << 1) << 1 => 0 + public int testDoubleShift5(int x) { + return ((x << 30) << 1) << 1; + } + + @Test + @IR(failOn = {IRNode.MUL}) + @IR(counts = {IRNode.LSHIFT, "1"}) + // Checks (x * 4) << 3 => x << 5 + public int testDoubleShift6(int x) { + return (x * 4) << 3; + } + + @Test + @IR(failOn = {IRNode.MUL}) + @IR(counts = {IRNode.LSHIFT, "1"}) + // Checks (x << 3) * 4 => x << 5 + public int testDoubleShift7(int x) { + return (x << 3) * 4; + } + + @Test + @IR(counts = {IRNode.LSHIFT, "1"}) + // Checks x << 33 => x << 1 + public int testDoubleShift8(int x) { + return x << 33; + } + + @Test + @IR(failOn = {IRNode.LSHIFT}) + // Checks (x << 30) << 3 => 0 + public int testDoubleShift9(int x) { + return (x << 30) << 3; + } + + @Test + @IR(failOn = {IRNode.MUL, IRNode.RSHIFT}) + @IR(counts = {IRNode.LSHIFT, "1"}) + // Checks (short) (a[0] << 3) => (((a[0] << 3) << 16) >> 16) => ((a[0] << 19) >> 16) => (a[0] << 3) + public short[] testDoubleShiftSliceAndStore(short[] a) { + short[] res = new short[1]; + res[0] = (short) (a[0] << 3); + return res; + } + + @Test + @IR(counts = {IRNode.LSHIFT, "<= 1"}) + public int testRandom(int x) { + return (x << CON0) << CON1; + } } diff --git a/test/hotspot/jtreg/compiler/c2/irTests/LShiftLNodeIdealizationTests.java b/test/hotspot/jtreg/compiler/c2/irTests/LShiftLNodeIdealizationTests.java index 31a13edc6cb..11eb928d564 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/LShiftLNodeIdealizationTests.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/LShiftLNodeIdealizationTests.java @@ -24,6 +24,7 @@ package compiler.c2.irTests; import jdk.test.lib.Asserts; import compiler.lib.ir_framework.*; +import static compiler.lib.generators.Generators.G; /* * @test @@ -33,11 +34,25 @@ import compiler.lib.ir_framework.*; * @run driver compiler.c2.irTests.LShiftLNodeIdealizationTests */ public class LShiftLNodeIdealizationTests { + private static final long CON0 = G.longs().next(); + private static final long CON1 = G.longs().next(); + public static void main(String[] args) { TestFramework.run(); } - @Run(test = { "test3", "test4", "test5", "test6", "test7", "test8" }) + @Run(test = {"test3", "test4", "test5", "test6", "test7", "test8", + "testDoubleShift1", + "testDoubleShift2", + "testDoubleShift3", + "testDoubleShift4", + "testDoubleShift5", + "testDoubleShift6", + "testDoubleShift7", + "testDoubleShift8", + "testDoubleShift9", + "testRandom", + }) public void runMethod() { long a = RunInfo.getRandom().nextLong(); long b = RunInfo.getRandom().nextLong(); @@ -64,6 +79,8 @@ public class LShiftLNodeIdealizationTests { Asserts.assertEQ((a >>> 8L) << 4L, test6(a)); Asserts.assertEQ(((a >> 4L) & 0xFFL) << 8L, test7(a)); Asserts.assertEQ(((a >>> 4L) & 0xFFL) << 8L, test8(a)); + + assertDoubleShiftResult(a); } @Test @@ -113,4 +130,107 @@ public class LShiftLNodeIdealizationTests { public long test8(long x) { return ((x >>> 4L) & 0xFFL) << 8L; } + + @DontCompile + public void assertDoubleShiftResult(long a) { + Asserts.assertEQ((a << 2L) << 3L, testDoubleShift1(a)); + Asserts.assertEQ(a << 5L, testDoubleShift1(a)); + + Asserts.assertEQ(((a << 2L) << 3L) << 1L, testDoubleShift2(a)); + Asserts.assertEQ(a << 6L, testDoubleShift2(a)); + + Asserts.assertEQ((a << 63L) << 1L, testDoubleShift3(a)); + Asserts.assertEQ(0L, testDoubleShift3(a)); + + Asserts.assertEQ((a << 1L) << 63L, testDoubleShift4(a)); + Asserts.assertEQ(0L, testDoubleShift4(a)); + + Asserts.assertEQ(((a << 62L) << 1L) << 1L, testDoubleShift5(a)); + Asserts.assertEQ(0L, testDoubleShift5(a)); + + Asserts.assertEQ((a * 4L) << 3L, testDoubleShift6(a)); + Asserts.assertEQ(a << 5L, testDoubleShift6(a)); + + Asserts.assertEQ((a << 3L) * 4L, testDoubleShift7(a)); + Asserts.assertEQ(a << 5L, testDoubleShift7(a)); + + Asserts.assertEQ(a << 65L, testDoubleShift8(a)); + Asserts.assertEQ(a << 1L, testDoubleShift8(a)); + + Asserts.assertEQ((a << 62L) << 3L, testDoubleShift9(a)); + Asserts.assertEQ(0L, testDoubleShift9(a)); + + Asserts.assertEQ((a << CON0) << CON1, testRandom(a)); + } + + @Test + @IR(counts = {IRNode.LSHIFT, "1"}) + // Checks (x << 2) << 3 => x << 5 + public long testDoubleShift1(long x) { + return (x << 2L) << 3L; + } + + @Test + @IR(counts = {IRNode.LSHIFT, "1"}) + // Checks ((x << 2) << 3) << 1 => x << 6 + public long testDoubleShift2(long x) { + return ((x << 2L) << 3L) << 1L; + } + + @Test + @IR(failOn = {IRNode.LSHIFT}) + // Checks (x << 63) << 1 => 0 + public long testDoubleShift3(long x) { + return (x << 63L) << 1L; + } + + @Test + @IR(failOn = {IRNode.LSHIFT}) + // Checks (x << 1) << 63 => 0 + public long testDoubleShift4(long x) { + return (x << 1L) << 63L; + } + + @Test + @IR(failOn = {IRNode.LSHIFT}) + // Checks ((x << 62) << 1) << 1 => 0 + public long testDoubleShift5(long x) { + return ((x << 62L) << 1L) << 1L; + } + + @Test + @IR(failOn = {IRNode.MUL}) + @IR(counts = {IRNode.LSHIFT, "1"}) + // Checks (x * 4) << 3 => x << 5 + public long testDoubleShift6(long x) { + return (x * 4L) << 3L; + } + + @Test + @IR(failOn = {IRNode.MUL}) + @IR(counts = {IRNode.LSHIFT, "1"}) + // Checks (x << 3) * 4 => x << 5 + public long testDoubleShift7(long x) { + return (x << 3L) * 4L; + } + + @Test + @IR(counts = {IRNode.LSHIFT, "1"}) + // Checks x << 65 => x << 1 + public long testDoubleShift8(long x) { + return x << 65L; + } + + @Test + @IR(failOn = {IRNode.LSHIFT}) + // Checks (x << 62) << 3 => 0 + public long testDoubleShift9(long x) { + return (x << 62L) << 3L; + } + + @Test + @IR(counts = {IRNode.LSHIFT, "<= 1"}) + public long testRandom(long x) { + return (x << CON0) << CON1; + } }