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

namespace ic {
namespace callable {

extern macro IncrementCallCount(FeedbackVector, uintptr): void;

macro IsMonomorphic(feedback: MaybeObject, target: JSAny): bool {
  return IsWeakReferenceToObject(feedback, target);
}

macro InSameNativeContext(lhs: Context, rhs: Context): bool {
  return LoadNativeContext(lhs) == LoadNativeContext(rhs);
}

macro MaybeObjectToStrong(maybeObject: MaybeObject):
    HeapObject labels IfCleared {
  assert(IsWeakOrCleared(maybeObject));
  const weakObject = %RawDownCast<Weak<HeapObject>>(maybeObject);
  return WeakToStrong(weakObject) otherwise IfCleared;
}

macro TryInitializeAsMonomorphic(implicit context: Context)(
    maybeTarget: JSAny, feedbackVector: FeedbackVector,
    slotId: uintptr): void labels TransitionToMegamorphic {
  const targetHeapObject =
      Cast<HeapObject>(maybeTarget) otherwise TransitionToMegamorphic;

  let unwrappedTarget = targetHeapObject;
  while (Is<JSBoundFunction>(unwrappedTarget)) {
    unwrappedTarget =
        UnsafeCast<JSBoundFunction>(unwrappedTarget).bound_target_function;
  }

  const unwrappedTargetJSFunction =
      Cast<JSFunction>(unwrappedTarget) otherwise TransitionToMegamorphic;
  if (!InSameNativeContext(unwrappedTargetJSFunction.context, context)) {
    goto TransitionToMegamorphic;
  }

  StoreWeakReferenceInFeedbackVector(feedbackVector, slotId, targetHeapObject);
  ReportFeedbackUpdate(feedbackVector, slotId, 'Call:Initialize');
}

macro TransitionToMegamorphic(implicit context: Context)(
    feedbackVector: FeedbackVector, slotId: uintptr): void {
  StoreFeedbackVectorSlot(feedbackVector, slotId, kMegamorphicSymbol);
  ReportFeedbackUpdate(feedbackVector, slotId, 'Call:TransitionMegamorphic');
}

macro CollectCallFeedback(
    maybeTarget: JSAny, context: Context,
    maybeFeedbackVector: Undefined|FeedbackVector, slotId: uintptr): void {
  // TODO(v8:9891): Remove this assert once all callers are ported to Torque.
  // This assert ensures correctness of maybeFeedbackVector's type which can
  // be easily broken for calls from CSA.
  assert(
      IsUndefined(maybeFeedbackVector) ||
      Is<FeedbackVector>(maybeFeedbackVector));
  const feedbackVector =
      Cast<FeedbackVector>(maybeFeedbackVector) otherwise return;
  IncrementCallCount(feedbackVector, slotId);

  try {
    const feedback: MaybeObject =
        LoadFeedbackVectorSlot(feedbackVector, slotId);
    if (IsMonomorphic(feedback, maybeTarget)) return;
    if (IsMegamorphic(feedback)) return;
    if (IsUninitialized(feedback)) goto TryInitializeAsMonomorphic;

    // If cleared, we have a new chance to become monomorphic.
    const feedbackValue: HeapObject =
        MaybeObjectToStrong(feedback) otherwise TryInitializeAsMonomorphic;

    // Try transitioning to a feedback cell.
    // Check if {target}s feedback cell matches the {feedbackValue}.
    const target =
        Cast<JSFunction>(maybeTarget) otherwise TransitionToMegamorphic;
    const targetFeedbackCell: FeedbackCell = target.feedback_cell;
    if (TaggedEqual(feedbackValue, targetFeedbackCell)) return;

    // Check if {target} and {feedbackValue} are both JSFunctions with
    // the same feedback vector cell, and that those functions were
    // actually compiled already.
    const feedbackValueJSFunction =
        Cast<JSFunction>(feedbackValue) otherwise TransitionToMegamorphic;
    const feedbackCell: FeedbackCell = feedbackValueJSFunction.feedback_cell;
    if (!TaggedEqual(feedbackCell, targetFeedbackCell))
      goto TransitionToMegamorphic;

    StoreWeakReferenceInFeedbackVector(feedbackVector, slotId, feedbackCell);
    ReportFeedbackUpdate(feedbackVector, slotId, 'Call:FeedbackVectorCell');
  } label TryInitializeAsMonomorphic {
    TryInitializeAsMonomorphic(maybeTarget, feedbackVector, slotId)
        otherwise TransitionToMegamorphic;
  } label TransitionToMegamorphic {
    TransitionToMegamorphic(feedbackVector, slotId);
  }
}

macro CollectInstanceOfFeedback(
    maybeTarget: JSAny, context: Context,
    maybeFeedbackVector: Undefined|FeedbackVector, slotId: uintptr): void {
  // TODO(v8:9891): Remove this assert once all callers are ported to Torque.
  // This assert ensures correctness of maybeFeedbackVector's type which can
  // be easily broken for calls from CSA.
  assert(
      IsUndefined(maybeFeedbackVector) ||
      Is<FeedbackVector>(maybeFeedbackVector));
  const feedbackVector =
      Cast<FeedbackVector>(maybeFeedbackVector) otherwise return;
  // Note: The call count is not incremented.

  try {
    const feedback: MaybeObject =
        LoadFeedbackVectorSlot(feedbackVector, slotId);
    if (IsMonomorphic(feedback, maybeTarget)) return;
    if (IsMegamorphic(feedback)) return;
    if (IsUninitialized(feedback)) goto TryInitializeAsMonomorphic;

    // If cleared, we have a new chance to become monomorphic.
    const _feedbackValue: HeapObject =
        MaybeObjectToStrong(feedback) otherwise TryInitializeAsMonomorphic;

    goto TransitionToMegamorphic;
  } label TryInitializeAsMonomorphic {
    TryInitializeAsMonomorphic(maybeTarget, feedbackVector, slotId)
        otherwise TransitionToMegamorphic;
  } label TransitionToMegamorphic {
    TransitionToMegamorphic(feedbackVector, slotId);
  }
}

macro BothTaggedEqualArrayFunction(implicit context: Context)(
    first: JSAny, second: JSAny): bool {
  return TaggedEqual(first, second) && TaggedEqual(second, GetArrayFunction());
}

extern macro CreateAllocationSiteInFeedbackVector(
    FeedbackVector, uintptr): AllocationSite;

macro CastFeedbackVector(
    maybeFeedbackVector: Undefined|FeedbackVector,
    updateFeedbackMode: constexpr UpdateFeedbackMode):
    FeedbackVector labels Fallback {
  if constexpr (updateFeedbackMode == UpdateFeedbackMode::kGuaranteedFeedback) {
    return UnsafeCast<FeedbackVector>(maybeFeedbackVector);
  } else if constexpr (
      updateFeedbackMode == UpdateFeedbackMode::kOptionalFeedback) {
    return Cast<FeedbackVector>(maybeFeedbackVector) otherwise goto Fallback;
  } else {
    unreachable;
  }
}

macro CollectConstructFeedback(implicit context: Context)(
    target: JSAny, newTarget: JSAny,
    maybeFeedbackVector: Undefined|FeedbackVector, slotId: uintptr,
    updateFeedbackMode: constexpr UpdateFeedbackMode):
    never labels ConstructGeneric,
    ConstructArray(AllocationSite) {
  // TODO(v8:9891): Remove this assert once all callers are ported to Torque.
  // This assert ensures correctness of maybeFeedbackVector's type which can
  // be easily broken for calls from CSA.
  assert(
      IsUndefined(maybeFeedbackVector) ||
      Is<FeedbackVector>(maybeFeedbackVector));

  const feedbackVector = CastFeedbackVector(
      maybeFeedbackVector, updateFeedbackMode) otherwise goto ConstructGeneric;

  IncrementCallCount(feedbackVector, slotId);

  try {
    const feedback: MaybeObject =
        LoadFeedbackVectorSlot(feedbackVector, slotId);
    if (IsMonomorphic(feedback, newTarget)) goto ConstructGeneric;
    if (IsMegamorphic(feedback)) goto ConstructGeneric;
    if (IsUninitialized(feedback)) goto TryInitializeAsMonomorphic;

    if (!IsWeakOrCleared(feedback)) {
      const feedbackAsStrong = %RawDownCast<Object>(feedback);
      if (Is<AllocationSite>(feedbackAsStrong)) {
        if (BothTaggedEqualArrayFunction(target, newTarget)) {
          goto ConstructArray(UnsafeCast<AllocationSite>(feedbackAsStrong));
        }
        goto TransitionToMegamorphic;
      }
    }

    // If cleared, we have a new chance to become monomorphic.
    const _feedbackValue: HeapObject =
        MaybeObjectToStrong(feedback) otherwise TryInitializeAsMonomorphic;

    goto TransitionToMegamorphic;
  } label TryInitializeAsMonomorphic {
    if (BothTaggedEqualArrayFunction(target, newTarget)) {
      // In this case we can skip unwrapping and context validation since we
      // know the target is the current context's array function.
      const allocationSite =
          CreateAllocationSiteInFeedbackVector(feedbackVector, slotId);
      ReportFeedbackUpdate(
          feedbackVector, slotId, 'Construct:CreateAllocationSite');
      goto ConstructArray(allocationSite);
    }

    TryInitializeAsMonomorphic(newTarget, feedbackVector, slotId)
        otherwise TransitionToMegamorphic;
  } label TransitionToMegamorphic {
    TransitionToMegamorphic(feedbackVector, slotId);
  }
  goto ConstructGeneric;
}

}  // namespace callable
}  // namespace ic