Commit 76c93685 authored by Michael Lippautz's avatar Michael Lippautz Committed by Commit Bot

[api, global-handles] Add TracedGlobal

TracedGlobal integrates with the use case of EmbedderHeapTracer and replaces
regular weak Global or Persistent nodes for such cases. This allows to simplify
the case for regular weak handles in a sense that they follow regular weak
semantics (if the underlying object is otherwise unreachable the weak handle
will be reset).

TracedGlobal requires slightly different semantics in the sense that it can be
required to keep them alive on Scavenge garbage collections because there's a
transitive path that is only known when using the EmbedderHeapTracer.
TracedGlobal accomodates that use case.

TracedGlobal follows move semantics and can thus be used in regular std
containers without wrapping data structure.

The internal state uses 20% less memory and allows for only iterating those
nodes when necessary. The design trades the virtual call when iterating
interesting persistents in the GC prologue with calling out through the
EmbedderHeapTracer for each node which is also a virtual call. There is one less
iteration over the set of handles required though and the design is robust
against recursive GCs that mutate the embedder state during the prologue
callback.

Bug: chromium:923361
Change-Id: Idbacfbe4723cd12af9de21058a4792e51dc4df74
Reviewed-on: https://chromium-review.googlesource.com/c/1425523
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59183}
parent d8baf215
This diff is collapsed.
......@@ -8,6 +8,7 @@ include_rules = [
"+src/compiler/code-assembler.h",
"+src/compiler/wasm-compiler.h",
"-src/heap",
"+src/heap/embedder-tracing.h",
"+src/heap/factory.h",
"+src/heap/factory-inl.h",
"+src/heap/heap.h",
......
......@@ -1012,6 +1012,19 @@ i::Address* V8::GlobalizeReference(i::Isolate* isolate, i::Address* obj) {
return result.location();
}
i::Address* V8::GlobalizeTracedReference(i::Isolate* isolate, i::Address* obj,
internal::Address* slot) {
LOG_API(isolate, TracedGlobal, New);
i::Handle<i::Object> result =
isolate->global_handles()->CreateTraced(*obj, slot);
#ifdef VERIFY_HEAP
if (i::FLAG_verify_heap) {
i::Object(*obj)->ObjectVerify(isolate);
}
#endif // VERIFY_HEAP
return result.location();
}
i::Address* V8::CopyGlobalReference(i::Address* from) {
i::Handle<i::Object> result = i::GlobalHandles::CopyGlobal(from);
return result.location();
......@@ -1021,6 +1034,11 @@ void V8::MoveGlobalReference(internal::Address** from, internal::Address** to) {
i::GlobalHandles::MoveGlobal(from, to);
}
void V8::MoveTracedGlobalReference(internal::Address** from,
internal::Address** to) {
i::GlobalHandles::MoveTracedGlobal(from, to);
}
void V8::RegisterExternallyReferencedObject(i::Address* location,
i::Isolate* isolate) {
isolate->heap()->RegisterExternallyReferencedObject(location);
......@@ -1048,6 +1066,10 @@ void V8::DisposeGlobal(i::Address* location) {
i::GlobalHandles::Destroy(location);
}
void V8::DisposeTracedGlobal(internal::Address* location) {
i::GlobalHandles::DestroyTraced(location);
}
Value* V8::Eternalize(Isolate* v8_isolate, Value* value) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
i::Object object = *Utils::OpenHandle(value);
......@@ -10469,6 +10491,22 @@ void EmbedderHeapTracer::GarbageCollectionForTesting(
kGCCallbackFlagForced);
}
void EmbedderHeapTracer::RegisterEmbedderReference(
const TracedGlobal<v8::Value>& ref) {
if (ref.IsEmpty()) return;
i::Heap* const heap = reinterpret_cast<i::Isolate*>(isolate_)->heap();
heap->RegisterExternallyReferencedObject(
reinterpret_cast<i::Address*>(ref.val_));
}
void EmbedderHeapTracer::IterateTracedGlobalHandles(
TracedGlobalHandleVisitor* visitor) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(isolate_);
i::DisallowHeapAllocation no_allocation;
isolate->global_handles()->IterateTracedNodes(visitor);
}
namespace internal {
const size_t HandleScopeImplementer::kEnteredContextsOffset =
......
......@@ -839,6 +839,7 @@ class RuntimeCallTimer final {
V(SymbolObject_New) \
V(SymbolObject_SymbolValue) \
V(SyntaxError_New) \
V(TracedGlobal_New) \
V(TryCatch_StackTrace) \
V(TypeError_New) \
V(Uint16Array_New) \
......
This diff is collapsed.
......@@ -40,14 +40,19 @@ enum WeaknessType {
// callbacks and finalizers attached to them.
class GlobalHandles final {
public:
template <class NodeType>
class NodeBlock;
// Move a global handle.
static void MoveGlobal(Address** from, Address** to);
// Copy a global handle.
static Handle<Object> CopyGlobal(Address* location);
static void MoveTracedGlobal(Address** from, Address** to);
// Destroy a global handle.
static void Destroy(Address* location);
static void DestroyTraced(Address* location);
// Copy a global handle.
static Handle<Object> CopyGlobal(Address* location);
// Make the global handle weak and set the callback parameter for the
// handle. When the garbage collector recognizes that only weak global
......@@ -90,6 +95,9 @@ class GlobalHandles final {
return Handle<T>::cast(Create(Object(value)));
}
Handle<Object> CreateTraced(Object value, Address* slot);
Handle<Object> CreateTraced(Address value, Address* slot);
void RecordStats(HeapStats* stats);
size_t InvokeFirstPassWeakCallbacks();
......@@ -103,7 +111,6 @@ class GlobalHandles final {
void IterateStrongRoots(RootVisitor* v);
void IterateWeakRoots(RootVisitor* v);
void IterateAllRoots(RootVisitor* v);
void IterateAllNewSpaceRoots(RootVisitor* v);
// Iterates over all handles that have embedder-assigned class ID.
......@@ -117,15 +124,22 @@ class GlobalHandles final {
// and have class IDs
void IterateWeakRootsInNewSpaceWithClassIds(v8::PersistentHandleVisitor* v);
// Iterates over weak roots on the heap.
// Iterates over all traces handles represented by TracedGlobal.
void IterateTracedNodes(
v8::EmbedderHeapTracer::TracedGlobalHandleVisitor* visitor);
// Marks handles with finalizers on the predicate |should_reset_handle| as
// pending.
void IterateWeakRootsIdentifyFinalizers(
WeakSlotCallbackWithHeap should_reset_handle);
// Uses the provided visitor |v| to mark handles with finalizers that are
// pending.
void IterateWeakRootsForFinalizers(RootVisitor* v);
// Marks handles that are phantom or have callbacks based on the predicate
// |should_reset_handle| as pending.
void IterateWeakRootsForPhantomHandles(
WeakSlotCallbackWithHeap should_reset_handle);
// Marks all handles that should be finalized based on the predicate
// |should_reset_handle| as pending.
void IdentifyWeakHandles(WeakSlotCallbackWithHeap should_reset_handle);
// Note: The following *NewSpace* methods are used for the Scavenger to
// identify and process handles in new space. The set of new space handles is
// complete but the methods may encounter handles that are already in old
......@@ -167,13 +181,12 @@ class GlobalHandles final {
private:
// Internal node structures.
class Node;
template <class NodeType>
class NodeBlock;
template <class BlockType>
class NodeIterator;
template <class NodeType>
class NodeSpace;
class PendingPhantomCallback;
class TracedNode;
bool InRecursiveGC(unsigned gc_processing_counter);
......@@ -182,6 +195,8 @@ class GlobalHandles final {
size_t PostScavengeProcessing(unsigned post_processing_count);
size_t PostMarkSweepProcessing(unsigned post_processing_count);
template <typename T>
void UpdateAndCompactListOfNewSpaceNode(std::vector<T*>* node_list);
void UpdateListOfNewSpaceNodes();
void ApplyPersistentHandleVisitor(v8::PersistentHandleVisitor* visitor,
......@@ -194,6 +209,9 @@ class GlobalHandles final {
// is accessed, some of the objects may have been promoted already.
std::vector<Node*> new_space_nodes_;
std::unique_ptr<NodeSpace<TracedNode>> traced_nodes_;
std::vector<TracedNode*> traced_new_space_nodes_;
// Field always containing the number of handles to global objects.
size_t handles_count_ = 0;
size_t number_of_phantom_handle_resets_ = 0;
......
......@@ -54,6 +54,10 @@ class V8_EXPORT_PRIVATE LocalEmbedderHeapTracer final {
bool Trace(double deadline);
bool IsRemoteTracingDone();
bool IsRootForNonTracingGC(const v8::TracedGlobal<v8::Value>& handle) {
return !InUse() || remote_tracer_->IsRootForNonTracingGC(handle);
}
void NotifyV8MarkingWorklistWasEmpty() {
num_v8_marking_worklist_was_empty_++;
}
......
......@@ -1849,7 +1849,7 @@ void MarkCompactCollector::MarkLiveObjects() {
{
TRACE_GC(heap()->tracer(),
GCTracer::Scope::MC_MARK_WEAK_CLOSURE_WEAK_HANDLES);
heap()->isolate()->global_handles()->IdentifyWeakHandles(
heap()->isolate()->global_handles()->IterateWeakRootsIdentifyFinalizers(
&IsUnmarkedHeapObject);
ProcessMarkingWorklist();
}
......
......@@ -15,6 +15,10 @@ namespace v8 {
namespace internal {
namespace heap {
void InvokeScavenge() { CcTest::CollectGarbage(i::NEW_SPACE); }
void InvokeMarkSweep() { CcTest::CollectAllGarbage(); }
void SealCurrentObjects(Heap* heap) {
CcTest::CollectAllGarbage();
CcTest::CollectAllGarbage();
......
......@@ -5,6 +5,7 @@
#ifndef HEAP_HEAP_UTILS_H_
#define HEAP_HEAP_UTILS_H_
#include "src/api-inl.h"
#include "src/heap/heap.h"
namespace v8 {
......@@ -52,6 +53,17 @@ void GcAndSweep(Heap* heap, AllocationSpace space);
void ForceEvacuationCandidate(Page* page);
void InvokeScavenge();
void InvokeMarkSweep();
template <typename GlobalOrPersistent>
bool InNewSpace(v8::Isolate* isolate, const GlobalOrPersistent& global) {
v8::HandleScope scope(isolate);
auto tmp = global.Get(isolate);
return i::Heap::InNewSpace(*v8::Utils::OpenHandle(*tmp));
}
} // namespace heap
} // namespace internal
} // namespace v8
......
......@@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <unordered_map>
#include <vector>
#include "include/v8.h"
#include "src/api-inl.h"
#include "src/objects-inl.h"
......@@ -9,6 +12,7 @@
#include "src/objects/script.h"
#include "src/objects/shared-function-info.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-utils.h"
namespace v8 {
namespace internal {
......@@ -70,10 +74,19 @@ class TestEmbedderHeapTracer final : public v8::EmbedderHeapTracer {
return false;
}
void ConsiderTracedGlobalAsRoot(bool value) {
consider_traced_global_as_root_ = value;
}
bool IsRootForNonTracingGC(const v8::TracedGlobal<v8::Value>& handle) final {
return consider_traced_global_as_root_;
}
private:
v8::Isolate* const isolate_;
std::vector<std::pair<void*, void*>> registered_from_v8_;
std::vector<v8::Persistent<v8::Object>*> to_register_with_v8_;
bool consider_traced_global_as_root_ = true;
};
class TemporaryEmbedderHeapTracerScope {
......@@ -265,6 +278,218 @@ TEST(GarbageCollectionForTesting) {
CHECK_GT(i_isolate->heap()->gc_count(), saved_gc_counter);
}
namespace {
void ConstructJSObject(v8::Isolate* isolate, v8::Local<v8::Context> context,
v8::TracedGlobal<v8::Object>* global) {
v8::HandleScope scope(isolate);
v8::Local<v8::Object> object(v8::Object::New(isolate));
CHECK(!object.IsEmpty());
*global = v8::TracedGlobal<v8::Object>(isolate, object);
CHECK(!global->IsEmpty());
}
void ConstructJSApiObject(v8::Isolate* isolate, v8::Local<v8::Context> context,
v8::TracedGlobal<v8::Object>* global) {
v8::HandleScope scope(isolate);
v8::Local<v8::Object> object(
ConstructTraceableJSApiObject(context, nullptr, nullptr));
CHECK(!object.IsEmpty());
*global = v8::TracedGlobal<v8::Object>(isolate, object);
CHECK(!global->IsEmpty());
}
enum class SurvivalMode { kSurvives, kDies };
template <typename ModifierFunction, typename ConstructTracedGlobalFunction>
void TracedGlobalTest(v8::Isolate* isolate,
ConstructTracedGlobalFunction construct_function,
ModifierFunction modifier_function, void (*gc_function)(),
SurvivalMode survives) {
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context = v8::Context::New(isolate);
v8::Context::Scope context_scope(context);
v8::TracedGlobal<v8::Object> global;
construct_function(isolate, context, &global);
CHECK(InNewSpace(isolate, global));
modifier_function(global);
gc_function();
CHECK_IMPLIES(survives == SurvivalMode::kSurvives, !global.IsEmpty());
CHECK_IMPLIES(survives == SurvivalMode::kDies, global.IsEmpty());
}
} // namespace
TEST(TracedGlobalReset) {
CcTest::InitializeVM();
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
v8::TracedGlobal<v8::Object> traced;
ConstructJSObject(isolate, isolate->GetCurrentContext(), &traced);
CHECK(!traced.IsEmpty());
traced.Reset();
CHECK(traced.IsEmpty());
}
TEST(TracedGlobalInStdVector) {
ManualGCScope manual_gc;
CcTest::InitializeVM();
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
std::vector<v8::TracedGlobal<v8::Object>> vec;
{
v8::HandleScope scope(isolate);
vec.emplace_back(isolate, v8::Object::New(isolate));
}
CHECK(!vec[0].IsEmpty());
InvokeMarkSweep();
CHECK(vec[0].IsEmpty());
}
TEST(TracedGlobalInStdUnorderedMap) {
ManualGCScope manual_gc;
CcTest::InitializeVM();
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
std::unordered_map<int, v8::TracedGlobal<v8::Object>> map;
{
v8::HandleScope scope(isolate);
map.emplace(std::piecewise_construct, std::forward_as_tuple(1),
std::forward_as_tuple(isolate, v8::Object::New(isolate)));
}
CHECK(!map[1].IsEmpty());
InvokeMarkSweep();
CHECK(map[1].IsEmpty());
}
TEST(TracedGlobalToUnmodifiedJSObjectDiesOnMarkSweep) {
CcTest::InitializeVM();
TracedGlobalTest(
CcTest::isolate(), ConstructJSObject,
[](const TracedGlobal<v8::Object>& global) {}, InvokeMarkSweep,
SurvivalMode::kDies);
}
TEST(TracedGlobalToUnmodifiedJSObjectSurvivesMarkSweepWhenHeldAliveOtherwise) {
CcTest::InitializeVM();
v8::Isolate* isolate = CcTest::isolate();
v8::Global<v8::Object> strong_global;
TracedGlobalTest(
CcTest::isolate(), ConstructJSObject,
[isolate, &strong_global](const TracedGlobal<v8::Object>& global) {
v8::HandleScope scope(isolate);
strong_global = v8::Global<v8::Object>(isolate, global.Get(isolate));
},
InvokeMarkSweep, SurvivalMode::kSurvives);
}
TEST(TracedGlobalToUnmodifiedJSObjectSurvivesScavenge) {
ManualGCScope manual_gc;
CcTest::InitializeVM();
TracedGlobalTest(
CcTest::isolate(), ConstructJSObject,
[](const TracedGlobal<v8::Object>& global) {}, InvokeScavenge,
SurvivalMode::kSurvives);
}
TEST(TracedGlobalToUnmodifiedJSObjectSurvivesScavengeWhenExcludedFromRoots) {
ManualGCScope manual_gc;
CcTest::InitializeVM();
v8::Isolate* isolate = CcTest::isolate();
TestEmbedderHeapTracer tracer(isolate);
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
tracer.ConsiderTracedGlobalAsRoot(false);
TracedGlobalTest(
CcTest::isolate(), ConstructJSObject,
[](const TracedGlobal<v8::Object>& global) {}, InvokeScavenge,
SurvivalMode::kSurvives);
}
TEST(TracedGlobalToUnmodifiedJSApiObjectSurvivesScavengePerDefault) {
ManualGCScope manual_gc;
CcTest::InitializeVM();
v8::Isolate* isolate = CcTest::isolate();
TestEmbedderHeapTracer tracer(isolate);
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
tracer.ConsiderTracedGlobalAsRoot(true);
TracedGlobalTest(
CcTest::isolate(), ConstructJSApiObject,
[](const TracedGlobal<v8::Object>& global) {}, InvokeScavenge,
SurvivalMode::kSurvives);
}
TEST(TracedGlobalToUnmodifiedJSApiObjectDiesOnScavengeWhenExcludedFromRoots) {
ManualGCScope manual_gc;
CcTest::InitializeVM();
v8::Isolate* isolate = CcTest::isolate();
TestEmbedderHeapTracer tracer(isolate);
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
tracer.ConsiderTracedGlobalAsRoot(false);
TracedGlobalTest(
CcTest::isolate(), ConstructJSApiObject,
[](const TracedGlobal<v8::Object>& global) {}, InvokeScavenge,
SurvivalMode::kDies);
}
TEST(TracedGlobalWrapperClassId) {
ManualGCScope manual_gc;
CcTest::InitializeVM();
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
TestEmbedderHeapTracer tracer(isolate);
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
v8::TracedGlobal<v8::Object> traced;
ConstructJSObject(isolate, isolate->GetCurrentContext(), &traced);
CHECK_EQ(0, traced.WrapperClassId());
traced.SetWrapperClassId(17);
CHECK_EQ(17, traced.WrapperClassId());
}
namespace {
class TracedGlobalVisitor final
: public v8::EmbedderHeapTracer::TracedGlobalHandleVisitor {
public:
~TracedGlobalVisitor() override = default;
void VisitTracedGlobalHandle(const TracedGlobal<Value>& value) final {
if (value.WrapperClassId() == 57) {
count_++;
}
}
size_t count() const { return count_; }
private:
size_t count_ = 0;
};
} // namespace
TEST(TracedGlobalIteration) {
ManualGCScope manual_gc;
CcTest::InitializeVM();
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
TestEmbedderHeapTracer tracer(isolate);
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
v8::TracedGlobal<v8::Object> traced;
ConstructJSObject(isolate, isolate->GetCurrentContext(), &traced);
CHECK(!traced.IsEmpty());
traced.SetWrapperClassId(57);
TracedGlobalVisitor visitor;
{
v8::HandleScope scope(isolate);
tracer.IterateTracedGlobalHandles(&visitor);
}
CHECK_EQ(1, visitor.count());
}
} // namespace heap
} // namespace internal
} // namespace v8
......@@ -31,6 +31,7 @@
#include "src/isolate.h"
#include "src/objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-utils.h"
namespace v8 {
namespace internal {
......@@ -103,12 +104,7 @@ void WeakHandleTest(v8::Isolate* isolate, ConstructFunction construct_function,
FlagAndPersistent fp;
construct_function(isolate, context, &fp);
{
v8::HandleScope scope(isolate);
v8::Local<v8::Object> tmp = v8::Local<v8::Object>::New(isolate, fp.handle);
CHECK(i::Heap::InNewSpace(*v8::Utils::OpenHandle(*tmp)));
}
CHECK(heap::InNewSpace(isolate, fp.handle));
fp.handle.SetWeak(&fp, &ResetHandleAndSetFlag,
v8::WeakCallbackType::kParameter);
fp.flag = false;
......@@ -495,12 +491,7 @@ TEST(GCFromWeakCallbacks) {
for (int inner_gc = 0; inner_gc < kNumberOfGCTypes; inner_gc++) {
FlagAndPersistent fp;
ConstructJSApiObject(isolate, context, &fp);
{
v8::HandleScope scope(isolate);
v8::Local<v8::Object> tmp =
v8::Local<v8::Object>::New(isolate, fp.handle);
CHECK(i::Heap::InNewSpace(*v8::Utils::OpenHandle(*tmp)));
}
CHECK(heap::InNewSpace(isolate, fp.handle));
fp.flag = false;
fp.handle.SetWeak(&fp, gc_forcing_callback[inner_gc],
v8::WeakCallbackType::kParameter);
......
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