ARM64: Optimization of HRem and HDiv when a denominator is power of 2

On ARM64 when a denominator is a power of 2 fewer instructions can be
used to represent HDiv and HRem. For example, a/2 can be lowered to
add+asr; a%2 to cmp+and+csneg. Currently four instructions
are always used for the division by a power of 2 and five instructions for the
remainder.

This patch optimizes the division by 2 (lowering to two instructions),
the remainder from the division by 2 (lowering to three instructions)
and the remainder from the division by a power of 2 (lowering to four
instructions).

On Pixel 2, performance improvements, geomean of diff for a benchmark group (%),
max - the maximum seen diff of a single case in a benchmark group, higher better:
Big core:
algorithm                 0.664 (max: 1.6)
intrinsics                5.813 (max: 19.0)
micro                     4.734 (max: 22.0)

Little core:
algorithm                 2.097 (max: 5.4)
intrinsics               14.610 (max: 27.3)
micro                    12.687 (max: 35.6)

Test: 012-math, 014-math3, 411-optimizing-arith, 411-checker-hdiv-hrem-pow2
Test: test-art-host, test-art-target
Change-Id: Iaaec6dc8fc0ec5df2b2d0e8692d5dea573b8d284
diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc
index ad4b5cf..1965135 100644
--- a/compiler/optimizing/code_generator_arm64.cc
+++ b/compiler/optimizing/code_generator_arm64.cc
@@ -3351,14 +3351,19 @@
 
   Register out = OutputRegister(instruction);
   Register dividend = InputRegisterAt(instruction, 0);
+
+  if (abs_imm == 2) {
+    int bits = DataType::Size(instruction->GetResultType()) * kBitsPerByte;
+    __ Add(out, dividend, Operand(dividend, LSR, bits - 1));
+  } else {
+    UseScratchRegisterScope temps(GetVIXLAssembler());
+    Register temp = temps.AcquireSameSizeAs(out);
+    __ Add(temp, dividend, abs_imm - 1);
+    __ Cmp(dividend, 0);
+    __ Csel(out, temp, dividend, lt);
+  }
+
   int ctz_imm = CTZ(abs_imm);
-
-  UseScratchRegisterScope temps(GetVIXLAssembler());
-  Register temp = temps.AcquireSameSizeAs(out);
-
-  __ Add(temp, dividend, abs_imm - 1);
-  __ Cmp(dividend, 0);
-  __ Csel(out, temp, dividend, lt);
   if (imm > 0) {
     __ Asr(out, out, ctz_imm);
   } else {
@@ -5635,17 +5640,20 @@
 
   Register out = OutputRegister(instruction);
   Register dividend = InputRegisterAt(instruction, 0);
-  int ctz_imm = CTZ(abs_imm);
 
-  UseScratchRegisterScope temps(GetVIXLAssembler());
-  Register temp = temps.AcquireSameSizeAs(out);
+  if (abs_imm == 2) {
+    __ Cmp(dividend, 0);
+    __ And(out, dividend, 1);
+    __ Csneg(out, out, out, ge);
+  } else {
+    UseScratchRegisterScope temps(GetVIXLAssembler());
+    Register temp = temps.AcquireSameSizeAs(out);
 
-  int bits = (instruction->GetResultType() == DataType::Type::kInt32) ? 32 : 64;
-  __ Asr(temp, dividend, bits - 1);
-  __ Lsr(temp, temp, bits - ctz_imm);
-  __ Add(out, dividend, temp);
-  __ And(out, out, abs_imm - 1);
-  __ Sub(out, out, temp);
+    __ Negs(temp, dividend);
+    __ And(out, dividend, abs_imm - 1);
+    __ And(temp, temp, abs_imm - 1);
+    __ Csneg(out, out, temp, mi);
+  }
 }
 
 void InstructionCodeGeneratorARM64::GenerateIntRemForOneOrMinusOneDenom(HRem *instruction) {
diff --git a/test/411-checker-hdiv-hrem-pow2/expected.txt b/test/411-checker-hdiv-hrem-pow2/expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/411-checker-hdiv-hrem-pow2/expected.txt
diff --git a/test/411-checker-hdiv-hrem-pow2/info.txt b/test/411-checker-hdiv-hrem-pow2/info.txt
new file mode 100644
index 0000000..df1c988
--- /dev/null
+++ b/test/411-checker-hdiv-hrem-pow2/info.txt
@@ -0,0 +1,2 @@
+Test the optimization of integer division and remainder instructions when
+the denominator is power of 2 on arm64.
diff --git a/test/411-checker-hdiv-hrem-pow2/src/DivTest.java b/test/411-checker-hdiv-hrem-pow2/src/DivTest.java
new file mode 100644
index 0000000..a3882e7
--- /dev/null
+++ b/test/411-checker-hdiv-hrem-pow2/src/DivTest.java
@@ -0,0 +1,251 @@
+/*
+ * 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 DivTest {
+
+  public static <T extends Number> void expectEquals(T expected, T result) {
+    if (!expected.equals(result)) {
+      throw new Error("Expected: " + expected + ", found: " + result);
+    }
+  }
+
+  public static void main() {
+    divInt();
+    divLong();
+  }
+
+  private static void divInt() {
+    expectEquals(0, $noinline$IntDivBy2(0));
+    expectEquals(0, $noinline$IntDivBy2(1));
+    expectEquals(0, $noinline$IntDivBy2(-1));
+    expectEquals(1, $noinline$IntDivBy2(2));
+    expectEquals(-1, $noinline$IntDivBy2(-2));
+    expectEquals(1, $noinline$IntDivBy2(3));
+    expectEquals(-1, $noinline$IntDivBy2(-3));
+    expectEquals(3, $noinline$IntDivBy2(7));
+    expectEquals(-3, $noinline$IntDivBy2(-7));
+    expectEquals(4, $noinline$IntDivBy2(8));
+    expectEquals(-4, $noinline$IntDivBy2(-8));
+    expectEquals(7, $noinline$IntDivBy2(0x0f));
+    expectEquals(0x007f, $noinline$IntDivBy2(0x00ff));
+    expectEquals(0x07ff, $noinline$IntDivBy2(0x0fff));
+    expectEquals(0x007fff, $noinline$IntDivBy2(0x00ffff));
+    expectEquals(0x3fffffff, $noinline$IntDivBy2(Integer.MAX_VALUE));
+    expectEquals(0xc0000000, $noinline$IntDivBy2(Integer.MIN_VALUE));
+
+    expectEquals(0, $noinline$IntDivByMinus2(0));
+    expectEquals(0, $noinline$IntDivByMinus2(1));
+    expectEquals(0, $noinline$IntDivByMinus2(-1));
+    expectEquals(-1, $noinline$IntDivByMinus2(2));
+    expectEquals(1, $noinline$IntDivByMinus2(-2));
+    expectEquals(-1, $noinline$IntDivByMinus2(3));
+    expectEquals(1, $noinline$IntDivByMinus2(-3));
+    expectEquals(-3, $noinline$IntDivByMinus2(7));
+    expectEquals(3, $noinline$IntDivByMinus2(-7));
+    expectEquals(-4, $noinline$IntDivByMinus2(8));
+    expectEquals(4, $noinline$IntDivByMinus2(-8));
+    expectEquals(-7, $noinline$IntDivByMinus2(0x0f));
+    expectEquals(0xffffff81, $noinline$IntDivByMinus2(0x00ff));
+    expectEquals(0xfffff801, $noinline$IntDivByMinus2(0x0fff));
+    expectEquals(0xffff8001, $noinline$IntDivByMinus2(0x00ffff));
+    expectEquals(0xc0000001, $noinline$IntDivByMinus2(Integer.MAX_VALUE));
+    expectEquals(0x40000000, $noinline$IntDivByMinus2(Integer.MIN_VALUE));
+
+    expectEquals(0, $noinline$IntDivBy16(0));
+    expectEquals(1, $noinline$IntDivBy16(16));
+    expectEquals(-1, $noinline$IntDivBy16(-16));
+    expectEquals(2, $noinline$IntDivBy16(33));
+    expectEquals(0x000f, $noinline$IntDivBy16(0x00ff));
+    expectEquals(0x00ff, $noinline$IntDivBy16(0x0fff));
+    expectEquals(0x000fff, $noinline$IntDivBy16(0x00ffff));
+    expectEquals(0x07ffffff, $noinline$IntDivBy16(Integer.MAX_VALUE));
+    expectEquals(0xf8000000, $noinline$IntDivBy16(Integer.MIN_VALUE));
+
+    expectEquals(0, $noinline$IntDivByMinus16(0));
+    expectEquals(-1, $noinline$IntDivByMinus16(16));
+    expectEquals(1, $noinline$IntDivByMinus16(-16));
+    expectEquals(-2, $noinline$IntDivByMinus16(33));
+    expectEquals(0xfffffff1, $noinline$IntDivByMinus16(0x00ff));
+    expectEquals(0xffffff01, $noinline$IntDivByMinus16(0x0fff));
+    expectEquals(0xfffff001, $noinline$IntDivByMinus16(0x00ffff));
+    expectEquals(0xf8000001, $noinline$IntDivByMinus16(Integer.MAX_VALUE));
+    expectEquals(0x08000000, $noinline$IntDivByMinus16(Integer.MIN_VALUE));
+
+    expectEquals(0, $noinline$IntDivByIntMin(0));
+    expectEquals(0, $noinline$IntDivByIntMin(1));
+    expectEquals(0, $noinline$IntDivByIntMin(-1));
+    expectEquals(1, $noinline$IntDivByIntMin(Integer.MIN_VALUE));
+    expectEquals(0, $noinline$IntDivByIntMin(Integer.MAX_VALUE));
+  }
+
+  /// CHECK-START-ARM64: java.lang.Integer DivTest.$noinline$IntDivBy2(int) disassembly (after)
+  /// CHECK:                 add w{{\d+}}, w{{\d+}}, w{{\d+}}, lsr #31
+  /// CHECK:                 asr w{{\d+}}, w{{\d+}}, #1
+  private static Integer $noinline$IntDivBy2(int v) {
+    int r = v / 2;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: java.lang.Integer DivTest.$noinline$IntDivByMinus2(int) disassembly (after)
+  /// CHECK:                 add w{{\d+}}, w{{\d+}}, w{{\d+}}, lsr #31
+  /// CHECK:                 neg w{{\d+}}, w{{\d+}}, asr #1
+  private static Integer $noinline$IntDivByMinus2(int v) {
+    int r = v / -2;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: java.lang.Integer DivTest.$noinline$IntDivBy16(int) disassembly (after)
+  /// CHECK:                add w{{\d+}}, w{{\d+}}, #0xf
+  /// CHECK:                cmp w{{\d+}}, #0x0
+  /// CHECK:                csel w{{\d+}}, w{{\d+}}, w{{\d+}}, lt
+  /// CHECK:                asr w{{\d+}}, w{{\d+}}, #4
+  private static Integer $noinline$IntDivBy16(int v) {
+    int r = v / 16;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: java.lang.Integer DivTest.$noinline$IntDivByMinus16(int) disassembly (after)
+  /// CHECK:                add w{{\d+}}, w{{\d+}}, #0xf
+  /// CHECK:                cmp w{{\d+}}, #0x0
+  /// CHECK:                csel w{{\d+}}, w{{\d+}}, w{{\d+}}, lt
+  /// CHECK:                neg w{{\d+}}, w{{\d+}}, asr #4
+  private static Integer $noinline$IntDivByMinus16(int v) {
+    int r = v / -16;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: java.lang.Integer DivTest.$noinline$IntDivByIntMin(int) disassembly (after)
+  /// CHECK:                mov w{{\d+}}, #0x7fffffff
+  /// CHECK:                add w{{\d+}}, w{{\d+}}, w{{\d+}}
+  /// CHECK:                cmp w{{\d+}}, #0x0
+  /// CHECK:                csel w{{\d+}}, w{{\d+}}, w{{\d+}}, lt
+  /// CHECK:                neg w{{\d+}}, w{{\d+}}, asr #31
+  private static Integer $noinline$IntDivByIntMin(int v) {
+    int r = v / Integer.MIN_VALUE;
+    return r;
+  }
+
+  private static void divLong() {
+    expectEquals(0L, $noinline$LongDivBy2(0L));
+    expectEquals(0L, $noinline$LongDivBy2(1L));
+    expectEquals(0L, $noinline$LongDivBy2(-1L));
+    expectEquals(1L, $noinline$LongDivBy2(2L));
+    expectEquals(-1L, $noinline$LongDivBy2(-2L));
+    expectEquals(1L, $noinline$LongDivBy2(3L));
+    expectEquals(-1L, $noinline$LongDivBy2(-3L));
+    expectEquals(3L, $noinline$LongDivBy2(7L));
+    expectEquals(-3L, $noinline$LongDivBy2(-7L));
+    expectEquals(4L, $noinline$LongDivBy2(8L));
+    expectEquals(-4L, $noinline$LongDivBy2(-8L));
+    expectEquals(7L, $noinline$LongDivBy2(0x0fL));
+    expectEquals(0x007fL, $noinline$LongDivBy2(0x00ffL));
+    expectEquals(0x07ffL, $noinline$LongDivBy2(0x0fffL));
+    expectEquals(0x007fffL, $noinline$LongDivBy2(0x00ffffL));
+    expectEquals(0x3fffffffffffffffL, $noinline$LongDivBy2(Long.MAX_VALUE));
+    expectEquals(0xc000000000000000L, $noinline$LongDivBy2(Long.MIN_VALUE));
+
+    expectEquals(0L, $noinline$LongDivByMinus2(0));
+    expectEquals(0L, $noinline$LongDivByMinus2(1L));
+    expectEquals(0L, $noinline$LongDivByMinus2(-1L));
+    expectEquals(-1L, $noinline$LongDivByMinus2(2L));
+    expectEquals(1L, $noinline$LongDivByMinus2(-2L));
+    expectEquals(-1L, $noinline$LongDivByMinus2(3L));
+    expectEquals(1L, $noinline$LongDivByMinus2(-3L));
+    expectEquals(-3L, $noinline$LongDivByMinus2(7L));
+    expectEquals(3L, $noinline$LongDivByMinus2(-7L));
+    expectEquals(-4L, $noinline$LongDivByMinus2(8L));
+    expectEquals(4L, $noinline$LongDivByMinus2(-8L));
+    expectEquals(-7L, $noinline$LongDivByMinus2(0x0fL));
+    expectEquals(0xffffffffffffff81L, $noinline$LongDivByMinus2(0x00ffL));
+    expectEquals(0xfffffffffffff801L, $noinline$LongDivByMinus2(0x0fffL));
+    expectEquals(0xffffffffffff8001L, $noinline$LongDivByMinus2(0x00ffffL));
+    expectEquals(0xc000000000000001L, $noinline$LongDivByMinus2(Long.MAX_VALUE));
+    expectEquals(0x4000000000000000L, $noinline$LongDivByMinus2(Long.MIN_VALUE));
+
+    expectEquals(0L, $noinline$LongDivBy16(0));
+    expectEquals(1L, $noinline$LongDivBy16(16L));
+    expectEquals(-1L, $noinline$LongDivBy16(-16L));
+    expectEquals(2L, $noinline$LongDivBy16(33L));
+    expectEquals(0x000fL, $noinline$LongDivBy16(0x00ffL));
+    expectEquals(0x00ffL, $noinline$LongDivBy16(0x0fffL));
+    expectEquals(0x000fffL, $noinline$LongDivBy16(0x00ffffL));
+    expectEquals(0x07ffffffffffffffL, $noinline$LongDivBy16(Long.MAX_VALUE));
+    expectEquals(0xf800000000000000L, $noinline$LongDivBy16(Long.MIN_VALUE));
+
+    expectEquals(0L, $noinline$LongDivByMinus16(0));
+    expectEquals(-1L, $noinline$LongDivByMinus16(16L));
+    expectEquals(1L, $noinline$LongDivByMinus16(-16L));
+    expectEquals(-2L, $noinline$LongDivByMinus16(33L));
+    expectEquals(0xfffffffffffffff1L, $noinline$LongDivByMinus16(0x00ffL));
+    expectEquals(0xffffffffffffff01L, $noinline$LongDivByMinus16(0x0fffL));
+    expectEquals(0xfffffffffffff001L, $noinline$LongDivByMinus16(0x00ffffL));
+    expectEquals(0xf800000000000001L, $noinline$LongDivByMinus16(Long.MAX_VALUE));
+    expectEquals(0x0800000000000000L, $noinline$LongDivByMinus16(Long.MIN_VALUE));
+
+    expectEquals(0L, $noinline$LongDivByLongMin(0));
+    expectEquals(0L, $noinline$LongDivByLongMin(1));
+    expectEquals(0L, $noinline$LongDivByLongMin(-1));
+    expectEquals(1L, $noinline$LongDivByLongMin(Long.MIN_VALUE));
+    expectEquals(0L, $noinline$LongDivByLongMin(Long.MAX_VALUE));
+  }
+
+  /// CHECK-START-ARM64: java.lang.Long DivTest.$noinline$LongDivBy2(long) disassembly (after)
+  /// CHECK:                 add x{{\d+}}, x{{\d+}}, x{{\d+}}, lsr #63
+  /// CHECK:                 asr x{{\d+}}, x{{\d+}}, #1
+  private static Long $noinline$LongDivBy2(long v) {
+    long r = v / 2;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: java.lang.Long DivTest.$noinline$LongDivByMinus2(long) disassembly (after)
+  /// CHECK:                 add x{{\d+}}, x{{\d+}}, x{{\d+}}, lsr #63
+  /// CHECK:                 neg x{{\d+}}, x{{\d+}}, asr #1
+  private static Long $noinline$LongDivByMinus2(long v) {
+    long r = v / -2;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: java.lang.Long DivTest.$noinline$LongDivBy16(long) disassembly (after)
+  /// CHECK:                add x{{\d+}}, x{{\d+}}, #0xf
+  /// CHECK:                cmp x{{\d+}}, #0x0
+  /// CHECK:                csel x{{\d+}}, x{{\d+}}, x{{\d+}}, lt
+  /// CHECK:                asr x{{\d+}}, x{{\d+}}, #4
+  private static Long $noinline$LongDivBy16(long v) {
+    long r = v / 16;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: java.lang.Long DivTest.$noinline$LongDivByMinus16(long) disassembly (after)
+  /// CHECK:                add x{{\d+}}, x{{\d+}}, #0xf
+  /// CHECK:                cmp x{{\d+}}, #0x0
+  /// CHECK:                csel x{{\d+}}, x{{\d+}}, x{{\d+}}, lt
+  /// CHECK:                neg x{{\d+}}, x{{\d+}}, asr #4
+  private static Long $noinline$LongDivByMinus16(long v) {
+    long r = v / -16;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: java.lang.Long DivTest.$noinline$LongDivByLongMin(long) disassembly (after)
+  /// CHECK:                mov x{{\d+}}, #0x7fffffffffffffff
+  /// CHECK:                add x{{\d+}}, x{{\d+}}, x{{\d+}}
+  /// CHECK:                cmp x{{\d+}}, #0x0
+  /// CHECK:                csel x{{\d+}}, x{{\d+}}, x{{\d+}}, lt
+  /// CHECK:                neg x{{\d+}}, x{{\d+}}, asr #63
+  private static Long $noinline$LongDivByLongMin(long v) {
+    long r = v / Long.MIN_VALUE;
+    return r;
+  }
+}
diff --git a/test/411-checker-hdiv-hrem-pow2/src/Main.java b/test/411-checker-hdiv-hrem-pow2/src/Main.java
new file mode 100644
index 0000000..4b34bf1
--- /dev/null
+++ b/test/411-checker-hdiv-hrem-pow2/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-pow2/src/RemTest.java b/test/411-checker-hdiv-hrem-pow2/src/RemTest.java
new file mode 100644
index 0000000..72725c1
--- /dev/null
+++ b/test/411-checker-hdiv-hrem-pow2/src/RemTest.java
@@ -0,0 +1,257 @@
+/*
+ * 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 RemTest {
+
+  public static <T extends Number> void expectEquals(T expected, T result) {
+    if (!expected.equals(result)) {
+      throw new Error("Expected: " + expected + ", found: " + result);
+    }
+  }
+
+  public static void main() {
+    remInt();
+    remLong();
+  }
+
+  private static void remInt() {
+    expectEquals(0, $noinline$IntMod2(0));
+    expectEquals(1, $noinline$IntMod2(1));
+    expectEquals(-1, $noinline$IntMod2(-1));
+    expectEquals(0, $noinline$IntMod2(2));
+    expectEquals(0, $noinline$IntMod2(-2));
+    expectEquals(1, $noinline$IntMod2(3));
+    expectEquals(-1, $noinline$IntMod2(-3));
+    expectEquals(1, $noinline$IntMod2(0x0f));
+    expectEquals(1, $noinline$IntMod2(0x00ff));
+    expectEquals(1, $noinline$IntMod2(0x00ffff));
+    expectEquals(1, $noinline$IntMod2(Integer.MAX_VALUE));
+    expectEquals(0, $noinline$IntMod2(Integer.MIN_VALUE));
+
+    expectEquals(0, $noinline$IntModMinus2(0));
+    expectEquals(1, $noinline$IntModMinus2(1));
+    expectEquals(-1, $noinline$IntModMinus2(-1));
+    expectEquals(0, $noinline$IntModMinus2(2));
+    expectEquals(0, $noinline$IntModMinus2(-2));
+    expectEquals(1, $noinline$IntModMinus2(3));
+    expectEquals(-1, $noinline$IntModMinus2(-3));
+    expectEquals(1, $noinline$IntModMinus2(0x0f));
+    expectEquals(1, $noinline$IntModMinus2(0x00ff));
+    expectEquals(1, $noinline$IntModMinus2(0x00ffff));
+    expectEquals(1, $noinline$IntModMinus2(Integer.MAX_VALUE));
+    expectEquals(0, $noinline$IntModMinus2(Integer.MIN_VALUE));
+
+    expectEquals(0, $noinline$IntMod16(0));
+    expectEquals(1, $noinline$IntMod16(1));
+    expectEquals(1, $noinline$IntMod16(17));
+    expectEquals(-1, $noinline$IntMod16(-1));
+    expectEquals(0, $noinline$IntMod16(32));
+    expectEquals(0, $noinline$IntMod16(-32));
+    expectEquals(0x0f, $noinline$IntMod16(0x0f));
+    expectEquals(0x0f, $noinline$IntMod16(0x00ff));
+    expectEquals(0x0f, $noinline$IntMod16(0x00ffff));
+    expectEquals(15, $noinline$IntMod16(Integer.MAX_VALUE));
+    expectEquals(0, $noinline$IntMod16(Integer.MIN_VALUE));
+
+    expectEquals(0, $noinline$IntModMinus16(0));
+    expectEquals(1, $noinline$IntModMinus16(1));
+    expectEquals(1, $noinline$IntModMinus16(17));
+    expectEquals(-1, $noinline$IntModMinus16(-1));
+    expectEquals(0, $noinline$IntModMinus16(32));
+    expectEquals(0, $noinline$IntModMinus16(-32));
+    expectEquals(0x0f, $noinline$IntModMinus16(0x0f));
+    expectEquals(0x0f, $noinline$IntModMinus16(0x00ff));
+    expectEquals(0x0f, $noinline$IntModMinus16(0x00ffff));
+    expectEquals(15, $noinline$IntModMinus16(Integer.MAX_VALUE));
+    expectEquals(0, $noinline$IntModMinus16(Integer.MIN_VALUE));
+
+    expectEquals(0, $noinline$IntModIntMin(0));
+    expectEquals(1, $noinline$IntModIntMin(1));
+    expectEquals(0, $noinline$IntModIntMin(Integer.MIN_VALUE));
+    expectEquals(-1, $noinline$IntModIntMin(-1));
+    expectEquals(0x0f, $noinline$IntModIntMin(0x0f));
+    expectEquals(0x00ff, $noinline$IntModIntMin(0x00ff));
+    expectEquals(0x00ffff, $noinline$IntModIntMin(0x00ffff));
+    expectEquals(Integer.MAX_VALUE, $noinline$IntModIntMin(Integer.MAX_VALUE));
+  }
+
+  /// CHECK-START-ARM64: java.lang.Integer RemTest.$noinline$IntMod2(int) disassembly (after)
+  /// CHECK:                 cmp w{{\d+}}, #0x0
+  /// CHECK:                 and w{{\d+}}, w{{\d+}}, #0x1
+  /// CHECK:                 cneg w{{\d+}}, w{{\d+}}, lt
+  private static Integer $noinline$IntMod2(int v) {
+    int r = v % 2;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: java.lang.Integer RemTest.$noinline$IntModMinus2(int) disassembly (after)
+  /// CHECK:                 cmp w{{\d+}}, #0x0
+  /// CHECK:                 and w{{\d+}}, w{{\d+}}, #0x1
+  /// CHECK:                 cneg w{{\d+}}, w{{\d+}}, lt
+  private static Integer $noinline$IntModMinus2(int v) {
+    int r = v % -2;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: java.lang.Integer RemTest.$noinline$IntMod16(int) disassembly (after)
+  /// CHECK:                 negs w{{\d+}}, w{{\d+}}
+  /// CHECK:                 and w{{\d+}}, w{{\d+}}, #0xf
+  /// CHECK:                 and w{{\d+}}, w{{\d+}}, #0xf
+  /// CHECK:                 csneg w{{\d+}}, w{{\d+}}, mi
+  private static Integer $noinline$IntMod16(int v) {
+    int r = v % 16;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: java.lang.Integer RemTest.$noinline$IntModMinus16(int) disassembly (after)
+  /// CHECK:                 negs w{{\d+}}, w{{\d+}}
+  /// CHECK:                 and w{{\d+}}, w{{\d+}}, #0xf
+  /// CHECK:                 and w{{\d+}}, w{{\d+}}, #0xf
+  /// CHECK:                 csneg w{{\d+}}, w{{\d+}}, mi
+  private static Integer $noinline$IntModMinus16(int v) {
+    int r = v % -16;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: java.lang.Integer RemTest.$noinline$IntModIntMin(int) disassembly (after)
+  /// CHECK:                 negs w{{\d+}}, w{{\d+}}
+  /// CHECK:                 and w{{\d+}}, w{{\d+}}, #0x7fffffff
+  /// CHECK:                 and w{{\d+}}, w{{\d+}}, #0x7fffffff
+  /// CHECK:                 csneg w{{\d+}}, w{{\d+}}, mi
+  private static Integer $noinline$IntModIntMin(int v) {
+    int r = v % Integer.MIN_VALUE;
+    return r;
+  }
+
+  private static void remLong() {
+    expectEquals(0L, $noinline$LongMod2(0));
+    expectEquals(1L, $noinline$LongMod2(1));
+    expectEquals(-1L, $noinline$LongMod2(-1));
+    expectEquals(0L, $noinline$LongMod2(2));
+    expectEquals(0L, $noinline$LongMod2(-2));
+    expectEquals(1L, $noinline$LongMod2(3));
+    expectEquals(-1L, $noinline$LongMod2(-3));
+    expectEquals(1L, $noinline$LongMod2(0x0f));
+    expectEquals(1L, $noinline$LongMod2(0x00ff));
+    expectEquals(1L, $noinline$LongMod2(0x00ffff));
+    expectEquals(1L, $noinline$LongMod2(0x00ffffff));
+    expectEquals(1L, $noinline$LongMod2(0x00ffffffffL));
+    expectEquals(1L, $noinline$LongMod2(Long.MAX_VALUE));
+    expectEquals(0L, $noinline$LongMod2(Long.MIN_VALUE));
+
+    expectEquals(0L, $noinline$LongModMinus2(0));
+    expectEquals(1L, $noinline$LongModMinus2(1));
+    expectEquals(-1L, $noinline$LongModMinus2(-1));
+    expectEquals(0L, $noinline$LongModMinus2(2));
+    expectEquals(0L, $noinline$LongModMinus2(-2));
+    expectEquals(1L, $noinline$LongModMinus2(3));
+    expectEquals(-1L, $noinline$LongModMinus2(-3));
+    expectEquals(1L, $noinline$LongModMinus2(0x0f));
+    expectEquals(1L, $noinline$LongModMinus2(0x00ff));
+    expectEquals(1L, $noinline$LongModMinus2(0x00ffff));
+    expectEquals(1L, $noinline$LongModMinus2(0x00ffffff));
+    expectEquals(1L, $noinline$LongModMinus2(0x00ffffffffL));
+    expectEquals(1L, $noinline$LongModMinus2(Long.MAX_VALUE));
+    expectEquals(0L, $noinline$LongModMinus2(Long.MIN_VALUE));
+
+    expectEquals(0L, $noinline$LongMod16(0));
+    expectEquals(1L, $noinline$LongMod16(1));
+    expectEquals(1L, $noinline$LongMod16(17));
+    expectEquals(-1L, $noinline$LongMod16(-1));
+    expectEquals(0L, $noinline$LongMod16(32));
+    expectEquals(0L, $noinline$LongMod16(-32));
+    expectEquals(0x0fL, $noinline$LongMod16(0x0f));
+    expectEquals(0x0fL, $noinline$LongMod16(0x00ff));
+    expectEquals(0x0fL, $noinline$LongMod16(0x00ffff));
+    expectEquals(0x0fL, $noinline$LongMod16(0x00ffffff));
+    expectEquals(0x0fL, $noinline$LongMod16(0x00ffffffffL));
+    expectEquals(15L, $noinline$LongMod16(Long.MAX_VALUE));
+    expectEquals(0L, $noinline$LongMod16(Long.MIN_VALUE));
+
+    expectEquals(0L, $noinline$LongModMinus16(0));
+    expectEquals(1L, $noinline$LongModMinus16(1));
+    expectEquals(1L, $noinline$LongModMinus16(17));
+    expectEquals(-1L, $noinline$LongModMinus16(-1));
+    expectEquals(0L, $noinline$LongModMinus16(32));
+    expectEquals(0L, $noinline$LongModMinus16(-32));
+    expectEquals(0x0fL, $noinline$LongModMinus16(0x0f));
+    expectEquals(0x0fL, $noinline$LongModMinus16(0x00ff));
+    expectEquals(0x0fL, $noinline$LongModMinus16(0x00ffff));
+    expectEquals(0x0fL, $noinline$LongModMinus16(0x00ffffff));
+    expectEquals(0x0fL, $noinline$LongModMinus16(0x00ffffffffL));
+    expectEquals(15L, $noinline$LongModMinus16(Long.MAX_VALUE));
+    expectEquals(0L, $noinline$LongModMinus16(Long.MIN_VALUE));
+
+    expectEquals(0L, $noinline$LongModLongMin(0));
+    expectEquals(1L, $noinline$LongModLongMin(1));
+    expectEquals(0L, $noinline$LongModLongMin(Long.MIN_VALUE));
+    expectEquals(-1L, $noinline$LongModLongMin(-1));
+    expectEquals(0x0fL, $noinline$LongModLongMin(0x0f));
+    expectEquals(0x00ffL, $noinline$LongModLongMin(0x00ff));
+    expectEquals(0x00ffffL, $noinline$LongModLongMin(0x00ffff));
+    expectEquals(0x00ffffffL, $noinline$LongModLongMin(0x00ffffff));
+    expectEquals(0x00ffffffffL, $noinline$LongModLongMin(0x00ffffffffL));
+    expectEquals(Long.MAX_VALUE, $noinline$LongModLongMin(Long.MAX_VALUE));
+  }
+
+  /// CHECK-START-ARM64: java.lang.Long RemTest.$noinline$LongMod2(long) disassembly (after)
+  /// CHECK:                 cmp x{{\d+}}, #0x0
+  /// CHECK:                 and x{{\d+}}, x{{\d+}}, #0x1
+  /// CHECK:                 cneg x{{\d+}}, x{{\d+}}, lt
+  private static Long $noinline$LongMod2(long v) {
+    long r = v % 2;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: java.lang.Long RemTest.$noinline$LongModMinus2(long) disassembly (after)
+  /// CHECK:                 cmp x{{\d+}}, #0x0
+  /// CHECK:                 and x{{\d+}}, x{{\d+}}, #0x1
+  /// CHECK:                 cneg x{{\d+}}, x{{\d+}}, lt
+  private static Long $noinline$LongModMinus2(long v) {
+    long r = v % -2;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: java.lang.Long RemTest.$noinline$LongMod16(long) disassembly (after)
+  /// CHECK:                 negs x{{\d+}}, x{{\d+}}
+  /// CHECK:                 and x{{\d+}}, x{{\d+}}, #0xf
+  /// CHECK:                 and x{{\d+}}, x{{\d+}}, #0xf
+  /// CHECK:                 csneg x{{\d+}}, x{{\d+}}, mi
+  private static Long $noinline$LongMod16(long v) {
+    long r = v % 16;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: java.lang.Long RemTest.$noinline$LongModMinus16(long) disassembly (after)
+  /// CHECK:                 negs x{{\d+}}, x{{\d+}}
+  /// CHECK:                 and x{{\d+}}, x{{\d+}}, #0xf
+  /// CHECK:                 and x{{\d+}}, x{{\d+}}, #0xf
+  /// CHECK:                 csneg x{{\d+}}, x{{\d+}}, mi
+  private static Long $noinline$LongModMinus16(long v) {
+    long r = v % -16;
+    return r;
+  }
+
+  /// CHECK-START-ARM64: java.lang.Long RemTest.$noinline$LongModLongMin(long) disassembly (after)
+  /// CHECK:                 negs x{{\d+}}, x{{\d+}}
+  /// CHECK:                 and x{{\d+}}, x{{\d+}}, #0x7fffffffffffffff
+  /// CHECK:                 and x{{\d+}}, x{{\d+}}, #0x7fffffffffffffff
+  /// CHECK:                 csneg x{{\d+}}, x{{\d+}}, mi
+  private static Long $noinline$LongModLongMin(long v) {
+    long r = v % Long.MIN_VALUE;
+    return r;
+  }
+}