[heap] Convert unittest to avoid using deprecated APIs

- Converts tests in EmbedderTracingTest to UnifiedHeapTest
- Move EmbedderRootsHandler tests to their own file
- Adds support for CppHeap in ManualGCScope
- Drive-by fix typo

Reland: Fix issue with attaching a CppHeap while incremental marking
is already running.

This reverts commit d90a98edc1.

Change-Id: Ifafa9145df3103578c4c7f1b3b0336b4bd9f34dd
Bug: v8:13207
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4110941
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Reviewed-by: Dominik Inführ <dinfuehr@chromium.org>
Cr-Commit-Position: refs/heads/main@{#84950}
This commit is contained in:
Michael Lippautz 2022-12-20 00:16:30 +01:00 committed by V8 LUCI CQ
parent 5373b52f01
commit 8d4af46404
12 changed files with 634 additions and 645 deletions

View File

@ -536,7 +536,7 @@ void CppHeap::AttachIsolate(Isolate* isolate) {
}
SetMetricRecorder(std::make_unique<MetricRecorderAdapter>(*this));
oom_handler().SetCustomHandler(&FatalOutOfMemoryHandlerImpl);
ReduceGCCapabilititesFromFlags();
ReduceGCCapabilitiesFromFlags();
sweeping_on_mutator_thread_observer_ =
std::make_unique<SweepingOnMutatorThreadForGlobalHandlesObserver>(
*this, *isolate_->traced_handles());
@ -607,7 +607,7 @@ CppHeap::SweepingType CppHeap::SelectSweepingType() const {
return sweeping_support();
}
void CppHeap::ReduceGCCapabilititesFromFlags() {
void CppHeap::ReduceGCCapabilitiesFromFlags() {
CHECK_IMPLIES(v8_flags.cppheap_concurrent_marking,
v8_flags.cppheap_incremental_marking);
if (v8_flags.cppheap_concurrent_marking) {
@ -1081,5 +1081,9 @@ void CppHeap::ResetCrossHeapRememberedSet() {
cross_heap_remembered_set_.Reset(*isolate_);
}
void CppHeap::ReduceGCCapabilitiesFromFlagsForTesting() {
ReduceGCCapabilitiesFromFlags();
}
} // namespace internal
} // namespace v8

View File

@ -130,10 +130,6 @@ class V8_EXPORT_PRIVATE CppHeap final
void Terminate();
void EnableDetachedGarbageCollectionsForTesting();
void CollectGarbageForTesting(CollectionType, StackState);
void CollectCustomSpaceStatisticsAtLastGC(
std::vector<cppgc::CustomSpaceIndex>,
std::unique_ptr<CustomSpaceStatisticsReceiver>);
@ -179,8 +175,13 @@ class V8_EXPORT_PRIVATE CppHeap final
inline void VisitCrossHeapRememberedSetIfNeeded(F f);
void ResetCrossHeapRememberedSet();
// Testing-only APIs.
void EnableDetachedGarbageCollectionsForTesting();
void CollectGarbageForTesting(CollectionType, StackState);
void ReduceGCCapabilitiesFromFlagsForTesting();
private:
void ReduceGCCapabilititesFromFlags();
void ReduceGCCapabilitiesFromFlags();
void FinalizeIncrementalGarbageCollectionIfNeeded(
cppgc::Heap::StackState) final {

View File

@ -5818,6 +5818,7 @@ EmbedderRootsHandler* Heap::GetEmbedderRootsHandler() const {
}
void Heap::AttachCppHeap(v8::CppHeap* cpp_heap) {
CHECK(!incremental_marking()->IsMarking());
CppHeap::From(cpp_heap)->AttachIsolate(isolate());
cpp_heap_ = cpp_heap;
local_embedder_heap_tracer()->SetCppHeap(CppHeap::From(cpp_heap));

View File

@ -405,6 +405,7 @@ v8_source_set("unittests_sources") {
"heap/bitmap-test-utils.h",
"heap/bitmap-unittest.cc",
"heap/code-object-registry-unittest.cc",
"heap/cppgc-js/embedder-roots-handler-unittest.cc",
"heap/cppgc-js/traced-reference-unittest.cc",
"heap/cppgc-js/unified-heap-snapshot-unittest.cc",
"heap/cppgc-js/unified-heap-unittest.cc",

View File

@ -0,0 +1,283 @@
// Copyright 2022 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/handles/handles.h"
#include "src/handles/traced-handles.h"
#include "test/unittests/heap/cppgc-js/unified-heap-utils.h"
#include "test/unittests/heap/heap-utils.h"
namespace v8::internal {
namespace {
constexpr uint16_t kClassIdToOptimize = 23;
using EmbedderRootsHandlerTest = TestWithHeapInternalsAndContext;
class V8_NODISCARD TemporaryEmbedderRootsHandleScope final {
public:
TemporaryEmbedderRootsHandleScope(v8::Isolate* isolate,
v8::EmbedderRootsHandler* handler)
: isolate_(isolate) {
isolate_->SetEmbedderRootsHandler(handler);
}
~TemporaryEmbedderRootsHandleScope() {
isolate_->SetEmbedderRootsHandler(nullptr);
}
private:
v8::Isolate* const isolate_;
};
// EmbedderRootsHandler that can optimize Scavenger handling when used with
// TracedReference.
class ClearingEmbedderRootsHandler final : public v8::EmbedderRootsHandler {
public:
explicit ClearingEmbedderRootsHandler(uint16_t class_id_to_optimize)
: class_id_to_optimize_(class_id_to_optimize) {}
bool IsRoot(const v8::TracedReference<v8::Value>& handle) final {
return handle.WrapperClassId() != class_id_to_optimize_;
}
void ResetRoot(const v8::TracedReference<v8::Value>& handle) final {
if (handle.WrapperClassId() != class_id_to_optimize_) return;
// Convention (for test): Objects that are optimized have their first field
// set as a back pointer.
BasicTracedReference<v8::Value>* original_handle =
reinterpret_cast<BasicTracedReference<v8::Value>*>(
v8::Object::GetAlignedPointerFromInternalField(
handle.As<v8::Object>(), 0));
original_handle->Reset();
}
private:
const uint16_t class_id_to_optimize_;
};
template <typename T>
void SetupOptimizedAndNonOptimizedHandle(v8::Isolate* isolate,
uint16_t optimized_class_id,
T* optimized_handle,
T* non_optimized_handle) {
v8::HandleScope scope(isolate);
v8::Local<v8::Object> optimized_object = WrapperHelper::CreateWrapper(
isolate->GetCurrentContext(), optimized_handle, nullptr);
EXPECT_TRUE(optimized_handle->IsEmpty());
*optimized_handle = T(isolate, optimized_object);
EXPECT_FALSE(optimized_handle->IsEmpty());
optimized_handle->SetWrapperClassId(optimized_class_id);
v8::Local<v8::Object> non_optimized_object = WrapperHelper::CreateWrapper(
isolate->GetCurrentContext(), nullptr, nullptr);
EXPECT_TRUE(non_optimized_handle->IsEmpty());
*non_optimized_handle = T(isolate, non_optimized_object);
EXPECT_FALSE(non_optimized_handle->IsEmpty());
}
} // namespace
TEST_F(EmbedderRootsHandlerTest,
TracedReferenceNoDestructorReclaimedOnScavenge) {
if (v8_flags.single_generation) return;
ManualGCScope manual_gc(i_isolate());
v8::HandleScope scope(v8_isolate());
ClearingEmbedderRootsHandler handler(kClassIdToOptimize);
TemporaryEmbedderRootsHandleScope roots_handler_scope(v8_isolate(), &handler);
auto* traced_handles = i_isolate()->traced_handles();
const size_t initial_count = traced_handles->used_node_count();
auto* optimized_handle = new v8::TracedReference<v8::Value>();
auto* non_optimized_handle = new v8::TracedReference<v8::Value>();
SetupOptimizedAndNonOptimizedHandle(v8_isolate(), kClassIdToOptimize,
optimized_handle, non_optimized_handle);
EXPECT_EQ(initial_count + 2, traced_handles->used_node_count());
YoungGC();
EXPECT_EQ(initial_count + 1, traced_handles->used_node_count());
EXPECT_TRUE(optimized_handle->IsEmpty());
delete optimized_handle;
EXPECT_FALSE(non_optimized_handle->IsEmpty());
non_optimized_handle->Reset();
delete non_optimized_handle;
EXPECT_EQ(initial_count, traced_handles->used_node_count());
}
namespace {
void ConstructJSObject(v8::Isolate* isolate, v8::Local<v8::Context> context,
v8::TracedReference<v8::Object>* handle) {
v8::HandleScope scope(isolate);
v8::Local<v8::Object> object(v8::Object::New(isolate));
EXPECT_FALSE(object.IsEmpty());
*handle = v8::TracedReference<v8::Object>(isolate, object);
EXPECT_FALSE(handle->IsEmpty());
}
template <typename T>
void ConstructJSApiObject(v8::Isolate* isolate, v8::Local<v8::Context> context,
T* global) {
v8::HandleScope scope(isolate);
v8::Local<v8::Object> object =
WrapperHelper::CreateWrapper(context, nullptr, nullptr);
EXPECT_FALSE(object.IsEmpty());
*global = T(isolate, object);
EXPECT_FALSE(global->IsEmpty());
}
enum class SurvivalMode { kSurvives, kDies };
template <typename ModifierFunction, typename ConstructTracedReferenceFunction,
typename GCFunction>
void TracedReferenceTest(v8::Isolate* isolate,
ConstructTracedReferenceFunction construct_function,
ModifierFunction modifier_function,
GCFunction gc_function, SurvivalMode survives) {
v8::HandleScope scope(isolate);
auto* traced_handles =
reinterpret_cast<i::Isolate*>(isolate)->traced_handles();
const size_t initial_count = traced_handles->used_node_count();
auto gc_invisible_handle =
std::make_unique<v8::TracedReference<v8::Object>>();
construct_function(isolate, isolate->GetCurrentContext(),
gc_invisible_handle.get());
ASSERT_TRUE(IsNewObjectInCorrectGeneration(isolate, *gc_invisible_handle));
modifier_function(*gc_invisible_handle);
const size_t after_modification_count = traced_handles->used_node_count();
gc_function();
// Cannot check the handle as it is not explicitly cleared by the GC. Instead
// check the handles count.
CHECK_IMPLIES(survives == SurvivalMode::kSurvives,
after_modification_count == traced_handles->used_node_count());
CHECK_IMPLIES(survives == SurvivalMode::kDies,
initial_count == traced_handles->used_node_count());
}
} // namespace
TEST_F(EmbedderRootsHandlerTest, TracedReferenceWrapperClassId) {
ManualGCScope manual_gc(i_isolate());
v8::HandleScope scope(v8_isolate());
v8::TracedReference<v8::Object> traced;
ConstructJSObject(v8_isolate(), v8_isolate()->GetCurrentContext(), &traced);
EXPECT_EQ(0, traced.WrapperClassId());
traced.SetWrapperClassId(17);
EXPECT_EQ(17, traced.WrapperClassId());
}
// EmbedderRootsHandler does not affect full GCs.
TEST_F(EmbedderRootsHandlerTest,
TracedReferenceToUnmodifiedJSObjectDiesOnFullGC) {
// When stressing incremental marking, a write barrier may keep the object
// alive.
if (v8_flags.stress_incremental_marking) return;
ClearingEmbedderRootsHandler handler(kClassIdToOptimize);
TemporaryEmbedderRootsHandleScope roots_handler_scope(v8_isolate(), &handler);
TracedReferenceTest(
v8_isolate(), ConstructJSObject,
[](const TracedReference<v8::Object>&) {}, [this]() { FullGC(); },
SurvivalMode::kDies);
}
// EmbedderRootsHandler does not affect full GCs.
TEST_F(
EmbedderRootsHandlerTest,
TracedReferenceToUnmodifiedJSObjectDiesOnFullGCEvenWhenPointeeIsHeldAlive) {
ManualGCScope manual_gcs(i_isolate());
ClearingEmbedderRootsHandler handler(kClassIdToOptimize);
TemporaryEmbedderRootsHandleScope roots_handler_scope(v8_isolate(), &handler);
// The TracedReference itself will die as it's not found by the full GC. The
// pointee will be kept alive through other means.
v8::Global<v8::Object> strong_global;
TracedReferenceTest(
v8_isolate(), ConstructJSObject,
[this, &strong_global](const TracedReference<v8::Object>& handle) {
v8::HandleScope scope(v8_isolate());
strong_global =
v8::Global<v8::Object>(v8_isolate(), handle.Get(v8_isolate()));
},
[this, &strong_global]() {
FullGC();
strong_global.Reset();
},
SurvivalMode::kDies);
}
// EmbedderRootsHandler does not affect non-API objects.
TEST_F(EmbedderRootsHandlerTest,
TracedReferenceToUnmodifiedJSObjectSurvivesYoungGC) {
if (v8_flags.single_generation) return;
ManualGCScope manual_gc(i_isolate());
ClearingEmbedderRootsHandler handler(kClassIdToOptimize);
TemporaryEmbedderRootsHandleScope roots_handler_scope(v8_isolate(), &handler);
TracedReferenceTest(
v8_isolate(), ConstructJSObject,
[](const TracedReference<v8::Object>&) {}, [this]() { YoungGC(); },
SurvivalMode::kSurvives);
}
// EmbedderRootsHandler does not affect non-API objects, even when the handle
// has a wrapper class id that allows for reclamation.
TEST_F(
EmbedderRootsHandlerTest,
TracedReferenceToUnmodifiedJSObjectSurvivesYoungGCWhenExcludedFromRoots) {
if (v8_flags.single_generation) return;
ManualGCScope manual_gc(i_isolate());
ClearingEmbedderRootsHandler handler(kClassIdToOptimize);
TemporaryEmbedderRootsHandleScope roots_handler_scope(v8_isolate(), &handler);
TracedReferenceTest(
v8_isolate(), ConstructJSObject,
[](TracedReference<v8::Object>& handle) {
handle.SetWrapperClassId(kClassIdToOptimize);
},
[this]() { YoungGC(); }, SurvivalMode::kSurvives);
}
// EmbedderRootsHandler does not affect API objects for handles that have
// their class ids not set up.
TEST_F(EmbedderRootsHandlerTest,
TracedReferenceToUnmodifiedJSApiObjectSurvivesScavengePerDefault) {
if (v8_flags.single_generation) return;
ManualGCScope manual_gc(i_isolate());
ClearingEmbedderRootsHandler handler(kClassIdToOptimize);
TemporaryEmbedderRootsHandleScope roots_handler_scope(v8_isolate(), &handler);
TracedReferenceTest(
v8_isolate(), ConstructJSApiObject<TracedReference<v8::Object>>,
[](const TracedReference<v8::Object>&) {}, [this]() { YoungGC(); },
SurvivalMode::kSurvives);
}
// EmbedderRootsHandler resets API objects for handles that have their class ids
// set to being optimized.
TEST_F(
EmbedderRootsHandlerTest,
TracedReferenceToUnmodifiedJSApiObjectDiesOnScavengeWhenExcludedFromRoots) {
if (v8_flags.single_generation) return;
ManualGCScope manual_gc(i_isolate());
ClearingEmbedderRootsHandler handler(kClassIdToOptimize);
TemporaryEmbedderRootsHandleScope roots_handler_scope(v8_isolate(), &handler);
TracedReferenceTest(
v8_isolate(), ConstructJSApiObject<TracedReference<v8::Object>>,
[this](TracedReference<v8::Object>& handle) {
handle.SetWrapperClassId(kClassIdToOptimize);
{
HandleScope handles(i_isolate());
auto local = handle.Get(v8_isolate());
local->SetAlignedPointerInInternalField(0, &handle);
}
},
[this]() { YoungGC(); }, SurvivalMode::kDies);
}
} // namespace v8::internal

View File

@ -10,7 +10,6 @@
#include "include/cppgc/heap-consistency.h"
#include "include/cppgc/internal/api-constants.h"
#include "include/cppgc/persistent.h"
#include "include/cppgc/platform.h"
#include "include/cppgc/testing.h"
#include "include/libplatform/libplatform.h"
#include "include/v8-context.h"
@ -19,7 +18,6 @@
#include "include/v8-object.h"
#include "include/v8-traced-handle.h"
#include "src/api/api-inl.h"
#include "src/base/platform/time.h"
#include "src/common/globals.h"
#include "src/heap/cppgc-js/cpp-heap.h"
#include "src/heap/cppgc/heap-object-header.h"
@ -28,8 +26,7 @@
#include "test/unittests/heap/cppgc-js/unified-heap-utils.h"
#include "test/unittests/heap/heap-utils.h"
namespace v8 {
namespace internal {
namespace v8::internal {
namespace {
@ -61,13 +58,11 @@ TEST_F(UnifiedHeapTest, OnlyGC) { CollectGarbageWithEmbedderStack(); }
TEST_F(UnifiedHeapTest, FindingV8ToBlinkReference) {
v8::HandleScope scope(v8_isolate());
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
uint16_t wrappable_type = WrapperHelper::kTracedEmbedderId;
auto* wrappable_object =
cppgc::MakeGarbageCollected<Wrappable>(allocation_handle());
v8::Local<v8::Object> api_object =
WrapperHelper::CreateWrapper(context, &wrappable_type, wrappable_object);
v8::Local<v8::Object> api_object = WrapperHelper::CreateWrapper(
v8_isolate()->GetCurrentContext(), &wrappable_type, wrappable_object);
Wrappable::destructor_callcount = 0;
EXPECT_FALSE(api_object.IsEmpty());
EXPECT_EQ(0u, Wrappable::destructor_callcount);
@ -80,12 +75,11 @@ TEST_F(UnifiedHeapTest, FindingV8ToBlinkReference) {
TEST_F(UnifiedHeapTest, WriteBarrierV8ToCppReference) {
if (!v8_flags.incremental_marking) return;
v8::HandleScope scope(v8_isolate());
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
void* wrappable = cppgc::MakeGarbageCollected<Wrappable>(allocation_handle());
v8::Local<v8::Object> api_object =
WrapperHelper::CreateWrapper(context, nullptr, nullptr);
v8::Local<v8::Object> api_object = WrapperHelper::CreateWrapper(
v8_isolate()->GetCurrentContext(), nullptr, nullptr);
Wrappable::destructor_callcount = 0;
WrapperHelper::ResetWrappableConnection(api_object);
SimulateIncrementalMarking();
@ -105,8 +99,6 @@ class Unreferenced : public cppgc::GarbageCollected<Unreferenced> {
TEST_F(UnifiedHeapTest, FreeUnreferencedDuringNoGcScope) {
v8::HandleScope handle_scope(v8_isolate());
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
auto* unreferenced = cppgc::MakeGarbageCollected<Unreferenced>(
allocation_handle(),
cppgc::AdditionalBytes(cppgc::internal::api_constants::kMB));
@ -134,8 +126,6 @@ TEST_F(UnifiedHeapTest, FreeUnreferencedDuringNoGcScope) {
TEST_F(UnifiedHeapTest, TracedReferenceRetainsFromStack) {
v8::HandleScope handle_scope(v8_isolate());
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
TracedReference<v8::Object> holder;
{
v8::HandleScope inner_handle_scope(v8_isolate());
@ -211,8 +201,7 @@ TEST_F(UnifiedHeapDetachedTest, StandaloneTestingHeap) {
heap.FinalizeGarbageCollection(cppgc::EmbedderStackState::kNoHeapPointers);
}
} // namespace internal
} // namespace v8
} // namespace v8::internal
namespace cppgc {
@ -225,8 +214,7 @@ constexpr size_t CustomSpaceForTest::kSpaceIndex;
} // namespace cppgc
namespace v8 {
namespace internal {
namespace v8::internal {
namespace {
@ -267,8 +255,7 @@ class GCed final : public cppgc::GarbageCollected<GCed> {
};
} // namespace
} // namespace internal
} // namespace v8
} // namespace v8::internal
namespace cppgc {
template <>
@ -278,8 +265,7 @@ struct SpaceTrait<v8::internal::GCed> {
} // namespace cppgc
namespace v8 {
namespace internal {
namespace v8::internal {
namespace {
@ -375,8 +361,6 @@ class InConstructionObjectReferringToGlobalHandle final
TEST_F(UnifiedHeapTest, InConstructionObjectReferringToGlobalHandle) {
v8::HandleScope handle_scope(v8_isolate());
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
{
v8::HandleScope inner_handle_scope(v8_isolate());
auto local = v8::Object::New(v8_isolate());
@ -408,8 +392,6 @@ class ResetReferenceInDestructorObject final
TEST_F(UnifiedHeapTest, ResetReferenceInDestructor) {
v8::HandleScope handle_scope(v8_isolate());
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
{
v8::HandleScope inner_handle_scope(v8_isolate());
auto local = v8::Object::New(v8_isolate());
@ -420,5 +402,302 @@ TEST_F(UnifiedHeapTest, ResetReferenceInDestructor) {
CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic);
}
} // namespace internal
} // namespace v8
TEST_F(UnifiedHeapTest, OnStackReferencesAreTemporary) {
ManualGCScope manual_gc(i_isolate());
v8::Global<v8::Object> observer;
{
v8::TracedReference<v8::Value> stack_ref;
v8::HandleScope scope(v8_isolate());
v8::Local<v8::Object> api_object = WrapperHelper::CreateWrapper(
v8_isolate()->GetCurrentContext(), nullptr, nullptr);
stack_ref.Reset(v8_isolate(), api_object);
observer.Reset(v8_isolate(), api_object);
observer.SetWeak();
}
EXPECT_FALSE(observer.IsEmpty());
{
// Conservative scanning may find stale pointers to on-stack handles.
// Disable scanning, assuming the slots are overwritten.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(
reinterpret_cast<Isolate*>(v8_isolate())->heap());
CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic);
}
EXPECT_TRUE(observer.IsEmpty());
}
TEST_F(UnifiedHeapTest, TracedReferenceOnStack) {
ManualGCScope manual_gc(i_isolate());
v8::Global<v8::Object> observer;
v8::TracedReference<v8::Value> stack_ref;
{
v8::HandleScope scope(v8_isolate());
v8::Local<v8::Object> object = WrapperHelper::CreateWrapper(
v8_isolate()->GetCurrentContext(), nullptr, nullptr);
stack_ref.Reset(v8_isolate(), object);
observer.Reset(v8_isolate(), object);
observer.SetWeak();
}
EXPECT_FALSE(observer.IsEmpty());
FullGC();
EXPECT_FALSE(observer.IsEmpty());
}
namespace {
enum class Operation {
kCopy,
kMove,
};
template <typename T>
V8_NOINLINE void PerformOperation(Operation op, T* target, T* source) {
switch (op) {
case Operation::kMove:
*target = std::move(*source);
break;
case Operation::kCopy:
*target = *source;
source->Reset();
break;
}
}
enum class TargetHandling {
kNonInitialized,
kInitializedYoungGen,
kInitializedOldGen
};
class GCedWithHeapRef final : public cppgc::GarbageCollected<GCedWithHeapRef> {
public:
v8::TracedReference<v8::Value> heap_handle;
void Trace(cppgc::Visitor* visitor) const { visitor->Trace(heap_handle); }
};
V8_NOINLINE void StackToHeapTest(v8::Isolate* v8_isolate, Operation op,
TargetHandling target_handling) {
v8::Global<v8::Object> observer;
v8::TracedReference<v8::Value> stack_handle;
v8::CppHeap* cpp_heap = v8_isolate->GetCppHeap();
cppgc::Persistent<GCedWithHeapRef> cpp_heap_obj =
cppgc::MakeGarbageCollected<GCedWithHeapRef>(
cpp_heap->GetAllocationHandle());
if (target_handling != TargetHandling::kNonInitialized) {
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Object> to_object = WrapperHelper::CreateWrapper(
v8_isolate->GetCurrentContext(), nullptr, nullptr);
EXPECT_TRUE(
IsNewObjectInCorrectGeneration(*v8::Utils::OpenHandle(*to_object)));
if (!v8_flags.single_generation &&
target_handling == TargetHandling::kInitializedOldGen) {
FullGC(v8_isolate);
EXPECT_FALSE(
i::Heap::InYoungGeneration(*v8::Utils::OpenHandle(*to_object)));
}
cpp_heap_obj->heap_handle.Reset(v8_isolate, to_object);
}
{
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Object> object = WrapperHelper::CreateWrapper(
v8_isolate->GetCurrentContext(), nullptr, nullptr);
stack_handle.Reset(v8_isolate, object);
observer.Reset(v8_isolate, object);
observer.SetWeak();
}
EXPECT_FALSE(observer.IsEmpty());
FullGC(v8_isolate);
EXPECT_FALSE(observer.IsEmpty());
PerformOperation(op, &cpp_heap_obj->heap_handle, &stack_handle);
FullGC(v8_isolate);
EXPECT_FALSE(observer.IsEmpty());
cpp_heap_obj.Clear();
{
// Conservative scanning may find stale pointers to on-stack handles.
// Disable scanning, assuming the slots are overwritten.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(
reinterpret_cast<i::Isolate*>(v8_isolate)->heap());
EmbedderStackStateScope scope =
EmbedderStackStateScope::ExplicitScopeForTesting(
reinterpret_cast<i::Isolate*>(v8_isolate)
->heap()
->local_embedder_heap_tracer(),
cppgc::EmbedderStackState::kNoHeapPointers);
FullGC(v8_isolate);
}
ASSERT_TRUE(observer.IsEmpty());
}
V8_NOINLINE void HeapToStackTest(v8::Isolate* v8_isolate, Operation op,
TargetHandling target_handling) {
v8::Global<v8::Object> observer;
v8::TracedReference<v8::Value> stack_handle;
v8::CppHeap* cpp_heap = v8_isolate->GetCppHeap();
cppgc::Persistent<GCedWithHeapRef> cpp_heap_obj =
cppgc::MakeGarbageCollected<GCedWithHeapRef>(
cpp_heap->GetAllocationHandle());
if (target_handling != TargetHandling::kNonInitialized) {
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Object> to_object = WrapperHelper::CreateWrapper(
v8_isolate->GetCurrentContext(), nullptr, nullptr);
EXPECT_TRUE(
IsNewObjectInCorrectGeneration(*v8::Utils::OpenHandle(*to_object)));
if (!v8_flags.single_generation &&
target_handling == TargetHandling::kInitializedOldGen) {
FullGC(v8_isolate);
EXPECT_FALSE(
i::Heap::InYoungGeneration(*v8::Utils::OpenHandle(*to_object)));
}
stack_handle.Reset(v8_isolate, to_object);
}
{
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Object> object = WrapperHelper::CreateWrapper(
v8_isolate->GetCurrentContext(), nullptr, nullptr);
cpp_heap_obj->heap_handle.Reset(v8_isolate, object);
observer.Reset(v8_isolate, object);
observer.SetWeak();
}
EXPECT_FALSE(observer.IsEmpty());
FullGC(v8_isolate);
EXPECT_FALSE(observer.IsEmpty());
PerformOperation(op, &stack_handle, &cpp_heap_obj->heap_handle);
FullGC(v8_isolate);
EXPECT_FALSE(observer.IsEmpty());
stack_handle.Reset();
FullGC(v8_isolate);
EXPECT_TRUE(observer.IsEmpty());
}
V8_NOINLINE void StackToStackTest(v8::Isolate* v8_isolate, Operation op,
TargetHandling target_handling) {
v8::Global<v8::Object> observer;
v8::TracedReference<v8::Value> stack_handle1;
v8::TracedReference<v8::Value> stack_handle2;
if (target_handling != TargetHandling::kNonInitialized) {
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Object> to_object = WrapperHelper::CreateWrapper(
v8_isolate->GetCurrentContext(), nullptr, nullptr);
EXPECT_TRUE(
IsNewObjectInCorrectGeneration(*v8::Utils::OpenHandle(*to_object)));
if (!v8_flags.single_generation &&
target_handling == TargetHandling::kInitializedOldGen) {
FullGC(v8_isolate);
EXPECT_FALSE(
i::Heap::InYoungGeneration(*v8::Utils::OpenHandle(*to_object)));
}
stack_handle2.Reset(v8_isolate, to_object);
}
{
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Object> object = WrapperHelper::CreateWrapper(
v8_isolate->GetCurrentContext(), nullptr, nullptr);
stack_handle1.Reset(v8_isolate, object);
observer.Reset(v8_isolate, object);
observer.SetWeak();
}
EXPECT_FALSE(observer.IsEmpty());
FullGC(v8_isolate);
EXPECT_FALSE(observer.IsEmpty());
PerformOperation(op, &stack_handle2, &stack_handle1);
FullGC(v8_isolate);
EXPECT_FALSE(observer.IsEmpty());
stack_handle2.Reset();
FullGC(v8_isolate);
EXPECT_TRUE(observer.IsEmpty());
}
} // namespace
TEST_F(UnifiedHeapTest, TracedReferenceMove) {
ManualGCScope manual_gc(i_isolate());
StackToHeapTest(v8_isolate(), Operation::kMove,
TargetHandling::kNonInitialized);
StackToHeapTest(v8_isolate(), Operation::kMove,
TargetHandling::kInitializedYoungGen);
StackToHeapTest(v8_isolate(), Operation::kMove,
TargetHandling::kInitializedOldGen);
HeapToStackTest(v8_isolate(), Operation::kMove,
TargetHandling::kNonInitialized);
HeapToStackTest(v8_isolate(), Operation::kMove,
TargetHandling::kInitializedYoungGen);
HeapToStackTest(v8_isolate(), Operation::kMove,
TargetHandling::kInitializedOldGen);
StackToStackTest(v8_isolate(), Operation::kMove,
TargetHandling::kNonInitialized);
StackToStackTest(v8_isolate(), Operation::kMove,
TargetHandling::kInitializedYoungGen);
StackToStackTest(v8_isolate(), Operation::kMove,
TargetHandling::kInitializedOldGen);
}
TEST_F(UnifiedHeapTest, TracedReferenceCopy) {
ManualGCScope manual_gc(i_isolate());
StackToHeapTest(v8_isolate(), Operation::kCopy,
TargetHandling::kNonInitialized);
StackToHeapTest(v8_isolate(), Operation::kCopy,
TargetHandling::kInitializedYoungGen);
StackToHeapTest(v8_isolate(), Operation::kCopy,
TargetHandling::kInitializedOldGen);
HeapToStackTest(v8_isolate(), Operation::kCopy,
TargetHandling::kNonInitialized);
HeapToStackTest(v8_isolate(), Operation::kCopy,
TargetHandling::kInitializedYoungGen);
HeapToStackTest(v8_isolate(), Operation::kCopy,
TargetHandling::kInitializedOldGen);
StackToStackTest(v8_isolate(), Operation::kCopy,
TargetHandling::kNonInitialized);
StackToStackTest(v8_isolate(), Operation::kCopy,
TargetHandling::kInitializedYoungGen);
StackToStackTest(v8_isolate(), Operation::kCopy,
TargetHandling::kInitializedOldGen);
}
TEST_F(UnifiedHeapTest, TracingInEphemerons) {
// Tests that wrappers that are part of ephemerons are traced.
ManualGCScope manual_gc(i_isolate());
v8::HandleScope scope(v8_isolate());
v8::Local<v8::Context> context = v8::Context::New(v8_isolate());
v8::Context::Scope context_scope(context);
uint16_t wrappable_type = WrapperHelper::kTracedEmbedderId;
Wrappable::destructor_callcount = 0;
v8::Local<v8::Object> key =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
Handle<JSWeakMap> weak_map = i_isolate()->factory()->NewJSWeakMap();
{
v8::HandleScope inner_scope(v8_isolate());
// C++ object that should be traced through ephemeron value.
auto* wrappable_object =
cppgc::MakeGarbageCollected<Wrappable>(allocation_handle());
v8::Local<v8::Object> value = WrapperHelper::CreateWrapper(
v8_isolate()->GetCurrentContext(), &wrappable_type, wrappable_object);
EXPECT_FALSE(value.IsEmpty());
Handle<JSObject> js_key =
handle(JSObject::cast(*v8::Utils::OpenHandle(*key)), i_isolate());
Handle<JSReceiver> js_value = v8::Utils::OpenHandle(*value);
int32_t hash = js_key->GetOrCreateHash(i_isolate()).value();
JSWeakCollection::Set(weak_map, js_key, js_value, hash);
}
CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic);
EXPECT_EQ(Wrappable::destructor_callcount, 0u);
}
TEST_F(UnifiedHeapTest, TracedReferenceHandlesDoNotLeak) {
// TracedReference handles are not cleared by the destructor of the embedder
// object. To avoid leaks we need to mark these handles during GC.
// This test checks that unmarked handles do not leak.
ManualGCScope manual_gc(i_isolate());
v8::HandleScope scope(v8_isolate());
v8::TracedReference<v8::Value> ref;
ref.Reset(v8_isolate(), v8::Undefined(v8_isolate()));
auto* traced_handles = i_isolate()->traced_handles();
const size_t initial_count = traced_handles->used_node_count();
CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic);
CollectGarbageWithoutEmbedderStack(cppgc::Heap::SweepingType::kAtomic);
const size_t final_count = traced_handles->used_node_count();
EXPECT_EQ(initial_count, final_count + 1);
}
} // namespace v8::internal

View File

@ -11,6 +11,7 @@
#include "src/heap/cppgc-js/cpp-heap.h"
#include "src/heap/heap.h"
#include "src/objects/objects-inl.h"
#include "test/unittests/heap/heap-utils.h"
namespace v8 {
namespace internal {
@ -24,6 +25,9 @@ UnifiedHeapTest::UnifiedHeapTest(
V8::GetCurrentPlatform(),
CppHeapCreateParams{std::move(custom_spaces),
WrapperHelper::DefaultWrapperDescriptor()})) {
// --stress-incremental-marking may have started an incremental GC at this
// point already.
FinalizeGCIfRunning(isolate());
isolate()->heap()->AttachCppHeap(cpp_heap_.get());
}

View File

@ -18,7 +18,7 @@ namespace internal {
class CppHeap;
class UnifiedHeapTest : public TestWithHeapInternals {
class UnifiedHeapTest : public TestWithHeapInternalsAndContext {
public:
UnifiedHeapTest();
explicit UnifiedHeapTest(

View File

@ -406,34 +406,6 @@ TEST_F(EmbedderTracingTest, EmbedderRegisteringV8Reference) {
EXPECT_FALSE(handle->IsEmpty());
}
TEST_F(EmbedderTracingTest, TracingInEphemerons) {
// Tests that wrappers that are part of ephemerons are traced.
ManualGCScope manual_gc(i_isolate());
TestEmbedderHeapTracer tracer;
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
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> key =
v8::Local<v8::Object>::New(v8_isolate(), v8::Object::New(v8_isolate()));
void* first_and_second_field = reinterpret_cast<void*>(0x8);
Handle<JSWeakMap> weak_map = i_isolate()->factory()->NewJSWeakMap();
{
v8::HandleScope inner_scope(v8_isolate());
v8::Local<v8::Object> api_object = ConstructTraceableJSApiObject(
context, first_and_second_field, first_and_second_field);
EXPECT_FALSE(api_object.IsEmpty());
Handle<JSObject> js_key =
handle(JSObject::cast(*v8::Utils::OpenHandle(*key)), i_isolate());
Handle<JSReceiver> js_api_object = v8::Utils::OpenHandle(*api_object);
int32_t hash = js_key->GetOrCreateHash(i_isolate()).value();
JSWeakCollection::Set(weak_map, js_key, js_api_object, hash);
}
CollectGarbage(i::OLD_SPACE);
EXPECT_TRUE(tracer.IsRegisteredFromV8(first_and_second_field));
}
TEST_F(EmbedderTracingTest, FinalizeTracingIsNoopWhenNotMarking) {
ManualGCScope manual_gc(i_isolate());
TestEmbedderHeapTracer tracer;
@ -492,191 +464,8 @@ void ConstructJSObject(v8::Isolate* isolate, v8::Local<v8::Context> context,
EXPECT_FALSE(handle->IsEmpty());
}
template <typename T>
void ConstructJSApiObject(v8::Isolate* isolate, v8::Local<v8::Context> context,
T* global) {
v8::HandleScope scope(isolate);
v8::Local<v8::Object> object(
ConstructTraceableJSApiObject(context, nullptr, nullptr));
EXPECT_FALSE(object.IsEmpty());
*global = T(isolate, object);
EXPECT_FALSE(global->IsEmpty());
}
enum class SurvivalMode { kSurvives, kDies };
template <typename ModifierFunction, typename ConstructTracedReferenceFunction,
typename GCFunction>
void TracedReferenceTest(v8::Isolate* isolate,
ConstructTracedReferenceFunction construct_function,
ModifierFunction modifier_function,
GCFunction gc_function, SurvivalMode survives) {
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context = v8::Context::New(isolate);
v8::Context::Scope context_scope(context);
auto* global_handles =
reinterpret_cast<i::Isolate*>(isolate)->global_handles();
const size_t initial_count = global_handles->handles_count();
auto handle = std::make_unique<v8::TracedReference<v8::Object>>();
construct_function(isolate, context, handle.get());
ASSERT_TRUE(IsNewObjectInCorrectGeneration(isolate, *handle));
modifier_function(*handle);
const size_t after_modification_count = global_handles->handles_count();
gc_function();
// Cannot check the handle as it is not explicitly cleared by the GC. Instead
// check the handles count.
CHECK_IMPLIES(survives == SurvivalMode::kSurvives,
after_modification_count == global_handles->handles_count());
CHECK_IMPLIES(survives == SurvivalMode::kDies,
initial_count == global_handles->handles_count());
}
} // namespace
TEST_F(EmbedderTracingTest, TracedReferenceReset) {
v8::HandleScope scope(v8_isolate());
v8::TracedReference<v8::Object> handle;
ConstructJSObject(v8_isolate(), v8_isolate()->GetCurrentContext(), &handle);
EXPECT_FALSE(handle.IsEmpty());
handle.Reset();
EXPECT_TRUE(handle.IsEmpty());
}
TEST_F(EmbedderTracingTest, TracedReferenceCopyReferences) {
ManualGCScope manual_gc(i_isolate());
v8::HandleScope outer_scope(v8_isolate());
auto* traced_handles = i_isolate()->traced_handles();
const size_t initial_count = traced_handles->used_node_count();
auto handle1 = std::make_unique<v8::TracedReference<v8::Value>>();
{
v8::HandleScope scope(v8_isolate());
handle1->Reset(v8_isolate(), v8::Object::New(v8_isolate()));
}
auto handle2 = std::make_unique<v8::TracedReference<v8::Value>>(*handle1);
auto handle3 = std::make_unique<v8::TracedReference<v8::Value>>();
*handle3 = *handle2;
EXPECT_EQ(initial_count + 3, traced_handles->used_node_count());
EXPECT_FALSE(handle1->IsEmpty());
EXPECT_EQ(*handle1, *handle2);
EXPECT_EQ(*handle2, *handle3);
{
v8::HandleScope scope(v8_isolate());
auto tmp = v8::Local<v8::Value>::New(v8_isolate(), *handle3);
EXPECT_FALSE(tmp.IsEmpty());
// Conservative scanning may find stale pointers to on-stack handles.
// Disable scanning, assuming the slots are overwritten.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(
i_isolate()->heap());
EmbedderStackStateScope stack_scope =
EmbedderStackStateScope::ExplicitScopeForTesting(
reinterpret_cast<i::Isolate*>(v8_isolate())
->heap()
->local_embedder_heap_tracer(),
EmbedderHeapTracer::EmbedderStackState::kNoHeapPointers);
FullGC();
}
EXPECT_EQ(initial_count, traced_handles->used_node_count());
}
TEST_F(EmbedderTracingTest, TracedReferenceToUnmodifiedJSObjectDiesOnFullGC) {
// When stressing incremental marking, a write barrier may keep the object
// alive.
if (v8_flags.stress_incremental_marking) return;
TracedReferenceTest(
v8_isolate(), ConstructJSObject,
[](const TracedReference<v8::Object>&) {}, [this]() { FullGC(); },
SurvivalMode::kDies);
}
TEST_F(
EmbedderTracingTest,
TracedReferenceToUnmodifiedJSObjectDiesOnFullGCEvenWhenPointeeIsHeldAlive) {
ManualGCScope manual_gcs(i_isolate());
// The TracedReference itself will die as it's not found by the full GC. The
// pointee will be kept alive through other means.
v8::Global<v8::Object> strong_global;
TracedReferenceTest(
v8_isolate(), ConstructJSObject,
[this, &strong_global](const TracedReference<v8::Object>& handle) {
v8::HandleScope scope(v8_isolate());
strong_global =
v8::Global<v8::Object>(v8_isolate(), handle.Get(v8_isolate()));
},
[this, &strong_global]() {
FullGC();
strong_global.Reset();
},
SurvivalMode::kDies);
}
TEST_F(EmbedderTracingTest,
TracedReferenceToUnmodifiedJSObjectSurvivesYoungGC) {
if (v8_flags.single_generation) return;
ManualGCScope manual_gc(i_isolate());
TracedReferenceTest(
v8_isolate(), ConstructJSObject,
[](const TracedReference<v8::Object>&) {}, [this]() { YoungGC(); },
SurvivalMode::kSurvives);
}
TEST_F(
EmbedderTracingTest,
TracedReferenceToUnmodifiedJSObjectSurvivesYoungGCWhenExcludedFromRoots) {
if (v8_flags.single_generation) return;
ManualGCScope manual_gc(i_isolate());
TestEmbedderHeapTracer tracer;
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
TracedReferenceTest(
v8_isolate(), ConstructJSObject,
[&tracer](const TracedReference<v8::Object>& handle) {
tracer.DoNotConsiderAsRootForScavenge(&handle.As<v8::Value>());
},
[this]() { YoungGC(); }, SurvivalMode::kSurvives);
}
TEST_F(EmbedderTracingTest,
TracedReferenceToUnmodifiedJSApiObjectSurvivesScavengePerDefault) {
if (v8_flags.single_generation) return;
ManualGCScope manual_gc(i_isolate());
TestEmbedderHeapTracer tracer;
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
TracedReferenceTest(
v8_isolate(), ConstructJSApiObject<TracedReference<v8::Object>>,
[](const TracedReference<v8::Object>&) {}, [this]() { YoungGC(); },
SurvivalMode::kSurvives);
}
TEST_F(
EmbedderTracingTest,
TracedReferenceToUnmodifiedJSApiObjectDiesOnScavengeWhenExcludedFromRoots) {
if (v8_flags.single_generation) return;
ManualGCScope manual_gc(i_isolate());
TestEmbedderHeapTracer tracer;
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
TracedReferenceTest(
v8_isolate(), ConstructJSApiObject<TracedReference<v8::Object>>,
[&tracer](const TracedReference<v8::Object>& handle) {
tracer.DoNotConsiderAsRootForScavenge(&handle.As<v8::Value>());
},
[this]() { YoungGC(); }, SurvivalMode::kDies);
}
TEST_F(EmbedderTracingTest, TracedReferenceWrapperClassId) {
ManualGCScope manual_gc(i_isolate());
v8::HandleScope scope(v8_isolate());
TestEmbedderHeapTracer tracer;
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
v8::TracedReference<v8::Object> traced;
ConstructJSObject(v8_isolate(), v8_isolate()->GetCurrentContext(), &traced);
EXPECT_EQ(0, traced.WrapperClassId());
traced.SetWrapperClassId(17);
EXPECT_EQ(17, traced.WrapperClassId());
}
TEST_F(EmbedderTracingTest, TracedReferenceHandlesMarking) {
ManualGCScope manual_gc(i_isolate());
v8::HandleScope scope(v8_isolate());
@ -709,23 +498,6 @@ TEST_F(EmbedderTracingTest, TracedReferenceHandlesMarking) {
}
}
TEST_F(EmbedderTracingTest, TracedReferenceHandlesDoNotLeak) {
// TracedReference handles are not cleared by the destructor of the embedder
// object. To avoid leaks we need to mark these handles during GC.
// This test checks that unmarked handles do not leak.
ManualGCScope manual_gc(i_isolate());
v8::HandleScope scope(v8_isolate());
auto ref = std::make_unique<v8::TracedReference<v8::Value>>();
ref->Reset(v8_isolate(), v8::Undefined(v8_isolate()));
auto* traced_handles = i_isolate()->traced_handles();
const size_t initial_count = traced_handles->used_node_count();
// We need two GCs because handles are black allocated.
FullGC();
FullGC();
const size_t final_count = traced_handles->used_node_count();
EXPECT_EQ(initial_count, final_count + 1);
}
namespace {
START_ALLOW_USE_DEPRECATED()
@ -830,379 +602,6 @@ TEST_F(EmbedderTracingTest, BasicTracedReference) {
delete[] memory;
}
namespace {
// EmbedderRootsHandler that can optimize Scavenger handling when used with
// TracedReference.
class EmbedderHeapTracerNoDestructorNonTracingClearing final
: public v8::EmbedderRootsHandler {
public:
explicit EmbedderHeapTracerNoDestructorNonTracingClearing(
uint16_t class_id_to_optimize)
: class_id_to_optimize_(class_id_to_optimize) {}
bool IsRoot(const v8::TracedReference<v8::Value>& handle) final {
return handle.WrapperClassId() != class_id_to_optimize_;
}
void ResetRoot(const v8::TracedReference<v8::Value>& handle) final {
if (handle.WrapperClassId() != class_id_to_optimize_) return;
// Convention (for test): Objects that are optimized have their first field
// set as a back pointer.
BasicTracedReference<v8::Value>* original_handle =
reinterpret_cast<BasicTracedReference<v8::Value>*>(
v8::Object::GetAlignedPointerFromInternalField(
handle.As<v8::Object>(), 0));
original_handle->Reset();
}
private:
const uint16_t class_id_to_optimize_;
};
template <typename T>
void SetupOptimizedAndNonOptimizedHandle(v8::Isolate* isolate,
uint16_t optimized_class_id,
T* optimized_handle,
T* non_optimized_handle) {
v8::HandleScope scope(isolate);
v8::Local<v8::Object> optimized_object(ConstructTraceableJSApiObject(
isolate->GetCurrentContext(), optimized_handle, nullptr));
EXPECT_TRUE(optimized_handle->IsEmpty());
*optimized_handle = T(isolate, optimized_object);
EXPECT_FALSE(optimized_handle->IsEmpty());
optimized_handle->SetWrapperClassId(optimized_class_id);
v8::Local<v8::Object> non_optimized_object(ConstructTraceableJSApiObject(
isolate->GetCurrentContext(), nullptr, nullptr));
EXPECT_TRUE(non_optimized_handle->IsEmpty());
*non_optimized_handle = T(isolate, non_optimized_object);
EXPECT_FALSE(non_optimized_handle->IsEmpty());
}
} // namespace
TEST_F(EmbedderTracingTest, TracedReferenceNoDestructorReclaimedOnScavenge) {
if (v8_flags.single_generation) return;
ManualGCScope manual_gc(i_isolate());
v8::HandleScope scope(v8_isolate());
constexpr uint16_t kClassIdToOptimize = 23;
EmbedderHeapTracerNoDestructorNonTracingClearing handler(kClassIdToOptimize);
v8_isolate()->SetEmbedderRootsHandler(&handler);
auto* traced_handles = i_isolate()->traced_handles();
const size_t initial_count = traced_handles->used_node_count();
auto* optimized_handle = new v8::TracedReference<v8::Value>();
auto* non_optimized_handle = new v8::TracedReference<v8::Value>();
SetupOptimizedAndNonOptimizedHandle(v8_isolate(), kClassIdToOptimize,
optimized_handle, non_optimized_handle);
EXPECT_EQ(initial_count + 2, traced_handles->used_node_count());
YoungGC();
EXPECT_EQ(initial_count + 1, traced_handles->used_node_count());
EXPECT_TRUE(optimized_handle->IsEmpty());
delete optimized_handle;
EXPECT_FALSE(non_optimized_handle->IsEmpty());
non_optimized_handle->Reset();
delete non_optimized_handle;
EXPECT_EQ(initial_count, traced_handles->used_node_count());
v8_isolate()->SetEmbedderRootsHandler(nullptr);
}
namespace {
template <typename T>
V8_NOINLINE void OnStackTest(v8::Isolate* v8_isolate,
TestEmbedderHeapTracer* tracer) {
v8::Global<v8::Object> observer;
T stack_ref;
{
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
v8_isolate->GetCurrentContext(), nullptr, nullptr));
stack_ref.Reset(v8_isolate, object);
observer.Reset(v8_isolate, object);
observer.SetWeak();
}
EXPECT_FALSE(observer.IsEmpty());
FullGC(v8_isolate);
EXPECT_FALSE(observer.IsEmpty());
}
} // namespace
TEST_F(EmbedderTracingTest, TracedReferenceOnStack) {
ManualGCScope manual_gc(i_isolate());
TestEmbedderHeapTracer tracer;
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
tracer.SetStackStart(
static_cast<void*>(base::Stack::GetCurrentFrameAddress()));
OnStackTest<v8::TracedReference<v8::Value>>(v8_isolate(), &tracer);
}
namespace {
enum class Operation {
kCopy,
kMove,
};
template <typename T>
V8_NOINLINE void PerformOperation(Operation op, T* target, T* source) {
switch (op) {
case Operation::kMove:
*target = std::move(*source);
break;
case Operation::kCopy:
*target = *source;
source->Reset();
break;
}
}
enum class TargetHandling {
kNonInitialized,
kInitializedYoungGen,
kInitializedOldGen
};
V8_NOINLINE void StackToHeapTest(v8::Isolate* v8_isolate,
TestEmbedderHeapTracer* tracer, Operation op,
TargetHandling target_handling) {
v8::Global<v8::Object> observer;
v8::TracedReference<v8::Value> stack_handle;
v8::TracedReference<v8::Value>* heap_handle =
new v8::TracedReference<v8::Value>();
if (target_handling != TargetHandling::kNonInitialized) {
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Object> to_object(ConstructTraceableJSApiObject(
v8_isolate->GetCurrentContext(), nullptr, nullptr));
EXPECT_TRUE(
IsNewObjectInCorrectGeneration(*v8::Utils::OpenHandle(*to_object)));
if (!v8_flags.single_generation &&
target_handling == TargetHandling::kInitializedOldGen) {
FullGC(v8_isolate);
EXPECT_FALSE(
i::Heap::InYoungGeneration(*v8::Utils::OpenHandle(*to_object)));
}
heap_handle->Reset(v8_isolate, to_object);
}
{
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
v8_isolate->GetCurrentContext(), nullptr, nullptr));
stack_handle.Reset(v8_isolate, object);
observer.Reset(v8_isolate, object);
observer.SetWeak();
}
EXPECT_FALSE(observer.IsEmpty());
tracer->AddReferenceForTracing(heap_handle);
FullGC(v8_isolate);
EXPECT_FALSE(observer.IsEmpty());
PerformOperation(op, heap_handle, &stack_handle);
tracer->AddReferenceForTracing(heap_handle);
FullGC(v8_isolate);
EXPECT_FALSE(observer.IsEmpty());
{
// Conservative scanning may find stale pointers to on-stack handles.
// Disable scanning, assuming the slots are overwritten.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(
reinterpret_cast<i::Isolate*>(v8_isolate)->heap());
EmbedderStackStateScope scope =
EmbedderStackStateScope::ExplicitScopeForTesting(
reinterpret_cast<i::Isolate*>(v8_isolate)
->heap()
->local_embedder_heap_tracer(),
EmbedderHeapTracer::EmbedderStackState::kNoHeapPointers);
FullGC(v8_isolate);
}
ASSERT_TRUE(observer.IsEmpty());
delete heap_handle;
}
V8_NOINLINE void HeapToStackTest(v8::Isolate* v8_isolate,
TestEmbedderHeapTracer* tracer, Operation op,
TargetHandling target_handling) {
v8::Global<v8::Object> observer;
v8::TracedReference<v8::Value> stack_handle;
v8::TracedReference<v8::Value>* heap_handle =
new v8::TracedReference<v8::Value>();
if (target_handling != TargetHandling::kNonInitialized) {
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Object> to_object(ConstructTraceableJSApiObject(
v8_isolate->GetCurrentContext(), nullptr, nullptr));
EXPECT_TRUE(
IsNewObjectInCorrectGeneration(*v8::Utils::OpenHandle(*to_object)));
if (!v8_flags.single_generation &&
target_handling == TargetHandling::kInitializedOldGen) {
FullGC(v8_isolate);
EXPECT_FALSE(
i::Heap::InYoungGeneration(*v8::Utils::OpenHandle(*to_object)));
}
stack_handle.Reset(v8_isolate, to_object);
}
{
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
v8_isolate->GetCurrentContext(), nullptr, nullptr));
heap_handle->Reset(v8_isolate, object);
observer.Reset(v8_isolate, object);
observer.SetWeak();
}
EXPECT_FALSE(observer.IsEmpty());
tracer->AddReferenceForTracing(heap_handle);
FullGC(v8_isolate);
EXPECT_FALSE(observer.IsEmpty());
PerformOperation(op, &stack_handle, heap_handle);
FullGC(v8_isolate);
EXPECT_FALSE(observer.IsEmpty());
stack_handle.Reset();
FullGC(v8_isolate);
EXPECT_TRUE(observer.IsEmpty());
delete heap_handle;
}
V8_NOINLINE void StackToStackTest(v8::Isolate* v8_isolate,
TestEmbedderHeapTracer* tracer, Operation op,
TargetHandling target_handling) {
v8::Global<v8::Object> observer;
v8::TracedReference<v8::Value> stack_handle1;
v8::TracedReference<v8::Value> stack_handle2;
if (target_handling != TargetHandling::kNonInitialized) {
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Object> to_object(ConstructTraceableJSApiObject(
v8_isolate->GetCurrentContext(), nullptr, nullptr));
EXPECT_TRUE(
IsNewObjectInCorrectGeneration(*v8::Utils::OpenHandle(*to_object)));
if (!v8_flags.single_generation &&
target_handling == TargetHandling::kInitializedOldGen) {
FullGC(v8_isolate);
EXPECT_FALSE(
i::Heap::InYoungGeneration(*v8::Utils::OpenHandle(*to_object)));
}
stack_handle2.Reset(v8_isolate, to_object);
}
{
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Object> object(ConstructTraceableJSApiObject(
v8_isolate->GetCurrentContext(), nullptr, nullptr));
stack_handle1.Reset(v8_isolate, object);
observer.Reset(v8_isolate, object);
observer.SetWeak();
}
EXPECT_FALSE(observer.IsEmpty());
FullGC(v8_isolate);
EXPECT_FALSE(observer.IsEmpty());
PerformOperation(op, &stack_handle2, &stack_handle1);
FullGC(v8_isolate);
EXPECT_FALSE(observer.IsEmpty());
stack_handle2.Reset();
FullGC(v8_isolate);
EXPECT_TRUE(observer.IsEmpty());
}
} // namespace
TEST_F(EmbedderTracingTest, TracedReferenceMove) {
ManualGCScope manual_gc(i_isolate());
TestEmbedderHeapTracer tracer;
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
tracer.SetStackStart(
static_cast<void*>(base::Stack::GetCurrentFrameAddress()));
StackToHeapTest(v8_isolate(), &tracer, Operation::kMove,
TargetHandling::kNonInitialized);
StackToHeapTest(v8_isolate(), &tracer, Operation::kMove,
TargetHandling::kInitializedYoungGen);
StackToHeapTest(v8_isolate(), &tracer, Operation::kMove,
TargetHandling::kInitializedOldGen);
HeapToStackTest(v8_isolate(), &tracer, Operation::kMove,
TargetHandling::kNonInitialized);
HeapToStackTest(v8_isolate(), &tracer, Operation::kMove,
TargetHandling::kInitializedYoungGen);
HeapToStackTest(v8_isolate(), &tracer, Operation::kMove,
TargetHandling::kInitializedOldGen);
StackToStackTest(v8_isolate(), &tracer, Operation::kMove,
TargetHandling::kNonInitialized);
StackToStackTest(v8_isolate(), &tracer, Operation::kMove,
TargetHandling::kInitializedYoungGen);
StackToStackTest(v8_isolate(), &tracer, Operation::kMove,
TargetHandling::kInitializedOldGen);
}
TEST_F(EmbedderTracingTest, TracedReferenceCopy) {
ManualGCScope manual_gc(i_isolate());
TestEmbedderHeapTracer tracer;
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
tracer.SetStackStart(
static_cast<void*>(base::Stack::GetCurrentFrameAddress()));
StackToHeapTest(v8_isolate(), &tracer, Operation::kCopy,
TargetHandling::kNonInitialized);
StackToHeapTest(v8_isolate(), &tracer, Operation::kCopy,
TargetHandling::kInitializedYoungGen);
StackToHeapTest(v8_isolate(), &tracer, Operation::kCopy,
TargetHandling::kInitializedOldGen);
HeapToStackTest(v8_isolate(), &tracer, Operation::kCopy,
TargetHandling::kNonInitialized);
HeapToStackTest(v8_isolate(), &tracer, Operation::kCopy,
TargetHandling::kInitializedYoungGen);
HeapToStackTest(v8_isolate(), &tracer, Operation::kCopy,
TargetHandling::kInitializedOldGen);
StackToStackTest(v8_isolate(), &tracer, Operation::kCopy,
TargetHandling::kNonInitialized);
StackToStackTest(v8_isolate(), &tracer, Operation::kCopy,
TargetHandling::kInitializedYoungGen);
StackToStackTest(v8_isolate(), &tracer, Operation::kCopy,
TargetHandling::kInitializedOldGen);
}
namespace {
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 TracedReferenceOnStackReferencesAreTemporaryTest(
v8::Isolate* v8_isolate, TestEmbedderHeapTracer* tracer) {
v8::Global<v8::Object> observer;
CreateTracedReferenceInDeepStack(v8_isolate, &observer);
EXPECT_FALSE(observer.IsEmpty());
{
// Conservative scanning may find stale pointers to on-stack handles.
// Disable scanning, assuming the slots are overwritten.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(
reinterpret_cast<Isolate*>(v8_isolate)->heap());
EmbedderStackStateScope scope =
EmbedderStackStateScope::ExplicitScopeForTesting(
reinterpret_cast<i::Isolate*>(v8_isolate)
->heap()
->local_embedder_heap_tracer(),
EmbedderHeapTracer::EmbedderStackState::kNoHeapPointers);
FullGC(v8_isolate);
}
EXPECT_TRUE(observer.IsEmpty());
}
} // namespace
TEST_F(EmbedderTracingTest, OnStackReferencesAreTemporary) {
ManualGCScope manual_gc(i_isolate());
TestEmbedderHeapTracer tracer;
heap::TemporaryEmbedderHeapTracerScope tracer_scope(v8_isolate(), &tracer);
tracer.SetStackStart(
static_cast<void*>(base::Stack::GetCurrentFrameAddress()));
TracedReferenceOnStackReferencesAreTemporaryTest(v8_isolate(), &tracer);
}
} // namespace heap
} // namespace internal
} // namespace v8

View File

@ -282,5 +282,16 @@ bool IsNewObjectInCorrectGeneration(HeapObject object) {
: i::Heap::InYoungGeneration(object);
}
void FinalizeGCIfRunning(Isolate* isolate) {
if (!isolate) {
return;
}
auto* heap = isolate->heap();
if (heap->incremental_marking()->IsMarking()) {
heap->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kTesting);
heap->CompleteSweepingFull();
}
}
} // namespace internal
} // namespace v8

View File

@ -162,6 +162,8 @@ bool IsNewObjectInCorrectGeneration(v8::Isolate* isolate,
return IsNewObjectInCorrectGeneration(*v8::Utils::OpenHandle(*tmp));
}
void FinalizeGCIfRunning(Isolate* isolate);
} // namespace internal
} // namespace v8

View File

@ -10,8 +10,10 @@
#include "src/base/platform/time.h"
#include "src/execution/isolate.h"
#include "src/flags/flags.h"
#include "src/heap/cppgc-js/cpp-heap.h"
#include "src/init/v8.h"
#include "src/objects/objects-inl.h"
#include "test/unittests/heap/heap-utils.h"
namespace v8 {
@ -90,12 +92,7 @@ ManualGCScope::ManualGCScope(i::Isolate* isolate) {
// Some tests run threaded (back-to-back) and thus the GC may already be
// running by the time a ManualGCScope is created. Finalizing existing marking
// prevents any undefined/unexpected behavior.
if (isolate && isolate->heap()->incremental_marking()->IsMarking()) {
isolate->heap()->CollectGarbage(OLD_SPACE,
GarbageCollectionReason::kTesting);
// Make sure there is no concurrent sweeping running in the background.
isolate->heap()->CompleteSweepingFull();
}
FinalizeGCIfRunning(isolate);
i::v8_flags.concurrent_marking = false;
i::v8_flags.concurrent_sweeping = false;
@ -105,6 +102,13 @@ ManualGCScope::ManualGCScope(i::Isolate* isolate) {
// Parallel marking has a dependency on concurrent marking.
i::v8_flags.parallel_marking = false;
i::v8_flags.detect_ineffective_gcs_near_heap_limit = false;
// CppHeap concurrent marking has a dependency on concurrent marking.
i::v8_flags.cppheap_concurrent_marking = false;
if (isolate && isolate->heap()->cpp_heap()) {
CppHeap::From(isolate->heap()->cpp_heap())
->ReduceGCCapabilitiesFromFlagsForTesting();
}
}
} // namespace internal