blob: 0f3b62b1c4f9e274eeccc2d5aa30e0a5627d78a6 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/platform/heap/thread_state.h"
#include <fstream>
#include <iostream>
#include "base/functional/callback.h"
#include "base/notreached.h"
#include "gin/public/v8_platform.h"
#include "third_party/blink/renderer/platform/bindings/active_script_wrappable_manager.h"
#include "third_party/blink/renderer/platform/bindings/dom_data_store.h"
#include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h"
#include "third_party/blink/renderer/platform/bindings/runtime_call_stats.h"
#include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
#include "third_party/blink/renderer/platform/bindings/wrapper_type_info.h"
#include "third_party/blink/renderer/platform/heap/custom_spaces.h"
#include "third_party/blink/renderer/platform/heap/thread_state_storage.h"
#include "third_party/blink/renderer/platform/wtf/hash_set.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
#include "v8/include/cppgc/heap-consistency.h"
#include "v8/include/v8-cppgc.h"
#include "v8/include/v8-embedder-heap.h"
#include "v8/include/v8-isolate.h"
#include "v8/include/v8-object.h"
#include "v8/include/v8-profiler.h"
#include "v8/include/v8-traced-handle.h"
namespace blink {
namespace {
// Handler allowing for dropping V8 wrapper objects that can be recreated
// lazily.
class BlinkRootsHandler final : public v8::EmbedderRootsHandler {
public:
explicit BlinkRootsHandler(v8::Isolate* isolate) : isolate_(isolate) {}
// ResetRoot() clears references to V8 wrapper objects in all worlds. It is
// invoked for references where IsRoot() returned false during young
// generation garbage collections.
void ResetRoot(const v8::TracedReference<v8::Value>& handle) final {
const v8::TracedReference<v8::Object>& traced = handle.As<v8::Object>();
const bool success = DOMDataStore::ClearWrapperInAnyWorldIfEqualTo(
ToAnyScriptWrappable(isolate_, traced), traced);
// Since V8 found a handle, Blink needs to find it as well when trying to
// remove it. Note that this is even true for the case where a
// DOMWrapperWorld and DOMDataStore are already unreachable as the internal
// worldmap contains a weak ref that remains valid until the next full GC
// call. The weak ref is guaranteed to still be valid because it is only
// cleared on full GCs and the `BlinkRootsHandler` is used on minor V8 GCs.
CHECK(success);
}
bool TryResetRoot(const v8::TracedReference<v8::Value>& handle) final {
const v8::TracedReference<v8::Object>& traced = handle.As<v8::Object>();
return DOMDataStore::ClearInlineStorageWrapperIfEqualTo(
ToAnyScriptWrappable(isolate_, traced), traced);
}
private:
v8::Isolate* isolate_;
};
} // namespace
// static
ThreadState* ThreadState::AttachMainThread() {
auto* thread_state = new ThreadState(gin::V8Platform::Get());
ThreadStateStorage::AttachMainThread(
*thread_state, thread_state->cpp_heap().GetAllocationHandle(),
thread_state->cpp_heap().GetHeapHandle());
return thread_state;
}
// static
ThreadState* ThreadState::AttachMainThreadForTesting(v8::Platform* platform) {
auto* thread_state = new ThreadState(platform);
ThreadStateStorage::AttachMainThread(
*thread_state, thread_state->cpp_heap().GetAllocationHandle(),
thread_state->cpp_heap().GetHeapHandle());
thread_state->EnableDetachedGarbageCollectionsForTesting();
return thread_state;
}
// static
ThreadState* ThreadState::AttachCurrentThread() {
auto* thread_state = new ThreadState(gin::V8Platform::Get());
ThreadStateStorage::AttachNonMainThread(
*thread_state, thread_state->cpp_heap().GetAllocationHandle(),
thread_state->cpp_heap().GetHeapHandle());
return thread_state;
}
// static
ThreadState* ThreadState::AttachCurrentThreadForTesting(
v8::Platform* platform) {
ThreadState* thread_state = new ThreadState(platform);
ThreadStateStorage::AttachNonMainThread(
*thread_state, thread_state->cpp_heap().GetAllocationHandle(),
thread_state->cpp_heap().GetHeapHandle());
thread_state->EnableDetachedGarbageCollectionsForTesting();
return thread_state;
}
void ThreadState::RecoverCppHeap(std::unique_ptr<v8::CppHeap> cpp_heap) {
CHECK(!owning_cpp_heap_);
CHECK(!cpp_heap_);
// We want to keep the invariant that the ThreadState does not own a CppHeap
// while it is attached to an isolate. When it's attached to an isolate, the
// isolate owns the CppHeap.
CHECK(!isolate_);
owning_cpp_heap_ = std::move(cpp_heap);
cpp_heap_ = owning_cpp_heap_.get();
}
// static
void ThreadState::RecoverCppHeapTrampoline(
std::unique_ptr<v8::CppHeap> cpp_heap) {
ThreadState::Current()->RecoverCppHeap(std::move(cpp_heap));
}
void ThreadState::RecoverCppHeapAfterIsolateTearDownForTesting() {
isolate_->SetReleaseCppHeapCallbackForTesting(RecoverCppHeapTrampoline);
}
// static
void ThreadState::DetachCurrentThread() {
auto* state = ThreadState::Current();
DCHECK(state);
delete state;
}
void ThreadState::AttachToIsolate(
v8::Isolate* isolate,
DevToolsCountersCallback dev_tools_counters_callback) {
CHECK(!owning_cpp_heap_);
CHECK_EQ(cpp_heap_, isolate->GetCppHeap());
isolate_ = isolate;
embedder_roots_handler_ = std::make_unique<BlinkRootsHandler>(isolate);
isolate_->SetEmbedderRootsHandler(embedder_roots_handler_.get());
isolate_->AddGCPrologueCallback(GcPrologue);
isolate_->AddGCEpilogueCallback(GcEpilogue);
dev_tools_counters_callback_ = dev_tools_counters_callback;
active_script_wrappable_manager_ =
MakeGarbageCollected<ActiveScriptWrappableManager>();
}
void ThreadState::DetachFromIsolate() {
CHECK(!owning_cpp_heap_);
CHECK_EQ(cpp_heap_, isolate_->GetCppHeap());
CHECK_EQ(gc_callback_depth_, 0u);
active_script_wrappable_manager_.Clear();
isolate_->SetEmbedderRootsHandler(nullptr);
isolate_->RemoveGCPrologueCallback(GcPrologue);
isolate_->RemoveGCEpilogueCallback(GcEpilogue);
isolate_ = nullptr;
cpp_heap_ = nullptr;
}
std::unique_ptr<v8::CppHeap> ThreadState::ReleaseCppHeap() {
return std::move(owning_cpp_heap_);
}
ThreadState::ThreadState(v8::Platform* platform)
: owning_cpp_heap_(v8::CppHeap::Create(
platform,
v8::CppHeapCreateParams(CustomSpaces::CreateCustomSpaces()))),
cpp_heap_(owning_cpp_heap_.get()),
heap_handle_(cpp_heap_->GetHeapHandle()),
thread_id_(CurrentThread()) {}
ThreadState::~ThreadState() {
DCHECK(IsCreationThread());
owning_cpp_heap_.reset();
ThreadStateStorage::DetachNonMainThread(*ThreadStateStorage::Current());
}
void ThreadState::CollectAllGarbageForTesting(StackState stack_state) {
size_t previous_live_bytes = 0;
for (size_t i = 0; i < 5; i++) {
// Either triggers unified heap or stand-alone garbage collections.
cpp_heap().CollectGarbageForTesting(stack_state);
const size_t live_bytes =
cpp_heap()
.CollectStatistics(cppgc::HeapStatistics::kBrief)
.used_size_bytes;
if (previous_live_bytes == live_bytes) {
break;
}
previous_live_bytes = live_bytes;
}
}
void ThreadState::CollectGarbageInYoungGenerationForTesting(
StackState stack_state) {
cpp_heap().CollectGarbageInYoungGenerationForTesting(stack_state);
}
namespace {
class CustomSpaceStatisticsReceiverImpl final
: public v8::CustomSpaceStatisticsReceiver {
public:
explicit CustomSpaceStatisticsReceiverImpl(
base::OnceCallback<void(size_t allocated_node_bytes,
size_t allocated_css_bytes)> callback)
: callback_(std::move(callback)) {}
~CustomSpaceStatisticsReceiverImpl() final {
DCHECK(node_bytes_.has_value());
DCHECK(css_bytes_.has_value());
std::move(callback_).Run(*node_bytes_, *css_bytes_);
}
void AllocatedBytes(cppgc::CustomSpaceIndex space_index, size_t bytes) final {
if (space_index.value == NodeSpace::kSpaceIndex.value) {
node_bytes_ = bytes;
} else {
DCHECK_EQ(space_index.value, CSSValueSpace::kSpaceIndex.value);
css_bytes_ = bytes;
}
}
private:
base::OnceCallback<void(size_t allocated_node_bytes,
size_t allocated_css_bytes)>
callback_;
std::optional<size_t> node_bytes_;
std::optional<size_t> css_bytes_;
};
} // anonymous namespace
void ThreadState::CollectNodeAndCssStatistics(
base::OnceCallback<void(size_t allocated_node_bytes,
size_t allocated_css_bytes)> callback) {
std::vector<cppgc::CustomSpaceIndex> spaces{NodeSpace::kSpaceIndex,
CSSValueSpace::kSpaceIndex};
cpp_heap().CollectCustomSpaceStatisticsAtLastGC(
std::move(spaces),
std::make_unique<CustomSpaceStatisticsReceiverImpl>(std::move(callback)));
}
void ThreadState::EnableDetachedGarbageCollectionsForTesting() {
cpp_heap().EnableDetachedGarbageCollectionsForTesting();
}
bool ThreadState::IsIncrementalMarking() const {
return cppgc::subtle::HeapState::IsMarking(heap_handle()) &&
!cppgc::subtle::HeapState::IsInAtomicPause(heap_handle());
}
bool ThreadState::IsSweepingOnOwningThread() const {
return cppgc::subtle::HeapState::IsSweepingOnOwningThread(heap_handle());
}
namespace {
class BufferedStream final : public v8::OutputStream {
public:
explicit BufferedStream(std::streambuf* stream_buffer)
: out_stream_(stream_buffer) {}
WriteResult WriteAsciiChunk(char* data, int size) override {
out_stream_.write(data, size);
return kContinue;
}
void EndOfStream() override {}
private:
std::ostream out_stream_;
};
} // namespace
void ThreadState::TakeHeapSnapshotForTesting(const char* filename) const {
CHECK(isolate_);
v8::HeapProfiler* profiler = isolate_->GetHeapProfiler();
CHECK(profiler);
v8::HeapProfiler::HeapSnapshotOptions options;
options.snapshot_mode = v8::HeapProfiler::HeapSnapshotMode::kExposeInternals;
const v8::HeapSnapshot* snapshot = profiler->TakeHeapSnapshot(options);
{
std::ofstream file_stream;
if (filename) {
file_stream.open(filename, std::ios_base::out | std::ios_base::trunc);
}
BufferedStream stream(filename ? file_stream.rdbuf() : std::cout.rdbuf());
snapshot->Serialize(&stream);
}
const_cast<v8::HeapSnapshot*>(snapshot)->Delete();
}
bool ThreadState::IsTakingHeapSnapshot() const {
if (!isolate_) {
return false;
}
v8::HeapProfiler* profiler = isolate_->GetHeapProfiler();
return profiler && profiler->IsTakingSnapshot();
}
const char* ThreadState::CopyNameForHeapSnapshot(const char* name) const {
CHECK(isolate_);
v8::HeapProfiler* profiler = isolate_->GetHeapProfiler();
CHECK(profiler);
return profiler->CopyNameForHeapSnapshot(name);
}
// static
void ThreadState::GcPrologue(v8::Isolate* isolate,
v8::GCType type,
v8::GCCallbackFlags) {
RUNTIME_CALL_TIMER_SCOPE(isolate, RuntimeCallStats::CounterId::kGcPrologue);
auto* thread_state = ThreadState::Current();
CHECK_EQ(thread_state->isolate_, isolate);
thread_state->gc_callback_depth_++;
ScriptForbiddenScope::Enter();
ActiveScriptWrappableManager* const active_script_wrappable_manager =
thread_state->active_script_wrappable_manager_.Get();
v8::HandleScope scope(isolate);
switch (type) {
case v8::kGCTypeIncrementalMarking:
// Recomputing ASWs is opportunistic during incremental marking as they
// only need to be recomputing during the atomic pause for correctness.
if (active_script_wrappable_manager) {
active_script_wrappable_manager->RecomputeActiveScriptWrappables(
ActiveScriptWrappableManager::RecomputeMode::kOpportunistic);
}
break;
case v8::kGCTypeMarkSweepCompact:
if (active_script_wrappable_manager) {
active_script_wrappable_manager->RecomputeActiveScriptWrappables(
ActiveScriptWrappableManager::RecomputeMode::kRequired);
}
break;
default:
break;
}
}
// static
void ThreadState::GcEpilogue(v8::Isolate* isolate,
v8::GCType,
v8::GCCallbackFlags) {
RUNTIME_CALL_TIMER_SCOPE(isolate, RuntimeCallStats::CounterId::kGcEpilogue);
auto* thread_state = ThreadState::Current();
CHECK_EQ(thread_state->isolate_, isolate);
thread_state->gc_callback_depth_--;
if (thread_state->dev_tools_counters_callback_) {
thread_state->dev_tools_counters_callback_(isolate);
}
ScriptForbiddenScope::Exit();
}
} // namespace blink