Commit 79e91f0c authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[builtins] Extend the @@species protector to guard Promises.

Use this in the PromiseThen operation to skip the (expensive) lookup in
the SpeciesConstructor operation. This yields in a nice 3-5% improvement
on the bluebird and wikipedia benchmarks, and paves the way for inlining
certain Promise operations into TurboFan optimized code later.

On the micro-benchmark mentioned in the bug (from the findings doc), we
reduce the overall execution time by 25%, which makes sense given that
Promise.prototype.then spends a significant portion of it's time just
figuring out the appropriate constructor.

Bug: v8:7253, v8:7349
Change-Id: Ia1577b59d1b7e4b8dbda83e2186583edab76695a
Reviewed-on: https://chromium-review.googlesource.com/880681Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#50794}
parent d5f7d5c4
......@@ -2397,6 +2397,7 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
// Setup %PromisePrototype%.
Handle<JSObject> prototype(
JSObject::cast(promise_fun->instance_prototype()));
native_context()->set_promise_prototype(*prototype);
// Install the @@toStringTag property on the {prototype}.
JSObject::AddProperty(
......
......@@ -251,7 +251,7 @@ BUILTIN(ArraySlice) {
JSArray* array = JSArray::cast(*receiver);
if (V8_UNLIKELY(!array->HasFastElements() ||
!IsJSArrayFastElementMovingAllowed(isolate, array) ||
!isolate->IsArraySpeciesLookupChainIntact() ||
!isolate->IsSpeciesLookupChainIntact() ||
// If this is a subclass of Array, then call out to JS
!array->HasArrayPrototype(isolate))) {
AllowHeapAllocation allow_allocation;
......@@ -316,7 +316,7 @@ BUILTIN(ArraySplice) {
// If this is a subclass of Array, then call out to JS.
!Handle<JSArray>::cast(receiver)->HasArrayPrototype(isolate) ||
// If anything with @@species has been messed with, call out to JS.
!isolate->IsArraySpeciesLookupChainIntact())) {
!isolate->IsSpeciesLookupChainIntact())) {
return CallJsIntrinsic(isolate, isolate->array_splice(), args);
}
Handle<JSArray> array = Handle<JSArray>::cast(receiver);
......@@ -1186,7 +1186,7 @@ BUILTIN(ArrayConcat) {
// Avoid a real species read to avoid extra lookups to the array constructor
if (V8_LIKELY(receiver->IsJSArray() &&
Handle<JSArray>::cast(receiver)->HasArrayPrototype(isolate) &&
isolate->IsArraySpeciesLookupChainIntact())) {
isolate->IsSpeciesLookupChainIntact())) {
if (Fast_ArrayConcat(isolate, &args).ToHandle(&result_array)) {
return *result_array;
}
......
......@@ -286,30 +286,36 @@ Node* PromiseBuiltinsAssembler::InternalPromiseThen(Node* context,
Node* promise,
Node* on_resolve,
Node* on_reject) {
Isolate* isolate = this->isolate();
// 2. If IsPromise(promise) is false, throw a TypeError exception.
ThrowIfNotInstanceType(context, promise, JS_PROMISE_TYPE,
"Promise.prototype.then");
// 3. Let C be ? SpeciesConstructor(promise, %Promise%).
Label fast_promise_capability(this), slow_constructor(this, Label::kDeferred),
slow_promise_capability(this, Label::kDeferred);
Node* const native_context = LoadNativeContext(context);
Node* const promise_fun =
LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
// 3. Let C be ? SpeciesConstructor(promise, %Promise%).
Node* constructor = SpeciesConstructor(context, promise, promise_fun);
Node* const promise_prototype =
LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_INDEX);
Node* const promise_map = LoadMap(promise);
GotoIfNot(WordEqual(LoadMapPrototype(promise_map), promise_prototype),
&slow_constructor);
Branch(IsSpeciesProtectorCellInvalid(), &slow_constructor,
&fast_promise_capability);
BIND(&slow_constructor);
Node* const constructor =
SpeciesConstructor(native_context, promise, promise_fun);
Branch(WordEqual(constructor, promise_fun), &fast_promise_capability,
&slow_promise_capability);
// 4. Let resultCapability be ? NewPromiseCapability(C).
Callable call_callable = CodeFactory::Call(isolate);
Label fast_promise_capability(this), promise_capability(this),
perform_promise_then(this);
Label perform_promise_then(this);
VARIABLE(var_deferred_promise, MachineRepresentation::kTagged);
VARIABLE(var_deferred_on_resolve, MachineRepresentation::kTagged);
VARIABLE(var_deferred_on_reject, MachineRepresentation::kTagged);
Branch(WordEqual(promise_fun, constructor), &fast_promise_capability,
&promise_capability);
BIND(&fast_promise_capability);
{
Node* const deferred_promise = AllocateAndInitJSPromise(context, promise);
......@@ -319,7 +325,7 @@ Node* PromiseBuiltinsAssembler::InternalPromiseThen(Node* context,
Goto(&perform_promise_then);
}
BIND(&promise_capability);
BIND(&slow_promise_capability);
{
Node* const capability = NewPromiseCapability(context, constructor);
var_deferred_promise.Bind(
......
......@@ -1286,7 +1286,7 @@ Reduction JSCallReducer::ReduceArrayMap(Handle<JSFunction> function,
if (result == NodeProperties::kNoReceiverMaps) return NoChange();
// Ensure that any changes to the Array species constructor cause deopt.
if (!isolate()->IsArraySpeciesLookupChainIntact()) return NoChange();
if (!isolate()->IsSpeciesLookupChainIntact()) return NoChange();
const ElementsKind kind = receiver_maps[0]->elements_kind();
......@@ -1486,7 +1486,7 @@ Reduction JSCallReducer::ReduceArrayFilter(Handle<JSFunction> function,
if (result == NodeProperties::kNoReceiverMaps) return NoChange();
// And ensure that any changes to the Array species constructor cause deopt.
if (!isolate()->IsArraySpeciesLookupChainIntact()) return NoChange();
if (!isolate()->IsSpeciesLookupChainIntact()) return NoChange();
const ElementsKind kind = receiver_maps[0]->elements_kind();
// The output array is packed (filter doesn't visit holes).
......@@ -2079,7 +2079,7 @@ Reduction JSCallReducer::ReduceArrayEvery(Handle<JSFunction> function,
if (result == NodeProperties::kNoReceiverMaps) return NoChange();
// And ensure that any changes to the Array species constructor cause deopt.
if (!isolate()->IsArraySpeciesLookupChainIntact()) return NoChange();
if (!isolate()->IsSpeciesLookupChainIntact()) return NoChange();
const ElementsKind kind = receiver_maps[0]->elements_kind();
......@@ -2299,7 +2299,7 @@ Reduction JSCallReducer::ReduceArraySome(Handle<JSFunction> function,
if (result == NodeProperties::kNoReceiverMaps) return NoChange();
// And ensure that any changes to the Array species constructor cause deopt.
if (!isolate()->IsArraySpeciesLookupChainIntact()) return NoChange();
if (!isolate()->IsSpeciesLookupChainIntact()) return NoChange();
if (receiver_maps.size() == 0) return NoChange();
......
......@@ -338,6 +338,7 @@ enum ContextLookupFlags {
promise_thrower_finally_shared_fun) \
V(PROMISE_ALL_RESOLVE_ELEMENT_SHARED_FUN, SharedFunctionInfo, \
promise_all_resolve_element_shared_fun) \
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_FUNCTION_INDEX, JSFunction, regexp_function) \
......
......@@ -131,7 +131,7 @@ bool Isolate::IsArrayConstructorIntact() {
return array_constructor_cell->value() == Smi::FromInt(kProtectorValid);
}
bool Isolate::IsArraySpeciesLookupChainIntact() {
bool Isolate::IsSpeciesLookupChainIntact() {
// Note: It would be nice to have debug checks to make sure that the
// species protector is accurate, but this would be hard to do for most of
// what the protector stands for:
......
......@@ -3394,11 +3394,11 @@ void Isolate::InvalidateArrayConstructorProtector() {
DCHECK(!IsArrayConstructorIntact());
}
void Isolate::InvalidateArraySpeciesProtector() {
void Isolate::InvalidateSpeciesProtector() {
DCHECK(factory()->species_protector()->value()->IsSmi());
DCHECK(IsArraySpeciesLookupChainIntact());
DCHECK(IsSpeciesLookupChainIntact());
factory()->species_protector()->set_value(Smi::FromInt(kProtectorInvalid));
DCHECK(!IsArraySpeciesLookupChainIntact());
DCHECK(!IsSpeciesLookupChainIntact());
}
void Isolate::InvalidateStringLengthOverflowProtector() {
......
......@@ -1104,7 +1104,7 @@ class Isolate {
bool IsNoElementsProtectorIntact(Context* context);
bool IsNoElementsProtectorIntact();
inline bool IsArraySpeciesLookupChainIntact();
inline bool IsSpeciesLookupChainIntact();
bool IsIsConcatSpreadableLookupChainIntact();
bool IsIsConcatSpreadableLookupChainIntact(JSReceiver* receiver);
inline bool IsStringLengthOverflowIntact();
......@@ -1131,7 +1131,7 @@ class Isolate {
UpdateNoElementsProtectorOnSetElement(object);
}
void InvalidateArrayConstructorProtector();
void InvalidateArraySpeciesProtector();
void InvalidateSpeciesProtector();
void InvalidateIsConcatSpreadableProtector();
void InvalidateStringLengthOverflowProtector();
void InvalidateArrayIteratorProtector();
......
......@@ -238,6 +238,7 @@ void LookupIterator::ReloadPropertyInformation() {
}
namespace {
bool IsTypedArrayFunctionInAnyContext(Isolate* isolate, JSReceiver* holder) {
static uint32_t context_slots[] = {
#define TYPED_ARRAY_CONTEXT_SLOTS(Type, type, TYPE, ctype, size) \
......@@ -253,43 +254,49 @@ bool IsTypedArrayFunctionInAnyContext(Isolate* isolate, JSReceiver* holder) {
std::begin(context_slots), std::end(context_slots),
[=](uint32_t slot) { return isolate->IsInAnyContext(holder, slot); });
}
} // namespace
void LookupIterator::InternalUpdateProtector() {
if (isolate_->bootstrapper()->IsActive()) return;
if (*name_ == heap()->constructor_string()) {
if (!isolate_->IsArraySpeciesLookupChainIntact()) return;
if (!isolate_->IsSpeciesLookupChainIntact()) return;
// Setting the constructor property could change an instance's @@species
if (holder_->IsJSArray() || holder_->IsJSTypedArray()) {
if (holder_->IsJSArray() || holder_->IsJSPromise() ||
holder_->IsJSTypedArray()) {
isolate_->CountUsage(
v8::Isolate::UseCounterFeature::kArrayInstanceConstructorModified);
isolate_->InvalidateArraySpeciesProtector();
isolate_->InvalidateSpeciesProtector();
} else if (holder_->map()->is_prototype_map()) {
DisallowHeapAllocation no_gc;
// Setting the constructor of Array.prototype or %TypedArray%.prototype of
// any realm also needs to invalidate the species protector.
// Setting the constructor of Array.prototype, Promise.prototype or
// %TypedArray%.prototype of any realm also needs to invalidate the
// @@species protector.
// For typed arrays, we check a prototype of this holder since TypedArrays
// have different prototypes for each type, and their parent prototype is
// pointing the same TYPED_ARRAY_PROTOTYPE.
if (isolate_->IsInAnyContext(*holder_,
Context::INITIAL_ARRAY_PROTOTYPE_INDEX) ||
isolate_->IsInAnyContext(*holder_,
Context::PROMISE_PROTOTYPE_INDEX) ||
isolate_->IsInAnyContext(holder_->map()->prototype(),
Context::TYPED_ARRAY_PROTOTYPE_INDEX)) {
isolate_->CountUsage(v8::Isolate::UseCounterFeature::
kArrayPrototypeConstructorModified);
isolate_->InvalidateArraySpeciesProtector();
isolate_->InvalidateSpeciesProtector();
}
}
} else if (*name_ == heap()->species_symbol()) {
if (!isolate_->IsArraySpeciesLookupChainIntact()) return;
// Setting the Symbol.species property of any Array or TypedArray
// constructor invalidates the species protector
if (!isolate_->IsSpeciesLookupChainIntact()) return;
// Setting the Symbol.species property of any Array, Promise or TypedArray
// constructor invalidates the @@species protector
if (isolate_->IsInAnyContext(*holder_, Context::ARRAY_FUNCTION_INDEX) ||
isolate_->IsInAnyContext(*holder_, Context::PROMISE_FUNCTION_INDEX) ||
IsTypedArrayFunctionInAnyContext(isolate_, *holder_)) {
isolate_->CountUsage(
v8::Isolate::UseCounterFeature::kArraySpeciesModified);
isolate_->InvalidateArraySpeciesProtector();
isolate_->InvalidateSpeciesProtector();
}
} else if (*name_ == heap()->is_concat_spreadable_symbol()) {
if (!isolate_->IsIsConcatSpreadableLookupChainIntact()) return;
......
......@@ -2482,7 +2482,7 @@ MaybeHandle<Object> Object::ArraySpeciesConstructor(
Handle<Object> default_species = isolate->array_function();
if (original_array->IsJSArray() &&
Handle<JSArray>::cast(original_array)->HasArrayPrototype(isolate) &&
isolate->IsArraySpeciesLookupChainIntact()) {
isolate->IsSpeciesLookupChainIntact()) {
return default_species;
}
Handle<Object> constructor = isolate->factory()->undefined_value();
......@@ -16704,7 +16704,7 @@ MaybeHandle<JSTypedArray> JSTypedArray::SpeciesCreate(
// 3. Let constructor be ? SpeciesConstructor(exemplar, defaultConstructor).
Handle<Object> ctor = default_ctor;
if (!exemplar->HasJSTypedArrayPrototype(isolate) ||
!isolate->IsArraySpeciesLookupChainIntact()) {
!isolate->IsSpeciesLookupChainIntact()) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, ctor,
Object::SpeciesConstructor(isolate, exemplar, default_ctor),
......
......@@ -390,7 +390,7 @@ RUNTIME_FUNCTION(Runtime_TrySliceSimpleNonFastElements) {
// implementation.
if (receiver->IsJSArray()) {
// This "fastish" path must make sure the destination array is a JSArray.
if (!isolate->IsArraySpeciesLookupChainIntact() ||
if (!isolate->IsSpeciesLookupChainIntact() ||
!JSArray::cast(*receiver)->HasArrayPrototype(isolate)) {
return Smi::FromInt(0);
}
......
......@@ -999,7 +999,7 @@ TYPED_ARRAYS(FIXED_TYPED_ARRAYS_CHECK_RUNTIME_FUNCTION)
RUNTIME_FUNCTION(Runtime_SpeciesProtector) {
SealHandleScope shs(isolate);
DCHECK_EQ(0, args.length());
return isolate->heap()->ToBoolean(isolate->IsArraySpeciesLookupChainIntact());
return isolate->heap()->ToBoolean(isolate->IsSpeciesLookupChainIntact());
}
// Take a compiled wasm module, serialize it and copy the buffer into an array
......
......@@ -114,12 +114,12 @@ void TestSpeciesProtector(char* code,
v8::internal::Isolate* i_isolate =
reinterpret_cast<v8::internal::Isolate*>(isolate);
CHECK(i_isolate->IsArraySpeciesLookupChainIntact());
CHECK(i_isolate->IsSpeciesLookupChainIntact());
CompileRun(code);
if (invalidates_species_protector) {
CHECK(!i_isolate->IsArraySpeciesLookupChainIntact());
CHECK(!i_isolate->IsSpeciesLookupChainIntact());
} else {
CHECK(i_isolate->IsArraySpeciesLookupChainIntact());
CHECK(i_isolate->IsSpeciesLookupChainIntact());
}
v8::Local<v8::Value> my_typed_array = CompileRun("MyTypedArray");
......
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