Commit 50f713c9 authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[promises] Add fast-path for native promises to Promise.all.

This CL introduces a new fast-path for `Promise.all(a)` for the case
that elements in `a` are native promises, and the Promise.prototype
and Promise function itself are intact. If so, we can skip the lookups
of "resolve" on Promise and "then" on the result of invoking "resolve",
which are both quite expensive, and we can instead directly call the
PerformPromiseThen() operation on the element of `a`.

In addition to that we don't need to create and chain a result promise,
since this is only used when either async_hooks or DevTools are enabled.
Otherwise it's a "throwaway promise" only used to satisfy the operation
parameter signature (see https://github.com/tc39/ecma262/pull/1146).

This results in a significant performance improvement on `Promise.all()`
heavy code. For example the parallel-promises-es2015-native test goes
from around 84ms to roughly 68ms, which is almost a 20% improvement.

Bug: v8:7253
Ref: tc39/ecma262#1146
Change-Id: Iab9c57edb26d13a467b0653fd8de6149c382efc6
Reviewed-on: https://chromium-review.googlesource.com/c/1293374Reviewed-by: 's avatarSathya Gunasekaran <gsathya@chromium.org>
Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#56858}
parent 5edf567a
......@@ -637,6 +637,14 @@ void PromiseBuiltinsAssembler::BranchIfPromiseResolveLookupChainIntact(
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));
......@@ -1889,16 +1897,14 @@ Node* PromiseBuiltinsAssembler::PerformPromiseAll(
native_context, next, fast_iterator_result_map, if_exception,
var_exception);
// Let nextPromise be ? Invoke(constructor, "resolve", « nextValue »).
Node* const next_promise =
InvokeResolve(native_context, constructor, next_value, &close_iterator,
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(
......@@ -1921,28 +1927,73 @@ Node* PromiseBuiltinsAssembler::PerformPromiseAll(
Node* const resolve_element_fun = CreatePromiseAllResolveElementFunction(
resolve_element_context, index, native_context);
// Perform ? Invoke(nextPromise, "then", « resolveElement,
// resultCapability.[[Reject]] »).
Node* const then =
GetProperty(native_context, next_promise, factory()->then_string());
GotoIfException(then, &close_iterator, var_exception);
// 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 {next_value} is a JSPromise whose [[Prototype]] field
// contains the intrinsic %PromisePrototype%, and
// (c) 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);
GotoIfNotPromiseResolveLookupChainIntact(native_context, constructor,
&if_slow);
GotoIf(instrumenting, &if_slow);
GotoIf(IsPromiseHookEnabledOrHasAsyncEventDelegate(), &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,
LoadObjectField(capability, PromiseCapability::kRejectOffset),
UndefinedConstant());
Goto(&loop);
}
Node* const then_call = CallJS(
CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined),
native_context, then, next_promise, resolve_element_fun,
LoadObjectField(capability, PromiseCapability::kRejectOffset));
GotoIfException(then_call, &close_iterator, var_exception);
BIND(&if_slow);
{
// Let nextPromise be ? Invoke(constructor, "resolve", « nextValue »).
Node* const next_promise =
InvokeResolve(native_context, constructor, next_value,
&close_iterator, var_exception);
// For catch prediction, mark that rejections here are semantically
// handled by the combined Promise.
SetPromiseHandledByIfTrue(native_context, instrumenting, then_call, [=]() {
// Load promiseCapability.[[Promise]]
return LoadObjectField(capability, PromiseCapability::kPromiseOffset);
});
// Perform ? Invoke(nextPromise, "then", « resolveElement,
// resultCapability.[[Reject]] »).
Node* const then =
GetProperty(native_context, next_promise, factory()->then_string());
GotoIfException(then, &close_iterator, var_exception);
// Set index to index + 1.
var_index = SmiAdd(index, SmiConstant(1));
Goto(&loop);
Node* const then_call =
CallJS(CodeFactory::Call(isolate(),
ConvertReceiverMode::kNotNullOrUndefined),
native_context, then, next_promise, resolve_element_fun,
LoadObjectField(capability, PromiseCapability::kRejectOffset));
GotoIfException(then_call, &close_iterator, var_exception);
// For catch prediction, mark that rejections here are semantically
// handled by the combined Promise.
SetPromiseHandledByIfTrue(
native_context, instrumenting, then_call, [=]() {
// Load promiseCapability.[[Promise]]
return LoadObjectField(capability,
PromiseCapability::kPromiseOffset);
});
Goto(&loop);
}
}
BIND(&too_many_elements);
......
......@@ -144,6 +144,9 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
void BranchIfPromiseResolveLookupChainIntact(Node* native_context,
Node* constructor,
Label* if_fast, Label* if_slow);
void GotoIfNotPromiseResolveLookupChainIntact(Node* native_context,
Node* constructor,
Label* if_slow);
// We can shortcut the SpeciesConstructor on {promise_map} if it's
// [[Prototype]] is the (initial) Promise.prototype and the @@species
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment