Commit cf080aeb authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[async-generators] Add fast-path to skip "then" lookup in AsyncGeneratorResolve.

This extends the Promise#then protector to also guard the intrinsic
%ObjectPrototype%, making it usable for fast-path logic in the
AsyncGeneratorResolve operation, where we can skip the "then" lookup
on the iteratorResult in that case. We also add a corresponding fast-
path to the ResolvePromise builtin itself, which avoids the second
"then" lookup on the async iterator side.

This reduces execution time of the fibonacci-async-es2017-native test
from 298.16ms to 280.55ms, which corresponds to an improvement of ~6%
in this case.

This is a rebased reland of

  https://chromium-review.googlesource.com/967203

which landed earlier, but had to be reverted as part of the mega-revert
that was necessary to fix the async_hooks breakage in Node 10.

Bug: v8:7253
Change-Id: Id9dd1ddc4a8285d64e92cd2030f3168a12e4b509
Reviewed-on: https://chromium-review.googlesource.com/1130523
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Cr-Commit-Position: refs/heads/master@{#54352}
parent 2bb56165
......@@ -527,11 +527,38 @@ TF_BUILTIN(AsyncGeneratorResolve, AsyncGeneratorBuiltinsAssembler) {
done);
}
// We know that {iter_result} itself doesn't have any "then" property (a
// freshly allocated IterResultObject only has "value" and "done" properties)
// and we also know that the [[Prototype]] of {iter_result} is the intrinsic
// %ObjectPrototype%. So we can skip the [[Resolve]] logic here completely
// and directly call into the FulfillPromise operation if we can prove
// that the %ObjectPrototype% also doesn't have any "then" property. This
// is guarded by the Promise#then() protector.
// If the PromiseHooks are enabled, we cannot take the shortcut here, since
// the "promiseResolve" hook would not be fired otherwise.
Label if_fast(this), if_slow(this, Label::kDeferred), return_promise(this);
GotoIfForceSlowPath(&if_slow);
GotoIf(IsPromiseHookEnabled(), &if_slow);
Branch(IsPromiseThenProtectorCellInvalid(), &if_slow, &if_fast);
BIND(&if_fast);
{
// Skip the "then" on {iter_result} and directly fulfill the {promise}
// with the {iter_result}.
CallBuiltin(Builtins::kFulfillPromise, context, promise, iter_result);
Goto(&return_promise);
}
BIND(&if_slow);
{
// Perform Call(promiseCapability.[[Resolve]], undefined, «iteratorResult»).
CallBuiltin(Builtins::kResolvePromise, context, promise, iter_result);
Goto(&return_promise);
}
// Per spec, AsyncGeneratorResolve() returns undefined. However, for the
// benefit of %TraceExit(), return the Promise.
BIND(&return_promise);
Return(promise);
}
......
......@@ -1746,29 +1746,47 @@ TF_BUILTIN(ResolvePromise, PromiseBuiltinsAssembler) {
// 7. If Type(resolution) is not Object, then
GotoIf(TaggedIsSmi(resolution), &if_fulfill);
Node* const result_map = LoadMap(resolution);
GotoIfNot(IsJSReceiverMap(result_map), &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_slow(this, Label::kDeferred);
Label if_fast(this), if_receiver(this), if_slow(this, Label::kDeferred);
Node* const native_context = LoadNativeContext(context);
BranchIfPromiseThenLookupChainIntact(native_context, result_map, &if_fast,
&if_slow);
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);
// Resolution is a native promise and if it's already resolved or
// rejected, shortcircuit the resolution procedure by directly
// reusing the value from the promise.
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").
......
......@@ -1150,7 +1150,10 @@ class Isolate : private HiddenFactory {
bool IsPromiseResolveLookupChainIntact();
// Make sure a lookup of "then" on any JSPromise whose [[Prototype]] is the
// initial %PromisePrototype% yields the initial method.
// initial %PromisePrototype% yields the initial method. In addition this
// protector also guards the negative lookup of "then" on the intrinsic
// %ObjectPrototype%, meaning that such lookups are guaranteed to yield
// undefined without triggering any side-effects.
bool IsPromiseThenLookupChainIntact();
bool IsPromiseThenLookupChainIntact(Handle<JSReceiver> receiver);
......
......@@ -367,7 +367,14 @@ void LookupIterator::InternalUpdateProtector() {
if (!isolate_->IsPromiseThenLookupChainIntact()) return;
// Setting the "then" property on any JSPromise instance or on the
// initial %PromisePrototype% invalidates the Promise#then protector.
// Also setting the "then" property on the initial %ObjectPrototype%
// invalidates the Promise#then protector, since we use this protector
// to guard the fast-path in AsyncGeneratorResolve, where we can skip
// the ResolvePromise step and go directly to FulfillPromise if we
// know that the Object.prototype doesn't contain a "then" method.
if (holder_->IsJSPromise() ||
isolate_->IsInAnyContext(*holder_,
Context::INITIAL_OBJECT_PROTOTYPE_INDEX) ||
isolate_->IsInAnyContext(*holder_, Context::PROMISE_PROTOTYPE_INDEX)) {
isolate_->InvalidatePromiseThenProtector();
}
......
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