Extract finalizer hooks (#4723)

(cherry picked from commit 31aa3521925a22f077acb723315aabb0a7274121)
This commit is contained in:
Alexander Shabalin
2021-03-02 11:15:03 +03:00
committed by Space
parent 7bfcf815ba
commit 248e340cd9
10 changed files with 216 additions and 25 deletions
@@ -39,6 +39,7 @@
#include "CyclicCollector.h"
#endif // USE_CYCLIC_GC
#include "Exceptions.h"
#include "FinalizerHooks.hpp"
#include "FreezeHooks.hpp"
#include "KString.h"
#include "Memory.h"
@@ -1198,27 +1199,11 @@ void freeAggregatingFrozenContainer(ContainerHeader* container) {
MEMORY_LOG("Freeing subcontainers done\n");
}
// Not inlining this call as it affects deallocation performance for
// all types.
NO_INLINE RUNTIME_NOTHROW void runFinalizers(ObjHeader* obj) {
auto* type_info = obj->type_info();
if (type_info == theCleanerImplTypeInfo) {
DisposeCleaner(obj);
}
if (type_info == theWorkerBoundReferenceTypeInfo) {
DisposeWorkerBoundReference(obj);
}
}
// This is called from 2 places where it's unconditionally called,
// so better be inlined.
ALWAYS_INLINE void runDeallocationHooks(ContainerHeader* container) {
ObjHeader* obj = reinterpret_cast<ObjHeader*>(container + 1);
for (uint32_t index = 0; index < container->objectCount(); index++) {
auto* type_info = obj->type_info();
if ((type_info->flags_ & TF_HAS_FINALIZER) != 0) {
runFinalizers(obj);
}
#if USE_CYCLIC_GC
if ((type_info->flags_ & TF_LEAK_DETECTOR_CANDIDATE) != 0) {
cyclicRemoveAtomicRoot(obj);
@@ -1227,9 +1212,7 @@ ALWAYS_INLINE void runDeallocationHooks(ContainerHeader* container) {
#if USE_CYCLE_DETECTOR
CycleDetector::removeCandidateIfNeeded(obj);
#endif // USE_CYCLE_DETECTOR
if (obj->has_meta_object()) {
ObjHeader::destroyMetaObject(obj);
}
kotlin::RunFinalizers(obj);
obj = reinterpret_cast<ObjHeader*>(reinterpret_cast<uintptr_t>(obj) + objectSize(obj));
}
}
@@ -107,7 +107,6 @@ TEST_F(KonanAllocatorAwareTest, PlacementAllocated) {
EXPECT_THAT(a->value(), 42);
EXPECT_CALL(*destructorHook, Call(42));
a->~A();
testing::Mock::VerifyAndClearExpectations(destructorHook.get());
}
TEST_F(KonanAllocatorAwareTest, PlacementConstructedArray) {
@@ -131,5 +130,4 @@ TEST_F(KonanAllocatorAwareTest, PlacementConstructedArray) {
for (A* a = as; a != as + kCount; ++a) {
a->~A();
}
testing::Mock::VerifyAndClearExpectations(destructorHook.get());
}
@@ -0,0 +1,53 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#include "FinalizerHooks.hpp"
#include "Cleaner.h"
#include "Memory.h"
#include "Types.h"
#include "WorkerBoundReference.h"
using namespace kotlin;
namespace {
void (*g_hookOverrideForTesting)(ObjHeader*) = nullptr;
// Not inlining this call as it affects deallocation performance for
// all types.
NO_INLINE void RunFinalizerHooksImpl(ObjHeader* object, const TypeInfo* type) noexcept {
if (g_hookOverrideForTesting != nullptr) {
g_hookOverrideForTesting(object);
return;
}
// TODO: Consider some global registration.
if (type == theCleanerImplTypeInfo) {
DisposeCleaner(object);
} else if (type == theWorkerBoundReferenceTypeInfo) {
DisposeWorkerBoundReference(object);
}
}
} // namespace
ALWAYS_INLINE bool kotlin::HasFinalizers(ObjHeader* object) noexcept {
return object->has_meta_object() || (object->type_info()->flags_ & TF_HAS_FINALIZER) != 0;
}
ALWAYS_INLINE void kotlin::RunFinalizers(ObjHeader* object) noexcept {
auto* type = object->type_info();
if ((type->flags_ & TF_HAS_FINALIZER) != 0) {
// This is a cold path.
RunFinalizerHooksImpl(object, type);
}
if (object->has_meta_object()) {
ObjHeader::destroyMetaObject(object);
}
}
void kotlin::SetFinalizerHookForTesting(void (*hook)(ObjHeader*)) noexcept {
g_hookOverrideForTesting = hook;
}
@@ -0,0 +1,20 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#ifndef RUNTIME_MM_FINALIZER_HOOKS_H
#define RUNTIME_MM_FINALIZER_HOOKS_H
struct ObjHeader;
namespace kotlin {
bool HasFinalizers(ObjHeader* object) noexcept;
void RunFinalizers(ObjHeader* object) noexcept;
void SetFinalizerHookForTesting(void (*hook)(ObjHeader*)) noexcept;
} // namespace kotlin
#endif // RUNTIME_MM_FINALIZER_HOOKS_H
@@ -0,0 +1,82 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#include "FinalizerHooks.hpp"
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include "FinalizerHooksTestSupport.hpp"
#include "Memory.h"
using namespace kotlin;
using ::testing::_;
namespace {
class FinalizerHooksTest : public testing::Test {
public:
testing::MockFunction<void(ObjHeader*)>& finalizerHook() { return finalizerHooks_.finalizerHook(); }
private:
FinalizerHooksTestSupport finalizerHooks_;
};
} // namespace
TEST_F(FinalizerHooksTest, TypeWithFinalizerHookWithoutExtra) {
TypeInfo type;
type.typeInfo_ = &type;
type.flags_ |= TF_HAS_FINALIZER;
ObjHeader obj = {&type};
ASSERT_FALSE(obj.has_meta_object());
EXPECT_TRUE(HasFinalizers(&obj));
EXPECT_CALL(finalizerHook(), Call(&obj));
RunFinalizers(&obj);
EXPECT_FALSE(obj.has_meta_object());
}
TEST_F(FinalizerHooksTest, TypeWithFinalizerHookWithExtra) {
TypeInfo type;
type.typeInfo_ = &type;
type.flags_ |= TF_HAS_FINALIZER;
ObjHeader obj = {&type};
ObjHeader::createMetaObject(&obj);
ASSERT_TRUE(obj.has_meta_object());
EXPECT_TRUE(HasFinalizers(&obj));
EXPECT_CALL(finalizerHook(), Call(&obj));
RunFinalizers(&obj);
EXPECT_FALSE(obj.has_meta_object());
}
TEST_F(FinalizerHooksTest, TypeWithoutFinalizerHookWithoutExtra) {
TypeInfo type;
type.typeInfo_ = &type;
type.flags_ &= ~TF_HAS_FINALIZER;
ObjHeader obj = {&type};
ASSERT_FALSE(obj.has_meta_object());
EXPECT_FALSE(HasFinalizers(&obj));
EXPECT_CALL(finalizerHook(), Call(_)).Times(0);
RunFinalizers(&obj);
EXPECT_FALSE(obj.has_meta_object());
}
TEST_F(FinalizerHooksTest, TypeWithoutFinalizerHookWithExtra) {
TypeInfo type;
type.typeInfo_ = &type;
type.flags_ &= ~TF_HAS_FINALIZER;
ObjHeader obj = {&type};
ObjHeader::createMetaObject(&obj);
ASSERT_TRUE(obj.has_meta_object());
EXPECT_TRUE(HasFinalizers(&obj));
EXPECT_CALL(finalizerHook(), Call(_)).Times(0);
RunFinalizers(&obj);
EXPECT_FALSE(obj.has_meta_object());
}
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#include "FinalizerHooksTestSupport.hpp"
#include "FinalizerHooks.hpp"
using namespace kotlin;
namespace {
testing::MockFunction<void(ObjHeader*)>* g_finalizerHook = nullptr;
void finalizerHook(ObjHeader* object) {
g_finalizerHook->Call(object);
}
} // namespace
kotlin::FinalizerHooksTestSupport::FinalizerHooksTestSupport() {
g_finalizerHook = &finalizerHook_;
SetFinalizerHookForTesting(&::finalizerHook);
}
kotlin::FinalizerHooksTestSupport::~FinalizerHooksTestSupport() {
SetFinalizerHookForTesting(nullptr);
g_finalizerHook = nullptr;
}
@@ -0,0 +1,29 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#ifndef RUNTIME_MM_FINALIZER_HOOKS_TEST_SUPPORT_H
#define RUNTIME_MM_FINALIZER_HOOKS_TEST_SUPPORT_H
#include "gtest/gtest.h"
#include "gmock/gmock.h"
struct ObjHeader;
namespace kotlin {
class FinalizerHooksTestSupport {
public:
FinalizerHooksTestSupport();
~FinalizerHooksTestSupport();
testing::MockFunction<void(ObjHeader*)>& finalizerHook() { return finalizerHook_; }
private:
testing::StrictMock<testing::MockFunction<void(ObjHeader*)>> finalizerHook_;
};
} // namespace kotlin
#endif // RUNTIME_MM_FINALIZER_HOOKS_TEST_SUPPORT_H
@@ -34,7 +34,6 @@ TEST_F(FreezeHooksTest, TypeWithFreezeHook) {
ObjHeader obj = {&type};
EXPECT_CALL(freezeHook(), Call(&obj));
RunFreezeHooks(&obj);
testing::Mock::VerifyAndClearExpectations(&freezeHook());
}
TEST_F(FreezeHooksTest, TypeWithoutFreezeHook) {
@@ -44,5 +43,4 @@ TEST_F(FreezeHooksTest, TypeWithoutFreezeHook) {
ObjHeader obj = {&type};
EXPECT_CALL(freezeHook(), Call(_)).Times(0);
RunFreezeHooks(&obj);
testing::Mock::VerifyAndClearExpectations(&freezeHook());
}
@@ -342,5 +342,4 @@ TEST(SingleLockListTest, Destructor) {
EXPECT_CALL(hook, Call(first));
}
}
testing::Mock::VerifyAndClear(&hook);
}
@@ -39,7 +39,6 @@ public:
RuntimeCheck(*globalMockLocation_ == mock_.get(), "unexpected global mock location");
testing::Mock::VerifyAndClear(mock_.get());
mock_.reset();
*globalMockLocation_ = nullptr;