Commit 26ca6abd authored by Michael Lippautz's avatar Michael Lippautz Committed by Commit Bot

api,handles: Support for on-stack TracedReference

- Introduces a API to set top of the stack through
  EmbedderHeapTracer::SetStackTop.
- Introduces a new API to inform V8 about an empty embedder stack.
- Switch internal representation of TracedReference
  for on-stack handles to a proper stack that considers all
  contained handles as roots.
- Handle garbage is avoided by cleaning up on handle creation or
  GC.

Design doc: https://bit.ly/on-stack-traced-reference

Bug: chromium:1040038
Change-Id: I927ef0abb268fdb5853c9e17b1bc96e2491cf101
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1993973
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Reviewed-by: 's avatarDominik Inführ <dinfuehr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#65757}
parent 1077308c
...@@ -1048,6 +1048,11 @@ class TracedGlobal : public TracedReferenceBase<T> { ...@@ -1048,6 +1048,11 @@ class TracedGlobal : public TracedReferenceBase<T> {
* to ensure that the handle is not accessed once the V8 object has been * to ensure that the handle is not accessed once the V8 object has been
* reclaimed. This can happen when the handle is not passed through the * reclaimed. This can happen when the handle is not passed through the
* EmbedderHeapTracer. For more details see TracedReferenceBase. * EmbedderHeapTracer. For more details see TracedReferenceBase.
*
* The reference assumes the embedder has precise knowledge about references at
* all times. In case V8 needs to separately handle on-stack references, the
* embedder is required to set the stack start through
* |EmbedderHeapTracer::SetStackStart|.
*/ */
template <typename T> template <typename T>
class TracedReference : public TracedReferenceBase<T> { class TracedReference : public TracedReferenceBase<T> {
...@@ -7870,6 +7875,17 @@ class V8_EXPORT EmbedderHeapTracer { ...@@ -7870,6 +7875,17 @@ class V8_EXPORT EmbedderHeapTracer {
*/ */
void IterateTracedGlobalHandles(TracedGlobalHandleVisitor* visitor); void IterateTracedGlobalHandles(TracedGlobalHandleVisitor* visitor);
/**
* Called by the embedder to set the start of the stack which is e.g. used by
* V8 to determine whether handles are used from stack or heap.
*/
void SetStackStart(void* stack_start);
/**
* Called by the embedder to notify V8 of an empty execution stack.
*/
void NotifyEmptyEmbedderStack();
/** /**
* Called by v8 to register internal fields of found wrappers. * Called by v8 to register internal fields of found wrappers.
* *
......
...@@ -378,12 +378,11 @@ ...@@ -378,12 +378,11 @@
// A macro used to tell the compiler to never inline a particular function. // A macro used to tell the compiler to never inline a particular function.
// Don't bother for debug builds.
// Use like: // Use like:
// V8_NOINLINE int GetMinusOne() { return -1; } // V8_NOINLINE int GetMinusOne() { return -1; }
#if !defined(DEBUG) && V8_HAS_ATTRIBUTE_NOINLINE #if V8_HAS_ATTRIBUTE_NOINLINE
# define V8_NOINLINE __attribute__((noinline)) # define V8_NOINLINE __attribute__((noinline))
#elif !defined(DEBUG) && V8_HAS_DECLSPEC_NOINLINE #elif V8_HAS_DECLSPEC_NOINLINE
# define V8_NOINLINE __declspec(noinline) # define V8_NOINLINE __declspec(noinline)
#else #else
# define V8_NOINLINE /* NOT SUPPORTED */ # define V8_NOINLINE /* NOT SUPPORTED */
......
...@@ -10750,6 +10750,19 @@ void HeapProfiler::RemoveBuildEmbedderGraphCallback( ...@@ -10750,6 +10750,19 @@ void HeapProfiler::RemoveBuildEmbedderGraphCallback(
callback, data); callback, data);
} }
void EmbedderHeapTracer::SetStackStart(void* stack_start) {
CHECK(isolate_);
reinterpret_cast<i::Isolate*>(isolate_)->global_handles()->SetStackStart(
stack_start);
}
void EmbedderHeapTracer::NotifyEmptyEmbedderStack() {
CHECK(isolate_);
reinterpret_cast<i::Isolate*>(isolate_)
->global_handles()
->NotifyEmptyEmbedderStack();
}
void EmbedderHeapTracer::FinalizeTracing() { void EmbedderHeapTracer::FinalizeTracing() {
if (isolate_) { if (isolate_) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(isolate_); i::Isolate* isolate = reinterpret_cast<i::Isolate*>(isolate_);
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
#include "src/handles/global-handles.h" #include "src/handles/global-handles.h"
#include <map>
#include "src/api/api-inl.h" #include "src/api/api-inl.h"
#include "src/base/compiler-specific.h" #include "src/base/compiler-specific.h"
#include "src/execution/vm-state-inl.h" #include "src/execution/vm-state-inl.h"
...@@ -14,6 +16,7 @@ ...@@ -14,6 +16,7 @@
#include "src/objects/objects-inl.h" #include "src/objects/objects-inl.h"
#include "src/objects/slots.h" #include "src/objects/slots.h"
#include "src/objects/visitors.h" #include "src/objects/visitors.h"
#include "src/sanitizer/asan.h"
#include "src/tasks/cancelable-task.h" #include "src/tasks/cancelable-task.h"
#include "src/tasks/task-utils.h" #include "src/tasks/task-utils.h"
...@@ -617,6 +620,12 @@ class GlobalHandles::TracedNode final ...@@ -617,6 +620,12 @@ class GlobalHandles::TracedNode final
public: public:
TracedNode() { set_in_young_list(false); } TracedNode() { set_in_young_list(false); }
// Copy and move ctors are used when constructing a TracedNode when recording
// a node for on-stack data structures. (Older compilers may refer to copy
// instead of move ctor.)
TracedNode(TracedNode&& other) V8_NOEXCEPT = default;
TracedNode(const TracedNode& other) V8_NOEXCEPT = default;
enum State { FREE = 0, NORMAL, NEAR_DEATH }; enum State { FREE = 0, NORMAL, NEAR_DEATH };
State state() const { return NodeState::decode(flags_); } State state() const { return NodeState::decode(flags_); }
...@@ -641,6 +650,9 @@ class GlobalHandles::TracedNode final ...@@ -641,6 +650,9 @@ class GlobalHandles::TracedNode final
void clear_markbit() { flags_ = Markbit::update(flags_, false); } void clear_markbit() { flags_ = Markbit::update(flags_, false); }
void set_markbit() { flags_ = Markbit::update(flags_, true); } void set_markbit() { flags_ = Markbit::update(flags_, true); }
bool is_on_stack() const { return IsOnStack::decode(flags_); }
void set_is_on_stack(bool v) { flags_ = IsOnStack::update(flags_, v); }
void SetFinalizationCallback(void* parameter, void SetFinalizationCallback(void* parameter,
WeakCallbackInfo<void>::Callback callback) { WeakCallbackInfo<void>::Callback callback) {
set_parameter(parameter); set_parameter(parameter);
...@@ -683,12 +695,14 @@ class GlobalHandles::TracedNode final ...@@ -683,12 +695,14 @@ class GlobalHandles::TracedNode final
using IsRoot = IsInYoungList::Next<bool, 1>; using IsRoot = IsInYoungList::Next<bool, 1>;
using HasDestructor = IsRoot::Next<bool, 1>; using HasDestructor = IsRoot::Next<bool, 1>;
using Markbit = HasDestructor::Next<bool, 1>; using Markbit = HasDestructor::Next<bool, 1>;
using IsOnStack = Markbit::Next<bool, 1>;
void ClearImplFields() { void ClearImplFields() {
set_root(true); set_root(true);
// Nodes are black allocated for simplicity. // Nodes are black allocated for simplicity.
set_markbit(); set_markbit();
callback_ = nullptr; callback_ = nullptr;
set_is_on_stack(false);
} }
void CheckImplFieldsAreCleared() const { void CheckImplFieldsAreCleared() const {
...@@ -701,13 +715,150 @@ class GlobalHandles::TracedNode final ...@@ -701,13 +715,150 @@ class GlobalHandles::TracedNode final
friend class NodeBase<GlobalHandles::TracedNode>; friend class NodeBase<GlobalHandles::TracedNode>;
DISALLOW_COPY_AND_ASSIGN(TracedNode); DISALLOW_ASSIGN(TracedNode);
};
// Space to keep track of on-stack handles (e.g. TracedReference). Such
// references are treated as root for any V8 garbage collection. The data
// structure is self healing and pessimistally filters outdated entries on
// insertion and iteration.
//
// Design doc: http://bit.ly/on-stack-traced-reference
class GlobalHandles::OnStackTracedNodeSpace final {
public:
explicit OnStackTracedNodeSpace(GlobalHandles* global_handles)
: global_handles_(global_handles) {}
void SetStackStart(void* stack_start) {
CHECK(on_stack_nodes_.empty());
stack_start_ =
GetStackAddressForSlot(reinterpret_cast<uintptr_t>(stack_start));
}
bool IsOnStack(uintptr_t slot) const {
const uintptr_t address = GetStackAddressForSlot(slot);
return stack_start_ >= address && address > GetCurrentStackPosition();
}
void Iterate(RootVisitor* v);
TracedNode* Acquire(Object value, uintptr_t address);
void CleanupBelowCurrentStackPosition();
void NotifyEmptyEmbedderStack();
size_t NumberOfHandlesForTesting() const { return on_stack_nodes_.size(); }
private:
struct NodeEntry {
TracedNode node;
// Used to find back to GlobalHandles from a Node on copy. Needs to follow
// node.
GlobalHandles* global_handles;
};
uintptr_t GetStackAddressForSlot(uintptr_t slot) const;
V8_NOINLINE uintptr_t GetCurrentStackPosition() const {
#if V8_CC_MSVC
return reinterpret_cast<uintptr_t>(_AddressOfReturnAddress());
#else
return reinterpret_cast<uintptr_t>(__builtin_frame_address(0));
#endif // V8_CC_MSVC
}
// Keeps track of registered handles and their stack address. The data
// structure is cleaned on iteration and when adding new references using the
// current stack address.
std::map<uintptr_t, NodeEntry> on_stack_nodes_;
uintptr_t stack_start_ = 0;
GlobalHandles* global_handles_ = nullptr;
}; };
uintptr_t GlobalHandles::OnStackTracedNodeSpace::GetStackAddressForSlot(
uintptr_t slot) const {
#ifdef V8_USE_ADDRESS_SANITIZER
void* fake_stack = __asan_get_current_fake_stack();
if (fake_stack) {
void* real_slot = __asan_addr_is_in_fake_stack(
fake_stack, reinterpret_cast<void*>(slot), nullptr, nullptr);
if (real_slot) {
return reinterpret_cast<uintptr_t>(real_slot);
}
}
#endif // V8_USE_ADDRESS_SANITIZER
return slot;
}
void GlobalHandles::OnStackTracedNodeSpace::NotifyEmptyEmbedderStack() {
#ifdef DEBUG
uintptr_t current = GetCurrentStackPosition();
for (const auto it : on_stack_nodes_) {
DCHECK_LT(current, it.first);
}
#endif // DEBUG
on_stack_nodes_.clear();
}
void GlobalHandles::OnStackTracedNodeSpace::Iterate(RootVisitor* v) {
// Handles have been cleaned from the GC entry point which is higher up the
// stack.
for (auto& pair : on_stack_nodes_) {
TracedNode& node = pair.second.node;
if (node.IsRetainer()) {
v->VisitRootPointer(Root::kGlobalHandles, "on-stack TracedReference",
node.location());
}
}
}
GlobalHandles::TracedNode* GlobalHandles::OnStackTracedNodeSpace::Acquire(
Object value, uintptr_t slot) {
DCHECK(IsOnStack(slot));
CleanupBelowCurrentStackPosition();
NodeEntry entry;
entry.node.Free(nullptr);
entry.global_handles = global_handles_;
auto pair =
on_stack_nodes_.insert({GetStackAddressForSlot(slot), std::move(entry)});
if (!pair.second) {
// Insertion failed because there already was an entry present for that
// stack address. This can happen because cleanup is conservative in which
// stack limits it used. Reusing the entry is fine as there's no aliasing of
// different references with the same stack slot.
pair.first->second.node.Free(nullptr);
}
TracedNode* result = &(pair.first->second.node);
result->Acquire(value);
result->set_is_on_stack(true);
return result;
}
void GlobalHandles::OnStackTracedNodeSpace::CleanupBelowCurrentStackPosition() {
if (on_stack_nodes_.empty()) return;
const auto it = on_stack_nodes_.upper_bound(GetCurrentStackPosition());
on_stack_nodes_.erase(on_stack_nodes_.begin(), it);
}
void GlobalHandles::CleanupOnStackReferencesBelowCurrentStackPosition() {
on_stack_nodes_->CleanupBelowCurrentStackPosition();
}
size_t GlobalHandles::NumberOfOnStackHandlesForTesting() {
return on_stack_nodes_->NumberOfHandlesForTesting();
}
void GlobalHandles::SetStackStart(void* stack_start) {
on_stack_nodes_->SetStackStart(stack_start);
}
void GlobalHandles::NotifyEmptyEmbedderStack() {
on_stack_nodes_->NotifyEmptyEmbedderStack();
}
GlobalHandles::GlobalHandles(Isolate* isolate) GlobalHandles::GlobalHandles(Isolate* isolate)
: isolate_(isolate), : isolate_(isolate),
regular_nodes_(new NodeSpace<GlobalHandles::Node>(this)), regular_nodes_(new NodeSpace<GlobalHandles::Node>(this)),
traced_nodes_(new NodeSpace<GlobalHandles::TracedNode>(this)) {} traced_nodes_(new NodeSpace<GlobalHandles::TracedNode>(this)),
on_stack_nodes_(new OnStackTracedNodeSpace(this)) {}
GlobalHandles::~GlobalHandles() { regular_nodes_.reset(nullptr); } GlobalHandles::~GlobalHandles() { regular_nodes_.reset(nullptr); }
...@@ -726,11 +877,19 @@ Handle<Object> GlobalHandles::Create(Address value) { ...@@ -726,11 +877,19 @@ Handle<Object> GlobalHandles::Create(Address value) {
Handle<Object> GlobalHandles::CreateTraced(Object value, Address* slot, Handle<Object> GlobalHandles::CreateTraced(Object value, Address* slot,
bool has_destructor) { bool has_destructor) {
GlobalHandles::TracedNode* result = traced_nodes_->Acquire(value); GlobalHandles::TracedNode* result;
const uintptr_t slot_address = reinterpret_cast<uintptr_t>(slot);
if (on_stack_nodes_->IsOnStack(slot_address)) {
CHECK_WITH_MSG(!has_destructor,
"TracedGlobal is prohibited from on-stack usage.");
result = on_stack_nodes_->Acquire(value, slot_address);
} else {
result = traced_nodes_->Acquire(value);
if (ObjectInYoungGeneration(value) && !result->is_in_young_list()) { if (ObjectInYoungGeneration(value) && !result->is_in_young_list()) {
traced_young_nodes_.push_back(result); traced_young_nodes_.push_back(result);
result->set_in_young_list(true); result->set_in_young_list(true);
} }
}
result->set_parameter(slot); result->set_parameter(slot);
result->set_has_destructor(has_destructor); result->set_has_destructor(has_destructor);
return result->handle(); return result->handle();
...@@ -762,8 +921,12 @@ void GlobalHandles::CopyTracedGlobal(const Address* const* from, Address** to) { ...@@ -762,8 +921,12 @@ void GlobalHandles::CopyTracedGlobal(const Address* const* from, Address** to) {
// the callback may require knowing about multiple copies of the traced // the callback may require knowing about multiple copies of the traced
// handle. // handle.
CHECK(!node->HasFinalizationCallback()); CHECK(!node->HasFinalizationCallback());
GlobalHandles* global_handles = GlobalHandles* global_handles =
NodeBlock<TracedNode>::From(node)->global_handles(); (node->is_on_stack())
? *reinterpret_cast<GlobalHandles**>(const_cast<TracedNode*>(node) +
1)
: NodeBlock<TracedNode>::From(node)->global_handles();
Handle<Object> o = global_handles->CreateTraced( Handle<Object> o = global_handles->CreateTraced(
node->object(), reinterpret_cast<Address*>(to), node->has_destructor()); node->object(), reinterpret_cast<Address*>(to), node->has_destructor());
*to = o.location(); *to = o.location();
...@@ -815,7 +978,12 @@ void GlobalHandles::Destroy(Address* location) { ...@@ -815,7 +978,12 @@ void GlobalHandles::Destroy(Address* location) {
void GlobalHandles::DestroyTraced(Address* location) { void GlobalHandles::DestroyTraced(Address* location) {
if (location != nullptr) { if (location != nullptr) {
NodeSpace<TracedNode>::Release(TracedNode::FromLocation(location)); TracedNode* node = TracedNode::FromLocation(location);
if (node->is_on_stack()) {
node->Release(nullptr);
} else {
NodeSpace<TracedNode>::Release(node);
}
} }
} }
...@@ -957,6 +1125,7 @@ void GlobalHandles::IterateYoungStrongAndDependentRoots(RootVisitor* v) { ...@@ -957,6 +1125,7 @@ void GlobalHandles::IterateYoungStrongAndDependentRoots(RootVisitor* v) {
v->VisitRootPointer(Root::kGlobalHandles, nullptr, node->location()); v->VisitRootPointer(Root::kGlobalHandles, nullptr, node->location());
} }
} }
on_stack_nodes_->Iterate(v);
} }
void GlobalHandles::MarkYoungWeakUnmodifiedObjectsPending( void GlobalHandles::MarkYoungWeakUnmodifiedObjectsPending(
...@@ -1241,6 +1410,7 @@ void GlobalHandles::IterateStrongRoots(RootVisitor* v) { ...@@ -1241,6 +1410,7 @@ void GlobalHandles::IterateStrongRoots(RootVisitor* v) {
node->location()); node->location());
} }
} }
on_stack_nodes_->Iterate(v);
} }
void GlobalHandles::IterateWeakRoots(RootVisitor* v) { void GlobalHandles::IterateWeakRoots(RootVisitor* v) {
...@@ -1270,6 +1440,7 @@ void GlobalHandles::IterateAllRoots(RootVisitor* v) { ...@@ -1270,6 +1440,7 @@ void GlobalHandles::IterateAllRoots(RootVisitor* v) {
v->VisitRootPointer(Root::kGlobalHandles, nullptr, node->location()); v->VisitRootPointer(Root::kGlobalHandles, nullptr, node->location());
} }
} }
on_stack_nodes_->Iterate(v);
} }
DISABLE_CFI_PERF DISABLE_CFI_PERF
...@@ -1285,6 +1456,7 @@ void GlobalHandles::IterateAllYoungRoots(RootVisitor* v) { ...@@ -1285,6 +1456,7 @@ void GlobalHandles::IterateAllYoungRoots(RootVisitor* v) {
v->VisitRootPointer(Root::kGlobalHandles, nullptr, node->location()); v->VisitRootPointer(Root::kGlobalHandles, nullptr, node->location());
} }
} }
on_stack_nodes_->Iterate(v);
} }
DISABLE_CFI_PERF DISABLE_CFI_PERF
......
...@@ -182,6 +182,11 @@ class V8_EXPORT_PRIVATE GlobalHandles final { ...@@ -182,6 +182,11 @@ class V8_EXPORT_PRIVATE GlobalHandles final {
return old; return old;
} }
void SetStackStart(void* stack_start);
void NotifyEmptyEmbedderStack();
void CleanupOnStackReferencesBelowCurrentStackPosition();
size_t NumberOfOnStackHandlesForTesting();
#ifdef DEBUG #ifdef DEBUG
void PrintStats(); void PrintStats();
void Print(); void Print();
...@@ -196,6 +201,7 @@ class V8_EXPORT_PRIVATE GlobalHandles final { ...@@ -196,6 +201,7 @@ class V8_EXPORT_PRIVATE GlobalHandles final {
class NodeSpace; class NodeSpace;
class PendingPhantomCallback; class PendingPhantomCallback;
class TracedNode; class TracedNode;
class OnStackTracedNodeSpace;
bool InRecursiveGC(unsigned gc_processing_counter); bool InRecursiveGC(unsigned gc_processing_counter);
...@@ -224,6 +230,7 @@ class V8_EXPORT_PRIVATE GlobalHandles final { ...@@ -224,6 +230,7 @@ class V8_EXPORT_PRIVATE GlobalHandles final {
std::unique_ptr<NodeSpace<TracedNode>> traced_nodes_; std::unique_ptr<NodeSpace<TracedNode>> traced_nodes_;
std::vector<TracedNode*> traced_young_nodes_; std::vector<TracedNode*> traced_young_nodes_;
std::unique_ptr<OnStackTracedNodeSpace> on_stack_nodes_;
// Field always containing the number of handles to global objects. // Field always containing the number of handles to global objects.
size_t handles_count_ = 0; size_t handles_count_ = 0;
......
...@@ -69,6 +69,9 @@ void LocalEmbedderHeapTracer::SetEmbedderStackStateForNextFinalization( ...@@ -69,6 +69,9 @@ void LocalEmbedderHeapTracer::SetEmbedderStackStateForNextFinalization(
if (!InUse()) return; if (!InUse()) return;
embedder_stack_state_ = stack_state; embedder_stack_state_ = stack_state;
if (EmbedderHeapTracer::EmbedderStackState::kEmpty == stack_state) {
remote_tracer()->NotifyEmptyEmbedderStack();
}
} }
LocalEmbedderHeapTracer::ProcessingScope::ProcessingScope( LocalEmbedderHeapTracer::ProcessingScope::ProcessingScope(
......
...@@ -145,6 +145,10 @@ class V8_EXPORT_PRIVATE EmbedderStackStateScope final { ...@@ -145,6 +145,10 @@ class V8_EXPORT_PRIVATE EmbedderStackStateScope final {
: local_tracer_(local_tracer), : local_tracer_(local_tracer),
old_stack_state_(local_tracer_->embedder_stack_state_) { old_stack_state_(local_tracer_->embedder_stack_state_) {
local_tracer_->embedder_stack_state_ = stack_state; local_tracer_->embedder_stack_state_ = stack_state;
if (EmbedderHeapTracer::EmbedderStackState::kEmpty == stack_state) {
if (local_tracer->remote_tracer())
local_tracer->remote_tracer()->NotifyEmptyEmbedderStack();
}
} }
~EmbedderStackStateScope() { ~EmbedderStackStateScope() {
......
...@@ -1520,6 +1520,11 @@ bool Heap::CollectGarbage(AllocationSpace space, ...@@ -1520,6 +1520,11 @@ bool Heap::CollectGarbage(AllocationSpace space,
InvokeNearHeapLimitCallback(); InvokeNearHeapLimitCallback();
} }
// Filter on-stack reference below this method.
isolate()
->global_handles()
->CleanupOnStackReferencesBelowCurrentStackPosition();
// Ensure that all pending phantom callbacks are invoked. // Ensure that all pending phantom callbacks are invoked.
isolate()->global_handles()->InvokeSecondPassPhantomCallbacks(); isolate()->global_handles()->InvokeSecondPassPhantomCallbacks();
......
...@@ -907,6 +907,171 @@ TEST(TracedGlobalNoDestructorReclaimedOnScavenge) { ...@@ -907,6 +907,171 @@ TEST(TracedGlobalNoDestructorReclaimedOnScavenge) {
CHECK_EQ(initial_count, global_handles->handles_count()); CHECK_EQ(initial_count, global_handles->handles_count());
} }
namespace {
V8_NOINLINE void TracedReferenceOnStack(TestEmbedderHeapTracer* tracer) {
v8::Isolate* isolate = CcTest::isolate();
v8::Global<v8::Object> observer;
v8::TracedReference<v8::Value> stack_ref;
{
v8::HandleScope scope(isolate);
v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
isolate->GetCurrentContext(), nullptr, nullptr));
stack_ref.Reset(isolate, object);
observer.Reset(isolate, object);
observer.SetWeak();
}
CHECK(!observer.IsEmpty());
heap::InvokeMarkSweep();
CHECK(!observer.IsEmpty());
}
V8_NOINLINE void CreateTracedReferenceInDeepStack(
v8::Isolate* isolate, v8::Global<v8::Object>* observer) {
v8::TracedReference<v8::Value> stack_ref;
v8::HandleScope scope(isolate);
v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
isolate->GetCurrentContext(), nullptr, nullptr));
stack_ref.Reset(isolate, object);
observer->Reset(isolate, object);
observer->SetWeak();
}
V8_NOINLINE void TracedReferenceNotifyEmptyStack(
TestEmbedderHeapTracer* tracer) {
v8::Isolate* isolate = CcTest::isolate();
v8::Global<v8::Object> observer;
CreateTracedReferenceInDeepStack(isolate, &observer);
CHECK(!observer.IsEmpty());
tracer->NotifyEmptyEmbedderStack();
heap::InvokeMarkSweep();
CHECK(observer.IsEmpty());
}
V8_NOINLINE void TracedReferenceCopyStackToHeapTest(
TestEmbedderHeapTracer* tracer) {
v8::Isolate* isolate = CcTest::isolate();
v8::Global<v8::Object> observer;
v8::TracedReference<v8::Value> stack_handle;
v8::TracedReference<v8::Value>* heap_handle =
new v8::TracedReference<v8::Value>();
{
v8::HandleScope scope(isolate);
v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
isolate->GetCurrentContext(), nullptr, nullptr));
stack_handle.Reset(isolate, object);
observer.Reset(isolate, object);
observer.SetWeak();
}
CHECK(!observer.IsEmpty());
heap::InvokeMarkSweep();
CHECK(!observer.IsEmpty());
tracer->AddReferenceForTracing(heap_handle);
*heap_handle = stack_handle;
stack_handle.Reset();
heap::InvokeMarkSweep();
CHECK(!observer.IsEmpty());
heap::InvokeMarkSweep();
CHECK(observer.IsEmpty());
delete heap_handle;
}
V8_NOINLINE void TracedReferenceCopyHeapToStackTest(
TestEmbedderHeapTracer* tracer) {
v8::Isolate* isolate = CcTest::isolate();
v8::Global<v8::Object> observer;
v8::TracedReference<v8::Value> stack_handle;
v8::TracedReference<v8::Value>* heap_handle =
new v8::TracedReference<v8::Value>();
{
v8::HandleScope scope(isolate);
v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
isolate->GetCurrentContext(), nullptr, nullptr));
heap_handle->Reset(isolate, object);
observer.Reset(isolate, object);
observer.SetWeak();
}
CHECK(!observer.IsEmpty());
tracer->AddReferenceForTracing(heap_handle);
heap::InvokeMarkSweep();
CHECK(!observer.IsEmpty());
stack_handle = *heap_handle;
delete heap_handle;
heap::InvokeMarkSweep();
CHECK(!observer.IsEmpty());
stack_handle.Reset();
heap::InvokeMarkSweep();
CHECK(observer.IsEmpty());
}
V8_NOINLINE void TracedReferenceCleanedTest(TestEmbedderHeapTracer* tracer) {
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
isolate->GetCurrentContext(), nullptr, nullptr));
const size_t before =
CcTest::i_isolate()->global_handles()->NumberOfOnStackHandlesForTesting();
for (int i = 0; i < 100; i++) {
v8::TracedReference<v8::Value> stack_handle;
stack_handle.Reset(isolate, object);
}
CHECK_EQ(before + 1, CcTest::i_isolate()
->global_handles()
->NumberOfOnStackHandlesForTesting());
}
} // namespace
TEST(TracedReferenceOnStack) {
ManualGCScope manual_gc;
CcTest::InitializeVM();
TestEmbedderHeapTracer tracer;
heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
&tracer);
tracer.SetStackStart(&manual_gc);
TracedReferenceOnStack(&tracer);
}
TEST(TracedReferenceNotifyEmptyStack) {
ManualGCScope manual_gc;
CcTest::InitializeVM();
TestEmbedderHeapTracer tracer;
heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
&tracer);
tracer.SetStackStart(&manual_gc);
TracedReferenceNotifyEmptyStack(&tracer);
}
TEST(TracedReferenceCopyStackToHeap) {
ManualGCScope manual_gc;
CcTest::InitializeVM();
TestEmbedderHeapTracer tracer;
heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
&tracer);
tracer.SetStackStart(&manual_gc);
TracedReferenceCopyStackToHeapTest(&tracer);
}
TEST(TracedReferenceCopyHeapToStack) {
ManualGCScope manual_gc;
CcTest::InitializeVM();
TestEmbedderHeapTracer tracer;
heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
&tracer);
tracer.SetStackStart(&manual_gc);
TracedReferenceCopyHeapToStackTest(&tracer);
}
TEST(TracedReferenceCleaned) {
ManualGCScope manual_gc;
CcTest::InitializeVM();
TestEmbedderHeapTracer tracer;
heap::TemporaryEmbedderHeapTracerScope tracer_scope(CcTest::isolate(),
&tracer);
tracer.SetStackStart(&manual_gc);
TracedReferenceCleanedTest(&tracer);
}
} // namespace heap } // namespace heap
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
...@@ -109,9 +109,10 @@ TEST(LocalEmbedderHeapTracer, EnterFinalPauseDefaultStackStateUnkown) { ...@@ -109,9 +109,10 @@ TEST(LocalEmbedderHeapTracer, EnterFinalPauseDefaultStackStateUnkown) {
local_tracer.EnterFinalPause(); local_tracer.EnterFinalPause();
} }
TEST(LocalEmbedderHeapTracer, EnterFinalPauseStackStateIsForwarded) { TEST_F(LocalEmbedderHeapTracerWithIsolate,
EnterFinalPauseStackStateIsForwarded) {
StrictMock<MockEmbedderHeapTracer> remote_tracer; StrictMock<MockEmbedderHeapTracer> remote_tracer;
LocalEmbedderHeapTracer local_tracer(nullptr); LocalEmbedderHeapTracer local_tracer(isolate());
local_tracer.SetRemoteTracer(&remote_tracer); local_tracer.SetRemoteTracer(&remote_tracer);
local_tracer.SetEmbedderStackStateForNextFinalization( local_tracer.SetEmbedderStackStateForNextFinalization(
EmbedderHeapTracer::kEmpty); EmbedderHeapTracer::kEmpty);
...@@ -119,9 +120,9 @@ TEST(LocalEmbedderHeapTracer, EnterFinalPauseStackStateIsForwarded) { ...@@ -119,9 +120,9 @@ TEST(LocalEmbedderHeapTracer, EnterFinalPauseStackStateIsForwarded) {
local_tracer.EnterFinalPause(); local_tracer.EnterFinalPause();
} }
TEST(LocalEmbedderHeapTracer, TemporaryEmbedderStackState) { TEST_F(LocalEmbedderHeapTracerWithIsolate, TemporaryEmbedderStackState) {
StrictMock<MockEmbedderHeapTracer> remote_tracer; StrictMock<MockEmbedderHeapTracer> remote_tracer;
LocalEmbedderHeapTracer local_tracer(nullptr); LocalEmbedderHeapTracer local_tracer(isolate());
local_tracer.SetRemoteTracer(&remote_tracer); local_tracer.SetRemoteTracer(&remote_tracer);
// Default is unknown, see above. // Default is unknown, see above.
{ {
...@@ -131,9 +132,10 @@ TEST(LocalEmbedderHeapTracer, TemporaryEmbedderStackState) { ...@@ -131,9 +132,10 @@ TEST(LocalEmbedderHeapTracer, TemporaryEmbedderStackState) {
} }
} }
TEST(LocalEmbedderHeapTracer, TemporaryEmbedderStackStateRestores) { TEST_F(LocalEmbedderHeapTracerWithIsolate,
TemporaryEmbedderStackStateRestores) {
StrictMock<MockEmbedderHeapTracer> remote_tracer; StrictMock<MockEmbedderHeapTracer> remote_tracer;
LocalEmbedderHeapTracer local_tracer(nullptr); LocalEmbedderHeapTracer local_tracer(isolate());
local_tracer.SetRemoteTracer(&remote_tracer); local_tracer.SetRemoteTracer(&remote_tracer);
// Default is unknown, see above. // Default is unknown, see above.
{ {
...@@ -149,9 +151,9 @@ TEST(LocalEmbedderHeapTracer, TemporaryEmbedderStackStateRestores) { ...@@ -149,9 +151,9 @@ TEST(LocalEmbedderHeapTracer, TemporaryEmbedderStackStateRestores) {
} }
} }
TEST(LocalEmbedderHeapTracer, EnterFinalPauseStackStateResets) { TEST_F(LocalEmbedderHeapTracerWithIsolate, EnterFinalPauseStackStateResets) {
StrictMock<MockEmbedderHeapTracer> remote_tracer; StrictMock<MockEmbedderHeapTracer> remote_tracer;
LocalEmbedderHeapTracer local_tracer(nullptr); LocalEmbedderHeapTracer local_tracer(isolate());
local_tracer.SetRemoteTracer(&remote_tracer); local_tracer.SetRemoteTracer(&remote_tracer);
local_tracer.SetEmbedderStackStateForNextFinalization( local_tracer.SetEmbedderStackStateForNextFinalization(
EmbedderHeapTracer::kEmpty); EmbedderHeapTracer::kEmpty);
......
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