// 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-data-view-gen.h'

namespace data_view {

macro MakeDataViewGetterNameString(kind: constexpr ElementsKind): String {
  if constexpr (kind == ElementsKind::UINT8_ELEMENTS) {
    return 'DataView.prototype.getUint8';
  } else if constexpr (kind == ElementsKind::INT8_ELEMENTS) {
    return 'DataView.prototype.getInt8';
  } else if constexpr (kind == ElementsKind::UINT16_ELEMENTS) {
    return 'DataView.prototype.getUint16';
  } else if constexpr (kind == ElementsKind::INT16_ELEMENTS) {
    return 'DataView.prototype.getInt16';
  } else if constexpr (kind == ElementsKind::UINT32_ELEMENTS) {
    return 'DataView.prototype.getUint32';
  } else if constexpr (kind == ElementsKind::INT32_ELEMENTS) {
    return 'DataView.prototype.getInt32';
  } else if constexpr (kind == ElementsKind::FLOAT32_ELEMENTS) {
    return 'DataView.prototype.getFloat32';
  } else if constexpr (kind == ElementsKind::FLOAT64_ELEMENTS) {
    return 'DataView.prototype.getFloat64';
  } else if constexpr (kind == ElementsKind::BIGINT64_ELEMENTS) {
    return 'DataView.prototype.getBigInt64';
  } else if constexpr (kind == ElementsKind::BIGUINT64_ELEMENTS) {
    return 'DataView.prototype.getBigUint64';
  } else {
    unreachable;
  }
}

macro MakeDataViewSetterNameString(kind: constexpr ElementsKind): String {
  if constexpr (kind == ElementsKind::UINT8_ELEMENTS) {
    return 'DataView.prototype.setUint8';
  } else if constexpr (kind == ElementsKind::INT8_ELEMENTS) {
    return 'DataView.prototype.setInt8';
  } else if constexpr (kind == ElementsKind::UINT16_ELEMENTS) {
    return 'DataView.prototype.setUint16';
  } else if constexpr (kind == ElementsKind::INT16_ELEMENTS) {
    return 'DataView.prototype.setInt16';
  } else if constexpr (kind == ElementsKind::UINT32_ELEMENTS) {
    return 'DataView.prototype.setUint32';
  } else if constexpr (kind == ElementsKind::INT32_ELEMENTS) {
    return 'DataView.prototype.setInt32';
  } else if constexpr (kind == ElementsKind::FLOAT32_ELEMENTS) {
    return 'DataView.prototype.setFloat32';
  } else if constexpr (kind == ElementsKind::FLOAT64_ELEMENTS) {
    return 'DataView.prototype.setFloat64';
  } else if constexpr (kind == ElementsKind::BIGINT64_ELEMENTS) {
    return 'DataView.prototype.setBigInt64';
  } else if constexpr (kind == ElementsKind::BIGUINT64_ELEMENTS) {
    return 'DataView.prototype.setBigUint64';
  } else {
    unreachable;
  }
}

macro WasDetached(view: JSArrayBufferView): bool {
  return IsDetachedBuffer(view.buffer);
}

macro ValidateDataView(context: Context, o: JSAny, method: String): JSDataView {
  try {
    return Cast<JSDataView>(o) otherwise CastError;
  } label CastError {
    ThrowTypeError(MessageTemplate::kIncompatibleMethodReceiver, method);
  }
}

// ES6 section 24.2.4.1 get DataView.prototype.buffer
javascript builtin DataViewPrototypeGetBuffer(
    js-implicit context: NativeContext,
    receiver: JSAny)(...arguments): JSArrayBuffer {
  const dataView: JSDataView =
      ValidateDataView(context, receiver, 'get DataView.prototype.buffer');
  return dataView.buffer;
}

// ES6 section 24.2.4.2 get DataView.prototype.byteLength
javascript builtin DataViewPrototypeGetByteLength(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): Number {
  const dataView: JSDataView =
      ValidateDataView(context, receiver, 'get DataView.prototype.byte_length');
  if (WasDetached(dataView)) {
    // TODO(bmeurer): According to the ES6 spec, we should throw a TypeError
    // here if the JSArrayBuffer of the {dataView} was detached.
    return 0;
  }
  return Convert<Number>(dataView.byte_length);
}

// ES6 section 24.2.4.3 get DataView.prototype.byteOffset
javascript builtin DataViewPrototypeGetByteOffset(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): Number {
  const dataView: JSDataView =
      ValidateDataView(context, receiver, 'get DataView.prototype.byte_offset');
  if (WasDetached(dataView)) {
    // TODO(bmeurer): According to the ES6 spec, we should throw a TypeError
    // here if the JSArrayBuffer of the {dataView} was detached.
    return 0;
  }
  return Convert<Number>(dataView.byte_offset);
}

extern macro BitcastInt32ToFloat32(uint32): float32;
extern macro BitcastFloat32ToInt32(float32): uint32;
extern macro Float64ExtractLowWord32(float64): uint32;
extern macro Float64ExtractHighWord32(float64): uint32;
extern macro Float64InsertLowWord32(float64, uint32): float64;
extern macro Float64InsertHighWord32(float64, uint32): float64;

extern macro DataViewBuiltinsAssembler::LoadUint8(RawPtr, uintptr): uint32;
extern macro DataViewBuiltinsAssembler::LoadInt8(RawPtr, uintptr): int32;

macro LoadDataView8(
    buffer: JSArrayBuffer, offset: uintptr, signed: constexpr bool): Smi {
  if constexpr (signed) {
    return Convert<Smi>(LoadInt8(buffer.backing_store_ptr, offset));
  } else {
    return Convert<Smi>(LoadUint8(buffer.backing_store_ptr, offset));
  }
}

macro LoadDataView16(
    buffer: JSArrayBuffer, offset: uintptr, requestedLittleEndian: bool,
    signed: constexpr bool): Number {
  const dataPointer: RawPtr = buffer.backing_store_ptr;

  let b0: int32;
  let b1: int32;
  let result: int32;

  // Sign-extend the most significant byte by loading it as an Int8.
  if (requestedLittleEndian) {
    b0 = Signed(LoadUint8(dataPointer, offset));
    b1 = LoadInt8(dataPointer, offset + 1);
    result = (b1 << 8) + b0;
  } else {
    b0 = LoadInt8(dataPointer, offset);
    b1 = Signed(LoadUint8(dataPointer, offset + 1));
    result = (b0 << 8) + b1;
  }
  if constexpr (signed) {
    return Convert<Smi>(result);
  } else {
    // Bit-mask the higher bits to prevent sign extension if we're unsigned.
    return Convert<Smi>(result & 0xFFFF);
  }
}

macro LoadDataView32(
    buffer: JSArrayBuffer, offset: uintptr, requestedLittleEndian: bool,
    kind: constexpr ElementsKind): Number {
  const dataPointer: RawPtr = buffer.backing_store_ptr;

  const b0: uint32 = LoadUint8(dataPointer, offset);
  const b1: uint32 = LoadUint8(dataPointer, offset + 1);
  const b2: uint32 = LoadUint8(dataPointer, offset + 2);
  const b3: uint32 = LoadUint8(dataPointer, offset + 3);
  let result: uint32;

  if (requestedLittleEndian) {
    result = (b3 << 24) | (b2 << 16) | (b1 << 8) | b0;
  } else {
    result = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
  }

  if constexpr (kind == ElementsKind::INT32_ELEMENTS) {
    return Convert<Number>(Signed(result));
  } else if constexpr (kind == ElementsKind::UINT32_ELEMENTS) {
    return Convert<Number>(result);
  } else if constexpr (kind == ElementsKind::FLOAT32_ELEMENTS) {
    const floatRes: float64 = Convert<float64>(BitcastInt32ToFloat32(result));
    return Convert<Number>(floatRes);
  } else {
    unreachable;
  }
}

macro LoadDataViewFloat64(
    buffer: JSArrayBuffer, offset: uintptr,
    requestedLittleEndian: bool): Number {
  const dataPointer: RawPtr = buffer.backing_store_ptr;

  const b0: uint32 = LoadUint8(dataPointer, offset);
  const b1: uint32 = LoadUint8(dataPointer, offset + 1);
  const b2: uint32 = LoadUint8(dataPointer, offset + 2);
  const b3: uint32 = LoadUint8(dataPointer, offset + 3);
  const b4: uint32 = LoadUint8(dataPointer, offset + 4);
  const b5: uint32 = LoadUint8(dataPointer, offset + 5);
  const b6: uint32 = LoadUint8(dataPointer, offset + 6);
  const b7: uint32 = LoadUint8(dataPointer, offset + 7);
  let lowWord: uint32;
  let highWord: uint32;

  if (requestedLittleEndian) {
    lowWord = (b3 << 24) | (b2 << 16) | (b1 << 8) | b0;
    highWord = (b7 << 24) | (b6 << 16) | (b5 << 8) | b4;
  } else {
    highWord = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
    lowWord = (b4 << 24) | (b5 << 16) | (b6 << 8) | b7;
  }

  let result: float64 = 0;
  result = Float64InsertLowWord32(result, lowWord);
  result = Float64InsertHighWord32(result, highWord);

  return Convert<Number>(result);
}

const kZeroDigitBigInt: constexpr int31 = 0;
const kOneDigitBigInt: constexpr int31 = 1;
const kTwoDigitBigInt: constexpr int31 = 2;

// Create a BigInt on a 64-bit architecture from two 32-bit values.
macro MakeBigIntOn64Bit(implicit context: Context)(
    lowWord: uint32, highWord: uint32, signed: constexpr bool): BigInt {
  // 0n is represented by a zero-length BigInt.
  if (lowWord == 0 && highWord == 0) {
    return Convert<BigInt>(bigint::AllocateBigInt(kZeroDigitBigInt));
  }

  let sign: uint32 = bigint::kPositiveSign;
  const highPart: intptr = Signed(Convert<uintptr>(highWord));
  const lowPart: intptr = Signed(Convert<uintptr>(lowWord));
  let rawValue: intptr = (highPart << 32) + lowPart;

  if constexpr (signed) {
    if (rawValue < 0) {
      sign = bigint::kNegativeSign;
      // We have to store the absolute value of rawValue in the digit.
      rawValue = 0 - rawValue;
    }
  }

  // Allocate the BigInt and store the absolute value.
  const result: MutableBigInt =
      bigint::AllocateEmptyBigInt(sign, kOneDigitBigInt);
  bigint::StoreBigIntDigit(result, 0, Unsigned(rawValue));
  return Convert<BigInt>(result);
}

// Create a BigInt on a 32-bit architecture from two 32-bit values.
macro MakeBigIntOn32Bit(implicit context: Context)(
    lowWord: uint32, highWord: uint32, signed: constexpr bool): BigInt {
  // 0n is represented by a zero-length BigInt.
  if (lowWord == 0 && highWord == 0) {
    return Convert<BigInt>(bigint::AllocateBigInt(kZeroDigitBigInt));
  }

  // On a 32-bit platform, we might need 1 or 2 digits to store the number.
  let needTwoDigits: bool = false;
  let sign: uint32 = bigint::kPositiveSign;

  // We need to do some math on lowWord and highWord,
  // so Convert them to int32.
  let lowPart: int32 = Signed(lowWord);
  let highPart: int32 = Signed(highWord);

  // If highWord == 0, the number is positive, and we only need 1 digit,
  // so we don't have anything to do.
  // Otherwise, all cases are possible.
  if (highWord != 0) {
    if constexpr (signed) {
      // If highPart < 0, the number is always negative.
      if (highPart < 0) {
        sign = bigint::kNegativeSign;

        // We have to compute the absolute value by hand.
        // There will be a negative carry from the low word
        // to the high word iff low != 0.
        highPart = 0 - highPart;
        if (lowPart != 0) {
          highPart = highPart - 1;
        }
        lowPart = 0 - lowPart;

        // Here, highPart could be 0 again so we might have 1 or 2 digits.
        if (highPart != 0) {
          needTwoDigits = true;
        }

      } else {
        // In this case, the number is positive, and we need 2 digits.
        needTwoDigits = true;
      }

    } else {
      // In this case, the number is positive (unsigned),
      // and we need 2 digits.
      needTwoDigits = true;
    }
  }

  // Allocate the BigInt with the right sign and length.
  let result: MutableBigInt;
  if (needTwoDigits) {
    result = bigint::AllocateEmptyBigInt(sign, kTwoDigitBigInt);
  } else {
    result = bigint::AllocateEmptyBigInt(sign, kOneDigitBigInt);
  }

  // Finally, write the digit(s) to the BigInt.
  bigint::StoreBigIntDigit(result, 0, Unsigned(Convert<intptr>(lowPart)));
  if (needTwoDigits) {
    bigint::StoreBigIntDigit(result, 1, Unsigned(Convert<intptr>(highPart)));
  }
  return Convert<BigInt>(result);
}

macro MakeBigInt(implicit context: Context)(
    lowWord: uint32, highWord: uint32, signed: constexpr bool): BigInt {
  // A BigInt digit has the platform word size, so we only need one digit
  // on 64-bit platforms but may need two on 32-bit.
  if constexpr (Is64()) {
    return MakeBigIntOn64Bit(lowWord, highWord, signed);
  } else {
    return MakeBigIntOn32Bit(lowWord, highWord, signed);
  }
}

macro LoadDataViewBigInt(implicit context: Context)(
    buffer: JSArrayBuffer, offset: uintptr, requestedLittleEndian: bool,
    signed: constexpr bool): BigInt {
  const dataPointer: RawPtr = buffer.backing_store_ptr;

  const b0: uint32 = LoadUint8(dataPointer, offset);
  const b1: uint32 = LoadUint8(dataPointer, offset + 1);
  const b2: uint32 = LoadUint8(dataPointer, offset + 2);
  const b3: uint32 = LoadUint8(dataPointer, offset + 3);
  const b4: uint32 = LoadUint8(dataPointer, offset + 4);
  const b5: uint32 = LoadUint8(dataPointer, offset + 5);
  const b6: uint32 = LoadUint8(dataPointer, offset + 6);
  const b7: uint32 = LoadUint8(dataPointer, offset + 7);
  let lowWord: uint32;
  let highWord: uint32;

  if (requestedLittleEndian) {
    lowWord = (b3 << 24) | (b2 << 16) | (b1 << 8) | b0;
    highWord = (b7 << 24) | (b6 << 16) | (b5 << 8) | b4;
  } else {
    highWord = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
    lowWord = (b4 << 24) | (b5 << 16) | (b6 << 8) | b7;
  }

  return MakeBigInt(lowWord, highWord, signed);
}

extern macro DataViewBuiltinsAssembler::DataViewElementSize(
    constexpr ElementsKind): constexpr int31;

// GetViewValue ( view, requestIndex, isLittleEndian, type )
// https://tc39.es/ecma262/#sec-getviewvalue
transitioning macro DataViewGet(
    context: Context, receiver: JSAny, requestIndex: JSAny,
    requestedLittleEndian: JSAny, kind: constexpr ElementsKind): Numeric {
  // 1. Perform ? RequireInternalSlot(view, [[DataView]]).
  // 2. Assert: view has a [[ViewedArrayBuffer]] internal slot.
  const dataView: JSDataView =
      ValidateDataView(context, receiver, MakeDataViewGetterNameString(kind));

  try {
    // 3. Let getIndex be ? ToIndex(requestIndex).
    const getIndex: uintptr = ToIndex(requestIndex) otherwise RangeError;

    // 4. Set isLittleEndian to ! ToBoolean(isLittleEndian).
    const littleEndian: bool = ToBoolean(requestedLittleEndian);

    // 5. Let buffer be view.[[ViewedArrayBuffer]].
    const buffer: JSArrayBuffer = dataView.buffer;

    // 6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
    if (IsDetachedBuffer(buffer)) {
      ThrowTypeError(
          MessageTemplate::kDetachedOperation,
          MakeDataViewGetterNameString(kind));
    }

    // 7. Let viewOffset be view.[[ByteOffset]].
    const viewOffset: uintptr = dataView.byte_offset;

    // 8. Let viewSize be view.[[ByteLength]].
    const viewSize: uintptr = dataView.byte_length;

    // 9. Let elementSize be the Element Size value specified in Table 62
    // for Element Type type.
    const elementSize: uintptr = DataViewElementSize(kind);

    // 10. If getIndex + elementSize > viewSize, throw a RangeError exception.
    CheckIntegerIndexAdditionOverflow(getIndex, elementSize, viewSize)
        otherwise RangeError;

    // 11. Let bufferIndex be getIndex + viewOffset.
    const bufferIndex: uintptr = getIndex + viewOffset;

    if constexpr (kind == ElementsKind::UINT8_ELEMENTS) {
      return LoadDataView8(buffer, bufferIndex, false);
    } else if constexpr (kind == ElementsKind::INT8_ELEMENTS) {
      return LoadDataView8(buffer, bufferIndex, true);
    } else if constexpr (kind == ElementsKind::UINT16_ELEMENTS) {
      return LoadDataView16(buffer, bufferIndex, littleEndian, false);
    } else if constexpr (kind == ElementsKind::INT16_ELEMENTS) {
      return LoadDataView16(buffer, bufferIndex, littleEndian, true);
    } else if constexpr (kind == ElementsKind::UINT32_ELEMENTS) {
      return LoadDataView32(buffer, bufferIndex, littleEndian, kind);
    } else if constexpr (kind == ElementsKind::INT32_ELEMENTS) {
      return LoadDataView32(buffer, bufferIndex, littleEndian, kind);
    } else if constexpr (kind == ElementsKind::FLOAT32_ELEMENTS) {
      return LoadDataView32(buffer, bufferIndex, littleEndian, kind);
    } else if constexpr (kind == ElementsKind::FLOAT64_ELEMENTS) {
      return LoadDataViewFloat64(buffer, bufferIndex, littleEndian);
    } else if constexpr (kind == ElementsKind::BIGUINT64_ELEMENTS) {
      return LoadDataViewBigInt(buffer, bufferIndex, littleEndian, false);
    } else if constexpr (kind == ElementsKind::BIGINT64_ELEMENTS) {
      return LoadDataViewBigInt(buffer, bufferIndex, littleEndian, true);
    } else {
      unreachable;
    }
  } label RangeError {
    ThrowRangeError(MessageTemplate::kInvalidDataViewAccessorOffset);
  }
}

transitioning javascript builtin DataViewPrototypeGetUint8(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const offset: JSAny = arguments[0];
  return DataViewGet(
      context, receiver, offset, Undefined, ElementsKind::UINT8_ELEMENTS);
}

transitioning javascript builtin DataViewPrototypeGetInt8(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const offset: JSAny = arguments[0];
  return DataViewGet(
      context, receiver, offset, Undefined, ElementsKind::INT8_ELEMENTS);
}

transitioning javascript builtin DataViewPrototypeGetUint16(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const offset: JSAny = arguments[0];
  const isLittleEndian: JSAny = arguments[1];
  return DataViewGet(
      context, receiver, offset, isLittleEndian, ElementsKind::UINT16_ELEMENTS);
}

transitioning javascript builtin DataViewPrototypeGetInt16(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const offset: JSAny = arguments[0];
  const isLittleEndian: JSAny = arguments[1];
  return DataViewGet(
      context, receiver, offset, isLittleEndian, ElementsKind::INT16_ELEMENTS);
}

transitioning javascript builtin DataViewPrototypeGetUint32(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const offset: JSAny = arguments[0];
  const isLittleEndian: JSAny = arguments[1];
  return DataViewGet(
      context, receiver, offset, isLittleEndian, ElementsKind::UINT32_ELEMENTS);
}

transitioning javascript builtin DataViewPrototypeGetInt32(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const offset: JSAny = arguments[0];
  const isLittleEndian: JSAny = arguments[1];
  return DataViewGet(
      context, receiver, offset, isLittleEndian, ElementsKind::INT32_ELEMENTS);
}

transitioning javascript builtin DataViewPrototypeGetFloat32(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const offset: JSAny = arguments[0];
  const isLittleEndian: JSAny = arguments[1];
  return DataViewGet(
      context, receiver, offset, isLittleEndian,
      ElementsKind::FLOAT32_ELEMENTS);
}

transitioning javascript builtin DataViewPrototypeGetFloat64(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const offset: JSAny = arguments[0];
  const isLittleEndian: JSAny = arguments[1];
  return DataViewGet(
      context, receiver, offset, isLittleEndian,
      ElementsKind::FLOAT64_ELEMENTS);
}

transitioning javascript builtin DataViewPrototypeGetBigUint64(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const offset: JSAny = arguments[0];
  const isLittleEndian: JSAny = arguments[1];
  return DataViewGet(
      context, receiver, offset, isLittleEndian,
      ElementsKind::BIGUINT64_ELEMENTS);
}

transitioning javascript builtin DataViewPrototypeGetBigInt64(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const offset: JSAny = arguments[0];
  const isLittleEndian: JSAny = arguments[1];
  return DataViewGet(
      context, receiver, offset, isLittleEndian,
      ElementsKind::BIGINT64_ELEMENTS);
}

extern macro ToNumber(Context, JSAny): Number;
extern macro ToBigInt(Context, JSAny): BigInt;
extern macro TruncateFloat64ToWord32(float64): uint32;

extern macro DataViewBuiltinsAssembler::StoreWord8(
    RawPtr, uintptr, uint32): void;

macro StoreDataView8(buffer: JSArrayBuffer, offset: uintptr, value: uint32) {
  StoreWord8(buffer.backing_store_ptr, offset, value & 0xFF);
}

macro StoreDataView16(
    buffer: JSArrayBuffer, offset: uintptr, value: uint32,
    requestedLittleEndian: bool) {
  const dataPointer: RawPtr = buffer.backing_store_ptr;

  const b0: uint32 = value & 0xFF;
  const b1: uint32 = (value >>> 8) & 0xFF;

  if (requestedLittleEndian) {
    StoreWord8(dataPointer, offset, b0);
    StoreWord8(dataPointer, offset + 1, b1);
  } else {
    StoreWord8(dataPointer, offset, b1);
    StoreWord8(dataPointer, offset + 1, b0);
  }
}

macro StoreDataView32(
    buffer: JSArrayBuffer, offset: uintptr, value: uint32,
    requestedLittleEndian: bool) {
  const dataPointer: RawPtr = buffer.backing_store_ptr;

  const b0: uint32 = value & 0xFF;
  const b1: uint32 = (value >>> 8) & 0xFF;
  const b2: uint32 = (value >>> 16) & 0xFF;
  const b3: uint32 = value >>> 24;  // We don't need to mask here.

  if (requestedLittleEndian) {
    StoreWord8(dataPointer, offset, b0);
    StoreWord8(dataPointer, offset + 1, b1);
    StoreWord8(dataPointer, offset + 2, b2);
    StoreWord8(dataPointer, offset + 3, b3);
  } else {
    StoreWord8(dataPointer, offset, b3);
    StoreWord8(dataPointer, offset + 1, b2);
    StoreWord8(dataPointer, offset + 2, b1);
    StoreWord8(dataPointer, offset + 3, b0);
  }
}

macro StoreDataView64(
    buffer: JSArrayBuffer, offset: uintptr, lowWord: uint32, highWord: uint32,
    requestedLittleEndian: bool) {
  const dataPointer: RawPtr = buffer.backing_store_ptr;

  const b0: uint32 = lowWord & 0xFF;
  const b1: uint32 = (lowWord >>> 8) & 0xFF;
  const b2: uint32 = (lowWord >>> 16) & 0xFF;
  const b3: uint32 = lowWord >>> 24;

  const b4: uint32 = highWord & 0xFF;
  const b5: uint32 = (highWord >>> 8) & 0xFF;
  const b6: uint32 = (highWord >>> 16) & 0xFF;
  const b7: uint32 = highWord >>> 24;

  if (requestedLittleEndian) {
    StoreWord8(dataPointer, offset, b0);
    StoreWord8(dataPointer, offset + 1, b1);
    StoreWord8(dataPointer, offset + 2, b2);
    StoreWord8(dataPointer, offset + 3, b3);
    StoreWord8(dataPointer, offset + 4, b4);
    StoreWord8(dataPointer, offset + 5, b5);
    StoreWord8(dataPointer, offset + 6, b6);
    StoreWord8(dataPointer, offset + 7, b7);
  } else {
    StoreWord8(dataPointer, offset, b7);
    StoreWord8(dataPointer, offset + 1, b6);
    StoreWord8(dataPointer, offset + 2, b5);
    StoreWord8(dataPointer, offset + 3, b4);
    StoreWord8(dataPointer, offset + 4, b3);
    StoreWord8(dataPointer, offset + 5, b2);
    StoreWord8(dataPointer, offset + 6, b1);
    StoreWord8(dataPointer, offset + 7, b0);
  }
}

extern macro DataViewBuiltinsAssembler::DataViewDecodeBigIntLength(BigIntBase):
    uint32;
extern macro DataViewBuiltinsAssembler::DataViewDecodeBigIntSign(BigIntBase):
    uint32;

// We might get here a BigInt that is bigger than 64 bits, but we're only
// interested in the 64 lowest ones. This means the lowest BigInt digit
// on 64-bit platforms, and the 2 lowest BigInt digits on 32-bit ones.
macro StoreDataViewBigInt(
    buffer: JSArrayBuffer, offset: uintptr, bigIntValue: BigInt,
    requestedLittleEndian: bool) {
  const length: uint32 = DataViewDecodeBigIntLength(bigIntValue);
  const sign: uint32 = DataViewDecodeBigIntSign(bigIntValue);

  // The 32-bit words that will hold the BigInt's value in
  // two's complement representation.
  let lowWord: uint32 = 0;
  let highWord: uint32 = 0;

  // The length is nonzero if and only if the BigInt's value is nonzero.
  if (length != 0) {
    if constexpr (Is64()) {
      // There is always exactly 1 BigInt digit to load in this case.
      const value: uintptr = bigint::LoadBigIntDigit(bigIntValue, 0);
      lowWord = Convert<uint32>(value);  // Truncates value to 32 bits.
      highWord = Convert<uint32>(value >>> 32);
    } else {  // There might be either 1 or 2 BigInt digits we need to load.
      lowWord = Convert<uint32>(bigint::LoadBigIntDigit(bigIntValue, 0));
      if (length >= 2) {  // Only load the second digit if there is one.
        highWord = Convert<uint32>(bigint::LoadBigIntDigit(bigIntValue, 1));
      }
    }
  }

  if (sign != 0) {  // The number is negative, Convert it.
    highWord = Unsigned(0 - Signed(highWord));
    if (lowWord != 0) {
      highWord = Unsigned(Signed(highWord) - 1);
    }
    lowWord = Unsigned(0 - Signed(lowWord));
  }

  StoreDataView64(buffer, offset, lowWord, highWord, requestedLittleEndian);
}

// SetViewValue ( view, requestIndex, isLittleEndian, type, value )
// https://tc39.es/ecma262/#sec-setviewvalue
transitioning macro DataViewSet(
    context: Context, receiver: JSAny, requestIndex: JSAny, value: JSAny,
    requestedLittleEndian: JSAny, kind: constexpr ElementsKind): JSAny {
  // 1. Perform ? RequireInternalSlot(view, [[DataView]]).
  // 2. Assert: view has a [[ViewedArrayBuffer]] internal slot.
  const dataView: JSDataView =
      ValidateDataView(context, receiver, MakeDataViewSetterNameString(kind));

  try {
    // 3. Let getIndex be ? ToIndex(requestIndex).
    const getIndex: uintptr = ToIndex(requestIndex) otherwise RangeError;

    const littleEndian: bool = ToBoolean(requestedLittleEndian);
    const buffer: JSArrayBuffer = dataView.buffer;

    let numberValue: Numeric;
    if constexpr (
        kind == ElementsKind::BIGUINT64_ELEMENTS ||
        kind == ElementsKind::BIGINT64_ELEMENTS) {
      // 4. If ! IsBigIntElementType(type) is true, let numberValue be
      // ? ToBigInt(value).
      numberValue = ToBigInt(context, value);
    } else {
      // 5. Otherwise, let numberValue be ? ToNumber(value).
      numberValue = ToNumber(context, value);
    }

    // 6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
    if (IsDetachedBuffer(buffer)) {
      ThrowTypeError(
          MessageTemplate::kDetachedOperation,
          MakeDataViewSetterNameString(kind));
    }

    // 9. Let viewOffset be view.[[ByteOffset]].
    const viewOffset: uintptr = dataView.byte_offset;

    // 10. Let viewSize be view.[[ByteLength]].
    const viewSize: uintptr = dataView.byte_length;

    // 11. Let elementSize be the Element Size value specified in Table 62
    // for Element Type type.
    const elementSize: uintptr = DataViewElementSize(kind);

    // 12. If getIndex + elementSize > viewSize, throw a RangeError exception.
    CheckIntegerIndexAdditionOverflow(getIndex, elementSize, viewSize)
        otherwise RangeError;

    // 13. Let bufferIndex be getIndex + viewOffset.
    const bufferIndex: uintptr = getIndex + viewOffset;

    if constexpr (
        kind == ElementsKind::BIGUINT64_ELEMENTS ||
        kind == ElementsKind::BIGINT64_ELEMENTS) {
      // For these elements kinds numberValue is BigInt.
      const bigIntValue: BigInt = %RawDownCast<BigInt>(numberValue);
      StoreDataViewBigInt(buffer, bufferIndex, bigIntValue, littleEndian);
    } else {
      // For these elements kinds numberValue is Number.
      const numValue: Number = %RawDownCast<Number>(numberValue);
      const doubleValue: float64 = ChangeNumberToFloat64(numValue);

      if constexpr (
          kind == ElementsKind::UINT8_ELEMENTS ||
          kind == ElementsKind::INT8_ELEMENTS) {
        StoreDataView8(
            buffer, bufferIndex, TruncateFloat64ToWord32(doubleValue));
      } else if constexpr (
          kind == ElementsKind::UINT16_ELEMENTS ||
          kind == ElementsKind::INT16_ELEMENTS) {
        StoreDataView16(
            buffer, bufferIndex, TruncateFloat64ToWord32(doubleValue),
            littleEndian);
      } else if constexpr (
          kind == ElementsKind::UINT32_ELEMENTS ||
          kind == ElementsKind::INT32_ELEMENTS) {
        StoreDataView32(
            buffer, bufferIndex, TruncateFloat64ToWord32(doubleValue),
            littleEndian);
      } else if constexpr (kind == ElementsKind::FLOAT32_ELEMENTS) {
        const floatValue: float32 = TruncateFloat64ToFloat32(doubleValue);
        StoreDataView32(
            buffer, bufferIndex, BitcastFloat32ToInt32(floatValue),
            littleEndian);
      } else if constexpr (kind == ElementsKind::FLOAT64_ELEMENTS) {
        const lowWord: uint32 = Float64ExtractLowWord32(doubleValue);
        const highWord: uint32 = Float64ExtractHighWord32(doubleValue);
        StoreDataView64(buffer, bufferIndex, lowWord, highWord, littleEndian);
      }
    }
    return Undefined;
  } label RangeError {
    ThrowRangeError(MessageTemplate::kInvalidDataViewAccessorOffset);
  }
}

transitioning javascript builtin DataViewPrototypeSetUint8(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const offset: JSAny = arguments[0];
  const value: JSAny = arguments[1];
  return DataViewSet(
      context, receiver, offset, value, Undefined,
      ElementsKind::UINT8_ELEMENTS);
}

transitioning javascript builtin DataViewPrototypeSetInt8(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const offset: JSAny = arguments[0];
  const value: JSAny = arguments[1];
  return DataViewSet(
      context, receiver, offset, value, Undefined, ElementsKind::INT8_ELEMENTS);
}

transitioning javascript builtin DataViewPrototypeSetUint16(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const offset: JSAny = arguments[0];
  const value: JSAny = arguments[1];
  const isLittleEndian: JSAny = arguments[2];
  return DataViewSet(
      context, receiver, offset, value, isLittleEndian,
      ElementsKind::UINT16_ELEMENTS);
}

transitioning javascript builtin DataViewPrototypeSetInt16(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const offset: JSAny = arguments[0];
  const value: JSAny = arguments[1];
  const isLittleEndian: JSAny = arguments[2];
  return DataViewSet(
      context, receiver, offset, value, isLittleEndian,
      ElementsKind::INT16_ELEMENTS);
}

transitioning javascript builtin DataViewPrototypeSetUint32(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const offset: JSAny = arguments[0];
  const value: JSAny = arguments[1];
  const isLittleEndian: JSAny = arguments[2];
  return DataViewSet(
      context, receiver, offset, value, isLittleEndian,
      ElementsKind::UINT32_ELEMENTS);
}

transitioning javascript builtin DataViewPrototypeSetInt32(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const offset: JSAny = arguments[0];
  const value: JSAny = arguments[1];
  const isLittleEndian: JSAny = arguments[2];
  return DataViewSet(
      context, receiver, offset, value, isLittleEndian,
      ElementsKind::INT32_ELEMENTS);
}

transitioning javascript builtin DataViewPrototypeSetFloat32(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const offset: JSAny = arguments[0];
  const value: JSAny = arguments[1];
  const isLittleEndian: JSAny = arguments[2];
  return DataViewSet(
      context, receiver, offset, value, isLittleEndian,
      ElementsKind::FLOAT32_ELEMENTS);
}

transitioning javascript builtin DataViewPrototypeSetFloat64(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const offset: JSAny = arguments[0];
  const value: JSAny = arguments[1];
  const isLittleEndian: JSAny = arguments[2];
  return DataViewSet(
      context, receiver, offset, value, isLittleEndian,
      ElementsKind::FLOAT64_ELEMENTS);
}

transitioning javascript builtin DataViewPrototypeSetBigUint64(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const offset: JSAny = arguments[0];
  const value: JSAny = arguments[1];
  const isLittleEndian: JSAny = arguments[2];
  return DataViewSet(
      context, receiver, offset, value, isLittleEndian,
      ElementsKind::BIGUINT64_ELEMENTS);
}

transitioning javascript builtin DataViewPrototypeSetBigInt64(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const offset: JSAny = arguments[0];
  const value: JSAny = arguments[1];
  const isLittleEndian: JSAny = arguments[2];
  return DataViewSet(
      context, receiver, offset, value, isLittleEndian,
      ElementsKind::BIGINT64_ELEMENTS);
}
}