// 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.

namespace array {
macro HandleSimpleArgumentsSlice(
    context: NativeContext, args: JSArgumentsObjectWithLength, start: Smi,
    count: Smi): JSArray
    labels Bailout {
  // If the resulting array doesn't fit in new space, use the slow path.
  if (count >= kMaxNewSpaceFixedArrayElements) goto Bailout;

  const end: Smi = start + count;
  const sourceElements: FixedArray =
      Cast<FixedArray>(args.elements) otherwise Bailout;
  if (SmiAbove(end, sourceElements.length)) goto Bailout;

  const arrayMap: Map =
      LoadJSArrayElementsMap(ElementsKind::HOLEY_ELEMENTS, context);
  const result: JSArray =
      AllocateJSArray(ElementsKind::HOLEY_ELEMENTS, arrayMap, count, count);
  const newElements: FixedArray =
      Cast<FixedArray>(result.elements) otherwise Bailout;
  CopyElements(
      ElementsKind::PACKED_ELEMENTS, newElements, 0, sourceElements,
      Convert<intptr>(start), Convert<intptr>(count));
  return result;
}

macro HandleFastAliasedSloppyArgumentsSlice(
    context: NativeContext, args: JSArgumentsObjectWithLength, start: Smi,
    count: Smi): JSArray
    labels Bailout {
  // If the resulting array doesn't fit in new space, use the slow path.
  if (count >= kMaxNewSpaceFixedArrayElements) goto Bailout;

  const sloppyElements: SloppyArgumentsElements =
      Cast<SloppyArgumentsElements>(args.elements) otherwise Bailout;
  const parameterMapLength: Smi = sloppyElements.length;

  // Check to make sure that the extraction will not access outside the
  // defined arguments
  const end: Smi = start + count;
  const unmappedElements: FixedArray =
      Cast<FixedArray>(sloppyElements.arguments)
      otherwise Bailout;
  const unmappedElementsLength: Smi = unmappedElements.length;
  if (SmiAbove(end, unmappedElementsLength)) goto Bailout;

  const argumentsContext: Context = sloppyElements.context;

  const arrayMap: Map =
      LoadJSArrayElementsMap(ElementsKind::HOLEY_ELEMENTS, context);
  const result: JSArray =
      AllocateJSArray(ElementsKind::HOLEY_ELEMENTS, arrayMap, count, count);

  let indexOut: Smi = 0;
  const resultElements: FixedArray = UnsafeCast<FixedArray>(result.elements);
  const to: Smi = SmiMin(parameterMapLength, end);

  // Fill in the part of the result that map to context-mapped parameters.
  for (let current: Smi = start; current < to; ++current) {
    const e: Object = sloppyElements.mapped_entries[current];
    const newElement = UnsafeCast<(JSAny | TheHole)>(
        e != TheHole ? argumentsContext.elements[UnsafeCast<Smi>(e)] :
                       unmappedElements.objects[current]);
    // It is safe to skip the write barrier here because resultElements was
    // allocated together with result in a folded allocation.
    // TODO(turbofan): The verification of this fails at the moment due to
    // missing load elimination.
    StoreFixedArrayElement(
        resultElements, indexOut++, newElement, UNSAFE_SKIP_WRITE_BARRIER);
  }

  // Fill in the rest of the result that contains the unmapped parameters
  // above the formal parameters.
  const unmappedFrom: Smi = SmiMin(SmiMax(parameterMapLength, start), end);
  const restCount: Smi = end - unmappedFrom;
  CopyElements(
      ElementsKind::PACKED_ELEMENTS, resultElements, Convert<intptr>(indexOut),
      unmappedElements, Convert<intptr>(unmappedFrom),
      Convert<intptr>(restCount));
  return result;
}

macro HandleFastSlice(
    context: NativeContext, o: JSAny, startNumber: Number,
    countNumber: Number): JSArray
    labels Bailout {
  const start: Smi = Cast<Smi>(startNumber) otherwise Bailout;
  const count: Smi = Cast<Smi>(countNumber) otherwise Bailout;
  assert(start >= 0);

  try {
    typeswitch (o) {
      case (a: FastJSArrayForCopy): {
        // It's possible to modify the array length from a valueOf
        // callback between the original array length read and this
        // point. That can change the length of the array backing store,
        // in the worst case, making it smaller than the region that needs
        // to be copied out. Therefore, re-check the length before calling
        // the appropriate fast path. See regress-785804.js
        if (SmiAbove(start + count, a.length)) goto Bailout;
        return ExtractFastJSArray(context, a, start, count);
      }
      case (a: JSStrictArgumentsObject): {
        goto HandleSimpleArgumentsSlice(a);
      }
      case (a: JSSloppyArgumentsObject): {
        const map: Map = a.map;
        if (IsFastAliasedArgumentsMap(map)) {
          return HandleFastAliasedSloppyArgumentsSlice(context, a, start, count)
              otherwise Bailout;
        } else if (IsSloppyArgumentsMap(map)) {
          goto HandleSimpleArgumentsSlice(a);
        }
        goto Bailout;
      }
      case (JSAny): {
        goto Bailout;
      }
    }
  } label HandleSimpleArgumentsSlice(a: JSArgumentsObjectWithLength) {
    return HandleSimpleArgumentsSlice(context, a, start, count)
        otherwise Bailout;
  }
}

// https://tc39.github.io/ecma262/#sec-array.prototype.slice
transitioning javascript builtin
ArrayPrototypeSlice(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  // Handle array cloning case if the receiver is a fast array.
  if (arguments.length == 0) {
    typeswitch (receiver) {
      case (a: FastJSArrayForCopy): {
        return CloneFastJSArray(context, a);
      }
      case (JSAny): {
      }
    }
  }

  // 1. Let O be ? ToObject(this value).
  const o: JSReceiver = ToObject_Inline(context, receiver);

  // 2. Let len be ? ToLength(? Get(O, "length")).
  const len: Number = GetLengthProperty(o);

  // 3. Let relativeStart be ? ToInteger(start).
  const start: JSAny = arguments[0];
  const relativeStart: Number = ToInteger_Inline(start);

  // 4. If relativeStart < 0, let k be max((len + relativeStart), 0);
  //    else let k be min(relativeStart, len).
  let k: Number = relativeStart < 0 ? Max((len + relativeStart), 0) :
                                      Min(relativeStart, len);

  // 5. If end is undefined, let relativeEnd be len;
  //    else let relativeEnd be ? ToInteger(end).
  const end: JSAny = arguments[1];
  const relativeEnd: Number = end == Undefined ? len : ToInteger_Inline(end);

  // 6. If relativeEnd < 0, let final be max((len + relativeEnd), 0);
  //    else let final be min(relativeEnd, len).
  const final: Number =
      relativeEnd < 0 ? Max((len + relativeEnd), 0) : Min(relativeEnd, len);

  // 7. Let count be max(final - k, 0).
  const count: Number = Max(final - k, 0);

  assert(0 <= k);
  assert(k <= len);
  assert(0 <= final);
  assert(final <= len);
  assert(0 <= count);
  assert(count <= len);

  try {
    return HandleFastSlice(context, o, k, count)
        otherwise Slow;
  } label Slow {}

  // 8. Let A be ? ArraySpeciesCreate(O, count).
  const a: JSReceiver = ArraySpeciesCreate(context, o, count);

  // 9. Let n be 0.
  let n: Number = 0;

  // 10. Repeat, while k < final
  while (k < final) {
    // a. Let Pk be ! ToString(k).
    const pK: Number = k;

    // b. Let kPresent be ? HasProperty(O, Pk).
    const fromPresent: Boolean = HasProperty(o, pK);

    // c. If kPresent is true, then
    if (fromPresent == True) {
      // i. Let kValue be ? Get(O, Pk).
      const kValue: JSAny = GetProperty(o, pK);

      // ii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(n), kValue).
      FastCreateDataProperty(a, n, kValue);
    }

    // d. Increase k by 1.
    k++;

    // e. Increase n by 1.
    n++;
  }

  // 11. Perform ? Set(A, "length", n, true).
  SetProperty(a, kLengthString, n);

  // 12. Return A.
  return a;
}
}