ARM64: Combine LSR+ASR into ASR for Int32 HDiv/HRem

HDiv/HRem having a constant divisor are optimized by using
multiplication of the dividend by a sort of reciprocal of the divisor.
The multiplication is done by multiplying 32-bit numbers into a 64-bit
result. The high 32 bits of the result are used. In case of Int32 LSR
is used to get those bits. After that there might be correction
operations and ASR. When there are no correction operations between LSR
and ASR they can be combined into one ASR.

This CL implements this optimization.

Improvements (Pixel 3):
                                                little core  big core
  jit_aot/LoadCheck.RandomSumInvokeStaticMethod   7.1%         8.3%
  jit_aot/LoadCheck.RandomSumInvokeUserClass      4.6%         12.0%
  benchmarksgame/fasta                            3.3%         1.0%
  benchmarksgame/fasta_4                          2.4%         2.6%
  benchmarksgame/fastaredux                       2.2%         2.2%
  SPECjvm2k8 MPEGAudio                            1.7%         1.0%

Test: test.py --host --optimizing --jit
Test: test.py --target --optimizing --jit
Change-Id: I5267b38d3a58319e24152917fabe836d5b346bce
diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc
index a2896fc..f4e18cf 100644
--- a/compiler/optimizing/code_generator_arm64.cc
+++ b/compiler/optimizing/code_generator_arm64.cc
@@ -3045,6 +3045,13 @@
   return divisor < 0 && magic_number > 0;
 }
 
+// Return true if the result of multiplication of the dividend by a sort of reciprocal
+// of the divisor (magic_number) needs to be corrected. This means additional operations will
+// be generated.
+static inline bool NeedToCorrectMulResult(int64_t magic_number, int64_t divisor) {
+  return NeedToAddDividend(magic_number, divisor) || NeedToSubDividend(magic_number, divisor);
+}
+
 void InstructionCodeGeneratorARM64::GenerateResultDivRemWithAnyConstant(
     bool is_rem,
     int final_right_shift,
@@ -3131,7 +3138,17 @@
   // temp = get_high(dividend * magic)
   __ Mov(temp, magic);
   __ Smull(temp.X(), dividend, temp);
-  __ Lsr(temp.X(), temp.X(), 32);
+
+  if (NeedToCorrectMulResult(magic, imm)) {
+    __ Lsr(temp.X(), temp.X(), 32);
+  } else {
+    // As between 'lsr temp.X(), temp.X(), #32' and 'asr temp, temp, #shift' there are
+    // no other instructions modifying 'temp', they can be combined into one
+    // 'asr temp.X(), temp.X(), #32 + shift'.
+    DCHECK_LT(shift, 32);
+    __ Asr(temp.X(), temp.X(), 32 + shift);
+    shift = 0;
+  }
 
   GenerateResultDivRemWithAnyConstant(/* is_rem= */ instruction->IsRem(),
                                       /* final_right_shift= */ shift,
diff --git a/test/411-checker-hdiv-hrem-const/expected.txt b/test/411-checker-hdiv-hrem-const/expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/411-checker-hdiv-hrem-const/expected.txt
diff --git a/test/411-checker-hdiv-hrem-const/info.txt b/test/411-checker-hdiv-hrem-const/info.txt
new file mode 100644
index 0000000..ff8332f
--- /dev/null
+++ b/test/411-checker-hdiv-hrem-const/info.txt
@@ -0,0 +1,2 @@
+Test the optimization of LSR+ASR into one ASR for integer division and remainder instructions when
+the denominator is a constant, not power of 2.
diff --git a/test/411-checker-hdiv-hrem-const/src/DivTest.java b/test/411-checker-hdiv-hrem-const/src/DivTest.java
new file mode 100644
index 0000000..cb97abd
--- /dev/null
+++ b/test/411-checker-hdiv-hrem-const/src/DivTest.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class DivTest {
+  private static void expectEquals(int expected, int result) {
+    if (expected != result) {
+      throw new Error("Expected: " + expected + ", found: " + result);
+    }
+  }
+
+  private static void expectEquals(long expected, long result) {
+    if (expected != result) {
+      throw new Error("Expected: " + expected + ", found: " + result);
+    }
+  }
+
+  public static void main() {
+    divInt();
+    divLong();
+  }
+
+  private static void divInt() {
+    expectEquals(0, $noinline$IntDivBy18(0));
+    expectEquals(0, $noinline$IntDivBy18(1));
+    expectEquals(0, $noinline$IntDivBy18(-1));
+    expectEquals(1, $noinline$IntDivBy18(18));
+    expectEquals(-1, $noinline$IntDivBy18(-18));
+    expectEquals(3, $noinline$IntDivBy18(65));
+    expectEquals(-3, $noinline$IntDivBy18(-65));
+
+    expectEquals(0, $noinline$IntDivByMinus18(0));
+    expectEquals(0, $noinline$IntDivByMinus18(1));
+    expectEquals(0, $noinline$IntDivByMinus18(-1));
+    expectEquals(-1, $noinline$IntDivByMinus18(18));
+    expectEquals(1, $noinline$IntDivByMinus18(-18));
+    expectEquals(-3, $noinline$IntDivByMinus18(65));
+    expectEquals(3, $noinline$IntDivByMinus18(-65));
+
+    expectEquals(0, $noinline$IntDivBy7(0));
+    expectEquals(0, $noinline$IntDivBy7(1));
+    expectEquals(0, $noinline$IntDivBy7(-1));
+    expectEquals(1, $noinline$IntDivBy7(7));
+    expectEquals(-1, $noinline$IntDivBy7(-7));
+    expectEquals(3, $noinline$IntDivBy7(22));
+    expectEquals(-3, $noinline$IntDivBy7(-22));
+
+    expectEquals(0, $noinline$IntDivByMinus7(0));
+    expectEquals(0, $noinline$IntDivByMinus7(1));
+    expectEquals(0, $noinline$IntDivByMinus7(-1));
+    expectEquals(-1, $noinline$IntDivByMinus7(7));
+    expectEquals(1, $noinline$IntDivByMinus7(-7));
+    expectEquals(-3, $noinline$IntDivByMinus7(22));
+    expectEquals(3, $noinline$IntDivByMinus7(-22));
+
+    expectEquals(0, $noinline$IntDivBy6(0));
+    expectEquals(0, $noinline$IntDivBy6(1));
+    expectEquals(0, $noinline$IntDivBy6(-1));
+    expectEquals(1, $noinline$IntDivBy6(6));
+    expectEquals(-1, $noinline$IntDivBy6(-6));
+    expectEquals(3, $noinline$IntDivBy6(19));
+    expectEquals(-3, $noinline$IntDivBy6(-19));
+
+    expectEquals(0, $noinline$IntDivByMinus6(0));
+    expectEquals(0, $noinline$IntDivByMinus6(1));
+    expectEquals(0, $noinline$IntDivByMinus6(-1));
+    expectEquals(-1, $noinline$IntDivByMinus6(6));
+    expectEquals(1, $noinline$IntDivByMinus6(-6));
+    expectEquals(-3, $noinline$IntDivByMinus6(19));
+    expectEquals(3, $noinline$IntDivByMinus6(-19));
+  }
+
+  // A test case to check that 'lsr' and 'asr' are combined into one 'asr'.
+  // For divisor 18 seen in an MP3 decoding workload there is no need
+  // to correct the result of get_high(dividend * magic). So there are no
+  // instructions between 'lsr' and 'asr'. In such a case they can be combined
+  // into one 'asr'.
+  //
+  /// CHECK-START-ARM64: int DivTest.$noinline$IntDivBy18(int) disassembly (after)
+  /// CHECK:                 asr x{{\d+}}, x{{\d+}}, #34
+  /// CHECK-NEXT:            add w{{\d+}}, w{{\d+}}, w{{\d+}}, lsr #31
+  private static int $noinline$IntDivBy18(int v) {
+    int r = v / 18;
+    return r;
+  }
+
+  // A test case to check that 'lsr' and 'asr' are combined into one 'asr'.
+  // Divisor -18 has the same property as divisor 18: no need to correct the
+  // result of get_high(dividend * magic). So there are no
+  // instructions between 'lsr' and 'asr'. In such a case they can be combined
+  // into one 'asr'.
+  //
+  /// CHECK-START-ARM64: int DivTest.$noinline$IntDivByMinus18(int) disassembly (after)
+  /// CHECK:                 asr x{{\d+}}, x{{\d+}}, #34
+  /// CHECK-NEXT:            add w{{\d+}}, w{{\d+}}, w{{\d+}}, lsr #31
+  private static int $noinline$IntDivByMinus18(int v) {
+    int r = v / -18;
+    return r;
+  }
+
+  // A test case to check that 'lsr' and 'asr' are not combined into one 'asr'.
+  // For divisor 7 seen in the core library the result of get_high(dividend * magic)
+  // must be corrected by the 'add' instruction which is between 'lsr' and 'asr'
+  // instructions. In such a case they cannot be combined into one 'asr'.
+  //
+  /// CHECK-START-ARM64: int DivTest.$noinline$IntDivBy7(int) disassembly (after)
+  /// CHECK:                 lsr x{{\d+}}, x{{\d+}}, #32
+  /// CHECK-NEXT:            add w{{\d+}}, w{{\d+}}, w{{\d+}}
+  /// CHECK-NEXT:            asr w{{\d+}}, w{{\d+}}, #2
+  /// CHECK-NEXT:            add w{{\d+}}, w{{\d+}}, w{{\d+}}, lsr #31
+  private static int $noinline$IntDivBy7(int v) {
+    int r = v / 7;
+    return r;
+  }
+
+  // A test case to check that 'lsr' and 'asr' are not combined into one 'asr'.
+  // Divisor -7 has the same property as divisor 7: the result of get_high(dividend * magic)
+  // must be corrected. In this case it is a 'sub' instruction which is between 'lsr' and 'asr'
+  // instructions. So they cannot be combined into one 'asr'.
+  //
+  /// CHECK-START-ARM64: int DivTest.$noinline$IntDivByMinus7(int) disassembly (after)
+  /// CHECK:                 lsr x{{\d+}}, x{{\d+}}, #32
+  /// CHECK-NEXT:            sub w{{\d+}}, w{{\d+}}, w{{\d+}}
+  /// CHECK-NEXT:            asr w{{\d+}}, w{{\d+}}, #2
+  /// CHECK-NEXT:            add w{{\d+}}, w{{\d+}}, w{{\d+}}, lsr #31
+  private static int $noinline$IntDivByMinus7(int v) {
+    int r = v / -7;
+    return r;
+  }
+
+  // A test case to check that 'asr' is used to get the high 32 bits of the result of
+  // 'dividend * magic'.
+  // For divisor 6 seen in the core library there is no need to correct the result of
+  // get_high(dividend * magic). Also there is no 'asr' before the final 'add' instruction
+  // which uses only the high 32 bits of the result. In such a case 'asr' getting the high
+  // 32 bits can be used as well.
+  //
+  /// CHECK-START-ARM64: int DivTest.$noinline$IntDivBy6(int) disassembly (after)
+  /// CHECK:                 asr x{{\d+}}, x{{\d+}}, #32
+  /// CHECK-NEXT:            add w{{\d+}}, w{{\d+}}, w{{\d+}}, lsr #31
+  private static int $noinline$IntDivBy6(int v) {
+    int r = v / 6;
+    return r;
+  }
+
+  // A test case to check that 'asr' is used to get the high 32 bits of the result of
+  // 'dividend * magic'.
+  // Divisor -6 has the same property as divisor 6: no need to correct the result of
+  // get_high(dividend * magic) and no 'asr' before the final 'add' instruction
+  // which uses only the high 32 bits of the result. In such a case 'asr' getting the high
+  // 32 bits can be used as well.
+  //
+  /// CHECK-START-ARM64: int DivTest.$noinline$IntDivByMinus6(int) disassembly (after)
+  /// CHECK:                 asr x{{\d+}}, x{{\d+}}, #32
+  /// CHECK-NEXT:            add w{{\d+}}, w{{\d+}}, w{{\d+}}, lsr #31
+  private static int $noinline$IntDivByMinus6(int v) {
+    int r = v / -6;
+    return r;
+  }
+
+  private static void divLong() {
+    expectEquals(0L, $noinline$LongDivBy18(0L));
+    expectEquals(0L, $noinline$LongDivBy18(1L));
+    expectEquals(0L, $noinline$LongDivBy18(-1L));
+    expectEquals(1L, $noinline$LongDivBy18(18L));
+    expectEquals(-1L, $noinline$LongDivBy18(-18L));
+    expectEquals(3L, $noinline$LongDivBy18(65L));
+    expectEquals(-3L, $noinline$LongDivBy18(-65L));
+
+    expectEquals(0L, $noinline$LongDivByMinus18(0L));
+    expectEquals(0L, $noinline$LongDivByMinus18(1L));
+    expectEquals(0L, $noinline$LongDivByMinus18(-1L));
+    expectEquals(-1L, $noinline$LongDivByMinus18(18L));
+    expectEquals(1L, $noinline$LongDivByMinus18(-18L));
+    expectEquals(-3L, $noinline$LongDivByMinus18(65L));
+    expectEquals(3L, $noinline$LongDivByMinus18(-65L));
+
+    expectEquals(0L, $noinline$LongDivBy7(0L));
+    expectEquals(0L, $noinline$LongDivBy7(1L));
+    expectEquals(0L, $noinline$LongDivBy7(-1L));
+    expectEquals(1L, $noinline$LongDivBy7(7L));
+    expectEquals(-1L, $noinline$LongDivBy7(-7L));
+    expectEquals(3L, $noinline$LongDivBy7(22L));
+    expectEquals(-3L, $noinline$LongDivBy7(-22L));
+
+    expectEquals(0L, $noinline$LongDivByMinus7(0L));
+    expectEquals(0L, $noinline$LongDivByMinus7(1L));
+    expectEquals(0L, $noinline$LongDivByMinus7(-1L));
+    expectEquals(-1L, $noinline$LongDivByMinus7(7L));
+    expectEquals(1L, $noinline$LongDivByMinus7(-7L));
+    expectEquals(-3L, $noinline$LongDivByMinus7(22L));
+    expectEquals(3L, $noinline$LongDivByMinus7(-22L));
+
+    expectEquals(0L, $noinline$LongDivBy6(0L));
+    expectEquals(0L, $noinline$LongDivBy6(1L));
+    expectEquals(0L, $noinline$LongDivBy6(-1L));
+    expectEquals(1L, $noinline$LongDivBy6(6L));
+    expectEquals(-1L, $noinline$LongDivBy6(-6L));
+    expectEquals(3L, $noinline$LongDivBy6(19L));
+    expectEquals(-3L, $noinline$LongDivBy6(-19L));
+
+    expectEquals(0L, $noinline$LongDivByMinus6(0L));
+    expectEquals(0L, $noinline$LongDivByMinus6(1L));
+    expectEquals(0L, $noinline$LongDivByMinus6(-1L));
+    expectEquals(-1L, $noinline$LongDivByMinus6(6L));
+    expectEquals(1L, $noinline$LongDivByMinus6(-6L));
+    expectEquals(-3L, $noinline$LongDivByMinus6(19L));
+    expectEquals(3L, $noinline$LongDivByMinus6(-19L));
+  }
+
+  // Test cases for Int64 HDiv/HRem to check that optimizations implemented for Int32 are not
+  // used for Int64. The same divisors 18, -18, 7, -7, 6 and -6 are used.
+
+  /// CHECK-START-ARM64: long DivTest.$noinline$LongDivBy18(long) disassembly (after)
+  /// CHECK:                 smulh x{{\d+}}, x{{\d+}}, x{{\d+}}
+  /// CHECK-NEXT:            add x{{\d+}}, x{{\d+}}, x{{\d+}}, lsr #63
+  private static long $noinline$LongDivBy18(long v) {
+    long r = v / 18L;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: long DivTest.$noinline$LongDivByMinus18(long) disassembly (after)
+  /// CHECK:                 smulh x{{\d+}}, x{{\d+}}, x{{\d+}}
+  /// CHECK-NEXT:            add x{{\d+}}, x{{\d+}}, x{{\d+}}, lsr #63
+  private static long $noinline$LongDivByMinus18(long v) {
+    long r = v / -18L;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: long DivTest.$noinline$LongDivBy7(long) disassembly (after)
+  /// CHECK:                 smulh x{{\d+}}, x{{\d+}}, x{{\d+}}
+  /// CHECK-NEXT:            asr x{{\d+}}, x{{\d+}}, #1
+  /// CHECK-NEXT:            add x{{\d+}}, x{{\d+}}, x{{\d+}}, lsr #63
+  private static long $noinline$LongDivBy7(long v) {
+    long r = v / 7L;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: long DivTest.$noinline$LongDivByMinus7(long) disassembly (after)
+  /// CHECK:                 smulh x{{\d+}}, x{{\d+}}, x{{\d+}}
+  /// CHECK-NEXT:            asr x{{\d+}}, x{{\d+}}, #1
+  /// CHECK-NEXT:            add x{{\d+}}, x{{\d+}}, x{{\d+}}, lsr #63
+  private static long $noinline$LongDivByMinus7(long v) {
+    long r = v / -7L;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: long DivTest.$noinline$LongDivBy6(long) disassembly (after)
+  /// CHECK:                 smulh x{{\d+}}, x{{\d+}}, x{{\d+}}
+  /// CHECK-NEXT:            add x{{\d+}}, x{{\d+}}, x{{\d+}}, lsr #63
+  private static long $noinline$LongDivBy6(long v) {
+    long r = v / 6L;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: long DivTest.$noinline$LongDivByMinus6(long) disassembly (after)
+  /// CHECK:                 smulh x{{\d+}}, x{{\d+}}, x{{\d+}}
+  /// CHECK-NEXT:            add x{{\d+}}, x{{\d+}}, x{{\d+}}, lsr #63
+  private static long $noinline$LongDivByMinus6(long v) {
+    long r = v / -6L;
+    return r;
+  }
+}
diff --git a/test/411-checker-hdiv-hrem-const/src/Main.java b/test/411-checker-hdiv-hrem-const/src/Main.java
new file mode 100644
index 0000000..4b34bf1
--- /dev/null
+++ b/test/411-checker-hdiv-hrem-const/src/Main.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+    public static void main(String args[]) {
+        DivTest.main();
+        RemTest.main();
+    }
+}
diff --git a/test/411-checker-hdiv-hrem-const/src/RemTest.java b/test/411-checker-hdiv-hrem-const/src/RemTest.java
new file mode 100644
index 0000000..bcb1834
--- /dev/null
+++ b/test/411-checker-hdiv-hrem-const/src/RemTest.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class RemTest {
+  private static void expectEquals(int expected, int result) {
+    if (expected != result) {
+      throw new Error("Expected: " + expected + ", found: " + result);
+    }
+  }
+
+  private static void expectEquals(long expected, long result) {
+    if (expected != result) {
+      throw new Error("Expected: " + expected + ", found: " + result);
+    }
+  }
+
+  public static void main() {
+    remInt();
+    remLong();
+  }
+
+  private static void remInt() {
+    expectEquals(0, $noinline$IntRemBy18(0));
+    expectEquals(1, $noinline$IntRemBy18(1));
+    expectEquals(-1, $noinline$IntRemBy18(-1));
+    expectEquals(0, $noinline$IntRemBy18(18));
+    expectEquals(0, $noinline$IntRemBy18(-18));
+    expectEquals(11, $noinline$IntRemBy18(65));
+    expectEquals(-11, $noinline$IntRemBy18(-65));
+
+    expectEquals(0, $noinline$IntRemByMinus18(0));
+    expectEquals(1, $noinline$IntRemByMinus18(1));
+    expectEquals(-1, $noinline$IntRemByMinus18(-1));
+    expectEquals(0, $noinline$IntRemByMinus18(18));
+    expectEquals(0, $noinline$IntRemByMinus18(-18));
+    expectEquals(11, $noinline$IntRemByMinus18(65));
+    expectEquals(-11, $noinline$IntRemByMinus18(-65));
+
+    expectEquals(0, $noinline$IntRemBy7(0));
+    expectEquals(1, $noinline$IntRemBy7(1));
+    expectEquals(-1, $noinline$IntRemBy7(-1));
+    expectEquals(0, $noinline$IntRemBy7(7));
+    expectEquals(0, $noinline$IntRemBy7(-7));
+    expectEquals(1, $noinline$IntRemBy7(22));
+    expectEquals(-1, $noinline$IntRemBy7(-22));
+
+    expectEquals(0, $noinline$IntRemByMinus7(0));
+    expectEquals(1, $noinline$IntRemByMinus7(1));
+    expectEquals(-1, $noinline$IntRemByMinus7(-1));
+    expectEquals(0, $noinline$IntRemByMinus7(7));
+    expectEquals(0, $noinline$IntRemByMinus7(-7));
+    expectEquals(1, $noinline$IntRemByMinus7(22));
+    expectEquals(-1, $noinline$IntRemByMinus7(-22));
+
+    expectEquals(0, $noinline$IntRemBy6(0));
+    expectEquals(1, $noinline$IntRemBy6(1));
+    expectEquals(-1, $noinline$IntRemBy6(-1));
+    expectEquals(0, $noinline$IntRemBy6(6));
+    expectEquals(0, $noinline$IntRemBy6(-6));
+    expectEquals(1, $noinline$IntRemBy6(19));
+    expectEquals(-1, $noinline$IntRemBy6(-19));
+
+    expectEquals(0, $noinline$IntRemByMinus6(0));
+    expectEquals(1, $noinline$IntRemByMinus6(1));
+    expectEquals(-1, $noinline$IntRemByMinus6(-1));
+    expectEquals(0, $noinline$IntRemByMinus6(6));
+    expectEquals(0, $noinline$IntRemByMinus6(-6));
+    expectEquals(1, $noinline$IntRemByMinus6(19));
+    expectEquals(-1, $noinline$IntRemByMinus6(-19));
+  }
+
+  // A test case to check that 'lsr' and 'asr' are combined into one 'asr'.
+  // For divisor 18 seen in an MP3 decoding workload there is no need
+  // to correct the result of get_high(dividend * magic). So there are no
+  // instructions between 'lsr' and 'asr'. In such a case they can be combined
+  // into one 'asr'.
+  //
+  /// CHECK-START-ARM64: int RemTest.$noinline$IntRemBy18(int) disassembly (after)
+  /// CHECK:                 asr x{{\d+}}, x{{\d+}}, #34
+  /// CHECK-NEXT:            add w{{\d+}}, w{{\d+}}, w{{\d+}}, lsr #31
+  /// CHECK-NEXT:            mov w{{\d+}}, #0x12
+  /// CHECK-NEXT:            msub w{{\d+}}, w{{\d+}}, w{{\d+}}, w{{\d+}}
+  private static int $noinline$IntRemBy18(int v) {
+    int r = v % 18;
+    return r;
+  }
+
+  // A test case to check that 'lsr' and 'asr' are combined into one 'asr'.
+  // Divisor -18 has the same property as divisor 18: no need to correct the
+  // result of get_high(dividend * magic). So there are no
+  // instructions between 'lsr' and 'asr'. In such a case they can be combined
+  // into one 'asr'.
+  //
+  /// CHECK-START-ARM64: int RemTest.$noinline$IntRemByMinus18(int) disassembly (after)
+  /// CHECK:                 asr x{{\d+}}, x{{\d+}}, #34
+  /// CHECK-NEXT:            add w{{\d+}}, w{{\d+}}, w{{\d+}}, lsr #31
+  /// CHECK-NEXT:            mov w{{\d+}}, #0xffffffee
+  /// CHECK-NEXT:            msub w{{\d+}}, w{{\d+}}, w{{\d+}}, w{{\d+}}
+  private static int $noinline$IntRemByMinus18(int v) {
+    int r = v % -18;
+    return r;
+  }
+
+  // A test case to check that 'lsr' and 'asr' are not combined into one 'asr'.
+  // For divisor 7 seen in the core library the result of get_high(dividend * magic)
+  // must be corrected by the 'add' instruction which is between 'lsr' and 'asr'
+  // instructions. In such a case they cannot be combined into one 'asr'.
+  //
+  /// CHECK-START-ARM64: int RemTest.$noinline$IntRemBy7(int) disassembly (after)
+  /// CHECK:                 lsr x{{\d+}}, x{{\d+}}, #32
+  /// CHECK-NEXT:            add w{{\d+}}, w{{\d+}}, w{{\d+}}
+  /// CHECK-NEXT:            asr w{{\d+}}, w{{\d+}}, #2
+  /// CHECK-NEXT:            add w{{\d+}}, w{{\d+}}, w{{\d+}}, lsr #31
+  /// CHECK-NEXT:            mov w{{\d+}}, #0x7
+  /// CHECK-NEXT:            msub w{{\d+}}, w{{\d+}}, w{{\d+}}, w{{\d+}}
+  private static int $noinline$IntRemBy7(int v) {
+    int r = v % 7;
+    return r;
+  }
+
+  // A test case to check that 'lsr' and 'asr' are not combined into one 'asr'.
+  // Divisor -7 has the same property as divisor 7: the result of get_high(dividend * magic)
+  // must be corrected. In this case it is a 'sub' instruction which is between 'lsr' and 'asr'
+  // instructions. So they cannot be combined into one 'asr'.
+  //
+  /// CHECK-START-ARM64: int RemTest.$noinline$IntRemByMinus7(int) disassembly (after)
+  /// CHECK:                 lsr x{{\d+}}, x{{\d+}}, #32
+  /// CHECK-NEXT:            sub w{{\d+}}, w{{\d+}}, w{{\d+}}
+  /// CHECK-NEXT:            asr w{{\d+}}, w{{\d+}}, #2
+  /// CHECK-NEXT:            add w{{\d+}}, w{{\d+}}, w{{\d+}}, lsr #31
+  /// CHECK-NEXT:            mov w{{\d+}}, #0xfffffff9
+  /// CHECK-NEXT:            msub w{{\d+}}, w{{\d+}}, w{{\d+}}, w{{\d+}}
+  private static int $noinline$IntRemByMinus7(int v) {
+    int r = v % -7;
+    return r;
+  }
+
+  // A test case to check that 'asr' is used to get the high 32 bits of the result of
+  // 'dividend * magic'.
+  // For divisor 6 seen in the core library there is no need to correct the result of
+  // get_high(dividend * magic). Also there is no 'asr' before the final 'add' instruction
+  // which uses only the high 32 bits of the result. In such a case 'asr' getting the high
+  // 32 bits can be used as well.
+  //
+  /// CHECK-START-ARM64: int RemTest.$noinline$IntRemBy6(int) disassembly (after)
+  /// CHECK:                 asr x{{\d+}}, x{{\d+}}, #32
+  /// CHECK-NEXT:            add w{{\d+}}, w{{\d+}}, w{{\d+}}, lsr #31
+  /// CHECK-NEXT:            mov w{{\d+}}, #0x6
+  /// CHECK-NEXT:            msub w{{\d+}}, w{{\d+}}, w{{\d+}}, w{{\d+}}
+  private static int $noinline$IntRemBy6(int v) {
+    int r = v % 6;
+    return r;
+  }
+
+  // A test case to check that 'asr' is used to get the high 32 bits of the result of
+  // 'dividend * magic'.
+  // Divisor -6 has the same property as divisor 6: no need to correct the result of
+  // get_high(dividend * magic) and no 'asr' before the final 'add' instruction
+  // which uses only the high 32 bits of the result. In such a case 'asr' getting the high
+  // 32 bits can be used as well.
+  //
+  /// CHECK-START-ARM64: int RemTest.$noinline$IntRemByMinus6(int) disassembly (after)
+  /// CHECK:                 asr x{{\d+}}, x{{\d+}}, #32
+  /// CHECK-NEXT:            add w{{\d+}}, w{{\d+}}, w{{\d+}}, lsr #31
+  /// CHECK-NEXT:            mov w{{\d+}}, #0xfffffffa
+  /// CHECK-NEXT:            msub w{{\d+}}, w{{\d+}}, w{{\d+}}, w{{\d+}}
+  private static int $noinline$IntRemByMinus6(int v) {
+    int r = v % -6;
+    return r;
+  }
+
+  private static void remLong() {
+    expectEquals(0L, $noinline$LongRemBy18(0L));
+    expectEquals(1L, $noinline$LongRemBy18(1L));
+    expectEquals(-1L, $noinline$LongRemBy18(-1L));
+    expectEquals(0L, $noinline$LongRemBy18(18L));
+    expectEquals(0L, $noinline$LongRemBy18(-18L));
+    expectEquals(11L, $noinline$LongRemBy18(65L));
+    expectEquals(-11L, $noinline$LongRemBy18(-65L));
+
+    expectEquals(0L, $noinline$LongRemByMinus18(0L));
+    expectEquals(1L, $noinline$LongRemByMinus18(1L));
+    expectEquals(-1L, $noinline$LongRemByMinus18(-1L));
+    expectEquals(0L, $noinline$LongRemByMinus18(18L));
+    expectEquals(0L, $noinline$LongRemByMinus18(-18L));
+    expectEquals(11L, $noinline$LongRemByMinus18(65L));
+    expectEquals(-11L, $noinline$LongRemByMinus18(-65L));
+
+    expectEquals(0L, $noinline$LongRemBy7(0L));
+    expectEquals(1L, $noinline$LongRemBy7(1L));
+    expectEquals(-1L, $noinline$LongRemBy7(-1L));
+    expectEquals(0L, $noinline$LongRemBy7(7L));
+    expectEquals(0L, $noinline$LongRemBy7(-7L));
+    expectEquals(1L, $noinline$LongRemBy7(22L));
+    expectEquals(-1L, $noinline$LongRemBy7(-22L));
+
+    expectEquals(0L, $noinline$LongRemByMinus7(0L));
+    expectEquals(1L, $noinline$LongRemByMinus7(1L));
+    expectEquals(-1L, $noinline$LongRemByMinus7(-1L));
+    expectEquals(0L, $noinline$LongRemByMinus7(7L));
+    expectEquals(0L, $noinline$LongRemByMinus7(-7L));
+    expectEquals(1L, $noinline$LongRemByMinus7(22L));
+    expectEquals(-1L, $noinline$LongRemByMinus7(-22L));
+
+    expectEquals(0L, $noinline$LongRemBy6(0L));
+    expectEquals(1L, $noinline$LongRemBy6(1L));
+    expectEquals(-1L, $noinline$LongRemBy6(-1L));
+    expectEquals(0L, $noinline$LongRemBy6(6L));
+    expectEquals(0L, $noinline$LongRemBy6(-6L));
+    expectEquals(1L, $noinline$LongRemBy6(19L));
+    expectEquals(-1L, $noinline$LongRemBy6(-19L));
+
+    expectEquals(0L, $noinline$LongRemByMinus6(0L));
+    expectEquals(1L, $noinline$LongRemByMinus6(1L));
+    expectEquals(-1L, $noinline$LongRemByMinus6(-1L));
+    expectEquals(0L, $noinline$LongRemByMinus6(6L));
+    expectEquals(0L, $noinline$LongRemByMinus6(-6L));
+    expectEquals(1L, $noinline$LongRemByMinus6(19L));
+    expectEquals(-1L, $noinline$LongRemByMinus6(-19L));
+  }
+
+  // Test cases for Int64 HDiv/HRem to check that optimizations implemented for Int32 are not
+  // used for Int64. The same divisors 18, -18, 7, -7, 6 and -6 are used.
+
+  /// CHECK-START-ARM64: long RemTest.$noinline$LongRemBy18(long) disassembly (after)
+  /// CHECK:                 smulh x{{\d+}}, x{{\d+}}, x{{\d+}}
+  /// CHECK-NEXT:            add x{{\d+}}, x{{\d+}}, x{{\d+}}, lsr #63
+  /// CHECK-NEXT:            mov x{{\d+}}, #0x12
+  /// CHECK-NEXT:            msub x{{\d+}}, x{{\d+}}, x{{\d+}}, x{{\d+}}
+  private static long $noinline$LongRemBy18(long v) {
+    long r = v % 18L;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: long RemTest.$noinline$LongRemByMinus18(long) disassembly (after)
+  /// CHECK:                 smulh x{{\d+}}, x{{\d+}}, x{{\d+}}
+  /// CHECK-NEXT:            add x{{\d+}}, x{{\d+}}, x{{\d+}}, lsr #63
+  /// CHECK-NEXT:            mov x{{\d+}}, #0xffffffffffffffee
+  /// CHECK-NEXT:            msub x{{\d+}}, x{{\d+}}, x{{\d+}}, x{{\d+}}
+  private static long $noinline$LongRemByMinus18(long v) {
+    long r = v % -18L;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: long RemTest.$noinline$LongRemBy7(long) disassembly (after)
+  /// CHECK:                 smulh x{{\d+}}, x{{\d+}}, x{{\d+}}
+  /// CHECK-NEXT:            asr x{{\d+}}, x{{\d+}}, #1
+  /// CHECK-NEXT:            add x{{\d+}}, x{{\d+}}, x{{\d+}}, lsr #63
+  /// CHECK-NEXT:            mov x{{\d+}}, #0x7
+  /// CHECK-NEXT:            msub x{{\d+}}, x{{\d+}}, x{{\d+}}, x{{\d+}}
+  private static long $noinline$LongRemBy7(long v) {
+    long r = v % 7L;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: long RemTest.$noinline$LongRemByMinus7(long) disassembly (after)
+  /// CHECK:                 smulh x{{\d+}}, x{{\d+}}, x{{\d+}}
+  /// CHECK-NEXT:            asr x{{\d+}}, x{{\d+}}, #1
+  /// CHECK-NEXT:            add x{{\d+}}, x{{\d+}}, x{{\d+}}, lsr #63
+  /// CHECK-NEXT:            mov x{{\d+}}, #0xfffffffffffffff9
+  /// CHECK-NEXT:            msub x{{\d+}}, x{{\d+}}, x{{\d+}}, x{{\d+}}
+  private static long $noinline$LongRemByMinus7(long v) {
+    long r = v % -7L;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: long RemTest.$noinline$LongRemBy6(long) disassembly (after)
+  /// CHECK:                 smulh x{{\d+}}, x{{\d+}}, x{{\d+}}
+  /// CHECK-NEXT:            add x{{\d+}}, x{{\d+}}, x{{\d+}}, lsr #63
+  /// CHECK-NEXT:            mov x{{\d+}}, #0x6
+  /// CHECK-NEXT:            msub x{{\d+}}, x{{\d+}}, x{{\d+}}, x{{\d+}}
+  private static long $noinline$LongRemBy6(long v) {
+    long r = v % 6L;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: long RemTest.$noinline$LongRemByMinus6(long) disassembly (after)
+  /// CHECK:                 smulh x{{\d+}}, x{{\d+}}, x{{\d+}}
+  /// CHECK-NEXT:            add x{{\d+}}, x{{\d+}}, x{{\d+}}, lsr #63
+  /// CHECK-NEXT:            mov x{{\d+}}, #0xfffffffffffffffa
+  /// CHECK-NEXT:            msub x{{\d+}}, x{{\d+}}, x{{\d+}}, x{{\d+}}
+  private static long $noinline$LongRemByMinus6(long v) {
+    long r = v % -6L;
+    return r;
+  }
+}