| // 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; |
| } |
| } |
| } |