cppgc, heap: snapshot: Add support for C++->JS references

Enables following JS references for unified heap snapshots. Any object
that's referencing a JS objects is marked as visible.

Followup:
- Handling (merging) of wrapper/wrappable pairs.

Change-Id: I02d41a3224265f38d934dcb2686ac24b49c1dbd7
Bug: chromium:1056170
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2489698
Reviewed-by: Ulan Degenbaev <ulan@chromium.org>
Reviewed-by: Omer Katz <omerkatz@chromium.org>
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#70735}
This commit is contained in:
Michael Lippautz 2020-10-22 09:02:17 +02:00 committed by Commit Bot
parent 719d0c32ee
commit 35382590cb
8 changed files with 146 additions and 21 deletions

View File

@ -15,6 +15,8 @@ class Isolate;
template <typename T>
class JSMember;
class JSVisitor;
template <typename T>
class Local;
namespace internal {
@ -33,6 +35,8 @@ class V8_EXPORT JSMemberBase {
*/
inline void Reset();
inline v8::Local<v8::Value> Get(v8::Isolate* isolate) const;
private:
static internal::Address* New(v8::Isolate* isolate,
internal::Address* object_slot,
@ -96,6 +100,11 @@ void JSMemberBase::Reset() {
SetSlotThreadSafe(nullptr);
}
v8::Local<v8::Value> JSMemberBase::Get(v8::Isolate* isolate) const {
if (IsEmpty()) return Local<Value>();
return Local<Value>::New(isolate, reinterpret_cast<Value*>(val_));
}
template <typename U>
inline bool operator==(const v8::internal::JSMemberBase& lhs,
const v8::Local<U>& rhs) {

View File

@ -135,6 +135,7 @@ class Heap;
class HeapObject;
class ExternalString;
class Isolate;
class JSMemberBase;
class LocalEmbedderHeapTracer;
class MicrotaskQueue;
class PropertyCallbackArguments;
@ -304,6 +305,7 @@ class Local {
const TracedReferenceBase<T>& that);
private:
friend class internal::JSMemberBase;
friend class Utils;
template<class F> friend class Eternal;
template<class F> friend class PersistentBase;

View File

@ -328,6 +328,7 @@ class CppGraphBuilderImpl final {
void Run();
void VisitForVisibility(State* parent, const HeapObjectHeader&);
void VisitForVisibility(State& parent, const JSMemberBase&);
void VisitRootForGraphBuilding(RootState&, const HeapObjectHeader&,
const cppgc::SourceLocation&);
void ProcessPendingObjects();
@ -359,6 +360,18 @@ class CppGraphBuilderImpl final {
graph_.AddEdge(parent.get_node(), current.get_node());
}
void AddEdge(State& parent, const JSMemberBase& ref) {
DCHECK(parent.IsVisibleNotDependent());
v8::Local<v8::Value> v8_value = ref.Get(cpp_heap_.isolate());
if (!v8_value.IsEmpty()) {
if (!parent.get_node()) {
parent.set_node(AddNode(*parent.header()));
}
auto* v8_node = graph_.V8Node(v8_value);
graph_.AddEdge(parent.get_node(), v8_node);
}
}
void AddRootEdge(RootState& root, State& child, std::string edge_name) {
DCHECK(root.IsVisibleNotDependent());
if (!child.IsVisibleNotDependent()) return;
@ -462,7 +475,10 @@ class VisiblityVisitor final : public JSVisitor {
}
// JS handling.
void Visit(const JSMemberBase& ref) final {}
void Visit(const JSMemberBase& ref) final {
graph_builder_.VisitForVisibility(parent_scope_.ParentAsRegularState(),
ref);
}
private:
CppGraphBuilderImpl& graph_builder_;
@ -492,7 +508,9 @@ class GraphBuildingVisitor final : public JSVisitor {
void VisitWeakRoot(const void*, cppgc::TraceDescriptor, cppgc::WeakCallback,
const void*, const cppgc::SourceLocation&) final {}
// JS handling.
void Visit(const JSMemberBase& ref) final {}
void Visit(const JSMemberBase& ref) final {
graph_builder_.AddEdge(parent_scope_.ParentAsRegularState(), ref);
}
private:
CppGraphBuilderImpl& graph_builder_;
@ -585,6 +603,14 @@ void CppGraphBuilderImpl::VisitForVisibility(State* parent,
}
}
void CppGraphBuilderImpl::VisitForVisibility(State& parent,
const JSMemberBase& ref) {
v8::Local<v8::Value> v8_value = ref.Get(cpp_heap_.isolate());
if (!v8_value.IsEmpty()) {
parent.MarkVisible();
}
}
void CppGraphBuilderImpl::VisitRootForGraphBuilding(
RootState& root, const HeapObjectHeader& header,
const cppgc::SourceLocation& loc) {

View File

@ -300,6 +300,8 @@ v8_source_set("unittests_sources") {
"heap/spaces-unittest.cc",
"heap/unified-heap-snapshot-unittest.cc",
"heap/unified-heap-unittest.cc",
"heap/unified-heap-utils.cc",
"heap/unified-heap-utils.h",
"heap/unmapper-unittest.cc",
"heap/worklist-unittest.cc",
"interpreter/bytecode-array-builder-unittest.cc",

View File

@ -10,6 +10,7 @@
#include "include/cppgc/name-provider.h"
#include "include/cppgc/persistent.h"
#include "include/cppgc/platform.h"
#include "include/v8-cppgc.h"
#include "include/v8-profiler.h"
#include "src/api/api-inl.h"
#include "src/heap/cppgc-js/cpp-heap.h"
@ -17,6 +18,7 @@
#include "src/profiler/heap-snapshot-generator-inl.h"
#include "src/profiler/heap-snapshot-generator.h"
#include "test/unittests/heap/heap-utils.h"
#include "test/unittests/heap/unified-heap-utils.h"
namespace v8 {
namespace internal {
@ -281,5 +283,47 @@ TEST_F(UnifiedHeapSnapshotTest, ReferenceToFinishedSCC) {
}));
}
namespace {
class GCedWithJSRef : public cppgc::GarbageCollected<GCedWithJSRef> {
public:
static constexpr const char kExpectedName[] =
"v8::internal::(anonymous namespace)::GCedWithJSRef";
virtual void Trace(cppgc::Visitor* v) const { v->Trace(v8_object_); }
void SetV8Object(v8::Isolate* isolate, v8::Local<v8::Object> object) {
v8_object_.Set(isolate, object);
}
private:
JSMember<v8::Object> v8_object_;
};
constexpr const char GCedWithJSRef::kExpectedName[];
} // namespace
TEST_F(UnifiedHeapSnapshotTest, JSReferenceForcesVisibleObject) {
// Test ensures that a C++->JS reference forces an object to be visible in the
// snapshot.
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> api_object =
ConstructTraceableJSApiObject(context, gc_w_js_ref.Get(), "LeafJSObject");
gc_w_js_ref->SetV8Object(v8_isolate(), api_object);
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_TRUE(
ContainsRetainingPath(*snapshot,
{
kExpectedCppRootsName, // NOLINT
GetExpectedName<GCedWithJSRef>(), // NOLINT
"LeafJSObject" // NOLINT
}));
}
} // namespace internal
} // namespace v8

View File

@ -9,31 +9,13 @@
#include "src/heap/cppgc-js/cpp-heap.h"
#include "src/objects/objects-inl.h"
#include "test/unittests/heap/heap-utils.h"
#include "test/unittests/heap/unified-heap-utils.h"
namespace v8 {
namespace internal {
namespace {
v8::Local<v8::Object> ConstructTraceableJSApiObject(
v8::Local<v8::Context> context, void* object) {
v8::EscapableHandleScope scope(context->GetIsolate());
v8::Local<v8::FunctionTemplate> function_t =
v8::FunctionTemplate::New(context->GetIsolate());
v8::Local<v8::ObjectTemplate> instance_t = function_t->InstanceTemplate();
instance_t->SetInternalFieldCount(2);
v8::Local<v8::Function> function =
function_t->GetFunction(context).ToLocalChecked();
v8::Local<v8::Object> instance =
function->NewInstance(context).ToLocalChecked();
instance->SetAlignedPointerInInternalField(0, object);
instance->SetAlignedPointerInInternalField(1, 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());
return scope.Escape(instance);
}
void ResetWrappableConnection(v8::Local<v8::Object> api_object) {
api_object->SetAlignedPointerInInternalField(0, nullptr);
api_object->SetAlignedPointerInInternalField(1, nullptr);

View File

@ -0,0 +1,38 @@
// Copyright 2020 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 "test/unittests/heap/unified-heap-utils.h"
#include "src/api/api-inl.h"
#include "src/objects/objects-inl.h"
namespace v8 {
namespace internal {
v8::Local<v8::Object> ConstructTraceableJSApiObject(
v8::Local<v8::Context> context, void* object, const char* class_name) {
v8::EscapableHandleScope scope(context->GetIsolate());
v8::Local<v8::FunctionTemplate> function_t =
v8::FunctionTemplate::New(context->GetIsolate());
if (strlen(class_name) != 0) {
function_t->SetClassName(
v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), class_name)
.ToLocalChecked());
}
v8::Local<v8::ObjectTemplate> instance_t = function_t->InstanceTemplate();
instance_t->SetInternalFieldCount(2);
v8::Local<v8::Function> function =
function_t->GetFunction(context).ToLocalChecked();
v8::Local<v8::Object> instance =
function->NewInstance(context).ToLocalChecked();
instance->SetAlignedPointerInInternalField(0, object);
instance->SetAlignedPointerInInternalField(1, 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());
return scope.Escape(instance);
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,22 @@
// Copyright 2020 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.
#ifndef V8_UNITTESTS_HEAP_UNIFIED_HEAP_UTILS_H_
#define V8_UNITTESTS_HEAP_UNIFIED_HEAP_UTILS_H_
#include "include/v8.h"
namespace v8 {
namespace internal {
// 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).
v8::Local<v8::Object> ConstructTraceableJSApiObject(
v8::Local<v8::Context> context, void* object, const char* class_name = "");
} // namespace internal
} // namespace v8
#endif // V8_UNITTESTS_HEAP_UNIFIED_HEAP_UTILS_H_