Commit 18d02b4f authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[turbofan] Reduce promise creation overhead in async functions

This adds a new operator JSCreatePromise, which currently allocates
a native JSPromise instance and initializes it to pending state.

In addition to that we introduce a new PromiseHookProtector, which
get's invalidated the first time someone enables the debugger or
installs a PromiseHook (via async_hooks for example). As long as
the protector is intact we lower AsyncFunctionPromiseCreate to
JSCreatePromise and AsyncFunctionPromiseRelease to a no-op in
optimized code.

This yields a speedup of roughly 33% on the benchmark mentioned
in the bug.

Bug: v8:7271, v8:7253
Change-Id: Ib5d219f2b6e052a7cc5e6ed5aa66dd3c8885a859
Reviewed-on: https://chromium-review.googlesource.com/883124
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarSathya Gunasekaran <gsathya@chromium.org>
Cr-Commit-Position: refs/heads/master@{#50849}
parent 4de2be99
......@@ -2964,6 +2964,10 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
return ReduceStringPrototypeStringAt(
simplified()->StringCodePointAt(), jsgraph()->UndefinedConstant(),
node);
case Builtins::kAsyncFunctionPromiseCreate:
return ReduceAsyncFunctionPromiseCreate(node);
case Builtins::kAsyncFunctionPromiseRelease:
return ReduceAsyncFunctionPromiseRelease(node);
default:
break;
}
......@@ -3917,6 +3921,38 @@ Reduction JSCallReducer::ReduceStringPrototypeStringAt(
return Replace(value);
}
Reduction JSCallReducer::ReduceAsyncFunctionPromiseCreate(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
Node* context = NodeProperties::GetContextInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
if (!isolate()->IsPromiseHookProtectorIntact()) return NoChange();
// Install a code dependency on the promise hook protector cell.
dependencies()->AssumePropertyCell(factory()->promise_hook_protector());
// Morph this {node} into a JSCreatePromise node.
RelaxControls(node);
node->ReplaceInput(0, context);
node->ReplaceInput(1, effect);
node->TrimInputCount(2);
NodeProperties::ChangeOp(node, javascript()->CreatePromise());
return Changed(node);
}
Reduction JSCallReducer::ReduceAsyncFunctionPromiseRelease(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
if (!isolate()->IsPromiseHookProtectorIntact()) return NoChange();
// Install a code dependency on the promise hook protector cell.
dependencies()->AssumePropertyCell(factory()->promise_hook_protector());
// The AsyncFunctionPromiseRelease builtin is a no-op as long as neither
// the debugger is active nor any promise hook has been installed (ever).
Node* value = jsgraph()->UndefinedConstant();
ReplaceWithValue(node, value);
return Replace(value);
}
Graph* JSCallReducer::graph() const { return jsgraph()->graph(); }
Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); }
......
......@@ -99,6 +99,8 @@ class JSCallReducer final : public AdvancedReducer {
Node* node);
Reduction ReduceStringPrototypeStringAt(
const Operator* string_access_operator, Node* default_return, Node* node);
Reduction ReduceAsyncFunctionPromiseCreate(Node* node);
Reduction ReduceAsyncFunctionPromiseRelease(Node* node);
Reduction ReduceSoftDeoptimize(Node* node, DeoptimizeReason reason);
......
......@@ -145,6 +145,8 @@ Reduction JSCreateLowering::Reduce(Node* node) {
return ReduceJSCreateIterResultObject(node);
case IrOpcode::kJSCreateKeyValueArray:
return ReduceJSCreateKeyValueArray(node);
case IrOpcode::kJSCreatePromise:
return ReduceJSCreatePromise(node);
case IrOpcode::kJSCreateLiteralArray:
case IrOpcode::kJSCreateLiteralObject:
return ReduceJSCreateLiteralArrayOrObject(node);
......@@ -998,6 +1000,44 @@ Reduction JSCreateLowering::ReduceJSCreateKeyValueArray(Node* node) {
return Changed(node);
}
Reduction JSCreateLowering::ReduceJSCreatePromise(Node* node) {
DCHECK_EQ(IrOpcode::kJSCreatePromise, node->opcode());
Node* effect = NodeProperties::GetEffectInput(node);
Handle<Map> promise_map(native_context()->promise_function()->initial_map());
AllocationBuilder a(jsgraph(), effect, graph()->start());
a.Allocate(promise_map->instance_size());
a.Store(AccessBuilder::ForMap(), promise_map);
a.Store(AccessBuilder::ForJSObjectPropertiesOrHash(),
jsgraph()->EmptyFixedArrayConstant());
a.Store(AccessBuilder::ForJSObjectElements(),
jsgraph()->EmptyFixedArrayConstant());
a.Store(AccessBuilder::ForJSObjectOffset(JSPromise::kResultOffset),
jsgraph()->UndefinedConstant());
a.Store(AccessBuilder::ForJSObjectOffset(JSPromise::kDeferredPromiseOffset),
jsgraph()->UndefinedConstant());
a.Store(AccessBuilder::ForJSObjectOffset(JSPromise::kDeferredOnResolveOffset),
jsgraph()->UndefinedConstant());
a.Store(AccessBuilder::ForJSObjectOffset(JSPromise::kDeferredOnRejectOffset),
jsgraph()->UndefinedConstant());
a.Store(AccessBuilder::ForJSObjectOffset(JSPromise::kFulfillReactionsOffset),
jsgraph()->UndefinedConstant());
a.Store(AccessBuilder::ForJSObjectOffset(JSPromise::kRejectReactionsOffset),
jsgraph()->UndefinedConstant());
STATIC_ASSERT(v8::Promise::kPending == 0);
a.Store(AccessBuilder::ForJSObjectOffset(JSPromise::kFlagsOffset),
jsgraph()->ZeroConstant());
STATIC_ASSERT(JSPromise::kSize == 10 * kPointerSize);
for (int i = 0; i < v8::Promise::kEmbedderFieldCount; ++i) {
a.Store(
AccessBuilder::ForJSObjectOffset(JSPromise::kSize + i * kPointerSize),
jsgraph()->ZeroConstant());
}
a.FinishAndChange(node);
return Changed(node);
}
Reduction JSCreateLowering::ReduceJSCreateLiteralArrayOrObject(Node* node) {
DCHECK(node->opcode() == IrOpcode::kJSCreateLiteralArray ||
node->opcode() == IrOpcode::kJSCreateLiteralObject);
......
......@@ -54,6 +54,7 @@ class V8_EXPORT_PRIVATE JSCreateLowering final
Reduction ReduceJSCreateClosure(Node* node);
Reduction ReduceJSCreateIterResultObject(Node* node);
Reduction ReduceJSCreateKeyValueArray(Node* node);
Reduction ReduceJSCreatePromise(Node* node);
Reduction ReduceJSCreateLiteralArrayOrObject(Node* node);
Reduction ReduceJSCreateEmptyLiteralObject(Node* node);
Reduction ReduceJSCreateEmptyLiteralArray(Node* node);
......
......@@ -424,6 +424,10 @@ void JSGenericLowering::LowerJSCreateKeyValueArray(Node* node) {
UNREACHABLE(); // Eliminated in typed lowering.
}
void JSGenericLowering::LowerJSCreatePromise(Node* node) {
UNREACHABLE(); // Eliminated in typed lowering.
}
void JSGenericLowering::LowerJSCreateLiteralArray(Node* node) {
CreateLiteralParameters const& p = CreateLiteralParametersOf(node->op());
CallDescriptor::Flags flags = FrameStateFlagForCall(node);
......
......@@ -569,6 +569,7 @@ CompareOperationHint CompareOperationHintOf(const Operator* op) {
V(Create, Operator::kNoProperties, 2, 1) \
V(CreateIterResultObject, Operator::kEliminatable, 2, 1) \
V(CreateKeyValueArray, Operator::kEliminatable, 2, 1) \
V(CreatePromise, Operator::kEliminatable, 0, 1) \
V(HasProperty, Operator::kNoProperties, 2, 1) \
V(HasInPrototypeChain, Operator::kNoProperties, 2, 1) \
V(OrdinaryHasInstance, Operator::kNoProperties, 2, 1) \
......
......@@ -656,6 +656,7 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
PretenureFlag pretenure);
const Operator* CreateIterResultObject();
const Operator* CreateKeyValueArray();
const Operator* CreatePromise();
const Operator* CreateLiteralArray(Handle<ConstantElementsPair> constant,
VectorSlotPair const& feedback,
int literal_flags, int number_of_elements);
......
......@@ -141,6 +141,7 @@
V(JSCreateGeneratorObject) \
V(JSCreateIterResultObject) \
V(JSCreateKeyValueArray) \
V(JSCreatePromise) \
V(JSCreateLiteralArray) \
V(JSCreateEmptyLiteralArray) \
V(JSCreateLiteralObject) \
......
......@@ -1233,6 +1233,10 @@ Type* Typer::Visitor::TypeJSCreateKeyValueArray(Node* node) {
return Type::OtherObject();
}
Type* Typer::Visitor::TypeJSCreatePromise(Node* node) {
return Type::OtherObject();
}
Type* Typer::Visitor::TypeJSCreateLiteralArray(Node* node) {
return Type::Array();
}
......
......@@ -681,6 +681,10 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
// Type is OtherObject.
CheckTypeIs(node, Type::OtherObject());
break;
case IrOpcode::kJSCreatePromise:
// Type is OtherObject.
CheckTypeIs(node, Type::OtherObject());
break;
case IrOpcode::kJSCreateLiteralArray:
// Type is Array.
CheckTypeIs(node, Type::Array());
......
......@@ -213,6 +213,7 @@ using v8::MemoryPressureLevel;
V(PropertyCell, array_iterator_protector, ArrayIteratorProtector) \
V(PropertyCell, array_buffer_neutering_protector, \
ArrayBufferNeuteringProtector) \
V(PropertyCell, promise_hook_protector, PromiseHookProtector) \
/* Special numbers */ \
V(HeapNumber, nan_value, NanValue) \
V(HeapNumber, hole_nan_value, HoleNanValue) \
......
......@@ -638,6 +638,10 @@ void Heap::CreateInitialObjects() {
cell->set_value(Smi::FromInt(Isolate::kProtectorValid));
set_array_buffer_neutering_protector(*cell);
cell = factory->NewPropertyCell(factory->empty_string());
cell->set_value(Smi::FromInt(Isolate::kProtectorValid));
set_promise_hook_protector(*cell);
set_serialized_objects(empty_fixed_array());
set_serialized_global_proxy_sizes(empty_fixed_array());
......
......@@ -3368,6 +3368,15 @@ bool Isolate::IsIsConcatSpreadableLookupChainIntact(JSReceiver* receiver) {
return !receiver->HasProxyInPrototype(this);
}
bool Isolate::IsPromiseHookProtectorIntact() {
PropertyCell* promise_hook_cell = heap()->promise_hook_protector();
bool is_promise_hook_protector_intact =
Smi::ToInt(promise_hook_cell->value()) == kProtectorValid;
DCHECK_IMPLIES(is_promise_hook_protector_intact,
!promise_hook_or_debug_is_active_);
return is_promise_hook_protector_intact;
}
void Isolate::UpdateNoElementsProtectorOnSetElement(Handle<JSObject> object) {
DisallowHeapAllocation no_gc;
if (!object->map()->is_prototype_map()) return;
......@@ -3427,6 +3436,15 @@ void Isolate::InvalidateArrayBufferNeuteringProtector() {
DCHECK(!IsArrayBufferNeuteringIntact());
}
void Isolate::InvalidatePromiseHookProtector() {
DCHECK(factory()->promise_hook_protector()->value()->IsSmi());
DCHECK(IsPromiseHookProtectorIntact());
PropertyCell::SetValueWithInvalidation(
factory()->promise_hook_protector(),
handle(Smi::FromInt(kProtectorInvalid), this));
DCHECK(!IsPromiseHookProtectorIntact());
}
bool Isolate::IsAnyInitialArrayPrototype(Handle<JSArray> array) {
DisallowHeapAllocation no_gc;
return IsInAnyContext(*array, Context::INITIAL_ARRAY_PROTOTYPE_INDEX);
......@@ -3588,7 +3606,11 @@ void Isolate::FireCallCompletedCallback() {
}
void Isolate::DebugStateUpdated() {
promise_hook_or_debug_is_active_ = promise_hook_ || debug()->is_active();
bool promise_hook_or_debug_is_active = promise_hook_ || debug()->is_active();
if (promise_hook_or_debug_is_active && IsPromiseHookProtectorIntact()) {
InvalidatePromiseHookProtector();
}
promise_hook_or_debug_is_active_ = promise_hook_or_debug_is_active;
}
namespace {
......
......@@ -1116,6 +1116,10 @@ class Isolate {
// Make sure we do check for neutered array buffers.
inline bool IsArrayBufferNeuteringIntact();
// Disable promise optimizations if promise (debug) hooks have ever been
// active.
bool IsPromiseHookProtectorIntact();
// 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
// object prototype. Also ensure that changes to prototype chain between
......@@ -1136,6 +1140,7 @@ class Isolate {
void InvalidateStringLengthOverflowProtector();
void InvalidateArrayIteratorProtector();
void InvalidateArrayBufferNeuteringProtector();
void InvalidatePromiseHookProtector();
// Returns true if array is the initial array prototype in any native context.
bool IsAnyInitialArrayPrototype(Handle<JSArray> array);
......
......@@ -4666,6 +4666,7 @@ TEST(NoHiddenProperties) {
TEST(SetDebugEventListenerOnUninitializedVM) {
v8::HandleScope scope(CcTest::isolate());
EnableDebugger(CcTest::isolate());
}
......
......@@ -326,9 +326,9 @@ KNOWN_OBJECTS = {
("OLD_SPACE", 0x029d9): "FastArrayIterationProtector",
("OLD_SPACE", 0x029e9): "ArrayIteratorProtector",
("OLD_SPACE", 0x02a11): "ArrayBufferNeuteringProtector",
("OLD_SPACE", 0x02a39): "InfinityValue",
("OLD_SPACE", 0x02a49): "MinusZeroValue",
("OLD_SPACE", 0x02a59): "MinusInfinityValue",
("OLD_SPACE", 0x02a61): "InfinityValue",
("OLD_SPACE", 0x02a71): "MinusZeroValue",
("OLD_SPACE", 0x02a81): "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