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

extern macro IsBigInt(HeapObject): bool;
extern macro IsConstructor(HeapObject): bool;
extern macro IsCustomElementsReceiverInstanceType(int32): bool;
extern macro IsExtensibleMap(Map): bool;
extern macro IsNumberNormalized(Number): bool;
extern macro IsSafeInteger(Object): bool;

@export
macro IsAccessorInfo(o: HeapObject): bool {
  return Is<AccessorInfo>(o);
}

@export
macro IsAccessorPair(o: HeapObject): bool {
  return Is<AccessorPair>(o);
}

@export
macro IsAllocationSite(o: HeapObject): bool {
  return Is<AllocationSite>(o);
}

@export
macro IsCell(o: HeapObject): bool {
  return Is<Cell>(o);
}

@export
macro IsCode(o: HeapObject): bool {
  return Is<Code>(o);
}

@export
macro IsCodeDataContainer(o: HeapObject): bool {
  return Is<CodeDataContainer>(o);
}

@export
macro IsContext(o: HeapObject): bool {
  return Is<Context>(o);
}

@export
macro IsCoverageInfo(o: HeapObject): bool {
  return Is<CoverageInfo>(o);
}

@export
macro IsDebugInfo(o: HeapObject): bool {
  return Is<DebugInfo>(o);
}

@export
macro IsFixedDoubleArray(o: HeapObject): bool {
  return Is<FixedDoubleArray>(o);
}

@export
macro IsFeedbackCell(o: HeapObject): bool {
  return Is<FeedbackCell>(o);
}

@export
macro IsFeedbackVector(o: HeapObject): bool {
  return Is<FeedbackVector>(o);
}

@export
macro IsHeapNumber(o: HeapObject): bool {
  return Is<HeapNumber>(o);
}

@export
macro IsNativeContext(o: HeapObject): bool {
  return Is<NativeContext>(o);
}

@export
macro IsNumber(o: Object): bool {
  return Is<Number>(o);
}

@export
macro IsPrivateSymbol(o: HeapObject): bool {
  return Is<PrivateSymbol>(o);
}

@export
macro IsPromiseCapability(o: HeapObject): bool {
  return Is<PromiseCapability>(o);
}

@export
macro IsPromiseFulfillReactionJobTask(o: HeapObject): bool {
  return Is<PromiseFulfillReactionJobTask>(o);
}

@export
macro IsPromiseReaction(o: HeapObject): bool {
  return Is<PromiseReaction>(o);
}

@export
macro IsPromiseRejectReactionJobTask(o: HeapObject): bool {
  return Is<PromiseRejectReactionJobTask>(o);
}

@export
macro IsSharedFunctionInfo(o: HeapObject): bool {
  return Is<SharedFunctionInfo>(o);
}

@export
macro IsSymbol(o: HeapObject): bool {
  return Is<Symbol>(o);
}

extern macro TaggedToHeapObject(Object): HeapObject
    labels CastError;
extern macro TaggedToSmi(Object): Smi
    labels CastError;
extern macro TaggedToPositiveSmi(Object): PositiveSmi
    labels CastError;
extern macro TaggedToDirectString(Object): DirectString
    labels CastError;
extern macro HeapObjectToCallable(HeapObject): Callable
    labels CastError;
extern macro HeapObjectToConstructor(HeapObject): Constructor
    labels CastError;
extern macro HeapObjectToJSFunctionWithPrototypeSlot(HeapObject):
    JSFunctionWithPrototypeSlot
    labels CastError;

macro Cast<A : type extends WeakHeapObject>(o: A|Object): A labels CastError {
  if (!IsWeakOrCleared(o)) goto CastError;
  return %RawDownCast<A>(o);
}

macro Cast<A : type extends Object>(implicit context: Context)(o: MaybeObject):
    A labels CastError {
  typeswitch (o) {
    case (WeakHeapObject): {
      goto CastError;
    }
    case (o: Object): {
      return Cast<A>(o) otherwise CastError;
    }
  }
}

Cast<Undefined>(o: MaybeObject): Undefined labels CastError {
  if (TaggedNotEqual(o, Undefined)) goto CastError;
  return %RawDownCast<Undefined>(o);
}

macro Cast<A : type extends Object>(implicit context: Context)(o: Object): A
    labels CastError {
  return Cast<A>(TaggedToHeapObject(o) otherwise CastError)
      otherwise CastError;
}

// This is required for casting MaybeObject to Object.
Cast<Smi>(o: Object): Smi
    labels CastError {
  return TaggedToSmi(o) otherwise CastError;
}

Cast<PositiveSmi>(o: Object): PositiveSmi
    labels CastError {
  return TaggedToPositiveSmi(o) otherwise CastError;
}

Cast<Zero>(o: Object): Zero labels CastError {
  if (TaggedEqual(o, SmiConstant(0))) return %RawDownCast<Zero>(o);
  goto CastError;
}

Cast<Number>(o: Object): Number
    labels CastError {
  typeswitch (o) {
    case (s: Smi): {
      return s;
    }
    case (n: HeapNumber): {
      return n;
    }
    case (Object): {
      goto CastError;
    }
  }
}

Cast<Undefined>(o: Object): Undefined
    labels CastError {
  const o: MaybeObject = o;
  return Cast<Undefined>(o) otherwise CastError;
}

Cast<Numeric>(o: Object): Numeric labels CastError {
  typeswitch (o) {
    case (o: Number): {
      return o;
    }
    case (o: BigInt): {
      return o;
    }
    case (HeapObject): {
      goto CastError;
    }
  }
}

Cast<TheHole>(o: Object): TheHole labels CastError {
  if (o == TheHole) return %RawDownCast<TheHole>(o);
  goto CastError;
}

Cast<TheHole>(o: HeapObject): TheHole labels CastError {
  const o: Object = o;
  return Cast<TheHole>(o) otherwise CastError;
}

Cast<True>(o: Object): True labels CastError {
  if (o == True) return %RawDownCast<True>(o);
  goto CastError;
}

Cast<True>(o: HeapObject): True labels CastError {
  const o: Object = o;
  return Cast<True>(o) otherwise CastError;
}

Cast<False>(o: Object): False labels CastError {
  if (o == False) return %RawDownCast<False>(o);
  goto CastError;
}

Cast<False>(o: HeapObject): False labels CastError {
  const o: Object = o;
  return Cast<False>(o) otherwise CastError;
}

Cast<Boolean>(o: Object): Boolean labels CastError {
  typeswitch (o) {
    case (o: True): {
      return o;
    }
    case (o: False): {
      return o;
    }
    case (Object): {
      goto CastError;
    }
  }
}

Cast<Boolean>(o: HeapObject): Boolean labels CastError {
  const o: Object = o;
  return Cast<Boolean>(o) otherwise CastError;
}

// TODO(turbofan): These trivial casts for union types should be generated
// automatically.

Cast<JSPrimitive>(o: Object): JSPrimitive labels CastError {
  typeswitch (o) {
    case (o: Numeric): {
      return o;
    }
    case (o: String): {
      return o;
    }
    case (o: Symbol): {
      return o;
    }
    case (o: Boolean): {
      return o;
    }
    case (o: Undefined): {
      return o;
    }
    case (o: Null): {
      return o;
    }
    case (Object): {
      goto CastError;
    }
  }
}

Cast<JSAny>(o: Object): JSAny labels CastError {
  typeswitch (o) {
    case (o: JSPrimitive): {
      return o;
    }
    case (o: JSReceiver): {
      return o;
    }
    case (Object): {
      goto CastError;
    }
  }
}

Cast<JSAny|TheHole>(o: Object): JSAny|TheHole labels CastError {
  typeswitch (o) {
    case (o: JSAny): {
      return o;
    }
    case (o: TheHole): {
      return o;
    }
    case (Object): {
      goto CastError;
    }
  }
}

Cast<Number|TheHole>(o: Object): Number|TheHole labels CastError {
  typeswitch (o) {
    case (o: Number): {
      return o;
    }
    case (o: TheHole): {
      return o;
    }
    case (Object): {
      goto CastError;
    }
  }
}

Cast<Context|Zero|Undefined>(o: Object): Context|Zero|Undefined
    labels CastError {
  typeswitch (o) {
    case (o: Context): {
      return o;
    }
    case (o: Zero): {
      return o;
    }
    case (o: Undefined): {
      return o;
    }
    case (Object): {
      goto CastError;
    }
  }
}

macro Cast<A : type extends HeapObject>(o: HeapObject): A
    labels CastError;

Cast<HeapObject>(o: HeapObject): HeapObject
labels _CastError {
  return o;
}

Cast<Null>(o: HeapObject): Null
    labels CastError {
  if (o != Null) goto CastError;
  return %RawDownCast<Null>(o);
}

Cast<Undefined>(o: HeapObject): Undefined
    labels CastError {
  const o: MaybeObject = o;
  return Cast<Undefined>(o) otherwise CastError;
}

Cast<EmptyFixedArray>(o: Object): EmptyFixedArray
    labels CastError {
  if (o != kEmptyFixedArray) goto CastError;
  return %RawDownCast<EmptyFixedArray>(o);
}
Cast<EmptyFixedArray>(o: HeapObject): EmptyFixedArray
    labels CastError {
  const o: Object = o;
  return Cast<EmptyFixedArray>(o) otherwise CastError;
}

Cast<(FixedDoubleArray | EmptyFixedArray)>(o: HeapObject): FixedDoubleArray|
    EmptyFixedArray labels CastError {
  typeswitch (o) {
    case (o: EmptyFixedArray): {
      return o;
    }
    case (o: FixedDoubleArray): {
      return o;
    }
    case (HeapObject): {
      goto CastError;
    }
  }
}

Cast<Callable>(o: HeapObject): Callable
    labels CastError {
  return HeapObjectToCallable(o) otherwise CastError;
}

Cast<Undefined|Callable>(o: HeapObject): Undefined|Callable
    labels CastError {
  if (o == Undefined) return Undefined;
  return HeapObjectToCallable(o) otherwise CastError;
}

Cast<Undefined|JSFunction>(o: HeapObject): Undefined|JSFunction
    labels CastError {
  if (o == Undefined) return Undefined;
  return Cast<JSFunction>(o) otherwise CastError;
}

macro Cast<T : type extends Symbol>(o: Symbol): T labels CastError;
Cast<PublicSymbol>(s: Symbol): PublicSymbol labels CastError {
  if (s.flags.is_private) goto CastError;
  return %RawDownCast<PublicSymbol>(s);
}
Cast<PrivateSymbol>(s: Symbol): PrivateSymbol labels CastError {
  if (s.flags.is_private) return %RawDownCast<PrivateSymbol>(s);
  goto CastError;
}
Cast<PublicSymbol>(o: HeapObject): PublicSymbol labels CastError {
  const s = Cast<Symbol>(o) otherwise CastError;
  return Cast<PublicSymbol>(s) otherwise CastError;
}
Cast<PrivateSymbol>(o: HeapObject): PrivateSymbol labels CastError {
  const s = Cast<Symbol>(o) otherwise CastError;
  return Cast<PrivateSymbol>(s) otherwise CastError;
}

Cast<DirectString>(o: String): DirectString
    labels CastError {
  return TaggedToDirectString(o) otherwise CastError;
}

Cast<Constructor>(o: HeapObject): Constructor
    labels CastError {
  return HeapObjectToConstructor(o) otherwise CastError;
}

Cast<JSFunctionWithPrototypeSlot>(o: HeapObject): JSFunctionWithPrototypeSlot
    labels CastError {
  return HeapObjectToJSFunctionWithPrototypeSlot(o) otherwise CastError;
}

Cast<BigInt>(o: HeapObject): BigInt labels CastError {
  if (IsBigInt(o)) return %RawDownCast<BigInt>(o);
  goto CastError;
}

Cast<JSRegExpResult>(implicit context: Context)(o: HeapObject): JSRegExpResult
    labels CastError {
  if (regexp::IsRegExpResult(o)) return %RawDownCast<JSRegExpResult>(o);
  goto CastError;
}

Cast<JSSloppyArgumentsObject>(implicit context: Context)(o: HeapObject):
    JSSloppyArgumentsObject
    labels CastError {
  const map: Map = o.map;
  if (IsFastAliasedArgumentsMap(map) || IsSloppyArgumentsMap(map) ||
      IsSlowAliasedArgumentsMap(map)) {
    return %RawDownCast<JSSloppyArgumentsObject>(o);
  }
  goto CastError;
}

Cast<JSStrictArgumentsObject>(implicit context: Context)(o: HeapObject):
    JSStrictArgumentsObject
    labels CastError {
  const map: Map = o.map;
  if (!IsStrictArgumentsMap(map)) goto CastError;
  return %RawDownCast<JSStrictArgumentsObject>(o);
}

Cast<JSArgumentsObjectWithLength>(implicit context: Context)(o: HeapObject):
    JSArgumentsObjectWithLength
    labels CastError {
  typeswitch (o) {
    case (o: JSStrictArgumentsObject): {
      return o;
    }
    case (o: JSSloppyArgumentsObject): {
      return o;
    }
    case (HeapObject): {
      goto CastError;
    }
  }
}

Cast<FastJSRegExp>(implicit context: Context)(o: HeapObject): FastJSRegExp
    labels CastError {
  // TODO(jgruber): Remove or redesign this. There is no single 'fast' regexp,
  // the conditions to make a regexp object fast differ based on the callsite.
  // For now, run the strict variant since replace (the only current callsite)
  // accesses flag getters.
  if (regexp::IsFastRegExpStrict(o)) {
    return %RawDownCast<FastJSRegExp>(o);
  }
  goto CastError;
}

Cast<FastJSArray>(implicit context: Context)(o: HeapObject): FastJSArray
    labels CastError {
  if (IsForceSlowPath()) goto CastError;

  if (!Is<JSArray>(o)) goto CastError;

  // Bailout if receiver has slow elements.
  const map: Map = o.map;
  const elementsKind: ElementsKind = LoadMapElementsKind(map);
  if (!IsFastElementsKind(elementsKind)) goto CastError;

  // Verify that our prototype is the initial array prototype.
  if (!IsPrototypeInitialArrayPrototype(map)) goto CastError;

  if (IsNoElementsProtectorCellInvalid()) goto CastError;
  return %RawDownCast<FastJSArray>(o);
}

Cast<FastJSArrayForRead>(implicit context: Context)(o: HeapObject):
    FastJSArrayForRead
    labels CastError {
  if (!Is<JSArray>(o)) goto CastError;

  // Bailout if receiver has slow elements.
  const map: Map = o.map;
  const elementsKind: ElementsKind = LoadMapElementsKind(map);
  if (!IsElementsKindLessThanOrEqual(
          elementsKind, ElementsKind::LAST_ANY_NONEXTENSIBLE_ELEMENTS_KIND))
    goto CastError;

  // Verify that our prototype is the initial array prototype.
  if (!IsPrototypeInitialArrayPrototype(map)) goto CastError;

  if (IsNoElementsProtectorCellInvalid()) goto CastError;
  return %RawDownCast<FastJSArrayForRead>(o);
}

Cast<FastJSArrayForCopy>(implicit context: Context)(o: HeapObject):
    FastJSArrayForCopy
    labels CastError {
  if (IsArraySpeciesProtectorCellInvalid()) goto CastError;
  // TODO(victorgomes): Check if we can cast from FastJSArrayForRead instead.
  const a = Cast<FastJSArray>(o) otherwise CastError;
  return %RawDownCast<FastJSArrayForCopy>(a);
}

Cast<FastJSArrayForConcat>(implicit context: Context)(o: HeapObject):
    FastJSArrayForConcat
    labels CastError {
  if (IsIsConcatSpreadableProtectorCellInvalid()) goto CastError;
  const a = Cast<FastJSArrayForCopy>(o) otherwise CastError;
  return %RawDownCast<FastJSArrayForConcat>(a);
}

Cast<FastJSArrayWithNoCustomIteration>(implicit context: Context)(
    o: HeapObject): FastJSArrayWithNoCustomIteration
    labels CastError {
  if (IsArrayIteratorProtectorCellInvalid()) goto CastError;
  const a = Cast<FastJSArray>(o) otherwise CastError;
  return %RawDownCast<FastJSArrayWithNoCustomIteration>(a);
}

Cast<FastJSArrayForReadWithNoCustomIteration>(implicit context: Context)(
    o: HeapObject): FastJSArrayForReadWithNoCustomIteration
    labels CastError {
  if (IsArrayIteratorProtectorCellInvalid()) goto CastError;
  const a = Cast<FastJSArrayForRead>(o) otherwise CastError;
  return %RawDownCast<FastJSArrayForReadWithNoCustomIteration>(a);
}

macro Cast<T: type>(o: String): T labels CastError;

Cast<SeqOneByteString>(o: HeapObject): SeqOneByteString labels CastError {
  return Cast<SeqOneByteString>(Cast<String>(o) otherwise CastError)
      otherwise CastError;
}

Cast<SeqOneByteString>(o: String): SeqOneByteString labels CastError {
  const instanceType = o.StringInstanceType();
  // Using & instead of && enables Turbofan to merge the two checks into one.
  if (!(instanceType.representation == StringRepresentationTag::kSeqStringTag &
        instanceType.is_one_byte)) {
    goto CastError;
  }
  return %RawDownCast<SeqOneByteString>(o);
}

Cast<SeqTwoByteString>(o: HeapObject): SeqTwoByteString labels CastError {
  return Cast<SeqTwoByteString>(Cast<String>(o) otherwise CastError)
      otherwise CastError;
}

Cast<SeqTwoByteString>(o: String): SeqTwoByteString labels CastError {
  const instanceType = o.StringInstanceType();
  // Using & instead of && enables Turbofan to merge the two checks into one.
  if (!(instanceType.representation == StringRepresentationTag::kSeqStringTag &
        !instanceType.is_one_byte)) {
    goto CastError;
  }
  return %RawDownCast<SeqTwoByteString>(o);
}

Cast<ThinString>(o: HeapObject): ThinString labels CastError {
  return Cast<ThinString>(Cast<String>(o) otherwise CastError)
      otherwise CastError;
}

Cast<ThinString>(o: String): ThinString labels CastError {
  const instanceType = o.StringInstanceType();
  if (instanceType.representation != StringRepresentationTag::kThinStringTag) {
    goto CastError;
  }
  return %RawDownCast<ThinString>(o);
}

Cast<ConsString>(o: HeapObject): ConsString labels CastError {
  return Cast<ConsString>(Cast<String>(o) otherwise CastError)
      otherwise CastError;
}

Cast<ConsString>(o: String): ConsString labels CastError {
  const instanceType = o.StringInstanceType();
  if (instanceType.representation != StringRepresentationTag::kConsStringTag) {
    goto CastError;
  }
  return %RawDownCast<ConsString>(o);
}

Cast<SlicedString>(o: HeapObject): SlicedString labels CastError {
  return Cast<SlicedString>(Cast<String>(o) otherwise CastError)
      otherwise CastError;
}

Cast<SlicedString>(o: String): SlicedString labels CastError {
  const instanceType = o.StringInstanceType();
  if (instanceType.representation !=
      StringRepresentationTag::kSlicedStringTag) {
    goto CastError;
  }
  return %RawDownCast<SlicedString>(o);
}

Cast<ExternalOneByteString>(o: HeapObject):
    ExternalOneByteString labels CastError {
  return Cast<ExternalOneByteString>(Cast<String>(o) otherwise CastError)
      otherwise CastError;
}

Cast<ExternalOneByteString>(o: String): ExternalOneByteString labels CastError {
  const instanceType = o.StringInstanceType();
  // Using & instead of && enables Turbofan to merge the two checks into one.
  if (!(instanceType.representation ==
            StringRepresentationTag::kExternalStringTag &
        instanceType.is_one_byte)) {
    goto CastError;
  }
  return %RawDownCast<ExternalOneByteString>(o);
}

Cast<ExternalTwoByteString>(o: HeapObject):
    ExternalTwoByteString labels CastError {
  return Cast<ExternalTwoByteString>(Cast<String>(o) otherwise CastError)
      otherwise CastError;
}

Cast<ExternalTwoByteString>(o: String): ExternalTwoByteString labels CastError {
  const instanceType = o.StringInstanceType();
  // Using & instead of && enables Turbofan to merge the two checks into one.
  if (!(instanceType.representation ==
            StringRepresentationTag::kExternalStringTag &
        !instanceType.is_one_byte)) {
    goto CastError;
  }
  return %RawDownCast<ExternalTwoByteString>(o);
}

Cast<JSReceiver|Null>(o: HeapObject): JSReceiver|Null
    labels CastError {
  typeswitch (o) {
    case (o: Null): {
      return o;
    }
    case (o: JSReceiver): {
      return o;
    }
    case (HeapObject): {
      goto CastError;
    }
  }
}

Cast<Smi|PromiseReaction>(o: Object): Smi|PromiseReaction labels CastError {
  typeswitch (o) {
    case (o: Smi): {
      return o;
    }
    case (o: PromiseReaction): {
      return o;
    }
    case (Object): {
      goto CastError;
    }
  }
}

Cast<String|Callable>(implicit context: Context)(o: Object): String|
    Callable labels CastError {
  typeswitch (o) {
    case (o: String): {
      return o;
    }
    case (o: Callable): {
      return o;
    }
    case (Object): {
      goto CastError;
    }
  }
}

Cast<Zero|PromiseReaction>(implicit context: Context)(o: Object): Zero|
    PromiseReaction labels CastError {
  typeswitch (o) {
    case (o: Zero): {
      return o;
    }
    case (o: PromiseReaction): {
      return o;
    }
    case (Object): {
      goto CastError;
    }
  }
}

Cast<JSFunction|JSBoundFunction>(implicit context: Context)(o: Object):
    JSFunction|JSBoundFunction labels CastError {
  typeswitch (o) {
    case (o: JSFunction): {
      return o;
    }
    case (o: JSBoundFunction): {
      return o;
    }
    case (Object): {
      goto CastError;
    }
  }
}

Cast<FixedArray|Undefined>(o: HeapObject): FixedArray|
    Undefined labels CastError {
  typeswitch (o) {
    case (o: Undefined): {
      return o;
    }
    case (o: FixedArray): {
      return o;
    }
    case (Object): {
      goto CastError;
    }
  }
}

Cast<JSProxy|Null>(o: HeapObject): JSProxy|Null labels CastError {
  typeswitch (o) {
    case (o: Null): {
      return o;
    }
    case (o: JSProxy): {
      return o;
    }
    case (Object): {
      goto CastError;
    }
  }
}

macro Is<A : type extends Object, B : type extends Object>(
    implicit context: Context)(o: B): bool {
  Cast<A>(o) otherwise return false;
  return true;
}

macro UnsafeCast<A : type extends Object>(implicit context: Context)(o: Object):
    A {
  assert(Is<A>(o));
  return %RawDownCast<A>(o);
}

macro UnsafeConstCast<T: type>(r: const &T):&T {
  return %RawDownCast<&T>(r);
}

UnsafeCast<RegExpMatchInfo>(implicit context: Context)(o: Object):
    RegExpMatchInfo {
  assert(Is<FixedArray>(o));
  return %RawDownCast<RegExpMatchInfo>(o);
}

macro UnsafeCast<A : type extends WeakHeapObject>(o: A|Object): A {
  assert(IsWeakOrCleared(o));
  return %RawDownCast<A>(o);
}

macro
CastOrDefault<T: type, Arg: type, Default: type>(implicit context: Context)(
    x: Arg, default: Default): T|Default {
  return Cast<T>(x) otherwise return default;
}

// This is required for casting MaybeObject to Object.
Cast<Object>(o: Object): Object
labels _CastError {
  return o;
}