Commit c8da060b authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[builtins] Refactor the Microtask pumping to CSA-only.

This adjusts the RunMicrotask logic to invoke CallHandlerInfo microtasks
from CSA land directly (via a runtime function call), instead of bailing
out to C++ for the rest of the microtask queue entries. Even in simple
micro-benchmarks there doesn't seem to be a huge performance difference.
In fact performance get's better when CallHandlerInfo and promises are
mixed, which makes sense, since calling from C++ to JS land is more
expensive than the other way around.

But just in case the runtime function call overhead ever becomes the
bottleneck we can introduce a direct C++ call and setup a handle scope
around it, much like a very simple version of CallApiFunctionStub.

This greatly simplifies the microtask handling and paves the way for
refactoring the queue to significant reduce the GC overhead associated
with promises currently.

Bug: v8:7253
Change-Id: I33adb62a6bada138674d324f36d4be894e27f3c9
Cq-Include-Trybots: master.tryserver.chromium.linux:linux_chromium_rel_ng
Reviewed-on: https://chromium-review.googlesource.com/890441Reviewed-by: 's avatarMichael Starzinger <mstarzinger@chromium.org>
Reviewed-by: 's avatarSathya Gunasekaran <gsathya@chromium.org>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#50934}
parent 9a6c54fc
......@@ -2453,20 +2453,6 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
native_context()->set_promise_resolve(*function);
}
{ // Internal: PromiseHandle
Handle<JSFunction> function =
SimpleCreateFunction(isolate, factory->empty_string(),
Builtins::kPromiseHandleJS, 5, false);
native_context()->set_promise_handle(*function);
}
{ // Internal: PromiseHandleReject
Handle<JSFunction> function =
SimpleCreateFunction(isolate, factory->empty_string(),
Builtins::kPromiseHandleReject, 3, false);
native_context()->set_promise_handle_reject(*function);
}
{ // Internal: InternalPromiseReject
Handle<JSFunction> function =
SimpleCreateFunction(isolate, factory->empty_string(),
......
......@@ -819,8 +819,6 @@ namespace internal {
TFS(PromiseHandleReject, kPromise, kOnReject, kException) \
TFS(PromiseHandle, kValue, kHandler, kDeferredPromise, kDeferredOnResolve, \
kDeferredOnReject) \
TFJ(PromiseHandleJS, 5, kValue, kHandler, kDeferredPromise, \
kDeferredOnResolve, kDeferredOnReject) \
/* ES #sec-promise.resolve */ \
TFJ(PromiseResolveWrapper, 1, kValue) \
TFS(PromiseResolve, kConstructor, kValue) \
......
......@@ -812,8 +812,10 @@ TF_BUILTIN(EnqueueMicrotask, InternalBuiltinsAssembler) {
}
TF_BUILTIN(RunMicrotasks, InternalBuiltinsAssembler) {
Label init_queue_loop(this);
// Load the current context from the isolate.
TNode<Context> current_context = GetCurrentContext();
Label init_queue_loop(this);
Goto(&init_queue_loop);
BIND(&init_queue_loop);
{
......@@ -866,39 +868,36 @@ TF_BUILTIN(RunMicrotasks, InternalBuiltinsAssembler) {
BIND(&is_call_handler_info);
{
// Bailout to C++ slow path for the remainder of the loop.
auto index_ref =
ExternalReference(kMicrotaskQueueBailoutIndexAddress, isolate());
auto count_ref =
ExternalReference(kMicrotaskQueueBailoutCountAddress, isolate());
auto rep = kIntSize == 4 ? MachineRepresentation::kWord32
: MachineRepresentation::kWord64;
// index was pre-incremented, decrement for bailout to C++.
Node* value = IntPtrSub(index, IntPtrConstant(1));
if (kPointerSize == 4) {
DCHECK_EQ(kIntSize, 4);
StoreNoWriteBarrier(rep, ExternalConstant(index_ref), value);
StoreNoWriteBarrier(rep, ExternalConstant(count_ref), num_tasks);
} else {
Node* count = num_tasks;
if (kIntSize == 4) {
value = TruncateInt64ToInt32(value);
count = TruncateInt64ToInt32(count);
}
StoreNoWriteBarrier(rep, ExternalConstant(index_ref), value);
StoreNoWriteBarrier(rep, ExternalConstant(count_ref), count);
}
Return(queue);
Node* const microtask_callback =
LoadObjectField(microtask, CallHandlerInfo::kCallbackOffset);
Node* const microtask_data =
LoadObjectField(microtask, CallHandlerInfo::kDataOffset);
// If this turns out to become a bottleneck because of the calls
// to C++ via CEntryStub, we can choose to speed them up using a
// similar mechanism that we use for the CallApiFunction stub,
// except that calling the MicrotaskCallback is even easier, since
// it doesn't accept any tagged parameters, doesn't return a value
// and ignores exceptions.
//
// But from our current measurements it doesn't seem to be a
// serious performance problem, even if the microtask is full
// of CallHandlerInfo tasks (which is not a realistic use case
// anyways).
Label if_continue(this);
Node* const result =
CallRuntime(Runtime::kRunMicrotaskCallback, current_context,
microtask_callback, microtask_data);
GotoIfException(result, &if_continue);
Goto(&if_continue);
BIND(&if_continue);
Branch(IntPtrLessThan(index, num_tasks), &loop, &init_queue_loop);
}
BIND(&is_function);
{
Label cont(this);
VARIABLE(exception, MachineRepresentation::kTagged, TheHoleConstant());
TNode<Context> old_context = GetCurrentContext();
TNode<Context> fn_context = TNode<Context>::UncheckedCast(
LoadObjectField(microtask, JSFunction::kContextOffset));
TNode<Context> native_context =
......@@ -911,14 +910,13 @@ TF_BUILTIN(RunMicrotasks, InternalBuiltinsAssembler) {
Goto(&cont);
BIND(&cont);
LeaveMicrotaskContext();
SetCurrentContext(old_context);
SetCurrentContext(current_context);
Branch(IntPtrLessThan(index, num_tasks), &loop, &init_queue_loop);
}
BIND(&is_promise_resolve_thenable_job);
{
VARIABLE(exception, MachineRepresentation::kTagged, TheHoleConstant());
TNode<Context> old_context = GetCurrentContext();
TNode<Context> microtask_context =
TNode<Context>::UncheckedCast(LoadObjectField(
microtask, PromiseResolveThenableJobInfo::kContextOffset));
......@@ -939,7 +937,7 @@ TF_BUILTIN(RunMicrotasks, InternalBuiltinsAssembler) {
BIND(&done);
LeaveMicrotaskContext();
SetCurrentContext(old_context);
SetCurrentContext(current_context);
Branch(IntPtrLessThan(index, num_tasks), &loop, &init_queue_loop);
}
......@@ -960,7 +958,6 @@ TF_BUILTIN(RunMicrotasks, InternalBuiltinsAssembler) {
Node* const deferred_on_rejects = LoadObjectField(
microtask, PromiseReactionJobInfo::kDeferredOnRejectOffset);
TNode<Context> old_context = GetCurrentContext();
TNode<Context> microtask_context = TNode<Context>::UncheckedCast(
LoadObjectField(microtask, PromiseReactionJobInfo::kContextOffset));
TNode<Context> native_context =
......@@ -976,7 +973,7 @@ TF_BUILTIN(RunMicrotasks, InternalBuiltinsAssembler) {
deferred_promises, deferred_on_resolves,
deferred_on_rejects);
LeaveMicrotaskContext();
SetCurrentContext(old_context);
SetCurrentContext(current_context);
Branch(IntPtrLessThan(index, num_tasks), &loop, &init_queue_loop);
}
......@@ -1008,7 +1005,7 @@ TF_BUILTIN(RunMicrotasks, InternalBuiltinsAssembler) {
BIND(&done);
LeaveMicrotaskContext();
SetCurrentContext(old_context);
SetCurrentContext(current_context);
Branch(IntPtrLessThan(index, num_tasks), &loop, &init_queue_loop);
}
......
......@@ -1224,20 +1224,6 @@ TF_BUILTIN(PromiseHandle, PromiseBuiltinsAssembler) {
}
}
TF_BUILTIN(PromiseHandleJS, PromiseBuiltinsAssembler) {
Node* const value = Parameter(Descriptor::kValue);
Node* const handler = Parameter(Descriptor::kHandler);
Node* const deferred_promise = Parameter(Descriptor::kDeferredPromise);
Node* const deferred_on_resolve = Parameter(Descriptor::kDeferredOnResolve);
Node* const deferred_on_reject = Parameter(Descriptor::kDeferredOnReject);
Node* const context = Parameter(Descriptor::kContext);
Node* const result =
CallBuiltin(Builtins::kPromiseHandle, context, value, handler,
deferred_promise, deferred_on_resolve, deferred_on_reject);
Return(result);
}
// ES#sec-promise.prototype.catch
// Promise.prototype.catch ( onRejected )
TF_BUILTIN(PromisePrototypeCatch, PromiseBuiltinsAssembler) {
......
......@@ -76,8 +76,6 @@ enum ContextLookupFlags {
V(IS_PROMISE_INDEX, JSFunction, is_promise) \
V(PROMISE_RESOLVE_INDEX, JSFunction, promise_resolve) \
V(PROMISE_THEN_INDEX, JSFunction, promise_then) \
V(PROMISE_HANDLE_INDEX, JSFunction, promise_handle) \
V(PROMISE_HANDLE_REJECT_INDEX, JSFunction, promise_handle_reject) \
V(ASYNC_GENERATOR_AWAIT_CAUGHT, JSFunction, async_generator_await_caught) \
V(ASYNC_GENERATOR_AWAIT_UNCAUGHT, JSFunction, async_generator_await_uncaught)
......
......@@ -1482,9 +1482,7 @@ enum class ConcurrencyMode { kNotConcurrent, kConcurrent };
C(PendingHandlerFP, pending_handler_fp) \
C(PendingHandlerSP, pending_handler_sp) \
C(ExternalCaughtException, external_caught_exception) \
C(JSEntrySP, js_entry_sp) \
C(MicrotaskQueueBailoutIndex, microtask_queue_bailout_index) \
C(MicrotaskQueueBailoutCount, microtask_queue_bailout_count)
C(JSEntrySP, js_entry_sp)
enum IsolateAddressId {
#define DECLARE_ENUM(CamelName, hacker_name) k##CamelName##Address,
......
......@@ -110,8 +110,6 @@ void ThreadLocalTop::InitializeInternal() {
rethrowing_message_ = false;
pending_message_obj_ = nullptr;
scheduled_exception_ = nullptr;
microtask_queue_bailout_index_ = -1;
microtask_queue_bailout_count_ = 0;
}
......@@ -3720,69 +3718,6 @@ void Isolate::ReportPromiseReject(Handle<JSPromise> promise,
v8::Utils::StackTraceToLocal(stack_trace)));
}
void Isolate::PromiseReactionJob(Handle<PromiseReactionJobInfo> info,
MaybeHandle<Object>* result,
MaybeHandle<Object>* maybe_exception) {
Handle<Object> value(info->value(), this);
Handle<Object> tasks(info->tasks(), this);
Handle<JSFunction> promise_handle_fn = promise_handle();
Handle<Object> undefined = factory()->undefined_value();
Handle<Object> deferred_promise(info->deferred_promise(), this);
if (deferred_promise->IsFixedArray()) {
DCHECK(tasks->IsFixedArray());
Handle<FixedArray> deferred_promise_arr =
Handle<FixedArray>::cast(deferred_promise);
Handle<FixedArray> deferred_on_resolve_arr(
FixedArray::cast(info->deferred_on_resolve()), this);
Handle<FixedArray> deferred_on_reject_arr(
FixedArray::cast(info->deferred_on_reject()), this);
Handle<FixedArray> tasks_arr = Handle<FixedArray>::cast(tasks);
for (int i = 0; i < deferred_promise_arr->length(); i++) {
Handle<Object> argv[] = {value, handle(tasks_arr->get(i), this),
handle(deferred_promise_arr->get(i), this),
handle(deferred_on_resolve_arr->get(i), this),
handle(deferred_on_reject_arr->get(i), this)};
*result = Execution::TryCall(
this, promise_handle_fn, undefined, arraysize(argv), argv,
Execution::MessageHandling::kReport, maybe_exception);
// If execution is terminating, just bail out.
if (result->is_null() && maybe_exception->is_null()) {
return;
}
}
} else {
Handle<Object> argv[] = {value, tasks, deferred_promise,
handle(info->deferred_on_resolve(), this),
handle(info->deferred_on_reject(), this)};
*result = Execution::TryCall(
this, promise_handle_fn, undefined, arraysize(argv), argv,
Execution::MessageHandling::kReport, maybe_exception);
}
}
void Isolate::PromiseResolveThenableJob(
Handle<PromiseResolveThenableJobInfo> info, MaybeHandle<Object>* result,
MaybeHandle<Object>* maybe_exception) {
Handle<JSReceiver> thenable(info->thenable(), this);
Handle<JSFunction> resolve(info->resolve(), this);
Handle<JSFunction> reject(info->reject(), this);
Handle<JSReceiver> then(info->then(), this);
Handle<Object> argv[] = {resolve, reject};
*result =
Execution::TryCall(this, then, thenable, arraysize(argv), argv,
Execution::MessageHandling::kReport, maybe_exception);
Handle<Object> reason;
if (maybe_exception->ToHandle(&reason)) {
DCHECK(result->is_null());
Handle<Object> reason_arg[] = {reason};
*result = Execution::TryCall(
this, reject, factory()->undefined_value(), arraysize(reason_arg),
reason_arg, Execution::MessageHandling::kReport, maybe_exception);
}
}
void Isolate::EnqueueMicrotask(Handle<Object> microtask) {
DCHECK(microtask->IsJSFunction() || microtask->IsCallHandlerInfo() ||
microtask->IsPromiseResolveThenableJobInfo() ||
......@@ -3807,100 +3742,25 @@ void Isolate::RunMicrotasks() {
// Increase call depth to prevent recursive callbacks.
v8::Isolate::SuppressMicrotaskExecutionScope suppress(
reinterpret_cast<v8::Isolate*>(this));
is_running_microtasks_ = true;
RunMicrotasksInternal();
is_running_microtasks_ = false;
FireMicrotasksCompletedCallback();
}
void Isolate::RunMicrotasksInternal() {
if (!pending_microtask_count()) return;
TRACE_EVENT0("v8.execute", "RunMicrotasks");
TRACE_EVENT_CALL_STATS_SCOPED(this, "v8", "V8.RunMicrotasks");
if (pending_microtask_count()) {
is_running_microtasks_ = true;
TRACE_EVENT0("v8.execute", "RunMicrotasks");
TRACE_EVENT_CALL_STATS_SCOPED(this, "v8", "V8.RunMicrotasks");
do {
HandleScope handle_scope(this);
set_microtask_queue_bailout_index(-1);
set_microtask_queue_bailout_count(-1);
HandleScope scope(this);
MaybeHandle<Object> maybe_exception;
MaybeHandle<Object> maybe_result = Execution::RunMicrotasks(
this, Execution::MessageHandling::kReport, &maybe_exception);
// If execution is terminating, just bail out.
if (maybe_result.is_null() && maybe_exception.is_null()) {
heap()->set_microtask_queue(heap()->empty_fixed_array());
set_pending_microtask_count(0);
return;
}
Handle<Object> result = maybe_result.ToHandleChecked();
if (result->IsUndefined(this)) return;
Handle<FixedArray> queue = Handle<FixedArray>::cast(result);
int num_tasks = microtask_queue_bailout_count();
DCHECK_GE(microtask_queue_bailout_index(), 0);
Isolate* isolate = this;
FOR_WITH_HANDLE_SCOPE(
isolate, int, i = microtask_queue_bailout_index(), i, i < num_tasks,
i++, {
Handle<Object> microtask(queue->get(i), this);
if (microtask->IsCallHandlerInfo()) {
Handle<CallHandlerInfo> callback_info =
Handle<CallHandlerInfo>::cast(microtask);
v8::MicrotaskCallback callback =
v8::ToCData<v8::MicrotaskCallback>(callback_info->callback());
void* data = v8::ToCData<void*>(callback_info->data());
callback(data);
} else {
SaveContext save(this);
Context* context;
if (microtask->IsJSFunction()) {
context = Handle<JSFunction>::cast(microtask)->context();
} else if (microtask->IsPromiseResolveThenableJobInfo()) {
context = Handle<PromiseResolveThenableJobInfo>::cast(microtask)
->context();
} else {
context =
Handle<PromiseReactionJobInfo>::cast(microtask)->context();
}
set_context(context->native_context());
handle_scope_implementer_->EnterMicrotaskContext(
Handle<Context>(context, this));
MaybeHandle<Object> result;
MaybeHandle<Object> maybe_exception;
if (microtask->IsJSFunction()) {
Handle<JSFunction> microtask_function =
Handle<JSFunction>::cast(microtask);
result = Execution::TryCall(
this, microtask_function, factory()->undefined_value(), 0,
nullptr, Execution::MessageHandling::kReport,
&maybe_exception);
} else if (microtask->IsPromiseResolveThenableJobInfo()) {
PromiseResolveThenableJob(
Handle<PromiseResolveThenableJobInfo>::cast(microtask),
&result, &maybe_exception);
} else {
PromiseReactionJob(
Handle<PromiseReactionJobInfo>::cast(microtask), &result,
&maybe_exception);
}
handle_scope_implementer_->LeaveMicrotaskContext();
// If execution is terminating, just bail out.
if (result.is_null() && maybe_exception.is_null()) {
// Clear out any remaining callbacks in the queue.
heap()->set_microtask_queue(heap()->empty_fixed_array());
set_pending_microtask_count(0);
return;
}
}
});
} while (pending_microtask_count() > 0);
CHECK_EQ(0, pending_microtask_count());
CHECK_EQ(0, heap()->microtask_queue()->length());
is_running_microtasks_ = false;
}
FireMicrotasksCompletedCallback();
}
void Isolate::SetUseCounterCallback(v8::Isolate::UseCounterCallback callback) {
......
......@@ -372,9 +372,6 @@ class ThreadLocalTop BASE_EMBEDDED {
// Call back function to report unsafe JS accesses.
v8::FailedAccessCheckCallback failed_access_check_callback_;
int microtask_queue_bailout_index_;
int microtask_queue_bailout_count_;
private:
void InitializeInternal();
......@@ -675,18 +672,6 @@ class Isolate {
return &thread_local_top_.js_entry_sp_;
}
THREAD_LOCAL_TOP_ACCESSOR(int, microtask_queue_bailout_index)
Address microtask_queue_bailout_index_address() {
return reinterpret_cast<Address>(
&thread_local_top_.microtask_queue_bailout_index_);
}
THREAD_LOCAL_TOP_ACCESSOR(int, microtask_queue_bailout_count)
Address microtask_queue_bailout_count_address() {
return reinterpret_cast<Address>(
&thread_local_top_.microtask_queue_bailout_count_);
}
// Returns the global object of the current context. It could be
// a builtin object, or a JS global object.
inline Handle<JSGlobalObject> global_object();
......@@ -1220,13 +1205,6 @@ class Isolate {
void ReportPromiseReject(Handle<JSPromise> promise, Handle<Object> value,
v8::PromiseRejectEvent event);
void PromiseReactionJob(Handle<PromiseReactionJobInfo> info,
MaybeHandle<Object>* result,
MaybeHandle<Object>* maybe_exception);
void PromiseResolveThenableJob(Handle<PromiseResolveThenableJobInfo> info,
MaybeHandle<Object>* result,
MaybeHandle<Object>* maybe_exception);
void EnqueueMicrotask(Handle<Object> microtask);
void RunMicrotasks();
bool IsRunningMicrotasks() const { return is_running_microtasks_; }
......@@ -1481,8 +1459,6 @@ class Isolate {
// then return true.
bool PropagatePendingExceptionToExternalTryCatch();
void RunMicrotasksInternal();
const char* RAILModeName(RAILMode rail_mode) const {
switch (rail_mode) {
case PERFORMANCE_RESPONSE:
......
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/runtime/runtime-utils.h"
#include "src/api.h"
#include "src/arguments.h"
#include "src/counters.h"
#include "src/debug/debug.h"
......@@ -85,6 +87,17 @@ RUNTIME_FUNCTION(Runtime_RunMicrotasks) {
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_RunMicrotaskCallback) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
CONVERT_ARG_CHECKED(Object, microtask_callback, 0);
CONVERT_ARG_CHECKED(Object, microtask_data, 1);
MicrotaskCallback callback = ToCData<MicrotaskCallback>(microtask_callback);
void* data = ToCData<void*>(microtask_data);
callback(data);
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_PromiseStatus) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
......
......@@ -309,6 +309,7 @@ namespace internal {
F(PromoteScheduledException, 0, 1) \
F(ReThrow, 1, 1) \
F(RunMicrotasks, 0, 1) \
F(RunMicrotaskCallback, 2, 1) \
F(StackGuard, 0, 1) \
F(Throw, 1, 1) \
F(ThrowApplyNonFunction, 1, 1) \
......
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