[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: Sathya Gunasekaran <gsathya@chromium.org> Cr-Commit-Position: refs/heads/master@{#50849}
This commit is contained in:
parent
4de2be999d
commit
18d02b4fa9
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user