blob: ace275dddb0e2fd37edf7c1da3b0859dead7f867 [file] [log] [blame]
// Copyright 2020 The Chromium 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 "third_party/blink/renderer/platform/heap/thread_state.h"
#include "gin/public/v8_platform.h"
#include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.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/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-initialization.h"
#include "v8/include/v8-isolate.h"
#include "v8/include/v8-object.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::CppHeap& cpp_heap) : cpp_heap_(cpp_heap) {}
~BlinkRootsHandler() final = default;
bool IsRoot(const v8::TracedReference<v8::Value>& handle) final {
const uint16_t class_id = handle.WrapperClassId();
// Stand-alone reference or kCustomWrappableId. Keep as root as
// we don't know better.
if (class_id != WrapperTypeInfo::kNodeClassId &&
class_id != WrapperTypeInfo::kObjectClassId)
return true;
const v8::TracedReference<v8::Object>& traced =
handle.template As<v8::Object>();
if (ToWrapperTypeInfo(traced)->IsActiveScriptWrappable() &&
ToScriptWrappable(traced)->HasPendingActivity()) {
return true;
}
if (ToScriptWrappable(traced)->HasEventListeners()) {
return true;
}
return false;
}
bool IsRoot(const v8::TracedGlobal<v8::Value>& handle) final {
CHECK(false) << "Blink does not use v8::TracedGlobal.";
return false;
}
// 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 uint16_t class_id = handle.WrapperClassId();
// Only consider handles that have not been treated as roots, see IsRoot().
if (class_id != WrapperTypeInfo::kNodeClassId &&
class_id != WrapperTypeInfo::kObjectClassId)
return;
// Clearing the wrapper below adjusts the DOM wrapper store which may
// re-allocate its backing. NoGarbageCollectionScope is required to avoid
// triggering a GC from such re-allocating calls as ResetRoot() is itself
// called from GC.
cppgc::subtle::NoGarbageCollectionScope no_gc(cpp_heap_.GetHeapHandle());
const v8::TracedReference<v8::Object>& traced = handle.As<v8::Object>();
bool success = DOMWrapperWorld::UnsetSpecificWrapperIfSet(
ToScriptWrappable(traced), traced);
// Since V8 found a handle, Blink needs to find it as well when trying to
// remove it.
CHECK(success);
}
private:
v8::CppHeap& cpp_heap_;
};
} // namespace
thread_local ThreadState* g_thread_specific_ CONSTINIT
__attribute__((tls_model(BLINK_HEAP_THREAD_LOCAL_MODEL))) = nullptr;
// static
alignas(ThreadState) uint8_t
ThreadState::main_thread_state_storage_[sizeof(ThreadState)];
BLINK_HEAP_DEFINE_THREAD_LOCAL_GETTER(ThreadState::Current,
ThreadState*,
g_thread_specific_)
// static
ThreadState* ThreadState::AttachMainThread() {
return new (main_thread_state_storage_) ThreadState(gin::V8Platform::Get());
}
// static
ThreadState* ThreadState::AttachMainThreadForTesting(v8::Platform* platform) {
ThreadState* thread_state =
new (main_thread_state_storage_) ThreadState(platform);
thread_state->EnableDetachedGarbageCollectionsForTesting();
return thread_state;
}
// static
ThreadState* ThreadState::AttachCurrentThread() {
return new ThreadState(gin::V8Platform::Get());
}
// static
ThreadState* ThreadState::AttachCurrentThreadForTesting(
v8::Platform* platform) {
ThreadState* thread_state = new ThreadState(platform);
thread_state->EnableDetachedGarbageCollectionsForTesting();
return thread_state;
}
// static
void ThreadState::DetachCurrentThread() {
auto* state = ThreadState::Current();
DCHECK(state);
delete state;
}
void ThreadState::AttachToIsolate(v8::Isolate* isolate,
V8BuildEmbedderGraphCallback) {
isolate->AttachCppHeap(cpp_heap_.get());
CHECK_EQ(cpp_heap_.get(), isolate->GetCppHeap());
isolate_ = isolate;
embedder_roots_handler_ = std::make_unique<BlinkRootsHandler>(cpp_heap());
isolate_->SetEmbedderRootsHandler(embedder_roots_handler_.get());
}
void ThreadState::DetachFromIsolate() {
CHECK_EQ(cpp_heap_.get(), isolate_->GetCppHeap());
isolate_->DetachCppHeap();
isolate_->SetEmbedderRootsHandler(nullptr);
isolate_ = nullptr;
}
ThreadState::ThreadState(v8::Platform* platform)
: cpp_heap_(v8::CppHeap::Create(
platform,
{CustomSpaces::CreateCustomSpaces(),
v8::WrapperDescriptor(kV8DOMWrapperTypeIndex,
kV8DOMWrapperObjectIndex,
gin::GinEmbedder::kEmbedderBlink)})),
allocation_handle_(cpp_heap_->GetAllocationHandle()),
heap_handle_(cpp_heap_->GetHeapHandle()),
thread_id_(CurrentThread()) {
g_thread_specific_ = this;
}
ThreadState::~ThreadState() {
DCHECK(!IsMainThread());
DCHECK(IsCreationThread());
cpp_heap_->Terminate();
}
void ThreadState::SafePoint(BlinkGC::StackState stack_state) {
DCHECK(IsCreationThread());
if (stack_state != BlinkGC::kNoHeapPointersOnStack)
return;
if (forced_scheduled_gc_for_testing_) {
CollectAllGarbageForTesting(stack_state);
forced_scheduled_gc_for_testing_ = false;
}
}
void ThreadState::NotifyGarbageCollection(v8::GCType type,
v8::GCCallbackFlags flags) {
if (flags & v8::kGCCallbackFlagForced) {
// Forces a precise GC at the end of the current event loop. This is
// required for testing code that cannot use GC internals but rather has
// to rely on window.gc(). Only schedule additional GCs if the last GC was
// using conservative stack scanning.
if (type == v8::kGCTypeScavenge) {
forced_scheduled_gc_for_testing_ = true;
} else if (type == v8::kGCTypeMarkSweepCompact) {
forced_scheduled_gc_for_testing_ =
cppgc::subtle::HeapState::PreviousGCWasConservative(heap_handle());
}
}
}
void ThreadState::CollectAllGarbageForTesting(BlinkGC::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 == BlinkGC::kHeapPointersOnStack
? cppgc::EmbedderStackState::kMayContainHeapPointers
: cppgc::EmbedderStackState::kNoHeapPointers);
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;
}
}
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_;
absl::optional<size_t> node_bytes_;
absl::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();
// Detached GCs cannot rely on the V8 platform being initialized which is
// needed by cppgc to perform a garbage collection.
static bool v8_platform_initialized = false;
if (!v8_platform_initialized) {
v8::V8::InitializePlatform(gin::V8Platform::Get());
v8_platform_initialized = true;
}
}
bool ThreadState::IsIncrementalMarking() {
return cppgc::subtle::HeapState::IsMarking(
ThreadState::Current()->heap_handle()) &&
!cppgc::subtle::HeapState::IsInAtomicPause(
ThreadState::Current()->heap_handle());
}
} // namespace blink