blob: bf07ff622746db070ca8b0221c1f0f870efff9d4 [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'
#include 'src/objects/property-array.h'
namespace promise {
struct PromiseAllWrapResultAsFulfilledFunctor {
macro Call(_nativeContext: NativeContext, value: JSAny): JSAny {
return value;
}
}
struct PromiseAllSettledWrapResultAsFulfilledFunctor {
transitioning
macro Call(implicit context: Context)(
nativeContext: NativeContext, value: JSAny): JSAny {
// TODO(gsathya): Optimize the creation using a cached map to
// prevent transitions here.
// 9. Let obj be ! ObjectCreate(%ObjectPrototype%).
const objectFunction =
*NativeContextSlot(nativeContext, ContextSlot::OBJECT_FUNCTION_INDEX);
const objectFunctionMap =
UnsafeCast<Map>(objectFunction.prototype_or_initial_map);
const obj = AllocateJSObjectFromMap(objectFunctionMap);
// 10. Perform ! CreateDataProperty(obj, "status", "fulfilled").
FastCreateDataProperty(
obj, StringConstant('status'), StringConstant('fulfilled'));
// 11. Perform ! CreateDataProperty(obj, "value", x).
FastCreateDataProperty(obj, StringConstant('value'), value);
return obj;
}
}
struct PromiseAllSettledWrapResultAsRejectedFunctor {
transitioning
macro Call(implicit context: Context)(
nativeContext: NativeContext, value: JSAny): JSAny {
// TODO(gsathya): Optimize the creation using a cached map to
// prevent transitions here.
// 9. Let obj be ! ObjectCreate(%ObjectPrototype%).
const objectFunction =
*NativeContextSlot(nativeContext, ContextSlot::OBJECT_FUNCTION_INDEX);
const objectFunctionMap =
UnsafeCast<Map>(objectFunction.prototype_or_initial_map);
const obj = AllocateJSObjectFromMap(objectFunctionMap);
// 10. Perform ! CreateDataProperty(obj, "status", "rejected").
FastCreateDataProperty(
obj, StringConstant('status'), StringConstant('rejected'));
// 11. Perform ! CreateDataProperty(obj, "reason", x).
FastCreateDataProperty(obj, StringConstant('reason'), value);
return obj;
}
}
extern macro LoadJSReceiverIdentityHash(Object): intptr labels IfNoHash;
type PromiseAllResolveElementContext extends FunctionContext;
extern enum PromiseAllResolveElementContextSlots extends intptr
constexpr 'PromiseBuiltins::PromiseAllResolveElementContextSlots' {
kPromiseAllResolveElementRemainingSlot:
Slot<PromiseAllResolveElementContext, Smi>,
kPromiseAllResolveElementCapabilitySlot:
Slot<PromiseAllResolveElementContext, PromiseCapability>,
kPromiseAllResolveElementValuesSlot:
Slot<PromiseAllResolveElementContext, FixedArray>,
kPromiseAllResolveElementLength
}
extern operator '[]=' macro StoreContextElement(
Context, constexpr PromiseAllResolveElementContextSlots, Object): void;
extern operator '[]' macro LoadContextElement(
Context, constexpr PromiseAllResolveElementContextSlots): Object;
const kPropertyArrayNoHashSentinel: constexpr int31
generates 'PropertyArray::kNoHashSentinel';
const kPropertyArrayHashFieldMax: constexpr int31
generates 'PropertyArray::HashField::kMax';
transitioning macro PromiseAllResolveElementClosure<F: type>(
implicit context: PromiseAllResolveElementContext|NativeContext)(
value: JSAny, function: JSFunction, wrapResultFunctor: F,
hasResolveAndRejectClosures: constexpr bool): JSAny {
// We use the {function}s context as the marker to remember whether this
// resolve element closure was already called. It points to the resolve
// element context (which is a FunctionContext) until it was called the
// first time, in which case we make it point to the native context here
// to mark this resolve element closure as done.
let promiseContext: PromiseAllResolveElementContext;
typeswitch (context) {
case (NativeContext): deferred {
return Undefined;
}
case (context: PromiseAllResolveElementContext): {
promiseContext = context;
}
}
assert(
promiseContext.length ==
SmiTag(PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementLength));
const nativeContext = LoadNativeContext(promiseContext);
function.context = nativeContext;
// Determine the index from the {function}.
assert(kPropertyArrayNoHashSentinel == 0);
const identityHash =
LoadJSReceiverIdentityHash(function) otherwise unreachable;
assert(identityHash > 0);
const index = identityHash - 1;
let remainingElementsCount = *ContextSlot(
promiseContext,
PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementRemainingSlot);
let values = *ContextSlot(
promiseContext,
PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementValuesSlot);
const newCapacity = index + 1;
if (newCapacity > values.length_intptr) deferred {
// This happens only when the promises are resolved during iteration.
values = ExtractFixedArray(values, 0, values.length_intptr, newCapacity);
*ContextSlot(
promiseContext,
PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementValuesSlot) = values;
}
// Promise.allSettled, for each input element, has both a resolve and a reject
// closure that share an [[AlreadyCalled]] boolean. That is, the input element
// can only be settled once: after resolve is called, reject returns early,
// and vice versa. Using {function}'s context as the marker only tracks
// per-closure instead of per-element. When the second resolve/reject closure
// is called on the same index, values.object[index] will already exist and
// will not be the hole value. In that case, return early. Everything up to
// this point is not yet observable to user code. This is not a problem for
// Promise.all since Promise.all has a single resolve closure (no reject) per
// element.
if (hasResolveAndRejectClosures) {
if (values.objects[index] != TheHole) deferred {
return Undefined;
}
}
// Update the value depending on whether Promise.all or
// Promise.allSettled is called.
const updatedValue = wrapResultFunctor.Call(nativeContext, value);
values.objects[index] = updatedValue;
remainingElementsCount = remainingElementsCount - 1;
check(remainingElementsCount >= 0);
*ContextSlot(
promiseContext,
PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementRemainingSlot) = remainingElementsCount;
if (remainingElementsCount == 0) {
const capability = *ContextSlot(
promiseContext,
PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementCapabilitySlot);
const resolve = UnsafeCast<JSAny>(capability.resolve);
const arrayMap =
*NativeContextSlot(
nativeContext, ContextSlot::JS_ARRAY_PACKED_ELEMENTS_MAP_INDEX);
const valuesArray = NewJSArray(arrayMap, values);
Call(promiseContext, resolve, Undefined, valuesArray);
}
return Undefined;
}
transitioning javascript builtin
PromiseAllResolveElementClosure(
js-implicit context: Context, receiver: JSAny,
target: JSFunction)(value: JSAny): JSAny {
const context =
%RawDownCast<PromiseAllResolveElementContext|NativeContext>(context);
return PromiseAllResolveElementClosure(
value, target, PromiseAllWrapResultAsFulfilledFunctor{}, false);
}
transitioning javascript builtin
PromiseAllSettledResolveElementClosure(
js-implicit context: Context, receiver: JSAny,
target: JSFunction)(value: JSAny): JSAny {
const context =
%RawDownCast<PromiseAllResolveElementContext|NativeContext>(context);
return PromiseAllResolveElementClosure(
value, target, PromiseAllSettledWrapResultAsFulfilledFunctor{}, true);
}
transitioning javascript builtin
PromiseAllSettledRejectElementClosure(
js-implicit context: Context, receiver: JSAny,
target: JSFunction)(value: JSAny): JSAny {
const context =
%RawDownCast<PromiseAllResolveElementContext|NativeContext>(context);
return PromiseAllResolveElementClosure(
value, target, PromiseAllSettledWrapResultAsRejectedFunctor{}, true);
}
}