[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:
parent
5373b52f01
commit
8d4af46404
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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));
|
||||
|
@ -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",
|
||||
|
283
test/unittests/heap/cppgc-js/embedder-roots-handler-unittest.cc
Normal file
283
test/unittests/heap/cppgc-js/embedder-roots-handler-unittest.cc
Normal 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
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ namespace internal {
|
||||
|
||||
class CppHeap;
|
||||
|
||||
class UnifiedHeapTest : public TestWithHeapInternals {
|
||||
class UnifiedHeapTest : public TestWithHeapInternalsAndContext {
|
||||
public:
|
||||
UnifiedHeapTest();
|
||||
explicit UnifiedHeapTest(
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -162,6 +162,8 @@ bool IsNewObjectInCorrectGeneration(v8::Isolate* isolate,
|
||||
return IsNewObjectInCorrectGeneration(*v8::Utils::OpenHandle(*tmp));
|
||||
}
|
||||
|
||||
void FinalizeGCIfRunning(Isolate* isolate);
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user