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

#include 'src/builtins/builtins-typed-array-gen.h'

namespace typed_array {
const kBuiltinNameSet: constexpr string = '%TypedArray%.prototype.set';

extern runtime TypedArraySet(
    Context, JSTypedArray, Object, Number, Number): void;

extern macro
TypedArrayBuiltinsAssembler::CallCCopyFastNumberJSArrayElementsToTypedArray(
    Context,
    FastJSArray,           // source
    AttachedJSTypedArray,  // dest
    uintptr,               // sourceLength
    uintptr                // destOffset
    ): void;

extern macro
TypedArrayBuiltinsAssembler::CallCCopyTypedArrayElementsToTypedArray(
    AttachedJSTypedArray,  // source
    AttachedJSTypedArray,  // dest
    uintptr,               // sourceLength
    uintptr                // destOffset
    ): void;

// %TypedArray%.prototype.set ( overloaded [ , offset ] )
// https://tc39.es/ecma262/#sec-%typedarray%.prototype.set-overloaded-offset
transitioning javascript builtin
TypedArrayPrototypeSet(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  // Steps 2-8 are the same for
  // %TypedArray%.prototype.set ( array [ , offset ] ) and
  // %TypedArray%.prototype.set ( typedArray [ , offset ] ) overloads.

  let target: JSTypedArray;
  try {
    // 2. Let target be the this value.
    // 3. Perform ? RequireInternalSlot(target, [[TypedArrayName]]).
    // 4. Assert: target has a [[ViewedArrayBuffer]] internal slot.
    target = Cast<JSTypedArray>(receiver) otherwise NotTypedArray;
  } label NotTypedArray deferred {
    ThrowTypeError(MessageTemplate::kNotTypedArray, kBuiltinNameSet);
  }

  try {
    // 5. Let targetOffset be ? ToInteger(offset).
    // 6. If targetOffset < 0, throw a RangeError exception.
    let targetOffsetOverflowed: bool = false;
    let targetOffset: uintptr = 0;
    if (arguments.length > 1) {
      const offsetArg = arguments[1];
      try {
        targetOffset = ToUintPtr(offsetArg)
        // On values less than zero throw RangeError immediately.
            otherwise OffsetOutOfBounds,
            // On UintPtr or SafeInteger range overflow throw RangeError after
            // performing observable steps to follow the spec.
            OffsetOverflow, OffsetOverflow;
      } label OffsetOverflow {
        targetOffsetOverflowed = true;
      }
    } else {
      // If the offset argument is not provided then the targetOffset is 0.
    }

    // 7. Let targetBuffer be target.[[ViewedArrayBuffer]].
    // 8. If IsDetachedBuffer(targetBuffer) is true, throw a TypeError
    //   exception.
    const utarget = typed_array::EnsureAttached(target) otherwise IsDetached;

    const overloadedArg = arguments[0];
    try {
      // 1. Choose 22.2.3.23.2 or 22.2.3.23.1 depending on whether the
      //   overloadedArg has a [[TypedArrayName]] internal slot.
      //   If it does, the definition in 22.2.3.23.2 applies.
      //   If it does not, the definition in 22.2.3.23.1 applies.
      const typedArray =
          Cast<JSTypedArray>(overloadedArg) otherwise NotTypedArray;

      // Step 9 is not observable, do it later.

      // 10. Let srcBuffer be typedArray.[[ViewedArrayBuffer]].
      // 11. If IsDetachedBuffer(srcBuffer) is true, throw a TypeError
      //   exception.
      const utypedArray =
          typed_array::EnsureAttached(typedArray) otherwise IsDetached;

      TypedArrayPrototypeSetTypedArray(
          utarget, utypedArray, targetOffset, targetOffsetOverflowed)
          otherwise OffsetOutOfBounds;
      return Undefined;
    } label NotTypedArray deferred {
      TypedArrayPrototypeSetArray(
          utarget, overloadedArg, targetOffset, targetOffsetOverflowed)
          otherwise OffsetOutOfBounds, IsDetached;
      return Undefined;
    }
  } label OffsetOutOfBounds deferred {
    ThrowRangeError(MessageTemplate::kTypedArraySetOffsetOutOfBounds);
  } label IsDetached deferred {
    ThrowTypeError(MessageTemplate::kDetachedOperation, kBuiltinNameSet);
  }
}

// %TypedArray%.prototype.set ( array [ , offset ] )
// https://tc39.es/ecma262/#sec-%typedarray%.prototype.set-array-offset
transitioning macro
TypedArrayPrototypeSetArray(implicit context: Context, receiver: JSAny)(
    target: JSTypedArray, arrayArg: JSAny, targetOffset: uintptr,
    targetOffsetOverflowed: bool): void labels IfOffsetOutOfBounds,
    IfDetached {
  // Steps 9-13 are not observable, do them later.

  // TODO(v8:8906): This ported behaviour is an observable spec violation and
  // the comment below seems to be outdated. Consider removing this code.
  try {
    const _arrayArgNum = Cast<Number>(arrayArg) otherwise NotNumber;
    // For number as a first argument, throw TypeError instead of silently
    // ignoring the call, so that users know they did something wrong.
    // (Consistent with Firefox and Blink/WebKit)
    ThrowTypeError(MessageTemplate::kInvalidArgument);
  } label NotNumber {
    // Proceed to step 14.
  }

  // 14. Let src be ? ToObject(array).
  const src: JSReceiver = ToObject_Inline(context, arrayArg);

  // 15. Let srcLength be ? LengthOfArrayLike(src).
  const srcLengthNum: Number = GetLengthProperty(src);

  if (targetOffsetOverflowed) goto IfOffsetOutOfBounds;

  // 9. Let targetLength be target.[[ArrayLength]].
  const targetLength = target.length;

  // 16. If srcLength + targetOffset > targetLength, throw a RangeError
  //   exception.
  const srcLength = ChangeSafeIntegerNumberToUintPtr(srcLengthNum)
      otherwise IfOffsetOutOfBounds;
  CheckIntegerIndexAdditionOverflow(srcLength, targetOffset, targetLength)
      otherwise IfOffsetOutOfBounds;

  // All the obvervable side effects are executed, so there's nothing else
  // to do with the empty source array.
  if (srcLength == 0) return;

  // 10. Let targetName be the String value of target.[[TypedArrayName]].
  // 11. Let targetElementSize be the Element Size value specified in
  //   Table 62 for targetName.
  // 12. Let targetType be the Element Type value in Table 62 for
  //   targetName.

  try {
    // BigInt typed arrays are not handled by
    // CopyFastNumberJSArrayElementsToTypedArray.
    if (IsBigInt64ElementsKind(target.elements_kind)) goto IfSlow;

    const fastSrc: FastJSArray = Cast<FastJSArray>(src) otherwise goto IfSlow;
    const srcKind: ElementsKind = fastSrc.map.elements_kind;

    // CopyFastNumberJSArrayElementsToTypedArray() can be used only with the
    // following elements kinds:
    // PACKED_SMI_ELEMENTS, HOLEY_SMI_ELEMENTS, PACKED_DOUBLE_ELEMENTS,
    // HOLEY_DOUBLE_ELEMENTS.
    if (IsElementsKindInRange(
            srcKind, ElementsKind::PACKED_SMI_ELEMENTS,
            ElementsKind::HOLEY_SMI_ELEMENTS) ||
        IsElementsKindInRange(
            srcKind, ElementsKind::PACKED_DOUBLE_ELEMENTS,
            ElementsKind::HOLEY_DOUBLE_ELEMENTS)) {
      const utarget = typed_array::EnsureAttached(target) otherwise IfDetached;
      CallCCopyFastNumberJSArrayElementsToTypedArray(
          context, fastSrc, utarget, srcLength, targetOffset);

    } else {
      goto IfSlow;
    }
  } label IfSlow deferred {
    TypedArraySet(
        context, target, src, srcLengthNum, Convert<Number>(targetOffset));
  }
}

// %TypedArray%.prototype.set ( typedArray [ , offset ] )
// https://tc39.es/ecma262/#sec-%typedarray%.prototype.set-typedarray-offset
transitioning macro
TypedArrayPrototypeSetTypedArray(implicit context: Context, receiver: JSAny)(
    target: AttachedJSTypedArray, typedArray: AttachedJSTypedArray,
    targetOffset: uintptr,
    targetOffsetOverflowed: bool): void labels IfOffsetOutOfBounds {
  // Steps 12-20 are not observable, so we can handle offset overflow
  // at step 21 here.
  if (targetOffsetOverflowed) goto IfOffsetOutOfBounds;

  // 9. Let targetLength be target.[[ArrayLength]].
  const targetLength = target.length;

  // 19. Let srcLength be typedArray.[[ArrayLength]].
  const srcLength: uintptr = typedArray.length;

  // Steps 12-20 are not observable, so we can do step 21 here.

  // 21. If srcLength + targetOffset > targetLength, throw a RangeError
  //   exception.
  CheckIntegerIndexAdditionOverflow(srcLength, targetOffset, targetLength)
      otherwise IfOffsetOutOfBounds;

  // 12. Let targetName be the String value of target.[[TypedArrayName]].
  // 13. Let targetType be the Element Type value in Table 62 for
  //   targetName.
  // 14. Let targetElementSize be the Element Size value specified in
  //   Table 62 for targetName.
  const targetElementsInfo = GetTypedArrayElementsInfo(target);

  // 16. Let srcName be the String value of typedArray.[[TypedArrayName]].
  // 17. Let srcType be the Element Type value in Table 62 for srcName.
  // 18. Let srcElementSize be the Element Size value specified in
  //   Table 62 for srcName.
  const srcKind: ElementsKind = typedArray.elements_kind;
  // const srcElementsInfo = GetTypedArrayElementsInfo(typedArray);

  // We skip steps 23-25 because both memmove and
  // CopyTypedArrayElementsToTypedArray() properly handle overlapping
  // regions.

  // 23. If both IsSharedArrayBuffer(srcBuffer) and
  //   IsSharedArrayBuffer(targetBuffer) are true, then
  // 23a. If srcBuffer.[[ArrayBufferData]] and
  //   targetBuffer.[[ArrayBufferData]] are the same Shared Data Block
  //   values, let same be true; else let same be false.
  // 24. Else, let same be SameValue(srcBuffer, targetBuffer).
  // 25. If same is true, then
  //   a. Let srcByteLength be typedArray.[[ByteLength]].
  //   b. Set srcBuffer to ? CloneArrayBuffer(srcBuffer, srcByteOffset,
  //    srcByteLength, %ArrayBuffer%).
  //   c. NOTE: %ArrayBuffer% is used to clone srcBuffer because is it known
  //    to not have any observable side-effects.
  //   d. Let srcByteIndex be 0.

  try {
    // Use memmove if possible.
    if (srcKind != targetElementsInfo.kind) {
      // Uint8/Uint8Clamped elements could still be copied with memmove.
      if (!IsUint8ElementsKind(srcKind) ||
          !IsUint8ElementsKind(targetElementsInfo.kind)) {
        goto IfSlow;
      }
    }

    // All the obvervable side effects are executed, so there's nothing else
    // to do with the empty source array.
    if (srcLength == 0) return;

    // Source and destination typed arrays have same elements kinds (modulo
    // Uint8-Uint8Clamped difference) so we can use targetElementsInfo for
    // calculations.
    const countBytes: uintptr =
        targetElementsInfo.CalculateByteLength(srcLength)
        otherwise unreachable;
    const startOffset: uintptr =
        targetElementsInfo.CalculateByteLength(targetOffset)
        otherwise unreachable;
    const dstPtr: RawPtr = target.data_ptr + Convert<intptr>(startOffset);

    assert(countBytes <= target.byte_length - startOffset);
    assert(countBytes <= typedArray.byte_length);

    // 29. If srcType is the same as targetType, then
    //   a. NOTE: If srcType and targetType are the same, the transfer must
    //      be performed in a manner that preserves the bit-level encoding of
    //      the source data.
    //   b. Repeat, while targetByteIndex < limit
    //      i. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, Uint8,
    //                                         true, Unordered).
    //     ii. Perform SetValueInBuffer(targetBuffer, targetByteIndex, Uint8,
    //                                  value, true, Unordered).
    //    iii. Set srcByteIndex to srcByteIndex + 1.
    //     iv. Set targetByteIndex to targetByteIndex + 1.
    CallCMemmove(dstPtr, typedArray.data_ptr, countBytes);
  } label IfSlow deferred {
    // 22. If target.[[ContentType]] is not equal to
    //   typedArray.[[ContentType]], throw a TypeError exception.
    if (IsBigInt64ElementsKind(srcKind) !=
        IsBigInt64ElementsKind(targetElementsInfo.kind))
      deferred {
        ThrowTypeError(MessageTemplate::kBigIntMixedTypes);
      }

    // All the obvervable side effects are executed, so there's nothing else
    // to do with the empty source array.
    if (srcLength == 0) return;

    // 30. Else,
    //   a. Repeat, while targetByteIndex < limit
    //      i. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex,
    //                                         srcType, true, Unordered).
    //     ii. Perform SetValueInBuffer(targetBuffer, targetByteIndex,
    //                                  targetType, value, true, Unordered).
    //    iii. Set srcByteIndex to srcByteIndex + srcElementSize.
    //     iv. Set targetByteIndex to targetByteIndex + targetElementSize.
    CallCCopyTypedArrayElementsToTypedArray(
        typedArray, target, srcLength, targetOffset);
  }
}
}