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

[builtins] Add fast-path for the Promise.resolve lookup.

This adds a global protector to guard the lookup of "resolve" on the
%Promise% intrinsic object (the initial Promise constructor), making
sure that Promise.resolve yields the initial builtin method. We use
this protector to avoid the lookup of "resolve" all the time inside
of Promise.all and Promise.race, when called with constructor being
the %Promise% intrinsic object.

This improves the performance on the parallel-async-es2017-native
benchmark by roughly 2-3%.

Bug: v8:7253
Change-Id: Ida93b88afbaeae61f17be4cd30ea6a78b4267cea
Reviewed-on: https://chromium-review.googlesource.com/955564Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#51810}
parent 6afd25ff
......@@ -526,6 +526,61 @@ Node* PromiseBuiltinsAssembler::InvokeThen(Node* native_context, Node* receiver,
return var_result.value();
}
Node* PromiseBuiltinsAssembler::InvokeResolve(Node* native_context,
Node* constructor, Node* value,
Label* if_exception,
Variable* var_exception) {
CSA_ASSERT(this, IsNativeContext(native_context));
VARIABLE(var_result, MachineRepresentation::kTagged);
Label if_fast(this), if_slow(this, Label::kDeferred), done(this, &var_result);
// 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.
BranchIfPromiseResolveLookupChainIntact(native_context, constructor, &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);
{
Node* const resolve =
GetProperty(native_context, constructor, factory()->resolve_string());
GotoIfException(resolve, if_exception, var_exception);
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::BranchIfPromiseSpeciesLookupChainIntact(
Node* native_context, Node* promise_map, Label* if_fast, Label* if_slow) {
CSA_ASSERT(this, IsNativeContext(native_context));
......@@ -1717,14 +1772,9 @@ Node* PromiseBuiltinsAssembler::PerformPromiseAll(
context, next, fast_iterator_result_map, if_exception, var_exception);
// Let nextPromise be ? Invoke(constructor, "resolve", « nextValue »).
Node* const promise_resolve =
GetProperty(context, constructor, factory()->resolve_string());
GotoIfException(promise_resolve, &close_iterator, var_exception);
Node* const next_promise = CallJS(
CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined),
context, promise_resolve, constructor, next_value);
GotoIfException(next_promise, &close_iterator, var_exception);
Node* const next_promise =
InvokeResolve(native_context, constructor, next_value, &close_iterator,
var_exception);
// Let resolveElement be a new built-in function object as defined in
// Promise.all Resolve Element Functions.
......@@ -2043,15 +2093,9 @@ TF_BUILTIN(PromiseRace, PromiseBuiltinsAssembler) {
&reject_promise, &var_exception);
// Let nextPromise be ? Invoke(constructor, "resolve", « nextValue »).
Node* const promise_resolve =
GetProperty(context, receiver, factory()->resolve_string());
GotoIfException(promise_resolve, &close_iterator, &var_exception);
Node* const next_promise =
CallJS(CodeFactory::Call(isolate(),
ConvertReceiverMode::kNotNullOrUndefined),
context, promise_resolve, receiver, next_value);
GotoIfException(next_promise, &close_iterator, &var_exception);
InvokeResolve(native_context, receiver, next_value, &close_iterator,
&var_exception);
// Perform ? Invoke(nextPromise, "then", « resolveElement,
// resultCapability.[[Reject]] »).
......
......@@ -126,6 +126,14 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
Node* TriggerPromiseReactions(Node* context, Node* promise, Node* result,
PromiseReaction::Type type);
// We can skip the "resolve" lookup on {constructor} if it's the (initial)
// Promise constructor and the Promise.resolve() protector is intact, as
// that guards the lookup path for the "resolve" property on the %Promise%
// intrinsic object.
void BranchIfPromiseResolveLookupChainIntact(Node* native_context,
Node* constructor,
Label* if_fast, Label* if_slow);
// We can shortcut the SpeciesConstructor on {promise_map} if it's
// [[Prototype]] is the (initial) Promise.prototype and the @@species
// protector is intact, as that guards the lookup path for the "constructor"
......@@ -142,6 +150,8 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
Node* receiver_map, Label* if_fast,
Label* if_slow);
Node* InvokeResolve(Node* native_context, Node* constructor, Node* value,
Label* if_exception, Variable* var_exception);
template <typename... TArgs>
Node* InvokeThen(Node* native_context, Node* receiver, TArgs... args);
......
......@@ -4340,6 +4340,13 @@ Node* CodeStubAssembler::IsNoElementsProtectorCellInvalid() {
return WordEqual(cell_value, invalid);
}
Node* CodeStubAssembler::IsPromiseResolveProtectorCellInvalid() {
Node* invalid = SmiConstant(Isolate::kProtectorInvalid);
Node* cell = LoadRoot(Heap::kPromiseResolveProtectorRootIndex);
Node* cell_value = LoadObjectField(cell, Cell::kValueOffset);
return WordEqual(cell_value, invalid);
}
Node* CodeStubAssembler::IsPromiseThenProtectorCellInvalid() {
Node* invalid = SmiConstant(Isolate::kProtectorInvalid);
Node* cell = LoadRoot(Heap::kPromiseThenProtectorRootIndex);
......@@ -7816,6 +7823,8 @@ void CodeStubAssembler::CheckForAssociatedProtector(Node* name,
if_protector);
GotoIf(WordEqual(name, LoadRoot(Heap::kis_concat_spreadable_symbolRootIndex)),
if_protector);
GotoIf(WordEqual(name, LoadRoot(Heap::kresolve_stringRootIndex)),
if_protector);
GotoIf(WordEqual(name, LoadRoot(Heap::kthen_stringRootIndex)), if_protector);
// Fall through if no case matched.
}
......
......@@ -1185,6 +1185,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
return IsSharedFunctionInfoMap(LoadMap(object));
}
Node* IsPromiseResolveProtectorCellInvalid();
Node* IsPromiseThenProtectorCellInvalid();
Node* IsSpeciesProtectorCellInvalid();
......
......@@ -225,6 +225,7 @@ using v8::MemoryPressureLevel;
V(PropertyCell, array_buffer_neutering_protector, \
ArrayBufferNeuteringProtector) \
V(PropertyCell, promise_hook_protector, PromiseHookProtector) \
V(Cell, promise_resolve_protector, PromiseResolveProtector) \
V(PropertyCell, promise_then_protector, PromiseThenProtector) \
/* Special numbers */ \
V(HeapNumber, nan_value, NanValue) \
......
......@@ -664,6 +664,10 @@ void Heap::CreateInitialObjects() {
cell->set_value(Smi::FromInt(Isolate::kProtectorValid));
set_promise_hook_protector(*cell);
Handle<Cell> promise_resolve_cell = factory->NewCell(
handle(Smi::FromInt(Isolate::kProtectorValid), isolate()));
set_promise_resolve_protector(*promise_resolve_cell);
cell = factory->NewPropertyCell(factory->empty_string());
cell->set_value(Smi::FromInt(Isolate::kProtectorValid));
set_promise_then_protector(*cell);
......
......@@ -3482,6 +3482,13 @@ bool Isolate::IsPromiseHookProtectorIntact() {
return is_promise_hook_protector_intact;
}
bool Isolate::IsPromiseResolveLookupChainIntact() {
Cell* promise_resolve_cell = heap()->promise_resolve_protector();
bool is_promise_resolve_protector_intact =
Smi::ToInt(promise_resolve_cell->value()) == kProtectorValid;
return is_promise_resolve_protector_intact;
}
bool Isolate::IsPromiseThenLookupChainIntact() {
PropertyCell* promise_then_cell = heap()->promise_then_protector();
bool is_promise_then_protector_intact =
......@@ -3567,6 +3574,14 @@ void Isolate::InvalidatePromiseHookProtector() {
DCHECK(!IsPromiseHookProtectorIntact());
}
void Isolate::InvalidatePromiseResolveProtector() {
DCHECK(factory()->promise_resolve_protector()->value()->IsSmi());
DCHECK(IsPromiseResolveLookupChainIntact());
factory()->promise_resolve_protector()->set_value(
Smi::FromInt(kProtectorInvalid));
DCHECK(!IsPromiseResolveLookupChainIntact());
}
void Isolate::InvalidatePromiseThenProtector() {
DCHECK(factory()->promise_then_protector()->value()->IsSmi());
DCHECK(IsPromiseThenLookupChainIntact());
......
......@@ -1088,6 +1088,10 @@ class Isolate {
// active.
bool IsPromiseHookProtectorIntact();
// Make sure a lookup of "resolve" on the %Promise% intrinsic object
// yeidls the initial Promise.resolve method.
bool IsPromiseResolveLookupChainIntact();
// Make sure a lookup of "then" on any JSPromise whose [[Prototype]] is the
// initial %PromisePrototype% yields the initial method.
bool IsPromiseThenLookupChainIntact();
......@@ -1114,6 +1118,7 @@ class Isolate {
void InvalidateArrayIteratorProtector();
void InvalidateArrayBufferNeuteringProtector();
V8_EXPORT_PRIVATE void InvalidatePromiseHookProtector();
void InvalidatePromiseResolveProtector();
void InvalidatePromiseThenProtector();
// Returns true if array is the initial array prototype in any native context.
......
......@@ -313,6 +313,13 @@ void LookupIterator::InternalUpdateProtector() {
if (holder_->IsJSArray()) {
isolate_->InvalidateArrayIteratorProtector();
}
} else if (*name_ == heap()->resolve_string()) {
if (!isolate_->IsPromiseResolveLookupChainIntact()) return;
// Setting the "resolve" property on any %Promise% intrinsic object
// invalidates the Promise.resolve protector.
if (isolate_->IsInAnyContext(*holder_, Context::PROMISE_FUNCTION_INDEX)) {
isolate_->InvalidatePromiseResolveProtector();
}
} else if (*name_ == heap()->then_string()) {
if (!isolate_->IsPromiseThenLookupChainIntact()) return;
// Setting the "then" property on any JSPromise instance or on the
......
......@@ -280,7 +280,7 @@ class V8_EXPORT_PRIVATE LookupIterator final BASE_EMBEDDED {
*name_ == heap()->constructor_string() ||
*name_ == heap()->next_string() || *name_ == heap()->species_symbol() ||
*name_ == heap()->iterator_symbol() ||
*name_ == heap()->then_string()) {
*name_ == heap()->resolve_string() || *name_ == heap()->then_string()) {
InternalUpdateProtector();
}
}
......
......@@ -316,9 +316,9 @@ KNOWN_OBJECTS = {
("OLD_SPACE", 0x02a29): "StringLengthProtector",
("OLD_SPACE", 0x02a39): "ArrayIteratorProtector",
("OLD_SPACE", 0x02a61): "ArrayBufferNeuteringProtector",
("OLD_SPACE", 0x02ad9): "InfinityValue",
("OLD_SPACE", 0x02ae9): "MinusZeroValue",
("OLD_SPACE", 0x02af9): "MinusInfinityValue",
("OLD_SPACE", 0x02ae9): "InfinityValue",
("OLD_SPACE", 0x02af9): "MinusZeroValue",
("OLD_SPACE", 0x02b09): "MinusInfinityValue",
}
# List of known V8 Frame Markers.
......
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