| // Copyright 2010 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/extensions/gc-extension.h" |
| |
| #include "include/v8.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 internal { |
| |
| namespace { |
| |
| enum class ExecutionType { kAsync, kSync }; |
| |
| struct GCOptions { |
| v8::Isolate::GarbageCollectionType type; |
| ExecutionType execution; |
| }; |
| |
| Maybe<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. |
| auto maybe_property = object->Get(ctx, k); |
| if (maybe_property.IsEmpty()) return Nothing<bool>(); |
| return Just<bool>(maybe_property.ToLocalChecked()->StrictEquals( |
| v8::String::NewFromUtf8(isolate, value).ToLocalChecked())); |
| } |
| |
| Maybe<GCOptions> Parse(v8::Isolate* isolate, |
| const v8::FunctionCallbackInfo<v8::Value>& args) { |
| // Default values. |
| auto options = |
| GCOptions{v8::Isolate::GarbageCollectionType::kFullGarbageCollection, |
| ExecutionType::kSync}; |
| bool found_options_object = false; |
| |
| if (args.Length() > 0 && args[0]->IsObject()) { |
| v8::HandleScope scope(isolate); |
| auto ctx = isolate->GetCurrentContext(); |
| auto param = v8::Local<v8::Object>::Cast(args[0]); |
| auto maybe_type = IsProperty(isolate, ctx, param, "type", "minor"); |
| if (maybe_type.IsNothing()) return Nothing<GCOptions>(); |
| if (maybe_type.ToChecked()) { |
| found_options_object = true; |
| options.type = |
| v8::Isolate::GarbageCollectionType::kMinorGarbageCollection; |
| } |
| auto maybe_execution = |
| IsProperty(isolate, ctx, param, "execution", "async"); |
| if (maybe_execution.IsNothing()) return Nothing<GCOptions>(); |
| if (maybe_execution.ToChecked()) { |
| 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 Just<GCOptions>(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->SetEmbedderStackStateForNextFinalization(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::Promise::Resolver> resolver, |
| v8::Isolate::GarbageCollectionType type) |
| : CancelableTask(reinterpret_cast<Isolate*>(isolate)), |
| isolate_(isolate), |
| ctx_(isolate, isolate->GetCurrentContext()), |
| resolver_(isolate, resolver), |
| type_(type) {} |
| |
| void RunInternal() final { |
| v8::HandleScope scope(isolate_); |
| InvokeGC(isolate_, type_, |
| v8::EmbedderHeapTracer::EmbedderStackState::kNoHeapPointers); |
| 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::Isolate* isolate, v8::Local<v8::String> str) { |
| return v8::FunctionTemplate::New(isolate, GCExtension::GC); |
| } |
| |
| void GCExtension::GC(const v8::FunctionCallbackInfo<v8::Value>& args) { |
| v8::Isolate* isolate = args.GetIsolate(); |
| |
| // Immediate bailout if no arguments are provided. |
| if (args.Length() == 0) { |
| InvokeGC( |
| isolate, v8::Isolate::GarbageCollectionType::kFullGarbageCollection, |
| v8::EmbedderHeapTracer::EmbedderStackState::kMayContainHeapPointers); |
| return; |
| } |
| |
| auto maybe_options = Parse(isolate, args); |
| if (maybe_options.IsNothing()) return; |
| GCOptions options = maybe_options.ToChecked(); |
| switch (options.execution) { |
| case ExecutionType::kSync: |
| InvokeGC( |
| isolate, options.type, |
| v8::EmbedderHeapTracer::EmbedderStackState::kMayContainHeapPointers); |
| break; |
| case ExecutionType::kAsync: { |
| v8::HandleScope scope(isolate); |
| 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, resolver, options.type)); |
| } break; |
| } |
| } |
| |
| } // namespace internal |
| } // namespace v8 |