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

heap, cpppgc: Add support for wrappper nodes in snapshots

Wrapper nodes are merged into their corresponding C++ object nodes
when the reference between C++ and JS object has a wrapper class id
set.

Instead of iterating all global handles and checking for those with
class ids, the new algorithm discovers them while iterating C++
objects.

Note: Additional wrapper nodes, e.g., those from isolated worlds in
Blink are not merged.

Bug: chromium:1056170
Change-Id: I6dff8992e41d7a1a2c3b99a115a53df6b6fbb64c
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2499661
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Reviewed-by: 's avatarOmer Katz <omerkatz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#70804}
parent a78c65f9
......@@ -855,6 +855,17 @@ class TracedReferenceBase {
return this->GetSlotThreadSafe() == nullptr;
}
/**
* Assigns a wrapper class ID to the handle.
*/
V8_INLINE void SetWrapperClassId(uint16_t class_id);
/**
* Returns the class ID previously assigned to this handle or 0 if no class ID
* was previously assigned.
*/
V8_INLINE uint16_t WrapperClassId() const;
protected:
/**
* Update this reference in a thread-safe way.
......@@ -907,17 +918,6 @@ class BasicTracedReference : public TracedReferenceBase {
*/
Local<T> Get(Isolate* isolate) const { return Local<T>::New(isolate, *this); }
/**
* Assigns a wrapper class ID to the handle.
*/
V8_INLINE void SetWrapperClassId(uint16_t class_id);
/**
* Returns the class ID previously assigned to this handle or 0 if no class ID
* was previously assigned.
*/
V8_INLINE uint16_t WrapperClassId() const;
template <class S>
V8_INLINE BasicTracedReference<S>& As() const {
return reinterpret_cast<BasicTracedReference<S>&>(
......@@ -11140,8 +11140,7 @@ TracedReference<T>& TracedReference<T>::operator=(const TracedReference& rhs) {
return *this;
}
template <class T>
void BasicTracedReference<T>::SetWrapperClassId(uint16_t class_id) {
void TracedReferenceBase::SetWrapperClassId(uint16_t class_id) {
typedef internal::Internals I;
if (IsEmpty()) return;
internal::Address* obj = reinterpret_cast<internal::Address*>(val_);
......@@ -11149,8 +11148,7 @@ void BasicTracedReference<T>::SetWrapperClassId(uint16_t class_id) {
*reinterpret_cast<uint16_t*>(addr) = class_id;
}
template <class T>
uint16_t BasicTracedReference<T>::WrapperClassId() const {
uint16_t TracedReferenceBase::WrapperClassId() const {
typedef internal::Internals I;
if (IsEmpty()) return 0;
internal::Address* obj = reinterpret_cast<internal::Address*>(val_);
......
......@@ -9,12 +9,15 @@
#include "include/cppgc/trace-trait.h"
#include "include/v8-cppgc.h"
#include "include/v8-profiler.h"
#include "src/api/api-inl.h"
#include "src/base/logging.h"
#include "src/execution/isolate.h"
#include "src/heap/cppgc-js/cpp-heap.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "src/heap/cppgc/heap-visitor.h"
#include "src/heap/embedder-tracing.h"
#include "src/heap/mark-compact.h"
#include "src/objects/js-objects.h"
#include "src/profiler/heap-profiler.h"
namespace v8 {
......@@ -37,8 +40,14 @@ class EmbedderNode : public v8::EmbedderGraph::Node {
const char* Name() final { return name_; }
size_t SizeInBytes() override { return 0; }
void SetWrapperNode(v8::EmbedderGraph::Node* wrapper_node) {
wrapper_node_ = wrapper_node;
}
Node* WrapperNode() final { return wrapper_node_; }
private:
const char* name_;
Node* wrapper_node_ = nullptr;
};
// Node representing an artificial root group, e.g., set of Persistent handles.
......@@ -281,6 +290,23 @@ class StateStorage final {
size_t state_count_ = 0;
};
bool HasEmbedderDataBackref(Isolate* isolate, v8::Local<v8::Value> v8_value,
void* expected_backref) {
// See LocalEmbedderHeapTracer::VerboseWrapperTypeInfo for details on how
// wrapper objects are set up.
if (!v8_value->IsObject()) return false;
Handle<Object> v8_object = Utils::OpenHandle(*v8_value);
if (!v8_object->IsJSObject() || !JSObject::cast(*v8_object).IsApiWrapper())
return false;
JSObject js_object = JSObject::cast(*v8_object);
return js_object.GetEmbedderFieldCount() >= 2 &&
LocalEmbedderHeapTracer::VerboseWrapperInfo(
LocalEmbedderHeapTracer::ExtractWrapperInfo(isolate, js_object))
.instance() == expected_backref;
}
// The following implements a snapshotting algorithm for C++ objects that also
// filters strongly-connected components (SCCs) of only "hidden" objects that
// are not (transitively) referencing any non-hidden objects.
......@@ -369,6 +395,17 @@ class CppGraphBuilderImpl final {
}
auto* v8_node = graph_.V8Node(v8_value);
graph_.AddEdge(parent.get_node(), v8_node);
// References that have a class id set may have their internal fields
// pointing back to the object. Set up a wrapper node for the graph so
// that the snapshot generator can merge the nodes appropriately.
if (!ref.WrapperClassId()) return;
if (HasEmbedderDataBackref(
reinterpret_cast<v8::internal::Isolate*>(cpp_heap_.isolate()),
v8_value, parent.header()->Payload())) {
parent.get_node()->SetWrapperNode(v8_node);
}
}
}
......
......@@ -86,19 +86,33 @@ 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)) {
return info;
}
return {nullptr, nullptr};
}
void LocalEmbedderHeapTracer::ProcessingScope::TracePossibleWrapper(
JSObject js_object) {
DCHECK(js_object.IsApiWrapper());
if (js_object.GetEmbedderFieldCount() < 2) return;
void* pointer0;
void* pointer1;
if (EmbedderDataSlot(js_object, 0)
.ToAlignedPointerSafe(tracer_->isolate_, &pointer0) &&
pointer0 &&
EmbedderDataSlot(js_object, 1)
.ToAlignedPointerSafe(tracer_->isolate_, &pointer1)) {
wrapper_cache_.push_back({pointer0, pointer1});
WrapperInfo info =
LocalEmbedderHeapTracer::ExtractWrapperInfo(tracer_->isolate_, js_object);
if (VerboseWrapperInfo(info).is_valid()) {
wrapper_cache_.push_back(std::move(info));
}
FlushWrapperCacheIfFull();
}
......
......@@ -20,6 +20,23 @@ class V8_EXPORT_PRIVATE LocalEmbedderHeapTracer final {
using WrapperInfo = std::pair<void*, void*>;
using WrapperCache = std::vector<WrapperInfo>;
// WrapperInfo is passed over the API. Use VerboseWrapperInfo to access pair
// internals in a named way. See ProcessingScope::TracePossibleJSWrapper()
// below on how a V8 object is parsed to gather the information.
struct VerboseWrapperInfo {
explicit VerboseWrapperInfo(const WrapperInfo& raw_info)
: raw_info(raw_info) {}
// Information describing the type pointed to via instance().
void* type_info() const { return raw_info.first; }
// Direct pointer to an instance described by type_info().
void* instance() const { return raw_info.second; }
bool is_valid() const { return type_info(); }
const WrapperInfo& raw_info;
};
class V8_EXPORT_PRIVATE ProcessingScope {
public:
explicit ProcessingScope(LocalEmbedderHeapTracer* tracer);
......@@ -38,6 +55,8 @@ class V8_EXPORT_PRIVATE LocalEmbedderHeapTracer final {
WrapperCache wrapper_cache_;
};
static WrapperInfo ExtractWrapperInfo(Isolate* isolate, JSObject js_object);
explicit LocalEmbedderHeapTracer(Isolate* isolate) : isolate_(isolate) {}
~LocalEmbedderHeapTracer() {
......
......@@ -296,6 +296,10 @@ class GCedWithJSRef : public cppgc::GarbageCollected<GCedWithJSRef> {
v8_object_.Reset(isolate, object);
}
void SetWrapperClassId(uint16_t class_id) {
v8_object_.SetWrapperClassId(class_id);
}
private:
TracedReference<v8::Object> v8_object_;
};
......@@ -325,5 +329,41 @@ TEST_F(UnifiedHeapSnapshotTest, JSReferenceForcesVisibleObject) {
}));
}
TEST_F(UnifiedHeapSnapshotTest, MergedWrapperNode) {
// Test ensures that the snapshot sets a wrapper node for C++->JS references
// that have a class id set and that object nodes are merged into the C++
// node, i.e., the directly reachable JS object is merged into the C++ object.
cppgc::Persistent<GCedWithJSRef> gc_w_js_ref =
cppgc::MakeGarbageCollected<GCedWithJSRef>(allocation_handle());
v8::HandleScope scope(v8_isolate());
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
v8::Local<v8::Object> wrapper_object =
ConstructTraceableJSApiObject(context, gc_w_js_ref.Get(), "MergedObject");
gc_w_js_ref->SetV8Object(v8_isolate(), wrapper_object);
gc_w_js_ref->SetWrapperClassId(1); // Any class id will do.
// Chain another object to `wrapper_object`. Since `wrapper_object` should be
// merged into `GCedWithJSRef`, the additional object must show up as direct
// child from `GCedWithJSRef`.
v8::Local<v8::Object> next_object =
ConstructTraceableJSApiObject(context, nullptr, "NextObject");
wrapper_object
->Set(context,
v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), "link")
.ToLocalChecked(),
next_object)
.ToChecked();
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_TRUE(
ContainsRetainingPath(*snapshot,
{
kExpectedCppRootsName, // NOLINT
GetExpectedName<GCedWithJSRef>(), // NOLINT
// MergedObject is merged into GCedWithJSRef.
"NextObject" // NOLINT
}));
}
} // namespace internal
} // namespace v8
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