Commit 28bc2649 authored by Omer Katz's avatar Omer Katz Committed by Commit Bot

cppgc: Add support for prefinalizers

This CL adds:
- Declaring and invoking prefinalizers
- NoAllocationScope

Bug: chromium:1056170
Change-Id: Ib0f688fa4a8bb5fde44b36597ce2d6d2664fdff5
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2139588
Commit-Queue: Omer Katz <omerkatz@chromium.org>
Reviewed-by: 's avatarAnton Bikineev <bikineev@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Reviewed-by: 's avatarMichael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#67125}
parent efea7407
......@@ -3950,12 +3950,17 @@ v8_source_set("cppgc_base") {
"include/cppgc/heap.h",
"include/cppgc/internal/accessors.h",
"include/cppgc/internal/api-contants.h",
"include/cppgc/internal/compiler-specific.h",
"include/cppgc/internal/finalizer-traits.h",
"include/cppgc/internal/gc-info.h",
"include/cppgc/internal/pointer-policies.h",
"include/cppgc/internal/prefinalizer-handler.h",
"include/cppgc/liveness-broker.h",
"include/cppgc/liveness-broker.h",
"include/cppgc/macros.h",
"include/cppgc/member.h",
"include/cppgc/platform.h",
"include/cppgc/prefinalizer.h",
"include/cppgc/source-location.h",
"include/cppgc/trace-trait.h",
"include/cppgc/type-traits.h",
......@@ -3979,6 +3984,8 @@ v8_source_set("cppgc_base") {
"src/heap/cppgc/page-memory.h",
"src/heap/cppgc/platform.cc",
"src/heap/cppgc/pointer-policies.cc",
"src/heap/cppgc/prefinalizer-handler.cc",
"src/heap/cppgc/prefinalizer-handler.h",
"src/heap/cppgc/sanitizers.h",
"src/heap/cppgc/source-location.cc",
"src/heap/cppgc/stack.cc",
......
......@@ -8,6 +8,7 @@
#include <type_traits>
#include "include/cppgc/internal/api-constants.h"
#include "include/cppgc/macros.h"
#include "include/cppgc/platform.h"
#include "include/cppgc/trace-trait.h"
#include "include/cppgc/type-traits.h"
......@@ -121,12 +122,6 @@ class GarbageCollectedMixin : public internal::GarbageCollectedBase {
virtual void Trace(cppgc::Visitor*) {}
};
namespace internal {
class __thisIsHereToForceASemicolonAfterThisMacro {};
} // namespace internal
/**
* Macro defines all methods and markers needed for handling mixins. Must be
* used on the type that is inheriting from GarbageCollected *and*
......
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef INCLUDE_CPPGC_INTERNAL_COMPILER_SPECIFIC_H_
#define INCLUDE_CPPGC_INTERNAL_COMPILER_SPECIFIC_H_
namespace cppgc {
#if defined(__has_cpp_attribute)
#define CPPGC_HAS_CPP_ATTRIBUTE(FEATURE) __has_cpp_attribute(FEATURE)
#else
#define CPPGC_HAS_CPP_ATTRIBUTE(FEATURE) 0
#endif
// [[no_unique_address]] comes in C++20 but supported in clang with -std >=
// c++11.
#if CPPGC_HAS_CPP_ATTRIBUTE(no_unique_address) // NOLINTNEXTLINE
#define CPPGC_NO_UNIQUE_ADDRESS [[no_unique_address]]
#else
#define CPPGC_NO_UNIQUE_ADDRESS
#endif
} // namespace cppgc
#endif // INCLUDE_CPPGC_INTERNAL_COMPILER_SPECIFIC_H_
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef INCLUDE_CPPGC_INTERNAL_PREFINALIZER_HANDLER_H_
#define INCLUDE_CPPGC_INTERNAL_PREFINALIZER_HANDLER_H_
#include "include/cppgc/heap.h"
#include "include/cppgc/liveness-broker.h"
namespace cppgc {
namespace internal {
class V8_EXPORT PreFinalizerRegistrationDispatcher final {
public:
using PreFinalizerCallback = bool (*)(const LivenessBroker&, void*);
struct PreFinalizer {
void* object_;
PreFinalizerCallback callback_;
bool operator==(const PreFinalizer& other);
};
static void RegisterPrefinalizer(cppgc::Heap* heap,
PreFinalizer prefinalzier);
};
} // namespace internal
} // namespace cppgc
#endif // INCLUDE_CPPGC_INTERNAL_PREFINALIZER_HANDLER_H_
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef INCLUDE_CPPGC_MACROS_H_
#define INCLUDE_CPPGC_MACROS_H_
namespace cppgc {
namespace internal {
class __thisIsHereToForceASemicolonAfterThisMacro {};
} // namespace internal
// Use if the object is only stack allocated.
#define CPPGC_STACK_ALLOCATED() \
public: \
using IsStackAllocatedTypeMarker = int; \
\
private: \
void* operator new(size_t) = delete; \
void* operator new(size_t, void*) = delete; \
friend class internal::__thisIsHereToForceASemicolonAfterThisMacro
} // namespace cppgc
#endif // INCLUDE_CPPGC_MACROS_H_
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef INCLUDE_CPPGC_PREFINALIZER_H_
#define INCLUDE_CPPGC_PREFINALIZER_H_
#include "include/cppgc/internal/accessors.h"
#include "include/cppgc/internal/compiler-specific.h"
#include "include/cppgc/internal/prefinalizer-handler.h"
#include "include/cppgc/liveness-broker.h"
#include "include/cppgc/macros.h"
namespace cppgc {
namespace internal {
template <typename T>
class PrefinalizerRegistration final {
public:
explicit PrefinalizerRegistration(T* self) {
static_assert(sizeof(&T::InvokePreFinalizer) > 0,
"USING_PRE_FINALIZER(T) must be defined.");
cppgc::internal::PreFinalizerRegistrationDispatcher::RegisterPrefinalizer(
internal::GetHeapFromPayload(self), {self, T::InvokePreFinalizer});
}
void* operator new(size_t, void* location) = delete;
void* operator new(size_t) = delete;
};
} // namespace internal
#define CPPGC_USING_PRE_FINALIZER(Class, PreFinalizer) \
public: \
static bool InvokePreFinalizer(const LivenessBroker& liveness_broker, \
void* object) { \
static_assert(internal::IsGarbageCollectedTypeV<Class>, \
"Only garbage collected objects can have prefinalizers"); \
Class* self = static_cast<Class*>(object); \
if (liveness_broker.IsHeapObjectAlive(self)) return false; \
self->Class::PreFinalizer(); \
return true; \
} \
\
private: \
CPPGC_NO_UNIQUE_ADDRESS internal::PrefinalizerRegistration<Class> \
prefinalizer_dummy_{this}; \
friend class internal::__thisIsHereToForceASemicolonAfterThisMacro
} // namespace cppgc
#endif // INCLUDE_CPPGC_PREFINALIZER_H_
......@@ -14,6 +14,7 @@ namespace cppgc {
namespace internal {
void* Heap::Allocate(size_t size, GCInfoIndex index) {
DCHECK(is_allocation_allowed());
// TODO(chromium:1056170): This is merely a dummy implementation and will be
// replaced with proper allocation code throughout the migration.
size_t allocation_size = size + sizeof(HeapObjectHeader);
......
......@@ -54,7 +54,8 @@ class StackMarker final : public StackVisitor {
Heap::Heap()
: stack_(std::make_unique<Stack>(v8::base::Stack::GetStackStart())),
allocator_(std::make_unique<BasicAllocator>(this)) {}
allocator_(std::make_unique<BasicAllocator>(this)),
prefinalizer_handler_(std::make_unique<PreFinalizerHandler>()) {}
Heap::~Heap() {
for (HeapObjectHeader* header : objects_) {
......@@ -73,6 +74,11 @@ void Heap::CollectGarbage(GCConfig config) {
stack_->IteratePointers(&marker);
}
// "Sweeping and finalization".
{
// Pre finalizers are forbidden from allocating objects
NoAllocationScope no_allocation_scope_(this);
prefinalizer_handler_->InvokePreFinalizers();
}
for (auto it = objects_.begin(); it != objects_.end();) {
HeapObjectHeader* header = *it;
if (header->IsMarked()) {
......@@ -89,6 +95,11 @@ Heap::NoGCScope::NoGCScope(Heap* heap) : heap_(heap) { heap_->no_gc_scope_++; }
Heap::NoGCScope::~NoGCScope() { heap_->no_gc_scope_--; }
Heap::NoAllocationScope::NoAllocationScope(Heap* heap) : heap_(heap) {
heap_->no_allocation_scope_++;
}
Heap::NoAllocationScope::~NoAllocationScope() { heap_->no_allocation_scope_--; }
Heap::BasicAllocator::BasicAllocator(Heap* heap) : heap_(heap) {}
Heap::BasicAllocator::~BasicAllocator() {
......
......@@ -12,6 +12,7 @@
#include "include/cppgc/internal/gc-info.h"
#include "include/cppgc/liveness-broker.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "src/heap/cppgc/prefinalizer-handler.h"
namespace cppgc {
namespace internal {
......@@ -35,6 +36,21 @@ class V8_EXPORT_PRIVATE Heap final : public cppgc::Heap {
Heap* heap_;
};
// The NoAllocationScope class is used in debug mode to catch unwanted
// allocations. E.g. allocations during GC.
class V8_EXPORT_PRIVATE NoAllocationScope final {
CPPGC_STACK_ALLOCATED();
public:
explicit NoAllocationScope(Heap* heap);
~NoAllocationScope();
private:
Heap* const heap_;
DISALLOW_COPY_AND_ASSIGN(NoAllocationScope);
};
struct GCConfig {
enum class StackState : uint8_t {
kEmpty,
......@@ -55,6 +71,10 @@ class V8_EXPORT_PRIVATE Heap final : public cppgc::Heap {
void CollectGarbage(GCConfig config = GCConfig::Default());
PreFinalizerHandler* prefinalizer_handler() {
return prefinalizer_handler_.get();
}
private:
// TODO(chromium:1056170): Remove as soon as arenas are available for
// allocation.
......@@ -79,11 +99,15 @@ class V8_EXPORT_PRIVATE Heap final : public cppgc::Heap {
bool in_no_gc_scope() { return no_gc_scope_ > 0; }
bool is_allocation_allowed() { return no_allocation_scope_ == 0; }
std::unique_ptr<Stack> stack_;
std::unique_ptr<BasicAllocator> allocator_;
std::vector<HeapObjectHeader*> objects_;
std::unique_ptr<PreFinalizerHandler> prefinalizer_handler_;
size_t no_gc_scope_ = 0;
size_t no_allocation_scope_ = 0;
};
} // namespace internal
......
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/heap/cppgc/prefinalizer-handler.h"
#include <memory>
#include "src/base/platform/platform.h"
#include "src/heap/cppgc/heap.h"
namespace cppgc {
namespace internal {
// static
void PreFinalizerRegistrationDispatcher::RegisterPrefinalizer(
cppgc::Heap* heap, PreFinalizer prefinalzier) {
internal::Heap::From(heap)->prefinalizer_handler()->RegisterPrefinalizer(
prefinalzier);
}
bool PreFinalizerRegistrationDispatcher::PreFinalizer::operator==(
const PreFinalizer& other) {
return (object_ == other.object_) && (callback_ == other.callback_);
}
PreFinalizerHandler::PreFinalizerHandler()
#ifdef DEBUG
: creation_thread_id_(v8::base::OS::GetCurrentThreadId())
#endif
{
}
void PreFinalizerHandler::RegisterPrefinalizer(PreFinalizer prefinalizer) {
DCHECK(CurrentThreadIsCreationThread());
DCHECK_EQ(ordered_pre_finalizers_.end(),
std::find(ordered_pre_finalizers_.begin(),
ordered_pre_finalizers_.end(), prefinalizer));
ordered_pre_finalizers_.push_back(prefinalizer);
}
void PreFinalizerHandler::InvokePreFinalizers() {
DCHECK(CurrentThreadIsCreationThread());
LivenessBroker liveness_broker = LivenessBrokerFactory::Create();
ordered_pre_finalizers_.erase(
ordered_pre_finalizers_.begin(),
std::remove_if(ordered_pre_finalizers_.rbegin(),
ordered_pre_finalizers_.rend(),
[liveness_broker](const PreFinalizer& pf) {
return (pf.callback_)(liveness_broker, pf.object_);
})
.base());
ordered_pre_finalizers_.shrink_to_fit();
}
bool PreFinalizerHandler::CurrentThreadIsCreationThread() {
#ifdef DEBUG
return creation_thread_id_ == v8::base::OS::GetCurrentThreadId();
#else
return true;
#endif
}
} // namespace internal
} // namespace cppgc
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_HEAP_CPPGC_PREFINALIZER_HANDLER_H_
#define V8_HEAP_CPPGC_PREFINALIZER_HANDLER_H_
#include <vector>
#include "include/cppgc/prefinalizer.h"
namespace cppgc {
namespace internal {
class PreFinalizerHandler final {
public:
using PreFinalizer =
cppgc::internal::PreFinalizerRegistrationDispatcher::PreFinalizer;
PreFinalizerHandler();
void RegisterPrefinalizer(PreFinalizer prefinalzier);
void InvokePreFinalizers();
private:
// Checks that the current thread is the thread that created the heap.
bool CurrentThreadIsCreationThread();
// Pre-finalizers are called in the reverse order in which they are
// registered by the constructors (including constructors of Mixin
// objects) for an object, by processing the ordered_pre_finalizers_
// back-to-front.
std::vector<PreFinalizer> ordered_pre_finalizers_;
#ifdef DEBUG
int creation_thread_id_;
#endif
};
} // namespace internal
} // namespace cppgc
#endif // V8_HEAP_CPPGC_PREFINALIZER_HANDLER_H_
......@@ -52,6 +52,7 @@ v8_source_set("cppgc_unittests_sources") {
"heap/cppgc/heap_unittest.cc",
"heap/cppgc/member_unittests.cc",
"heap/cppgc/page-memory_unittest.cc",
"heap/cppgc/prefinalizer_unittests.cc",
"heap/cppgc/source-location_unittest.cc",
"heap/cppgc/stack_unittest.cc",
"heap/cppgc/tests.cc",
......
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "include/cppgc/prefinalizer.h"
#include "include/cppgc/allocation.h"
#include "include/cppgc/garbage-collected.h"
#include "src/heap/cppgc/heap-object-header-inl.h"
#include "src/heap/cppgc/heap.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
class PrefinalizerTest : public testing::TestWithHeap {};
class GCed : public GarbageCollected<GCed> {
CPPGC_USING_PRE_FINALIZER(GCed, PreFinalizer);
public:
void Trace(Visitor*) {}
void PreFinalizer() { ++prefinalizer_callcount; }
static size_t prefinalizer_callcount;
};
size_t GCed::prefinalizer_callcount = 0;
} // namespace
TEST_F(PrefinalizerTest, PrefinalizerCalledOnDeadObject) {
GCed::prefinalizer_callcount = 0;
auto* object = MakeGarbageCollected<GCed>(GetHeap());
USE(object);
EXPECT_EQ(0u, GCed::prefinalizer_callcount);
PreciseGC();
EXPECT_EQ(1u, GCed::prefinalizer_callcount);
PreciseGC();
EXPECT_EQ(1u, GCed::prefinalizer_callcount);
}
TEST_F(PrefinalizerTest, PrefinalizerNotCalledOnLiveObject) {
GCed::prefinalizer_callcount = 0;
auto* object = MakeGarbageCollected<GCed>(GetHeap());
HeapObjectHeader::FromPayload(object).TryMarkAtomic();
EXPECT_EQ(0u, GCed::prefinalizer_callcount);
PreciseGC();
EXPECT_EQ(0u, GCed::prefinalizer_callcount);
PreciseGC();
EXPECT_EQ(1u, GCed::prefinalizer_callcount);
}
namespace {
class Mixin : public GarbageCollectedMixin {
CPPGC_USING_PRE_FINALIZER(Mixin, PreFinalizer);
public:
void PreFinalizer() { ++prefinalizer_callcount; }
static size_t prefinalizer_callcount;
};
size_t Mixin::prefinalizer_callcount = 0;
class GCedWithMixin : public GarbageCollected<GCedWithMixin>, public Mixin {
USING_GARBAGE_COLLECTED_MIXIN();
};
} // namespace
TEST_F(PrefinalizerTest, PrefinalizerCalledOnDeadMixinObject) {
Mixin::prefinalizer_callcount = 0;
auto* object = MakeGarbageCollected<GCedWithMixin>(GetHeap());
USE(object);
EXPECT_EQ(0u, Mixin::prefinalizer_callcount);
PreciseGC();
EXPECT_EQ(1u, Mixin::prefinalizer_callcount);
PreciseGC();
EXPECT_EQ(1u, Mixin::prefinalizer_callcount);
}
TEST_F(PrefinalizerTest, PrefinalizerNotCalledOnLiveMixinObject) {
Mixin::prefinalizer_callcount = 0;
auto* object = MakeGarbageCollected<GCedWithMixin>(GetHeap());
HeapObjectHeader::FromPayload(object).TryMarkAtomic();
EXPECT_EQ(0u, Mixin::prefinalizer_callcount);
PreciseGC();
EXPECT_EQ(0u, Mixin::prefinalizer_callcount);
PreciseGC();
EXPECT_EQ(1u, Mixin::prefinalizer_callcount);
}
namespace {
class BaseMixin : public GarbageCollectedMixin {
CPPGC_USING_PRE_FINALIZER(BaseMixin, PreFinalizer);
public:
void PreFinalizer();
static size_t prefinalizer_callcount;
};
size_t BaseMixin::prefinalizer_callcount = 0;
class InheritingMixin : public BaseMixin {
CPPGC_USING_PRE_FINALIZER(InheritingMixin, PreFinalizer);
public:
void PreFinalizer();
static size_t prefinalizer_callcount;
};
size_t InheritingMixin::prefinalizer_callcount = 0;
class GCedWithMixins : public GarbageCollected<GCedWithMixins>,
public InheritingMixin {
USING_GARBAGE_COLLECTED_MIXIN();
CPPGC_USING_PRE_FINALIZER(GCedWithMixins, PreFinalizer);
public:
void PreFinalizer();
static size_t prefinalizer_callcount;
};
size_t GCedWithMixins::prefinalizer_callcount = 0;
void BaseMixin::PreFinalizer() {
EXPECT_EQ(1u, GCedWithMixins::prefinalizer_callcount);
EXPECT_EQ(1u, InheritingMixin::prefinalizer_callcount);
EXPECT_EQ(0u, BaseMixin::prefinalizer_callcount);
++BaseMixin::prefinalizer_callcount;
}
void InheritingMixin::PreFinalizer() {
EXPECT_EQ(1u, GCedWithMixins::prefinalizer_callcount);
EXPECT_EQ(0u, InheritingMixin::prefinalizer_callcount);
EXPECT_EQ(0u, BaseMixin::prefinalizer_callcount);
InheritingMixin::prefinalizer_callcount = true;
}
void GCedWithMixins::PreFinalizer() {
EXPECT_EQ(0u, GCedWithMixins::prefinalizer_callcount);
EXPECT_EQ(0u, InheritingMixin::prefinalizer_callcount);
EXPECT_EQ(0u, BaseMixin::prefinalizer_callcount);
GCedWithMixins::prefinalizer_callcount = true;
}
} // namespace
TEST_F(PrefinalizerTest, PrefinalizerInvocationPreservesOrder) {
BaseMixin::prefinalizer_callcount = 0;
InheritingMixin::prefinalizer_callcount = 0;
GCedWithMixins::prefinalizer_callcount = 0;
auto* object = MakeGarbageCollected<GCedWithMixins>(GetHeap());
USE(object);
EXPECT_EQ(0u, GCedWithMixins::prefinalizer_callcount);
EXPECT_EQ(0u, InheritingMixin::prefinalizer_callcount);
EXPECT_EQ(0u, BaseMixin::prefinalizer_callcount);
PreciseGC();
EXPECT_EQ(1u, GCedWithMixins::prefinalizer_callcount);
EXPECT_EQ(1u, InheritingMixin::prefinalizer_callcount);
EXPECT_EQ(1u, BaseMixin::prefinalizer_callcount);
PreciseGC();
EXPECT_EQ(1u, GCedWithMixins::prefinalizer_callcount);
EXPECT_EQ(1u, InheritingMixin::prefinalizer_callcount);
EXPECT_EQ(1u, BaseMixin::prefinalizer_callcount);
}
namespace {
class AllocatingPrefinalizer : public GarbageCollected<AllocatingPrefinalizer> {
CPPGC_USING_PRE_FINALIZER(AllocatingPrefinalizer, PreFinalizer);
public:
explicit AllocatingPrefinalizer(cppgc::Heap* heap) : heap_(heap) {}
void Trace(Visitor*) {}
void PreFinalizer() { MakeGarbageCollected<GCed>(heap_); }
private:
cppgc::Heap* heap_;
};
} // namespace
#ifdef DEBUG
TEST_F(PrefinalizerTest, PrefinalizerFailsOnAllcoation) {
auto* object =
MakeGarbageCollected<AllocatingPrefinalizer>(GetHeap(), GetHeap());
USE(object);
EXPECT_DEATH_IF_SUPPORTED(PreciseGC(), "");
}
#endif // DEBUG
} // namespace internal
} // namespace cppgc
......@@ -27,6 +27,11 @@ class TestWithHeap : public TestWithPlatform {
protected:
TestWithHeap();
void PreciseGC() {
internal::Heap::From(GetHeap())->CollectGarbage(
{internal::Heap::GCConfig::StackState::kEmpty});
}
Heap* GetHeap() const { return heap_.get(); }
private:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment