diff --git a/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp b/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp index bc5e6ceebd6..cf211a89eb0 100644 --- a/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp +++ b/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp @@ -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(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(reinterpret_cast(obj) + objectSize(obj)); } } diff --git a/kotlin-native/runtime/src/main/cpp/AllocTest.cpp b/kotlin-native/runtime/src/main/cpp/AllocTest.cpp index e9b58dc6c59..94ea54ec668 100644 --- a/kotlin-native/runtime/src/main/cpp/AllocTest.cpp +++ b/kotlin-native/runtime/src/main/cpp/AllocTest.cpp @@ -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()); } diff --git a/kotlin-native/runtime/src/main/cpp/FinalizerHooks.cpp b/kotlin-native/runtime/src/main/cpp/FinalizerHooks.cpp new file mode 100644 index 00000000000..66ae2a479e9 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/FinalizerHooks.cpp @@ -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; +} diff --git a/kotlin-native/runtime/src/main/cpp/FinalizerHooks.hpp b/kotlin-native/runtime/src/main/cpp/FinalizerHooks.hpp new file mode 100644 index 00000000000..135dfa9b621 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/FinalizerHooks.hpp @@ -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 diff --git a/kotlin-native/runtime/src/main/cpp/FinalizerHooksTest.cpp b/kotlin-native/runtime/src/main/cpp/FinalizerHooksTest.cpp new file mode 100644 index 00000000000..274b4b37b17 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/FinalizerHooksTest.cpp @@ -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& 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()); +} diff --git a/kotlin-native/runtime/src/main/cpp/FinalizerHooksTestSupport.cpp b/kotlin-native/runtime/src/main/cpp/FinalizerHooksTestSupport.cpp new file mode 100644 index 00000000000..4c402e61aa4 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/FinalizerHooksTestSupport.cpp @@ -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* 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; +} diff --git a/kotlin-native/runtime/src/main/cpp/FinalizerHooksTestSupport.hpp b/kotlin-native/runtime/src/main/cpp/FinalizerHooksTestSupport.hpp new file mode 100644 index 00000000000..1a653f6c73b --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/FinalizerHooksTestSupport.hpp @@ -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& finalizerHook() { return finalizerHook_; } + +private: + testing::StrictMock> finalizerHook_; +}; + +} // namespace kotlin + +#endif // RUNTIME_MM_FINALIZER_HOOKS_TEST_SUPPORT_H diff --git a/kotlin-native/runtime/src/main/cpp/FreezeHooksTest.cpp b/kotlin-native/runtime/src/main/cpp/FreezeHooksTest.cpp index 521dfe64828..36042aa7bb4 100644 --- a/kotlin-native/runtime/src/main/cpp/FreezeHooksTest.cpp +++ b/kotlin-native/runtime/src/main/cpp/FreezeHooksTest.cpp @@ -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()); } diff --git a/kotlin-native/runtime/src/main/cpp/SingleLockListTest.cpp b/kotlin-native/runtime/src/main/cpp/SingleLockListTest.cpp index 0b74e32ac90..91ac6e399e7 100644 --- a/kotlin-native/runtime/src/main/cpp/SingleLockListTest.cpp +++ b/kotlin-native/runtime/src/main/cpp/SingleLockListTest.cpp @@ -342,5 +342,4 @@ TEST(SingleLockListTest, Destructor) { EXPECT_CALL(hook, Call(first)); } } - testing::Mock::VerifyAndClear(&hook); } diff --git a/kotlin-native/runtime/src/main/cpp/TestSupportCompilerGenerated.hpp b/kotlin-native/runtime/src/main/cpp/TestSupportCompilerGenerated.hpp index 17c295154c6..ef1e53df7a3 100644 --- a/kotlin-native/runtime/src/main/cpp/TestSupportCompilerGenerated.hpp +++ b/kotlin-native/runtime/src/main/cpp/TestSupportCompilerGenerated.hpp @@ -39,7 +39,6 @@ public: RuntimeCheck(*globalMockLocation_ == mock_.get(), "unexpected global mock location"); - testing::Mock::VerifyAndClear(mock_.get()); mock_.reset(); *globalMockLocation_ = nullptr;