// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Flags: --allow-natives-syntax --turbo-inline-array-builtins --turbofan
// Flags: --no-always-turbofan

// Out of bounds
(() => {
  const arr = [0, 1, 2.5, 3, {}, 5, "a", 7, 8, { "x": 42 }, 10];
  function testArrayAt(a, idx) {
    return a.at(idx);
  }
  %PrepareFunctionForOptimization(testArrayAt);
  testArrayAt(arr, 2);
  assertEquals(arr[2], testArrayAt(arr, 2));
  %OptimizeFunctionOnNextCall(testArrayAt);
  testArrayAt(arr, 2);
  assertOptimized(testArrayAt);
  // Checking out of bounds
  assertEquals(undefined, testArrayAt(arr, -20));
  assertOptimized(testArrayAt);
  assertEquals(undefined, testArrayAt(arr, 20));
  assertOptimized(testArrayAt);
})();

// Smi array
(() => {
  const arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
  function testArrayAt(a, idx) {
    return a.at(idx);
  }
  %PrepareFunctionForOptimization(testArrayAt);
  testArrayAt(arr, 2);
  assertEquals(arr[2], testArrayAt(arr, 2));
  %OptimizeFunctionOnNextCall(testArrayAt);
  testArrayAt(arr, 2);
  assertOptimized(testArrayAt);
  // Positive indices
  for (let idx = 0; idx < arr.length; idx++) {
    assertEquals(arr[idx], testArrayAt(arr, idx));
    assertOptimized(testArrayAt);
  }
  // Negative indices
  for (let idx = 1; idx <= arr.length; idx++) {
    assertEquals(arr[arr.length - idx], testArrayAt(arr, -idx));
    assertOptimized(testArrayAt);
  }
})();

// Double array
(() => {
  const arr = [0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5];
  function testArrayAt(a, idx) {
    return a.at(idx);
  }
  %PrepareFunctionForOptimization(testArrayAt);
  testArrayAt(arr, 2);
  assertEquals(arr[2], testArrayAt(arr, 2));
  %OptimizeFunctionOnNextCall(testArrayAt);
  testArrayAt(arr, 2);
  assertOptimized(testArrayAt);
  // Positive indices
  for (let idx = 0; idx < arr.length; idx++) {
    assertEquals(arr[idx], testArrayAt(arr, idx));
    assertOptimized(testArrayAt);
  }
  // Negative indices
  for (let idx = 1; idx <= arr.length; idx++) {
    assertEquals(arr[arr.length - idx], testArrayAt(arr, -idx));
    assertOptimized(testArrayAt);
  }
})();

// Obj array
(() => {
  const arr = [0, 1, 2.5, 3, {}, 5, "a", 7, 8, { "x": 42 }, 10];
  function testArrayAt(a, idx) {
    return a.at(idx);
  }
  %PrepareFunctionForOptimization(testArrayAt);
  testArrayAt(arr, 2);
  assertEquals(arr[2], testArrayAt(arr, 2));
  %OptimizeFunctionOnNextCall(testArrayAt);
  testArrayAt(arr, 2);
  assertOptimized(testArrayAt);
  // Positive indices
  for (let idx = 0; idx < arr.length; idx++) {
    assertEquals(arr[idx], testArrayAt(arr, idx));
    assertOptimized(testArrayAt);
  }
  // Negative indices
  for (let idx = 1; idx <= arr.length; idx++) {
    assertEquals(arr[arr.length - idx], testArrayAt(arr, -idx));
    assertOptimized(testArrayAt);
  }
})();

// Smi/Double/Obj arrays. This test ensure that:
//  - When different kinds are present in the feedback, they all get properly
//    optimized: |smis|, |doubles| and |objects| will produce 3 different
//    branches in the inlined code.
//
//  - The "needs_fallback_builtin_call" mechanism works as intended: while the
//    |smis|, |doubles| and |objects| cases will be inlined, |dict| should
//    produce a call to the builtin (without deoptimizing).
(() => {
  const smis = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
  const doubles = [0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5];
  const objects = [0, 1, 2.5, 3, {}, 5, "a", 7, 8, { "x": 42 }, 10];
  const dict = new Array();
  dict[2] = 42;
  dict[10000] = 5;
  assertEquals(smis.length, doubles.length);
  assertEquals(smis.length, objects.length);
  function testArrayAt(a, idx) {
    return a.at(idx);
  }
  %PrepareFunctionForOptimization(testArrayAt);
  testArrayAt(smis, 2);
  testArrayAt(doubles, 2);
  testArrayAt(objects, 2);
  testArrayAt(dict, 2);
  assertEquals(doubles[2], testArrayAt(doubles, 2));
  assertEquals(objects[2], testArrayAt(objects, 2));
  assertEquals(dict[2], testArrayAt(dict, 2));
  assertEquals(smis[2], testArrayAt(smis, 2));
  %OptimizeFunctionOnNextCall(testArrayAt);
  testArrayAt(smis, 2);
  assertOptimized(testArrayAt);
  testArrayAt(doubles, 2);
  assertOptimized(testArrayAt);
  testArrayAt(objects, 2);
  assertOptimized(testArrayAt);
  testArrayAt(dict, 2);
  assertOptimized(testArrayAt);
  // Positive indices
  for (let idx = 0; idx < smis.length; idx++) {
    assertEquals(smis[idx], testArrayAt(smis, idx));
    assertOptimized(testArrayAt);
    assertEquals(doubles[idx], testArrayAt(doubles, idx));
    assertOptimized(testArrayAt);
    assertEquals(objects[idx], testArrayAt(objects, idx));
    assertOptimized(testArrayAt);
    assertEquals(dict[idx], testArrayAt(dict, idx));
    assertOptimized(testArrayAt);
  }
  // Negative indices
  for (let idx = 1; idx <= smis.length; idx++) {
    assertEquals(smis[smis.length - idx], testArrayAt(smis, -idx));
    assertOptimized(testArrayAt);
    assertEquals(doubles[doubles.length - idx], testArrayAt(doubles, -idx));
    assertOptimized(testArrayAt);
    assertEquals(objects[objects.length - idx], testArrayAt(objects, -idx));
    assertOptimized(testArrayAt);
    assertEquals(dict[dict.length - idx], testArrayAt(dict, -idx));
    assertOptimized(testArrayAt);
  }
})();

// Checks that accessing holes in double arrays leads to deoptimization.
(() => {
  let doubles = Array();
  doubles[10] = 4.5;
  function testArrayAt(idx) {
    return doubles.at(idx);
  }
  %PrepareFunctionForOptimization(testArrayAt);
  testArrayAt(10);
  %OptimizeFunctionOnNextCall(testArrayAt);
  testArrayAt(10);
  assertOptimized(testArrayAt);
  assertEquals(doubles[10], testArrayAt(10));
  assertOptimized(testArrayAt);
  assertEquals(doubles[2], testArrayAt(2));
  assertUnoptimized(testArrayAt);
})();

// Checks that accessing holes in SMI array does not lead to deoptimization.
(() => {
  let smis = Array();
  smis[10] = 4;
  function testArrayAt(idx) {
    return smis.at(idx);
  }
  %PrepareFunctionForOptimization(testArrayAt);
  testArrayAt(10);
  %OptimizeFunctionOnNextCall(testArrayAt);
  testArrayAt(10);
  assertOptimized(testArrayAt);
  assertEquals(smis[10], testArrayAt(10));
  assertOptimized(testArrayAt);
  assertEquals(smis[2], testArrayAt(2));
  assertOptimized(testArrayAt);
})();
