From d84c83d7da09f40028bd4ab776db1f5adefcfb12 Mon Sep 17 00:00:00 2001 From: Alexander Shabalin Date: Fri, 29 Jan 2021 10:51:13 +0300 Subject: [PATCH] Extract FreezeHooks and make them testable (#4654) --- .../kotlin/backend/konan/KonanFqNames.kt | 1 + .../backend/konan/llvm/RTTIGenerator.kt | 5 ++ .../kotlin/bitcode/CompileToBitcode.kt | 2 + .../kotlin/testing/native/NativeTest.kt | 2 +- .../runtime/src/legacymm/cpp/Memory.cpp | 10 +--- .../runtime/src/main/cpp/FreezeHooks.cpp | 42 ++++++++++++++++ .../runtime/src/main/cpp/FreezeHooks.hpp | 21 ++++++++ .../runtime/src/main/cpp/FreezeHooksTest.cpp | 48 +++++++++++++++++++ .../src/main/cpp/FreezeHooksTestSupport.cpp | 30 ++++++++++++ .../src/main/cpp/FreezeHooksTestSupport.hpp | 29 +++++++++++ kotlin-native/runtime/src/main/cpp/TypeInfo.h | 1 + .../native/concurrent/WorkerBoundReference.kt | 1 + .../kotlin/native/internal/Annotations.kt | 6 +++ 13 files changed, 189 insertions(+), 9 deletions(-) create mode 100644 kotlin-native/runtime/src/main/cpp/FreezeHooks.cpp create mode 100644 kotlin-native/runtime/src/main/cpp/FreezeHooks.hpp create mode 100644 kotlin-native/runtime/src/main/cpp/FreezeHooksTest.cpp create mode 100644 kotlin-native/runtime/src/main/cpp/FreezeHooksTestSupport.cpp create mode 100644 kotlin-native/runtime/src/main/cpp/FreezeHooksTestSupport.hpp diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanFqNames.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanFqNames.kt index 0ec960f9c12..b4e33339db6 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanFqNames.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanFqNames.kt @@ -31,4 +31,5 @@ object KonanFqNames { val typedIntrinsic = FqName("kotlin.native.internal.TypedIntrinsic") val objCMethod = FqName("kotlinx.cinterop.ObjCMethod") val hasFinalizer = FqName("kotlin.native.internal.HasFinalizer") + val hasFreezeHook = FqName("kotlin.native.internal.HasFreezeHook") } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt index 502f9974249..fc4b3cceefc 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt @@ -75,6 +75,10 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { result = result or TF_HAS_FINALIZER } + if (irClass.hasAnnotation(KonanFqNames.hasFreezeHook)) { + result = result or TF_HAS_FREEZE_HOOK + } + return result } @@ -638,4 +642,5 @@ private const val TF_OBJC_DYNAMIC = 8 private const val TF_LEAK_DETECTOR_CANDIDATE = 16 private const val TF_SUSPEND_FUNCTION = 32 private const val TF_HAS_FINALIZER = 64 +private const val TF_HAS_FREEZE_HOOK = 128 diff --git a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/bitcode/CompileToBitcode.kt b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/bitcode/CompileToBitcode.kt index 1ef5d444f5b..c992c6e0ae3 100644 --- a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/bitcode/CompileToBitcode.kt +++ b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/bitcode/CompileToBitcode.kt @@ -33,7 +33,9 @@ open class CompileToBitcode @Inject constructor( val linkerArgs = mutableListOf() var excludeFiles: List = listOf( "**/*Test.cpp", + "**/*TestSupport.cpp", "**/*Test.mm", + "**/*TestSupport.mm", ) var includeFiles: List = listOf( "**/*.cpp", diff --git a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/testing/native/NativeTest.kt b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/testing/native/NativeTest.kt index 3c0a4c1c376..07348aece33 100644 --- a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/testing/native/NativeTest.kt +++ b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/testing/native/NativeTest.kt @@ -206,7 +206,7 @@ private fun createTestTask( ).apply { this.sanitizer = sanitizer excludeFiles = emptyList() - includeFiles = listOf("**/*Test.cpp", "**/*Test.mm") + includeFiles = listOf("**/*Test.cpp", "**/*TestSupport.cpp", "**/*Test.mm", "**/*TestSupport.mm") dependsOn(it) dependsOn("downloadGoogleTest") compilerArgs.addAll(it.compilerArgs) diff --git a/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp b/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp index 1ccd1102d01..8d5b166f42f 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 "FreezeHooks.hpp" #include "KString.h" #include "Memory.h" #include "MemoryPrivate.hpp" @@ -2861,13 +2862,6 @@ void freezeCyclic(ObjHeader* root, } } -// These hooks are only allowed to modify `obj` subgraph. -void runFreezeHooks(ObjHeader* obj) { - if (obj->type_info() == theWorkerBoundReferenceTypeInfo) { - WorkerBoundReferenceFreezeHook(obj); - } -} - void runFreezeHooksRecursive(ObjHeader* root) { KStdUnorderedSet seen; KStdVector toVisit; @@ -2877,7 +2871,7 @@ void runFreezeHooksRecursive(ObjHeader* root) { KRef obj = toVisit.back(); toVisit.pop_back(); - runFreezeHooks(obj); + kotlin::RunFreezeHooks(obj); traverseReferredObjects(obj, [&seen, &toVisit](ObjHeader* field) { auto wasNotSeenYet = seen.insert(field).second; diff --git a/kotlin-native/runtime/src/main/cpp/FreezeHooks.cpp b/kotlin-native/runtime/src/main/cpp/FreezeHooks.cpp new file mode 100644 index 00000000000..04959ef470f --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/FreezeHooks.cpp @@ -0,0 +1,42 @@ +/* + * 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 "FreezeHooks.hpp" + +#include "Memory.h" +#include "Types.h" +#include "WorkerBoundReference.h" + +using namespace kotlin; + +namespace { + +void (*g_hookOverrideForTesting)(ObjHeader*) = nullptr; + +NO_INLINE void RunFreezeHooksImpl(ObjHeader* object, const TypeInfo* type) noexcept { + if (g_hookOverrideForTesting != nullptr) { + g_hookOverrideForTesting(object); + return; + } + // TODO: Consider some global registration. + if (type == theWorkerBoundReferenceTypeInfo) { + WorkerBoundReferenceFreezeHook(object); + } +} + +} // namespace + +void kotlin::RunFreezeHooks(ObjHeader* object) noexcept { + auto* type = object->type_info(); + if ((type->flags_ & TF_HAS_FREEZE_HOOK) == 0) { + return; + } + // This is a cold path. + RunFreezeHooksImpl(object, type); +} + +void kotlin::SetFreezeHookForTesting(void (*hook)(ObjHeader*)) noexcept { + g_hookOverrideForTesting = hook; +} diff --git a/kotlin-native/runtime/src/main/cpp/FreezeHooks.hpp b/kotlin-native/runtime/src/main/cpp/FreezeHooks.hpp new file mode 100644 index 00000000000..f4e418ba01e --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/FreezeHooks.hpp @@ -0,0 +1,21 @@ +/* + * 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_FREEZE_HOOKS_H +#define RUNTIME_MM_FREEZE_HOOKS_H + +struct ObjHeader; +struct TypeInfo; + +namespace kotlin { + +// These hooks are only allowed to modify `object` subgraph. +void RunFreezeHooks(ObjHeader* object) noexcept; + +void SetFreezeHookForTesting(void (*hook)(ObjHeader*)) noexcept; + +} // namespace kotlin + +#endif // RUNTIME_MM_FREEZE_HOOKS_H diff --git a/kotlin-native/runtime/src/main/cpp/FreezeHooksTest.cpp b/kotlin-native/runtime/src/main/cpp/FreezeHooksTest.cpp new file mode 100644 index 00000000000..521dfe64828 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/FreezeHooksTest.cpp @@ -0,0 +1,48 @@ +/* + * 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 "FreezeHooks.hpp" + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#include "FreezeHooksTestSupport.hpp" +#include "Memory.h" + +using namespace kotlin; + +using ::testing::_; + +namespace { + +class FreezeHooksTest : public testing::Test { +public: + testing::MockFunction& freezeHook() { return freezeHooks_.freezeHook(); } + +private: + FreezeHooksTestSupport freezeHooks_; +}; + +} // namespace + +TEST_F(FreezeHooksTest, TypeWithFreezeHook) { + TypeInfo type; + type.typeInfo_ = &type; + type.flags_ |= TF_HAS_FREEZE_HOOK; + ObjHeader obj = {&type}; + EXPECT_CALL(freezeHook(), Call(&obj)); + RunFreezeHooks(&obj); + testing::Mock::VerifyAndClearExpectations(&freezeHook()); +} + +TEST_F(FreezeHooksTest, TypeWithoutFreezeHook) { + TypeInfo type; + type.typeInfo_ = &type; + type.flags_ &= ~TF_HAS_FREEZE_HOOK; + ObjHeader obj = {&type}; + EXPECT_CALL(freezeHook(), Call(_)).Times(0); + RunFreezeHooks(&obj); + testing::Mock::VerifyAndClearExpectations(&freezeHook()); +} diff --git a/kotlin-native/runtime/src/main/cpp/FreezeHooksTestSupport.cpp b/kotlin-native/runtime/src/main/cpp/FreezeHooksTestSupport.cpp new file mode 100644 index 00000000000..aaeecc76d4a --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/FreezeHooksTestSupport.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 "FreezeHooksTestSupport.hpp" + +#include "FreezeHooks.hpp" + +using namespace kotlin; + +namespace { + +testing::MockFunction* g_freezeHook = nullptr; + +void freezeHook(ObjHeader* object) { + g_freezeHook->Call(object); +} + +} // namespace + +kotlin::FreezeHooksTestSupport::FreezeHooksTestSupport() { + g_freezeHook = &freezeHook_; + SetFreezeHookForTesting(&::freezeHook); +} + +kotlin::FreezeHooksTestSupport::~FreezeHooksTestSupport() { + SetFreezeHookForTesting(nullptr); + g_freezeHook = nullptr; +} diff --git a/kotlin-native/runtime/src/main/cpp/FreezeHooksTestSupport.hpp b/kotlin-native/runtime/src/main/cpp/FreezeHooksTestSupport.hpp new file mode 100644 index 00000000000..cf88e86fdfb --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/FreezeHooksTestSupport.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_FREEZE_HOOKS_TEST_SUPPORT_H +#define RUNTIME_MM_FREEZE_HOOKS_TEST_SUPPORT_H + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +struct ObjHeader; + +namespace kotlin { + +class FreezeHooksTestSupport { +public: + FreezeHooksTestSupport(); + ~FreezeHooksTestSupport(); + + testing::MockFunction& freezeHook() { return freezeHook_; } + +private: + testing::StrictMock> freezeHook_; +}; + +} // namespace kotlin + +#endif // RUNTIME_MM_FREEZE_HOOKS_TEST_SUPPORT_H diff --git a/kotlin-native/runtime/src/main/cpp/TypeInfo.h b/kotlin-native/runtime/src/main/cpp/TypeInfo.h index ef3c302f6fd..69200b72a79 100644 --- a/kotlin-native/runtime/src/main/cpp/TypeInfo.h +++ b/kotlin-native/runtime/src/main/cpp/TypeInfo.h @@ -63,6 +63,7 @@ enum Konan_TypeFlags { TF_LEAK_DETECTOR_CANDIDATE = 1 << 4, TF_SUSPEND_FUNCTION = 1 << 5, TF_HAS_FINALIZER = 1 << 6, + TF_HAS_FREEZE_HOOK = 1 << 7, }; // Flags per object instance. diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/WorkerBoundReference.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/WorkerBoundReference.kt index 7ac382a321f..c1d5ef010d6 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/WorkerBoundReference.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/WorkerBoundReference.kt @@ -29,6 +29,7 @@ external private fun describeWorkerBoundReference(ref: NativePtr): String @NoReorderFields @ExportTypeInfo("theWorkerBoundReferenceTypeInfo") @HasFinalizer +@HasFreezeHook public class WorkerBoundReference(value: T) { private var ptr = NativePtr.NULL diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt index 90e60161a18..bb18ed0132a 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt @@ -145,3 +145,9 @@ internal annotation class HasFinalizer @RequiresOptIn(level = RequiresOptIn.Level.ERROR) @Retention(value = AnnotationRetention.BINARY) internal annotation class InternalForKotlinNative + +/** + * Marks a class that has a freeze hook. + */ +@Target(AnnotationTarget.CLASS) +internal annotation class HasFreezeHook \ No newline at end of file