Commit 78ffd6e8 authored by Michael Lippautz's avatar Michael Lippautz Committed by Commit Bot

cppgc-js: Add WrapperDescriptor

WrapperDescriptor is used to describe how JS wrapper objects can be
inspected to find C++ wrappable objects. In addition, to specifying
which embedder fields are used to find type and instance, the
descriptor also provides and embedder id that identifies
garbage-collected objects. It is expected that the first field of the
type is a uint16_t with that id.

Bug: chromium:1056170
Change-Id: I9cf8d79db972f2dea023114fd5a567e89a3bf373
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2688399Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Reviewed-by: 's avatarOmer Katz <omerkatz@chromium.org>
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#72657}
parent b1c36b23
......@@ -5,6 +5,7 @@
#ifndef INCLUDE_V8_CPPGC_H_
#define INCLUDE_V8_CPPGC_H_
#include <cstdint>
#include <memory>
#include <vector>
......@@ -26,8 +27,53 @@ namespace internal {
class CppHeap;
} // namespace internal
/**
* Describes how V8 wrapper objects maintain references to garbage-collected C++
* objects.
*/
struct WrapperDescriptor final {
/**
* The index used on `v8::Ojbect::SetAlignedPointerFromInternalField()` and
* related APIs to add additional data to an object which is used to identify
* JS->C++ references.
*/
using InternalFieldIndex = int;
/**
* Unknown embedder id. The value is reserved for internal usages and must not
* be used with `CppHeap`.
*/
static constexpr uint16_t kUnknownEmbedderId = UINT16_MAX;
constexpr WrapperDescriptor(InternalFieldIndex wrappable_type_index,
InternalFieldIndex wrappable_instance_index,
uint16_t embedder_id_for_garbage_collected)
: wrappable_type_index(wrappable_type_index),
wrappable_instance_index(wrappable_instance_index),
embedder_id_for_garbage_collected(embedder_id_for_garbage_collected) {}
/**
* Index of the wrappable type.
*/
InternalFieldIndex wrappable_type_index;
/**
* Index of the wrappable instance.
*/
InternalFieldIndex wrappable_instance_index;
/**
* Embedder id identifying instances of garbage-collected objects. It is
* expected that the first field of the wrappable type is a uint16_t holding
* the id. Only references to instances of wrappables types with an id of
* `embedder_id_for_garbage_collected` will be considered by CppHeap.
*/
uint16_t embedder_id_for_garbage_collected;
};
struct V8_EXPORT CppHeapCreateParams {
std::vector<std::unique_ptr<cppgc::CustomSpaceBase>> custom_spaces;
WrapperDescriptor wrapper_descriptor;
};
/**
......
......@@ -30,6 +30,7 @@
#include "src/heap/cppgc/prefinalizer-handler.h"
#include "src/heap/cppgc/stats-collector.h"
#include "src/heap/cppgc/sweeper.h"
#include "src/heap/embedder-tracing.h"
#include "src/heap/marking-worklist.h"
#include "src/heap/sweeper.h"
#include "src/init/v8.h"
......@@ -37,10 +38,14 @@
namespace v8 {
// static
constexpr uint16_t WrapperDescriptor::kUnknownEmbedderId;
// static
std::unique_ptr<CppHeap> CppHeap::Create(v8::Platform* platform,
const CppHeapCreateParams& params) {
return std::make_unique<internal::CppHeap>(platform, params.custom_spaces);
return std::make_unique<internal::CppHeap>(platform, params.custom_spaces,
params.wrapper_descriptor);
}
cppgc::AllocationHandle& CppHeap::GetAllocationHandle() {
......@@ -180,12 +185,16 @@ void UnifiedHeapMarker::AddObject(void* object) {
CppHeap::CppHeap(
v8::Platform* platform,
const std::vector<std::unique_ptr<cppgc::CustomSpaceBase>>& custom_spaces,
const v8::WrapperDescriptor& wrapper_descriptor,
std::unique_ptr<cppgc::internal::MetricRecorder> metric_recorder)
: cppgc::internal::HeapBase(
std::make_shared<CppgcPlatformAdapter>(platform), custom_spaces,
cppgc::internal::HeapBase::StackSupport::
kSupportsConservativeStackScan,
std::move(metric_recorder)) {
std::move(metric_recorder)),
wrapper_descriptor_(wrapper_descriptor) {
CHECK_NE(WrapperDescriptor::kUnknownEmbedderId,
wrapper_descriptor_.embedder_id_for_garbage_collected);
// Enter no GC scope. `AttachIsolate()` removes this and allows triggering
// garbage collections.
no_gc_scope_++;
......@@ -215,6 +224,8 @@ void CppHeap::AttachIsolate(Isolate* isolate) {
&CppGraphBuilder::Run, this);
}
isolate_->heap()->SetEmbedderHeapTracer(this);
isolate_->heap()->local_embedder_heap_tracer()->SetWrapperDescriptor(
wrapper_descriptor_);
no_gc_scope_--;
}
......
......@@ -34,6 +34,7 @@ class V8_EXPORT_PRIVATE CppHeap final
CppHeap(
v8::Platform* platform,
const std::vector<std::unique_ptr<cppgc::CustomSpaceBase>>& custom_spaces,
const v8::WrapperDescriptor& wrapper_descriptor,
std::unique_ptr<cppgc::internal::MetricRecorder> metric_recorder =
nullptr);
~CppHeap() final;
......@@ -79,6 +80,8 @@ class V8_EXPORT_PRIVATE CppHeap final
// atomic pause. Allocated bytes are buffer in case this is temporarily
// prohibited.
int64_t buffered_allocated_bytes_ = 0;
v8::WrapperDescriptor wrapper_descriptor_;
};
} // namespace internal
......
......@@ -308,10 +308,10 @@ bool HasEmbedderDataBackref(Isolate* isolate, v8::Local<v8::Value> v8_value,
return false;
JSObject js_object = JSObject::cast(*v8_object);
return js_object.GetEmbedderFieldCount() >= 2 &&
LocalEmbedderHeapTracer::VerboseWrapperInfo(
LocalEmbedderHeapTracer::ExtractWrapperInfo(isolate, js_object))
.instance() == expected_backref;
return LocalEmbedderHeapTracer::VerboseWrapperInfo(
isolate->heap()->local_embedder_heap_tracer()->ExtractWrapperInfo(
isolate, js_object))
.instance() == expected_backref;
}
// The following implements a snapshotting algorithm for C++ objects that also
......
......@@ -4,6 +4,7 @@
#include "src/heap/embedder-tracing.h"
#include "include/v8-cppgc.h"
#include "src/base/logging.h"
#include "src/heap/gc-tracer.h"
#include "src/objects/embedder-data-slot.h"
......@@ -74,9 +75,33 @@ void LocalEmbedderHeapTracer::SetEmbedderStackStateForNextFinalization(
}
}
namespace {
bool ExtractWrappableInfo(Isolate* isolate, JSObject js_object,
const WrapperDescriptor& wrapper_descriptor,
LocalEmbedderHeapTracer::WrapperInfo* info) {
DCHECK(js_object.IsApiWrapper());
if (js_object.GetEmbedderFieldCount() < 2) return false;
if (EmbedderDataSlot(js_object, wrapper_descriptor.wrappable_type_index)
.ToAlignedPointerSafe(isolate, &info->first) &&
info->first &&
EmbedderDataSlot(js_object, wrapper_descriptor.wrappable_instance_index)
.ToAlignedPointerSafe(isolate, &info->second) &&
info->second) {
return (wrapper_descriptor.embedder_id_for_garbage_collected ==
WrapperDescriptor::kUnknownEmbedderId) ||
(*static_cast<uint16_t*>(info->first) ==
wrapper_descriptor.embedder_id_for_garbage_collected);
}
return false;
}
} // namespace
LocalEmbedderHeapTracer::ProcessingScope::ProcessingScope(
LocalEmbedderHeapTracer* tracer)
: tracer_(tracer) {
: tracer_(tracer), wrapper_descriptor_(tracer->wrapper_descriptor_) {
wrapper_cache_.reserve(kWrapperCacheSize);
}
......@@ -86,19 +111,11 @@ LocalEmbedderHeapTracer::ProcessingScope::~ProcessingScope() {
}
}
// static
LocalEmbedderHeapTracer::WrapperInfo
LocalEmbedderHeapTracer::ExtractWrapperInfo(Isolate* isolate,
JSObject js_object) {
DCHECK_GE(js_object.GetEmbedderFieldCount(), 2);
DCHECK(js_object.IsApiWrapper());
WrapperInfo info;
if (EmbedderDataSlot(js_object, 0)
.ToAlignedPointerSafe(isolate, &info.first) &&
info.first &&
EmbedderDataSlot(js_object, 1)
.ToAlignedPointerSafe(isolate, &info.second)) {
if (ExtractWrappableInfo(isolate, js_object, wrapper_descriptor_, &info)) {
return info;
}
return {nullptr, nullptr};
......@@ -107,14 +124,12 @@ LocalEmbedderHeapTracer::ExtractWrapperInfo(Isolate* isolate,
void LocalEmbedderHeapTracer::ProcessingScope::TracePossibleWrapper(
JSObject js_object) {
DCHECK(js_object.IsApiWrapper());
if (js_object.GetEmbedderFieldCount() < 2) return;
WrapperInfo info =
LocalEmbedderHeapTracer::ExtractWrapperInfo(tracer_->isolate_, js_object);
if (!VerboseWrapperInfo(info).is_empty()) {
WrapperInfo info;
if (ExtractWrappableInfo(tracer_->isolate_, js_object, wrapper_descriptor_,
&info)) {
wrapper_cache_.push_back(std::move(info));
FlushWrapperCacheIfFull();
}
FlushWrapperCacheIfFull();
}
void LocalEmbedderHeapTracer::ProcessingScope::FlushWrapperCacheIfFull() {
......
......@@ -5,6 +5,7 @@
#ifndef V8_HEAP_EMBEDDER_TRACING_H_
#define V8_HEAP_EMBEDDER_TRACING_H_
#include "include/v8-cppgc.h"
#include "include/v8.h"
#include "src/common/globals.h"
#include "src/flags/flags.h"
......@@ -53,11 +54,10 @@ class V8_EXPORT_PRIVATE LocalEmbedderHeapTracer final {
void FlushWrapperCacheIfFull();
LocalEmbedderHeapTracer* const tracer_;
const WrapperDescriptor wrapper_descriptor_;
WrapperCache wrapper_cache_;
};
static WrapperInfo ExtractWrapperInfo(Isolate* isolate, JSObject js_object);
explicit LocalEmbedderHeapTracer(Isolate* isolate) : isolate_(isolate) {}
~LocalEmbedderHeapTracer() {
......@@ -122,9 +122,27 @@ class V8_EXPORT_PRIVATE LocalEmbedderHeapTracer final {
size_t used_size() const { return remote_stats_.used_size; }
size_t allocated_size() const { return remote_stats_.allocated_size; }
WrapperInfo ExtractWrapperInfo(Isolate* isolate, JSObject js_object);
void SetWrapperDescriptor(const WrapperDescriptor& wrapper_descriptor) {
wrapper_descriptor_ = wrapper_descriptor;
}
private:
static constexpr size_t kEmbedderAllocatedThreshold = 128 * KB;
static constexpr WrapperDescriptor::InternalFieldIndex
kDefaultWrapperTypeEmbedderIndex = 0;
static constexpr WrapperDescriptor::InternalFieldIndex
kDefaultWrapperInstanceEmbedderIndex = 1;
static constexpr WrapperDescriptor GetDefaultWrapperDescriptor() {
// The default descriptor assumes the indices that known embedders use.
return WrapperDescriptor(kDefaultWrapperTypeEmbedderIndex,
kDefaultWrapperInstanceEmbedderIndex,
WrapperDescriptor::kUnknownEmbedderId);
}
Isolate* const isolate_;
EmbedderHeapTracer* remote_tracer_ = nullptr;
......@@ -148,6 +166,11 @@ class V8_EXPORT_PRIVATE LocalEmbedderHeapTracer final {
size_t allocated_size_limit_for_check = 0;
} remote_stats_;
// Default descriptor only used when the embedder is using EmbedderHeapTracer.
// The value is overriden by CppHeap with values that the embedder provided
// upon initialization.
WrapperDescriptor wrapper_descriptor_ = GetDefaultWrapperDescriptor();
friend class EmbedderStackStateScope;
};
......
......@@ -265,6 +265,7 @@ namespace {
class GCedWithJSRef : public cppgc::GarbageCollected<GCedWithJSRef> {
public:
static uint16_t kWrappableType;
static constexpr const char kExpectedName[] =
"v8::internal::(anonymous namespace)::GCedWithJSRef";
......@@ -285,8 +286,12 @@ class GCedWithJSRef : public cppgc::GarbageCollected<GCedWithJSRef> {
private:
TracedReference<v8::Object> v8_object_;
};
constexpr const char GCedWithJSRef::kExpectedName[];
// static
uint16_t GCedWithJSRef::kWrappableType = WrapperHelper::kTracedEmbedderId;
class V8_NODISCARD JsTestingScope {
public:
explicit JsTestingScope(v8::Isolate* isolate)
......@@ -311,7 +316,8 @@ cppgc::Persistent<GCedWithJSRef> SetupWrapperWrappablePair(
cppgc::Persistent<GCedWithJSRef> gc_w_js_ref =
cppgc::MakeGarbageCollected<GCedWithJSRef>(allocation_handle);
v8::Local<v8::Object> wrapper_object = WrapperHelper::CreateWrapper(
testing_scope.context(), gc_w_js_ref.Get(), name);
testing_scope.context(), &GCedWithJSRef::kWrappableType,
gc_w_js_ref.Get(), name);
gc_w_js_ref->SetV8Object(testing_scope.isolate(), wrapper_object);
return std::move(gc_w_js_ref);
}
......@@ -356,7 +362,7 @@ TEST_F(UnifiedHeapSnapshotTest, MergedWrapperNode) {
testing_scope, allocation_handle(), "MergedObject");
gc_w_js_ref->SetWrapperClassId(1); // Any class id will do.
v8::Local<v8::Object> next_object = WrapperHelper::CreateWrapper(
testing_scope.context(), nullptr, "NextObject");
testing_scope.context(), nullptr, nullptr, "NextObject");
v8::Local<v8::Object> wrapper_object =
gc_w_js_ref->wrapper().Get(v8_isolate());
// Chain another object to `wrapper_object`. Since `wrapper_object` should be
......
......@@ -50,8 +50,10 @@ TEST_F(UnifiedHeapTest, FindingV8ToBlinkReference) {
v8::HandleScope scope(v8_isolate());
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
uint16_t wrappable_type = WrapperHelper::kTracedEmbedderId;
v8::Local<v8::Object> api_object = WrapperHelper::CreateWrapper(
context, cppgc::MakeGarbageCollected<Wrappable>(allocation_handle()));
context, &wrappable_type,
cppgc::MakeGarbageCollected<Wrappable>(allocation_handle()));
Wrappable::destructor_callcount = 0;
EXPECT_FALSE(api_object.IsEmpty());
EXPECT_EQ(0u, Wrappable::destructor_callcount);
......@@ -68,7 +70,7 @@ TEST_F(UnifiedHeapTest, WriteBarrierV8ToCppReference) {
v8::Context::Scope context_scope(context);
void* wrappable = cppgc::MakeGarbageCollected<Wrappable>(allocation_handle());
v8::Local<v8::Object> api_object =
WrapperHelper::CreateWrapper(context, wrappable);
WrapperHelper::CreateWrapper(context, nullptr, nullptr);
Wrappable::destructor_callcount = 0;
WrapperHelper::ResetWrappableConnection(api_object);
SimulateIncrementalMarking();
......@@ -103,7 +105,7 @@ TEST_F(UnifiedHeapTest, WriteBarrierCppToV8Reference) {
// setter for C++ to JS references.
v8::HandleScope nested_scope(v8_isolate());
v8::Local<v8::Object> api_object =
WrapperHelper::CreateWrapper(context, nullptr);
WrapperHelper::CreateWrapper(context, nullptr, nullptr);
// Setting only one field to avoid treating this as wrappable backref, see
// `LocalEmbedderHeapTracer::ExtractWrapperInfo`.
api_object->SetAlignedPointerInInternalField(1, kMagicAddress);
......@@ -123,8 +125,9 @@ TEST_F(UnifiedHeapTest, WriteBarrierCppToV8Reference) {
}
TEST_F(UnifiedHeapDetachedTest, AllocationBeforeConfigureHeap) {
auto heap =
v8::CppHeap::Create(V8::GetCurrentPlatform(), CppHeapCreateParams{});
auto heap = v8::CppHeap::Create(
V8::GetCurrentPlatform(),
CppHeapCreateParams{{}, WrapperHelper::DefaultWrapperDescriptor()});
auto* object =
cppgc::MakeGarbageCollected<Wrappable>(heap->GetAllocationHandle());
cppgc::WeakPersistent<Wrappable> weak_holder{object};
......
......@@ -15,8 +15,9 @@ namespace v8 {
namespace internal {
UnifiedHeapTest::UnifiedHeapTest()
: cpp_heap_(v8::CppHeap::Create(V8::GetCurrentPlatform(),
CppHeapCreateParams{})) {
: cpp_heap_(v8::CppHeap::Create(
V8::GetCurrentPlatform(),
CppHeapCreateParams{{}, WrapperHelper::DefaultWrapperDescriptor()})) {
isolate()->heap()->AttachCppHeap(cpp_heap_.get());
}
......@@ -50,8 +51,8 @@ cppgc::AllocationHandle& UnifiedHeapTest::allocation_handle() {
// static
v8::Local<v8::Object> WrapperHelper::CreateWrapper(
v8::Local<v8::Context> context, void* wrappable_object,
const char* class_name) {
v8::Local<v8::Context> context, void* wrappable_type,
void* wrappable_object, const char* class_name) {
v8::EscapableHandleScope scope(context->GetIsolate());
v8::Local<v8::FunctionTemplate> function_t =
v8::FunctionTemplate::New(context->GetIsolate());
......@@ -66,8 +67,7 @@ v8::Local<v8::Object> WrapperHelper::CreateWrapper(
function_t->GetFunction(context).ToLocalChecked();
v8::Local<v8::Object> instance =
function->NewInstance(context).ToLocalChecked();
instance->SetAlignedPointerInInternalField(0, wrappable_object);
instance->SetAlignedPointerInInternalField(1, wrappable_object);
SetWrappableConnection(instance, wrappable_type, wrappable_object);
CHECK(!instance.IsEmpty());
i::Handle<i::JSReceiver> js_obj = v8::Utils::OpenHandle(*instance);
CHECK_EQ(i::JS_API_OBJECT_TYPE, js_obj->map().instance_type());
......@@ -76,15 +76,19 @@ v8::Local<v8::Object> WrapperHelper::CreateWrapper(
// static
void WrapperHelper::ResetWrappableConnection(v8::Local<v8::Object> api_object) {
api_object->SetAlignedPointerInInternalField(0, nullptr);
api_object->SetAlignedPointerInInternalField(1, nullptr);
api_object->SetAlignedPointerInInternalField(kWrappableTypeEmbedderIndex,
nullptr);
api_object->SetAlignedPointerInInternalField(kWrappableInstanceEmbedderIndex,
nullptr);
}
// static
void WrapperHelper::SetWrappableConnection(v8::Local<v8::Object> api_object,
void* field1, void* field2) {
api_object->SetAlignedPointerInInternalField(0, field1);
api_object->SetAlignedPointerInInternalField(1, field2);
void* type, void* instance) {
api_object->SetAlignedPointerInInternalField(kWrappableTypeEmbedderIndex,
type);
api_object->SetAlignedPointerInInternalField(kWrappableInstanceEmbedderIndex,
instance);
}
} // namespace internal
......
......@@ -6,6 +6,7 @@
#define V8_UNITTESTS_HEAP_UNIFIED_HEAP_UTILS_H_
#include "include/cppgc/heap.h"
#include "include/v8-cppgc.h"
#include "include/v8.h"
#include "test/unittests/heap/heap-utils.h"
......@@ -37,10 +38,22 @@ class UnifiedHeapTest : public TestWithHeapInternals {
class WrapperHelper {
public:
static constexpr size_t kWrappableTypeEmbedderIndex = 0;
static constexpr size_t kWrappableInstanceEmbedderIndex = 1;
// Id that identifies types that should be traced.
static constexpr uint16_t kTracedEmbedderId = uint16_t{0xA50F};
static constexpr WrapperDescriptor DefaultWrapperDescriptor() {
return WrapperDescriptor(kWrappableTypeEmbedderIndex,
kWrappableInstanceEmbedderIndex,
kTracedEmbedderId);
}
// Sets up a V8 API object so that it points back to a C++ object. The setup
// used is recognized by the GC and references will be followed for liveness
// analysis (marking) as well as tooling (snapshot).
static v8::Local<v8::Object> CreateWrapper(v8::Local<v8::Context> context,
void* wrappable_type,
void* wrappable_object,
const char* class_name = "");
......
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