Extract FreezeHooks and make them testable (#4654)
This commit is contained in:
committed by
Vasily Levchenko
parent
e2280ce855
commit
d84c83d7da
+1
@@ -31,4 +31,5 @@ object KonanFqNames {
|
|||||||
val typedIntrinsic = FqName("kotlin.native.internal.TypedIntrinsic")
|
val typedIntrinsic = FqName("kotlin.native.internal.TypedIntrinsic")
|
||||||
val objCMethod = FqName("kotlinx.cinterop.ObjCMethod")
|
val objCMethod = FqName("kotlinx.cinterop.ObjCMethod")
|
||||||
val hasFinalizer = FqName("kotlin.native.internal.HasFinalizer")
|
val hasFinalizer = FqName("kotlin.native.internal.HasFinalizer")
|
||||||
|
val hasFreezeHook = FqName("kotlin.native.internal.HasFreezeHook")
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -75,6 +75,10 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
|||||||
result = result or TF_HAS_FINALIZER
|
result = result or TF_HAS_FINALIZER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (irClass.hasAnnotation(KonanFqNames.hasFreezeHook)) {
|
||||||
|
result = result or TF_HAS_FREEZE_HOOK
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
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_LEAK_DETECTOR_CANDIDATE = 16
|
||||||
private const val TF_SUSPEND_FUNCTION = 32
|
private const val TF_SUSPEND_FUNCTION = 32
|
||||||
private const val TF_HAS_FINALIZER = 64
|
private const val TF_HAS_FINALIZER = 64
|
||||||
|
private const val TF_HAS_FREEZE_HOOK = 128
|
||||||
|
|
||||||
|
|||||||
+2
@@ -33,7 +33,9 @@ open class CompileToBitcode @Inject constructor(
|
|||||||
val linkerArgs = mutableListOf<String>()
|
val linkerArgs = mutableListOf<String>()
|
||||||
var excludeFiles: List<String> = listOf(
|
var excludeFiles: List<String> = listOf(
|
||||||
"**/*Test.cpp",
|
"**/*Test.cpp",
|
||||||
|
"**/*TestSupport.cpp",
|
||||||
"**/*Test.mm",
|
"**/*Test.mm",
|
||||||
|
"**/*TestSupport.mm",
|
||||||
)
|
)
|
||||||
var includeFiles: List<String> = listOf(
|
var includeFiles: List<String> = listOf(
|
||||||
"**/*.cpp",
|
"**/*.cpp",
|
||||||
|
|||||||
+1
-1
@@ -206,7 +206,7 @@ private fun createTestTask(
|
|||||||
).apply {
|
).apply {
|
||||||
this.sanitizer = sanitizer
|
this.sanitizer = sanitizer
|
||||||
excludeFiles = emptyList()
|
excludeFiles = emptyList()
|
||||||
includeFiles = listOf("**/*Test.cpp", "**/*Test.mm")
|
includeFiles = listOf("**/*Test.cpp", "**/*TestSupport.cpp", "**/*Test.mm", "**/*TestSupport.mm")
|
||||||
dependsOn(it)
|
dependsOn(it)
|
||||||
dependsOn("downloadGoogleTest")
|
dependsOn("downloadGoogleTest")
|
||||||
compilerArgs.addAll(it.compilerArgs)
|
compilerArgs.addAll(it.compilerArgs)
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
#include "CyclicCollector.h"
|
#include "CyclicCollector.h"
|
||||||
#endif // USE_CYCLIC_GC
|
#endif // USE_CYCLIC_GC
|
||||||
#include "Exceptions.h"
|
#include "Exceptions.h"
|
||||||
|
#include "FreezeHooks.hpp"
|
||||||
#include "KString.h"
|
#include "KString.h"
|
||||||
#include "Memory.h"
|
#include "Memory.h"
|
||||||
#include "MemoryPrivate.hpp"
|
#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) {
|
void runFreezeHooksRecursive(ObjHeader* root) {
|
||||||
KStdUnorderedSet<KRef> seen;
|
KStdUnorderedSet<KRef> seen;
|
||||||
KStdVector<KRef> toVisit;
|
KStdVector<KRef> toVisit;
|
||||||
@@ -2877,7 +2871,7 @@ void runFreezeHooksRecursive(ObjHeader* root) {
|
|||||||
KRef obj = toVisit.back();
|
KRef obj = toVisit.back();
|
||||||
toVisit.pop_back();
|
toVisit.pop_back();
|
||||||
|
|
||||||
runFreezeHooks(obj);
|
kotlin::RunFreezeHooks(obj);
|
||||||
|
|
||||||
traverseReferredObjects(obj, [&seen, &toVisit](ObjHeader* field) {
|
traverseReferredObjects(obj, [&seen, &toVisit](ObjHeader* field) {
|
||||||
auto wasNotSeenYet = seen.insert(field).second;
|
auto wasNotSeenYet = seen.insert(field).second;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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<void(ObjHeader*)>& 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());
|
||||||
|
}
|
||||||
@@ -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<void(ObjHeader*)>* 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;
|
||||||
|
}
|
||||||
@@ -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<void(ObjHeader*)>& freezeHook() { return freezeHook_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
testing::StrictMock<testing::MockFunction<void(ObjHeader*)>> freezeHook_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace kotlin
|
||||||
|
|
||||||
|
#endif // RUNTIME_MM_FREEZE_HOOKS_TEST_SUPPORT_H
|
||||||
@@ -63,6 +63,7 @@ enum Konan_TypeFlags {
|
|||||||
TF_LEAK_DETECTOR_CANDIDATE = 1 << 4,
|
TF_LEAK_DETECTOR_CANDIDATE = 1 << 4,
|
||||||
TF_SUSPEND_FUNCTION = 1 << 5,
|
TF_SUSPEND_FUNCTION = 1 << 5,
|
||||||
TF_HAS_FINALIZER = 1 << 6,
|
TF_HAS_FINALIZER = 1 << 6,
|
||||||
|
TF_HAS_FREEZE_HOOK = 1 << 7,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Flags per object instance.
|
// Flags per object instance.
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ external private fun describeWorkerBoundReference(ref: NativePtr): String
|
|||||||
@NoReorderFields
|
@NoReorderFields
|
||||||
@ExportTypeInfo("theWorkerBoundReferenceTypeInfo")
|
@ExportTypeInfo("theWorkerBoundReferenceTypeInfo")
|
||||||
@HasFinalizer
|
@HasFinalizer
|
||||||
|
@HasFreezeHook
|
||||||
public class WorkerBoundReference<out T : Any>(value: T) {
|
public class WorkerBoundReference<out T : Any>(value: T) {
|
||||||
|
|
||||||
private var ptr = NativePtr.NULL
|
private var ptr = NativePtr.NULL
|
||||||
|
|||||||
@@ -145,3 +145,9 @@ internal annotation class HasFinalizer
|
|||||||
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
|
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
|
||||||
@Retention(value = AnnotationRetention.BINARY)
|
@Retention(value = AnnotationRetention.BINARY)
|
||||||
internal annotation class InternalForKotlinNative
|
internal annotation class InternalForKotlinNative
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks a class that has a freeze hook.
|
||||||
|
*/
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
internal annotation class HasFreezeHook
|
||||||
Reference in New Issue
Block a user