Commit aa7c6e22 authored by Michael Lippautz's avatar Michael Lippautz Committed by Commit Bot

[heap] Provide async GC for JS

Reuse the existing builtin and extension infrastructure to provide a
garbage collection mechanism that allows for asynchronous execution.

On --expose-gc, this changes the gc call to parse parameters the
following:
(1) Parse options when encountering an options object with known properties.
(2) No parameters is parsed as
    {type: 'major', execution: 'sync'}.
(3) Truthy parameter that is not setting options is parsed as
    {type: 'minor', execution: 'sync'}.

(2) and (3) preserve backwards compatibility for existing callers as this may be
used widely across various test and benchmarking infrastructures.

Valid options:
- type: 'major' or 'minor' for full GC and Scavenge, respectively.
- execution: 'sync' or 'async' for synchronous and asynchronous
  execution respectively.

Returns a Promise that resolves when GC is done when asynchronous execution
is requested, and undefined otherwise.

Note: This is implemented as builtin to avoid having any stack at all. This
information is also passed to the embedder to allow skipping stack scanning.

Change-Id: Ie5c9b6f0d55238abfeb9051ffa1837501d474934
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1793143
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#63659}
parent 09af9adf
...@@ -4,23 +4,143 @@ ...@@ -4,23 +4,143 @@
#include "src/extensions/gc-extension.h" #include "src/extensions/gc-extension.h"
#include "include/v8.h"
#include "src/base/platform/platform.h" #include "src/base/platform/platform.h"
#include "src/execution/isolate.h"
#include "src/heap/heap.h"
#include "src/tasks/cancelable-task.h"
namespace v8 { namespace v8 {
namespace internal { namespace internal {
namespace {
enum class ExecutionType { kAsync, kSync };
struct GCOptions {
v8::Isolate::GarbageCollectionType type;
ExecutionType execution;
};
bool IsProperty(v8::Isolate* isolate, v8::Local<v8::Context> ctx,
v8::Local<v8::Object> object, const char* key,
const char* value) {
auto k = v8::String::NewFromUtf8(isolate, key).ToLocalChecked();
// Get will return undefined for non-existing keys which will make
// StrictEquals fail.
return object->Get(ctx, k).ToLocalChecked()->StrictEquals(
v8::String::NewFromUtf8(isolate, value).ToLocalChecked());
}
GCOptions Parse(v8::Isolate* isolate, v8::Local<v8::Context> ctx,
const v8::FunctionCallbackInfo<v8::Value>& args) {
// Default values.
auto options =
GCOptions{v8::Isolate::GarbageCollectionType::kFullGarbageCollection,
ExecutionType::kSync};
bool found_options_object = false;
if (args[0]->IsObject()) {
auto param = v8::Local<v8::Object>::Cast(args[0]);
if (IsProperty(isolate, ctx, param, "type", "minor")) {
found_options_object = true;
options.type =
v8::Isolate::GarbageCollectionType::kMinorGarbageCollection;
}
if (IsProperty(isolate, ctx, param, "execution", "async")) {
found_options_object = true;
options.execution = ExecutionType::kAsync;
}
}
// If no options object is present default to legacy behavior.
if (!found_options_object) {
options.type =
args[0]->BooleanValue(isolate)
? v8::Isolate::GarbageCollectionType::kMinorGarbageCollection
: v8::Isolate::GarbageCollectionType::kFullGarbageCollection;
}
return options;
}
void InvokeGC(v8::Isolate* isolate, v8::Isolate::GarbageCollectionType type,
v8::EmbedderHeapTracer::EmbedderStackState embedder_stack_state) {
Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
switch (type) {
case v8::Isolate::GarbageCollectionType::kMinorGarbageCollection:
heap->CollectGarbage(i::NEW_SPACE, i::GarbageCollectionReason::kTesting,
kGCCallbackFlagForced);
break;
case v8::Isolate::GarbageCollectionType::kFullGarbageCollection:
heap->SetEmbedderStackStateForNextFinalizaton(embedder_stack_state);
heap->PreciseCollectAllGarbage(i::Heap::kNoGCFlags,
i::GarbageCollectionReason::kTesting,
kGCCallbackFlagForced);
break;
}
}
class AsyncGC final : public CancelableTask {
public:
~AsyncGC() final = default;
AsyncGC(v8::Isolate* isolate, v8::Local<v8::Context> ctx,
v8::Local<v8::Promise::Resolver> resolver,
v8::Isolate::GarbageCollectionType type)
: CancelableTask(reinterpret_cast<Isolate*>(isolate)),
isolate_(isolate),
ctx_(isolate, ctx),
resolver_(isolate, resolver),
type_(type) {}
void RunInternal() final {
v8::HandleScope scope(isolate_);
InvokeGC(isolate_, type_,
v8::EmbedderHeapTracer::EmbedderStackState::kEmpty);
auto resolver = v8::Local<v8::Promise::Resolver>::New(isolate_, resolver_);
auto ctx = Local<v8::Context>::New(isolate_, ctx_);
resolver->Resolve(ctx, v8::Undefined(isolate_)).ToChecked();
}
private:
v8::Isolate* isolate_;
v8::Persistent<v8::Context> ctx_;
v8::Persistent<v8::Promise::Resolver> resolver_;
v8::Isolate::GarbageCollectionType type_;
DISALLOW_COPY_AND_ASSIGN(AsyncGC);
};
} // namespace
v8::Local<v8::FunctionTemplate> GCExtension::GetNativeFunctionTemplate( v8::Local<v8::FunctionTemplate> GCExtension::GetNativeFunctionTemplate(
v8::Isolate* isolate, v8::Local<v8::String> str) { v8::Isolate* isolate, v8::Local<v8::String> str) {
return v8::FunctionTemplate::New(isolate, GCExtension::GC); return v8::FunctionTemplate::New(isolate, GCExtension::GC);
} }
void GCExtension::GC(const v8::FunctionCallbackInfo<v8::Value>& args) { void GCExtension::GC(const v8::FunctionCallbackInfo<v8::Value>& args) {
args.GetIsolate()->RequestGarbageCollectionForTesting( v8::Isolate* isolate = args.GetIsolate();
args[0]->BooleanValue(args.GetIsolate()) v8::HandleScope scope(isolate);
? v8::Isolate::kMinorGarbageCollection auto ctx = isolate->GetCurrentContext();
: v8::Isolate::kFullGarbageCollection);
auto options = Parse(isolate, ctx, args);
switch (options.execution) {
case ExecutionType::kSync:
InvokeGC(isolate, options.type,
v8::EmbedderHeapTracer::EmbedderStackState::kUnknown);
break;
case ExecutionType::kAsync: {
auto resolver = v8::Promise::Resolver::New(isolate->GetCurrentContext())
.ToLocalChecked();
args.GetReturnValue().Set(resolver->GetPromise());
auto task_runner =
V8::GetCurrentPlatform()->GetForegroundTaskRunner(isolate);
CHECK(task_runner->NonNestableTasksEnabled());
task_runner->PostNonNestableTask(
std::make_unique<AsyncGC>(isolate, ctx, resolver, options.type));
} break;
}
} }
} // namespace internal } // namespace internal
......
...@@ -11,6 +11,21 @@ ...@@ -11,6 +11,21 @@
namespace v8 { namespace v8 {
namespace internal { namespace internal {
// Provides garbage collection on invoking |fun_name|(options), where
// - options is a dictionary like object. See supported properties below.
// - no parameter refers to options:
// {type: 'major', execution: 'sync'}.
// - truthy parameter that is not setting any options:
// {type: 'minor', execution: 'sync'}.
//
// Supported options:
// - type: 'major' or 'minor' for full GC and Scavenge, respectively.
// - execution: 'sync' or 'async' for synchronous and asynchronous execution,
// respectively.
// - Defaults to {type: 'major', execution: 'sync'}.
//
// Returns a Promise that resolves when GC is done when asynchronous execution
// is requested, and undefined otherwise.
class GCExtension : public v8::Extension { class GCExtension : public v8::Extension {
public: public:
explicit GCExtension(const char* fun_name) explicit GCExtension(const char* fun_name)
......
...@@ -60,6 +60,16 @@ bool DefaultForegroundTaskRunner::IdleTasksEnabled() { ...@@ -60,6 +60,16 @@ bool DefaultForegroundTaskRunner::IdleTasksEnabled() {
return idle_task_support_ == IdleTaskSupport::kEnabled; return idle_task_support_ == IdleTaskSupport::kEnabled;
} }
void DefaultForegroundTaskRunner::PostNonNestableTask(
std::unique_ptr<Task> task) {
// Default platform does not nest tasks.
PostTask(std::move(task));
}
bool DefaultForegroundTaskRunner::NonNestableTasksEnabled() const {
return true;
}
std::unique_ptr<Task> DefaultForegroundTaskRunner::PopTaskFromQueue( std::unique_ptr<Task> DefaultForegroundTaskRunner::PopTaskFromQueue(
MessageLoopBehavior wait_for_work) { MessageLoopBehavior wait_for_work) {
base::MutexGuard guard(&lock_); base::MutexGuard guard(&lock_);
......
...@@ -35,14 +35,15 @@ class V8_PLATFORM_EXPORT DefaultForegroundTaskRunner ...@@ -35,14 +35,15 @@ class V8_PLATFORM_EXPORT DefaultForegroundTaskRunner
// v8::TaskRunner implementation. // v8::TaskRunner implementation.
void PostTask(std::unique_ptr<Task> task) override; void PostTask(std::unique_ptr<Task> task) override;
void PostDelayedTask(std::unique_ptr<Task> task, void PostDelayedTask(std::unique_ptr<Task> task,
double delay_in_seconds) override; double delay_in_seconds) override;
void PostIdleTask(std::unique_ptr<IdleTask> task) override; void PostIdleTask(std::unique_ptr<IdleTask> task) override;
bool IdleTasksEnabled() override; bool IdleTasksEnabled() override;
void PostNonNestableTask(std::unique_ptr<Task> task) override;
bool NonNestableTasksEnabled() const override;
private: private:
// The same as PostTask, but the lock is already held by the caller. The // The same as PostTask, but the lock is already held by the caller. The
// {guard} parameter should make sure that the caller is holding the lock. // {guard} parameter should make sure that the caller is holding the lock.
......
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