blob: e2409fc6d7b8cd692e51dcd8996980ef8c9b1a0b [file] [log] [blame]
// 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.
#include 'src/builtins/builtins-promise.h'
#include 'src/builtins/builtins-promise-gen.h'
namespace promise {
const kPromiseBuiltinsPromiseContextLength: constexpr int31
generates 'PromiseBuiltins::kPromiseContextLength';
// Creates the context used by all Promise.all resolve element closures,
// together with the values array. Since all closures for a single Promise.all
// call use the same context, we need to store the indices for the individual
// closures somewhere else (we put them into the identity hash field of the
// closures), and we also need to have a separate marker for when the closure
// was called already (we slap the native context onto the closure in that
// case to mark it's done).
macro CreatePromiseAllResolveElementContext(implicit context: Context)(
capability: PromiseCapability, nativeContext: NativeContext): Context {
const resolveContext = AllocateSyntheticFunctionContext(
nativeContext,
PromiseAllResolveElementContextSlots::kPromiseAllResolveElementLength);
resolveContext.elements[PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementRemainingSlot] =
SmiConstant(1);
resolveContext.elements[PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementCapabilitySlot] =
capability;
resolveContext.elements[PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementValuesSlot] =
kEmptyFixedArray;
return resolveContext;
}
macro CreatePromiseAllResolveElementFunction(implicit context: Context)(
resolveElementContext: Context, index: Smi, nativeContext: NativeContext,
resolveFunction: SharedFunctionInfo): JSFunction {
assert(index > 0);
assert(index < kPropertyArrayHashFieldMax);
const map = UnsafeCast<Map>(
nativeContext.elements
[NativeContextSlot::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX]);
const resolve = AllocateFunctionWithMapAndContext(
map, resolveFunction, resolveElementContext);
assert(kPropertyArrayNoHashSentinel == 0);
resolve.properties_or_hash = index;
return resolve;
}
@export
macro CreatePromiseResolvingFunctionsContext(implicit context: Context)(
promise: JSPromise, debugEvent: Object, nativeContext: NativeContext):
Context {
const resolveContext = AllocateSyntheticFunctionContext(
nativeContext, kPromiseBuiltinsPromiseContextLength);
resolveContext.elements[kPromiseBuiltinsPromiseSlot] = promise;
resolveContext.elements[kPromiseBuiltinsAlreadyResolvedSlot] = False;
resolveContext.elements[kPromiseBuiltinsDebugEventSlot] = debugEvent;
return resolveContext;
}
macro IsPromiseThenLookupChainIntact(implicit context: Context)(
nativeContext: NativeContext, receiverMap: Map): bool {
if (IsForceSlowPath()) return false;
if (!IsJSPromiseMap(receiverMap)) return false;
if (receiverMap.prototype !=
nativeContext.elements[NativeContextSlot::PROMISE_PROTOTYPE_INDEX])
return false;
return !IsPromiseThenProtectorCellInvalid();
}
struct PromiseAllResolveElementFunctor {
macro Call(implicit context: Context)(
resolveElementContext: Context, nativeContext: NativeContext, index: Smi,
_capability: PromiseCapability): Callable {
return CreatePromiseAllResolveElementFunction(
resolveElementContext, index, nativeContext,
PromiseAllResolveElementSharedFunConstant());
}
}
struct PromiseAllRejectElementFunctor {
macro Call(implicit context: Context)(
_resolveElementContext: Context, _nativeContext: NativeContext,
_index: Smi, capability: PromiseCapability): Callable {
return UnsafeCast<Callable>(capability.reject);
}
}
struct PromiseAllSettledResolveElementFunctor {
macro Call(implicit context: Context)(
resolveElementContext: Context, nativeContext: NativeContext, index: Smi,
_capability: PromiseCapability): Callable {
return CreatePromiseAllResolveElementFunction(
resolveElementContext, index, nativeContext,
PromiseAllSettledResolveElementSharedFunConstant());
}
}
struct PromiseAllSettledRejectElementFunctor {
macro Call(implicit context: Context)(
resolveElementContext: Context, nativeContext: NativeContext, index: Smi,
_capability: PromiseCapability): Callable {
return CreatePromiseAllResolveElementFunction(
resolveElementContext, index, nativeContext,
PromiseAllSettledRejectElementSharedFunConstant());
}
}
transitioning macro PerformPromiseAll<F1: type, F2: type>(
implicit context: Context)(
nativeContext: NativeContext, iter: iterator::IteratorRecord,
constructor: Constructor, capability: PromiseCapability,
promiseResolveFunction: JSAny, createResolveElementFunctor: F1,
createRejectElementFunctor: F2): JSAny labels
Reject(Object) {
const promise = capability.promise;
const resolve = capability.resolve;
const reject = capability.reject;
// For catch prediction, don't treat the .then calls as handling it;
// instead, recurse outwards.
if (IsDebugActive()) deferred {
SetPropertyStrict(context, reject, kPromiseForwardingHandlerSymbol, True);
}
const resolveElementContext =
CreatePromiseAllResolveElementContext(capability, nativeContext);
let index: Smi = 1;
try {
const fastIteratorResultMap = UnsafeCast<Map>(
nativeContext.elements[NativeContextSlot::ITERATOR_RESULT_MAP_INDEX]);
while (true) {
let nextValue: JSAny;
try {
// Let next be IteratorStep(iteratorRecord.[[Iterator]]).
// If next is an abrupt completion, set iteratorRecord.[[Done]] to
// true. ReturnIfAbrupt(next).
const next: JSReceiver = iterator::IteratorStep(
iter, fastIteratorResultMap) otherwise goto Done;
// Let nextValue be IteratorValue(next).
// If nextValue is an abrupt completion, set iteratorRecord.[[Done]]
// to true.
// ReturnIfAbrupt(nextValue).
nextValue = iterator::IteratorValue(next, fastIteratorResultMap);
} catch (e) {
goto Reject(e);
}
// Check if we reached the limit.
if (index == kPropertyArrayHashFieldMax) {
// If there are too many elements (currently more than 2**21-1),
// raise a RangeError here (which is caught below and turned into
// a rejection of the resulting promise). We could gracefully handle
// this case as well and support more than this number of elements
// by going to a separate function and pass the larger indices via a
// separate context, but it doesn't seem likely that we need this,
// and it's unclear how the rest of the system deals with 2**21 live
// Promises anyway.
ThrowRangeError(
MessageTemplate::kTooManyElementsInPromiseCombinator, 'all');
}
// Set remainingElementsCount.[[Value]] to
// remainingElementsCount.[[Value]] + 1.
const remainingElementsCount = UnsafeCast<Smi>(
resolveElementContext
.elements[PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementRemainingSlot]);
resolveElementContext
.elements[PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementRemainingSlot] =
remainingElementsCount + 1;
// Let resolveElement be CreateBuiltinFunction(steps,
// « [[AlreadyCalled]],
// [[Index]],
// [[Values]],
// [[Capability]],
// [[RemainingElements]]
// »).
// Set resolveElement.[[AlreadyCalled]] to a Record { [[Value]]: false
// }. Set resolveElement.[[Index]] to index. Set
// resolveElement.[[Values]] to values. Set
// resolveElement.[[Capability]] to resultCapability. Set
// resolveElement.[[RemainingElements]] to remainingElementsCount.
const resolveElementFun = createResolveElementFunctor.Call(
resolveElementContext, nativeContext, index, capability);
const rejectElementFun = createRejectElementFunctor.Call(
resolveElementContext, nativeContext, index, capability);
// We can skip the "then" lookup on the result of the "resolve" call and
// immediately chain the continuation onto the {next_value} if:
//
// (a) The {constructor} is the intrinsic %Promise% function, and
// looking up "resolve" on {constructor} yields the initial
// Promise.resolve() builtin, and
// (b) the promise @@species protector cell is valid, meaning that
// no one messed with the Symbol.species property on any
// intrinsic promise or on the Promise.prototype, and
// (c) the {next_value} is a JSPromise whose [[Prototype]] field
// contains the intrinsic %PromisePrototype%, and
// (d) we're not running with async_hooks or DevTools enabled.
//
// In that case we also don't need to allocate a chained promise for
// the PromiseReaction (aka we can pass undefined to
// PerformPromiseThen), since this is only necessary for DevTools and
// PromiseHooks.
if (promiseResolveFunction != Undefined ||
IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() ||
IsPromiseSpeciesProtectorCellInvalid() || Is<Smi>(nextValue) ||
!IsPromiseThenLookupChainIntact(
nativeContext, UnsafeCast<HeapObject>(nextValue).map)) {
// Let nextPromise be ? Call(constructor, _promiseResolve_, «
// nextValue »).
const nextPromise =
CallResolve(constructor, promiseResolveFunction, nextValue);
// Perform ? Invoke(nextPromise, "then", « resolveElement,
// resultCapability.[[Reject]] »).
const then = GetProperty(nextPromise, kThenString);
const thenResult = Call(
nativeContext, then, nextPromise, resolveElementFun,
rejectElementFun);
// For catch prediction, mark that rejections here are
// semantically handled by the combined Promise.
if (IsDebugActive() && Is<JSPromise>(thenResult)) deferred {
SetPropertyStrict(
context, thenResult, kPromiseHandledBySymbol, promise);
}
} else {
PerformPromiseThenImpl(
UnsafeCast<JSPromise>(nextValue), resolveElementFun,
rejectElementFun, Undefined);
}
// Set index to index + 1.
index += 1;
}
} catch (e) deferred {
iterator::IteratorCloseOnException(iter);
goto Reject(e);
} label Done {}
// Set iteratorRecord.[[Done]] to true.
// Set remainingElementsCount.[[Value]] to
// remainingElementsCount.[[Value]] - 1.
let remainingElementsCount = UnsafeCast<Smi>(
resolveElementContext
.elements[PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementRemainingSlot]);
remainingElementsCount -= 1;
resolveElementContext.elements[PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementRemainingSlot] =
remainingElementsCount;
if (remainingElementsCount > 0) {
// Pre-allocate the backing store for the {values} to the desired
// capacity. We may already have elements in "values" - this happens
// when the Thenable calls the resolve callback immediately.
let values = UnsafeCast<FixedArray>(
resolveElementContext
.elements[PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementValuesSlot]);
// 'index' is a 1-based index and incremented after every Promise. Later we
// use 'values' as a 0-based array, so capacity 'index - 1' is enough.
const newCapacity = SmiUntag(index) - 1;
const oldCapacity = values.length_intptr;
if (oldCapacity < newCapacity) {
values = ExtractFixedArray(values, 0, oldCapacity, newCapacity);
resolveElementContext.elements[PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementValuesSlot] =
values;
}
} else
deferred {
assert(remainingElementsCount == 0);
// If remainingElementsCount.[[Value]] is 0, then
// Let valuesArray be CreateArrayFromList(values).
// Perform ? Call(resultCapability.[[Resolve]], undefined,
// « valuesArray »).
const values = UnsafeCast<FixedArray>(
resolveElementContext
.elements[PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementValuesSlot]);
const arrayMap = UnsafeCast<Map>(
nativeContext
.elements[NativeContextSlot::JS_ARRAY_PACKED_ELEMENTS_MAP_INDEX]);
const valuesArray = NewJSArray(arrayMap, values);
Call(nativeContext, UnsafeCast<JSAny>(resolve), Undefined, valuesArray);
}
// Return resultCapability.[[Promise]].
return promise;
}
transitioning macro GeneratePromiseAll<F1: type, F2: type>(
implicit context: Context)(
receiver: JSAny, iterable: JSAny, createResolveElementFunctor: F1,
createRejectElementFunctor: F2): JSAny {
const nativeContext = LoadNativeContext(context);
// Let C be the this value.
// If Type(C) is not Object, throw a TypeError exception.
const receiver = Cast<JSReceiver>(receiver)
otherwise ThrowTypeError(MessageTemplate::kCalledOnNonObject, 'Promise.all');
// Let promiseCapability be ? NewPromiseCapability(C).
// Don't fire debugEvent so that forwarding the rejection through all does
// not trigger redundant ExceptionEvents
const capability = NewPromiseCapability(receiver, False);
// NewPromiseCapability guarantees that receiver is Constructor.
assert(Is<Constructor>(receiver));
const constructor = UnsafeCast<Constructor>(receiver);
try {
// Let promiseResolve be GetPromiseResolve(C).
// IfAbruptRejectPromise(promiseResolve, promiseCapability).
const promiseResolveFunction =
GetPromiseResolve(nativeContext, constructor);
// Let iterator be GetIterator(iterable).
// IfAbruptRejectPromise(iterator, promiseCapability).
let i = iterator::GetIterator(iterable);
// Let result be PerformPromiseAll(iteratorRecord, C,
// promiseCapability). If result is an abrupt completion, then
// If iteratorRecord.[[Done]] is false, let result be
// IteratorClose(iterator, result).
// IfAbruptRejectPromise(result, promiseCapability).
return PerformPromiseAll(
nativeContext, i, constructor, capability, promiseResolveFunction,
createResolveElementFunctor, createRejectElementFunctor)
otherwise Reject;
} catch (e) deferred {
goto Reject(e);
} label Reject(e: Object) deferred {
// Exception must be bound to a JS value.
const e = UnsafeCast<JSAny>(e);
const reject = UnsafeCast<JSAny>(capability.reject);
Call(context, reject, Undefined, e);
return capability.promise;
}
}
// ES#sec-promise.all
transitioning javascript builtin PromiseAll(
js-implicit context: Context, receiver: JSAny)(iterable: JSAny): JSAny {
return GeneratePromiseAll(
receiver, iterable, PromiseAllResolveElementFunctor{},
PromiseAllRejectElementFunctor{});
}
// ES#sec-promise.allsettled
// Promise.allSettled ( iterable )
transitioning javascript builtin PromiseAllSettled(
js-implicit context: Context, receiver: JSAny)(iterable: JSAny): JSAny {
return GeneratePromiseAll(
receiver, iterable, PromiseAllSettledResolveElementFunctor{},
PromiseAllSettledRejectElementFunctor{});
}
extern macro PromiseAllResolveElementSharedFunConstant(): SharedFunctionInfo;
extern macro PromiseAllSettledRejectElementSharedFunConstant():
SharedFunctionInfo;
extern macro PromiseAllSettledResolveElementSharedFunConstant():
SharedFunctionInfo;
}