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

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

namespace typed_array {
// Naming convention from elements.cc. We have a similar intent but implement
// fastpaths using generics instead of using a class hierarchy for elements
// kinds specific implementations.
type Uint8Elements extends ElementsKind;
type Int8Elements extends ElementsKind;
type Uint16Elements extends ElementsKind;
type Int16Elements extends ElementsKind;
type Uint32Elements extends ElementsKind;
type Int32Elements extends ElementsKind;
type Float32Elements extends ElementsKind;
type Float64Elements extends ElementsKind;
type Uint8ClampedElements extends ElementsKind;
type BigUint64Elements extends ElementsKind;
type BigInt64Elements extends ElementsKind;
type RabGsabUint8Elements extends ElementsKind;

@export
struct TypedArrayElementsInfo {
  // Calculates the number of bytes required for specified number of elements.
  macro CalculateByteLength(length: uintptr): uintptr labels IfInvalid {
    if (length > kTypedArrayMaxLength) goto IfInvalid;
    const maxArrayLength = kArrayBufferMaxByteLength >>> this.sizeLog2;
    if (length > maxArrayLength) goto IfInvalid;
    const byteLength = length << this.sizeLog2;
    return byteLength;
  }

  // Calculates the maximum number of elements supported by a specified number
  // of bytes.
  macro CalculateLength(byteLength: uintptr): uintptr labels IfInvalid {
    const length = byteLength >>> this.sizeLog2;
    if (length > kTypedArrayMaxLength) goto IfInvalid;
    return length;
  }

  // Determines if `bytes` (byte offset or length) cannot be evenly divided by
  // element size.
  macro IsUnaligned(bytes: uintptr): bool {
    // Exploits the fact the element size is a power of 2. Determining whether
    // there is remainder (not aligned) can be achieved efficiently with bit
    // masking. Shift is safe as sizeLog2 can be 3 at most (see
    // ElementsKindToShiftSize).
    return (bytes & ((1 << this.sizeLog2) - 1)) != 0;
  }

  sizeLog2: uintptr;
  kind: ElementsKind;
}
extern runtime TypedArrayCopyElements(
    Context, JSTypedArray, Object, Number): void;
extern macro TypedArrayBuiltinsAssembler::ValidateTypedArray(
    Context, JSAny, constexpr string): JSTypedArray;
extern macro TypedArrayBuiltinsAssembler::ValidateTypedArrayAndGetLength(
    Context, JSAny, constexpr string): uintptr;

extern macro TypedArrayBuiltinsAssembler::CallCMemcpy(
    RawPtr, RawPtr, uintptr): void;
extern macro TypedArrayBuiltinsAssembler::CallCMemmove(
    RawPtr, RawPtr, uintptr): void;
extern macro TypedArrayBuiltinsAssembler::CallCMemset(
    RawPtr, intptr, uintptr): void;
extern macro TypedArrayBuiltinsAssembler::CallCRelaxedMemcpy(
    RawPtr, RawPtr, uintptr): void;
extern macro TypedArrayBuiltinsAssembler::CallCRelaxedMemmove(
    RawPtr, RawPtr, uintptr): void;
extern macro GetTypedArrayBuffer(implicit context: Context)(JSTypedArray):
    JSArrayBuffer;
extern macro TypedArrayBuiltinsAssembler::GetTypedArrayElementsInfo(
    JSTypedArray): TypedArrayElementsInfo;
extern macro TypedArrayBuiltinsAssembler::GetTypedArrayElementsInfo(Map):
    TypedArrayElementsInfo;
extern macro TypedArrayBuiltinsAssembler::IsUint8ElementsKind(ElementsKind):
    bool;
extern macro TypedArrayBuiltinsAssembler::IsBigInt64ElementsKind(ElementsKind):
    bool;
extern macro LoadFixedTypedArrayElementAsTagged(
    RawPtr, uintptr, constexpr ElementsKind): Numeric;
extern macro TypedArrayBuiltinsAssembler::StoreJSTypedArrayElementFromNumeric(
    Context, JSTypedArray, uintptr, Numeric, constexpr ElementsKind): void;
extern macro TypedArrayBuiltinsAssembler::StoreJSTypedArrayElementFromTagged(
    Context, JSTypedArray, uintptr, JSAny,
    constexpr ElementsKind): void labels IfDetached;

extern macro LoadJSTypedArrayLengthAndCheckDetached(JSTypedArray): uintptr
    labels IfDetached;

type LoadNumericFn = builtin(JSTypedArray, uintptr) => Numeric;
type StoreNumericFn = builtin(Context, JSTypedArray, uintptr, Numeric) => Smi;
type StoreJSAnyFn = builtin(Context, JSTypedArray, uintptr, JSAny) => Smi;

// The result codes returned by StoreNumericFn and StoreJSAnyFn builtins.
const kStoreSucceded: Smi = 0;
const kStoreFailureArrayDetached: Smi = 1;

struct TypedArrayAccessor {
  macro LoadNumeric(array: JSTypedArray, index: uintptr): Numeric {
    const loadfn: LoadNumericFn = this.loadNumericFn;
    return loadfn(array, index);
  }

  macro StoreNumeric(
      context: Context, array: JSTypedArray, index: uintptr,
      value: Numeric): void {
    const storefn: StoreNumericFn = this.storeNumericFn;
    const result = storefn(context, array, index, value);
    dcheck(result == kStoreSucceded);
  }

  macro StoreJSAny(
      context: Context, array: JSTypedArray, index: uintptr,
      value: JSAny): void labels IfDetached {
    const storefn: StoreJSAnyFn = this.storeJSAnyFn;
    const result = storefn(context, array, index, value);
    if (result == kStoreFailureArrayDetached) {
      goto IfDetached;
    }
    dcheck(result == kStoreSucceded);
  }

  loadNumericFn: LoadNumericFn;
  storeNumericFn: StoreNumericFn;
  storeJSAnyFn: StoreJSAnyFn;
}

macro GetTypedArrayAccessor<T : type extends ElementsKind>():
    TypedArrayAccessor {
  const loadNumericFn = LoadTypedElement<T>;
  const storeNumericFn = StoreTypedElementNumeric<T>;
  const storeJSAnyFn = StoreTypedElementJSAny<T>;
  return TypedArrayAccessor{loadNumericFn, storeNumericFn, storeJSAnyFn};
}

macro GetTypedArrayAccessor(elementsKindParam: ElementsKind):
    TypedArrayAccessor {
  let elementsKind = elementsKindParam;
  if (IsElementsKindGreaterThanOrEqual(
          elementsKind, kFirstRabGsabFixedTypedArrayElementsKind)) {
    elementsKind = %RawDownCast<ElementsKind>(
        elementsKind - kFirstRabGsabFixedTypedArrayElementsKind +
        kFirstFixedTypedArrayElementsKind);
  }
  if (IsElementsKindGreaterThan(elementsKind, ElementsKind::UINT32_ELEMENTS)) {
    if (elementsKind == ElementsKind::INT32_ELEMENTS) {
      return GetTypedArrayAccessor<Int32Elements>();
    } else if (elementsKind == ElementsKind::FLOAT32_ELEMENTS) {
      return GetTypedArrayAccessor<Float32Elements>();
    } else if (elementsKind == ElementsKind::FLOAT64_ELEMENTS) {
      return GetTypedArrayAccessor<Float64Elements>();
    } else if (elementsKind == ElementsKind::UINT8_CLAMPED_ELEMENTS) {
      return GetTypedArrayAccessor<Uint8ClampedElements>();
    } else if (elementsKind == ElementsKind::BIGUINT64_ELEMENTS) {
      return GetTypedArrayAccessor<BigUint64Elements>();
    } else if (elementsKind == ElementsKind::BIGINT64_ELEMENTS) {
      return GetTypedArrayAccessor<BigInt64Elements>();
    }
  } else {
    if (elementsKind == ElementsKind::UINT8_ELEMENTS) {
      return GetTypedArrayAccessor<Uint8Elements>();
    } else if (elementsKind == ElementsKind::INT8_ELEMENTS) {
      return GetTypedArrayAccessor<Int8Elements>();
    } else if (elementsKind == ElementsKind::UINT16_ELEMENTS) {
      return GetTypedArrayAccessor<Uint16Elements>();
    } else if (elementsKind == ElementsKind::INT16_ELEMENTS) {
      return GetTypedArrayAccessor<Int16Elements>();
    } else if (elementsKind == ElementsKind::UINT32_ELEMENTS) {
      return GetTypedArrayAccessor<Uint32Elements>();
    }
  }
  unreachable;
}

extern macro TypedArrayBuiltinsAssembler::SetJSTypedArrayOnHeapDataPtr(
    JSTypedArray, ByteArray, uintptr): void;
extern macro TypedArrayBuiltinsAssembler::SetJSTypedArrayOffHeapDataPtr(
    JSTypedArray, RawPtr, uintptr): void;
extern macro IsJSArrayBufferViewDetachedOrOutOfBounds(JSArrayBufferView):
    never labels DetachedOrOutOfBounds, NotDetachedNorOutOfBounds;
extern macro IsJSArrayBufferViewDetachedOrOutOfBoundsBoolean(JSArrayBufferView):
    bool;

// AttachedJSTypedArray guards that the array's buffer is not detached.
transient type AttachedJSTypedArray extends JSTypedArray;

macro EnsureAttached(array: JSTypedArray): AttachedJSTypedArray
    labels DetachedOrOutOfBounds {
  try {
    IsJSArrayBufferViewDetachedOrOutOfBounds(array)
        otherwise DetachedOrOutOfBounds, NotDetachedNorOutOfBounds;
  } label NotDetachedNorOutOfBounds {
    return %RawDownCast<AttachedJSTypedArray>(array);
  }
}

struct AttachedJSTypedArrayAndLength {
  array: AttachedJSTypedArray;
  length: uintptr;
}

macro EnsureAttachedAndReadLength(array: JSTypedArray):
    AttachedJSTypedArrayAndLength
    labels DetachedOrOutOfBounds {
  const length = LoadJSTypedArrayLengthAndCheckDetached(array)
      otherwise DetachedOrOutOfBounds;
  return AttachedJSTypedArrayAndLength{
    array: %RawDownCast<AttachedJSTypedArray>(array),
    length: length
  };
}

struct AttachedJSTypedArrayWitness {
  macro GetStable(): JSTypedArray {
    return this.stable;
  }

  macro RecheckIndex(index: uintptr): void labels DetachedOrOutOfBounds {
    const length = LoadJSTypedArrayLengthAndCheckDetached(this.stable)
        otherwise DetachedOrOutOfBounds;
    if (index >= length) {
      goto DetachedOrOutOfBounds;
    }
    this.unstable = %RawDownCast<AttachedJSTypedArray>(this.stable);
  }

  macro Load(implicit context: Context)(k: uintptr): JSAny {
    const lf: LoadNumericFn = this.loadfn;
    return lf(this.unstable, k);
  }

  stable: JSTypedArray;
  unstable: AttachedJSTypedArray;
  loadfn: LoadNumericFn;
}

macro NewAttachedJSTypedArrayWitness(array: AttachedJSTypedArray):
    AttachedJSTypedArrayWitness {
  const kind = array.elements_kind;
  const accessor: TypedArrayAccessor = GetTypedArrayAccessor(kind);
  return AttachedJSTypedArrayWitness{
    stable: array,
    unstable: array,
    loadfn: accessor.loadNumericFn
  };
}

macro KindForArrayType<T : type extends ElementsKind>(): constexpr ElementsKind;
KindForArrayType<Uint8Elements>(): constexpr ElementsKind {
  return ElementsKind::UINT8_ELEMENTS;
}
KindForArrayType<Int8Elements>(): constexpr ElementsKind {
  return ElementsKind::INT8_ELEMENTS;
}
KindForArrayType<Uint16Elements>(): constexpr ElementsKind {
  return ElementsKind::UINT16_ELEMENTS;
}
KindForArrayType<Int16Elements>(): constexpr ElementsKind {
  return ElementsKind::INT16_ELEMENTS;
}
KindForArrayType<Uint32Elements>(): constexpr ElementsKind {
  return ElementsKind::UINT32_ELEMENTS;
}
KindForArrayType<Int32Elements>(): constexpr ElementsKind {
  return ElementsKind::INT32_ELEMENTS;
}
KindForArrayType<Float32Elements>(): constexpr ElementsKind {
  return ElementsKind::FLOAT32_ELEMENTS;
}
KindForArrayType<Float64Elements>(): constexpr ElementsKind {
  return ElementsKind::FLOAT64_ELEMENTS;
}
KindForArrayType<Uint8ClampedElements>(): constexpr ElementsKind {
  return ElementsKind::UINT8_CLAMPED_ELEMENTS;
}
KindForArrayType<BigUint64Elements>(): constexpr ElementsKind {
  return ElementsKind::BIGUINT64_ELEMENTS;
}
KindForArrayType<BigInt64Elements>(): constexpr ElementsKind {
  return ElementsKind::BIGINT64_ELEMENTS;
}

builtin LoadTypedElement<T : type extends ElementsKind>(
    array: JSTypedArray, index: uintptr): Numeric {
  return LoadFixedTypedArrayElementAsTagged(
      array.data_ptr, index, KindForArrayType<T>());
}

builtin StoreTypedElementNumeric<T : type extends ElementsKind>(
    context: Context, typedArray: JSTypedArray, index: uintptr,
    value: Numeric): Smi {
  StoreJSTypedArrayElementFromNumeric(
      context, typedArray, index, value, KindForArrayType<T>());
  return kStoreSucceded;
}

// Returns True on success or False if the typedArrays was detached.
builtin StoreTypedElementJSAny<T : type extends ElementsKind>(
    context: Context, typedArray: JSTypedArray, index: uintptr,
    value: JSAny): Smi {
  try {
    StoreJSTypedArrayElementFromTagged(
        context, typedArray, index, value, KindForArrayType<T>())
        otherwise IfDetached;
  } label IfDetached {
    return kStoreFailureArrayDetached;
  }
  return kStoreSucceded;
}
}