Commit fba14bde authored by Maya Lekova's avatar Maya Lekova Committed by Commit Bot

Revert "cppgc-js: Add snapshot for C++ objects"

This reverts commit 02849fd9.

Reason for revert: Breaks Win64 MSVC bot and closes the tree - https://ci.chromium.org/p/v8/builders/ci/V8%20Win64%20-%20msvc/15416

Original change's description:
> cppgc-js: Add snapshot for C++ objects
>
> 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.
>
> C++ objects come in two versions.
> a. Named objects that have been assigned a name through NameProvider.
> b. Unnamed objects, that are potentially hidden if the build
>    configuration requires Oilpan to hide such names. Hidden objects have
>    their name set to NameProvider::kHiddenName.
>
> The main challenge for the algorithm is to avoid blowing up the final
> object graph with hidden nodes that do not carry information. For that
> reason, the algorithm filters SCCs of only hidden objects, e.g.:
>   ...  -> (object) -> (object) -> (hidden) -> (hidden)
> In this case the (hidden) objects are filtered from the graph. The
> trickiest part is maintaining visibility state for objects referencing
> other objects that are currently being processed.
>
> Main algorithm idea (two passes):
> 1. First pass marks all non-hidden objects and those that transitively
>    reach non-hidden objects as visible. Details:
>    - Iterate over all objects.
>    - If object is non-hidden mark it as visible and also mark parent
>      as visible if needed.
>    - If object is hidden, traverse children as DFS to find non-hidden
>      objects. Post-order process the objects and mark those objects as
>      visible that have child nodes that are visible themselves.
>    - Maintain an epoch counter (StateStorage::state_count_) to allow
>      deferring the visibility decision to other objects in the same
>      SCC. This is similar to the "lowlink" value in Tarjan's algorithm
>      for SCC.
>    - After the first pass it is guaranteed that all deferred
>      visibility decisions can be resolved.
> 2. Second pass adds nodes and edges for all visible objects.
>    - Upon first checking the visibility state of an object, all deferred
>      visibility states are resolved.
>
> For practical reasons, the recursion is transformed into an iteration.
> We do not use plain Tarjan's algorithm to avoid another pass over
> all nodes to create SCCs.
>
> Follow ups:
> 1. Adding wrapper nodes for cpp objects that are wrappables for V8
>    wrappers.
> 2. Adding detachedness information.
>
> Change-Id: I6e127d2c6d65e77defe08e39295a2594f463b962
> Bug: chromium:1056170
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2467854
> Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
> Reviewed-by: Ulan Degenbaev <ulan@chromium.org>
> Reviewed-by: Omer Katz <omerkatz@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#70567}

TBR=ulan@chromium.org,mlippautz@chromium.org,bikineev@chromium.org,omerkatz@chromium.org

Change-Id: I64a2cf2259bdaed81f6e0f92bdcc7a1f0df4d197
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: chromium:1056170
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2479471Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Commit-Queue: Maya Lekova <mslekova@chromium.org>
Cr-Commit-Position: refs/heads/master@{#70571}
parent 518d67ad
......@@ -2593,8 +2593,6 @@ v8_source_set("v8_base_without_compiler") {
"src/heap/concurrent-marking.h",
"src/heap/cppgc-js/cpp-heap.cc",
"src/heap/cppgc-js/cpp-heap.h",
"src/heap/cppgc-js/cpp-snapshot.cc",
"src/heap/cppgc-js/cpp-snapshot.h",
"src/heap/cppgc-js/unified-heap-marking-state.h",
"src/heap/cppgc-js/unified-heap-marking-visitor.cc",
"src/heap/cppgc-js/unified-heap-marking-visitor.h",
......
......@@ -12,7 +12,6 @@
#include "src/execution/isolate.h"
#include "src/flags/flags.h"
#include "src/heap/base/stack.h"
#include "src/heap/cppgc-js/cpp-snapshot.h"
#include "src/heap/cppgc-js/unified-heap-marking-state.h"
#include "src/heap/cppgc-js/unified-heap-marking-visitor.h"
#include "src/heap/cppgc/concurrent-marker.h"
......@@ -29,7 +28,6 @@
#include "src/heap/marking-worklist.h"
#include "src/heap/sweeper.h"
#include "src/init/v8.h"
#include "src/profiler/heap-profiler.h"
namespace v8 {
namespace internal {
......@@ -148,13 +146,6 @@ CppHeap::CppHeap(
kSupportsConservativeStackScan),
isolate_(*reinterpret_cast<Isolate*>(isolate)) {
CHECK(!FLAG_incremental_marking_wrappers);
isolate_.heap_profiler()->AddBuildEmbedderGraphCallback(&CppGraphBuilder::Run,
this);
}
CppHeap::~CppHeap() {
isolate_.heap_profiler()->RemoveBuildEmbedderGraphCallback(
&CppGraphBuilder::Run, this);
}
void CppHeap::RegisterV8References(
......
......@@ -22,7 +22,6 @@ class V8_EXPORT_PRIVATE CppHeap final : public cppgc::internal::HeapBase,
CppHeap(v8::Isolate* isolate,
const std::vector<std::unique_ptr<cppgc::CustomSpaceBase>>&
custom_spaces);
~CppHeap() final;
CppHeap(const CppHeap&) = delete;
CppHeap& operator=(const CppHeap&) = delete;
......
// 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 "src/heap/cppgc-js/cpp-snapshot.h"
#include <memory>
#include "include/cppgc/trace-trait.h"
#include "include/v8-cppgc.h"
#include "include/v8-profiler.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/mark-compact.h"
#include "src/profiler/heap-profiler.h"
namespace v8 {
namespace internal {
class CppGraphBuilderImpl;
class StateStorage;
class State;
using cppgc::internal::GCInfo;
using cppgc::internal::GlobalGCInfoTable;
using cppgc::internal::HeapObjectHeader;
// Node representing a C++ object on the heap.
class EmbedderNode : public v8::EmbedderGraph::Node {
public:
explicit EmbedderNode(const char* name) : name_(name) {}
~EmbedderNode() override = default;
const char* Name() final { return name_; }
size_t SizeInBytes() override { return 0; }
private:
const char* name_;
};
// Node representing an artificial root group, e.g., set of Persistent handles.
class EmbedderRootNode final : public EmbedderNode {
public:
explicit EmbedderRootNode(const char* name) : EmbedderNode(name) {}
~EmbedderRootNode() final = default;
bool IsRootNode() final { return true; }
size_t SizeInBytes() final { return 0; }
};
// Canonical state representing real and artificial (e.g. root) objects.
class StateBase {
public:
// Objects can either be hidden/visible, or depend on some other nodes while
// traversing the same SCC.
enum class Visibility {
kHidden,
kDependentVisibility,
kVisible,
};
StateBase(const void* key, size_t state_count, Visibility visibility,
EmbedderNode* node, bool visited)
: key_(key),
state_count_(state_count),
visibility_(visibility),
node_(node),
visited_(visited) {
DCHECK_NE(Visibility::kDependentVisibility, visibility);
}
// Visited objects have already been processed or are currently being
// processed, see also IsPending() below.
bool IsVisited() const { return visited_; }
// Pending objects are currently being processed as part of the same SCC.
bool IsPending() const { return pending_; }
bool IsVisibleNotDependent() {
auto v = GetVisibility();
CHECK_NE(Visibility::kDependentVisibility, v);
return v == Visibility::kVisible;
}
void set_node(EmbedderNode* node) {
CHECK_EQ(Visibility::kVisible, GetVisibility());
node_ = node;
}
EmbedderNode* get_node() {
CHECK_EQ(Visibility::kVisible, GetVisibility());
return node_;
}
protected:
const void* key_;
// State count keeps track of node processing order. It is used to create only
// dependencies on ancestors in the sub graph which ensures that there will be
// no cycles in dependencies.
const size_t state_count_;
Visibility visibility_;
StateBase* visibility_dependency_ = nullptr;
EmbedderNode* node_;
bool visited_;
bool pending_ = false;
Visibility GetVisibility() {
FollowDependencies();
return visibility_;
}
StateBase* FollowDependencies() {
if (visibility_ != Visibility::kDependentVisibility) {
CHECK_NULL(visibility_dependency_);
return this;
}
StateBase* current = this;
std::vector<StateBase*> dependencies;
while (current->visibility_dependency_ &&
current->visibility_dependency_ != current) {
DCHECK_EQ(Visibility::kDependentVisibility, current->visibility_);
dependencies.push_back(current);
current = current->visibility_dependency_;
}
auto new_visibility = Visibility::kDependentVisibility;
auto* new_visibility_dependency = current;
if (current->visibility_ == Visibility::kVisible) {
new_visibility = Visibility::kVisible;
new_visibility_dependency = nullptr;
} else if (!IsPending()) {
DCHECK(IsVisited());
// The object was not visible (above case). Having a dependency on itself
// or null means no visible object was found.
new_visibility = Visibility::kHidden;
new_visibility_dependency = nullptr;
}
current->visibility_ = new_visibility;
current->visibility_dependency_ = new_visibility_dependency;
for (auto* state : dependencies) {
state->visibility_ = new_visibility;
state->visibility_dependency_ = new_visibility_dependency;
}
return current;
}
friend class State;
};
class State final : public StateBase {
public:
State(const HeapObjectHeader& header, size_t state_count)
: StateBase(&header, state_count, Visibility::kHidden, nullptr, false) {}
const HeapObjectHeader* header() const {
return static_cast<const HeapObjectHeader*>(key_);
}
void MarkVisited() { visited_ = true; }
void MarkPending() { pending_ = true; }
void UnmarkPending() { pending_ = false; }
void MarkVisible() {
visibility_ = Visibility::kVisible;
visibility_dependency_ = nullptr;
}
void MarkDependentVisibility(StateBase* dependency) {
// Follow and update dependencies as much as possible.
dependency = dependency->FollowDependencies();
DCHECK(dependency->IsVisited());
if (visibility_ == StateBase::Visibility::kVisible) {
// Already visible, no dependency needed.
DCHECK_NULL(visibility_dependency_);
return;
}
if (dependency->visibility_ == Visibility::kVisible) {
// Simple case: Dependency is visible.
visibility_ = Visibility::kVisible;
visibility_dependency_ = nullptr;
return;
}
if ((visibility_dependency_ &&
(visibility_dependency_->state_count_ > dependency->state_count_)) ||
(!visibility_dependency_ &&
(state_count_ > dependency->state_count_))) {
// Only update when new state_count_ < original state_count_. This
// ensures that we pick an ancestor as dependency and not a child which
// is guaranteed to converge to an answer.
//
// Dependency is now
// a) either pending with unknown visibility (same call chain), or
// b) not pending and has defined visibility.
//
// It's not possible to point to a state that is not pending but has
// dependent visibility because dependencies are updated to the top-most
// dependency at the beginning of method.
if (dependency->IsPending()) {
visibility_ = Visibility::kDependentVisibility;
visibility_dependency_ = dependency;
} else {
CHECK_NE(Visibility::kDependentVisibility, dependency->visibility_);
if (dependency->visibility_ == Visibility::kVisible) {
visibility_ = Visibility::kVisible;
visibility_dependency_ = nullptr;
}
}
}
}
};
// Root states are similar to regular states with the difference that they can
// have named edges (source location of the root) that aid debugging.
class RootState final : public StateBase {
public:
RootState(EmbedderRootNode* node, size_t state_count)
// Root states are always visited, visible, and have a node attached.
: StateBase(node, state_count, Visibility::kVisible, node, true) {}
void AddNamedEdge(std::unique_ptr<const char> edge_name) {
named_edges_.push_back(std::move(edge_name));
}
private:
// Edge names are passed to V8 but are required to be held alive from the
// embedder until the snapshot is compiled.
std::vector<std::unique_ptr<const char>> named_edges_;
};
// Abstraction for storing states. Storage allows for creation and lookup of
// different state objects.
class StateStorage final {
public:
bool StateExists(const void* key) const {
return states_.find(key) != states_.end();
}
StateBase& GetExistingState(const void* key) const {
CHECK(StateExists(key));
return *states_.at(key).get();
}
State& GetExistingState(const HeapObjectHeader& header) const {
return static_cast<State&>(GetExistingState(&header));
}
State& GetOrCreateState(const HeapObjectHeader& header) {
if (!StateExists(&header)) {
auto it = states_.insert(std::make_pair(
&header, std::make_unique<State>(header, ++state_count_)));
DCHECK(it.second);
USE(it);
}
return GetExistingState(header);
}
RootState& CreateRootState(EmbedderRootNode* root_node) {
CHECK(!StateExists(root_node));
auto it = states_.insert(std::make_pair(
root_node, std::make_unique<RootState>(root_node, ++state_count_)));
DCHECK(it.second);
USE(it);
return static_cast<RootState&>(*it.first->second.get());
}
template <typename Callback>
void ForAllVisibleStates(Callback callback) {
for (auto& state : states_) {
if (state.second->IsVisibleNotDependent()) {
callback(state.second.get());
}
}
}
private:
std::unordered_map<const void*, std::unique_ptr<StateBase>> states_;
size_t state_count_ = 0;
};
// 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.
//
// C++ objects come in two versions.
// a. Named objects that have been assigned a name through NameProvider.
// b. Unnamed objects, that are potentially hidden if the build configuration
// requires Oilpan to hide such names. Hidden objects have their name
// set to NameProvider::kHiddenName.
//
// The main challenge for the algorithm is to avoid blowing up the final object
// graph with hidden nodes that do not carry information. For that reason, the
// algorithm filters SCCs of only hidden objects, e.g.:
// ... -> (object) -> (object) -> (hidden) -> (hidden)
// In this case the (hidden) objects are filtered from the graph. The trickiest
// part is maintaining visibility state for objects referencing other objects
// that are currently being processed.
//
// Main algorithm idea (two passes):
// 1. First pass marks all non-hidden objects and those that transitively reach
// non-hidden objects as visible. Details:
// - Iterate over all objects.
// - If object is non-hidden mark it as visible and also mark parent as
// visible if needed.
// - If object is hidden, traverse children as DFS to find non-hidden
// objects. Post-order process the objects and mark those objects as
// visible that have child nodes that are visible themselves.
// - Maintain an epoch counter (StateStorage::state_count_) to allow
// deferring the visibility decision to other objects in the same SCC. This
// is similar to the "lowlink" value in Tarjan's algorithm for SCC.
// - After the first pass it is guaranteed that all deferred visibility
// decisions can be resolved.
// 2. Second pass adds nodes and edges for all visible objects.
// - Upon first checking the visibility state of an object, all deferred
// visibility states are resolved.
//
// For practical reasons, the recursion is transformed into an iteration. We do
// do not use plain Tarjan's algorithm to avoid another pass over all nodes to
// create SCCs.
class CppGraphBuilderImpl final {
public:
CppGraphBuilderImpl(CppHeap& cpp_heap, v8::EmbedderGraph& graph)
: cpp_heap_(cpp_heap), graph_(graph) {}
void Run();
void VisitForVisibility(State* parent, const HeapObjectHeader&);
void VisitRootForGraphBuilding(RootState&, const HeapObjectHeader&,
const cppgc::SourceLocation&);
void ProcessPendingObjects();
EmbedderRootNode* AddRootNode(const char* name) {
return static_cast<EmbedderRootNode*>(graph_.AddNode(
std::unique_ptr<v8::EmbedderGraph::Node>{new EmbedderRootNode(name)}));
}
EmbedderNode* AddNode(const HeapObjectHeader& header) {
return static_cast<EmbedderNode*>(
graph_.AddNode(std::unique_ptr<v8::EmbedderGraph::Node>{
new EmbedderNode(header.GetName().value)}));
}
void AddEdge(State& parent, const HeapObjectHeader& header) {
DCHECK(parent.IsVisibleNotDependent());
auto& current = states_.GetExistingState(header);
if (!current.IsVisibleNotDependent()) return;
// Both states are visible. Create nodes in case this is the first edge
// created for any of them.
if (!parent.get_node()) {
parent.set_node(AddNode(*parent.header()));
}
if (!current.get_node()) {
current.set_node(AddNode(header));
}
graph_.AddEdge(parent.get_node(), current.get_node());
}
void AddRootEdge(RootState& root, State& child, std::string edge_name) {
DCHECK(root.IsVisibleNotDependent());
if (!child.IsVisibleNotDependent()) return;
// Root states always have a node set.
DCHECK_NOT_NULL(root.get_node());
if (!child.get_node()) {
child.set_node(AddNode(*child.header()));
}
if (!edge_name.empty()) {
// V8's API is based on raw C strings. Allocate and temporarily keep the
// edge name alive from the corresponding node.
const size_t len = edge_name.length();
char* raw_location_string = new char[len + 1];
strncpy(raw_location_string, edge_name.c_str(), len);
raw_location_string[len] = 0;
std::unique_ptr<const char> holder(raw_location_string);
graph_.AddEdge(root.get_node(), child.get_node(), holder.get());
root.AddNamedEdge(std::move(holder));
return;
}
graph_.AddEdge(root.get_node(), child.get_node());
}
private:
class WorkstackItemBase;
class VisitationItem;
class VisitationDoneItem;
CppHeap& cpp_heap_;
v8::EmbedderGraph& graph_;
StateStorage states_;
std::vector<std::unique_ptr<WorkstackItemBase>> workstack_;
};
// Iterating live objects to mark them as visible if needed.
class LiveObjectsForVisibilityIterator final
: public cppgc::internal::HeapVisitor<LiveObjectsForVisibilityIterator> {
friend class cppgc::internal::HeapVisitor<LiveObjectsForVisibilityIterator>;
public:
explicit LiveObjectsForVisibilityIterator(CppGraphBuilderImpl& graph_builder)
: graph_builder_(graph_builder) {}
private:
bool VisitHeapObjectHeader(HeapObjectHeader* header) {
if (header->IsFree()) return true;
graph_builder_.VisitForVisibility(nullptr, *header);
graph_builder_.ProcessPendingObjects();
return true;
}
CppGraphBuilderImpl& graph_builder_;
};
class ParentScope final {
public:
explicit ParentScope(StateBase& parent) : parent_(parent) {}
RootState& ParentAsRootState() const {
return static_cast<RootState&>(parent_);
}
State& ParentAsRegularState() const { return static_cast<State&>(parent_); }
private:
StateBase& parent_;
};
class VisiblityVisitor final : public JSVisitor {
public:
explicit VisiblityVisitor(CppGraphBuilderImpl& graph_builder,
const ParentScope& parent_scope)
: JSVisitor(cppgc::internal::VisitorFactory::CreateKey()),
graph_builder_(graph_builder),
parent_scope_(parent_scope) {}
// C++ handling.
void Visit(const void*, cppgc::TraceDescriptor desc) final {
graph_builder_.VisitForVisibility(
&parent_scope_.ParentAsRegularState(),
HeapObjectHeader::FromPayload(desc.base_object_payload));
}
void VisitRoot(const void*, cppgc::TraceDescriptor,
const cppgc::SourceLocation&) final {}
void VisitWeakRoot(const void*, cppgc::TraceDescriptor, cppgc::WeakCallback,
const void*, const cppgc::SourceLocation&) final {}
// JS handling.
void Visit(const JSMemberBase& ref) final {}
private:
CppGraphBuilderImpl& graph_builder_;
const ParentScope& parent_scope_;
};
class GraphBuildingVisitor final : public JSVisitor {
public:
GraphBuildingVisitor(CppGraphBuilderImpl& graph_builder,
const ParentScope& parent_scope)
: JSVisitor(cppgc::internal::VisitorFactory::CreateKey()),
graph_builder_(graph_builder),
parent_scope_(parent_scope) {}
// C++ handling.
void Visit(const void*, cppgc::TraceDescriptor desc) final {
graph_builder_.AddEdge(
parent_scope_.ParentAsRegularState(),
HeapObjectHeader::FromPayload(desc.base_object_payload));
}
void VisitRoot(const void*, cppgc::TraceDescriptor desc,
const cppgc::SourceLocation& loc) final {
graph_builder_.VisitRootForGraphBuilding(
parent_scope_.ParentAsRootState(),
HeapObjectHeader::FromPayload(desc.base_object_payload), loc);
}
void VisitWeakRoot(const void*, cppgc::TraceDescriptor, cppgc::WeakCallback,
const void*, const cppgc::SourceLocation&) final {}
// JS handling.
void Visit(const JSMemberBase& ref) final {}
private:
CppGraphBuilderImpl& graph_builder_;
const ParentScope& parent_scope_;
};
// Base class for transforming recursion into iteration. Items are processed
// in stack fashion.
class CppGraphBuilderImpl::WorkstackItemBase {
public:
WorkstackItemBase(State* parent, State& current)
: parent_(parent), current_(current) {}
virtual ~WorkstackItemBase() = default;
virtual void Process(CppGraphBuilderImpl&) = 0;
protected:
State* parent_;
State& current_;
};
void CppGraphBuilderImpl::ProcessPendingObjects() {
while (!workstack_.empty()) {
std::unique_ptr<WorkstackItemBase> item = std::move(workstack_.back());
workstack_.pop_back();
item->Process(*this);
}
}
// Post-order processing of an object. It's guaranteed that all children have
// been processed first.
class CppGraphBuilderImpl::VisitationDoneItem final : public WorkstackItemBase {
public:
VisitationDoneItem(State* parent, State& current)
: WorkstackItemBase(parent, current) {}
void Process(CppGraphBuilderImpl& graph_builder) final {
CHECK(parent_);
parent_->MarkDependentVisibility(&current_);
current_.UnmarkPending();
}
};
class CppGraphBuilderImpl::VisitationItem final : public WorkstackItemBase {
public:
VisitationItem(State* parent, State& current)
: WorkstackItemBase(parent, current) {}
void Process(CppGraphBuilderImpl& graph_builder) final {
if (parent_) {
// Re-add the same object for post-order processing. This must happen
// lazily, as the parent's visibility depends on its children.
graph_builder.workstack_.push_back(std::unique_ptr<WorkstackItemBase>{
new VisitationDoneItem(parent_, current_)});
}
ParentScope parent_scope(current_);
VisiblityVisitor object_visitor(graph_builder, parent_scope);
current_.header()->Trace(&object_visitor);
if (!parent_) {
current_.UnmarkPending();
}
}
};
void CppGraphBuilderImpl::VisitForVisibility(State* parent,
const HeapObjectHeader& header) {
auto& current = states_.GetOrCreateState(header);
if (current.IsVisited()) {
// Avoid traversing into already visited subgraphs and just update the state
// based on a previous result.
if (parent) {
parent->MarkDependentVisibility(&current);
}
return;
}
current.MarkVisited();
if (header.GetName().name_was_hidden) {
current.MarkPending();
workstack_.push_back(std::unique_ptr<WorkstackItemBase>{
new VisitationItem(parent, current)});
} else {
// No need to mark/unmark pending as the node is immediately processed.
current.MarkVisible();
if (parent) {
// Eagerly update a parent object as its visibility state is now fixed.
parent->MarkVisible();
}
}
}
void CppGraphBuilderImpl::VisitRootForGraphBuilding(
RootState& root, const HeapObjectHeader& header,
const cppgc::SourceLocation& loc) {
State& current = states_.GetExistingState(header);
if (!current.IsVisibleNotDependent()) return;
AddRootEdge(root, current, loc.ToString());
}
void CppGraphBuilderImpl::Run() {
// First pass: Figure out which objects should be included in the graph -- see
// class-level comment on CppGraphBuilder.
LiveObjectsForVisibilityIterator visitor(*this);
visitor.Traverse(&cpp_heap_.raw_heap());
// Second pass: Add graph nodes for objects that must be shown.
states_.ForAllVisibleStates([this](StateBase* state) {
ParentScope parent_scope(*state);
GraphBuildingVisitor object_visitor(*this, parent_scope);
// No roots have been created so far, so all StateBase objects are State.
static_cast<State*>(state)->header()->Trace(&object_visitor);
});
// Add roots.
{
ParentScope parent_scope(states_.CreateRootState(AddRootNode("C++ roots")));
GraphBuildingVisitor object_visitor(*this, parent_scope);
cpp_heap_.GetStrongPersistentRegion().Trace(&object_visitor);
}
{
ParentScope parent_scope(
states_.CreateRootState(AddRootNode("C++ cross-thread roots")));
GraphBuildingVisitor object_visitor(*this, parent_scope);
cpp_heap_.GetStrongCrossThreadPersistentRegion().Trace(&object_visitor);
}
}
// static
void CppGraphBuilder::Run(v8::Isolate* isolate, v8::EmbedderGraph* graph,
void* data) {
CppHeap* cpp_heap = static_cast<CppHeap*>(data);
CHECK_NOT_NULL(cpp_heap);
CHECK_NOT_NULL(graph);
CppGraphBuilderImpl graph_builder(*cpp_heap, *graph);
graph_builder.Run();
}
} // namespace internal
} // namespace v8
// 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_HEAP_CPPGC_JS_CPP_SNAPSHOT_H_
#define V8_HEAP_CPPGC_JS_CPP_SNAPSHOT_H_
#include "src/base/macros.h"
namespace v8 {
class Isolate;
class EmbedderGraph;
namespace internal {
class V8_EXPORT_PRIVATE CppGraphBuilder final {
public:
// Add the C++ snapshot to the existing |graph|. See CppGraphBuilderImpl for
// algorithm internals.
static void Run(v8::Isolate* isolate, v8::EmbedderGraph* graph, void* data);
CppGraphBuilder() = delete;
};
} // namespace internal
} // namespace v8
#endif // V8_HEAP_CPPGC_JS_CPP_SNAPSHOT_H_
......@@ -103,8 +103,8 @@ void HeapEntry::SetNamedAutoIndexReference(HeapGraphEdge::Type type,
SetNamedReference(type, name, child);
}
void HeapEntry::Print(const char* prefix, const char* edge_name, int max_depth,
int indent) const {
void HeapEntry::Print(
const char* prefix, const char* edge_name, int max_depth, int indent) {
STATIC_ASSERT(sizeof(unsigned) == sizeof(id()));
base::OS::Print("%6zu @%6u %*c %s%s: ", self_size(), id(), indent, ' ',
prefix, edge_name);
......@@ -162,7 +162,7 @@ void HeapEntry::Print(const char* prefix, const char* edge_name, int max_depth,
}
}
const char* HeapEntry::TypeAsString() const {
const char* HeapEntry::TypeAsString() {
switch (type()) {
case kHidden: return "/hidden/";
case kObject: return "/object/";
......
......@@ -151,12 +151,12 @@ class HeapEntry {
StringsStorage* strings);
V8_EXPORT_PRIVATE void Print(const char* prefix, const char* edge_name,
int max_depth, int indent) const;
int max_depth, int indent);
private:
V8_INLINE std::vector<HeapGraphEdge*>::iterator children_begin() const;
V8_INLINE std::vector<HeapGraphEdge*>::iterator children_end() const;
const char* TypeAsString() const;
const char* TypeAsString();
unsigned type_: 4;
unsigned index_ : 28; // Supports up to ~250M objects.
......@@ -196,9 +196,7 @@ class HeapSnapshot {
return gc_subroot_entries_[static_cast<int>(root)];
}
std::deque<HeapEntry>& entries() { return entries_; }
const std::deque<HeapEntry>& entries() const { return entries_; }
std::deque<HeapGraphEdge>& edges() { return edges_; }
const std::deque<HeapGraphEdge>& edges() const { return edges_; }
std::vector<HeapGraphEdge*>& children() { return children_; }
const std::vector<SourceLocation>& locations() const { return locations_; }
void RememberLastJSObjectId();
......
......@@ -297,7 +297,6 @@ v8_source_set("unittests_sources") {
"heap/safepoint-unittest.cc",
"heap/slot-set-unittest.cc",
"heap/spaces-unittest.cc",
"heap/unified-heap-snapshot-unittest.cc",
"heap/unified-heap-unittest.cc",
"heap/unmapper-unittest.cc",
"heap/worklist-unittest.cc",
......
// 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 <cstring>
#include "include/cppgc/allocation.h"
#include "include/cppgc/cross-thread-persistent.h"
#include "include/cppgc/garbage-collected.h"
#include "include/cppgc/name-provider.h"
#include "include/cppgc/persistent.h"
#include "include/cppgc/platform.h"
#include "include/v8-profiler.h"
#include "src/api/api-inl.h"
#include "src/heap/cppgc-js/cpp-heap.h"
#include "src/objects/objects-inl.h"
#include "src/profiler/heap-snapshot-generator-inl.h"
#include "src/profiler/heap-snapshot-generator.h"
#include "test/unittests/heap/heap-utils.h"
namespace v8 {
namespace internal {
namespace {
class UnifiedHeapSnapshotTest : public TestWithHeapInternals {
public:
UnifiedHeapSnapshotTest()
: saved_incremental_marking_wrappers_(FLAG_incremental_marking_wrappers) {
FLAG_incremental_marking_wrappers = false;
cppgc::InitializeProcess(V8::GetCurrentPlatform()->GetPageAllocator());
cpp_heap_ = std::make_unique<CppHeap>(
v8_isolate(), std::vector<std::unique_ptr<cppgc::CustomSpaceBase>>());
heap()->SetEmbedderHeapTracer(&cpp_heap());
}
~UnifiedHeapSnapshotTest() override {
heap()->SetEmbedderHeapTracer(nullptr);
FLAG_incremental_marking_wrappers = saved_incremental_marking_wrappers_;
cppgc::ShutdownProcess();
}
CppHeap& cpp_heap() const { return *cpp_heap_.get(); }
cppgc::AllocationHandle& allocation_handle() const {
return cpp_heap().object_allocator();
}
const v8::HeapSnapshot* TakeHeapSnapshot() {
v8::HeapProfiler* heap_profiler = v8_isolate()->GetHeapProfiler();
return heap_profiler->TakeHeapSnapshot();
}
private:
std::unique_ptr<CppHeap> cpp_heap_;
bool saved_incremental_marking_wrappers_;
};
bool IsValidSnapshot(const v8::HeapSnapshot* snapshot, int depth = 3) {
const HeapSnapshot* heap_snapshot =
reinterpret_cast<const HeapSnapshot*>(snapshot);
std::unordered_set<const HeapEntry*> visited;
for (const HeapGraphEdge& edge : heap_snapshot->edges()) {
visited.insert(edge.to());
}
size_t unretained_entries_count = 0;
for (const HeapEntry& entry : heap_snapshot->entries()) {
if (visited.find(&entry) == visited.end() && entry.id() != 1) {
entry.Print("entry with no retainer", "", depth, 0);
++unretained_entries_count;
}
}
return unretained_entries_count == 0;
}
bool ContainsRetainingPath(const v8::HeapSnapshot& snapshot,
const std::vector<const std::string> retaining_path,
bool debug_retaining_path = false) {
const HeapSnapshot& heap_snapshot =
reinterpret_cast<const HeapSnapshot&>(snapshot);
std::vector<HeapEntry*> haystack = {heap_snapshot.root()};
for (size_t i = 0; i < retaining_path.size(); ++i) {
const std::string& needle = retaining_path[i];
std::vector<HeapEntry*> new_haystack;
for (HeapEntry* parent : haystack) {
for (int j = 0; j < parent->children_count(); j++) {
HeapEntry* child = parent->child(j)->to();
if (0 == strcmp(child->name(), needle.c_str())) {
new_haystack.push_back(child);
}
}
}
if (new_haystack.empty()) {
if (debug_retaining_path) {
fprintf(stderr,
"#\n# Could not find object with name '%s'\n#\n# Path:\n",
needle.c_str());
for (size_t j = 0; j < retaining_path.size(); ++j) {
fprintf(stderr, "# - '%s'%s\n", retaining_path[j].c_str(),
i == j ? "\t<--- not found" : "");
}
fprintf(stderr, "#\n");
}
return false;
}
std::swap(haystack, new_haystack);
}
return true;
}
class BaseWithoutName : public cppgc::GarbageCollected<BaseWithoutName> {
public:
static constexpr const char kExpectedName[] =
"v8::internal::(anonymous namespace)::BaseWithoutName";
virtual void Trace(cppgc::Visitor* v) const {
v->Trace(next);
v->Trace(next2);
}
cppgc::Member<BaseWithoutName> next;
cppgc::Member<BaseWithoutName> next2;
};
// static
constexpr const char BaseWithoutName::kExpectedName[];
class GCed final : public BaseWithoutName, public cppgc::NameProvider {
public:
static constexpr const char kExpectedName[] = "GCed";
void Trace(cppgc::Visitor* v) const final { BaseWithoutName::Trace(v); }
const char* GetName() const final { return "GCed"; }
};
// static
constexpr const char GCed::kExpectedName[];
constexpr const char kExpectedCppRootsName[] = "C++ roots";
constexpr const char kExpectedCppCrossThreadRootsName[] =
"C++ cross-thread roots";
template <typename T>
constexpr const char* GetExpectedName() {
if (std::is_base_of<cppgc::NameProvider, T>::value ||
!cppgc::NameProvider::HideInternalNames()) {
return T::kExpectedName;
} else {
return cppgc::NameProvider::kHiddenName;
}
}
} // namespace
TEST_F(UnifiedHeapSnapshotTest, EmptySnapshot) {
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
EXPECT_TRUE(IsValidSnapshot(snapshot));
}
TEST_F(UnifiedHeapSnapshotTest, RetainedByCppRoot) {
cppgc::Persistent<GCed> gced =
cppgc::MakeGarbageCollected<GCed>(allocation_handle());
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_TRUE(
ContainsRetainingPath(*snapshot, {
kExpectedCppRootsName, // NOLINT
GetExpectedName<GCed>() // NOLINT
}));
}
TEST_F(UnifiedHeapSnapshotTest, RetainedByCppCrossThreadRoot) {
cppgc::subtle::CrossThreadPersistent<GCed> gced =
cppgc::MakeGarbageCollected<GCed>(allocation_handle());
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_TRUE(ContainsRetainingPath(
*snapshot, {
kExpectedCppCrossThreadRootsName, // NOLINT
GetExpectedName<GCed>() // NOLINT
}));
}
TEST_F(UnifiedHeapSnapshotTest, RetainingUnnamedType) {
cppgc::Persistent<BaseWithoutName> base_without_name =
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle());
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
EXPECT_TRUE(IsValidSnapshot(snapshot));
if (cppgc::NameProvider::HideInternalNames()) {
EXPECT_FALSE(ContainsRetainingPath(
*snapshot, {kExpectedCppRootsName, cppgc::NameProvider::kHiddenName}));
} else {
EXPECT_TRUE(ContainsRetainingPath(
*snapshot, {
kExpectedCppRootsName, // NOLINT
GetExpectedName<BaseWithoutName>() // NOLINT
}));
}
}
TEST_F(UnifiedHeapSnapshotTest, RetainingNamedThroughUnnamed) {
cppgc::Persistent<BaseWithoutName> base_without_name =
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle());
base_without_name->next =
cppgc::MakeGarbageCollected<GCed>(allocation_handle());
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_TRUE(ContainsRetainingPath(
*snapshot, {
kExpectedCppRootsName, // NOLINT
GetExpectedName<BaseWithoutName>(), // NOLINT
GetExpectedName<GCed>() // NOLINT
}));
}
TEST_F(UnifiedHeapSnapshotTest, PendingCallStack) {
// Test ensures that the algorithm handles references into the current call
// stack.
//
// Graph:
// Persistent -> BaseWithoutName (2) <-> BaseWithoutName (1) -> GCed (3)
//
// Visitation order is (1)->(2)->(3) which is a corner case, as when following
// back from (2)->(1) the object in (1) is already visited and will only later
// be marked as visible.
auto* first =
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle());
auto* second =
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle());
first->next = second;
first->next->next = first;
auto* third = cppgc::MakeGarbageCollected<GCed>(allocation_handle());
first->next2 = third;
cppgc::Persistent<BaseWithoutName> holder(second);
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_TRUE(
ContainsRetainingPath(*snapshot,
{
kExpectedCppRootsName, // NOLINT
GetExpectedName<BaseWithoutName>(), // NOLINT
GetExpectedName<BaseWithoutName>(), // NOLINT
GetExpectedName<GCed>() // NOLINT
}));
}
TEST_F(UnifiedHeapSnapshotTest, ReferenceToFinishedSCC) {
// Test ensures that the algorithm handles reference into an already finished
// SCC that is marked as hidden whereas the current SCC would resolve to
// visible.
//
// Graph:
// Persistent -> BaseWithoutName (1)
// Persistent -> BaseWithoutName (2)
// + <-> BaseWithoutName (3) -> BaseWithoutName (1)
// + -> GCed (4)
//
// Visitation order (1)->(2)->(3)->(1) which is a corner case as (3) would set
// a dependency on (1) which is hidden. Instead (3) should set a dependency on
// (2) as (1) resolves to hidden whereas (2) resolves to visible. The test
// ensures that resolved hidden dependencies are ignored.
cppgc::Persistent<BaseWithoutName> hidden_holder(
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle()));
auto* first =
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle());
auto* second =
cppgc::MakeGarbageCollected<BaseWithoutName>(allocation_handle());
first->next = second;
second->next = *hidden_holder;
second->next2 = first;
first->next2 = cppgc::MakeGarbageCollected<GCed>(allocation_handle());
cppgc::Persistent<BaseWithoutName> holder(first);
const v8::HeapSnapshot* snapshot = TakeHeapSnapshot();
EXPECT_TRUE(IsValidSnapshot(snapshot));
EXPECT_TRUE(
ContainsRetainingPath(*snapshot,
{
kExpectedCppRootsName, // NOLINT
GetExpectedName<BaseWithoutName>(), // NOLINT
GetExpectedName<BaseWithoutName>(), // NOLINT
GetExpectedName<BaseWithoutName>(), // NOLINT
GetExpectedName<GCed>() // 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