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

[builtins] Widen the fast-path for Promise builtins.

This adds a new isolate wide Promise#then protector, which guards the
"then" lookup for all JSPromise instances whose [[Prototype]] is the
initial %PromisePrototype%. Thus arbitrary mutations to the
Promise.prototype (i.e. monkey-patching other methods or installing
new functions) no longer sent you down the slow-path. Use this protector
in Promise.prototype.catch and in Promise.resolve.

Drive-by-fix: Restructure the resolve logic a bit and avoid the
expensive and large SameValue check, which can be turned into a simple
reference equal, as the promise in there is known to be a JSPromise
anyways.

Bug: v8:7253
Change-Id: If68b12c6bc6ca9c4d10552ae84854ebc3b5774f9
Reviewed-on: https://chromium-review.googlesource.com/899302
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarSathya Gunasekaran <gsathya@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#51085}
parent 3a0372f9
...@@ -2420,10 +2420,6 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object, ...@@ -2420,10 +2420,6 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
Handle<Map> prototype_map(prototype->map()); Handle<Map> prototype_map(prototype->map());
Map::SetShouldBeFastPrototypeMap(prototype_map, true, isolate); Map::SetShouldBeFastPrototypeMap(prototype_map, true, isolate);
// Store the initial Promise.prototype map. This is used in fast-path
// checks. Do not alter the prototype after this point.
native_context()->set_promise_prototype_map(*prototype_map);
{ // Internal: PromiseInternalConstructor { // Internal: PromiseInternalConstructor
// Also exposed as extrasUtils.createPromise. // Also exposed as extrasUtils.createPromise.
Handle<JSFunction> function = Handle<JSFunction> function =
...@@ -4445,7 +4441,6 @@ void Genesis::InitializeGlobal_harmony_promise_finally() { ...@@ -4445,7 +4441,6 @@ void Genesis::InitializeGlobal_harmony_promise_finally() {
// to prototype, so we update the saved map. // to prototype, so we update the saved map.
Handle<Map> prototype_map(prototype->map()); Handle<Map> prototype_map(prototype->map());
Map::SetShouldBeFastPrototypeMap(prototype_map, true, isolate()); Map::SetShouldBeFastPrototypeMap(prototype_map, true, isolate());
native_context()->set_promise_prototype_map(*prototype_map);
{ {
Handle<SharedFunctionInfo> info = SimpleCreateSharedFunctionInfo( Handle<SharedFunctionInfo> info = SimpleCreateSharedFunctionInfo(
......
This diff is collapsed.
...@@ -127,16 +127,18 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler { ...@@ -127,16 +127,18 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
void InternalResolvePromise(Node* context, Node* promise, Node* result); void InternalResolvePromise(Node* context, Node* promise, Node* result);
void BranchIfFastPath(Node* context, Node* promise, Label* if_isunmodified,
Label* if_ismodified);
void BranchIfFastPath(Node* native_context, Node* promise_fun, Node* promise,
Label* if_isunmodified, Label* if_ismodified);
Node* CreatePromiseContext(Node* native_context, int slots); Node* CreatePromiseContext(Node* native_context, int slots);
void PromiseFulfill(Node* context, Node* promise, Node* result, void PromiseFulfill(Node* context, Node* promise, Node* result,
v8::Promise::PromiseState status); v8::Promise::PromiseState status);
// We can skip the "then" lookup on {receiver_map} 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%.
void BranchIfPromiseThenLookupChainIntact(Node* native_context,
Node* receiver_map, Label* if_fast,
Label* if_slow);
void BranchIfAccessCheckFailed(Node* context, Node* native_context, void BranchIfAccessCheckFailed(Node* context, Node* native_context,
Node* promise_constructor, Node* executor, Node* promise_constructor, Node* executor,
Label* if_noaccess); Label* if_noaccess);
......
...@@ -4165,6 +4165,13 @@ Node* CodeStubAssembler::IsNoElementsProtectorCellInvalid() { ...@@ -4165,6 +4165,13 @@ Node* CodeStubAssembler::IsNoElementsProtectorCellInvalid() {
return WordEqual(cell_value, invalid); return WordEqual(cell_value, invalid);
} }
Node* CodeStubAssembler::IsPromiseThenProtectorCellInvalid() {
Node* invalid = SmiConstant(Isolate::kProtectorInvalid);
Node* cell = LoadRoot(Heap::kPromiseThenProtectorRootIndex);
Node* cell_value = LoadObjectField(cell, PropertyCell::kValueOffset);
return WordEqual(cell_value, invalid);
}
Node* CodeStubAssembler::IsSpeciesProtectorCellInvalid() { Node* CodeStubAssembler::IsSpeciesProtectorCellInvalid() {
Node* invalid = SmiConstant(Isolate::kProtectorInvalid); Node* invalid = SmiConstant(Isolate::kProtectorInvalid);
Node* cell = LoadRoot(Heap::kSpeciesProtectorRootIndex); Node* cell = LoadRoot(Heap::kSpeciesProtectorRootIndex);
...@@ -7560,6 +7567,7 @@ void CodeStubAssembler::CheckForAssociatedProtector(Node* name, ...@@ -7560,6 +7567,7 @@ void CodeStubAssembler::CheckForAssociatedProtector(Node* name,
if_protector); if_protector);
GotoIf(WordEqual(name, LoadRoot(Heap::kis_concat_spreadable_symbolRootIndex)), GotoIf(WordEqual(name, LoadRoot(Heap::kis_concat_spreadable_symbolRootIndex)),
if_protector); if_protector);
GotoIf(WordEqual(name, LoadRoot(Heap::kthen_stringRootIndex)), if_protector);
// Fall through if no case matched. // Fall through if no case matched.
} }
......
...@@ -1143,7 +1143,6 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler { ...@@ -1143,7 +1143,6 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
Node* IsShortExternalStringInstanceType(Node* instance_type); Node* IsShortExternalStringInstanceType(Node* instance_type);
Node* IsSpecialReceiverInstanceType(Node* instance_type); Node* IsSpecialReceiverInstanceType(Node* instance_type);
Node* IsSpecialReceiverMap(Node* map); Node* IsSpecialReceiverMap(Node* map);
Node* IsSpeciesProtectorCellInvalid();
Node* IsStringInstanceType(Node* instance_type); Node* IsStringInstanceType(Node* instance_type);
Node* IsString(Node* object); Node* IsString(Node* object);
Node* IsSymbolInstanceType(Node* instance_type); Node* IsSymbolInstanceType(Node* instance_type);
...@@ -1156,6 +1155,9 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler { ...@@ -1156,6 +1155,9 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
return IsSharedFunctionInfoMap(LoadMap(object)); return IsSharedFunctionInfoMap(LoadMap(object));
} }
Node* IsPromiseThenProtectorCellInvalid();
Node* IsSpeciesProtectorCellInvalid();
// True iff |object| is a Smi or a HeapNumber. // True iff |object| is a Smi or a HeapNumber.
Node* IsNumber(Node* object); Node* IsNumber(Node* object);
// True iff |object| is a Smi or a HeapNumber or a BigInt. // True iff |object| is a Smi or a HeapNumber or a BigInt.
......
...@@ -10,7 +10,6 @@ ...@@ -10,7 +10,6 @@
#include "src/code-stubs.h" #include "src/code-stubs.h"
#include "src/compilation-dependencies.h" #include "src/compilation-dependencies.h"
#include "src/compiler/access-builder.h" #include "src/compiler/access-builder.h"
#include "src/compiler/access-info.h"
#include "src/compiler/allocation-builder.h" #include "src/compiler/allocation-builder.h"
#include "src/compiler/js-graph.h" #include "src/compiler/js-graph.h"
#include "src/compiler/linkage.h" #include "src/compiler/linkage.h"
...@@ -3944,27 +3943,30 @@ Reduction JSCallReducer::ReducePromisePrototypeCatch(Node* node) { ...@@ -3944,27 +3943,30 @@ Reduction JSCallReducer::ReducePromisePrototypeCatch(Node* node) {
return NoChange(); return NoChange();
} }
// Check that the Promise.then protector is intact. This protector guards
// that all JSPromise instances whose [[Prototype]] is the initial
// %PromisePrototype% yield the initial %PromisePrototype%.then method
// when looking up "then".
if (!isolate()->IsPromiseThenLookupChainIntact()) return NoChange();
// Check if we know something about {receiver} already. // Check if we know something about {receiver} already.
ZoneHandleSet<Map> receiver_maps; ZoneHandleSet<Map> receiver_maps;
NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMapsResult result =
NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps); NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps);
if (result == NodeProperties::kNoReceiverMaps) return NoChange(); if (result == NodeProperties::kNoReceiverMaps) return NoChange();
DCHECK_NE(0, receiver_maps.size()); DCHECK_NE(0, receiver_maps.size());
if (receiver_maps.size() != 1) return NoChange();
Handle<Map> receiver_map = receiver_maps[0]; // Check whether all {receiver_maps} are JSPromise maps and
// have the initial Promise.prototype as their [[Prototype]].
// Lookup the "then" method on the {receiver_map}. for (Handle<Map> receiver_map : receiver_maps) {
PropertyAccessInfo access_info; if (!receiver_map->IsJSPromiseMap()) return NoChange();
AccessInfoFactory access_info_factory(dependencies(), native_context(), if (receiver_map->prototype() != native_context()->promise_prototype()) {
graph()->zone()); return NoChange();
if (!access_info_factory.ComputePropertyAccessInfo( }
receiver_map, factory()->then_string(), AccessMode::kLoad,
&access_info) ||
!access_info.IsDataConstant()) {
return NoChange();
} }
dependencies()->AssumePrototypeMapsStable(receiver_map, access_info.holder());
Handle<Object> then = access_info.constant(); // Add a code dependency on the necessary protectors.
dependencies()->AssumePropertyCell(factory()->promise_then_protector());
// If the {receiver_maps} aren't reliable, we need to repeat the // If the {receiver_maps} aren't reliable, we need to repeat the
// map check here, guarded by the CALL_IC. // map check here, guarded by the CALL_IC.
...@@ -3978,7 +3980,8 @@ Reduction JSCallReducer::ReducePromisePrototypeCatch(Node* node) { ...@@ -3978,7 +3980,8 @@ Reduction JSCallReducer::ReducePromisePrototypeCatch(Node* node) {
// Massage the {node} to call "then" instead by first removing all inputs // Massage the {node} to call "then" instead by first removing all inputs
// following the onRejected parameter, and then filling up the parameters // following the onRejected parameter, and then filling up the parameters
// to two inputs from the left with undefined. // to two inputs from the left with undefined.
NodeProperties::ReplaceValueInput(node, jsgraph()->Constant(then), 0); Node* target = jsgraph()->Constant(handle(native_context()->promise_then()));
NodeProperties::ReplaceValueInput(node, target, 0);
NodeProperties::ReplaceEffectInput(node, effect); NodeProperties::ReplaceEffectInput(node, effect);
for (; arity > 1; --arity) node->RemoveInput(3); for (; arity > 1; --arity) node->RemoveInput(3);
for (; arity < 2; ++arity) { for (; arity < 2; ++arity) {
......
...@@ -338,7 +338,6 @@ enum ContextLookupFlags { ...@@ -338,7 +338,6 @@ enum ContextLookupFlags {
V(PROMISE_ALL_RESOLVE_ELEMENT_SHARED_FUN, SharedFunctionInfo, \ V(PROMISE_ALL_RESOLVE_ELEMENT_SHARED_FUN, SharedFunctionInfo, \
promise_all_resolve_element_shared_fun) \ promise_all_resolve_element_shared_fun) \
V(PROMISE_PROTOTYPE_INDEX, JSObject, promise_prototype) \ V(PROMISE_PROTOTYPE_INDEX, JSObject, promise_prototype) \
V(PROMISE_PROTOTYPE_MAP_INDEX, Map, promise_prototype_map) \
V(REGEXP_EXEC_FUNCTION_INDEX, JSFunction, regexp_exec_function) \ V(REGEXP_EXEC_FUNCTION_INDEX, JSFunction, regexp_exec_function) \
V(REGEXP_FUNCTION_INDEX, JSFunction, regexp_function) \ V(REGEXP_FUNCTION_INDEX, JSFunction, regexp_function) \
V(REGEXP_LAST_MATCH_INFO_INDEX, RegExpMatchInfo, regexp_last_match_info) \ V(REGEXP_LAST_MATCH_INFO_INDEX, RegExpMatchInfo, regexp_last_match_info) \
......
...@@ -215,6 +215,7 @@ using v8::MemoryPressureLevel; ...@@ -215,6 +215,7 @@ using v8::MemoryPressureLevel;
V(PropertyCell, array_buffer_neutering_protector, \ V(PropertyCell, array_buffer_neutering_protector, \
ArrayBufferNeuteringProtector) \ ArrayBufferNeuteringProtector) \
V(PropertyCell, promise_hook_protector, PromiseHookProtector) \ V(PropertyCell, promise_hook_protector, PromiseHookProtector) \
V(PropertyCell, promise_then_protector, PromiseThenProtector) \
/* Special numbers */ \ /* Special numbers */ \
V(HeapNumber, nan_value, NanValue) \ V(HeapNumber, nan_value, NanValue) \
V(HeapNumber, hole_nan_value, HoleNanValue) \ V(HeapNumber, hole_nan_value, HoleNanValue) \
......
...@@ -643,6 +643,10 @@ void Heap::CreateInitialObjects() { ...@@ -643,6 +643,10 @@ void Heap::CreateInitialObjects() {
cell->set_value(Smi::FromInt(Isolate::kProtectorValid)); cell->set_value(Smi::FromInt(Isolate::kProtectorValid));
set_promise_hook_protector(*cell); set_promise_hook_protector(*cell);
cell = factory->NewPropertyCell(factory->empty_string());
cell->set_value(Smi::FromInt(Isolate::kProtectorValid));
set_promise_then_protector(*cell);
set_serialized_objects(empty_fixed_array()); set_serialized_objects(empty_fixed_array());
set_serialized_global_proxy_sizes(empty_fixed_array()); set_serialized_global_proxy_sizes(empty_fixed_array());
......
...@@ -3482,6 +3482,13 @@ bool Isolate::IsPromiseHookProtectorIntact() { ...@@ -3482,6 +3482,13 @@ bool Isolate::IsPromiseHookProtectorIntact() {
return is_promise_hook_protector_intact; return is_promise_hook_protector_intact;
} }
bool Isolate::IsPromiseThenLookupChainIntact() {
PropertyCell* promise_then_cell = heap()->promise_then_protector();
bool is_promise_then_protector_intact =
Smi::ToInt(promise_then_cell->value()) == kProtectorValid;
return is_promise_then_protector_intact;
}
void Isolate::UpdateNoElementsProtectorOnSetElement(Handle<JSObject> object) { void Isolate::UpdateNoElementsProtectorOnSetElement(Handle<JSObject> object) {
DisallowHeapAllocation no_gc; DisallowHeapAllocation no_gc;
if (!object->map()->is_prototype_map()) return; if (!object->map()->is_prototype_map()) return;
...@@ -3550,6 +3557,15 @@ void Isolate::InvalidatePromiseHookProtector() { ...@@ -3550,6 +3557,15 @@ void Isolate::InvalidatePromiseHookProtector() {
DCHECK(!IsPromiseHookProtectorIntact()); DCHECK(!IsPromiseHookProtectorIntact());
} }
void Isolate::InvalidatePromiseThenProtector() {
DCHECK(factory()->promise_then_protector()->value()->IsSmi());
DCHECK(IsPromiseThenLookupChainIntact());
PropertyCell::SetValueWithInvalidation(
factory()->promise_then_protector(),
handle(Smi::FromInt(kProtectorInvalid), this));
DCHECK(!IsPromiseThenLookupChainIntact());
}
bool Isolate::IsAnyInitialArrayPrototype(Handle<JSArray> array) { bool Isolate::IsAnyInitialArrayPrototype(Handle<JSArray> array) {
DisallowHeapAllocation no_gc; DisallowHeapAllocation no_gc;
return IsInAnyContext(*array, Context::INITIAL_ARRAY_PROTOTYPE_INDEX); return IsInAnyContext(*array, Context::INITIAL_ARRAY_PROTOTYPE_INDEX);
......
...@@ -1106,6 +1106,10 @@ class Isolate { ...@@ -1106,6 +1106,10 @@ class Isolate {
// active. // active.
bool IsPromiseHookProtectorIntact(); bool IsPromiseHookProtectorIntact();
// Make sure a lookup of "then" on any JSPromise whose [[Prototype]] is the
// initial %PromisePrototype% yields the initial method.
bool IsPromiseThenLookupChainIntact();
// On intent to set an element in object, make sure that appropriate // On intent to set an element in object, make sure that appropriate
// notifications occur if the set is on the elements of the array or // notifications occur if the set is on the elements of the array or
// object prototype. Also ensure that changes to prototype chain between // object prototype. Also ensure that changes to prototype chain between
...@@ -1127,6 +1131,7 @@ class Isolate { ...@@ -1127,6 +1131,7 @@ class Isolate {
void InvalidateArrayIteratorProtector(); void InvalidateArrayIteratorProtector();
void InvalidateArrayBufferNeuteringProtector(); void InvalidateArrayBufferNeuteringProtector();
void InvalidatePromiseHookProtector(); void InvalidatePromiseHookProtector();
void InvalidatePromiseThenProtector();
// Returns true if array is the initial array prototype in any native context. // Returns true if array is the initial array prototype in any native context.
bool IsAnyInitialArrayPrototype(Handle<JSArray> array); bool IsAnyInitialArrayPrototype(Handle<JSArray> array);
......
...@@ -306,6 +306,14 @@ void LookupIterator::InternalUpdateProtector() { ...@@ -306,6 +306,14 @@ void LookupIterator::InternalUpdateProtector() {
if (holder_->IsJSArray()) { if (holder_->IsJSArray()) {
isolate_->InvalidateArrayIteratorProtector(); isolate_->InvalidateArrayIteratorProtector();
} }
} else if (*name_ == heap()->then_string()) {
if (!isolate_->IsPromiseThenLookupChainIntact()) return;
// Setting the "then" property on any JSPromise instance or on the
// initial %PromisePrototype% invalidates the Promise#then protector.
if (holder_->IsJSPromise() ||
isolate_->IsInAnyContext(*holder_, Context::PROMISE_PROTOTYPE_INDEX)) {
isolate_->InvalidatePromiseThenProtector();
}
} }
} }
......
...@@ -275,11 +275,12 @@ class V8_EXPORT_PRIVATE LookupIterator final BASE_EMBEDDED { ...@@ -275,11 +275,12 @@ class V8_EXPORT_PRIVATE LookupIterator final BASE_EMBEDDED {
inline void UpdateProtector() { inline void UpdateProtector() {
if (IsElement()) return; if (IsElement()) return;
// This list must be kept in sync with // This list must be kept in sync with
// CodeStubAssembler::HasAssociatedProtector! // CodeStubAssembler::CheckForAssociatedProtector!
if (*name_ == heap()->is_concat_spreadable_symbol() || if (*name_ == heap()->is_concat_spreadable_symbol() ||
*name_ == heap()->constructor_string() || *name_ == heap()->constructor_string() ||
*name_ == heap()->species_symbol() || *name_ == heap()->species_symbol() ||
*name_ == heap()->iterator_symbol()) { *name_ == heap()->iterator_symbol() ||
*name_ == heap()->then_string()) {
InternalUpdateProtector(); InternalUpdateProtector();
} }
} }
......
...@@ -112,8 +112,6 @@ TEST(ContextMaps) { ...@@ -112,8 +112,6 @@ TEST(ContextMaps) {
Context::STRING_FUNCTION_INDEX); Context::STRING_FUNCTION_INDEX);
VerifyStoredPrototypeMap(isolate, Context::REGEXP_PROTOTYPE_MAP_INDEX, VerifyStoredPrototypeMap(isolate, Context::REGEXP_PROTOTYPE_MAP_INDEX,
Context::REGEXP_FUNCTION_INDEX); Context::REGEXP_FUNCTION_INDEX);
VerifyStoredPrototypeMap(isolate, Context::PROMISE_PROTOTYPE_MAP_INDEX,
Context::PROMISE_FUNCTION_INDEX);
} }
TEST(InitialObjects) { TEST(InitialObjects) {
......
// Copyright 2018 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.
// Flags: --allow-natives-syntax
function foo(p) { return p.catch(x => x); }
const a = Promise.resolve(1);
foo(a);
foo(a);
%OptimizeFunctionOnNextCall(foo);
foo(a);
let custom_then_called = false;
a.__proto__.then = function() { custom_then_called = true; }
foo(a);
assertTrue(custom_then_called);
// Copyright 2018 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.
// Flags: --allow-natives-syntax
function foo(p) { return p.catch(x => x); }
const a = Promise.resolve(1);
foo(a);
foo(a);
%OptimizeFunctionOnNextCall(foo);
foo(a);
let custom_then_called = false;
a.then = function() { custom_then_called = true; }
foo(a);
assertTrue(custom_then_called);
// Copyright 2018 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.
// Flags: --allow-natives-syntax
let custom_then_called = false;
function foo(p) {
custom_then_called = false;
p.catch(x => x);
return custom_then_called;
}
class MyPromise extends Promise {
then(onFulfilled, onRejected) {
custom_then_called = true;
return super.then(onFulfilled, onRejected);
}
}
const a = MyPromise.resolve(1);
assertTrue(foo(a));
assertTrue(foo(a));
%OptimizeFunctionOnNextCall(foo);
assertTrue(foo(a));
...@@ -337,9 +337,9 @@ KNOWN_OBJECTS = { ...@@ -337,9 +337,9 @@ KNOWN_OBJECTS = {
("OLD_SPACE", 0x029d9): "FastArrayIterationProtector", ("OLD_SPACE", 0x029d9): "FastArrayIterationProtector",
("OLD_SPACE", 0x029e9): "ArrayIteratorProtector", ("OLD_SPACE", 0x029e9): "ArrayIteratorProtector",
("OLD_SPACE", 0x02a11): "ArrayBufferNeuteringProtector", ("OLD_SPACE", 0x02a11): "ArrayBufferNeuteringProtector",
("OLD_SPACE", 0x02a61): "InfinityValue", ("OLD_SPACE", 0x02a89): "InfinityValue",
("OLD_SPACE", 0x02a71): "MinusZeroValue", ("OLD_SPACE", 0x02a99): "MinusZeroValue",
("OLD_SPACE", 0x02a81): "MinusInfinityValue", ("OLD_SPACE", 0x02aa9): "MinusInfinityValue",
} }
# List of known V8 Frame Markers. # 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