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:
parent
719d0c32ee
commit
35382590cb
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
38
test/unittests/heap/unified-heap-utils.cc
Normal file
38
test/unittests/heap/unified-heap-utils.cc
Normal 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
|
22
test/unittests/heap/unified-heap-utils.h
Normal file
22
test/unittests/heap/unified-heap-utils.h
Normal 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_
|
Loading…
Reference in New Issue
Block a user