blob: 65bfcc42c14da43c21431ab51de35831b6eb52c4 [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 runtime {
extern transitioning runtime
RejectPromise(implicit context: Context)(JSPromise, JSAny, Boolean): Object;
}
// https://tc39.es/ecma262/#sec-promise-abstract-operations
namespace promise {
const PROMISE_FUNCTION_INDEX: constexpr NativeContextSlot
generates 'Context::PROMISE_FUNCTION_INDEX';
const STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX: constexpr NativeContextSlot
generates 'Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX';
const PROMISE_CAPABILITY_DEFAULT_RESOLVE_SHARED_FUN_INDEX:
constexpr NativeContextSlot
generates 'Context::PROMISE_CAPABILITY_DEFAULT_RESOLVE_SHARED_FUN_INDEX'
;
const PROMISE_CAPABILITY_DEFAULT_REJECT_SHARED_FUN_INDEX:
constexpr NativeContextSlot
generates 'Context::PROMISE_CAPABILITY_DEFAULT_REJECT_SHARED_FUN_INDEX'
;
const PROMISE_GET_CAPABILITIES_EXECUTOR_SHARED_FUN:
constexpr NativeContextSlot
generates 'Context::PROMISE_GET_CAPABILITIES_EXECUTOR_SHARED_FUN';
const kPromiseNonCallable: constexpr MessageTemplate
generates 'MessageTemplate::kPromiseNonCallable';
extern macro AllocateFunctionWithMapAndContext(
Map, SharedFunctionInfo, Context): JSFunction;
extern macro PromiseReactionMapConstant(): Map;
extern macro PromiseFulfillReactionJobTaskMapConstant(): Map;
extern macro PromiseRejectReactionJobTaskMapConstant(): Map;
extern transitioning builtin
EnqueueMicrotask(Context, PromiseReactionJobTask): Undefined;
macro
ExtractHandlerContext(implicit context: Context)(handler: Callable|Undefined):
Context labels NotFound {
let iter: JSAny = handler;
while (true) {
typeswitch (iter) {
case (b: JSBoundFunction): {
iter = b.bound_target_function;
}
case (p: JSProxy): {
iter = p.target;
}
case (f: JSFunction): {
return f.context;
}
case (JSAny): {
break;
}
}
}
goto NotFound;
}
transitioning macro MorpAndEnqueuePromiseReaction(implicit context: Context)(
promiseReaction: PromiseReaction, argument: JSAny,
reactionType: constexpr PromiseReactionType): void {
let primaryHandler: Callable|Undefined;
let secondaryHandler: Callable|Undefined;
if constexpr (reactionType == kPromiseReactionFulfill) {
primaryHandler = promiseReaction.fulfill_handler;
secondaryHandler = promiseReaction.reject_handler;
} else {
StaticAssert(reactionType == kPromiseReactionReject);
primaryHandler = promiseReaction.reject_handler;
secondaryHandler = promiseReaction.fulfill_handler;
}
let handlerContext: Context;
try {
handlerContext = ExtractHandlerContext(primaryHandler) otherwise NotFound;
}
label NotFound {
handlerContext =
ExtractHandlerContext(secondaryHandler) otherwise Default;
}
label Default {
handlerContext = context;
}
// Morph {current} from a PromiseReaction into a PromiseReactionJobTask
// and schedule that on the microtask queue. We try to minimize the number
// of stores here to avoid screwing up the store buffer.
StaticAssert(
kPromiseReactionSize ==
kPromiseReactionJobTaskSizeOfAllPromiseReactionJobTasks);
if constexpr (reactionType == kPromiseReactionFulfill) {
promiseReaction.map = PromiseFulfillReactionJobTaskMapConstant();
const promiseReactionJobTask =
UnsafeCast<PromiseFulfillReactionJobTask>(promiseReaction);
promiseReactionJobTask.argument = argument;
promiseReactionJobTask.context = handlerContext;
EnqueueMicrotask(handlerContext, promiseReactionJobTask);
StaticAssert(
kPromiseReactionFulfillHandlerOffset ==
kPromiseReactionJobTaskHandlerOffset);
StaticAssert(
kPromiseReactionPromiseOrCapabilityOffset ==
kPromiseReactionJobTaskPromiseOrCapabilityOffset);
} else {
StaticAssert(reactionType == kPromiseReactionReject);
promiseReaction.map = PromiseRejectReactionJobTaskMapConstant();
const promiseReactionJobTask =
UnsafeCast<PromiseRejectReactionJobTask>(promiseReaction);
promiseReactionJobTask.argument = argument;
promiseReactionJobTask.context = handlerContext;
promiseReactionJobTask.handler = primaryHandler;
EnqueueMicrotask(handlerContext, promiseReactionJobTask);
StaticAssert(
kPromiseReactionPromiseOrCapabilityOffset ==
kPromiseReactionJobTaskPromiseOrCapabilityOffset);
}
}
// https://tc39.es/ecma262/#sec-triggerpromisereactions
transitioning macro TriggerPromiseReactions(implicit context: Context)(
reactions: Zero|PromiseReaction, argument: JSAny,
reactionType: constexpr PromiseReactionType): void {
// We need to reverse the {reactions} here, since we record them on the
// JSPromise in the reverse order.
let current = reactions;
let reversed: Zero|PromiseReaction = kZero;
// As an additional safety net against misuse of the V8 Extras API, we
// sanity check the {reactions} to make sure that they are actually
// PromiseReaction instances and not actual JavaScript values (which
// would indicate that we're rejecting or resolving an already settled
// promise), see https://crbug.com/931640 for details on this.
while (true) {
typeswitch (current) {
case (Zero): {
break;
}
case (currentReaction: PromiseReaction): {
current = currentReaction.next;
currentReaction.next = reversed;
reversed = currentReaction;
}
}
}
// Morph the {reactions} into PromiseReactionJobTasks and push them
// onto the microtask queue.
current = reversed;
while (true) {
typeswitch (current) {
case (Zero): {
break;
}
case (currentReaction: PromiseReaction): {
current = currentReaction.next;
MorpAndEnqueuePromiseReaction(
currentReaction, argument, reactionType);
}
}
}
}
// https://tc39.es/ecma262/#sec-fulfillpromise
transitioning builtin
FulfillPromise(implicit context: Context)(promise: JSPromise, value: JSAny):
Undefined {
// Assert: The value of promise.[[PromiseState]] is "pending".
assert(promise.Status() == kPromisePending);
// 2. Let reactions be promise.[[PromiseFulfillReactions]].
const reactions =
UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result);
// 3. Set promise.[[PromiseResult]] to value.
// 4. Set promise.[[PromiseFulfillReactions]] to undefined.
// 5. Set promise.[[PromiseRejectReactions]] to undefined.
promise.reactions_or_result = value;
// 6. Set promise.[[PromiseState]] to "fulfilled".
promise.SetStatus(kPromiseFulfilled);
// 7. Return TriggerPromiseReactions(reactions, value).
TriggerPromiseReactions(reactions, value, kPromiseReactionFulfill);
return Undefined;
}
extern macro PromiseBuiltinsAssembler::
IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(): bool;
// https://tc39.es/ecma262/#sec-rejectpromise
transitioning builtin
RejectPromise(implicit context: Context)(
promise: JSPromise, reason: JSAny, debugEvent: Boolean): Object {
// If promise hook is enabled or the debugger is active, let
// the runtime handle this operation, which greatly reduces
// the complexity here and also avoids a couple of back and
// forth between JavaScript and C++ land.
if (IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() ||
!promise.HasHandler()) {
// 7. If promise.[[PromiseIsHandled]] is false, perform
// HostPromiseRejectionTracker(promise, "reject").
// We don't try to handle rejecting {promise} without handler
// here, but we let the C++ code take care of this completely.
return runtime::RejectPromise(promise, reason, debugEvent);
}
// 2. Let reactions be promise.[[PromiseRejectReactions]].
const reactions =
UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result);
// 3. Set promise.[[PromiseResult]] to reason.
// 4. Set promise.[[PromiseFulfillReactions]] to undefined.
// 5. Set promise.[[PromiseRejectReactions]] to undefined.
promise.reactions_or_result = reason;
// 6. Set promise.[[PromiseState]] to "rejected".
promise.SetStatus(kPromiseRejected);
// 8. Return TriggerPromiseReactions(reactions, reason).
TriggerPromiseReactions(reactions, reason, kPromiseReactionReject);
return Undefined;
}
const kPromiseCapabilitySize:
constexpr int31 generates 'PromiseCapability::kSize';
const kPromiseBuiltinsCapabilitiesContextLength: constexpr int31
generates 'PromiseBuiltins::kCapabilitiesContextLength';
const kPromiseBuiltinsCapabilitySlot: constexpr ContextSlot
generates 'PromiseBuiltins::kCapabilitySlot';
extern macro
PromiseBuiltinsAssembler::AllocateAndInitJSPromise(Context): JSPromise;
extern macro
PromiseBuiltinsAssembler::CreatePromiseResolvingFunctionsContext(
JSPromise, Object, NativeContext): Context;
@export
macro CreatePromiseCapabilitiesExecutorContext(
nativeContext: NativeContext, capability: PromiseCapability): Context {
const executorContext = AllocateSyntheticFunctionContext(
nativeContext, kPromiseBuiltinsCapabilitiesContextLength);
executorContext[kPromiseBuiltinsCapabilitySlot] = capability;
return executorContext;
}
macro CreatePromiseCapability(
promise: JSReceiver|Undefined, resolve: JSFunction|Undefined,
reject: JSFunction|Undefined): PromiseCapability {
return new PromiseCapability{
map: kPromiseCapabilityMap,
promise: promise,
resolve: resolve,
reject: reject
};
}
@export
struct PromiseResolvingFunctions {
resolve: JSFunction;
reject: JSFunction;
}
@export
macro CreatePromiseResolvingFunctions(implicit context: Context)(
promise: JSPromise, debugEvent: Object, nativeContext: NativeContext):
PromiseResolvingFunctions {
const promiseContext = CreatePromiseResolvingFunctionsContext(
promise, debugEvent, nativeContext);
const map = UnsafeCast<Map>(
nativeContext[STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX]);
const resolveInfo = UnsafeCast<SharedFunctionInfo>(
nativeContext[PROMISE_CAPABILITY_DEFAULT_RESOLVE_SHARED_FUN_INDEX]);
const resolve: JSFunction =
AllocateFunctionWithMapAndContext(map, resolveInfo, promiseContext);
const rejectInfo = UnsafeCast<SharedFunctionInfo>(
nativeContext[PROMISE_CAPABILITY_DEFAULT_REJECT_SHARED_FUN_INDEX]);
const reject: JSFunction =
AllocateFunctionWithMapAndContext(map, rejectInfo, promiseContext);
return PromiseResolvingFunctions{resolve: resolve, reject: reject};
}
transitioning macro
InnerNewPromiseCapability(implicit context: Context)(
constructor: HeapObject, debugEvent: Object): PromiseCapability {
const nativeContext = LoadNativeContext(context);
if (TaggedEqual(constructor, nativeContext[PROMISE_FUNCTION_INDEX])) {
const promise = AllocateAndInitJSPromise(nativeContext);
const pair =
CreatePromiseResolvingFunctions(promise, debugEvent, nativeContext);
return CreatePromiseCapability(promise, pair.resolve, pair.reject);
} else {
// We have to create the capability before the associated promise
// because the builtin PromiseConstructor uses the executor.
const capability =
CreatePromiseCapability(Undefined, Undefined, Undefined);
const executorContext =
CreatePromiseCapabilitiesExecutorContext(nativeContext, capability);
const executorInfo = UnsafeCast<SharedFunctionInfo>(
nativeContext[PROMISE_GET_CAPABILITIES_EXECUTOR_SHARED_FUN]);
const functionMap = UnsafeCast<Map>(
nativeContext[STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX]);
const executor = AllocateFunctionWithMapAndContext(
functionMap, executorInfo, executorContext);
const promiseConstructor = UnsafeCast<Constructor>(constructor);
const promise = Construct(promiseConstructor, executor);
capability.promise = promise;
if (!TaggedIsCallable(capability.resolve) ||
!TaggedIsCallable(capability.reject)) {
ThrowTypeError(kPromiseNonCallable);
}
return capability;
}
}
// https://tc39.es/ecma262/#sec-newpromisecapability
transitioning builtin
NewPromiseCapability(implicit context: Context)(
maybeConstructor: Object, debugEvent: Object): PromiseCapability {
typeswitch (maybeConstructor) {
case (Smi): {
ThrowTypeError(kNotConstructor, maybeConstructor);
}
case (constructor: HeapObject): {
if (!IsConstructor(constructor)) {
ThrowTypeError(kNotConstructor, maybeConstructor);
}
return InnerNewPromiseCapability(constructor, debugEvent);
}
}
}
}