blob: 24837eb265e3e4eb5fa15518d4a3814993d4012b [file] [log] [blame]
// Copyright 2016 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-gen.h"
#include "src/builtins/builtins-constructor-gen.h"
#include "src/builtins/builtins-iterator-gen.h"
#include "src/builtins/builtins-promise.h"
#include "src/builtins/builtins-utils-gen.h"
#include "src/builtins/builtins.h"
#include "src/codegen/code-factory.h"
#include "src/codegen/code-stub-assembler.h"
#include "src/objects/js-promise.h"
#include "src/objects/objects-inl.h"
#include "src/objects/smi.h"
namespace v8 {
namespace internal {
typedef compiler::Node Node;
template <class T>
using TNode = CodeStubAssembler::TNode<T>;
using IteratorRecord = IteratorBuiltinsAssembler::IteratorRecord;
Node* PromiseBuiltinsAssembler::AllocateJSPromise(Node* context) {
Node* const native_context = LoadNativeContext(context);
Node* const promise_fun =
LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
CSA_ASSERT(this, IsFunctionWithPrototypeSlotMap(LoadMap(promise_fun)));
Node* const promise_map =
LoadObjectField(promise_fun, JSFunction::kPrototypeOrInitialMapOffset);
Node* const promise = Allocate(JSPromise::kSizeWithEmbedderFields);
StoreMapNoWriteBarrier(promise, promise_map);
StoreObjectFieldRoot(promise, JSPromise::kPropertiesOrHashOffset,
RootIndex::kEmptyFixedArray);
StoreObjectFieldRoot(promise, JSPromise::kElementsOffset,
RootIndex::kEmptyFixedArray);
return promise;
}
void PromiseBuiltinsAssembler::PromiseInit(Node* promise) {
STATIC_ASSERT(v8::Promise::kPending == 0);
StoreObjectFieldNoWriteBarrier(promise, JSPromise::kReactionsOrResultOffset,
SmiConstant(Smi::zero()));
StoreObjectFieldNoWriteBarrier(promise, JSPromise::kFlagsOffset,
SmiConstant(Smi::zero()));
for (int offset = JSPromise::kSize;
offset < JSPromise::kSizeWithEmbedderFields; offset += kTaggedSize) {
StoreObjectFieldNoWriteBarrier(promise, offset, SmiConstant(Smi::zero()));
}
}
Node* PromiseBuiltinsAssembler::AllocateAndInitJSPromise(Node* context) {
return AllocateAndInitJSPromise(context, UndefinedConstant());
}
Node* PromiseBuiltinsAssembler::AllocateAndInitJSPromise(Node* context,
Node* parent) {
Node* const instance = AllocateJSPromise(context);
PromiseInit(instance);
Label out(this);
GotoIfNot(IsPromiseHookEnabledOrHasAsyncEventDelegate(), &out);
CallRuntime(Runtime::kPromiseHookInit, context, instance, parent);
Goto(&out);
BIND(&out);
return instance;
}
Node* PromiseBuiltinsAssembler::AllocateAndSetJSPromise(
Node* context, v8::Promise::PromiseState status, Node* result) {
DCHECK_NE(Promise::kPending, status);
Node* const instance = AllocateJSPromise(context);
StoreObjectFieldNoWriteBarrier(instance, JSPromise::kReactionsOrResultOffset,
result);
STATIC_ASSERT(JSPromise::kStatusShift == 0);
StoreObjectFieldNoWriteBarrier(instance, JSPromise::kFlagsOffset,
SmiConstant(status));
for (int offset = JSPromise::kSize;
offset < JSPromise::kSizeWithEmbedderFields; offset += kTaggedSize) {
StoreObjectFieldNoWriteBarrier(instance, offset, SmiConstant(0));
}
Label out(this);
GotoIfNot(IsPromiseHookEnabledOrHasAsyncEventDelegate(), &out);
CallRuntime(Runtime::kPromiseHookInit, context, instance,
UndefinedConstant());
Goto(&out);
BIND(&out);
return instance;
}
std::pair<Node*, Node*>
PromiseBuiltinsAssembler::CreatePromiseResolvingFunctions(
Node* promise, Node* debug_event, Node* native_context) {
Node* const promise_context = CreatePromiseResolvingFunctionsContext(
promise, debug_event, native_context);
Node* const map = LoadContextElement(
native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
Node* const resolve_info = LoadContextElement(
native_context,
Context::PROMISE_CAPABILITY_DEFAULT_RESOLVE_SHARED_FUN_INDEX);
Node* const resolve =
AllocateFunctionWithMapAndContext(map, resolve_info, promise_context);
Node* const reject_info = LoadContextElement(
native_context,
Context::PROMISE_CAPABILITY_DEFAULT_REJECT_SHARED_FUN_INDEX);
Node* const reject =
AllocateFunctionWithMapAndContext(map, reject_info, promise_context);
return std::make_pair(resolve, reject);
}
void PromiseBuiltinsAssembler::ExtractHandlerContext(Node* handler,
Variable* var_context) {
VARIABLE(var_handler, MachineRepresentation::kTagged, handler);
Label loop(this, &var_handler), done(this, Label::kDeferred);
Goto(&loop);
BIND(&loop);
{
Label if_function(this), if_bound_function(this, Label::kDeferred),
if_proxy(this, Label::kDeferred);
GotoIf(TaggedIsSmi(var_handler.value()), &done);
int32_t case_values[] = {
JS_FUNCTION_TYPE,
JS_BOUND_FUNCTION_TYPE,
JS_PROXY_TYPE,
};
Label* case_labels[] = {
&if_function,
&if_bound_function,
&if_proxy,
};
static_assert(arraysize(case_values) == arraysize(case_labels), "");
TNode<Map> handler_map = LoadMap(var_handler.value());
TNode<Int32T> handler_type = LoadMapInstanceType(handler_map);
Switch(handler_type, &done, case_values, case_labels,
arraysize(case_labels));
BIND(&if_bound_function);
{
// Use the target function's context for JSBoundFunction.
var_handler.Bind(LoadObjectField(
var_handler.value(), JSBoundFunction::kBoundTargetFunctionOffset));
Goto(&loop);
}
BIND(&if_proxy);
{
// Use the target function's context for JSProxy.
// If the proxy is revoked, |var_handler| will be undefined and this
// function will return with unchanged |var_context|.
var_handler.Bind(
LoadObjectField(var_handler.value(), JSProxy::kTargetOffset));
Goto(&loop);
}
BIND(&if_function);
{
// Use the function's context.
Node* handler_context =
LoadObjectField(var_handler.value(), JSFunction::kContextOffset);
var_context->Bind(LoadNativeContext(CAST(handler_context)));
Goto(&done);
}
}
// If no valid context is available, |var_context| is unchanged and the caller
// will use a fallback context.
BIND(&done);
}
// ES #sec-newpromisecapability
TF_BUILTIN(NewPromiseCapability, PromiseBuiltinsAssembler) {
Node* const context = Parameter(Descriptor::kContext);
Node* const constructor = Parameter(Descriptor::kConstructor);
Node* const debug_event = Parameter(Descriptor::kDebugEvent);
TNode<Context> const native_context = LoadNativeContext(context);
Label if_not_constructor(this, Label::kDeferred),
if_notcallable(this, Label::kDeferred), if_fast_promise_capability(this),
if_slow_promise_capability(this, Label::kDeferred);
GotoIf(TaggedIsSmi(constructor), &if_not_constructor);
GotoIfNot(IsConstructorMap(LoadMap(constructor)), &if_not_constructor);
Branch(WordEqual(constructor,
LoadContextElement(native_context,
Context::PROMISE_FUNCTION_INDEX)),
&if_fast_promise_capability, &if_slow_promise_capability);
BIND(&if_fast_promise_capability);
{
Node* promise =
AllocateAndInitJSPromise(native_context, UndefinedConstant());
Node* resolve = nullptr;
Node* reject = nullptr;
std::tie(resolve, reject) =
CreatePromiseResolvingFunctions(promise, debug_event, native_context);
Node* capability = Allocate(PromiseCapability::kSize);
StoreMapNoWriteBarrier(capability, RootIndex::kPromiseCapabilityMap);
StoreObjectFieldNoWriteBarrier(capability,
PromiseCapability::kPromiseOffset, promise);
StoreObjectFieldNoWriteBarrier(capability,
PromiseCapability::kResolveOffset, resolve);
StoreObjectFieldNoWriteBarrier(capability, PromiseCapability::kRejectOffset,
reject);
Return(capability);
}
BIND(&if_slow_promise_capability);
{
Node* capability = Allocate(PromiseCapability::kSize);
StoreMapNoWriteBarrier(capability, RootIndex::kPromiseCapabilityMap);
StoreObjectFieldRoot(capability, PromiseCapability::kPromiseOffset,
RootIndex::kUndefinedValue);
StoreObjectFieldRoot(capability, PromiseCapability::kResolveOffset,
RootIndex::kUndefinedValue);
StoreObjectFieldRoot(capability, PromiseCapability::kRejectOffset,
RootIndex::kUndefinedValue);
Node* executor_context =
CreatePromiseGetCapabilitiesExecutorContext(capability, native_context);
Node* executor_info = LoadContextElement(
native_context, Context::PROMISE_GET_CAPABILITIES_EXECUTOR_SHARED_FUN);
Node* function_map = LoadContextElement(
native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
TNode<JSFunction> executor = CAST(AllocateFunctionWithMapAndContext(
function_map, executor_info, executor_context));
Node* promise = Construct(native_context, CAST(constructor), executor);
StoreObjectField(capability, PromiseCapability::kPromiseOffset, promise);
Node* resolve =
LoadObjectField(capability, PromiseCapability::kResolveOffset);
GotoIf(TaggedIsSmi(resolve), &if_notcallable);
GotoIfNot(IsCallable(resolve), &if_notcallable);
Node* reject =
LoadObjectField(capability, PromiseCapability::kRejectOffset);
GotoIf(TaggedIsSmi(reject), &if_notcallable);
GotoIfNot(IsCallable(reject), &if_notcallable);
Return(capability);
}
BIND(&if_not_constructor);
ThrowTypeError(context, MessageTemplate::kNotConstructor, constructor);
BIND(&if_notcallable);
ThrowTypeError(context, MessageTemplate::kPromiseNonCallable);
}
Node* PromiseBuiltinsAssembler::CreatePromiseContext(Node* native_context,
int slots) {
DCHECK_GE(slots, Context::MIN_CONTEXT_SLOTS);
Node* const context = AllocateInNewSpace(FixedArray::SizeFor(slots));
InitializeFunctionContext(native_context, context, slots);
return context;
}
Node* PromiseBuiltinsAssembler::CreatePromiseAllResolveElementContext(
Node* promise_capability, Node* native_context) {
CSA_ASSERT(this, IsNativeContext(native_context));
// TODO(bmeurer): Manually fold this into a single allocation.
TNode<Map> array_map = CAST(LoadContextElement(
native_context, Context::JS_ARRAY_PACKED_ELEMENTS_MAP_INDEX));
TNode<JSArray> values_array = AllocateJSArray(
PACKED_ELEMENTS, array_map, IntPtrConstant(0), SmiConstant(0));
Node* const context = CreatePromiseContext(
native_context, PromiseBuiltins::kPromiseAllResolveElementLength);
StoreContextElementNoWriteBarrier(
context, PromiseBuiltins::kPromiseAllResolveElementRemainingSlot,
SmiConstant(1));
StoreContextElementNoWriteBarrier(
context, PromiseBuiltins::kPromiseAllResolveElementCapabilitySlot,
promise_capability);
StoreContextElementNoWriteBarrier(
context, PromiseBuiltins::kPromiseAllResolveElementValuesArraySlot,
values_array);
return context;
}
TNode<JSFunction>
PromiseBuiltinsAssembler::CreatePromiseAllResolveElementFunction(
Node* context, TNode<Smi> index, Node* native_context, int slot_index) {
CSA_ASSERT(this, SmiGreaterThan(index, SmiConstant(0)));
CSA_ASSERT(this, SmiLessThanOrEqual(
index, SmiConstant(PropertyArray::HashField::kMax)));
CSA_ASSERT(this, IsNativeContext(native_context));
Node* const map = LoadContextElement(
native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
Node* const resolve_info = LoadContextElement(native_context, slot_index);
TNode<JSFunction> resolve =
Cast(AllocateFunctionWithMapAndContext(map, resolve_info, context));
STATIC_ASSERT(PropertyArray::kNoHashSentinel == 0);
StoreObjectFieldNoWriteBarrier(resolve, JSFunction::kPropertiesOrHashOffset,
index);
return resolve;
}
Node* PromiseBuiltinsAssembler::CreatePromiseResolvingFunctionsContext(
Node* promise, Node* debug_event, Node* native_context) {
Node* const context = CreatePromiseContext(
native_context, PromiseBuiltins::kPromiseContextLength);
StoreContextElementNoWriteBarrier(context, PromiseBuiltins::kPromiseSlot,
promise);
StoreContextElementNoWriteBarrier(
context, PromiseBuiltins::kAlreadyResolvedSlot, FalseConstant());
StoreContextElementNoWriteBarrier(context, PromiseBuiltins::kDebugEventSlot,
debug_event);
return context;
}
Node* PromiseBuiltinsAssembler::CreatePromiseGetCapabilitiesExecutorContext(
Node* promise_capability, Node* native_context) {
int kContextLength = PromiseBuiltins::kCapabilitiesContextLength;
Node* context = CreatePromiseContext(native_context, kContextLength);
StoreContextElementNoWriteBarrier(context, PromiseBuiltins::kCapabilitySlot,
promise_capability);
return context;
}
Node* PromiseBuiltinsAssembler::PromiseHasHandler(Node* promise) {
Node* const flags = LoadObjectField(promise, JSPromise::kFlagsOffset);
return IsSetWord(SmiUntag(flags), 1 << JSPromise::kHasHandlerBit);
}
void PromiseBuiltinsAssembler::PromiseSetHasHandler(Node* promise) {
TNode<Smi> const flags =
CAST(LoadObjectField(promise, JSPromise::kFlagsOffset));
TNode<Smi> const new_flags =
SmiOr(flags, SmiConstant(1 << JSPromise::kHasHandlerBit));
StoreObjectFieldNoWriteBarrier(promise, JSPromise::kFlagsOffset, new_flags);
}
Node* PromiseBuiltinsAssembler::IsPromiseStatus(
Node* actual, v8::Promise::PromiseState expected) {
return Word32Equal(actual, Int32Constant(expected));
}
Node* PromiseBuiltinsAssembler::PromiseStatus(Node* promise) {
STATIC_ASSERT(JSPromise::kStatusShift == 0);
TNode<Smi> const flags =
CAST(LoadObjectField(promise, JSPromise::kFlagsOffset));
return Word32And(SmiToInt32(flags), Int32Constant(JSPromise::kStatusMask));
}
void PromiseBuiltinsAssembler::PromiseSetStatus(
Node* promise, v8::Promise::PromiseState const status) {
CSA_ASSERT(this,
IsPromiseStatus(PromiseStatus(promise), v8::Promise::kPending));
CHECK_NE(status, v8::Promise::kPending);
TNode<Smi> mask = SmiConstant(status);
TNode<Smi> const flags =
CAST(LoadObjectField(promise, JSPromise::kFlagsOffset));
StoreObjectFieldNoWriteBarrier(promise, JSPromise::kFlagsOffset,
SmiOr(flags, mask));
}
void PromiseBuiltinsAssembler::PromiseSetHandledHint(Node* promise) {
TNode<Smi> const flags =
CAST(LoadObjectField(promise, JSPromise::kFlagsOffset));
TNode<Smi> const new_flags =
SmiOr(flags, SmiConstant(1 << JSPromise::kHandledHintBit));
StoreObjectFieldNoWriteBarrier(promise, JSPromise::kFlagsOffset, new_flags);
}
// ES #sec-performpromisethen
void PromiseBuiltinsAssembler::PerformPromiseThen(
Node* context, Node* promise, Node* on_fulfilled, Node* on_rejected,
Node* result_promise_or_capability) {
CSA_ASSERT(this, TaggedIsNotSmi(promise));
CSA_ASSERT(this, IsJSPromise(promise));
CSA_ASSERT(this,
Word32Or(IsCallable(on_fulfilled), IsUndefined(on_fulfilled)));
CSA_ASSERT(this, Word32Or(IsCallable(on_rejected), IsUndefined(on_rejected)));
CSA_ASSERT(this, TaggedIsNotSmi(result_promise_or_capability));
CSA_ASSERT(
this,
Word32Or(Word32Or(IsJSPromise(result_promise_or_capability),
IsPromiseCapability(result_promise_or_capability)),
IsUndefined(result_promise_or_capability)));
Label if_pending(this), if_notpending(this), done(this);
Node* const status = PromiseStatus(promise);
Branch(IsPromiseStatus(status, v8::Promise::kPending), &if_pending,
&if_notpending);
BIND(&if_pending);
{
// The {promise} is still in "Pending" state, so we just record a new
// PromiseReaction holding both the onFulfilled and onRejected callbacks.
// Once the {promise} is resolved we decide on the concrete handler to
// push onto the microtask queue.
Node* const promise_reactions =
LoadObjectField(promise, JSPromise::kReactionsOrResultOffset);
Node* const reaction =
AllocatePromiseReaction(promise_reactions, result_promise_or_capability,
on_fulfilled, on_rejected);
StoreObjectField(promise, JSPromise::kReactionsOrResultOffset, reaction);
Goto(&done);
}
BIND(&if_notpending);
{
VARIABLE(var_map, MachineRepresentation::kTagged);
VARIABLE(var_handler, MachineRepresentation::kTagged);
VARIABLE(var_handler_context, MachineRepresentation::kTagged,
UndefinedConstant());
Label if_fulfilled(this), if_rejected(this, Label::kDeferred),
enqueue(this);
Branch(IsPromiseStatus(status, v8::Promise::kFulfilled), &if_fulfilled,
&if_rejected);
BIND(&if_fulfilled);
{
var_map.Bind(LoadRoot(RootIndex::kPromiseFulfillReactionJobTaskMap));
var_handler.Bind(on_fulfilled);
Label use_fallback(this, Label::kDeferred), done(this);
ExtractHandlerContext(on_fulfilled, &var_handler_context);
Branch(IsUndefined(var_handler_context.value()), &use_fallback, &done);
BIND(&use_fallback);
var_handler_context.Bind(context);
ExtractHandlerContext(on_rejected, &var_handler_context);
Goto(&done);
BIND(&done);
Goto(&enqueue);
}
BIND(&if_rejected);
{
CSA_ASSERT(this, IsPromiseStatus(status, v8::Promise::kRejected));
var_map.Bind(LoadRoot(RootIndex::kPromiseRejectReactionJobTaskMap));
var_handler.Bind(on_rejected);
Label use_fallback(this, Label::kDeferred), done(this);
ExtractHandlerContext(on_rejected, &var_handler_context);
Branch(IsUndefined(var_handler_context.value()), &use_fallback, &done);
BIND(&use_fallback);
var_handler_context.Bind(context);
ExtractHandlerContext(on_fulfilled, &var_handler_context);
Goto(&done);
BIND(&done);
GotoIf(PromiseHasHandler(promise), &enqueue);
CallRuntime(Runtime::kPromiseRevokeReject, context, promise);
Goto(&enqueue);
}
BIND(&enqueue);
{
Node* argument =
LoadObjectField(promise, JSPromise::kReactionsOrResultOffset);
Node* microtask = AllocatePromiseReactionJobTask(
var_map.value(), var_handler_context.value(), argument,
var_handler.value(), result_promise_or_capability);
CallBuiltin(Builtins::kEnqueueMicrotask, var_handler_context.value(),
microtask);
Goto(&done);
}
}
BIND(&done);
PromiseSetHasHandler(promise);
}
// ES #sec-performpromisethen
TF_BUILTIN(PerformPromiseThen, PromiseBuiltinsAssembler) {
Node* const context = Parameter(Descriptor::kContext);
Node* const promise = Parameter(Descriptor::kPromise);
Node* const on_fulfilled = Parameter(Descriptor::kOnFulfilled);
Node* const on_rejected = Parameter(Descriptor::kOnRejected);
Node* const result_promise = Parameter(Descriptor::kResultPromise);
CSA_ASSERT(this, TaggedIsNotSmi(result_promise));
CSA_ASSERT(
this, Word32Or(IsJSPromise(result_promise), IsUndefined(result_promise)));
PerformPromiseThen(context, promise, on_fulfilled, on_rejected,
result_promise);
Return(result_promise);
}
Node* PromiseBuiltinsAssembler::AllocatePromiseReaction(
Node* next, Node* promise_or_capability, Node* fulfill_handler,
Node* reject_handler) {
Node* const reaction = Allocate(PromiseReaction::kSize);
StoreMapNoWriteBarrier(reaction, RootIndex::kPromiseReactionMap);
StoreObjectFieldNoWriteBarrier(reaction, PromiseReaction::kNextOffset, next);
StoreObjectFieldNoWriteBarrier(reaction,
PromiseReaction::kPromiseOrCapabilityOffset,
promise_or_capability);
StoreObjectFieldNoWriteBarrier(
reaction, PromiseReaction::kFulfillHandlerOffset, fulfill_handler);
StoreObjectFieldNoWriteBarrier(
reaction, PromiseReaction::kRejectHandlerOffset, reject_handler);
return reaction;
}
Node* PromiseBuiltinsAssembler::AllocatePromiseReactionJobTask(
Node* map, Node* context, Node* argument, Node* handler,
Node* promise_or_capability) {
Node* const microtask =
Allocate(PromiseReactionJobTask::kSizeOfAllPromiseReactionJobTasks);
StoreMapNoWriteBarrier(microtask, map);
StoreObjectFieldNoWriteBarrier(
microtask, PromiseReactionJobTask::kArgumentOffset, argument);
StoreObjectFieldNoWriteBarrier(
microtask, PromiseReactionJobTask::kContextOffset, context);
StoreObjectFieldNoWriteBarrier(
microtask, PromiseReactionJobTask::kHandlerOffset, handler);
StoreObjectFieldNoWriteBarrier(
microtask, PromiseReactionJobTask::kPromiseOrCapabilityOffset,
promise_or_capability);
return microtask;
}
Node* PromiseBuiltinsAssembler::AllocatePromiseReactionJobTask(
RootIndex map_root_index, Node* context, Node* argument, Node* handler,
Node* promise_or_capability) {
DCHECK(map_root_index == RootIndex::kPromiseFulfillReactionJobTaskMap ||
map_root_index == RootIndex::kPromiseRejectReactionJobTaskMap);
Node* const map = LoadRoot(map_root_index);
return AllocatePromiseReactionJobTask(map, context, argument, handler,
promise_or_capability);
}
Node* PromiseBuiltinsAssembler::AllocatePromiseResolveThenableJobTask(
Node* promise_to_resolve, Node* then, Node* thenable, Node* context) {
Node* const microtask = Allocate(PromiseResolveThenableJobTask::kSize);
StoreMapNoWriteBarrier(microtask,
RootIndex::kPromiseResolveThenableJobTaskMap);
StoreObjectFieldNoWriteBarrier(
microtask, PromiseResolveThenableJobTask::kContextOffset, context);
StoreObjectFieldNoWriteBarrier(
microtask, PromiseResolveThenableJobTask::kPromiseToResolveOffset,
promise_to_resolve);
StoreObjectFieldNoWriteBarrier(
microtask, PromiseResolveThenableJobTask::kThenOffset, then);
StoreObjectFieldNoWriteBarrier(
microtask, PromiseResolveThenableJobTask::kThenableOffset, thenable);
return microtask;
}
// ES #sec-triggerpromisereactions
Node* PromiseBuiltinsAssembler::TriggerPromiseReactions(
Node* context, Node* reactions, Node* argument,
PromiseReaction::Type type) {
// We need to reverse the {reactions} here, since we record them on the
// JSPromise in the reverse order.
{
VARIABLE(var_current, MachineRepresentation::kTagged, reactions);
VARIABLE(var_reversed, MachineRepresentation::kTagged,
SmiConstant(Smi::zero()));
// 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.
TNode<Map> promise_reaction_map =
CAST(LoadRoot(RootIndex::kPromiseReactionMap));
Label loop(this, {&var_current, &var_reversed}), done_loop(this);
Goto(&loop);
BIND(&loop);
{
Node* current = var_current.value();
GotoIf(TaggedIsSmi(current), &done_loop);
CSA_CHECK(this, WordEqual(LoadMap(CAST(current)), promise_reaction_map));
var_current.Bind(LoadObjectField(current, PromiseReaction::kNextOffset));
StoreObjectField(current, PromiseReaction::kNextOffset,
var_reversed.value());
var_reversed.Bind(current);
Goto(&loop);
}
BIND(&done_loop);
reactions = var_reversed.value();
}
// Morph the {reactions} into PromiseReactionJobTasks and push them
// onto the microtask queue.
{
VARIABLE(var_current, MachineRepresentation::kTagged, reactions);
Label loop(this, {&var_current}), done_loop(this);
Goto(&loop);
BIND(&loop);
{
Node* current = var_current.value();
GotoIf(TaggedIsSmi(current), &done_loop);
var_current.Bind(LoadObjectField(current, PromiseReaction::kNextOffset));
VARIABLE(var_context, MachineRepresentation::kTagged,
UndefinedConstant());
Node* primary_handler;
Node* secondary_handler;
if (type == PromiseReaction::kFulfill) {
primary_handler =
LoadObjectField(current, PromiseReaction::kFulfillHandlerOffset);
secondary_handler =
LoadObjectField(current, PromiseReaction::kRejectHandlerOffset);
} else {
primary_handler =
LoadObjectField(current, PromiseReaction::kRejectHandlerOffset);
secondary_handler =
LoadObjectField(current, PromiseReaction::kFulfillHandlerOffset);
}
{
Label use_fallback(this, Label::kDeferred), done(this);
ExtractHandlerContext(primary_handler, &var_context);
Branch(IsUndefined(var_context.value()), &use_fallback, &done);
BIND(&use_fallback);
var_context.Bind(context);
ExtractHandlerContext(secondary_handler, &var_context);
CSA_ASSERT(this, IsNotUndefined(var_context.value()));
Goto(&done);
BIND(&done);
}
// 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.
STATIC_ASSERT(
static_cast<int>(PromiseReaction::kSize) ==
static_cast<int>(
PromiseReactionJobTask::kSizeOfAllPromiseReactionJobTasks));
if (type == PromiseReaction::kFulfill) {
StoreMapNoWriteBarrier(current,
RootIndex::kPromiseFulfillReactionJobTaskMap);
StoreObjectField(current, PromiseReactionJobTask::kArgumentOffset,
argument);
StoreObjectField(current, PromiseReactionJobTask::kContextOffset,
var_context.value());
STATIC_ASSERT(
static_cast<int>(PromiseReaction::kFulfillHandlerOffset) ==
static_cast<int>(PromiseReactionJobTask::kHandlerOffset));
STATIC_ASSERT(
static_cast<int>(PromiseReaction::kPromiseOrCapabilityOffset) ==
static_cast<int>(
PromiseReactionJobTask::kPromiseOrCapabilityOffset));
} else {
StoreMapNoWriteBarrier(current,
RootIndex::kPromiseRejectReactionJobTaskMap);
StoreObjectField(current, PromiseReactionJobTask::kArgumentOffset,
argument);
StoreObjectField(current, PromiseReactionJobTask::kContextOffset,
var_context.value());
StoreObjectField(current, PromiseReactionJobTask::kHandlerOffset,
primary_handler);
STATIC_ASSERT(
static_cast<int>(PromiseReaction::kPromiseOrCapabilityOffset) ==
static_cast<int>(
PromiseReactionJobTask::kPromiseOrCapabilityOffset));
}
CallBuiltin(Builtins::kEnqueueMicrotask, var_context.value(), current);
Goto(&loop);
}
BIND(&done_loop);
}
return UndefinedConstant();
}
template <typename... TArgs>
Node* PromiseBuiltinsAssembler::InvokeThen(Node* native_context, Node* receiver,
TArgs... args) {
CSA_ASSERT(this, IsNativeContext(native_context));
VARIABLE(var_result, MachineRepresentation::kTagged);
Label if_fast(this), if_slow(this, Label::kDeferred), done(this, &var_result);
GotoIf(TaggedIsSmi(receiver), &if_slow);
Node* const receiver_map = LoadMap(receiver);
// We can skip the "then" lookup on {receiver} if it's [[Prototype]]
// is the (initial) Promise.prototype and the Promise#then protector
// is intact, as that guards the lookup path for the "then" property
// on JSPromise instances which have the (initial) %PromisePrototype%.
BranchIfPromiseThenLookupChainIntact(native_context, receiver_map, &if_fast,
&if_slow);
BIND(&if_fast);
{
Node* const then =
LoadContextElement(native_context, Context::PROMISE_THEN_INDEX);
Node* const result =
CallJS(CodeFactory::CallFunction(
isolate(), ConvertReceiverMode::kNotNullOrUndefined),
native_context, then, receiver, args...);
var_result.Bind(result);
Goto(&done);
}
BIND(&if_slow);
{
Node* const then = GetProperty(native_context, receiver,
isolate()->factory()->then_string());
Node* const result = CallJS(
CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined),
native_context, then, receiver, args...);
var_result.Bind(result);
Goto(&done);
}
BIND(&done);
return var_result.value();
}
Node* PromiseBuiltinsAssembler::CallResolve(Node* native_context,
Node* constructor, Node* resolve,
Node* value, Label* if_exception,
Variable* var_exception) {
CSA_ASSERT(this, IsNativeContext(native_context));
CSA_ASSERT(this, IsConstructor(constructor));
VARIABLE(var_result, MachineRepresentation::kTagged);
Label if_fast(this), if_slow(this, Label::kDeferred), done(this, &var_result);
// Undefined can never be a valid value for the resolve function,
// instead it is used as a special marker for the fast path.
Branch(IsUndefined(resolve), &if_fast, &if_slow);
BIND(&if_fast);
{
Node* const result = CallBuiltin(Builtins::kPromiseResolve, native_context,
constructor, value);
GotoIfException(result, if_exception, var_exception);
var_result.Bind(result);
Goto(&done);
}
BIND(&if_slow);
{
CSA_ASSERT(this, IsCallable(resolve));
Node* const result = CallJS(
CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined),
native_context, resolve, constructor, value);
GotoIfException(result, if_exception, var_exception);
var_result.Bind(result);
Goto(&done);
}
BIND(&done);
return var_result.value();
}
void PromiseBuiltinsAssembler::BranchIfPromiseResolveLookupChainIntact(
Node* native_context, Node* constructor, Label* if_fast, Label* if_slow) {
CSA_ASSERT(this, IsNativeContext(native_context));
GotoIfForceSlowPath(if_slow);
Node* const promise_fun =
LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
GotoIfNot(WordEqual(promise_fun, constructor), if_slow);
Branch(IsPromiseResolveProtectorCellInvalid(), if_slow, if_fast);
}
void PromiseBuiltinsAssembler::GotoIfNotPromiseResolveLookupChainIntact(
Node* native_context, Node* constructor, Label* if_slow) {
Label if_fast(this);
BranchIfPromiseResolveLookupChainIntact(native_context, constructor, &if_fast,
if_slow);
BIND(&if_fast);
}
void PromiseBuiltinsAssembler::BranchIfPromiseSpeciesLookupChainIntact(
Node* native_context, Node* promise_map, Label* if_fast, Label* if_slow) {
CSA_ASSERT(this, IsNativeContext(native_context));
CSA_ASSERT(this, IsJSPromiseMap(promise_map));
Node* const promise_prototype =
LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_INDEX);
GotoIfForceSlowPath(if_slow);
GotoIfNot(WordEqual(LoadMapPrototype(promise_map), promise_prototype),
if_slow);
Branch(IsPromiseSpeciesProtectorCellInvalid(), if_slow, if_fast);
}
void PromiseBuiltinsAssembler::BranchIfPromiseThenLookupChainIntact(
Node* native_context, Node* receiver_map, Label* if_fast, Label* if_slow) {
CSA_ASSERT(this, IsMap(receiver_map));
CSA_ASSERT(this, IsNativeContext(native_context));
GotoIfForceSlowPath(if_slow);
GotoIfNot(IsJSPromiseMap(receiver_map), if_slow);
Node* const promise_prototype =
LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_INDEX);
GotoIfNot(WordEqual(LoadMapPrototype(receiver_map), promise_prototype),
if_slow);
Branch(IsPromiseThenProtectorCellInvalid(), if_slow, if_fast);
}
void PromiseBuiltinsAssembler::BranchIfAccessCheckFailed(
Node* context, Node* native_context, Node* promise_constructor,
Node* executor, Label* if_noaccess) {
VARIABLE(var_executor, MachineRepresentation::kTagged);
var_executor.Bind(executor);
Label has_access(this), call_runtime(this, Label::kDeferred);
// If executor is a bound function, load the bound function until we've
// reached an actual function.
Label found_function(this), loop_over_bound_function(this, &var_executor);
Goto(&loop_over_bound_function);
BIND(&loop_over_bound_function);
{
Node* executor_type = LoadInstanceType(var_executor.value());
GotoIf(InstanceTypeEqual(executor_type, JS_FUNCTION_TYPE), &found_function);
GotoIfNot(InstanceTypeEqual(executor_type, JS_BOUND_FUNCTION_TYPE),
&call_runtime);
var_executor.Bind(LoadObjectField(
var_executor.value(), JSBoundFunction::kBoundTargetFunctionOffset));
Goto(&loop_over_bound_function);
}
// Load the context from the function and compare it to the Promise
// constructor's context. If they match, everything is fine, otherwise, bail
// out to the runtime.
BIND(&found_function);
{
Node* function_context =
LoadObjectField(var_executor.value(), JSFunction::kContextOffset);
Node* native_function_context = LoadNativeContext(function_context);
Branch(WordEqual(native_context, native_function_context), &has_access,
&call_runtime);
}
BIND(&call_runtime);
{
Branch(WordEqual(CallRuntime(Runtime::kAllowDynamicFunction, context,
promise_constructor),
TrueConstant()),
&has_access, if_noaccess);
}
BIND(&has_access);
}
void PromiseBuiltinsAssembler::SetForwardingHandlerIfTrue(
Node* context, Node* condition, const NodeGenerator& object) {
Label done(this);
GotoIfNot(condition, &done);
SetPropertyStrict(
CAST(context), CAST(object()),
HeapConstant(factory()->promise_forwarding_handler_symbol()),
TrueConstant());
Goto(&done);
BIND(&done);
}
void PromiseBuiltinsAssembler::SetPromiseHandledByIfTrue(
Node* context, Node* condition, Node* promise,
const NodeGenerator& handled_by) {
Label done(this);
GotoIfNot(condition, &done);
GotoIf(TaggedIsSmi(promise), &done);
GotoIfNot(HasInstanceType(promise, JS_PROMISE_TYPE), &done);
SetPropertyStrict(CAST(context), CAST(promise),
HeapConstant(factory()->promise_handled_by_symbol()),
CAST(handled_by()));
Goto(&done);
BIND(&done);
}
// ES #sec-promise-reject-functions
TF_BUILTIN(PromiseCapabilityDefaultReject, PromiseBuiltinsAssembler) {
Node* const reason = Parameter(Descriptor::kReason);
Node* const context = Parameter(Descriptor::kContext);
// 2. Let promise be F.[[Promise]].
Node* const promise =
LoadContextElement(context, PromiseBuiltins::kPromiseSlot);
// 3. Let alreadyResolved be F.[[AlreadyResolved]].
Label if_already_resolved(this, Label::kDeferred);
Node* const already_resolved =
LoadContextElement(context, PromiseBuiltins::kAlreadyResolvedSlot);
// 4. If alreadyResolved.[[Value]] is true, return undefined.
GotoIf(IsTrue(already_resolved), &if_already_resolved);
// 5. Set alreadyResolved.[[Value]] to true.
StoreContextElementNoWriteBarrier(
context, PromiseBuiltins::kAlreadyResolvedSlot, TrueConstant());
// 6. Return RejectPromise(promise, reason).
Node* const debug_event =
LoadContextElement(context, PromiseBuiltins::kDebugEventSlot);
Return(CallBuiltin(Builtins::kRejectPromise, context, promise, reason,
debug_event));
BIND(&if_already_resolved);
{
Return(CallRuntime(Runtime::kPromiseRejectAfterResolved, context, promise,
reason));
}
}
// ES #sec-promise-resolve-functions
TF_BUILTIN(PromiseCapabilityDefaultResolve, PromiseBuiltinsAssembler) {
Node* const resolution = Parameter(Descriptor::kResolution);
Node* const context = Parameter(Descriptor::kContext);
// 2. Let promise be F.[[Promise]].
Node* const promise =
LoadContextElement(context, PromiseBuiltins::kPromiseSlot);
// 3. Let alreadyResolved be F.[[AlreadyResolved]].
Label if_already_resolved(this, Label::kDeferred);
Node* const already_resolved =
LoadContextElement(context, PromiseBuiltins::kAlreadyResolvedSlot);
// 4. If alreadyResolved.[[Value]] is true, return undefined.
GotoIf(IsTrue(already_resolved), &if_already_resolved);
// 5. Set alreadyResolved.[[Value]] to true.
StoreContextElementNoWriteBarrier(
context, PromiseBuiltins::kAlreadyResolvedSlot, TrueConstant());
// The rest of the logic (and the catch prediction) is
// encapsulated in the dedicated ResolvePromise builtin.
Return(CallBuiltin(Builtins::kResolvePromise, context, promise, resolution));
BIND(&if_already_resolved);
{
Return(CallRuntime(Runtime::kPromiseResolveAfterResolved, context, promise,
resolution));
}
}
TF_BUILTIN(PromiseConstructorLazyDeoptContinuation, PromiseBuiltinsAssembler) {
Node* promise = Parameter(Descriptor::kPromise);
Node* reject = Parameter(Descriptor::kReject);
Node* exception = Parameter(Descriptor::kException);
Node* const context = Parameter(Descriptor::kContext);
Label finally(this);
GotoIf(IsTheHole(exception), &finally);
CallJS(CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined),
context, reject, UndefinedConstant(), exception);
Goto(&finally);
BIND(&finally);
Return(promise);
}
// ES6 #sec-promise-executor
TF_BUILTIN(PromiseConstructor, PromiseBuiltinsAssembler) {
Node* const executor = Parameter(Descriptor::kExecutor);
Node* const new_target = Parameter(Descriptor::kJSNewTarget);
Node* const context = Parameter(Descriptor::kContext);
Isolate* isolate = this->isolate();
Label if_targetisundefined(this, Label::kDeferred);
GotoIf(IsUndefined(new_target), &if_targetisundefined);
Label if_notcallable(this, Label::kDeferred);
GotoIf(TaggedIsSmi(executor), &if_notcallable);
Node* const executor_map = LoadMap(executor);
GotoIfNot(IsCallableMap(executor_map), &if_notcallable);
Node* const native_context = LoadNativeContext(context);
Node* const promise_fun =
LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
Node* const is_debug_active = IsDebugActive();
Label if_targetisnotmodified(this),
if_targetismodified(this, Label::kDeferred), run_executor(this),
debug_push(this), if_noaccess(this, Label::kDeferred);
BranchIfAccessCheckFailed(context, native_context, promise_fun, executor,
&if_noaccess);
Branch(WordEqual(promise_fun, new_target), &if_targetisnotmodified,
&if_targetismodified);
VARIABLE(var_result, MachineRepresentation::kTagged);
VARIABLE(var_reject_call, MachineRepresentation::kTagged);
VARIABLE(var_reason, MachineRepresentation::kTagged);
BIND(&if_targetisnotmodified);
{
Node* const instance = AllocateAndInitJSPromise(context);
var_result.Bind(instance);
Goto(&debug_push);
}
BIND(&if_targetismodified);
{
ConstructorBuiltinsAssembler constructor_assembler(this->state());
Node* const instance = constructor_assembler.EmitFastNewObject(
context, promise_fun, new_target);
PromiseInit(instance);
var_result.Bind(instance);
GotoIfNot(IsPromiseHookEnabledOrHasAsyncEventDelegate(), &debug_push);
CallRuntime(Runtime::kPromiseHookInit, context, instance,
UndefinedConstant());
Goto(&debug_push);
}
BIND(&debug_push);
{
GotoIfNot(is_debug_active, &run_executor);
CallRuntime(Runtime::kDebugPushPromise, context, var_result.value());
Goto(&run_executor);
}
BIND(&run_executor);
{
Label out(this), if_rejectpromise(this), debug_pop(this, Label::kDeferred);
Node *resolve, *reject;
std::tie(resolve, reject) = CreatePromiseResolvingFunctions(
var_result.value(), TrueConstant(), native_context);
Node* const maybe_exception = CallJS(
CodeFactory::Call(isolate, ConvertReceiverMode::kNullOrUndefined),
context, executor, UndefinedConstant(), resolve, reject);
GotoIfException(maybe_exception, &if_rejectpromise, &var_reason);
Branch(is_debug_active, &debug_pop, &out);
BIND(&if_rejectpromise);
{
CallJS(CodeFactory::Call(isolate, ConvertReceiverMode::kNullOrUndefined),
context, reject, UndefinedConstant(), var_reason.value());
Branch(is_debug_active, &debug_pop, &out);
}
BIND(&debug_pop);
{
CallRuntime(Runtime::kDebugPopPromise, context);
Goto(&out);
}
BIND(&out);
Return(var_result.value());
}
// 1. If NewTarget is undefined, throw a TypeError exception.
BIND(&if_targetisundefined);
ThrowTypeError(context, MessageTemplate::kNotAPromise, new_target);
// 2. If IsCallable(executor) is false, throw a TypeError exception.
BIND(&if_notcallable);
ThrowTypeError(context, MessageTemplate::kResolverNotAFunction, executor);
// Silently fail if the stack looks fishy.
BIND(&if_noaccess);
{
Node* const counter_id =
SmiConstant(v8::Isolate::kPromiseConstructorReturnedUndefined);
CallRuntime(Runtime::kIncrementUseCounter, context, counter_id);
Return(UndefinedConstant());
}
}
// V8 Extras: v8.createPromise(parent)
TF_BUILTIN(PromiseInternalConstructor, PromiseBuiltinsAssembler) {
Node* const parent = Parameter(Descriptor::kParent);
Node* const context = Parameter(Descriptor::kContext);
Return(AllocateAndInitJSPromise(context, parent));
}
// V8 Extras: v8.rejectPromise(promise, reason)
TF_BUILTIN(PromiseInternalReject, PromiseBuiltinsAssembler) {
Node* const promise = Parameter(Descriptor::kPromise);
Node* const reason = Parameter(Descriptor::kReason);
Node* const context = Parameter(Descriptor::kContext);
// Main V8 Extras invariant that {promise} is still "pending" at
// this point, aka that {promise} is not resolved multiple times.
Label if_promise_is_settled(this, Label::kDeferred);
GotoIfNot(IsPromiseStatus(PromiseStatus(promise), v8::Promise::kPending),
&if_promise_is_settled);
// We pass true to trigger the debugger's on exception handler.
Return(CallBuiltin(Builtins::kRejectPromise, context, promise, reason,
TrueConstant()));
BIND(&if_promise_is_settled);
Abort(AbortReason::kPromiseAlreadySettled);
}
// V8 Extras: v8.resolvePromise(promise, resolution)
TF_BUILTIN(PromiseInternalResolve, PromiseBuiltinsAssembler) {
Node* const promise = Parameter(Descriptor::kPromise);
Node* const resolution = Parameter(Descriptor::kResolution);
Node* const context = Parameter(Descriptor::kContext);
// Main V8 Extras invariant that {promise} is still "pending" at
// this point, aka that {promise} is not resolved multiple times.
Label if_promise_is_settled(this, Label::kDeferred);
GotoIfNot(IsPromiseStatus(PromiseStatus(promise), v8::Promise::kPending),
&if_promise_is_settled);
Return(CallBuiltin(Builtins::kResolvePromise, context, promise, resolution));
BIND(&if_promise_is_settled);
Abort(AbortReason::kPromiseAlreadySettled);
}
// ES#sec-promise.prototype.then
// Promise.prototype.then ( onFulfilled, onRejected )
TF_BUILTIN(PromisePrototypeThen, PromiseBuiltinsAssembler) {
// 1. Let promise be the this value.
Node* const promise = Parameter(Descriptor::kReceiver);
Node* const on_fulfilled = Parameter(Descriptor::kOnFulfilled);
Node* const on_rejected = Parameter(Descriptor::kOnRejected);
Node* const context = Parameter(Descriptor::kContext);
// 2. If IsPromise(promise) is false, throw a TypeError exception.
ThrowIfNotInstanceType(context, promise, JS_PROMISE_TYPE,
"Promise.prototype.then");
// 3. Let C be ? SpeciesConstructor(promise, %Promise%).
Label fast_promise_capability(this), slow_constructor(this, Label::kDeferred),
slow_promise_capability(this, Label::kDeferred);
Node* const native_context = LoadNativeContext(context);
Node* const promise_fun =
LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
Node* const promise_map = LoadMap(promise);
BranchIfPromiseSpeciesLookupChainIntact(
native_context, promise_map, &fast_promise_capability, &slow_constructor);
BIND(&slow_constructor);
Node* const constructor =
SpeciesConstructor(native_context, promise, promise_fun);
Branch(WordEqual(constructor, promise_fun), &fast_promise_capability,
&slow_promise_capability);
// 4. Let resultCapability be ? NewPromiseCapability(C).
Label perform_promise_then(this);
VARIABLE(var_result_promise, MachineRepresentation::kTagged);
VARIABLE(var_result_promise_or_capability, MachineRepresentation::kTagged);
BIND(&fast_promise_capability);
{
Node* const result_promise = AllocateAndInitJSPromise(context, promise);
var_result_promise_or_capability.Bind(result_promise);
var_result_promise.Bind(result_promise);
Goto(&perform_promise_then);
}
BIND(&slow_promise_capability);
{
Node* const debug_event = TrueConstant();
Node* const capability = CallBuiltin(Builtins::kNewPromiseCapability,
context, constructor, debug_event);
var_result_promise.Bind(
LoadObjectField(capability, PromiseCapability::kPromiseOffset));
var_result_promise_or_capability.Bind(capability);
Goto(&perform_promise_then);
}
// 5. Return PerformPromiseThen(promise, onFulfilled, onRejected,
// resultCapability).
BIND(&perform_promise_then);
{
// We do some work of the PerformPromiseThen operation here, in that
// we check the handlers and turn non-callable handlers into undefined.
// This is because this is the one and only callsite of PerformPromiseThen
// that has to do this.
// 3. If IsCallable(onFulfilled) is false, then
// a. Set onFulfilled to undefined.
VARIABLE(var_on_fulfilled, MachineRepresentation::kTagged, on_fulfilled);
Label if_fulfilled_done(this), if_fulfilled_notcallable(this);
GotoIf(TaggedIsSmi(on_fulfilled), &if_fulfilled_notcallable);
Branch(IsCallable(on_fulfilled), &if_fulfilled_done,
&if_fulfilled_notcallable);
BIND(&if_fulfilled_notcallable);
var_on_fulfilled.Bind(UndefinedConstant());
Goto(&if_fulfilled_done);
BIND(&if_fulfilled_done);
// 4. If IsCallable(onRejected) is false, then
// a. Set onRejected to undefined.
VARIABLE(var_on_rejected, MachineRepresentation::kTagged, on_rejected);
Label if_rejected_done(this), if_rejected_notcallable(this);
GotoIf(TaggedIsSmi(on_rejected), &if_rejected_notcallable);
Branch(IsCallable(on_rejected), &if_rejected_done,
&if_rejected_notcallable);
BIND(&if_rejected_notcallable);
var_on_rejected.Bind(UndefinedConstant());
Goto(&if_rejected_done);
BIND(&if_rejected_done);
PerformPromiseThen(context, promise, var_on_fulfilled.value(),
var_on_rejected.value(),
var_result_promise_or_capability.value());
Return(var_result_promise.value());
}
}
// ES#sec-promise.prototype.catch
// Promise.prototype.catch ( onRejected )
TF_BUILTIN(PromisePrototypeCatch, PromiseBuiltinsAssembler) {
// 1. Let promise be the this value.
Node* const receiver = Parameter(Descriptor::kReceiver);
Node* const on_fulfilled = UndefinedConstant();
Node* const on_rejected = Parameter(Descriptor::kOnRejected);
Node* const context = Parameter(Descriptor::kContext);
// 2. Return ? Invoke(promise, "then", « undefined, onRejected »).
Node* const native_context = LoadNativeContext(context);
Return(InvokeThen(native_context, receiver, on_fulfilled, on_rejected));
}
// ES #sec-promiseresolvethenablejob
TF_BUILTIN(PromiseResolveThenableJob, PromiseBuiltinsAssembler) {
Node* const native_context = Parameter(Descriptor::kContext);
Node* const promise_to_resolve = Parameter(Descriptor::kPromiseToResolve);
Node* const thenable = Parameter(Descriptor::kThenable);
Node* const then = Parameter(Descriptor::kThen);
CSA_ASSERT(this, TaggedIsNotSmi(thenable));
CSA_ASSERT(this, IsJSReceiver(thenable));
CSA_ASSERT(this, IsJSPromise(promise_to_resolve));
CSA_ASSERT(this, IsNativeContext(native_context));
// We can use a simple optimization here if we know that {then} is the initial
// Promise.prototype.then method, and {thenable} is a JSPromise whose
// @@species lookup chain is intact: We can connect {thenable} and
// {promise_to_resolve} directly in that case and avoid the allocation of a
// temporary JSPromise and the closures plus context.
//
// We take the generic (slow-)path if a PromiseHook is enabled or the debugger
// is active, to make sure we expose spec compliant behavior.
Label if_fast(this), if_slow(this, Label::kDeferred);
Node* const promise_then =
LoadContextElement(native_context, Context::PROMISE_THEN_INDEX);
GotoIfNot(WordEqual(then, promise_then), &if_slow);
Node* const thenable_map = LoadMap(thenable);
GotoIfNot(IsJSPromiseMap(thenable_map), &if_slow);
GotoIf(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
&if_slow);
BranchIfPromiseSpeciesLookupChainIntact(native_context, thenable_map,
&if_fast, &if_slow);
BIND(&if_fast);
{
// We know that the {thenable} is a JSPromise, which doesn't require
// any special treatment and that {then} corresponds to the initial
// Promise.prototype.then method. So instead of allocating a temporary
// JSPromise to connect the {thenable} with the {promise_to_resolve},
// we can directly schedule the {promise_to_resolve} with default
// handlers onto the {thenable} promise. This does not only save the
// JSPromise allocation, but also avoids the allocation of the two
// resolving closures and the shared context.
//
// What happens normally in this case is
//
// resolve, reject = CreateResolvingFunctions(promise_to_resolve)
// result_capability = NewPromiseCapability(%Promise%)
// PerformPromiseThen(thenable, resolve, reject, result_capability)
//
// which means that PerformPromiseThen will either schedule a new
// PromiseReaction with resolve and reject or a PromiseReactionJob
// with resolve or reject based on the state of {thenable}. And
// resolve or reject will just invoke the default [[Resolve]] or
// [[Reject]] functions on the {promise_to_resolve}.
//
// This is the same as just doing
//
// PerformPromiseThen(thenable, undefined, undefined, promise_to_resolve)
//
// which performs exactly the same (observable) steps.
TailCallBuiltin(Builtins::kPerformPromiseThen, native_context, thenable,
UndefinedConstant(), UndefinedConstant(),
promise_to_resolve);
}
BIND(&if_slow);
{
Node* resolve = nullptr;
Node* reject = nullptr;
std::tie(resolve, reject) = CreatePromiseResolvingFunctions(
promise_to_resolve, FalseConstant(), native_context);
Label if_exception(this, Label::kDeferred);
VARIABLE(var_exception, MachineRepresentation::kTagged, TheHoleConstant());
Node* const result = CallJS(
CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined),
native_context, then, thenable, resolve, reject);
GotoIfException(result, &if_exception, &var_exception);
Return(result);
BIND(&if_exception);
{
// We need to reject the {thenable}.
Node* const result = CallJS(
CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
native_context, reject, UndefinedConstant(), var_exception.value());
Return(result);
}
}
}
// ES #sec-promisereactionjob
void PromiseBuiltinsAssembler::PromiseReactionJob(Node* context, Node* argument,
Node* handler,
Node* promise_or_capability,
PromiseReaction::Type type) {
CSA_ASSERT(this, TaggedIsNotSmi(handler));
CSA_ASSERT(this, Word32Or(IsUndefined(handler), IsCallable(handler)));
CSA_ASSERT(this, TaggedIsNotSmi(promise_or_capability));
CSA_ASSERT(this,
Word32Or(Word32Or(IsJSPromise(promise_or_capability),
IsPromiseCapability(promise_or_capability)),
IsUndefined(promise_or_capability)));
VARIABLE(var_handler_result, MachineRepresentation::kTagged, argument);
Label if_handler_callable(this), if_fulfill(this), if_reject(this),
if_internal(this);
Branch(IsUndefined(handler),
type == PromiseReaction::kFulfill ? &if_fulfill : &if_reject,
&if_handler_callable);
BIND(&if_handler_callable);
{
Node* const result = CallJS(
CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
context, handler, UndefinedConstant(), argument);
GotoIfException(result, &if_reject, &var_handler_result);
var_handler_result.Bind(result);
Branch(IsUndefined(promise_or_capability), &if_internal, &if_fulfill);
}
BIND(&if_internal);
{
// There's no [[Capability]] for this promise reaction job, which
// means that this is a specification-internal operation (aka await)
// where the result does not matter (see the specification change in
// https://github.com/tc39/ecma262/pull/1146 for details).
Return(UndefinedConstant());
}
BIND(&if_fulfill);
{
Label if_promise(this), if_promise_capability(this, Label::kDeferred);
Node* const value = var_handler_result.value();
Branch(IsPromiseCapability(promise_or_capability), &if_promise_capability,
&if_promise);
BIND(&if_promise);
{
// For fast native promises we can skip the indirection
// via the promiseCapability.[[Resolve]] function and
// run the resolve logic directly from here.
TailCallBuiltin(Builtins::kResolvePromise, context, promise_or_capability,
value);
}
BIND(&if_promise_capability);
{
// In the general case we need to call the (user provided)
// promiseCapability.[[Resolve]] function.
Node* const resolve = LoadObjectField(promise_or_capability,
PromiseCapability::kResolveOffset);
Node* const result = CallJS(
CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
context, resolve, UndefinedConstant(), value);
GotoIfException(result, &if_reject, &var_handler_result);
Return(result);
}
}
BIND(&if_reject);
if (type == PromiseReaction::kReject) {
Label if_promise(this), if_promise_capability(this, Label::kDeferred);
Node* const reason = var_handler_result.value();
Branch(IsPromiseCapability(promise_or_capability), &if_promise_capability,
&if_promise);
BIND(&if_promise);
{
// For fast native promises we can skip the indirection
// via the promiseCapability.[[Reject]] function and
// run the resolve logic directly from here.
TailCallBuiltin(Builtins::kRejectPromise, context, promise_or_capability,
reason, FalseConstant());
}
BIND(&if_promise_capability);
{
// In the general case we need to call the (user provided)
// promiseCapability.[[Reject]] function.
Label if_exception(this, Label::kDeferred);
VARIABLE(var_exception, MachineRepresentation::kTagged,
TheHoleConstant());
Node* const reject = LoadObjectField(promise_or_capability,
PromiseCapability::kRejectOffset);
Node* const result = CallJS(
CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
context, reject, UndefinedConstant(), reason);
GotoIfException(result, &if_exception, &var_exception);
Return(result);
// Swallow the exception here.
BIND(&if_exception);
TailCallRuntime(Runtime::kReportMessage, context, var_exception.value());
}
} else {
// We have to call out to the dedicated PromiseRejectReactionJob builtin
// here, instead of just doing the work inline, as otherwise the catch
// predictions in the debugger will be wrong, which just walks the stack
// and checks for certain builtins.
TailCallBuiltin(Builtins::kPromiseRejectReactionJob, context,
var_handler_result.value(), UndefinedConstant(),
promise_or_capability);
}
}
// ES #sec-promisereactionjob
TF_BUILTIN(PromiseFulfillReactionJob, PromiseBuiltinsAssembler) {
Node* const context = Parameter(Descriptor::kContext);
Node* const value = Parameter(Descriptor::kValue);
Node* const handler = Parameter(Descriptor::kHandler);
Node* const promise_or_capability =
Parameter(Descriptor::kPromiseOrCapability);
PromiseReactionJob(context, value, handler, promise_or_capability,
PromiseReaction::kFulfill);
}
// ES #sec-promisereactionjob
TF_BUILTIN(PromiseRejectReactionJob, PromiseBuiltinsAssembler) {
Node* const context = Parameter(Descriptor::kContext);
Node* const reason = Parameter(Descriptor::kReason);
Node* const handler = Parameter(Descriptor::kHandler);
Node* const promise_or_capability =
Parameter(Descriptor::kPromiseOrCapability);
PromiseReactionJob(context, reason, handler, promise_or_capability,
PromiseReaction::kReject);
}
TF_BUILTIN(PromiseResolveTrampoline, PromiseBuiltinsAssembler) {
// 1. Let C be the this value.
Node* receiver = Parameter(Descriptor::kReceiver);
Node* value = Parameter(Descriptor::kValue);
Node* context = Parameter(Descriptor::kContext);
// 2. If Type(C) is not Object, throw a TypeError exception.
ThrowIfNotJSReceiver(context, receiver, MessageTemplate::kCalledOnNonObject,
"PromiseResolve");
// 3. Return ? PromiseResolve(C, x).
Return(CallBuiltin(Builtins::kPromiseResolve, context, receiver, value));
}
TF_BUILTIN(PromiseResolve, PromiseBuiltinsAssembler) {
Node* constructor = Parameter(Descriptor::kConstructor);
Node* value = Parameter(Descriptor::kValue);
Node* context = Parameter(Descriptor::kContext);
CSA_ASSERT(this, IsJSReceiver(constructor));
Node* const native_context = LoadNativeContext(context);
Node* const promise_fun =
LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
Label if_slow_constructor(this, Label::kDeferred), if_need_to_allocate(this);
// Check if {value} is a JSPromise.
GotoIf(TaggedIsSmi(value), &if_need_to_allocate);
Node* const value_map = LoadMap(value);
GotoIfNot(IsJSPromiseMap(value_map), &if_need_to_allocate);
// We can skip the "constructor" lookup on {value} if it's [[Prototype]]
// is the (initial) Promise.prototype and the @@species protector is
// intact, as that guards the lookup path for "constructor" on
// JSPromise instances which have the (initial) Promise.prototype.
Node* const promise_prototype =
LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_INDEX);
GotoIfNot(WordEqual(LoadMapPrototype(value_map), promise_prototype),
&if_slow_constructor);
GotoIf(IsPromiseSpeciesProtectorCellInvalid(), &if_slow_constructor);
// If the {constructor} is the Promise function, we just immediately
// return the {value} here and don't bother wrapping it into a
// native Promise.
GotoIfNot(WordEqual(promise_fun, constructor), &if_slow_constructor);
Return(value);
// At this point, value or/and constructor are not native promises, but
// they could be of the same subclass.
BIND(&if_slow_constructor);
{
Node* const value_constructor =
GetProperty(context, value, isolate()->factory()->constructor_string());
GotoIfNot(WordEqual(value_constructor, constructor), &if_need_to_allocate);
Return(value);
}
BIND(&if_need_to_allocate);
{
Label if_nativepromise(this), if_notnativepromise(this, Label::kDeferred);
Branch(WordEqual(promise_fun, constructor), &if_nativepromise,
&if_notnativepromise);
// This adds a fast path for native promises that don't need to
// create NewPromiseCapability.
BIND(&if_nativepromise);
{
Node* const result = AllocateAndInitJSPromise(context);
CallBuiltin(Builtins::kResolvePromise, context, result, value);
Return(result);
}
BIND(&if_notnativepromise);
{
Node* const debug_event = TrueConstant();
Node* const capability = CallBuiltin(Builtins::kNewPromiseCapability,
context, constructor, debug_event);
Node* const resolve =
LoadObjectField(capability, PromiseCapability::kResolveOffset);
CallJS(
CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
context, resolve, UndefinedConstant(), value);
Node* const result =
LoadObjectField(capability, PromiseCapability::kPromiseOffset);
Return(result);
}
}
}
// ES6 #sec-getcapabilitiesexecutor-functions
TF_BUILTIN(PromiseGetCapabilitiesExecutor, PromiseBuiltinsAssembler) {
Node* const resolve = Parameter(Descriptor::kResolve);
Node* const reject = Parameter(Descriptor::kReject);
Node* const context = Parameter(Descriptor::kContext);
Node* const capability =
LoadContextElement(context, PromiseBuiltins::kCapabilitySlot);
Label if_alreadyinvoked(this, Label::kDeferred);
GotoIfNot(IsUndefined(
LoadObjectField(capability, PromiseCapability::kResolveOffset)),
&if_alreadyinvoked);
GotoIfNot(IsUndefined(
LoadObjectField(capability, PromiseCapability::kRejectOffset)),
&if_alreadyinvoked);
StoreObjectField(capability, PromiseCapability::kResolveOffset, resolve);
StoreObjectField(capability, PromiseCapability::kRejectOffset, reject);
Return(UndefinedConstant());
BIND(&if_alreadyinvoked);
ThrowTypeError(context, MessageTemplate::kPromiseExecutorAlreadyInvoked);
}
TF_BUILTIN(PromiseReject, PromiseBuiltinsAssembler) {
// 1. Let C be the this value.
Node* const receiver = Parameter(Descriptor::kReceiver);
Node* const reason = Parameter(Descriptor::kReason);
Node* const context = Parameter(Descriptor::kContext);
// 2. If Type(C) is not Object, throw a TypeError exception.
ThrowIfNotJSReceiver(context, receiver, MessageTemplate::kCalledOnNonObject,
"PromiseReject");
Label if_nativepromise(this), if_custompromise(this, Label::kDeferred);
Node* const native_context = LoadNativeContext(context);
Node* const promise_fun =
LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
Branch(WordEqual(promise_fun, receiver), &if_nativepromise,
&if_custompromise);
BIND(&if_nativepromise);
{
Node* const promise =
AllocateAndSetJSPromise(context, v8::Promise::kRejected, reason);
CallRuntime(Runtime::kPromiseRejectEventFromStack, context, promise,
reason);
Return(promise);
}
BIND(&if_custompromise);
{
// 3. Let promiseCapability be ? NewPromiseCapability(C).
Node* const debug_event = TrueConstant();
Node* const capability = CallBuiltin(Builtins::kNewPromiseCapability,
context, receiver, debug_event);
// 4. Perform ? Call(promiseCapability.[[Reject]], undefined, « r »).
Node* const reject =
LoadObjectField(capability, PromiseCapability::kRejectOffset);
CallJS(CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
context, reject, UndefinedConstant(), reason);
// 5. Return promiseCapability.[[Promise]].
Node* const promise =
LoadObjectField(capability, PromiseCapability::kPromiseOffset);
Return(promise);
}
}
std::pair<Node*, Node*> PromiseBuiltinsAssembler::CreatePromiseFinallyFunctions(
Node* on_finally, Node* constructor, Node* native_context) {
Node* const promise_context = CreatePromiseContext(
native_context, PromiseBuiltins::kPromiseFinallyContextLength);
StoreContextElementNoWriteBarrier(
promise_context, PromiseBuiltins::kOnFinallySlot, on_finally);
StoreContextElementNoWriteBarrier(
promise_context, PromiseBuiltins::kConstructorSlot, constructor);
Node* const map = LoadContextElement(
native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
Node* const then_finally_info = LoadContextElement(
native_context, Context::PROMISE_THEN_FINALLY_SHARED_FUN);
Node* const then_finally = AllocateFunctionWithMapAndContext(
map, then_finally_info, promise_context);
Node* const catch_finally_info = LoadContextElement(
native_context, Context::PROMISE_CATCH_FINALLY_SHARED_FUN);
Node* const catch_finally = AllocateFunctionWithMapAndContext(
map, catch_finally_info, promise_context);
return std::make_pair(then_finally, catch_finally);
}
TF_BUILTIN(PromiseValueThunkFinally, PromiseBuiltinsAssembler) {
Node* const context = Parameter(Descriptor::kContext);
Node* const value = LoadContextElement(context, PromiseBuiltins::kValueSlot);
Return(value);
}
Node* PromiseBuiltinsAssembler::CreateValueThunkFunction(Node* value,
Node* native_context) {
Node* const value_thunk_context = CreatePromiseContext(
native_context, PromiseBuiltins::kPromiseValueThunkOrReasonContextLength);
StoreContextElementNoWriteBarrier(value_thunk_context,
PromiseBuiltins::kValueSlot, value);
Node* const map = LoadContextElement(
native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
Node* const value_thunk_info = LoadContextElement(
native_context, Context::PROMISE_VALUE_THUNK_FINALLY_SHARED_FUN);
Node* const value_thunk = AllocateFunctionWithMapAndContext(
map, value_thunk_info, value_thunk_context);
return value_thunk;
}
TF_BUILTIN(PromiseThenFinally, PromiseBuiltinsAssembler) {
CSA_ASSERT_JS_ARGC_EQ(this, 1);
Node* const value = Parameter(Descriptor::kValue);
Node* const context = Parameter(Descriptor::kContext);
// 1. Let onFinally be F.[[OnFinally]].
Node* const on_finally =
LoadContextElement(context, PromiseBuiltins::kOnFinallySlot);
// 2. Assert: IsCallable(onFinally) is true.
CSA_ASSERT(this, IsCallable(on_finally));
// 3. Let result be ? Call(onFinally).
Node* const result = CallJS(
CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
context, on_finally, UndefinedConstant());
// 4. Let C be F.[[Constructor]].
Node* const constructor =
LoadContextElement(context, PromiseBuiltins::kConstructorSlot);
// 5. Assert: IsConstructor(C) is true.
CSA_ASSERT(this, IsConstructor(constructor));
// 6. Let promise be ? PromiseResolve(C, result).
Node* const promise =
CallBuiltin(Builtins::kPromiseResolve, context, constructor, result);
// 7. Let valueThunk be equivalent to a function that returns value.
Node* const native_context = LoadNativeContext(context);
Node* const value_thunk = CreateValueThunkFunction(value, native_context);
// 8. Return ? Invoke(promise, "then", « valueThunk »).
Return(InvokeThen(native_context, promise, value_thunk));
}
TF_BUILTIN(PromiseThrowerFinally, PromiseBuiltinsAssembler) {
Node* const context = Parameter(Descriptor::kContext);
Node* const reason = LoadContextElement(context, PromiseBuiltins::kValueSlot);
CallRuntime(Runtime::kThrow, context, reason);
Unreachable();
}
Node* PromiseBuiltinsAssembler::CreateThrowerFunction(Node* reason,
Node* native_context) {
Node* const thrower_context = CreatePromiseContext(
native_context, PromiseBuiltins::kPromiseValueThunkOrReasonContextLength);
StoreContextElementNoWriteBarrier(thrower_context,
PromiseBuiltins::kValueSlot, reason);
Node* const map = LoadContextElement(
native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
Node* const thrower_info = LoadContextElement(
native_context, Context::PROMISE_THROWER_FINALLY_SHARED_FUN);
Node* const thrower =
AllocateFunctionWithMapAndContext(map, thrower_info, thrower_context);
return thrower;
}
TF_BUILTIN(PromiseCatchFinally, PromiseBuiltinsAssembler) {
CSA_ASSERT_JS_ARGC_EQ(this, 1);
Node* const reason = Parameter(Descriptor::kReason);
Node* const context = Parameter(Descriptor::kContext);
// 1. Let onFinally be F.[[OnFinally]].
Node* const on_finally =
LoadContextElement(context, PromiseBuiltins::kOnFinallySlot);
// 2. Assert: IsCallable(onFinally) is true.
CSA_ASSERT(this, IsCallable(on_finally));
// 3. Let result be ? Call(onFinally).
Node* result = CallJS(
CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
context, on_finally, UndefinedConstant());
// 4. Let C be F.[[Constructor]].
Node* const constructor =
LoadContextElement(context, PromiseBuiltins::kConstructorSlot);
// 5. Assert: IsConstructor(C) is true.
CSA_ASSERT(this, IsConstructor(constructor));
// 6. Let promise be ? PromiseResolve(C, result).
Node* const promise =
CallBuiltin(Builtins::kPromiseResolve, context, constructor, result);
// 7. Let thrower be equivalent to a function that throws reason.
Node* const native_context = LoadNativeContext(context);
Node* const thrower = CreateThrowerFunction(reason, native_context);
// 8. Return ? Invoke(promise, "then", « thrower »).
Return(InvokeThen(native_context, promise, thrower));
}
TF_BUILTIN(PromisePrototypeFinally, PromiseBuiltinsAssembler) {
CSA_ASSERT_JS_ARGC_EQ(this, 1);
// 1. Let promise be the this value.
Node* const receiver = Parameter(Descriptor::kReceiver);
Node* const on_finally = Parameter(Descriptor::kOnFinally);
Node* const context = Parameter(Descriptor::kContext);
// 2. If Type(promise) is not Object, throw a TypeError exception.
ThrowIfNotJSReceiver(context, receiver, MessageTemplate::kCalledOnNonObject,
"Promise.prototype.finally");
// 3. Let C be ? SpeciesConstructor(promise, %Promise%).
Node* const native_context = LoadNativeContext(context);
Node* const promise_fun =
LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
VARIABLE(var_constructor, MachineRepresentation::kTagged, promise_fun);
Label slow_constructor(this, Label::kDeferred), done_constructor(this);
Node* const receiver_map = LoadMap(receiver);
GotoIfNot(IsJSPromiseMap(receiver_map), &slow_constructor);
BranchIfPromiseSpeciesLookupChainIntact(native_context, receiver_map,
&done_constructor, &slow_constructor);
BIND(&slow_constructor);
{
Node* const constructor =
SpeciesConstructor(context, receiver, promise_fun);
var_constructor.Bind(constructor);
Goto(&done_constructor);
}
BIND(&done_constructor);
Node* const constructor = var_constructor.value();
// 4. Assert: IsConstructor(C) is true.
CSA_ASSERT(this, IsConstructor(constructor));
VARIABLE(var_then_finally, MachineRepresentation::kTagged);
VARIABLE(var_catch_finally, MachineRepresentation::kTagged);
Label if_notcallable(this, Label::kDeferred), perform_finally(this);
GotoIf(TaggedIsSmi(on_finally), &if_notcallable);
GotoIfNot(IsCallable(on_finally), &if_notcallable);
// 6. Else,
// a. Let thenFinally be a new built-in function object as defined
// in ThenFinally Function.
// b. Let catchFinally be a new built-in function object as
// defined in CatchFinally Function.
// c. Set thenFinally and catchFinally's [[Constructor]] internal
// slots to C.
// d. Set thenFinally and catchFinally's [[OnFinally]] internal
// slots to onFinally.
Node* then_finally = nullptr;
Node* catch_finally = nullptr;
std::tie(then_finally, catch_finally) =
CreatePromiseFinallyFunctions(on_finally, constructor, native_context);
var_then_finally.Bind(then_finally);
var_catch_finally.Bind(catch_finally);
Goto(&perform_finally);
// 5. If IsCallable(onFinally) is not true,
// a. Let thenFinally be onFinally.
// b. Let catchFinally be onFinally.
BIND(&if_notcallable);
{
var_then_finally.Bind(on_finally);
var_catch_finally.Bind(on_finally);
Goto(&perform_finally);
}
// 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »).
BIND(&perform_finally);
Return(InvokeThen(native_context, receiver, var_then_finally.value(),
var_catch_finally.value()));
}
// ES #sec-fulfillpromise
TF_BUILTIN(FulfillPromise, PromiseBuiltinsAssembler) {
Node* const promise = Parameter(Descriptor::kPromise);
Node* const value = Parameter(Descriptor::kValue);
Node* const context = Parameter(Descriptor::kContext);
CSA_ASSERT(this, TaggedIsNotSmi(promise));
CSA_ASSERT(this, IsJSPromise(promise));
// 2. Let reactions be promise.[[PromiseFulfillReactions]].
Node* const reactions =
LoadObjectField(promise, JSPromise::kReactionsOrResultOffset);
// 3. Set promise.[[PromiseResult]] to value.
// 4. Set promise.[[PromiseFulfillReactions]] to undefined.
// 5. Set promise.[[PromiseRejectReactions]] to undefined.
StoreObjectField(promise, JSPromise::kReactionsOrResultOffset, value);
// 6. Set promise.[[PromiseState]] to "fulfilled".
PromiseSetStatus(promise, Promise::kFulfilled);
// 7. Return TriggerPromiseReactions(reactions, value).
Return(TriggerPromiseReactions(context, reactions, value,
PromiseReaction::kFulfill));
}
// ES #sec-rejectpromise
TF_BUILTIN(RejectPromise, PromiseBuiltinsAssembler) {
Node* const promise = Parameter(Descriptor::kPromise);
Node* const reason = Parameter(Descriptor::kReason);
Node* const debug_event = Parameter(Descriptor::kDebugEvent);
Node* const context = Parameter(Descriptor::kContext);
CSA_ASSERT(this, TaggedIsNotSmi(promise));
CSA_ASSERT(this, IsJSPromise(promise));
CSA_ASSERT(this, IsBoolean(debug_event));
Label if_runtime(this, Label::kDeferred);
// 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.
GotoIf(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
&if_runtime);
// 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.
GotoIfNot(PromiseHasHandler(promise), &if_runtime);
// 2. Let reactions be promise.[[PromiseRejectReactions]].
Node* reactions =
LoadObjectField(promise, JSPromise::kReactionsOrResultOffset);
// 3. Set promise.[[PromiseResult]] to reason.
// 4. Set promise.[[PromiseFulfillReactions]] to undefined.
// 5. Set promise.[[PromiseRejectReactions]] to undefined.
StoreObjectField(promise, JSPromise::kReactionsOrResultOffset, reason);
// 6. Set promise.[[PromiseState]] to "rejected".
PromiseSetStatus(promise, Promise::kRejected);
// 7. Return TriggerPromiseReactions(reactions, reason).
Return(TriggerPromiseReactions(context, reactions, reason,
PromiseReaction::kReject));
BIND(&if_runtime);
TailCallRuntime(Runtime::kRejectPromise, context, promise, reason,
debug_event);
}
// ES #sec-promise-resolve-functions
TF_BUILTIN(ResolvePromise, PromiseBuiltinsAssembler) {
Node* const promise = Parameter(Descriptor::kPromise);
Node* const resolution = Parameter(Descriptor::kResolution);
Node* const context = Parameter(Descriptor::kContext);
CSA_ASSERT(this, TaggedIsNotSmi(promise));
CSA_ASSERT(this, IsJSPromise(promise));
Label do_enqueue(this), if_fulfill(this), if_reject(this, Label::kDeferred),
if_runtime(this, Label::kDeferred);
VARIABLE(var_reason, MachineRepresentation::kTagged);
VARIABLE(var_then, MachineRepresentation::kTagged);
// 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.
GotoIf(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
&if_runtime);
// 6. If SameValue(resolution, promise) is true, then
// We can use pointer comparison here, since the {promise} is guaranteed
// to be a JSPromise inside this function and thus is reference comparable.
GotoIf(WordEqual(promise, resolution), &if_runtime);
// 7. If Type(resolution) is not Object, then
GotoIf(TaggedIsSmi(resolution), &if_fulfill);
Node* const resolution_map = LoadMap(resolution);
GotoIfNot(IsJSReceiverMap(resolution_map), &if_fulfill);
// We can skip the "then" lookup on {resolution} if its [[Prototype]]
// is the (initial) Promise.prototype and the Promise#then protector
// is intact, as that guards the lookup path for the "then" property
// on JSPromise instances which have the (initial) %PromisePrototype%.
Label if_fast(this), if_receiver(this), if_slow(this, Label::kDeferred);
Node* const native_context = LoadNativeContext(context);
GotoIfForceSlowPath(&if_slow);
GotoIf(IsPromiseThenProtectorCellInvalid(), &if_slow);
GotoIfNot(IsJSPromiseMap(resolution_map), &if_receiver);
Node* const promise_prototype =
LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_INDEX);
Branch(WordEqual(LoadMapPrototype(resolution_map), promise_prototype),
&if_fast, &if_slow);
BIND(&if_fast);
{
// The {resolution} is a native Promise in this case.
Node* const then =
LoadContextElement(native_context, Context::PROMISE_THEN_INDEX);
var_then.Bind(then);
Goto(&do_enqueue);
}
BIND(&if_receiver);
{
// We can skip the lookup of "then" if the {resolution} is a (newly
// created) IterResultObject, as the Promise#then() protector also
// ensures that the intrinsic %ObjectPrototype% doesn't contain any
// "then" property. This helps to avoid negative lookups on iterator
// results from async generators.
CSA_ASSERT(this, IsJSReceiverMap(resolution_map));
CSA_ASSERT(this, Word32BinaryNot(IsPromiseThenProtectorCellInvalid()));
Node* const iterator_result_map =
LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX);
Branch(WordEqual(resolution_map, iterator_result_map), &if_fulfill,
&if_slow);
}
BIND(&if_slow);
{
// 8. Let then be Get(resolution, "then").
Node* const then =
GetProperty(context, resolution, isolate()->factory()->then_string());
// 9. If then is an abrupt completion, then
GotoIfException(then, &if_reject, &var_reason);
// 11. If IsCallable(thenAction) is false, then
GotoIf(TaggedIsSmi(then), &if_fulfill);
Node* const then_map = LoadMap(then);
GotoIfNot(IsCallableMap(then_map), &if_fulfill);
var_then.Bind(then);
Goto(&do_enqueue);
}
BIND(&do_enqueue);
{
// 12. Perform EnqueueJob("PromiseJobs", PromiseResolveThenableJob,
// «promise, resolution, thenAction»).
Node* const task = AllocatePromiseResolveThenableJobTask(
promise, var_then.value(), resolution, native_context);
TailCallBuiltin(Builtins::kEnqueueMicrotask, native_context, task);
}
BIND(&if_fulfill);
{
// 7.b Return FulfillPromise(promise, resolution).
TailCallBuiltin(Builtins::kFulfillPromise, context, promise, resolution);
}
BIND(&if_runtime);
Return(CallRuntime(Runtime::kResolvePromise, context, promise, resolution));
BIND(&if_reject);
{
// 9.a Return RejectPromise(promise, then.[[Value]]).
TailCallBuiltin(Builtins::kRejectPromise, context, promise,
var_reason.value(), FalseConstant());
}
}
Node* PromiseBuiltinsAssembler::PerformPromiseAll(
Node* context, Node* constructor, Node* capability,
const IteratorRecord& iterator,
const PromiseAllResolvingElementFunction& create_resolve_element_function,
const PromiseAllResolvingElementFunction& create_reject_element_function,
Label* if_exception, Variable* var_exception) {
IteratorBuiltinsAssembler iter_assembler(state());
TNode<NativeContext> native_context = Cast(LoadNativeContext(context));
// For catch prediction, don't treat the .then calls as handling it;
// instead, recurse outwards.
SetForwardingHandlerIfTrue(
native_context, IsDebugActive(),
LoadObjectField(capability, PromiseCapability::kRejectOffset));
TNode<Context> resolve_element_context =
Cast(CreatePromiseAllResolveElementContext(capability, native_context));
TVARIABLE(Smi, var_index, SmiConstant(1));
Label loop(this, &var_index), done_loop(this),
too_many_elements(this, Label::kDeferred),
close_iterator(this, Label::kDeferred), if_slow(this, Label::kDeferred);
// We can skip the "resolve" lookup on {constructor} if it's the
// Promise constructor and the Promise.resolve protector is intact,
// as that guards the lookup path for the "resolve" property on the
// Promise constructor.
TVARIABLE(Object, var_promise_resolve_function, UndefinedConstant());
GotoIfNotPromiseResolveLookupChainIntact(native_context, constructor,
&if_slow);
Goto(&loop);
BIND(&if_slow);
{
// 5. Let _promiseResolve_ be ? Get(_constructor_, `"resolve"`).
TNode<Object> resolve =
GetProperty(native_context, constructor, factory()->resolve_string());
GotoIfException(resolve, if_exception, var_exception);
// 6. If IsCallable(_promiseResolve_) is *false*, throw a *TypeError*
// exception.
ThrowIfNotCallable(CAST(context), resolve, "resolve");
var_promise_resolve_function = resolve;
Goto(&loop);
}
BIND(&loop);
{
// Let next be IteratorStep(iteratorRecord.[[Iterator]]).
// If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
// ReturnIfAbrupt(next).
Node* const fast_iterator_result_map =
LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX);
Node* const next = iter_assembler.IteratorStep(
native_context, iterator, &done_loop, fast_iterator_result_map,
if_exception, var_exception);
// Let nextValue be IteratorValue(next).
// If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to
// true.
// ReturnIfAbrupt(nextValue).
Node* const next_value = iter_assembler.IteratorValue(
native_context, next, fast_iterator_result_map, if_exception,
var_exception);
// Check if we reached the limit.
TNode<Smi> const index = var_index.value();
GotoIf(SmiEqual(index, SmiConstant(PropertyArray::HashField::kMax)),
&too_many_elements);
// Set index to index + 1.
var_index = SmiAdd(index, SmiConstant(1));
// Set remainingElementsCount.[[Value]] to
// remainingElementsCount.[[Value]] + 1.
TNode<Smi> const remaining_elements_count = CAST(LoadContextElement(
resolve_element_context,
PromiseBuiltins::kPromiseAllResolveElementRemainingSlot));
StoreContextElementNoWriteBarrier(
resolve_element_context,
PromiseBuiltins::kPromiseAllResolveElementRemainingSlot,
SmiAdd(remaining_elements_count, SmiConstant(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.
Node* const resolve_element_fun = create_resolve_element_function(
resolve_element_context, index, native_context, Cast(capability));
Node* const reject_element_fun = create_reject_element_function(
resolve_element_context, index, native_context, Cast(capability));
// We can skip the "resolve" lookup on the {constructor} as well as the
// "then" lookup on the result of the "resolve" call, and immediately
// chain 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.
Label if_fast(this), if_slow(this);
GotoIfNot(IsUndefined(var_promise_resolve_function.value()), &if_slow);
GotoIf(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
&if_slow);
GotoIf(IsPromiseSpeciesProtectorCellInvalid(), &if_slow);
GotoIf(TaggedIsSmi(next_value), &if_slow);
Node* const next_value_map = LoadMap(next_value);
BranchIfPromiseThenLookupChainIntact(native_context, next_value_map,
&if_fast, &if_slow);
BIND(&if_fast);
{
// Register the PromiseReaction immediately on the {next_value}, not
// passing any chained promise since neither async_hooks nor DevTools
// are enabled, so there's no use of the resulting promise.
PerformPromiseThen(native_context, next_value, resolve_element_fun,
reject_element_fun, UndefinedConstant());
Goto(&loop);
}
BIND(&if_slow);
{
// Let nextPromise be ? Call(constructor, _promiseResolve_, « nextValue
// »).
Node* const next_promise = CallResolve(
native_context, constructor, var_promise_resolve_function.value(),
next_value, &close_iterator, var_exception);
// Perform ? Invoke(nextPromise, "then", « resolveElement,
// resultCapability.[[Reject]] »).
Node* const then =
GetProperty(native_context, next_promise, factory()->then_string());
GotoIfException(then, &close_iterator, var_exception);
Node* const then_call =
CallJS(CodeFactory::Call(isolate(),
ConvertReceiverMode::kNotNullOrUndefined),
native_context, then, next_promise, resolve_element_fun,
reject_element_fun);
GotoIfException(then_call, &close_iterator, var_exception);
// For catch prediction, mark that rejections here are semantically
// handled by the combined Promise.
SetPromiseHandledByIfTrue(
native_context, IsDebugActive(), then_call, [=]() {
// Load promiseCapability.[[Promise]]
return LoadObjectField(capability,
PromiseCapability::kPromiseOffset);
});
Goto(&loop);
}
}
BIND(&too_many_elements);
{
// If there are too many elements (currently more than 2**21-1), raise a
// RangeError here (which is caught directly 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 anyways.
Node* const result =
CallRuntime(Runtime::kThrowRangeError, native_context,
SmiConstant(MessageTemplate::kTooManyElementsInPromiseAll));
GotoIfException(result, &close_iterator, var_exception);
Unreachable();
}
BIND(&close_iterator);
{
// Exception must be bound to a JS value.
CSA_ASSERT(this, IsNotTheHole(var_exception->value()));
iter_assembler.IteratorCloseOnException(native_context, iterator,
if_exception, var_exception);
}
BIND(&done_loop);
{
Label resolve_promise(this, Label::kDeferred), return_promise(this);
// Set iteratorRecord.[[Done]] to true.
// Set remainingElementsCount.[[Value]] to
// remainingElementsCount.[[Value]] - 1.
TNode<Smi> remaining_elements_count = CAST(LoadContextElement(
resolve_element_context,
PromiseBuiltins::kPromiseAllResolveElementRemainingSlot));
remaining_elements_count = SmiSub(remaining_elements_count, SmiConstant(1));
StoreContextElementNoWriteBarrier(
resolve_element_context,
PromiseBuiltins::kPromiseAllResolveElementRemainingSlot,
remaining_elements_count);
GotoIf(SmiEqual(remaining_elements_count, SmiConstant(0)),
&resolve_promise);
// Pre-allocate the backing store for the {values_array} to the desired
// capacity here. We may already have elements here in case of some
// fancy Thenable that calls the resolve callback immediately, so we need
// to handle that correctly here.
Node* const values_array = LoadContextElement(
resolve_element_context,
PromiseBuiltins::kPromiseAllResolveElementValuesArraySlot);
Node* const old_elements = LoadElements(values_array);
TNode<Smi> const old_capacity = LoadFixedArrayBaseLength(old_elements);
TNode<Smi> const new_capacity = var_index.value();
GotoIf(SmiGreaterThanOrEqual(old_capacity, new_capacity), &return_promise);
Node* const new_elements =
AllocateFixedArray(PACKED_ELEMENTS, new_capacity, SMI_PARAMETERS,
AllocationFlag::kAllowLargeObjectAllocation);
CopyFixedArrayElements(PACKED_ELEMENTS, old_elements, PACKED_ELEMENTS,
new_elements, SmiConstant(0), old_capacity,
new_capacity, UPDATE_WRITE_BARRIER, SMI_PARAMETERS);
StoreObjectField(values_array, JSArray::kElementsOffset, new_elements);
Goto(&return_promise);
// If remainingElementsCount.[[Value]] is 0, then
// Let valuesArray be CreateArrayFromList(values).
// Perform ? Call(resultCapability.[[Resolve]], undefined,
// « valuesArray »).
BIND(&resolve_promise);
{
Node* const resolve =
LoadObjectField(capability, PromiseCapability::kResolveOffset);
Node* const values_array = LoadContextElement(
resolve_element_context,
PromiseBuiltins::kPromiseAllResolveElementValuesArraySlot);
Node* const resolve_call = CallJS(
CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
native_context, resolve, UndefinedConstant(), values_array);
GotoIfException(resolve_call, if_exception, var_exception);
Goto(&return_promise);
}
// Return resultCapability.[[Promise]].
BIND(&return_promise);
}
Node* const promise =
LoadObjectField(capability, PromiseCapability::kPromiseOffset);
return promise;
}
void PromiseBuiltinsAssembler::Generate_PromiseAll(
TNode<Context> context, TNode<Object> receiver, TNode<Object> iterable,
const PromiseAllResolvingElementFunction& create_resolve_element_function,
const PromiseAllResolvingElementFunction& create_reject_element_function) {
IteratorBuiltinsAssembler iter_assembler(state());
// Let C be the this value.
// If Type(C) is not Object, throw a TypeError exception.
ThrowIfNotJSReceiver(context, receiver, 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
Node* const debug_event = FalseConstant();
Node* const capability = CallBuiltin(Builtins::kNewPromiseCapability, context,
receiver, debug_event);
VARIABLE(var_exception, MachineRepresentation::kTagged, TheHoleConstant());
Label reject_promise(this, &var_exception, Label::kDeferred);
// Let iterator be GetIterator(iterable).
// IfAbruptRejectPromise(iterator, promiseCapability).
IteratorRecord iterator = iter_assembler.GetIterator(
context, iterable, &reject_promise, &var_exception);
// 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).
Node* const result = PerformPromiseAll(
context, receiver, capability, iterator, create_resolve_element_function,
create_reject_element_function, &reject_promise, &var_exception);
Return(result);
BIND(&reject_promise);
{
// Exception must be bound to a JS value.
CSA_SLOW_ASSERT(this, IsNotTheHole(var_exception.value()));
Node* const reject =
LoadObjectField(capability, PromiseCapability::kRejectOffset);
CallJS(CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
context, reject, UndefinedConstant(), var_exception.value());
Node* const promise =
LoadObjectField(capability, PromiseCapability::kPromiseOffset);
Return(promise);
}
}
// ES#sec-promise.all
// Promise.all ( iterable )
TF_BUILTIN(PromiseAll, PromiseBuiltinsAssembler) {
TNode<Object> receiver = Cast(Parameter(Descriptor::kReceiver));
TNode<Context> context = Cast(Parameter(Descriptor::kContext));
TNode<Object> iterable = Cast(Parameter(Descriptor::kIterable));
Generate_PromiseAll(
context, receiver, iterable,
[this](TNode<Context> context, TNode<Smi> index,
TNode<NativeContext> native_context,
TNode<PromiseCapability> capability) {
return CreatePromiseAllResolveElementFunction(
context, index, native_context,
Context::PROMISE_ALL_RESOLVE_ELEMENT_SHARED_FUN);
},
[this](TNode<Context> context, TNode<Smi> index,
TNode<NativeContext> native_context,
TNode<PromiseCapability> capability) {
return LoadObjectField(capability, PromiseCapability::kRejectOffset);
});
}
// ES#sec-promise.allsettled
// Promise.allSettled ( iterable )
TF_BUILTIN(PromiseAllSettled, PromiseBuiltinsAssembler) {
TNode<Object> receiver = Cast(Parameter(Descriptor::kReceiver));
TNode<Context> context = Cast(Parameter(Descriptor::kContext));
TNode<Object> iterable = Cast(Parameter(Descriptor::kIterable));
Generate_PromiseAll(
context, receiver, iterable,
[this](TNode<Context> context, TNode<Smi> index,
TNode<NativeContext> native_context,
TNode<PromiseCapability> capability) {
return CreatePromiseAllResolveElementFunction(
context, index, native_context,
Context::PROMISE_ALL_SETTLED_RESOLVE_ELEMENT_SHARED_FUN);
},
[this](TNode<Context> context, TNode<Smi> index,
TNode<NativeContext> native_context,
TNode<PromiseCapability> capability) {
return CreatePromiseAllResolveElementFunction(
context, index, native_context,
Context::PROMISE_ALL_SETTLED_REJECT_ELEMENT_SHARED_FUN);
});
}
void PromiseBuiltinsAssembler::Generate_PromiseAllResolveElementClosure(
TNode<Context> context, TNode<Object> value, TNode<JSFunction> function,
const CreatePromiseAllResolveElementFunctionValue& callback) {
Label already_called(this, Label::kDeferred), resolve_promise(this);
// 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.
GotoIf(IsNativeContext(context), &already_called);
CSA_ASSERT(
this,
SmiEqual(LoadObjectField<Smi>(context, Context::kLengthOffset),
SmiConstant(PromiseBuiltins::kPromiseAllResolveElementLength)));
TNode<NativeContext> native_context = Cast(LoadNativeContext(context));
StoreObjectField(function, JSFunction::kContextOffset, native_context);
// Update the value depending on whether Promise.all or
// Promise.allSettled is called.
value = callback(context, native_context, value);
// Determine the index from the {function}.
Label unreachable(this, Label::kDeferred);
STATIC_ASSERT(PropertyArray::kNoHashSentinel == 0);
TNode<IntPtrT> identity_hash =
LoadJSReceiverIdentityHash(function, &unreachable);
CSA_ASSERT(this, IntPtrGreaterThan(identity_hash, IntPtrConstant(0)));
TNode<IntPtrT> index = IntPtrSub(identity_hash, IntPtrConstant(1));
// Check if we need to grow the [[ValuesArray]] to store {value} at {index}.
TNode<JSArray> values_array = CAST(LoadContextElement(
context, PromiseBuiltins::kPromiseAllResolveElementValuesArraySlot));
TNode<FixedArray> elements = CAST(LoadElements(values_array));
TNode<IntPtrT> values_length =
LoadAndUntagObjectField(values_array, JSArray::kLengthOffset);
Label if_inbounds(this), if_outofbounds(this), done(this);
Branch(IntPtrLessThan(index, values_length), &if_inbounds, &if_outofbounds);
BIND(&if_outofbounds);
{
// Check if we need to grow the backing store.
TNode<IntPtrT> new_length = IntPtrAdd(index, IntPtrConstant(1));
TNode<IntPtrT> elements_length =
LoadAndUntagObjectField(elements, FixedArray::kLengthOffset);
Label if_grow(this, Label::kDeferred), if_nogrow(this);
Branch(IntPtrLessThan(index, elements_length), &if_nogrow, &if_grow);
BIND(&if_grow);
{
// We need to grow the backing store to fit the {index} as well.
TNode<IntPtrT> new_elements_length =
IntPtrMin(CalculateNewElementsCapacity(new_length),
IntPtrConstant(PropertyArray::HashField::kMax + 1));
CSA_ASSERT(this, IntPtrLessThan(index, new_elements_length));
CSA_ASSERT(this, IntPtrLessThan(elements_length, new_elements_length));
TNode<FixedArray> new_elements =
CAST(AllocateFixedArray(PACKED_ELEMENTS, new_elements_length,
AllocationFlag::kAllowLargeObjectAllocation));
CopyFixedArrayElements(PACKED_ELEMENTS, elements, PACKED_ELEMENTS,
new_elements, elements_length,
new_elements_length);
StoreFixedArrayElement(new_elements, index, value);
// Update backing store and "length" on {values_array}.
StoreObjectField(values_array, JSArray::kElementsOffset, new_elements);
StoreObjectFieldNoWriteBarrier(values_array, JSArray::kLengthOffset,
SmiTag(new_length));
Goto(&done);
}
BIND(&if_nogrow);
{
// The {index} is within bounds of the {elements} backing store, so
// just store the {value} and update the "length" of the {values_array}.
StoreObjectFieldNoWriteBarrier(values_array, JSArray::kLengthOffset,
SmiTag(new_length));
StoreFixedArrayElement(elements, index, value);
Goto(&done);
}
}
BIND(&if_inbounds);
{
// The {index} is in bounds of the {values_array},
// just store the {value} and continue.
StoreFixedArrayElement(elements, index, value);
Goto(&done);
}
BIND(&done);
TNode<Smi> remaining_elements_count = CAST(LoadContextElement(
context, PromiseBuiltins::kPromiseAllResolveElementRemainingSlot));
remaining_elements_count = SmiSub(remaining_elements_count, SmiConstant(1));
StoreContextElement(context,
PromiseBuiltins::kPromiseAllResolveElementRemainingSlot,
remaining_elements_count);
GotoIf(SmiEqual(remaining_elements_count, SmiConstant(0)), &resolve_promise);
Return(UndefinedConstant());
BIND(&resolve_promise);
TNode<PromiseCapability> capability = CAST(LoadContextElement(
context, PromiseBuiltins::kPromiseAllResolveElementCapabilitySlot));
TNode<Object> resolve =
LoadObjectField(capability, PromiseCapability::kResolveOffset);
CallJS(CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
context, resolve, UndefinedConstant(), values_array);
Return(UndefinedConstant());
BIND(&already_called);
Return(UndefinedConstant());
BIND(&unreachable);
Unreachable();
}
TF_BUILTIN(PromiseAllResolveElementClosure, PromiseBuiltinsAssembler) {
TNode<Object> value = CAST(Parameter(Descriptor::kValue));
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
TNode<JSFunction> function = CAST(Parameter(Descriptor::kJSTarget));
Generate_PromiseAllResolveElementClosure(
context, value, function,
[](TNode<Object>, TNode<NativeContext>, TNode<Object> value) {
return value;
});
}
TF_BUILTIN(PromiseAllSettledResolveElementClosure, PromiseBuiltinsAssembler) {
TNode<Object> value = CAST(Parameter(Descriptor::kValue));
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
TNode<JSFunction> function = CAST(Parameter(Descriptor::kJSTarget));
Generate_PromiseAllResolveElementClosure(
context, value, function,
[this](TNode<Context> context, TNode<NativeContext> native_context,
TNode<Object> value) {
// TODO(gsathya): Optimize the creation using a cached map to
// prevent transitions here.
// 9. Let obj be ! ObjectCreate(%ObjectPrototype%).
TNode<HeapObject> object_function = Cast(
LoadContextElement(native_context, Context::OBJECT_FUNCTION_INDEX));
TNode<Map> object_function_map = Cast(LoadObjectField(
object_function, JSFunction::kPrototypeOrInitialMapOffset));
TNode<JSObject> obj =
Cast(AllocateJSObjectFromMap(object_function_map));
// 10. Perform ! CreateDataProperty(obj, "status", "fulfilled").
CallBuiltin(Builtins::kFastCreateDataProperty, context, obj,
StringConstant("status"), StringConstant("fulfilled"));
// 11. Perform ! CreateDataProperty(obj, "value", x).
CallBuiltin(Builtins::kFastCreateDataProperty, context, obj,
StringConstant("value"), value);
return obj;
});
}
TF_BUILTIN(PromiseAllSettledRejectElementClosure, PromiseBuiltinsAssembler) {
TNode<Object> value = CAST(Parameter(Descriptor::kValue));
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
TNode<JSFunction> function = CAST(Parameter(Descriptor::kJSTarget));
Generate_PromiseAllResolveElementClosure(
context, value, function,
[this](TNode<Context> context, TNode<NativeContext> native_context,
TNode<Object> value) {
// TODO(gsathya): Optimize the creation using a cached map to
// prevent transitions here.
// 9. Let obj be ! ObjectCreate(%ObjectPrototype%).
TNode<HeapObject> object_function = Cast(
LoadContextElement(native_context, Context::OBJECT_FUNCTION_INDEX));
TNode<Map> object_function_map = Cast(LoadObjectField(
object_function, JSFunction::kPrototypeOrInitialMapOffset));
TNode<JSObject> obj =
Cast(AllocateJSObjectFromMap(object_function_map));
// 10. Perform ! CreateDataProperty(obj, "status", "rejected").
CallBuiltin(Builtins::kFastCreateDataProperty, context, obj,
StringConstant("status"), StringConstant("rejected"));
// 11. Perform ! CreateDataProperty(obj, "reason", x).
CallBuiltin(Builtins::kFastCreateDataProperty, context, obj,
StringConstant("reason"), value);
return obj;
});
}
// ES#sec-promise.race
// Promise.race ( iterable )
TF_BUILTIN(PromiseRace, PromiseBuiltinsAssembler) {
IteratorBuiltinsAssembler iter_assembler(state());
VARIABLE(var_exception, MachineRepresentation::kTagged, TheHoleConstant());
Node* const receiver = Parameter(Descriptor::kReceiver);
Node* const context = Parameter(Descriptor::kContext);
ThrowIfNotJSReceiver(context, receiver, MessageTemplate::kCalledOnNonObject,
"Promise.race");
// Let promiseCapability be ? NewPromiseCapability(C).
// Don't fire debugEvent so that forwarding the rejection through all does not
// trigger redundant ExceptionEvents
Node* const debug_event = FalseConstant();
Node* const capability = CallBuiltin(Builtins::kNewPromiseCapability, context,
receiver, debug_event);
Node* const resolve =
LoadObjectField(capability, PromiseCapability::kResolveOffset);
Node* const reject =
LoadObjectField(capability, PromiseCapability::kRejectOffset);
Label close_iterator(this, Label::kDeferred);
Label reject_promise(this, Label::kDeferred);
// For catch prediction, don't treat the .then calls as handling it;
// instead, recurse outwards.
SetForwardingHandlerIfTrue(context, IsDebugActive(), reject);
// Let iterator be GetIterator(iterable).
// IfAbruptRejectPromise(iterator, promiseCapability).
Node* const iterable = Parameter(Descriptor::kIterable);
IteratorRecord iterator = iter_assembler.GetIterator(
context, iterable, &reject_promise, &var_exception);
// Let result be PerformPromiseRace(iteratorRecord, C, promiseCapability).
{
// We can skip the "resolve" lookup on {constructor} if it's the
// Promise constructor and the Promise.resolve protector is intact,
// as that guards the lookup path for the "resolve" property on the
// Promise constructor.
Label loop(this), break_loop(this), if_slow(this, Label::kDeferred);
Node* const native_context = LoadNativeContext(context);
TVARIABLE(Object, var_promise_resolve_function, UndefinedConstant());
GotoIfNotPromiseResolveLookupChainIntact(native_context, receiver,
&if_slow);
Goto(&loop);
BIND(&if_slow);
{
// 3. Let _promiseResolve_ be ? Get(_constructor_, `"resolve"`).
TNode<Object> resolve =
GetProperty(native_context, receiver, factory()->resolve_string());
GotoIfException(resolve, &reject_promise, &var_exception);
// 4. If IsCallable(_promiseResolve_) is *false*, throw a *TypeError*
// exception.
ThrowIfNotCallable(CAST(context), resolve, "resolve");
var_promise_resolve_function = resolve;
Goto(&loop);
}
BIND(&loop);
{
Node* const fast_iterator_result_map = LoadContextElement(
native_context, Context::ITERATOR_RESULT_MAP_INDEX);
// Let next be IteratorStep(iteratorRecord.[[Iterator]]).
// If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
// ReturnIfAbrupt(next).
Node* const next = iter_assembler.IteratorStep(
context, iterator, &break_loop, fast_iterator_result_map,
&reject_promise, &var_exception);
// Let nextValue be IteratorValue(next).
// If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to
// true.
// ReturnIfAbrupt(nextValue).
Node* const next_value =
iter_assembler.IteratorValue(context, next, fast_iterator_result_map,
&reject_promise, &var_exception);
// Let nextPromise be ? Call(constructor, _promiseResolve_, « nextValue
// »).
Node* const next_promise = CallResolve(
native_context, receiver, var_promise_resolve_function.value(),
next_value, &close_iterator, &var_exception);
// Perform ? Invoke(nextPromise, "then", « resolveElement,
// resultCapability.[[Reject]] »).
Node* const then =
GetProperty(context, next_promise, factory()->then_string());
GotoIfException(then, &close_iterator, &var_exception);
Node* const then_call =
CallJS(CodeFactory::Call(isolate(),
ConvertReceiverMode::kNotNullOrUndefined),
context, then, next_promise, resolve, reject);
GotoIfException(then_call, &close_iterator, &var_exception);
// For catch prediction, mark that rejections here are semantically
// handled by the combined Promise.
SetPromiseHandledByIfTrue(context, IsDebugActive(), then_call, [=]() {
// Load promiseCapability.[[Promise]]
return LoadObjectField(capability, PromiseCapability::kPromiseOffset);
});
Goto(&loop);
}
BIND(&break_loop);
Return(LoadObjectField(capability, PromiseCapability::kPromiseOffset));
}
BIND(&close_iterator);
{
CSA_ASSERT(this, IsNotTheHole(var_exception.value()));
iter_assembler.IteratorCloseOnException(context, iterator, &reject_promise,
&var_exception);
}
BIND(&reject_promise);
{
Node* const reject =
LoadObjectField(capability, PromiseCapability::kRejectOffset);
CallJS(CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
context, reject, UndefinedConstant(), var_exception.value());
Node* const promise =
LoadObjectField(capability, PromiseCapability::kPromiseOffset);
Return(promise);
}
}
} // namespace internal
} // namespace v8