Skip to content

Commit c15802e

Browse files
bmeurerCommit Bot
authored andcommitted
[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 186.9ms to 171.2ms, which corresponds to an improvement of 8.4% in this case. Bug: v8:7253 Change-Id: Ia09effc81a016703780d5c308f541e3d797ccabe Reviewed-on: https://chromium-review.googlesource.com/967203 Reviewed-by: Benedikt Meurer <[email protected]> Reviewed-by: Georg Neis <[email protected]> Commit-Queue: Benedikt Meurer <[email protected]> Cr-Commit-Position: refs/heads/master@{#52024}
1 parent e6bd3ee commit c15802e

4 files changed

Lines changed: 61 additions & 11 deletions

File tree

src/builtins/builtins-async-generator-gen.cc

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -518,11 +518,34 @@ TF_BUILTIN(AsyncGeneratorResolve, AsyncGeneratorBuiltinsAssembler) {
518518
done);
519519
}
520520

521-
// Perform Call(promiseCapability.[[Resolve]], undefined, «iteratorResult»).
522-
CallBuiltin(Builtins::kResolvePromise, context, promise, iter_result);
521+
// We know that {iter_result} itself doesn't have any "then" property and
522+
// we also know that the [[Prototype]] of {iter_result} is the intrinsic
523+
// %ObjectPrototype%. So we can skip the [[Resolve]] logic here completely
524+
// and directly call into the FulfillPromise operation if we can prove
525+
// that the %ObjectPrototype% also doesn't have any "then" property. This
526+
// is guarded by the Promise#then protector.
527+
Label if_fast(this), if_slow(this, Label::kDeferred), return_promise(this);
528+
GotoIfForceSlowPath(&if_slow);
529+
Branch(IsPromiseThenProtectorCellInvalid(), &if_slow, &if_fast);
530+
531+
BIND(&if_fast);
532+
{
533+
// Skip the "then" on {iter_result} and directly fulfill the {promise}
534+
// with the {iter_result}.
535+
CallBuiltin(Builtins::kFulfillPromise, context, promise, iter_result);
536+
Goto(&return_promise);
537+
}
538+
539+
BIND(&if_slow);
540+
{
541+
// Perform Call(promiseCapability.[[Resolve]], undefined, «iteratorResult»).
542+
CallBuiltin(Builtins::kResolvePromise, context, promise, iter_result);
543+
Goto(&return_promise);
544+
}
523545

524546
// Per spec, AsyncGeneratorResolve() returns undefined. However, for the
525547
// benefit of %TraceExit(), return the Promise.
548+
BIND(&return_promise);
526549
Return(promise);
527550
}
528551

src/builtins/builtins-promise-gen.cc

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1661,21 +1661,23 @@ TF_BUILTIN(ResolvePromise, PromiseBuiltinsAssembler) {
16611661

16621662
// 7. If Type(resolution) is not Object, then
16631663
GotoIf(TaggedIsSmi(resolution), &if_fulfill);
1664-
Node* const result_map = LoadMap(resolution);
1665-
GotoIfNot(IsJSReceiverMap(result_map), &if_fulfill);
1664+
Node* const resolution_map = LoadMap(resolution);
1665+
GotoIfNot(IsJSReceiverMap(resolution_map), &if_fulfill);
16661666

16671667
// We can skip the "then" lookup on {resolution} if its [[Prototype]]
16681668
// is the (initial) Promise.prototype and the Promise#then protector
16691669
// is intact, as that guards the lookup path for the "then" property
16701670
// on JSPromise instances which have the (initial) %PromisePrototype%.
1671-
Label if_fast(this), if_slow(this, Label::kDeferred);
1671+
Label if_fast(this), if_generic(this), if_slow(this, Label::kDeferred);
16721672
Node* const native_context = LoadNativeContext(context);
1673-
BranchIfPromiseThenLookupChainIntact(native_context, result_map, &if_fast,
1674-
&if_slow);
1673+
GotoIfForceSlowPath(&if_slow);
1674+
GotoIf(IsPromiseThenProtectorCellInvalid(), &if_slow);
1675+
GotoIfNot(IsJSPromiseMap(resolution_map), &if_generic);
1676+
Node* const promise_prototype =
1677+
LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_INDEX);
1678+
Branch(WordEqual(LoadMapPrototype(resolution_map), promise_prototype),
1679+
&if_fast, &if_slow);
16751680

1676-
// Resolution is a native promise and if it's already resolved or
1677-
// rejected, shortcircuit the resolution procedure by directly
1678-
// reusing the value from the promise.
16791681
BIND(&if_fast);
16801682
{
16811683
Node* const then =
@@ -1684,6 +1686,21 @@ TF_BUILTIN(ResolvePromise, PromiseBuiltinsAssembler) {
16841686
Goto(&do_enqueue);
16851687
}
16861688

1689+
BIND(&if_generic);
1690+
{
1691+
// We can skip the lookup of "then" if the {resolution} is a (newly
1692+
// created) IterResultObject, as the Promise#then protector also
1693+
// ensures that the intrinsic %ObjectPrototype% doesn't contain any
1694+
// "then" property. This helps to avoid negative lookups on iterator
1695+
// results from async generators.
1696+
CSA_ASSERT(this, IsJSReceiverMap(resolution_map));
1697+
CSA_ASSERT(this, Word32BinaryNot(IsPromiseThenProtectorCellInvalid()));
1698+
Node* const iterator_result_map =
1699+
LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX);
1700+
Branch(WordEqual(resolution_map, iterator_result_map), &if_fulfill,
1701+
&if_slow);
1702+
}
1703+
16871704
BIND(&if_slow);
16881705
{
16891706
// 8. Let then be Get(resolution, "then").

src/isolate.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1095,7 +1095,10 @@ class Isolate {
10951095
bool IsPromiseResolveLookupChainIntact();
10961096

10971097
// Make sure a lookup of "then" on any JSPromise whose [[Prototype]] is the
1098-
// initial %PromisePrototype% yields the initial method.
1098+
// initial %PromisePrototype% yields the initial method. In addition this
1099+
// protector also guards the negative lookup of "then" on the intrinsic
1100+
// %ObjectPrototype%, meaning that such lookups are guaranteed to yield
1101+
// undefined without triggering any side-effects.
10991102
bool IsPromiseThenLookupChainIntact();
11001103
bool IsPromiseThenLookupChainIntact(Handle<JSReceiver> receiver);
11011104

src/lookup.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,14 @@ void LookupIterator::InternalUpdateProtector() {
324324
if (!isolate_->IsPromiseThenLookupChainIntact()) return;
325325
// Setting the "then" property on any JSPromise instance or on the
326326
// initial %PromisePrototype% invalidates the Promise#then protector.
327+
// Also setting the "then" property on the initial %ObjectPrototype%
328+
// invalidates the Promise#then protector, since we use this protector
329+
// to guard the fast-path in AsyncGeneratorResolve, where we can skip
330+
// the ResolvePromise step and go directly to FulfillPromise if we
331+
// know that the Object.prototype doesn't contain a "then" method.
327332
if (holder_->IsJSPromise() ||
333+
isolate_->IsInAnyContext(*holder_,
334+
Context::INITIAL_OBJECT_PROTOTYPE_INDEX) ||
328335
isolate_->IsInAnyContext(*holder_, Context::PROMISE_PROTOTYPE_INDEX)) {
329336
isolate_->InvalidatePromiseThenProtector();
330337
}

0 commit comments

Comments
 (0)