// Copyright 2023 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 array {

extern enum ArrayFromAsyncLabels extends uint31
    constexpr 'ArrayBuiltins::ArrayFromAsyncLabels' {
  kGetIteratorStep,
  kCheckIteratorValueAndMapping,
  kIteratorMapping,
  kGetIteratorValueWithMapping,
  kAddIteratorValueToTheArray,
  kGetArrayLikeValue,
  kCheckArrayLikeValueAndMapping,
  kGetArrayLikeValueWithMapping,
  kAddArrayLikeValueToTheArray,
  kDoneAndResolvePromise,
  kCloseAsyncIterator,
  kRejectPromise
}

transitioning macro ArrayFromAsyncAwaitPoint<T : type extends FunctionContext>(
    implicit context: Context)(resolveContext: T, stepSlot: Slot<T, Smi>,
    promiseFunSlot: Slot<T, JSReceiver>,
    resolveSlot: Slot<T, Undefined|JSFunction>,
    rejectSlot: Slot<T, Undefined|JSFunction>, step: ArrayFromAsyncLabels,
    value: JSAny): JSAny {
  *ContextSlot(resolveContext, stepSlot) = SmiTag<ArrayFromAsyncLabels>(step);

  const promiseFun = *ContextSlot(resolveContext, promiseFunSlot);
  const resolve = *ContextSlot(resolveContext, resolveSlot);
  const reject = *ContextSlot(resolveContext, rejectSlot);

  const resultPromise = promise::PromiseResolve(promiseFun, value);

  promise::PerformPromiseThenImpl(
      UnsafeCast<JSPromise>(resultPromise), resolve, reject, Undefined);
  return Undefined;
}

// This macro reject the promise if any exception occurs in the execution of
// the asynchronous code.
transitioning macro
RejectArrayFromAsyncPromise<T : type extends FunctionContext>(
    implicit context: Context)(resolveContext: T, errorSlot: Slot<T, JSAny>,
    promiseSlot: Slot<T, JSPromise>): JSAny {
  const error = *ContextSlot(resolveContext, errorSlot);
  const promise = *ContextSlot(resolveContext, promiseSlot);

  return promise::RejectPromise(promise, error, False);
}

// --- Iterable path

struct ArrayFromAsyncIterableResumeState {
  step: ArrayFromAsyncLabels;
  awaitedValue: JSAny;
  index: Smi;
}

type ArrayFromAsyncIterableResolveContext extends FunctionContext;
extern enum ArrayFromAsyncIterableResolveContextSlots extends intptr
    constexpr 'ArrayBuiltins::ArrayFromAsyncIterableResolveContextSlots' {
  kArrayFromAsyncIterableResolveResumeStateStepSlot:
      Slot<ArrayFromAsyncIterableResolveContext, Smi>,
  kArrayFromAsyncIterableResolveResumeStateAwaitedValueSlot:
      Slot<ArrayFromAsyncIterableResolveContext, JSAny>,
  kArrayFromAsyncIterableResolveResumeStateIndexSlot:
      Slot<ArrayFromAsyncIterableResolveContext, Smi>,
  kArrayFromAsyncIterableResolvePromiseSlot:
      Slot<ArrayFromAsyncIterableResolveContext, JSPromise>,
  kArrayFromAsyncIterableResolvePromiseFunctionSlot:
      Slot<ArrayFromAsyncIterableResolveContext, JSReceiver>,
  kArrayFromAsyncIterableResolveOnFulfilledFunctionSlot:
      Slot<ArrayFromAsyncIterableResolveContext, JSFunction|Undefined>,
  kArrayFromAsyncIterableResolveOnRejectedFunctionSlot:
      Slot<ArrayFromAsyncIterableResolveContext, JSFunction|Undefined>,
  kArrayFromAsyncIterableResolveResultArraySlot:
      Slot<ArrayFromAsyncIterableResolveContext, JSReceiver>,
  kArrayFromAsyncIterableResolveIteratorSlot:
      Slot<ArrayFromAsyncIterableResolveContext, JSReceiver>,
  kArrayFromAsyncIterableResolveNextMethodSlot:
      Slot<ArrayFromAsyncIterableResolveContext, JSAny>,
  kArrayFromAsyncIterableResolveErrorSlot:
      Slot<ArrayFromAsyncIterableResolveContext, JSAny>,
  kArrayFromAsyncIterableResolveMapfnSlot:
      Slot<ArrayFromAsyncIterableResolveContext, JSAny>,
  kArrayFromAsyncIterableResolveThisArgSlot:
      Slot<ArrayFromAsyncIterableResolveContext, JSAny>,
  kArrayFromAsyncIterableResolveLength
}

extern macro AllocateRootFunctionWithContext(
    constexpr intptr, FunctionContext, NativeContext): JSFunction;

const kArrayFromAsyncIterableOnFulfilledSharedFun: constexpr intptr
    generates 'RootIndex::kArrayFromAsyncIterableOnFulfilledSharedFun';
const kArrayFromAsyncIterableOnRejectedSharedFun: constexpr intptr
    generates 'RootIndex::kArrayFromAsyncIterableOnRejectedSharedFun';

macro CreateArrayFromAsyncIterableResolveContext(
    implicit context: Context)(resumeState: ArrayFromAsyncIterableResumeState,
    promise: JSPromise, promiseFun: JSReceiver, iterator: JSReceiver,
    next: JSAny, arr: JSReceiver, error: JSAny, mapfn: JSAny, thisArg: JSAny,
    nativeContext: NativeContext): ArrayFromAsyncIterableResolveContext {
  const resolveContext = %RawDownCast<ArrayFromAsyncIterableResolveContext>(
      AllocateSyntheticFunctionContext(
          nativeContext,
          ArrayFromAsyncIterableResolveContextSlots::
              kArrayFromAsyncIterableResolveLength));
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolveResumeStateStepSlot,
      SmiTag<ArrayFromAsyncLabels>(resumeState.step));
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolveResumeStateAwaitedValueSlot,
      resumeState.awaitedValue);
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolveResumeStateIndexSlot,
      resumeState.index);
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolvePromiseSlot,
      promise);
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolvePromiseFunctionSlot,
      promiseFun);
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolveOnFulfilledFunctionSlot,
      AllocateRootFunctionWithContext(
          kArrayFromAsyncIterableOnFulfilledSharedFun, resolveContext,
          nativeContext));
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolveOnRejectedFunctionSlot,
      AllocateRootFunctionWithContext(
          kArrayFromAsyncIterableOnRejectedSharedFun, resolveContext,
          nativeContext));
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolveResultArraySlot,
      arr);
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolveIteratorSlot,
      iterator);
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolveNextMethodSlot,
      next);
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolveErrorSlot,
      error);
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolveMapfnSlot,
      mapfn);
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolveThisArgSlot,
      thisArg);
  return resolveContext;
}

macro GetIteratorRecordFromArrayFromAsyncIterableResolveContext(
    context: ArrayFromAsyncIterableResolveContext): iterator::IteratorRecord {
  const iterator = *ContextSlot(
      context,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolveIteratorSlot);

  const nextMethod = *ContextSlot(
      context,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolveNextMethodSlot);

  return iterator::IteratorRecord{object: iterator, next: nextMethod};
}

transitioning macro CreateArrayFromIterableAsynchronously(
    context: ArrayFromAsyncIterableResolveContext): JSAny {
  try {
    const fastIteratorResultMap = GetIteratorResultMap();

    const mapfn = *ContextSlot(
        context,
        ArrayFromAsyncIterableResolveContextSlots::
            kArrayFromAsyncIterableResolveMapfnSlot);

    const thisArg = *ContextSlot(
        context,
        ArrayFromAsyncIterableResolveContextSlots::
            kArrayFromAsyncIterableResolveThisArgSlot);

    const arr = *ContextSlot(
        context,
        ArrayFromAsyncIterableResolveContextSlots::
            kArrayFromAsyncIterableResolveResultArraySlot);

    let resumeState = ArrayFromAsyncIterableResumeState{
      step: SmiUntag<ArrayFromAsyncLabels>(
          %RawDownCast<SmiTagged<ArrayFromAsyncLabels>>(*ContextSlot(
              context,
              ArrayFromAsyncIterableResolveContextSlots::
                  kArrayFromAsyncIterableResolveResumeStateStepSlot))),
      awaitedValue: *ContextSlot(
          context,
          ArrayFromAsyncIterableResolveContextSlots::
              kArrayFromAsyncIterableResolveResumeStateAwaitedValueSlot),
      index: *ContextSlot(
          context,
          ArrayFromAsyncIterableResolveContextSlots::
              kArrayFromAsyncIterableResolveResumeStateIndexSlot)
    };

    let mappedValue: JSAny = Undefined;
    let nextValue: JSAny = Undefined;

    // TODO(v8:14290): Replace `if/else` with `switch/case` when the support
    // for `switch` is added.

    while (true) {
      if (resumeState.step == ArrayFromAsyncLabels::kGetIteratorStep) {
        const iteratorRecord =
            GetIteratorRecordFromArrayFromAsyncIterableResolveContext(context);
        let next: JSAny;
        // https://github.com/tc39/proposal-array-from-async/issues/33#issuecomment-1279296963
        //    3. Let nextResult be ? Call(iteratorRecord.[[NextMethod]],
        //    iteratorRecord.[[Iterator]]).
        //    4. Set nextResult to ? Await(nextResult).
        next = Call(context, iteratorRecord.next, iteratorRecord.object);

        return ArrayFromAsyncIterableAwaitPoint(
            context, ArrayFromAsyncLabels::kCheckIteratorValueAndMapping, next);
      } else if (
          resumeState.step ==
          ArrayFromAsyncLabels::kCheckIteratorValueAndMapping) {
        //    5. If nextResult is not an Object, throw a TypeError exception.
        const nextJSReceiver = Cast<JSReceiver>(resumeState.awaitedValue)
            otherwise ThrowTypeError(
            MessageTemplate::kIteratorResultNotAnObject, 'Array.fromAsync');

        try {
          //    6. Let done be ? IteratorComplete(nextResult).
          iterator::IteratorComplete(nextJSReceiver, fastIteratorResultMap)
              otherwise Done;

          //    8. Let nextValue be ? IteratorValue(nextResult).
          nextValue =
              iterator::IteratorValue(nextJSReceiver, fastIteratorResultMap);

          // When mapfn is not undefined, it is guaranteed to be callable as
          // checked upon entry.
          const mapping: bool = (mapfn != Undefined);

          //    9. If mapping is true, then
          if (mapping) {
            resumeState.step = ArrayFromAsyncLabels::kIteratorMapping;
          } else {
            //    10. Else, let mappedValue be nextValue.
            mappedValue = nextValue;
            resumeState.step =
                ArrayFromAsyncLabels::kAddIteratorValueToTheArray;
          }
        } label Done {
          //    7. If done is true,
          //       a. Perform ? Set(A, "length", 𝔽(k), true).
          //       b. Return Completion Record { [[Type]]: return, [[Value]]: A,
          //       [[Target]]: empty }.
          resumeState.step = ArrayFromAsyncLabels::kDoneAndResolvePromise;
        }
      } else if (resumeState.step == ArrayFromAsyncLabels::kIteratorMapping) {
        //      a. Let mappedValue be Call(mapfn, thisArg, « nextValue, 𝔽(k)
        //      »).
        //      b. IfAbruptCloseAsyncIterator(mappedValue,
        //      iteratorRecord).
        const mapResult = Call(
            context, UnsafeCast<Callable>(mapfn), thisArg, nextValue,
            resumeState.index);

        //      c. Set mappedValue to Await(mappedValue).
        //      d. IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord).
        return ArrayFromAsyncIterableAwaitPoint(
            context, ArrayFromAsyncLabels::kGetIteratorValueWithMapping,
            mapResult);
      } else if (
          resumeState.step ==
          ArrayFromAsyncLabels::kGetIteratorValueWithMapping) {
        mappedValue = resumeState.awaitedValue;
        resumeState.step = ArrayFromAsyncLabels::kAddIteratorValueToTheArray;
      } else if (
          resumeState.step ==
          ArrayFromAsyncLabels::kAddIteratorValueToTheArray) {
        //    11. Let defineStatus be CreateDataPropertyOrThrow(A, Pk,
        //    mappedValue).
        //    12. If defineStatus is an abrupt completion, return ?
        //    AsyncIteratorClose(iteratorRecord, defineStatus).
        FastCreateDataProperty(arr, resumeState.index, mappedValue);

        // 13. Set k to k + 1.
        resumeState.index++;

        *ContextSlot(
            context,
            ArrayFromAsyncIterableResolveContextSlots::
                kArrayFromAsyncIterableResolveResumeStateIndexSlot) =
            resumeState.index;

        resumeState.step = ArrayFromAsyncLabels::kGetIteratorStep;
      } else if (
          resumeState.step == ArrayFromAsyncLabels::kDoneAndResolvePromise) {
        array::SetPropertyLength(arr, resumeState.index);
        const promise = *ContextSlot(
            context,
            ArrayFromAsyncIterableResolveContextSlots::
                kArrayFromAsyncIterableResolvePromiseSlot);

        promise::ResolvePromise(promise, arr);
        return Undefined;
      } else if (
          resumeState.step == ArrayFromAsyncLabels::kCloseAsyncIterator) {
        resumeState.step = ArrayFromAsyncLabels::kRejectPromise;

        const iteratorRecord =
            GetIteratorRecordFromArrayFromAsyncIterableResolveContext(context);
        try {
          ArrayFromAsyncAsyncIteratorCloseOnException(iteratorRecord)
              otherwise RejectPromise;
          return Undefined;
        } label RejectPromise {
          // Do nothing so the codeflow continues to the kRejectPromise label.
        }
      } else if (resumeState.step == ArrayFromAsyncLabels::kRejectPromise) {
        return RejectArrayFromAsyncPromise<
            ArrayFromAsyncIterableResolveContext>(
            context,
            ArrayFromAsyncIterableResolveContextSlots::
                kArrayFromAsyncIterableResolveErrorSlot,
            ArrayFromAsyncIterableResolveContextSlots::
                kArrayFromAsyncIterableResolvePromiseSlot);
      }
    }
  } catch (e, _message) {
    *ContextSlot(
        context,
        ArrayFromAsyncIterableResolveContextSlots::
            kArrayFromAsyncIterableResolveErrorSlot) = e;

    const iteratorRecord =
        GetIteratorRecordFromArrayFromAsyncIterableResolveContext(context);
    try {
      ArrayFromAsyncAsyncIteratorCloseOnException(iteratorRecord)
          otherwise RejectPromise;
    } label RejectPromise {
      return RejectArrayFromAsyncPromise<ArrayFromAsyncIterableResolveContext>(
          context,
          ArrayFromAsyncIterableResolveContextSlots::
              kArrayFromAsyncIterableResolveErrorSlot,
          ArrayFromAsyncIterableResolveContextSlots::
              kArrayFromAsyncIterableResolvePromiseSlot);
    }
  }
  return Undefined;
}

transitioning macro ArrayFromAsyncIterableAwaitPoint(
    context: ArrayFromAsyncIterableResolveContext, step: ArrayFromAsyncLabels,
    value: JSAny): JSAny {
  return ArrayFromAsyncAwaitPoint<ArrayFromAsyncIterableResolveContext>(
      context,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolveResumeStateStepSlot,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolvePromiseFunctionSlot,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolveOnFulfilledFunctionSlot,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolveOnRejectedFunctionSlot,
      step, value);
}

// `ArrayFromAsyncIterableOnFulfilled` is the callback function for the
// fulfilled case of the promise in `then` handler.
transitioning javascript builtin ArrayFromAsyncIterableOnFulfilled(
    js-implicit context: Context, receiver: JSAny, target: JSFunction)(
    result: JSAny): JSAny {
  const context = %RawDownCast<ArrayFromAsyncIterableResolveContext>(context);
  *ContextSlot(
      context,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolveResumeStateAwaitedValueSlot) = result;

  return CreateArrayFromIterableAsynchronously(context);
}

// `ArrayFromAsyncIterableOnRejected` is the callback function for the rejected
// case of the promise in `then` handler.
transitioning javascript builtin ArrayFromAsyncIterableOnRejected(
    js-implicit context: Context, receiver: JSAny, target: JSFunction)(
    result: JSAny): JSAny {
  const context = %RawDownCast<ArrayFromAsyncIterableResolveContext>(context);

  *ContextSlot(
      context,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolveResumeStateStepSlot) =
      SmiTag<ArrayFromAsyncLabels>(ArrayFromAsyncLabels::kCloseAsyncIterator);
  *ContextSlot(
      context,
      ArrayFromAsyncIterableResolveContextSlots::
          kArrayFromAsyncIterableResolveErrorSlot) = result;

  return CreateArrayFromIterableAsynchronously(context);
}

// This is the specialized implementation of `IfAbruptCloseAsyncIterator` for
// Array.fromAsync
// https://tc39.es/proposal-array-from-async/#sec-ifabruptcloseasynciterator
transitioning macro ArrayFromAsyncAsyncIteratorCloseOnException(
    implicit context: Context)(
    iterator: iterator::IteratorRecord): void labels RejectPromise {
  try {
    const context = %RawDownCast<ArrayFromAsyncIterableResolveContext>(context);
    // 3. Let innerResult be GetMethod(iterator, "return").
    const method = GetProperty(iterator.object, kReturnString);

    // 4. If innerResult.[[Type]] is normal, then
    //   a. Let return be innerResult.[[Value]].
    //   b. If return is undefined, return Completion(completion).
    if (method == Undefined || method == Null) {
      goto RejectPromise;
    }

    //   c. Set innerResult to Call(return, iterator).
    // If an exception occurs, the original exception remains bound
    const innerResult = Call(context, method, iterator.object);

    //   d. If innerResult.[[Type]] is normal, set innerResult to
    //   Completion(Await(innerResult.[[Value]])).
    const step = ArrayFromAsyncLabels::kRejectPromise;
    ArrayFromAsyncIterableAwaitPoint(context, step, innerResult);
  } catch (_e, _message) {
    // Swallow the exception.
  }

  // (5. If completion.[[Type]] is throw) return Completion(completion).
}

extern macro ArrayFromAsyncIterableOnFulfilledSharedFunConstant():
    SharedFunctionInfo;
extern macro ArrayFromAsyncIterableOnRejectedSharedFunConstant():
    SharedFunctionInfo;

// --- Array-like path

struct ArrayFromAsyncArrayLikeResumeState {
  step: ArrayFromAsyncLabels;
  awaitedValue: JSAny;
  len: Number;
  index: Smi;
}

type ArrayFromAsyncArrayLikeResolveContext extends FunctionContext;
extern enum ArrayFromAsyncArrayLikeResolveContextSlots extends intptr
    constexpr 'ArrayBuiltins::ArrayFromAsyncArrayLikeResolveContextSlots' {
  kArrayFromAsyncArrayLikeResolveResumeStateStepSlot:
      Slot<ArrayFromAsyncArrayLikeResolveContext, Smi>,
  kArrayFromAsyncArrayLikeResolveResumeStateAwaitedValueSlot:
      Slot<ArrayFromAsyncArrayLikeResolveContext, JSAny>,
  kArrayFromAsyncArrayLikeResolveResumeStateLenSlot:
      Slot<ArrayFromAsyncArrayLikeResolveContext, Number>,
  kArrayFromAsyncArrayLikeResolveResumeStateIndexSlot:
      Slot<ArrayFromAsyncArrayLikeResolveContext, Smi>,
  kArrayFromAsyncArrayLikeResolvePromiseSlot:
      Slot<ArrayFromAsyncArrayLikeResolveContext, JSPromise>,
  kArrayFromAsyncArrayLikeResolvePromiseFunctionSlot:
      Slot<ArrayFromAsyncArrayLikeResolveContext, JSReceiver>,
  kArrayFromAsyncArrayLikeResolveOnFulfilledFunctionSlot:
      Slot<ArrayFromAsyncArrayLikeResolveContext, JSFunction|Undefined>,
  kArrayFromAsyncArrayLikeResolveOnRejectedFunctionSlot:
      Slot<ArrayFromAsyncArrayLikeResolveContext, JSFunction|Undefined>,
  kArrayFromAsyncArrayLikeResolveResultArraySlot:
      Slot<ArrayFromAsyncArrayLikeResolveContext, JSReceiver>,
  kArrayFromAsyncArrayLikeResolveArrayLikeSlot:
      Slot<ArrayFromAsyncArrayLikeResolveContext, JSReceiver>,
  kArrayFromAsyncArrayLikeResolveErrorSlot:
      Slot<ArrayFromAsyncArrayLikeResolveContext, JSAny>,
  kArrayFromAsyncArrayLikeResolveMapfnSlot:
      Slot<ArrayFromAsyncArrayLikeResolveContext, JSAny>,
  kArrayFromAsyncArrayLikeResolveThisArgSlot:
      Slot<ArrayFromAsyncArrayLikeResolveContext, JSAny>,
  kArrayFromAsyncArrayLikeResolveLength
}

const kArrayFromAsyncArrayLikeOnFulfilledSharedFun: constexpr intptr
    generates 'RootIndex::kArrayFromAsyncArrayLikeOnFulfilledSharedFun';
const kArrayFromAsyncArrayLikeOnRejectedSharedFun: constexpr intptr
    generates 'RootIndex::kArrayFromAsyncArrayLikeOnRejectedSharedFun';

macro CreateArrayFromAsyncArrayLikeResolveContext(
    implicit context: Context)(resumeState: ArrayFromAsyncArrayLikeResumeState,
    promise: JSPromise, promiseFun: JSReceiver, arrayLike: JSReceiver,
    arr: JSReceiver, error: JSAny, mapfn: JSAny, thisArg: JSAny,
    nativeContext: NativeContext): ArrayFromAsyncArrayLikeResolveContext {
  const resolveContext = %RawDownCast<ArrayFromAsyncArrayLikeResolveContext>(
      AllocateSyntheticFunctionContext(
          nativeContext,
          ArrayFromAsyncArrayLikeResolveContextSlots::
              kArrayFromAsyncArrayLikeResolveLength));
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncArrayLikeResolveContextSlots::
          kArrayFromAsyncArrayLikeResolveResumeStateStepSlot,
      SmiTag<ArrayFromAsyncLabels>(resumeState.step));
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncArrayLikeResolveContextSlots::
          kArrayFromAsyncArrayLikeResolveResumeStateAwaitedValueSlot,
      resumeState.awaitedValue);
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncArrayLikeResolveContextSlots::
          kArrayFromAsyncArrayLikeResolveResumeStateLenSlot,
      resumeState.len);
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncArrayLikeResolveContextSlots::
          kArrayFromAsyncArrayLikeResolveResumeStateIndexSlot,
      resumeState.index);
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncArrayLikeResolveContextSlots::
          kArrayFromAsyncArrayLikeResolvePromiseSlot,
      promise);
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncArrayLikeResolveContextSlots::
          kArrayFromAsyncArrayLikeResolvePromiseFunctionSlot,
      promiseFun);
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncArrayLikeResolveContextSlots::
          kArrayFromAsyncArrayLikeResolveOnFulfilledFunctionSlot,
      AllocateRootFunctionWithContext(
          kArrayFromAsyncArrayLikeOnFulfilledSharedFun, resolveContext,
          nativeContext));
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncArrayLikeResolveContextSlots::
          kArrayFromAsyncArrayLikeResolveOnRejectedFunctionSlot,
      AllocateRootFunctionWithContext(
          kArrayFromAsyncArrayLikeOnRejectedSharedFun, resolveContext,
          nativeContext));
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncArrayLikeResolveContextSlots::
          kArrayFromAsyncArrayLikeResolveResultArraySlot,
      arr);
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncArrayLikeResolveContextSlots::
          kArrayFromAsyncArrayLikeResolveArrayLikeSlot,
      arrayLike);
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncArrayLikeResolveContextSlots::
          kArrayFromAsyncArrayLikeResolveErrorSlot,
      error);
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncArrayLikeResolveContextSlots::
          kArrayFromAsyncArrayLikeResolveMapfnSlot,
      mapfn);
  InitContextSlot(
      resolveContext,
      ArrayFromAsyncArrayLikeResolveContextSlots::
          kArrayFromAsyncArrayLikeResolveThisArgSlot,
      thisArg);
  return resolveContext;
}

transitioning macro CreateArrayFromArrayLikeAsynchronously(
    context: ArrayFromAsyncArrayLikeResolveContext): JSAny {
  try {
    const mapfn = *ContextSlot(
        context,
        ArrayFromAsyncArrayLikeResolveContextSlots::
            kArrayFromAsyncArrayLikeResolveMapfnSlot);

    const thisArg = *ContextSlot(
        context,
        ArrayFromAsyncArrayLikeResolveContextSlots::
            kArrayFromAsyncArrayLikeResolveThisArgSlot);

    const arr = *ContextSlot(
        context,
        ArrayFromAsyncArrayLikeResolveContextSlots::
            kArrayFromAsyncArrayLikeResolveResultArraySlot);

    let resumeState = ArrayFromAsyncArrayLikeResumeState{
      step: SmiUntag<ArrayFromAsyncLabels>(
          %RawDownCast<SmiTagged<ArrayFromAsyncLabels>>(*ContextSlot(
              context,
              ArrayFromAsyncArrayLikeResolveContextSlots::
                  kArrayFromAsyncArrayLikeResolveResumeStateStepSlot))),
      awaitedValue: *ContextSlot(
          context,
          ArrayFromAsyncArrayLikeResolveContextSlots::
              kArrayFromAsyncArrayLikeResolveResumeStateAwaitedValueSlot),
      len: *ContextSlot(
          context,
          ArrayFromAsyncArrayLikeResolveContextSlots::
              kArrayFromAsyncArrayLikeResolveResumeStateLenSlot),
      index: *ContextSlot(
          context,
          ArrayFromAsyncArrayLikeResolveContextSlots::
              kArrayFromAsyncArrayLikeResolveResumeStateIndexSlot)
    };

    let mappedValue: JSAny = Undefined;

    // TODO(v8:14290): Replace `if/else` with `switch/case` when the support
    // for `switch` is added.

    while (true) {
      if (resumeState.step == ArrayFromAsyncLabels::kGetArrayLikeValue) {
        const arrayLike = *ContextSlot(
            context,
            ArrayFromAsyncArrayLikeResolveContextSlots::
                kArrayFromAsyncArrayLikeResolveArrayLikeSlot);

        //   vii. Repeat, while k < len,
        //     1. Let Pk be ! ToString(𝔽(k)).
        if (resumeState.index < resumeState.len) {
          //     2. Let kValue be ? Get(arrayLike, Pk).
          const kValue = GetProperty(arrayLike, resumeState.index);

          //     3. Set kValue to ? Await(kValue).
          return ArrayFromAsyncArrayLikeAwaitPoint(
              context, ArrayFromAsyncLabels::kCheckArrayLikeValueAndMapping,
              kValue);
        }
        //   viii. Perform ? Set(A, "length", 𝔽(len), true).
        //   ix. Return Completion Record { [[Type]]: return, [[Value]]: A,
        //   [[Target]]: empty }.
        resumeState.step = ArrayFromAsyncLabels::kDoneAndResolvePromise;
      } else if (
          resumeState.step ==
          ArrayFromAsyncLabels::kCheckArrayLikeValueAndMapping) {
        // When mapfn is not undefined, it is guaranteed to be callable as
        // checked upon entry.
        const mapping: bool = (mapfn != Undefined);
        //    4. If mapping is true, then
        if (mapping) {
          resumeState.step =
              ArrayFromAsyncLabels::kGetArrayLikeValueWithMapping;
        } else {
          resumeState.step = ArrayFromAsyncLabels::kAddArrayLikeValueToTheArray;
        }
      } else if (
          resumeState.step ==
          ArrayFromAsyncLabels::kGetArrayLikeValueWithMapping) {
        //      a. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k)
        //      »).
        //      b. Set mappedValue to ? Await(mappedValue).
        const mapResult = Call(
            context, UnsafeCast<Callable>(mapfn), thisArg,
            resumeState.awaitedValue, resumeState.index);
        return ArrayFromAsyncArrayLikeAwaitPoint(
            context, ArrayFromAsyncLabels::kAddArrayLikeValueToTheArray,
            mapResult);
      } else if (
          resumeState.step ==
          ArrayFromAsyncLabels::kAddArrayLikeValueToTheArray) {
        //    5. Else, let mappedValue be kValue.
        mappedValue = resumeState.awaitedValue;

        //    6. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
        FastCreateDataProperty(arr, resumeState.index, mappedValue);

        resumeState.index++;

        *ContextSlot(
            context,
            ArrayFromAsyncArrayLikeResolveContextSlots::
                kArrayFromAsyncArrayLikeResolveResumeStateIndexSlot) =
            resumeState.index;

        resumeState.step = ArrayFromAsyncLabels::kGetArrayLikeValue;
      } else if (
          resumeState.step == ArrayFromAsyncLabels::kDoneAndResolvePromise) {
        array::SetPropertyLength(arr, resumeState.index);
        const promise = *ContextSlot(
            context,
            ArrayFromAsyncArrayLikeResolveContextSlots::
                kArrayFromAsyncArrayLikeResolvePromiseSlot);

        promise::ResolvePromise(promise, arr);
        return Undefined;
      } else if (resumeState.step == ArrayFromAsyncLabels::kRejectPromise) {
        return RejectArrayFromAsyncPromise<
            ArrayFromAsyncArrayLikeResolveContext>(
            context,
            ArrayFromAsyncArrayLikeResolveContextSlots::
                kArrayFromAsyncArrayLikeResolveErrorSlot,
            ArrayFromAsyncArrayLikeResolveContextSlots::
                kArrayFromAsyncArrayLikeResolvePromiseSlot);
      }
    }
  } catch (e, _message) {
    *ContextSlot(
        context,
        ArrayFromAsyncArrayLikeResolveContextSlots::
            kArrayFromAsyncArrayLikeResolveErrorSlot) = e;

    return RejectArrayFromAsyncPromise<ArrayFromAsyncArrayLikeResolveContext>(
        context,
        ArrayFromAsyncArrayLikeResolveContextSlots::
            kArrayFromAsyncArrayLikeResolveErrorSlot,
        ArrayFromAsyncArrayLikeResolveContextSlots::
            kArrayFromAsyncArrayLikeResolvePromiseSlot);
  }
  return Undefined;
}

transitioning macro ArrayFromAsyncArrayLikeAwaitPoint(
    context: ArrayFromAsyncArrayLikeResolveContext, step: ArrayFromAsyncLabels,
    value: JSAny): JSAny {
  return ArrayFromAsyncAwaitPoint<ArrayFromAsyncArrayLikeResolveContext>(
      context,
      ArrayFromAsyncArrayLikeResolveContextSlots::
          kArrayFromAsyncArrayLikeResolveResumeStateStepSlot,
      ArrayFromAsyncArrayLikeResolveContextSlots::
          kArrayFromAsyncArrayLikeResolvePromiseFunctionSlot,
      ArrayFromAsyncArrayLikeResolveContextSlots::
          kArrayFromAsyncArrayLikeResolveOnFulfilledFunctionSlot,
      ArrayFromAsyncArrayLikeResolveContextSlots::
          kArrayFromAsyncArrayLikeResolveOnRejectedFunctionSlot,
      step, value);
}

// `ArrayFromAsyncArrayLikeOnFulfilled` is the callback function for the
// fulfilled case of the promise in `then` handler.
transitioning javascript builtin ArrayFromAsyncArrayLikeOnFulfilled(
    js-implicit context: Context, receiver: JSAny, target: JSFunction)(
    result: JSAny): JSAny {
  const context = %RawDownCast<ArrayFromAsyncArrayLikeResolveContext>(context);
  *ContextSlot(
      context,
      ArrayFromAsyncArrayLikeResolveContextSlots::
          kArrayFromAsyncArrayLikeResolveResumeStateAwaitedValueSlot) = result;

  return CreateArrayFromArrayLikeAsynchronously(context);
}

// `ArrayFromAsyncArrayLikeOnRejected` is the callback function for the rejected
// case of the promise in `then` handler.
transitioning javascript builtin ArrayFromAsyncArrayLikeOnRejected(
    js-implicit context: Context, receiver: JSAny, target: JSFunction)(
    result: JSAny): JSAny {
  const context = %RawDownCast<ArrayFromAsyncArrayLikeResolveContext>(context);

  *ContextSlot(
      context,
      ArrayFromAsyncArrayLikeResolveContextSlots::
          kArrayFromAsyncArrayLikeResolveResumeStateStepSlot) =
      SmiTag<ArrayFromAsyncLabels>(ArrayFromAsyncLabels::kRejectPromise);
  *ContextSlot(
      context,
      ArrayFromAsyncArrayLikeResolveContextSlots::
          kArrayFromAsyncArrayLikeResolveErrorSlot) = result;

  return CreateArrayFromArrayLikeAsynchronously(context);
}

extern macro ArrayFromAsyncArrayLikeOnFulfilledSharedFunConstant():
    SharedFunctionInfo;
extern macro ArrayFromAsyncArrayLikeOnRejectedSharedFunConstant():
    SharedFunctionInfo;

// --- Array.fromAsync builtin

// https://tc39.es/proposal-array-from-async/#sec-array.fromAsync
// Array.fromAsync ( asyncItems [ , mapfn [ , thisArg ] ] )
// Since we do not have support for `await` in torque, we handled
// asynchronous execution flow manually in torque. More information
// is available in go/array-from-async-implementation.
@incrementUseCounter('v8::Isolate::kArrayFromAsync')
transitioning javascript builtin ArrayFromAsync(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  // 1. Let C be the this value.
  const c = HasBuiltinSubclassingFlag() ? receiver : GetArrayFunction();

  const items = arguments[0];
  const mapfn = arguments[1];
  const thisArg = arguments[2];

  // 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
  const promise = promise::NewJSPromise();

  const promiseFun = *NativeContextSlot(
      context, ContextSlot::PROMISE_FUNCTION_INDEX);

  // 3. Let fromAsyncClosure be a new Abstract Closure with no parameters that
  // captures C, mapfn, and thisArg and performs the following steps when
  // called:

  let usingAsyncIterator: JSAny = Undefined;
  let usingSyncIterator: JSAny = Undefined;

  try {
    if (mapfn != Undefined) {
      // i. If IsCallable(mapfn) is false, throw a TypeError exception.
      if (!Is<Callable>(mapfn)) deferred {
          ThrowTypeError(MessageTemplate::kCalledNonCallable, mapfn);
        }
    }

    try {
      //  c. Let usingAsyncIterator be ?
      //  GetMethod(asyncItems, @@asyncIterator).
      usingAsyncIterator = GetMethod(items, AsyncIteratorSymbolConstant())
          otherwise AsyncIteratorIsUndefined, AsyncIteratorNotCallable;
    } label AsyncIteratorIsUndefined {
      //  d. If usingAsyncIterator is undefined, then
      //    i. Let usingSyncIterator be ?
      //    GetMethod(asyncItems, @@iterator).

      usingSyncIterator = GetMethod(items, IteratorSymbolConstant())
          otherwise SyncIteratorIsUndefined, SyncIteratorNotCallable;
    } label SyncIteratorIsUndefined deferred {
      //  i. Else, (iteratorRecord is undefined)
      //   i. NOTE: asyncItems is neither an AsyncIterable nor an
      //   Iterable so assume it is an array-like object.
      //   ii. Let arrayLike be ! ToObject(asyncItems).
      const arrayLike = ToObject_Inline(context, items);

      //   iii. Let len be ? LengthOfArrayLike(arrayLike).
      const len = GetLengthProperty(arrayLike);

      // TODO(v8:13321): Allocate an array with PACKED elements kind for
      // fast-path rather than calling the constructor which creates an
      // array with HOLEY kind.

      let arr: JSReceiver;
      typeswitch (c) {
        case (c: Constructor): {
          //   iv. If IsConstructor(C) is
          //   true, then
          //     1. Let A be ? Construct(C, « 𝔽(len) »).
          arr = Construct(c, len);
        }
        case (JSAny): {
          //   v. Else,
          //     1. Let A be ? ArrayCreate(len).
          arr = ArrayCreate(len);
        }
      }

      //   vi. Let k be 0.
      // Will be done when creating resumeState later.

      let resumeState = ArrayFromAsyncArrayLikeResumeState{
        step: ArrayFromAsyncLabels::kGetArrayLikeValue,
        awaitedValue: Undefined,
        len: len,
        index: 0
      };

      const arrayLikeResolveContext =
          CreateArrayFromAsyncArrayLikeResolveContext(
              resumeState, promise, promiseFun, arrayLike, arr, Undefined,
              mapfn, thisArg, context);

      CreateArrayFromArrayLikeAsynchronously(arrayLikeResolveContext);
      return promise;
    } label SyncIteratorNotCallable(_value: JSAny)
    deferred {
      ThrowTypeError(
          MessageTemplate::kFirstArgumentIteratorSymbolNonCallable,
          'Array.fromAsync');
    } label AsyncIteratorNotCallable(_value: JSAny)
    deferred {
      ThrowTypeError(
          MessageTemplate::kFirstArgumentAsyncIteratorSymbolNonCallable,
          'Array.fromAsync');
    }

    //  e. Let iteratorRecord be undefined.
    //  f. If usingAsyncIterator is not undefined, then
    //     i. Set iteratorRecord to ? GetIterator(asyncItems, async,
    // usingAsyncIterator).
    //  g. Else if usingSyncIterator is not undefined, then
    //     i. Set iteratorRecord to ?
    // CreateAsyncFromSyncIterator(GetIterator(asyncItems, sync,
    // usingSyncIterator)).

    const iteratorRecord = (usingAsyncIterator != Undefined) ?
        iterator::GetIterator(items, usingAsyncIterator) :
        iterator::GetIteratorRecordAfterCreateAsyncFromSyncIterator(
            context, iterator::GetIterator(items, usingSyncIterator));

    let arr: JSReceiver;
    //  h. If iteratorRecord is not undefined, then
    typeswitch (c) {
      case (c: Constructor): {
        //  i. If IsConstructor(C) is true, then
        //    1. Let A be ? Construct(C).
        arr = Construct(c);
      }
      case (JSAny): {
        //  ii. Else,
        //    1. Let A be ! ArrayCreate(0).
        arr = ArrayCreate(0);
      }
    }

    let iterableResumeState = ArrayFromAsyncIterableResumeState{
      step: ArrayFromAsyncLabels::kGetIteratorStep,
      awaitedValue: Undefined,
      index: 0
    };

    const iterableResolveContext = CreateArrayFromAsyncIterableResolveContext(
        iterableResumeState, promise, promiseFun, iteratorRecord.object,
        iteratorRecord.next, arr, Undefined, mapfn, thisArg, context);

    CreateArrayFromIterableAsynchronously(iterableResolveContext);
    return promise;

  } catch (e, _message) {
    promise::RejectPromise(promise, e, False);
    return promise;
  }
}
}