Commit 6ef05c78 authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[builtins] Turn EnqueueMicrotask into a dedicated builtin.

Inlining the EnqueueMicrotask logic into the various uses blows up the
snapshot size significantly. So instead of doing that we just turn the
operation into a dedicated builtin that we call from the various uses.
This still avoids the runtime function call overhead and maintains the
fast path without write barriers for the common case of the microtask
queue fitting into new space.

This also moves back the microtask helper CSA functions to the
specialized assembler.

Bug: v8:7253, chromium:799563
Change-Id: I2d24d0e5c01e442c5ad7f5d4373fbc6e94351ac5
Reviewed-on: https://chromium-review.googlesource.com/856618Reviewed-by: 's avatarSathya Gunasekaran <gsathya@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#50461}
parent b065ed47
......@@ -220,6 +220,7 @@ namespace internal {
TFS(RejectNativePromise, kPromise, kValue, kDebugEvent) \
TFS(PerformNativePromiseThen, kPromise, kResolveReaction, kRejectReaction, \
kResultPromise) \
TFS(EnqueueMicrotask, kMicrotask) \
TFC(RunMicrotasks, RunMicrotasks, 1) \
TFS(PromiseResolveThenableJob, kMicrotask) \
\
......
......@@ -616,6 +616,12 @@ 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);
......@@ -645,6 +651,39 @@ 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(
......@@ -706,6 +745,72 @@ void InternalBuiltinsAssembler::LeaveMicrotaskContext() {
}
}
TF_BUILTIN(EnqueueMicrotask, InternalBuiltinsAssembler) {
Node* microtask = Parameter(Descriptor::kMicrotask);
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);
Return(UndefinedConstant());
}
TF_BUILTIN(RunMicrotasks, InternalBuiltinsAssembler) {
Label init_queue_loop(this);
......
......@@ -528,7 +528,7 @@ Node* PromiseBuiltinsAssembler::InternalPerformPromiseThen(
Node* info = AllocatePromiseReactionJobInfo(
result, var_on_resolve.value(), deferred_promise, deferred_on_resolve,
deferred_on_reject, context);
EnqueueMicrotask(info);
CallBuiltin(Builtins::kEnqueueMicrotask, NoContextConstant(), info);
Goto(&out);
BIND(&reject);
......@@ -547,7 +547,7 @@ Node* PromiseBuiltinsAssembler::InternalPerformPromiseThen(
Node* info = AllocatePromiseReactionJobInfo(
result, var_on_reject.value(), deferred_promise,
deferred_on_resolve, deferred_on_reject, context);
EnqueueMicrotask(info);
CallBuiltin(Builtins::kEnqueueMicrotask, NoContextConstant(), info);
Goto(&out);
}
}
......@@ -755,7 +755,7 @@ void PromiseBuiltinsAssembler::InternalResolvePromise(Node* context,
// 12. Perform EnqueueJob("PromiseJobs",
// PromiseResolveThenableJob, « promise, resolution, thenAction»).
BIND(&enqueue);
EnqueueMicrotask(info);
CallBuiltin(Builtins::kEnqueueMicrotask, NoContextConstant(), info);
Goto(&out);
}
......@@ -813,7 +813,7 @@ void PromiseBuiltinsAssembler::PromiseFulfill(
result, tasks, deferred_promise, deferred_on_resolve, deferred_on_reject,
context);
EnqueueMicrotask(info);
CallBuiltin(Builtins::kEnqueueMicrotask, NoContextConstant(), info);
Goto(&do_promisereset);
BIND(&do_promisereset);
......@@ -966,69 +966,6 @@ 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) {
......
......@@ -176,8 +176,6 @@ 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);
......
......@@ -10558,39 +10558,6 @@ 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,
......
......@@ -1849,13 +1849,6 @@ 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);
......
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