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

// Unfortunately, MutableSlice<> is currently not a subtype of ConstSlice.
// This would require struct subtyping, which is not yet supported.
type MutableSlice<T: type> extends torque_internal::Slice<T, &T>;
type ConstSlice<T: type> extends torque_internal::Slice<T, const &T>;

macro Subslice<T: type>(slice: ConstSlice<T>, start: intptr, length: intptr):
    ConstSlice<T>labels OutOfBounds {
  if (Unsigned(length) > Unsigned(slice.length)) goto OutOfBounds;
  if (Unsigned(start) > Unsigned(slice.length - length)) goto OutOfBounds;
  const offset = slice.offset + torque_internal::TimesSizeOf<T>(start);
  return torque_internal::unsafe::NewConstSlice<T>(
      slice.object, offset, length);
}
macro Subslice<T: type>(slice: MutableSlice<T>, start: intptr, length: intptr):
    MutableSlice<T>labels OutOfBounds {
  if (Unsigned(length) > Unsigned(slice.length)) goto OutOfBounds;
  if (Unsigned(start) > Unsigned(slice.length - length)) goto OutOfBounds;
  const offset = slice.offset + torque_internal::TimesSizeOf<T>(start);
  return torque_internal::unsafe::NewMutableSlice<T>(
      slice.object, offset, length);
}

namespace unsafe {

macro AddOffset<T: type>(ref: &T, offset: intptr): &T {
  return torque_internal::unsafe::NewReference<T>(
      ref.object, ref.offset + torque_internal::TimesSizeOf<T>(offset));
}

macro AddOffset<T: type>(ref: const &T, offset: intptr): const &T {
  return torque_internal::unsafe::NewReference<T>(
      ref.object, ref.offset + torque_internal::TimesSizeOf<T>(offset));
}

}  // namespace unsafe

namespace torque_internal {
// Unsafe is a marker that we require to be passed when calling internal APIs
// that might lead to unsoundness when used incorrectly. Unsafe markers should
// therefore not be instantiated anywhere outside of this namespace.
struct Unsafe {}

// Size of a type in memory (on the heap). For class types, this is the size
// of the pointer, not of the instance.
intrinsic %SizeOf<T: type>(): constexpr int31;

macro TimesSizeOf<T: type>(i: intptr): intptr {
  return i * %SizeOf<T>();
}

struct Reference<T: type> {
  const object: HeapObject|TaggedZeroPattern;
  const offset: intptr;
  unsafeMarker: Unsafe;
}
type ConstReference<T: type> extends Reference<T>;
type MutableReference<T: type> extends ConstReference<T>;

namespace unsafe {
macro NewReference<T: type>(
    object: HeapObject|TaggedZeroPattern, offset: intptr):&T {
  return %RawDownCast<&T>(
      Reference<T>{object: object, offset: offset, unsafeMarker: Unsafe {}});
}
macro NewOffHeapReference<T: type>(ptr: RawPtr<T>):&T {
  return %RawDownCast<&T>(Reference<T>{
    object: kZeroBitPattern,
    offset: Convert<intptr>(Convert<RawPtr>(ptr)) + kHeapObjectTag,
    unsafeMarker: Unsafe {}
  });
}
macro ReferenceCast<T: type, U: type>(ref:&U):&T {
  const ref = NewReference<T>(ref.object, ref.offset);
  UnsafeCast<T>(*ref);
  return ref;
}

extern macro GCUnsafeReferenceToRawPtr(
    HeapObject | TaggedZeroPattern, intptr): RawPtr;

}  // namespace unsafe

struct Slice<T: type, Reference: type> {
  macro TryAtIndex(index: intptr): Reference labels OutOfBounds {
    if (Convert<uintptr>(index) < Convert<uintptr>(this.length)) {
      return this.UncheckedAtIndex(index);
    } else {
      goto OutOfBounds;
    }
  }
  macro UncheckedAtIndex(index: intptr): Reference {
    return unsafe::NewReference<T>(
        this.object, this.offset + TimesSizeOf<T>(index));
  }

  macro AtIndex(index: intptr): Reference {
    return this.TryAtIndex(index) otherwise unreachable;
  }

  macro AtIndex(index: uintptr): Reference {
    return this.TryAtIndex(Convert<intptr>(index)) otherwise unreachable;
  }

  macro AtIndex(index: constexpr IntegerLiteral): Reference {
    return this.AtIndex(FromConstexpr<uintptr>(index));
  }

  macro AtIndex(index: constexpr int31): Reference {
    const i: intptr = Convert<intptr>(index);
    return this.TryAtIndex(i) otherwise unreachable;
  }

  macro AtIndex(index: Smi): Reference {
    const i: intptr = Convert<intptr>(index);
    return this.TryAtIndex(i) otherwise unreachable;
  }

  macro Iterator(): SliceIterator<T, Reference> {
    const end = this.offset + TimesSizeOf<T>(this.length);
    return SliceIterator<T, Reference>{
      object: this.object,
      start: this.offset,
      end: end,
      unsafeMarker: Unsafe {}
    };
  }
  macro Iterator(
      startIndex: intptr, endIndex: intptr): SliceIterator<T, Reference> {
    check(
        Convert<uintptr>(endIndex) <= Convert<uintptr>(this.length) &&
        Convert<uintptr>(startIndex) <= Convert<uintptr>(endIndex));
    const start = this.offset + TimesSizeOf<T>(startIndex);
    const end = this.offset + TimesSizeOf<T>(endIndex);
    return SliceIterator<T, Reference>{
      object: this.object,
      start,
      end,
      unsafeMarker: Unsafe {}
    };
  }

  // WARNING: This can return a raw pointer into the heap, which is not GC-safe.
  macro GCUnsafeStartPointer(): RawPtr<T> {
    return %RawDownCast<RawPtr<T>>(
        unsafe::GCUnsafeReferenceToRawPtr(this.object, this.offset));
  }

  const object: HeapObject|TaggedZeroPattern;
  const offset: intptr;
  const length: intptr;
  unsafeMarker: Unsafe;
}

namespace unsafe {

macro NewMutableSlice<T: type>(
    object: HeapObject|TaggedZeroPattern, offset: intptr,
    length: intptr): MutableSlice<T> {
  return %RawDownCast<MutableSlice<T>>(Slice<T, &T>{
    object: object,
    offset: offset,
    length: length,
    unsafeMarker: Unsafe {}
  });
}

macro NewConstSlice<T: type>(
    object: HeapObject|TaggedZeroPattern, offset: intptr,
    length: intptr): ConstSlice<T> {
  return %RawDownCast<ConstSlice<T>>(Slice<T, const &T>{
    object: object,
    offset: offset,
    length: length,
    unsafeMarker: Unsafe {}
  });
}

macro NewOffHeapConstSlice<T: type>(
    startPointer: RawPtr<T>, length: intptr): ConstSlice<T> {
  return %RawDownCast<ConstSlice<T>>(Slice<T, const &T>{
    object: kZeroBitPattern,
    offset: Convert<intptr>(Convert<RawPtr>(startPointer)) + kHeapObjectTag,
    length: length,
    unsafeMarker: Unsafe {}
  });
}

}  // namespace unsafe

struct SliceIterator<T: type, Reference: type> {
  macro Empty(): bool {
    return this.start == this.end;
  }

  macro Next(): T labels NoMore {
    return *this.NextReference() otherwise NoMore;
  }

  macro NextReference(): Reference labels NoMore {
    if (this.Empty()) {
      goto NoMore;
    } else {
      const result = unsafe::NewReference<T>(this.object, this.start);
      this.start += %SizeOf<T>();
      return result;
    }
  }

  object: HeapObject|TaggedZeroPattern;
  start: intptr;
  end: intptr;
  unsafeMarker: Unsafe;
}

macro AddIndexedFieldSizeToObjectSize(
    baseSize: intptr, arrayLength: intptr, fieldSize: constexpr int32): intptr {
  const arrayLength = Convert<int32>(arrayLength);
  const byteLength = TryInt32Mul(arrayLength, fieldSize)
      otherwise unreachable;
  return TryIntPtrAdd(baseSize, Convert<intptr>(byteLength))
      otherwise unreachable;
}

macro AlignTagged(x: intptr): intptr {
  // Round up to a multiple of kTaggedSize.
  return (x + kObjectAlignmentMask) & ~kObjectAlignmentMask;
}

macro IsTaggedAligned(x: intptr): bool {
  return (x & kObjectAlignmentMask) == 0;
}

macro ValidAllocationSize(sizeInBytes: intptr, map: Map): bool {
  if (sizeInBytes <= 0) return false;
  if (!IsTaggedAligned(sizeInBytes)) return false;
  const instanceSizeInWords = Convert<intptr>(map.instance_size_in_words);
  return instanceSizeInWords == kVariableSizeSentinel ||
      instanceSizeInWords * kTaggedSize == sizeInBytes;
}

type UninitializedHeapObject extends HeapObject;

extern macro GetInstanceTypeMap(constexpr InstanceType): Map;
extern macro Allocate(
    intptr, constexpr AllocationFlag): UninitializedHeapObject;

const kAllocateBaseFlags: constexpr AllocationFlag =
    AllocationFlag::kAllowLargeObjectAllocation;
macro AllocateFromNew(
    sizeInBytes: intptr, map: Map, pretenured: bool): UninitializedHeapObject {
  dcheck(ValidAllocationSize(sizeInBytes, map));
  if (pretenured) {
    return Allocate(
        sizeInBytes,
        %RawConstexprCast<constexpr AllocationFlag>(
            %RawConstexprCast<constexpr int32>(kAllocateBaseFlags) |
            %RawConstexprCast<constexpr int32>(AllocationFlag::kPretenured)));
  } else {
    return Allocate(sizeInBytes, kAllocateBaseFlags);
  }
}

macro InitializeFieldsFromIterator<T: type, Iterator: type>(
    target: MutableSlice<T>, originIterator: Iterator): void {
  let targetIterator = target.Iterator();
  let originIterator = originIterator;
  while (true) {
    const ref:&T = targetIterator.NextReference() otherwise break;
    *ref = originIterator.Next() otherwise unreachable;
  }
}
// Dummy implementations: do not initialize for UninitializedIterator.
InitializeFieldsFromIterator<char8, UninitializedIterator>(
    _target: MutableSlice<char8>,
    _originIterator: UninitializedIterator): void {}
InitializeFieldsFromIterator<char16, UninitializedIterator>(
    _target: MutableSlice<char16>,
    _originIterator: UninitializedIterator): void {}

extern macro IsDoubleHole(HeapObject, intptr): bool;
extern macro StoreDoubleHole(HeapObject, intptr): void;

macro LoadFloat64OrHole(r:&float64_or_hole): float64_or_hole {
  return float64_or_hole{
    is_hole: IsDoubleHole(
        %RawDownCast<HeapObject>(r.object), r.offset - kHeapObjectTag),
    value: *unsafe::NewReference<float64>(r.object, r.offset)
  };
}
macro StoreFloat64OrHole(r:&float64_or_hole, value: float64_or_hole): void {
  if (value.is_hole) {
    StoreDoubleHole(
        %RawDownCast<HeapObject>(r.object), r.offset - kHeapObjectTag);
  } else {
    *unsafe::NewReference<float64>(r.object, r.offset) = value.value;
  }
}

macro DownCastForTorqueClass<T : type extends HeapObject>(o: HeapObject):
    T labels CastError {
  const map = o.map;
  const minInstanceType = %MinInstanceType<T>();
  const maxInstanceType = %MaxInstanceType<T>();
  if constexpr (minInstanceType == maxInstanceType) {
    if constexpr (%ClassHasMapConstant<T>()) {
      if (map != %GetClassMapConstant<T>()) goto CastError;
    } else {
      if (map.instance_type != minInstanceType) goto CastError;
    }
  } else {
    const diff: int32 = maxInstanceType - minInstanceType;
    const offset = Convert<int32>(Convert<uint16>(map.instance_type)) -
        Convert<int32>(Convert<uint16>(
            FromConstexpr<InstanceType>(minInstanceType)));
    if (Unsigned(offset) > Unsigned(diff)) goto CastError;
  }
  return %RawDownCast<T>(o);
}

extern macro StaticAssert(bool, constexpr string): void;

// This is for the implementation of the dot operator. In any context where the
// dot operator is available, the correct way to get the length of an indexed
// field x from object o is `(&o.x).length`.
intrinsic %IndexedFieldLength<T: type>(o: T, f: constexpr string): intptr;

// If field x is defined as optional, then &o.x returns a reference to the field
// or crashes the program (unreachable) if the field is not present. Usually
// that's the most convenient behavior, but in rare cases such as the
// implementation of the dot operator, we may instead need to get a Slice to the
// optional field, which is either length zero or one depending on whether the
// field is present. This intrinsic provides Slices for both indexed fields
// (equivalent to &o.x) and optional fields.
intrinsic %FieldSlice<T: type, TSlice: type>(
    o: T, f: constexpr string): TSlice;

extern macro GetPendingMessage(): TheHole|JSMessageObject;
extern macro SetPendingMessage(TheHole | JSMessageObject): void;

// This is implicitly performed at the beginning of Torque catch-blocks.
macro GetAndResetPendingMessage(): TheHole|JSMessageObject {
  const message = GetPendingMessage();
  SetPendingMessage(TheHole);
  return message;
}

}  // namespace torque_internal

// Indicates that an array-field should not be initialized.
// For safety reasons, this is only allowed for untagged types.
struct UninitializedIterator {}

// %RawDownCast should *never* be used anywhere in Torque code except for
// in Torque-based UnsafeCast operators preceeded by an appropriate
// type dcheck()
intrinsic %RawDownCast<To: type, From: type>(x: From): To;
intrinsic %RawConstexprCast<To: type, From: type>(f: From): To;

intrinsic %MinInstanceType<T: type>(): constexpr InstanceType;
intrinsic %MaxInstanceType<T: type>(): constexpr InstanceType;

intrinsic %ClassHasMapConstant<T: type>(): constexpr bool;
intrinsic %GetClassMapConstant<T: type>(): Map;

struct IteratorSequence<T: type, FirstIterator: type, SecondIterator: type> {
  macro Empty(): bool {
    return this.first.Empty() && this.second.Empty();
  }

  macro Next(): T labels NoMore {
    return this.first.Next()
        otherwise return (this.second.Next() otherwise NoMore);
  }

  first: FirstIterator;
  second: SecondIterator;
}

macro IteratorSequence<T: type, FirstIterator: type, SecondIterator: type>(
    first: FirstIterator, second: SecondIterator):
    IteratorSequence<T, FirstIterator, SecondIterator> {
  return IteratorSequence<T>{first, second};
}