[builtins] Port EnqueueMicrotask to CSA.

Previously the Promise builtins would always use a runtime function to
schedule a new microtask, which is unnecessarily expensive. Since the
runtime function only adds the microtask to a FixedArray (potentially
growing that array) and increments the number of pending microtasks, it
is fairly straight-forward to do this in CSA land instead.

This change improves the Bluebird benchmarks by 2-4% on average.

Bug: v8:7253
Change-Id: I77e96b9e5afbb4bdbe129b6bb289d9905ed581bf
Reviewed-on: https://chromium-review.googlesource.com/851972
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: Yang Guo <yangguo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#50372}
This commit is contained in:
Benedikt Meurer 2018-01-05 08:55:08 +01:00 committed by Commit Bot
parent 204441b230
commit 2b4cc835f1
7 changed files with 120 additions and 75 deletions

View File

@ -616,12 +616,6 @@ class InternalBuiltinsAssembler : public CodeStubAssembler {
explicit InternalBuiltinsAssembler(compiler::CodeAssemblerState* state)
: CodeStubAssembler(state) {}
TNode<IntPtrT> GetPendingMicrotaskCount();
void SetPendingMicrotaskCount(TNode<IntPtrT> count);
TNode<FixedArray> GetMicrotaskQueue();
void SetMicrotaskQueue(TNode<FixedArray> queue);
TNode<Context> GetCurrentContext();
void SetCurrentContext(TNode<Context> context);
@ -651,39 +645,6 @@ class InternalBuiltinsAssembler : public CodeStubAssembler {
}
};
TNode<IntPtrT> InternalBuiltinsAssembler::GetPendingMicrotaskCount() {
auto ref = ExternalReference::pending_microtask_count_address(isolate());
if (kIntSize == 8) {
return TNode<IntPtrT>::UncheckedCast(
Load(MachineType::Int64(), ExternalConstant(ref)));
} else {
Node* const value = Load(MachineType::Int32(), ExternalConstant(ref));
return ChangeInt32ToIntPtr(value);
}
}
void InternalBuiltinsAssembler::SetPendingMicrotaskCount(TNode<IntPtrT> count) {
auto ref = ExternalReference::pending_microtask_count_address(isolate());
auto rep = kIntSize == 8 ? MachineRepresentation::kWord64
: MachineRepresentation::kWord32;
if (kIntSize == 4 && kPointerSize == 8) {
Node* const truncated_count =
TruncateInt64ToInt32(TNode<Int64T>::UncheckedCast(count));
StoreNoWriteBarrier(rep, ExternalConstant(ref), truncated_count);
} else {
StoreNoWriteBarrier(rep, ExternalConstant(ref), count);
}
}
TNode<FixedArray> InternalBuiltinsAssembler::GetMicrotaskQueue() {
return TNode<FixedArray>::UncheckedCast(
LoadRoot(Heap::kMicrotaskQueueRootIndex));
}
void InternalBuiltinsAssembler::SetMicrotaskQueue(TNode<FixedArray> queue) {
StoreRoot(Heap::kMicrotaskQueueRootIndex, queue);
}
TNode<Context> InternalBuiltinsAssembler::GetCurrentContext() {
auto ref = ExternalReference(kContextAddress, isolate());
return TNode<Context>::UncheckedCast(

View File

@ -547,8 +547,7 @@ Node* PromiseBuiltinsAssembler::InternalPerformPromiseThen(
Node* info = AllocatePromiseReactionJobInfo(
result, var_on_resolve.value(), deferred_promise, deferred_on_resolve,
deferred_on_reject, context);
// TODO(gsathya): Move this to TF
CallRuntime(Runtime::kEnqueuePromiseReactionJob, context, info);
EnqueueMicrotask(info);
Goto(&out);
BIND(&reject);
@ -567,8 +566,7 @@ Node* PromiseBuiltinsAssembler::InternalPerformPromiseThen(
Node* info = AllocatePromiseReactionJobInfo(
result, var_on_reject.value(), deferred_promise,
deferred_on_resolve, deferred_on_reject, context);
// TODO(gsathya): Move this to TF
CallRuntime(Runtime::kEnqueuePromiseReactionJob, context, info);
EnqueueMicrotask(info);
Goto(&out);
}
}
@ -776,8 +774,7 @@ void PromiseBuiltinsAssembler::InternalResolvePromise(Node* context,
// 12. Perform EnqueueJob("PromiseJobs",
// PromiseResolveThenableJob, « promise, resolution, thenAction»).
BIND(&enqueue);
// TODO(gsathya): Move this to TF
CallRuntime(Runtime::kEnqueuePromiseResolveThenableJob, context, info);
EnqueueMicrotask(info);
Goto(&out);
}
@ -835,7 +832,7 @@ void PromiseBuiltinsAssembler::PromiseFulfill(
result, tasks, deferred_promise, deferred_on_resolve, deferred_on_reject,
context);
CallRuntime(Runtime::kEnqueuePromiseReactionJob, context, info);
EnqueueMicrotask(info);
Goto(&do_promisereset);
BIND(&do_promisereset);
@ -988,6 +985,69 @@ void PromiseBuiltinsAssembler::PerformFulfillClosure(Node* context, Node* value,
BIND(&out);
}
void PromiseBuiltinsAssembler::EnqueueMicrotask(Node* microtask) {
TNode<IntPtrT> num_tasks = GetPendingMicrotaskCount();
TNode<IntPtrT> new_num_tasks = IntPtrAdd(num_tasks, IntPtrConstant(1));
TNode<FixedArray> queue = GetMicrotaskQueue();
TNode<IntPtrT> queue_length = LoadAndUntagFixedArrayBaseLength(queue);
Label if_append(this), if_grow(this), done(this);
Branch(WordEqual(num_tasks, queue_length), &if_grow, &if_append);
BIND(&if_grow);
{
// Determine the new queue length and check if we need to allocate
// in large object space (instead of just going to new space, where
// we also know that we don't need any write barriers for setting
// up the new queue object).
Label if_newspace(this), if_lospace(this, Label::kDeferred);
TNode<IntPtrT> new_queue_length =
IntPtrMax(IntPtrConstant(8), IntPtrAdd(num_tasks, num_tasks));
Branch(IntPtrLessThanOrEqual(new_queue_length,
IntPtrConstant(FixedArray::kMaxRegularLength)),
&if_newspace, &if_lospace);
BIND(&if_newspace);
{
// This is the likely case where the new queue fits into new space,
// and thus we don't need any write barriers for initializing it.
TNode<FixedArray> new_queue =
CAST(AllocateFixedArray(PACKED_ELEMENTS, new_queue_length));
CopyFixedArrayElements(PACKED_ELEMENTS, queue, new_queue, num_tasks,
SKIP_WRITE_BARRIER);
StoreFixedArrayElement(new_queue, num_tasks, microtask,
SKIP_WRITE_BARRIER);
FillFixedArrayWithValue(PACKED_ELEMENTS, new_queue, new_num_tasks,
new_queue_length, Heap::kUndefinedValueRootIndex);
SetMicrotaskQueue(new_queue);
Goto(&done);
}
BIND(&if_lospace);
{
// The fallback case where the new queue ends up in large object space.
TNode<FixedArray> new_queue = CAST(AllocateFixedArray(
PACKED_ELEMENTS, new_queue_length, INTPTR_PARAMETERS,
AllocationFlag::kAllowLargeObjectAllocation));
CopyFixedArrayElements(PACKED_ELEMENTS, queue, new_queue, num_tasks);
StoreFixedArrayElement(new_queue, num_tasks, microtask);
FillFixedArrayWithValue(PACKED_ELEMENTS, new_queue, new_num_tasks,
new_queue_length, Heap::kUndefinedValueRootIndex);
SetMicrotaskQueue(new_queue);
Goto(&done);
}
}
BIND(&if_append);
{
StoreFixedArrayElement(queue, num_tasks, microtask);
Goto(&done);
}
BIND(&done);
SetPendingMicrotaskCount(new_num_tasks);
}
// ES#sec-promise-reject-functions
// Promise Reject Functions
TF_BUILTIN(PromiseRejectClosure, PromiseBuiltinsAssembler) {

View File

@ -177,6 +177,8 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
Node* PromiseStatus(Node* promise);
void PerformFulfillClosure(Node* context, Node* value, bool should_resolve);
void EnqueueMicrotask(Node* microtask);
private:
Node* IsPromiseStatus(Node* actual, v8::Promise::PromiseState expected);
void PromiseSetStatus(Node* promise, v8::Promise::PromiseState status);

View File

@ -10522,6 +10522,39 @@ Node* CodeStubAssembler::AllocatePromiseReactionJobInfo(
return result;
}
TNode<IntPtrT> CodeStubAssembler::GetPendingMicrotaskCount() {
auto ref = ExternalReference::pending_microtask_count_address(isolate());
if (kIntSize == 8) {
return TNode<IntPtrT>::UncheckedCast(
Load(MachineType::Int64(), ExternalConstant(ref)));
} else {
Node* const value = Load(MachineType::Int32(), ExternalConstant(ref));
return ChangeInt32ToIntPtr(value);
}
}
void CodeStubAssembler::SetPendingMicrotaskCount(TNode<IntPtrT> count) {
auto ref = ExternalReference::pending_microtask_count_address(isolate());
auto rep = kIntSize == 8 ? MachineRepresentation::kWord64
: MachineRepresentation::kWord32;
if (kIntSize == 4 && kPointerSize == 8) {
Node* const truncated_count =
TruncateInt64ToInt32(TNode<Int64T>::UncheckedCast(count));
StoreNoWriteBarrier(rep, ExternalConstant(ref), truncated_count);
} else {
StoreNoWriteBarrier(rep, ExternalConstant(ref), count);
}
}
TNode<FixedArray> CodeStubAssembler::GetMicrotaskQueue() {
return TNode<FixedArray>::UncheckedCast(
LoadRoot(Heap::kMicrotaskQueueRootIndex));
}
void CodeStubAssembler::SetMicrotaskQueue(TNode<FixedArray> queue) {
StoreRoot(Heap::kMicrotaskQueueRootIndex, queue);
}
Node* CodeStubAssembler::MarkerIsFrameType(Node* marker_or_function,
StackFrame::Type frame_type) {
return WordEqual(marker_or_function,

View File

@ -1840,6 +1840,13 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
Node* deferred_on_resolve,
Node* deferred_on_reject, Node* context);
// Microtask helpers
TNode<IntPtrT> GetPendingMicrotaskCount();
void SetPendingMicrotaskCount(TNode<IntPtrT> count);
TNode<FixedArray> GetMicrotaskQueue();
void SetMicrotaskQueue(TNode<FixedArray> queue);
// Helpers for StackFrame markers.
Node* MarkerIsFrameType(Node* marker_or_function,
StackFrame::Type frame_type);

View File

@ -70,22 +70,6 @@ RUNTIME_FUNCTION(Runtime_PromiseRevokeReject) {
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_EnqueuePromiseReactionJob) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(PromiseReactionJobInfo, info, 0);
isolate->EnqueueMicrotask(info);
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_EnqueuePromiseResolveThenableJob) {
HandleScope scope(isolate);
DCHECK_EQ(args.length(), 1);
CONVERT_ARG_HANDLE_CHECKED(PromiseResolveThenableJobInfo, info, 0);
isolate->EnqueueMicrotask(info);
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_EnqueueMicrotask) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());

View File

@ -463,19 +463,17 @@ namespace internal {
F(GreaterThanOrEqual, 2, 1) \
F(InstanceOf, 2, 1)
#define FOR_EACH_INTRINSIC_PROMISE(F) \
F(EnqueueMicrotask, 1, 1) \
F(EnqueuePromiseReactionJob, 1, 1) \
F(EnqueuePromiseResolveThenableJob, 1, 1) \
F(PromiseHookInit, 2, 1) \
F(PromiseHookResolve, 1, 1) \
F(PromiseHookBefore, 1, 1) \
F(PromiseHookAfter, 1, 1) \
F(PromiseMarkAsHandled, 1, 1) \
F(PromiseRejectEventFromStack, 2, 1) \
F(PromiseRevokeReject, 1, 1) \
F(PromiseResult, 1, 1) \
F(PromiseStatus, 1, 1) \
#define FOR_EACH_INTRINSIC_PROMISE(F) \
F(EnqueueMicrotask, 1, 1) \
F(PromiseHookInit, 2, 1) \
F(PromiseHookResolve, 1, 1) \
F(PromiseHookBefore, 1, 1) \
F(PromiseHookAfter, 1, 1) \
F(PromiseMarkAsHandled, 1, 1) \
F(PromiseRejectEventFromStack, 2, 1) \
F(PromiseRevokeReject, 1, 1) \
F(PromiseResult, 1, 1) \
F(PromiseStatus, 1, 1) \
F(ReportPromiseReject, 2, 1)
#define FOR_EACH_INTRINSIC_PROXY(F) \