// 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 == UINT8_ELEMENTS) {
      return 'DataView.prototype.getUint8';
    } else if constexpr (kind == INT8_ELEMENTS) {
      return 'DataView.prototype.getInt8';
    } else if constexpr (kind == UINT16_ELEMENTS) {
      return 'DataView.prototype.getUint16';
    } else if constexpr (kind == INT16_ELEMENTS) {
      return 'DataView.prototype.getInt16';
    } else if constexpr (kind == UINT32_ELEMENTS) {
      return 'DataView.prototype.getUint32';
    } else if constexpr (kind == INT32_ELEMENTS) {
      return 'DataView.prototype.getInt32';
    } else if constexpr (kind == FLOAT32_ELEMENTS) {
      return 'DataView.prototype.getFloat32';
    } else if constexpr (kind == FLOAT64_ELEMENTS) {
      return 'DataView.prototype.getFloat64';
    } else if constexpr (kind == BIGINT64_ELEMENTS) {
      return 'DataView.prototype.getBigInt64';
    } else if constexpr (kind == BIGUINT64_ELEMENTS) {
      return 'DataView.prototype.getBigUint64';
    } else {
      unreachable;
    }
  }

  macro MakeDataViewSetterNameString(kind: constexpr ElementsKind): String {
    if constexpr (kind == UINT8_ELEMENTS) {
      return 'DataView.prototype.setUint8';
    } else if constexpr (kind == INT8_ELEMENTS) {
      return 'DataView.prototype.setInt8';
    } else if constexpr (kind == UINT16_ELEMENTS) {
      return 'DataView.prototype.setUint16';
    } else if constexpr (kind == INT16_ELEMENTS) {
      return 'DataView.prototype.setInt16';
    } else if constexpr (kind == UINT32_ELEMENTS) {
      return 'DataView.prototype.setUint32';
    } else if constexpr (kind == INT32_ELEMENTS) {
      return 'DataView.prototype.setInt32';
    } else if constexpr (kind == FLOAT32_ELEMENTS) {
      return 'DataView.prototype.setFloat32';
    } else if constexpr (kind == FLOAT64_ELEMENTS) {
      return 'DataView.prototype.setFloat64';
    } else if constexpr (kind == BIGINT64_ELEMENTS) {
      return 'DataView.prototype.setBigInt64';
    } else if constexpr (kind == 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(kIncompatibleMethodReceiver, method);
    }
  }

  // ES6 section 24.2.4.1 get DataView.prototype.buffer
  javascript builtin DataViewPrototypeGetBuffer(
      js-implicit context: Context,
      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: Context, 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: Context, 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, offset));
    } else {
      return Convert<Smi>(LoadUint8(buffer.backing_store, offset));
    }
  }

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

    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;

    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 == INT32_ELEMENTS) {
      return Convert<Number>(Signed(result));
    } else if constexpr (kind == UINT32_ELEMENTS) {
      return Convert<Number>(result);
    } else if constexpr (kind == 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;

    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;

    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(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.
      if constexpr (Is64()) {
        // Given that
        // a) getIndex is in [0, kMaxSafeInteger] range
        // b) elementSize is in [1, 8] range
        // the addition can't overflow.
        if (getIndex + elementSize > viewSize) goto RangeError;
      } else {
        // In order to avoid operating on float64s we deal with uintptr values
        // and do two comparisons to handle potential uintptr overflow on
        // 32-bit architectures.
        const lastPossibleElementOffset: uintptr = viewSize - elementSize;
        // Check if lastPossibleElementOffset underflowed.
        if (lastPossibleElementOffset > viewSize) goto RangeError;
        if (getIndex > lastPossibleElementOffset) goto RangeError;
      }

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

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

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

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

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

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

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

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

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

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

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

  transitioning javascript builtin DataViewPrototypeGetBigInt64(
      js-implicit context: Context, receiver: JSAny)(...arguments): JSAny {
    const offset: JSAny = arguments.length > 0 ? arguments[0] : Undefined;
    const isLittleEndian: JSAny =
        arguments.length > 1 ? arguments[1] : Undefined;
    return DataViewGet(
        context, receiver, offset, isLittleEndian, 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, offset, value & 0xFF);
  }

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

    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;

    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;

    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 == BIGUINT64_ELEMENTS || kind == 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(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.
      if constexpr (Is64()) {
        // Given that
        // a) getIndex is in [0, kMaxSafeInteger] range
        // b) elementSize is in [1, 8] range
        // the addition can't overflow.
        if (getIndex + elementSize > viewSize) goto RangeError;
      } else {
        // In order to avoid operating on float64s we deal with uintptr values
        // and do two comparisons to handle potential uintptr overflow on
        // 32-bit architectures.
        const lastPossibleElementOffset: uintptr = viewSize - elementSize;
        // Check if lastPossibleElementOffset underflowed.
        if (lastPossibleElementOffset > viewSize) goto RangeError;
        if (getIndex > lastPossibleElementOffset) goto RangeError;
      }

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

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

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

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

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

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

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

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

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

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

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

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