/*
 * 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 Main {
  private static void expectEquals(long expected, long result) {
    if (expected != result) {
      throw new Error("Expected: " + expected + ", found: " + result);
    }
  }

  private static void remInt() {
    expectEquals(1L << 32, $noinline$IntRemBy3(3));
    expectEquals((3L << 32) | 6, $noinline$IntRemBy7(27));
    expectEquals((1L << 32) | 1, $noinline$IntRemBy12(13));
    expectEquals((1L << 32) | 1, $noinline$IntRemBy12A(13));
  }

  // A test case to check:
  //  BCE detects the optimized 'v % 3' and eliminates bounds checks.
  //
  /// CHECK-START:       long Main.$noinline$IntRemBy3(int) BCE (before)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Add
  /// CHECK-NEXT:            Sub
  /// CHECK:                 BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  //
  /// CHECK-START:       long Main.$noinline$IntRemBy3(int) BCE (after)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Add
  /// CHECK-NEXT:            Sub
  /// CHECK-NOT:             BoundsCheck
  /// CHECK:                 ArrayGet
  private static long $noinline$IntRemBy3(int v) {
    int[] values = {0, 1, 2};
    if (v > 0) {
      int q = v / 3;
      int r = v % 3;
      return ((long)q << 32) | values[r];
    } else {
      return -1;
    }
  }

  // A test case to check:
  //  BCE detects the optimized 'v % 7' and eliminates bounds checks.
  //
  /// CHECK-START:       long Main.$noinline$IntRemBy7(int) BCE (before)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            Sub
  /// CHECK:                 BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  //
  /// CHECK-START:       long Main.$noinline$IntRemBy7(int) BCE (after)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            Sub
  /// CHECK-NOT:             BoundsCheck
  /// CHECK:                 ArrayGet
  private static long $noinline$IntRemBy7(int v) {
    int[] values = {0, 1, 2, 3, 4, 5, 6};
    if (v > 0) {
      int q = v / 7;
      int r = v % 7;
      return ((long)q << 32) | values[r];
    } else {
      return -1;
    }
  }

  // A test case to check:
  //  BCE detects the optimized 'v % 12' and eliminates bounds checks.
  //
  /// CHECK-START:       long Main.$noinline$IntRemBy12(int) BCE (before)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Mul
  /// CHECK-NEXT:            Sub
  /// CHECK:                 BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  //
  /// CHECK-START:       long Main.$noinline$IntRemBy12(int) BCE (after)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Mul
  /// CHECK-NEXT:            Sub
  /// CHECK-NOT:             BoundsCheck
  /// CHECK:                 ArrayGet
  private static long $noinline$IntRemBy12(int v) {
    int[] values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
    if (v > 0) {
      int q = v / 12;
      int r = v % 12;
      return ((long)q << 32) | values[r];
    } else {
      return -1;
    }
  }

  // A test case to check:
  //  BCE detects the optimized 'v % 12' and eliminates bounds checks.
  //
  /// CHECK-START:       long Main.$noinline$IntRemBy12A(int) BCE (before)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Mul
  /// CHECK-NEXT:            Sub
  /// CHECK:                 BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  //
  /// CHECK-START:       long Main.$noinline$IntRemBy12A(int) BCE (after)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Mul
  /// CHECK-NEXT:            Sub
  /// CHECK-NOT:             BoundsCheck
  /// CHECK:                 ArrayGet
  private static long $noinline$IntRemBy12A(int v) {
    int[] values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
    if (v > 0) {
      int q = v / 12;
      int t = q * 12;
      int r = v  - t;
      return ((long)q << 32) | values[r];
    } else {
      return -1;
    }
  }

  // A test case to check:
  //  BCE detects the optimized 'v % Integer.MAX_VALUE' and eliminates bounds checks.
  //
  /// CHECK-START:       int Main.$noinline$IntRemByMaxInt(int) BCE (before)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            Sub
  /// CHECK:                 BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  //
  /// CHECK-START:       int Main.$noinline$IntRemByMaxInt(int) BCE (after)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            Sub
  /// CHECK-NOT:             BoundsCheck
  /// CHECK:                 ArrayGet
  private static int $noinline$IntRemByMaxInt(int v) {
    int[] values = new int[Integer.MAX_VALUE];
    if (v > 0) {
      int q = v / Integer.MAX_VALUE;
      int r = v % Integer.MAX_VALUE;
      return values[v % Integer.MAX_VALUE] + q;
    } else {
      return -1;
    }
  }

  // A test case to check:
  //  BCE detects the optimized 'v % Integer.MIN_VALUE' and eliminates bounds checks.
  //
  /// CHECK-START:       int Main.$noinline$IntRemByMinInt(int) BCE (before)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Mul
  /// CHECK-NEXT:            Sub
  /// CHECK:                 BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  //
  /// CHECK-START:       int Main.$noinline$IntRemByMinInt(int) BCE (after)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Mul
  /// CHECK-NEXT:            Sub
  /// CHECK-NOT:             BoundsCheck
  /// CHECK:                 ArrayGet
  private static int $noinline$IntRemByMinInt(int v) {
    int[] values = new int[Integer.MAX_VALUE];
    if (v > 0) {
      int q = v / Integer.MIN_VALUE;
      int t = q * Integer.MIN_VALUE;
      int r = v - t;
      return values[r - 1];
    } else {
      return -1;
    }
  }

  // A test case to check:
  //  Bounds checks are not eliminated if the checked value is not an optimized HDiv+HRem.
  //
  /// CHECK-START:       int Main.$noinline$NoRem01(int, int) BCE (before)
  /// CHECK:                 Mul
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  //
  /// CHECK-START:       int Main.$noinline$NoRem01(int, int) BCE (after)
  /// CHECK:                 Mul
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  private static int $noinline$NoRem01(int v, int s) {
    int[] values = {0, 1, 2};
    if (v > 0) {
      int a = v * 10;
      int b = s - a;
      return values[b];
    } else {
      return -1;
    }
  }

  // A test case to check:
  //  Bounds checks are not eliminated if the checked value is not an optimized HDiv+HRem.
  //
  /// CHECK-START:       int Main.$noinline$NoRem02(int, int) BCE (before)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Mul
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  //
  /// CHECK-START:       int Main.$noinline$NoRem02(int, int) BCE (after)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Mul
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  private static int $noinline$NoRem02(int v, int s) {
    int[] values = {0, 1, 2};
    if (v > 0) {
      int q = v / 10;
      int a = q * s;
      int b = v - a;
      return values[b];
    } else {
      return -1;
    }
  }

  // A test case to check:
  //  Bounds checks are not eliminated if the checked value is not an optimized HDiv+HRem.
  //
  /// CHECK-START:       int Main.$noinline$NoRem03(int, int) BCE (before)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Add
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  //
  /// CHECK-START:       int Main.$noinline$NoRem03(int, int) BCE (after)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Add
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  private static int $noinline$NoRem03(int v, int s) {
    int[] values = {0, 1, 2};
    if (v > 0) {
      int q = v / 10;
      int a = q + s;
      int b = v - a;
      return values[b];
    } else {
      return -1;
    }
  }

  // A test case to check:
  //  Bounds checks are not eliminated if the checked value is not an optimized HDiv+HRem.
  //
  /// CHECK-START:       int Main.$noinline$NoRem04(int, int) BCE (before)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Add
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  //
  /// CHECK-START:       int Main.$noinline$NoRem04(int, int) BCE (after)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Add
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  private static int $noinline$NoRem04(int v, int s) {
    int[] values = {0, 1, 2};
    if (v > 0) {
      int q = v / 10;
      int t = q << s;
      int a = q + t;
      int b = v - a;
      return values[b];
    } else {
      return -1;
    }
  }

  // A test case to check:
  //  Bounds checks are not eliminated if the checked value is not an optimized HDiv+HRem.
  //
  /// CHECK-START:       int Main.$noinline$NoRem05(int, int) BCE (before)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Add
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  //
  /// CHECK-START:       int Main.$noinline$NoRem05(int, int) BCE (after)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Add
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  private static int $noinline$NoRem05(int v, int s) {
    int[] values = {0, 1, 2};
    if (v > 0) {
      int q = v / 10;
      int t = s << 1;
      int a = q + t;
      int b = v - a;
      return values[b];
    } else {
      return -1;
    }
  }

  // A test case to check:
  //  Bounds checks are not eliminated if the checked value is not an optimized HDiv+HRem.
  //
  /// CHECK-START:       int Main.$noinline$NoRem06(int, int) BCE (before)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Mul
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  //
  /// CHECK-START:       int Main.$noinline$NoRem06(int, int) BCE (after)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Mul
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  private static int $noinline$NoRem06(int v, int s) {
    int[] values = {0, 1, 2};
    if (v > 0) {
      int q = v / 10;
      int a = q * 11;
      int b = v - a;
      return values[b];
    } else {
      return -1;
    }
  }

  // A test case to check:
  //  Bounds checks are not eliminated if the checked value is not an optimized HDiv+HRem.
  //
  /// CHECK-START:       int Main.$noinline$NoRem07(int, int) BCE (before)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Add
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  //
  /// CHECK-START:       int Main.$noinline$NoRem07(int, int) BCE (after)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Add
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  private static int $noinline$NoRem07(int v, int s) {
    int[] values = {0, 1, 2};
    if (v > 0) {
      int q = v / 10;
      int t = q << 1;
      int a = s + t;
      int b = v - a;
      return values[b];
    } else {
      return -1;
    }
  }

  // A test case to check:
  //  Bounds checks are not eliminated if the checked value is not an optimized HDiv+HRem.
  //
  /// CHECK-START:       int Main.$noinline$NoRem08(int, int) BCE (before)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Add
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  //
  /// CHECK-START:       int Main.$noinline$NoRem08(int, int) BCE (after)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Add
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  private static int $noinline$NoRem08(int v, int s) {
    int[] values = {0, 1, 2};
    if (v > 0) {
      int q = v / 10;
      int t = q << 31;
      int a = q + t;
      int b = v - a;
      return values[b];
    } else {
      return -1;
    }
  }

  // A test case to check:
  //  Bounds checks are not eliminated if the checked value is not an optimized HDiv+HRem.
  //
  /// CHECK-START:       int Main.$noinline$NoRem09(int, int) BCE (before)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Add
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  //
  /// CHECK-START:       int Main.$noinline$NoRem09(int, int) BCE (after)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Add
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  private static int $noinline$NoRem09(int v, int s) {
    int[] values = {0, 1, 2};
    if (v > 0) {
      int q = v / 10;
      int t = q << 1;
      int a = q + t;
      int b = v - a;
      return values[b];
    } else {
      return -1;
    }
  }

  // A test case to check:
  //  Bounds checks are not eliminated if the checked value is not an optimized HDiv+HRem.
  //
  /// CHECK-START:       int Main.$noinline$NoRem10(int, int) BCE (before)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  //
  /// CHECK-START:       int Main.$noinline$NoRem10(int, int) BCE (after)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  private static int $noinline$NoRem10(int v, int s) {
    int[] values = {0, 1, 2};
    if (v > 0) {
      int q = v / 10;
      int t = q << s;
      int a = t - q;
      int b = v - a;
      return values[b];
    } else {
      return -1;
    }
  }

  // A test case to check:
  //  Bounds checks are not eliminated if the checked value is not an optimized HDiv+HRem.
  //
  /// CHECK-START:       int Main.$noinline$NoRem11(int, int) BCE (before)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  //
  /// CHECK-START:       int Main.$noinline$NoRem11(int, int) BCE (after)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  private static int $noinline$NoRem11(int v, int s) {
    int[] values = {0, 1, 2};
    if (v > 0) {
      int q = v / 10;
      int t = s << 1;
      int a = t - q;
      int b = v - a;
      return values[b];
    } else {
      return -1;
    }
  }

  // A test case to check:
  //  Bounds checks are not eliminated if the checked value is not an optimized HDiv+HRem.
  //
  /// CHECK-START:       int Main.$noinline$NoRem12(int, int) BCE (before)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  //
  /// CHECK-START:       int Main.$noinline$NoRem12(int, int) BCE (after)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  private static int $noinline$NoRem12(int v, int s) {
    int[] values = {0, 1, 2};
    if (v > 0) {
      int q = v / 10;
      int t = q << 1;
      int a = t - s;
      int b = v - a;
      return values[b];
    } else {
      return -1;
    }
  }

  // A test case to check:
  //  Bounds checks are not eliminated if the checked value is not an optimized HDiv+HRem.
  //
  /// CHECK-START:       int Main.$noinline$NoRem13(int, int) BCE (before)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  //
  /// CHECK-START:       int Main.$noinline$NoRem13(int, int) BCE (after)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Shl
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  private static int $noinline$NoRem13(int v, int s) {
    int[] values = {0, 1, 2};
    if (v > 0) {
      int q = v / 10;
      int t = q << 31;
      int a = t - q;
      int b = v - a;
      return values[b];
    } else {
      return -1;
    }
  }

  // A test case to check:
  //  Bounds checks are not eliminated if the checked value is not an optimized HDiv+HRem.
  //
  /// CHECK-START:       int Main.$noinline$NoRem14(int, int) BCE (before)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  //
  /// CHECK-START:       int Main.$noinline$NoRem14(int, int) BCE (after)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  private static int $noinline$NoRem14(int v, int s) {
    int[] values = {0, 1, 2};
    if (v > 0) {
      int a = v / 10;
      int b = s - a;
      return values[b];
    } else {
      return -1;
    }
  }

  // A test case to check:
  //  Bounds checks are not eliminated if the checked value is not an optimized HDiv+HRem.
  //
  /// CHECK-START:       int Main.$noinline$NoRem15(int, int) BCE (before)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Mul
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  //
  /// CHECK-START:       int Main.$noinline$NoRem15(int, int) BCE (after)
  /// CHECK:                 Div
  /// CHECK-NEXT:            Mul
  /// CHECK-NEXT:            Sub
  /// CHECK-NEXT:            BoundsCheck
  /// CHECK-NEXT:            ArrayGet
  private static int $noinline$NoRem15(int v, int s) {
    int[] values = {0, 1, 2};
    if (v > 0) {
      int q = v / 10;
      int a = q * 10;
      int b = s - a;
      return values[b];
    } else {
      return -1;
    }
  }

  /// CHECK-START:       int Main.$noinline$noRemNonConst(int, int) BCE (after)
  /// CHECK:                 BoundsCheck
  private static int $noinline$noRemNonConst(int v, int s) {
    // Regression test for compiler crash, b/169669115.
    int[] values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    if (v > 0) {
      int q = v / s;  // Non-constant divisor.
      int a = q * 10;  // Constant unrelated to the divisor above.
      int b = s - a;
      return values[b];
    } else {
      return -1;
    }
  }

  public static void main(String args[]) {
    remInt();
  }
}
