diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index 4ad7b01c63c..dc91b25dd13 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -1092,8 +1092,11 @@ standaloneTest("leakMemoryWithWorkerTermination") { disabled = (project.testTarget == 'wasm32') || // Needs pthreads. isExperimentalMM // Experimental MM doesn't support multiple mutators yet. source = "runtime/workers/leak_memory_with_worker_termination.kt" - expectedExitStatusChecker = { it != 0 } - outputChecker = { s -> s.contains("Memory leaks detected, 1 objects leaked!") } + + if (!isExperimentalMM) { // Experimental MM will not report memory leaks. + expectedExitStatusChecker = { it != 0 } + outputChecker = { s -> s.contains("Memory leaks detected, 1 objects leaked!") } + } } task superFunCall(type: KonanLocalTest) { @@ -4494,9 +4497,15 @@ standaloneTest("interop_objc_illegal_sharing") { isExperimentalMM // Experimental MM doesn't support multiple mutators and thread state switching for ObjC interop yet. source = "interop/objc/illegal_sharing.kt" UtilsKt.dependsOnPlatformLibs(it) - expectedExitStatusChecker = { it != 0 } - outputChecker = { - it.startsWith("Before") && !it.contains("After") + if (isExperimentalMM) { + outputChecker = { + it.startsWith("Before") && it.contains("After") + } + } else { + expectedExitStatusChecker = { it != 0 } + outputChecker = { + it.startsWith("Before") && !it.contains("After") + } } } @@ -4614,6 +4623,9 @@ dynamicTest("interop_migrating_main_thread") { isExperimentalMM // Experimental MM doesn't support multiple mutators yet. source = "interop/migrating_main_thread/lib.kt" flags = ['-Xdestroy-runtime-mode=on-shutdown'] + if (isExperimentalMM) { + clangFlags = ['-DEXPERIMENTAL_MM'] + } cSource = "$projectDir/interop/migrating_main_thread/main.cpp" clangTool = "clang++" } diff --git a/kotlin-native/backend.native/tests/interop/migrating_main_thread/main.cpp b/kotlin-native/backend.native/tests/interop/migrating_main_thread/main.cpp index 35664ea9580..5d80c64847a 100644 --- a/kotlin-native/backend.native/tests/interop/migrating_main_thread/main.cpp +++ b/kotlin-native/backend.native/tests/interop/migrating_main_thread/main.cpp @@ -24,6 +24,9 @@ int main() { #if defined(IS_LEGACY) // Globals were reinitialized. assert(testlib_symbols()->kotlin.root.tryReadFromA(kErrorValue) == kInitialValue); +#elif defined(EXPERIMENTAL_MM) + // Globals are preserved. + assert(testlib_symbols()->kotlin.root.tryReadFromA(kErrorValue) == kNewValue); #else // Globals are not accessible. assert(testlib_symbols()->kotlin.root.tryReadFromA(kErrorValue) == kErrorValue); diff --git a/kotlin-native/backend.native/tests/runtime/memory/stable_ref_cross_thread_check.kt b/kotlin-native/backend.native/tests/runtime/memory/stable_ref_cross_thread_check.kt index 7634a3c6a8d..062ccbc2c74 100644 --- a/kotlin-native/backend.native/tests/runtime/memory/stable_ref_cross_thread_check.kt +++ b/kotlin-native/backend.native/tests/runtime/memory/stable_ref_cross_thread_check.kt @@ -10,17 +10,24 @@ import kotlin.test.* import kotlin.native.concurrent.* import kotlinx.cinterop.* +class Holder(val value: Int) + @Test fun runTest1() { val worker = Worker.start() val future = worker.execute(TransferMode.SAFE, { }) { - StableRef.create(Any()) + StableRef.create(Holder(42)) } val ref = future.result - assertFailsWith { + if (kotlin.native.Platform.memoryModel == kotlin.native.MemoryModel.EXPERIMENTAL) { val value = ref.get() - println(value.toString()) + assertEquals(value.value, 42) + } else { + assertFailsWith { + val value = ref.get() + println(value.value) + } } worker.requestTermination().result @@ -30,15 +37,20 @@ fun runTest1() { fun runTest2() { val worker = Worker.start() - val mainThreadRef = StableRef.create(Any()) + val mainThreadRef = StableRef.create(Holder(42)) // Simulate this going through interop as raw C pointer. val pointerValue: Long = mainThreadRef.asCPointer().toLong() val future = worker.execute(TransferMode.SAFE, { pointerValue }) { val pointer: COpaquePointer = it.toCPointer()!! - assertFailsWith { - // Even attempting to convert a pointer to StableRef should fail. - val otherThreadRef: StableRef = pointer.asStableRef() - println(otherThreadRef.toString()) + if (kotlin.native.Platform.memoryModel == kotlin.native.MemoryModel.EXPERIMENTAL) { + val otherThreadRef: StableRef = pointer.asStableRef() + assertEquals(otherThreadRef.get().value, 42) + } else { + assertFailsWith { + // Even attempting to convert a pointer to StableRef should fail. + val otherThreadRef: StableRef = pointer.asStableRef() + println(otherThreadRef.get().value) + } } Unit } diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker6.kt b/kotlin-native/backend.native/tests/runtime/workers/worker6.kt index 0bd0a4df46a..ee2d8aaca1e 100644 --- a/kotlin-native/backend.native/tests/runtime/workers/worker6.kt +++ b/kotlin-native/backend.native/tests/runtime/workers/worker6.kt @@ -28,11 +28,16 @@ val int2 = 77 int1++ withWorker { executeAfter(0, { - assertFailsWith { + if (kotlin.native.Platform.memoryModel == kotlin.native.MemoryModel.EXPERIMENTAL) { int1++ + assertEquals(3, int1) + } else { + assertFailsWith { + int1++ + } + assertEquals(2, int1) } - assertEquals(2, int1) assertEquals(77, int2) }.freeze()) } -} \ No newline at end of file +} diff --git a/kotlin-native/runtime/src/gc/noop/cpp/GC.hpp b/kotlin-native/runtime/src/gc/noop/cpp/GC.hpp index 4b7678e4682..43b333eade7 100644 --- a/kotlin-native/runtime/src/gc/noop/cpp/GC.hpp +++ b/kotlin-native/runtime/src/gc/noop/cpp/GC.hpp @@ -13,6 +13,8 @@ namespace gc { using GC = kotlin::gc::NoOpGC; +inline constexpr bool kSupportsMultipleMutators = true; + } // namespace gc } // namespace kotlin diff --git a/kotlin-native/runtime/src/gc/stms/cpp/GC.hpp b/kotlin-native/runtime/src/gc/stms/cpp/GC.hpp index 281bc4fdd23..a2d8ed45fd3 100644 --- a/kotlin-native/runtime/src/gc/stms/cpp/GC.hpp +++ b/kotlin-native/runtime/src/gc/stms/cpp/GC.hpp @@ -13,6 +13,8 @@ namespace gc { using GC = kotlin::gc::SingleThreadMarkAndSweep; +inline constexpr bool kSupportsMultipleMutators = false; + } // namespace gc } // namespace kotlin diff --git a/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp b/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp index f5d85c8e3ae..56b299bb3b3 100644 --- a/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp +++ b/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp @@ -3737,3 +3737,5 @@ kotlin::ThreadState kotlin::GetThreadState(MemoryState* thread) noexcept { // Assume that we are always in the Runnable thread state. return ThreadState::kRunnable; } + +const bool kotlin::kSupportsMultipleMutators = true; diff --git a/kotlin-native/runtime/src/main/cpp/Memory.h b/kotlin-native/runtime/src/main/cpp/Memory.h index 61b1b0937ae..9a9179e27c4 100644 --- a/kotlin-native/runtime/src/main/cpp/Memory.h +++ b/kotlin-native/runtime/src/main/cpp/Memory.h @@ -441,6 +441,8 @@ ALWAYS_INLINE inline R CallWithThreadState(R(*function)(Args...), Args... args) return function(std::forward(args)...); } +extern const bool kSupportsMultipleMutators; + } // namespace kotlin #endif // RUNTIME_MEMORY_H diff --git a/kotlin-native/runtime/src/main/cpp/Runtime.cpp b/kotlin-native/runtime/src/main/cpp/Runtime.cpp index c7fcfc0a7b7..5cc5a404024 100644 --- a/kotlin-native/runtime/src/main/cpp/Runtime.cpp +++ b/kotlin-native/runtime/src/main/cpp/Runtime.cpp @@ -107,8 +107,9 @@ RuntimeState* initRuntime() { result->memoryState = InitMemory(false); // The argument will be ignored for legacy DestroyRuntimeMode result->worker = WorkerInit(result->memoryState, true); firstRuntime = atomicAdd(&aliveRuntimesCount, 1) == 1; - if (CurrentMemoryModel == MemoryModel::kExperimental) { - RuntimeCheck(firstRuntime, "Experimental MM does not support multiple mutator threads yet"); + if (!kotlin::kSupportsMultipleMutators && !firstRuntime) { + konan::consoleErrorf("This GC implementation does not support multiple mutator threads."); + konan::abort(); } break; case DESTROY_RUNTIME_ON_SHUTDOWN: @@ -120,8 +121,9 @@ RuntimeState* initRuntime() { RuntimeAssert(lastStatus != kGlobalRuntimeShutdown, "Kotlin runtime was shut down. Cannot create new runtimes."); } firstRuntime = lastStatus == kGlobalRuntimeUninitialized; - if (CurrentMemoryModel == MemoryModel::kExperimental) { - RuntimeCheck(firstRuntime, "Experimental MM does not support multiple mutator threads yet"); + if (!kotlin::kSupportsMultipleMutators && !firstRuntime) { + konan::consoleErrorf("This GC implementation does not support multiple mutator threads."); + konan::abort(); } result->memoryState = InitMemory(firstRuntime); result->worker = WorkerInit(result->memoryState, true); diff --git a/kotlin-native/runtime/src/mm/cpp/Memory.cpp b/kotlin-native/runtime/src/mm/cpp/Memory.cpp index 79b191c0bde..050a71b5dfb 100644 --- a/kotlin-native/runtime/src/mm/cpp/Memory.cpp +++ b/kotlin-native/runtime/src/mm/cpp/Memory.cpp @@ -9,6 +9,7 @@ #include "Exceptions.h" #include "ExtraObjectData.hpp" #include "Freezing.hpp" +#include "GC.hpp" #include "GlobalsRegistry.hpp" #include "InitializationScheme.hpp" #include "KAssert.h" @@ -509,3 +510,5 @@ extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void Kotlin_mm_switchThreadStateRunnabl MemoryState* kotlin::mm::GetMemoryState() { return ToMemoryState(ThreadRegistry::Instance().CurrentThreadDataNode()); } + +const bool kotlin::kSupportsMultipleMutators = kotlin::gc::kSupportsMultipleMutators;