// Copyright 2018 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 --opt


// Test CloneFastJSArray inserted by JSCallReducer for Array.prototype.slice.
// CloneFastJSArray produces COW arrays if the original array is COW.

// Trigger JSCallReducer on slice() and slice(0)
(function() {
  const arr = [1,2,3,4,5];

  function slice() {
    return arr.slice();
  }

  function slice0() {
    return arr.slice(0);
  }

  %PrepareFunctionForOptimization(slice0);
  %PrepareFunctionForOptimization(slice);

  assertEquals(arr, slice());
  assertFalse(arr === slice());
  assertEquals(slice(), slice0());
  assertEquals(slice0(), slice());

  %OptimizeFunctionOnNextCall(slice0);
  assertEquals(slice(), slice0());
  %OptimizeFunctionOnNextCall(slice);

  assertEquals(slice(), slice0());
  assertOptimized(slice); assertOptimized(slice0);
})();

// This will cause deopt of slice by a CheckMap installed by
// JSNativeContextSpecialization::ReduceNamedAccess
(function() {
  const arr = [1,2,3,4,5];

  function slice() {
    return arr.slice();
  }

  %PrepareFunctionForOptimization(slice);

  assertEquals(arr, slice());
  assertEquals(slice(), arr);

  %OptimizeFunctionOnNextCall(slice);
  slice();

  // Trigger deopt here
  arr.push(7.2);
  assertEquals(slice()[5], 7.2);
})();

// There should not be a deopt cycle.
(function() {
  const arr = [1,2,3,4,5];

  function slice() {
    return arr.slice();
  }

  %PrepareFunctionForOptimization(slice);

  assertEquals(arr, slice());
  assertEquals(slice(), arr);

  %OptimizeFunctionOnNextCall(slice);
  // Trigger opt
  assertEquals(slice(), arr);

  // Trigger deopt by CheckMap from JSNativeContextSpecialization
  arr.push(7.2);
  slice();

  %PrepareFunctionForOptimization(slice);
  %OptimizeFunctionOnNextCall(slice);
  // Trigger opt again
  slice();

  // Should not deopt again
  arr.push(8.2);
  slice();
  assertOptimized(slice);
})();

// JSCallReducer will not reduce because the species has been modified
(function() {
  const array = [3,4,5];

  function slice(){
    return array.slice();
  }

  class MyArray extends Array {};
  array.constructor = MyArray;

  %PrepareFunctionForOptimization(slice);

  slice(); slice();

  %OptimizeFunctionOnNextCall(slice);
  var narr = slice();
  assertInstanceof(narr, MyArray);
})();

(function() {
  const array = [3,4,5];

  function slice(){
    return array.slice();
  }

  %PrepareFunctionForOptimization(slice);

  slice(); slice();

  %OptimizeFunctionOnNextCall(slice);

  slice();

  class MyArray extends Array {};
  array.constructor = MyArray;
  // deopt
  var narr = slice();
  // if not deopt, narr will be instanceof Array
  assertTrue(narr instanceof MyArray);
})();

// JSCallReducer adds check for UnreliableReceiverMaps
(function() {
  const arr = [1,2,3,4,5];

  function slice() {
    return arr.slice();
  }

  %PrepareFunctionForOptimization(slice);

  slice(); slice();
  arr.foo = 6.2;

  %OptimizeFunctionOnNextCall(slice);
  // JSCallReducer will add check for UnreliableReceiverMaps
  slice();

  // Trigger deopt because of DependOnStableMaps
  // installed by JSNativeContextSpecialization,
  // but not the check installed by ReduceArrayPrototypeSlice itself
  arr.bar = 7.2;

  let narr = slice();
  assertEquals(arr, narr);
  assertEquals(narr.foo, undefined);
  assertEquals(narr.bar, undefined);
})();

// Multiple maps
(function() {
  const iarr = [1,2,3];
  const darr = [2.1, 3.3, 0.2];

  function slice(arr) {
    return arr.slice();
  }

  %PrepareFunctionForOptimization(slice);

  slice(iarr); slice(darr);
  slice(iarr); slice(darr);

  %OptimizeFunctionOnNextCall(slice);
  // The optimization works for both maps
  assertEquals(iarr, slice(iarr));
  assertEquals(darr, slice(darr));
  assertOptimized(slice);
})();

// Tests for the branch of CanInlineArrayIteratingBuiltin

// JSCallReducer will not reduce to CloneFastJSArray
// if array's prototype is not JS_ARRAY_TYPE
(function () {
  class MyArray extends Array {
    constructor() {
      super();
      this[6]= 6;
    }
  }
  let array = new MyArray(3, 5, 4);

  function slice() {
    return array.slice();
  }

  %PrepareFunctionForOptimization(slice);

  assertEquals(slice(),array);
  slice();

  %OptimizeFunctionOnNextCall(slice);
  let narr = slice();
  // here, slice supposes to call MyArray's constructor.
  // If we optimize with CloneFastJSArray, Array's constructor is called instead.
  assertEquals(narr[6], 6);
  assertTrue(narr instanceof MyArray);
})();

// JSCallReducer will not reduce to CloneFastJSArray
// if array's instance type is not JS_ARRAY_TYPE.
// CloneFastJSArray does not work with non JS_ARRAY_TYPE.
// Check : receiver_map->instance_type() == JS_ARRAY_TYPE
(function () {
  var x = {"0" : 0, "2": 2} ;
  x.__proto__ = Array.prototype;

  function slice() {
    return x.slice();
  }

  %PrepareFunctionForOptimization(slice);

  slice(); slice();

  %OptimizeFunctionOnNextCall(slice);
  assertEquals(slice(), []);
})();

// JSCallReducer will not reduce to CloneFastJSArray
// since array is not Fast Elements Kind
// Check : IsFastElementsKind(receiver_map->elements_kind())
(function () {
  var array = [3, 4, 5];

  function slice() {
    return array.slice();
  }

  %PrepareFunctionForOptimization(slice);

  assertEquals(slice(),array);
  slice();

  // a sparse array switches to Dictionary Elements
  array[9999] = 0;
  %OptimizeFunctionOnNextCall(slice);
  var narr = slice();
  assertEquals(narr, array);
})();

(function () {
  var array = [3, 4, 5];

  function slice() {
    return array.slice();
  }

  %PrepareFunctionForOptimization(slice);

  assertEquals(slice(),array);
  slice();

  %OptimizeFunctionOnNextCall(slice);
  slice();

  // a sparse array switches to Dictionary Elements
  array[9999] = 0;
  // trigger deopt because map changes
  assertEquals(slice(),array);
})();

// JSCallReducer will not reduce to CloneFastJSArray
// if array is used as a prototype and has unstable map
(function () {
  var array = [3, 5, 4];

  function slice(arr) {
    return arr.slice();
  }

  %PrepareFunctionForOptimization(slice);

  // make array's map is_prototype_map()
  var x = {__proto__ : array};

  assertEquals(slice(array),array);
  slice(array);

  // make array's map unstable
  array.push(6.3);
  slice(array);

  %OptimizeFunctionOnNextCall(slice);

  assertEquals(slice(array),array);
})();

// JSCallReducer will not reduce to CloneFastJSArray
// if the Array prototype got some elements.
// Check: isolate->IsNoElementsProtectorIntact()
(function () {
  var array = [, 6, 6];

  function slice() {
    return array.slice();
  }

  %PrepareFunctionForOptimization(slice);

  assertEquals(slice(),array);
  slice();

  array.__proto__.push(6);

  %OptimizeFunctionOnNextCall(slice);

  // if we optimized, we would get [ , 6, 6]
  // here, slice copies elements from both the object and the prototype
  let narr = slice();
  assertNotEquals(Object.getOwnPropertyDescriptor(narr,0), undefined);
  assertEquals(narr, [6, 6, 6]);
})();

(function () {
  var array = [, 6, 6];

  function slice() {
    return array.slice();
  }

  %PrepareFunctionForOptimization(slice);

  assertEquals(slice(),array);
  slice();

  %OptimizeFunctionOnNextCall(slice);
  slice();

  // Deopt
  array.__proto__.push(6);
  let narr = slice();
  assertNotEquals(Object.getOwnPropertyDescriptor(narr, 0), undefined);
  assertEquals(narr[0], 6);
})();

// JSCallReducer will not reduce to CloneFastJSArray
// if the Array prototype is not original
// Check: isolate->IsAnyInitialArrayPrototype(receiver_prototype)
(function () {
  var array = [6, , 6];

  function slice() {
    return array.slice();
  }

  %PrepareFunctionForOptimization(slice);

  assertEquals(slice(),array);
  slice();

  // change the prototype
  array.__proto__ = [ , 6, ];

  %OptimizeFunctionOnNextCall(slice);
  let narr = slice();
  // if optimized, we would get [6, , 6]
  assertNotEquals(Object.getOwnPropertyDescriptor(narr, 1), undefined);
  assertEquals(narr, [6,6,6]);
})();

(function () {
  var array = [6, ,6];

  function slice() {
    return array.slice();
  }

  %PrepareFunctionForOptimization(slice);

  assertEquals(slice(),array);
  slice();

  %OptimizeFunctionOnNextCall(slice);
  slice();

  // change the prototype
  array.__proto__ = [,6,];
  // deopt because of map changed
  let narr = slice();

  // if optimized, we would get [6, , 6]
  assertNotEquals(Object.getOwnPropertyDescriptor(narr, 1), undefined);
  assertEquals(narr, [6,6,6]);
})();

// Packed
// Trigger JSCallReducer on slice() and slice(0)
(function() {
  // Non-extensible:
  var arr = Object.preventExtensions([1,2,'a',4,5]);

  function slice() {
    return arr.slice();
  }

  function slice0() {
    return arr.slice(0);
  }

  function test() {
    %PrepareFunctionForOptimization(slice0);
    %PrepareFunctionForOptimization(slice);

    assertEquals(arr, slice());
    assertFalse(arr === slice());
    assertEquals(slice(), slice0());
    assertEquals(slice0(), slice());

    %OptimizeFunctionOnNextCall(slice0);
    assertEquals(slice(), slice0());
    %OptimizeFunctionOnNextCall(slice);

    assertEquals(slice(), slice0());
    assertOptimized(slice); assertOptimized(slice0);
  }
  test();

  // Sealed
  arr = Object.seal([1,2,'a',4,5]);
  test();

  // Frozen
  arr = Object.freeze([1,2,'a',4,5]);
  test();
})();

// Holey
// Trigger JSCallReducer on slice() and slice(0)
(function() {
  // Non-extensible:
  var arr = Object.preventExtensions([,1,2,'a',4,5]);

  function slice() {
    return arr.slice();
  }

  function slice0() {
    return arr.slice(0);
  }

  function test() {
    %PrepareFunctionForOptimization(slice0);
    %PrepareFunctionForOptimization(slice);
    assertEquals(arr, slice());
    assertFalse(arr === slice());
    assertEquals(slice(), slice0());
    assertEquals(slice0(), slice());

    %OptimizeFunctionOnNextCall(slice0);
    assertEquals(slice(), slice0());
    %OptimizeFunctionOnNextCall(slice);

    assertEquals(slice(), slice0());
    assertOptimized(slice0);
    assertOptimized(slice);
  }
  test();

  // Sealed
  arr = Object.seal([,1,2,'a',4,5]);
  test();

  // Frozen
  arr = Object.freeze([,1,2,'a',4,5]);
  test();
})();
