Make SingleThreadMarkAndSweep support multiple threads
This commit is contained in:
committed by
Space
parent
10ae9c511b
commit
9ebba93dd9
@@ -708,7 +708,7 @@ task runtime_basic_simd(type: KonanLocalTest) {
|
|||||||
|
|
||||||
task runtime_worker_random(type: KonanLocalTest) {
|
task runtime_worker_random(type: KonanLocalTest) {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Uses workers.
|
enabled = (project.testTarget != 'wasm32') && // Uses workers.
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
!(isExperimentalMM && project.testTarget == 'mingw_x64') // TODO: Fix Experimental MM performance on Windows.
|
||||||
source = "runtime/basic/worker_random.kt"
|
source = "runtime/basic/worker_random.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -826,170 +826,150 @@ task empty_substring(type: KonanLocalTest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
standaloneTest("cleaner_basic") {
|
standaloneTest("cleaner_basic") {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Cleaners need workers
|
enabled = (project.testTarget != 'wasm32') // Cleaners need workers
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
source = "runtime/basic/cleaner_basic.kt"
|
source = "runtime/basic/cleaner_basic.kt"
|
||||||
flags = ['-tr', '-Xopt-in=kotlin.native.internal.InternalForKotlinNative']
|
flags = ['-tr', '-Xopt-in=kotlin.native.internal.InternalForKotlinNative']
|
||||||
}
|
}
|
||||||
|
|
||||||
standaloneTest("cleaner_workers") {
|
standaloneTest("cleaner_workers") {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Cleaners need workers
|
enabled = (project.testTarget != 'wasm32') // Cleaners need workers
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
source = "runtime/basic/cleaner_workers.kt"
|
source = "runtime/basic/cleaner_workers.kt"
|
||||||
flags = ['-tr', '-Xopt-in=kotlin.native.internal.InternalForKotlinNative']
|
flags = ['-tr', '-Xopt-in=kotlin.native.internal.InternalForKotlinNative']
|
||||||
}
|
}
|
||||||
|
|
||||||
standaloneTest("cleaner_in_main_with_checker") {
|
standaloneTest("cleaner_in_main_with_checker") {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Cleaners need workers
|
enabled = (project.testTarget != 'wasm32') // Cleaners need workers
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
source = "runtime/basic/cleaner_in_main_with_checker.kt"
|
source = "runtime/basic/cleaner_in_main_with_checker.kt"
|
||||||
goldValue = "42\n"
|
goldValue = "42\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
standaloneTest("cleaner_in_main_without_checker") {
|
standaloneTest("cleaner_in_main_without_checker") {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Cleaners need workers
|
enabled = (project.testTarget != 'wasm32') // Cleaners need workers
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
source = "runtime/basic/cleaner_in_main_without_checker.kt"
|
source = "runtime/basic/cleaner_in_main_without_checker.kt"
|
||||||
goldValue = ""
|
goldValue = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
standaloneTest("cleaner_leak_without_checker") {
|
standaloneTest("cleaner_leak_without_checker") {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Cleaners need workers
|
enabled = (project.testTarget != 'wasm32') // Cleaners need workers
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
source = "runtime/basic/cleaner_leak_without_checker.kt"
|
source = "runtime/basic/cleaner_leak_without_checker.kt"
|
||||||
goldValue = ""
|
goldValue = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
standaloneTest("cleaner_leak_with_checker") {
|
standaloneTest("cleaner_leak_with_checker") {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Cleaners need workers
|
enabled = (project.testTarget != 'wasm32') // Cleaners need workers
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
source = "runtime/basic/cleaner_leak_with_checker.kt"
|
source = "runtime/basic/cleaner_leak_with_checker.kt"
|
||||||
expectedExitStatusChecker = { it != 0 }
|
expectedExitStatusChecker = { it != 0 }
|
||||||
outputChecker = { s -> (s =~ /Cleaner (0x)?[0-9a-fA-F]+ was disposed during program exit/).find() }
|
outputChecker = { s -> (s =~ /Cleaner (0x)?[0-9a-fA-F]+ was disposed during program exit/).find() }
|
||||||
}
|
}
|
||||||
|
|
||||||
standaloneTest("cleaner_in_tls_main_without_checker") {
|
standaloneTest("cleaner_in_tls_main_without_checker") {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Cleaners need workers
|
enabled = (project.testTarget != 'wasm32') // Cleaners need workers
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
source = "runtime/basic/cleaner_in_tls_main_without_checker.kt"
|
source = "runtime/basic/cleaner_in_tls_main_without_checker.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
standaloneTest("cleaner_in_tls_main_with_checker") {
|
standaloneTest("cleaner_in_tls_main_with_checker") {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Cleaners need workers
|
enabled = (project.testTarget != 'wasm32') // Cleaners need workers
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
source = "runtime/basic/cleaner_in_tls_main_with_checker.kt"
|
source = "runtime/basic/cleaner_in_tls_main_with_checker.kt"
|
||||||
expectedExitStatusChecker = { it != 0 }
|
expectedExitStatusChecker = { it != 0 }
|
||||||
outputChecker = { s -> (s =~ /Cleaner (0x)?[0-9a-fA-F]+ was disposed during program exit/).find() }
|
outputChecker = { s -> (s =~ /Cleaner (0x)?[0-9a-fA-F]+ was disposed during program exit/).find() }
|
||||||
}
|
}
|
||||||
|
|
||||||
standaloneTest("cleaner_in_tls_worker") {
|
standaloneTest("cleaner_in_tls_worker") {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Cleaners need workers
|
enabled = (project.testTarget != 'wasm32') // Cleaners need workers
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
source = "runtime/basic/cleaner_in_tls_worker.kt"
|
source = "runtime/basic/cleaner_in_tls_worker.kt"
|
||||||
flags = ['-Xopt-in=kotlin.native.internal.InternalForKotlinNative']
|
flags = ['-Xopt-in=kotlin.native.internal.InternalForKotlinNative']
|
||||||
}
|
}
|
||||||
|
|
||||||
standaloneTest("worker_bound_reference0") {
|
standaloneTest("worker_bound_reference0") {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads.
|
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads.
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
!(isExperimentalMM && project.testTarget == 'mingw_x64') // TODO: Fix Experimental MM performance on Windows.
|
||||||
source = "runtime/concurrent/worker_bound_reference0.kt"
|
source = "runtime/concurrent/worker_bound_reference0.kt"
|
||||||
flags = ['-tr']
|
flags = ['-tr']
|
||||||
}
|
}
|
||||||
|
|
||||||
task worker0(type: KonanLocalTest) {
|
task worker0(type: KonanLocalTest) {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads.
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
goldValue = "Got Input processed\nOK\n"
|
goldValue = "Got Input processed\nOK\n"
|
||||||
source = "runtime/workers/worker0.kt"
|
source = "runtime/workers/worker0.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
task worker1(type: KonanLocalTest) {
|
task worker1(type: KonanLocalTest) {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads.
|
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads.
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
!(isExperimentalMM && project.testTarget == 'mingw_x64') // TODO: Fix Experimental MM performance on Windows.
|
||||||
goldValue = "OK\n"
|
goldValue = "OK\n"
|
||||||
source = "runtime/workers/worker1.kt"
|
source = "runtime/workers/worker1.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
task worker2(type: KonanLocalTest) {
|
task worker2(type: KonanLocalTest) {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads.
|
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads.
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
!(isExperimentalMM && project.testTarget == 'mingw_x64') // TODO: Fix Experimental MM performance on Windows.
|
||||||
goldValue = "OK\n"
|
goldValue = "OK\n"
|
||||||
source = "runtime/workers/worker2.kt"
|
source = "runtime/workers/worker2.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
task worker3(type: KonanLocalTest) {
|
task worker3(type: KonanLocalTest) {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads.
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
goldValue = "OK\n"
|
goldValue = "OK\n"
|
||||||
source = "runtime/workers/worker3.kt"
|
source = "runtime/workers/worker3.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
task worker4(type: KonanLocalTest) {
|
task worker4(type: KonanLocalTest) {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads.
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
goldValue = "Got 42\nOK\n"
|
goldValue = "Got 42\nOK\n"
|
||||||
source = "runtime/workers/worker4.kt"
|
source = "runtime/workers/worker4.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
// This tests changes main thread worker queue state, so better be executed alone.
|
// This tests changes main thread worker queue state, so better be executed alone.
|
||||||
standaloneTest("worker5") {
|
standaloneTest("worker5") {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads.
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
goldValue = "Got 3\nOK\n"
|
goldValue = "Got 3\nOK\n"
|
||||||
source = "runtime/workers/worker5.kt"
|
source = "runtime/workers/worker5.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
task worker6(type: KonanLocalTest) {
|
task worker6(type: KonanLocalTest) {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads.
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
goldValue = "Got 42\nOK\n"
|
goldValue = "Got 42\nOK\n"
|
||||||
source = "runtime/workers/worker6.kt"
|
source = "runtime/workers/worker6.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
task worker7(type: KonanLocalTest) {
|
task worker7(type: KonanLocalTest) {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads.
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
goldValue = "Input\nGot kotlin.Unit\nOK\n"
|
goldValue = "Input\nGot kotlin.Unit\nOK\n"
|
||||||
source = "runtime/workers/worker7.kt"
|
source = "runtime/workers/worker7.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
task worker8(type: KonanLocalTest) {
|
task worker8(type: KonanLocalTest) {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads.
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
goldValue = "SharedData(string=Hello, int=10, member=SharedDataMember(double=0.1))\nGot kotlin.Unit\nOK\n"
|
goldValue = "SharedData(string=Hello, int=10, member=SharedDataMember(double=0.1))\nGot kotlin.Unit\nOK\n"
|
||||||
source = "runtime/workers/worker8.kt"
|
source = "runtime/workers/worker8.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
task worker9(type: KonanLocalTest) {
|
task worker9(type: KonanLocalTest) {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads.
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
goldValue = "zzz\n42\nOK\nfirst 2\nsecond 3\nfrozen OK\n"
|
goldValue = "zzz\n42\nOK\nfirst 2\nsecond 3\nfrozen OK\n"
|
||||||
source = "runtime/workers/worker9.kt"
|
source = "runtime/workers/worker9.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
task worker10(type: KonanLocalTest) {
|
task worker10(type: KonanLocalTest) {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads.
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
goldValue = "OK\ntrue\ntrue\n"
|
goldValue = "OK\ntrue\ntrue\n"
|
||||||
source = "runtime/workers/worker10.kt"
|
source = "runtime/workers/worker10.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
task worker11(type: KonanLocalTest) {
|
task worker11(type: KonanLocalTest) {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads.
|
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads.
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
!(isExperimentalMM && project.testTarget == 'mingw_x64') // TODO: Fix Experimental MM performance on Windows.
|
||||||
goldValue = "OK\n"
|
goldValue = "OK\n"
|
||||||
source = "runtime/workers/worker11.kt"
|
source = "runtime/workers/worker11.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
standaloneTest("worker_threadlocal_no_leak") {
|
standaloneTest("worker_threadlocal_no_leak") {
|
||||||
disabled = (project.testTarget == 'wasm32') || // Needs pthreads.
|
disabled = (project.testTarget == 'wasm32') // Needs pthreads.
|
||||||
isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
source = "runtime/workers/worker_threadlocal_no_leak.kt"
|
source = "runtime/workers/worker_threadlocal_no_leak.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
task freeze0(type: KonanLocalTest) {
|
task freeze0(type: KonanLocalTest) {
|
||||||
enabled = (project.testTarget != 'wasm32') && // No workers on WASM.
|
enabled = (project.testTarget != 'wasm32') // No workers on WASM.
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
goldValue = "frozen bit is true\n" +
|
goldValue = "frozen bit is true\n" +
|
||||||
"Worker: SharedData(string=Hello, int=10, member=SharedDataMember(double=0.1))\n" +
|
"Worker: SharedData(string=Hello, int=10, member=SharedDataMember(double=0.1))\n" +
|
||||||
"Main: SharedData(string=Hello, int=10, member=SharedDataMember(double=0.1))\n" +
|
"Main: SharedData(string=Hello, int=10, member=SharedDataMember(double=0.1))\n" +
|
||||||
@@ -1010,8 +990,7 @@ task freeze_stress(type: KonanLocalTest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
task freeze2(type: KonanLocalTest) {
|
task freeze2(type: KonanLocalTest) {
|
||||||
enabled = (project.testTarget != 'wasm32') && // No exceptions on WASM.
|
enabled = (project.testTarget != 'wasm32') // No exceptions on WASM.
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
goldValue =
|
goldValue =
|
||||||
"Worker 1: Hello world\n" + "Worker2: 42\n" +
|
"Worker 1: Hello world\n" + "Worker2: 42\n" +
|
||||||
"Worker3: 239.0\n" + "Worker4: a\n" + "OK\n"
|
"Worker3: 239.0\n" + "Worker4: a\n" + "OK\n"
|
||||||
@@ -1019,8 +998,7 @@ task freeze2(type: KonanLocalTest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
task freeze3(type: KonanLocalTest) {
|
task freeze3(type: KonanLocalTest) {
|
||||||
enabled = (project.testTarget != 'wasm32') && // No exceptions on WASM.
|
enabled = (project.testTarget != 'wasm32') // No exceptions on WASM.
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
goldValue = "OK\n"
|
goldValue = "OK\n"
|
||||||
source = "runtime/workers/freeze3.kt"
|
source = "runtime/workers/freeze3.kt"
|
||||||
}
|
}
|
||||||
@@ -1044,7 +1022,7 @@ task freeze6(type: KonanLocalTest) {
|
|||||||
|
|
||||||
task atomic0(type: KonanLocalTest) {
|
task atomic0(type: KonanLocalTest) {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads.
|
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads.
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
!(isExperimentalMM && project.testTarget == 'mingw_x64') // TODO: Fix Experimental MM performance on Windows.
|
||||||
goldValue = "35\n" + "20\n" + "OK\n"
|
goldValue = "35\n" + "20\n" + "OK\n"
|
||||||
source = "runtime/workers/atomic0.kt"
|
source = "runtime/workers/atomic0.kt"
|
||||||
}
|
}
|
||||||
@@ -1056,8 +1034,7 @@ standaloneTest("atomic1") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
task lazy0(type: KonanLocalTest) {
|
task lazy0(type: KonanLocalTest) {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads.
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
goldValue = "OK\n"
|
goldValue = "OK\n"
|
||||||
source = "runtime/workers/lazy0.kt"
|
source = "runtime/workers/lazy0.kt"
|
||||||
}
|
}
|
||||||
@@ -1078,29 +1055,25 @@ standaloneTest("lazy3") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
task mutableData1(type: KonanLocalTest) {
|
task mutableData1(type: KonanLocalTest) {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads. Need exceptions
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads. Need exceptions
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
source = "runtime/workers/mutableData1.kt"
|
source = "runtime/workers/mutableData1.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
task enumIdentity(type: KonanLocalTest) {
|
task enumIdentity(type: KonanLocalTest) {
|
||||||
enabled = (project.testTarget != 'wasm32') && // Workers need pthreads.
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
!isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
goldValue = "true\n"
|
goldValue = "true\n"
|
||||||
source = "runtime/workers/enum_identity.kt"
|
source = "runtime/workers/enum_identity.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
standaloneTest("leakWorker") {
|
standaloneTest("leakWorker") {
|
||||||
disabled = (project.testTarget == 'wasm32') || // Needs pthreads.
|
disabled = (project.testTarget == 'wasm32') // Needs pthreads.
|
||||||
isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
source = "runtime/workers/leak_worker.kt"
|
source = "runtime/workers/leak_worker.kt"
|
||||||
expectedExitStatusChecker = { it != 0 }
|
expectedExitStatusChecker = { it != 0 }
|
||||||
outputChecker = { s -> s.contains("Unfinished workers detected, 1 workers leaked!") }
|
outputChecker = { s -> s.contains("Unfinished workers detected, 1 workers leaked!") }
|
||||||
}
|
}
|
||||||
|
|
||||||
standaloneTest("leakMemoryWithWorkerTermination") {
|
standaloneTest("leakMemoryWithWorkerTermination") {
|
||||||
disabled = (project.testTarget == 'wasm32') || // Needs pthreads.
|
disabled = (project.testTarget == 'wasm32') // Needs pthreads.
|
||||||
isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
source = "runtime/workers/leak_memory_with_worker_termination.kt"
|
source = "runtime/workers/leak_memory_with_worker_termination.kt"
|
||||||
|
|
||||||
if (!isExperimentalMM) { // Experimental MM will not report memory leaks.
|
if (!isExperimentalMM) { // Experimental MM will not report memory leaks.
|
||||||
@@ -2841,7 +2814,7 @@ task initializers5(type: KonanLocalTest) {
|
|||||||
|
|
||||||
task initializers6(type: KonanLocalTest) {
|
task initializers6(type: KonanLocalTest) {
|
||||||
disabled = (project.testTarget == 'wasm32') || // Needs workers.
|
disabled = (project.testTarget == 'wasm32') || // Needs workers.
|
||||||
isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
(isExperimentalMM && project.testTarget == 'mingw_x64') // TODO: Fix Experimental MM performance on Windows.
|
||||||
source = "runtime/basic/initializers6.kt"
|
source = "runtime/basic/initializers6.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2939,8 +2912,7 @@ standaloneTest("memory_only_gc") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
task memory_stable_ref_cross_thread_check(type: KonanLocalTest) {
|
task memory_stable_ref_cross_thread_check(type: KonanLocalTest) {
|
||||||
disabled = (project.testTarget == 'wasm32') || // Needs workers.
|
disabled = (project.testTarget == 'wasm32') // Needs workers.
|
||||||
isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
source = "runtime/memory/stable_ref_cross_thread_check.kt"
|
source = "runtime/memory/stable_ref_cross_thread_check.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4224,15 +4196,13 @@ dynamicTest("interop_kt43502") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interopTest("interop_leakMemoryWithRunningThreadUnchecked") {
|
interopTest("interop_leakMemoryWithRunningThreadUnchecked") {
|
||||||
disabled = (project.testTarget == 'wasm32') || // No interop for wasm yet.
|
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
|
||||||
isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
interop = 'leakMemoryWithRunningThread'
|
interop = 'leakMemoryWithRunningThread'
|
||||||
source = "interop/leakMemoryWithRunningThread/unchecked.kt"
|
source = "interop/leakMemoryWithRunningThread/unchecked.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
interopTest("interop_leakMemoryWithRunningThreadChecked") {
|
interopTest("interop_leakMemoryWithRunningThreadChecked") {
|
||||||
disabled = (project.testTarget == 'wasm32') || // No interop for wasm yet.
|
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
|
||||||
isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
interop = 'leakMemoryWithRunningThread'
|
interop = 'leakMemoryWithRunningThread'
|
||||||
source = "interop/leakMemoryWithRunningThread/checked.kt"
|
source = "interop/leakMemoryWithRunningThread/checked.kt"
|
||||||
expectedExitStatusChecker = { it != 0 }
|
expectedExitStatusChecker = { it != 0 }
|
||||||
@@ -4452,7 +4422,7 @@ if (PlatformInfo.isAppleTarget(project)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interopTest("interop_objc_illegal_sharing_with_weak") {
|
interopTest("interop_objc_illegal_sharing_with_weak") {
|
||||||
enabled = !isExperimentalMM // Experimental MM doesn't support multiple mutators and thread state switching for ObjC interop yet.
|
enabled = !isExperimentalMM // Experimental MM doesn't support thread state switching for ObjC interop yet.
|
||||||
source = "interop/objc/illegal_sharing_with_weak/main.kt"
|
source = "interop/objc/illegal_sharing_with_weak/main.kt"
|
||||||
interop = 'objc_illegal_sharing_with_weak'
|
interop = 'objc_illegal_sharing_with_weak'
|
||||||
|
|
||||||
@@ -4465,7 +4435,7 @@ if (PlatformInfo.isAppleTarget(project)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interopTest("interop_objc_kt42172") {
|
interopTest("interop_objc_kt42172") {
|
||||||
enabled = !isExperimentalMM // Experimental MM doesn't support multiple mutators and thread state switching for ObjC interop yet.
|
enabled = !isExperimentalMM // Experimental MM doesn't support thread state switching for ObjC interop yet.
|
||||||
source = "interop/objc/kt42172/main.kt"
|
source = "interop/objc/kt42172/main.kt"
|
||||||
interop = "objc_kt42172"
|
interop = "objc_kt42172"
|
||||||
flags = ['-Xopt-in=kotlin.native.internal.InternalForKotlinNative']
|
flags = ['-Xopt-in=kotlin.native.internal.InternalForKotlinNative']
|
||||||
@@ -4516,7 +4486,7 @@ standaloneTest("interop_zlib") {
|
|||||||
|
|
||||||
standaloneTest("interop_objc_illegal_sharing") {
|
standaloneTest("interop_objc_illegal_sharing") {
|
||||||
disabled = !isAppleTarget(project) ||
|
disabled = !isAppleTarget(project) ||
|
||||||
isExperimentalMM // Experimental MM doesn't support multiple mutators and thread state switching for ObjC interop yet.
|
isExperimentalMM // Experimental MM doesn't support thread state switching for ObjC interop yet.
|
||||||
source = "interop/objc/illegal_sharing.kt"
|
source = "interop/objc/illegal_sharing.kt"
|
||||||
UtilsKt.dependsOnPlatformLibs(it)
|
UtilsKt.dependsOnPlatformLibs(it)
|
||||||
if (isExperimentalMM) {
|
if (isExperimentalMM) {
|
||||||
@@ -4601,8 +4571,7 @@ dynamicTest("interop_kt42397") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dynamicTest("interop_cleaners_main_thread") {
|
dynamicTest("interop_cleaners_main_thread") {
|
||||||
disabled = (project.target.name != project.hostName) ||
|
disabled = (project.target.name != project.hostName)
|
||||||
isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
source = "interop/cleaners/cleaners.kt"
|
source = "interop/cleaners/cleaners.kt"
|
||||||
cSource = "$projectDir/interop/cleaners/main_thread.cpp"
|
cSource = "$projectDir/interop/cleaners/main_thread.cpp"
|
||||||
clangTool = "clang++"
|
clangTool = "clang++"
|
||||||
@@ -4611,8 +4580,7 @@ dynamicTest("interop_cleaners_main_thread") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dynamicTest("interop_cleaners_second_thread") {
|
dynamicTest("interop_cleaners_second_thread") {
|
||||||
disabled = (project.target.name != project.hostName) ||
|
disabled = (project.target.name != project.hostName)
|
||||||
isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
source = "interop/cleaners/cleaners.kt"
|
source = "interop/cleaners/cleaners.kt"
|
||||||
cSource = "$projectDir/interop/cleaners/second_thread.cpp"
|
cSource = "$projectDir/interop/cleaners/second_thread.cpp"
|
||||||
clangTool = "clang++"
|
clangTool = "clang++"
|
||||||
@@ -4621,8 +4589,7 @@ dynamicTest("interop_cleaners_second_thread") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dynamicTest("interop_cleaners_leak") {
|
dynamicTest("interop_cleaners_leak") {
|
||||||
disabled = (project.target.name != project.hostName) ||
|
disabled = (project.target.name != project.hostName)
|
||||||
isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
source = "interop/cleaners/cleaners.kt"
|
source = "interop/cleaners/cleaners.kt"
|
||||||
cSource = "$projectDir/interop/cleaners/leak.cpp"
|
cSource = "$projectDir/interop/cleaners/leak.cpp"
|
||||||
clangTool = "clang++"
|
clangTool = "clang++"
|
||||||
@@ -4641,8 +4608,7 @@ dynamicTest("interop_migrating_main_thread_legacy") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dynamicTest("interop_migrating_main_thread") {
|
dynamicTest("interop_migrating_main_thread") {
|
||||||
disabled = (project.target.name != project.hostName) ||
|
disabled = (project.target.name != project.hostName)
|
||||||
isExperimentalMM // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
source = "interop/migrating_main_thread/lib.kt"
|
source = "interop/migrating_main_thread/lib.kt"
|
||||||
flags = ['-Xdestroy-runtime-mode=on-shutdown']
|
flags = ['-Xdestroy-runtime-mode=on-shutdown']
|
||||||
if (isExperimentalMM) {
|
if (isExperimentalMM) {
|
||||||
@@ -5218,8 +5184,7 @@ private void configureStdlibTest(KonanGTest task, boolean inWorker) {
|
|||||||
task.useFilter = false
|
task.useFilter = false
|
||||||
task.testLogger = KonanTest.Logger.GTEST
|
task.testLogger = KonanTest.Logger.GTEST
|
||||||
task.finalizedBy("resultsTask")
|
task.finalizedBy("resultsTask")
|
||||||
task.enabled = (project.testTarget != 'wasm32') && // Uses exceptions
|
task.enabled = (project.testTarget != 'wasm32') // Uses exceptions
|
||||||
(!inWorker || !ext.isExperimentalMM) // Experimental MM doesn't support multiple mutators yet.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1073,6 +1073,11 @@ class TestSharedRefs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This will free `object1` and release+dealloc its associated `Deinit` which nils `Deinit.object2`
|
||||||
|
ValuesKt.gc()
|
||||||
|
// This will free `object2`.
|
||||||
|
ValuesKt.gc()
|
||||||
|
|
||||||
try assertTrue(Deinit.weakVar2 === nil)
|
try assertTrue(Deinit.weakVar2 === nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1403,10 +1408,7 @@ class ValuesTests : SimpleTestProvider {
|
|||||||
test("TestInvalidIdentifiers", testInvalidIdentifiers)
|
test("TestInvalidIdentifiers", testInvalidIdentifiers)
|
||||||
test("TestDeprecation", testDeprecation)
|
test("TestDeprecation", testDeprecation)
|
||||||
test("TestWeakRefs", testWeakRefs)
|
test("TestWeakRefs", testWeakRefs)
|
||||||
if !ValuesKt.isExperimentalMM {
|
test("TestSharedRefs", TestSharedRefs().test)
|
||||||
// Experimental MM doesn't support multiple mutators yet.
|
|
||||||
test("TestSharedRefs", TestSharedRefs().test)
|
|
||||||
}
|
|
||||||
test("TestClassTypeCheck", testClassTypeCheck)
|
test("TestClassTypeCheck", testClassTypeCheck)
|
||||||
test("TestInterfaceTypeCheck", testInterfaceTypeCheck)
|
test("TestInterfaceTypeCheck", testInterfaceTypeCheck)
|
||||||
test("TestGH3503_1", testGH3503_1)
|
test("TestGH3503_1", testGH3503_1)
|
||||||
@@ -1419,9 +1421,6 @@ class ValuesTests : SimpleTestProvider {
|
|||||||
test("TestFakeOverrideInInterface", testFakeOverrideInInterface)
|
test("TestFakeOverrideInInterface", testFakeOverrideInInterface)
|
||||||
|
|
||||||
// Stress test, must remain the last one:
|
// Stress test, must remain the last one:
|
||||||
if !ValuesKt.isExperimentalMM {
|
test("TestGH2931", testGH2931)
|
||||||
// Experimental MM doesn't support multiple mutators yet.
|
|
||||||
test("TestGH2931", testGH2931)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ fun main() {
|
|||||||
|
|
||||||
worker.requestTermination().result
|
worker.requestTermination().result
|
||||||
waitWorkerTermination(worker)
|
waitWorkerTermination(worker)
|
||||||
|
GC.collect()
|
||||||
waitCleanerWorker()
|
waitCleanerWorker()
|
||||||
|
|
||||||
assertEquals(42, value.value)
|
assertEquals(42, value.value)
|
||||||
|
|||||||
@@ -59,27 +59,36 @@ data class Data(var int: Int)
|
|||||||
assertFailsWith<InvalidMutabilityException> { a8[1] = 2.0 }
|
assertFailsWith<InvalidMutabilityException> { a8[1] = 2.0 }
|
||||||
|
|
||||||
// Ensure that String and integral boxes are frozen by default, by passing local to the worker.
|
// Ensure that String and integral boxes are frozen by default, by passing local to the worker.
|
||||||
|
val hasToBeFrozen = Platform.memoryModel == MemoryModel.STRICT
|
||||||
val worker = Worker.start()
|
val worker = Worker.start()
|
||||||
var data: Any = "Hello" + " " + "world"
|
var data: Any = "Hello" + " " + "world"
|
||||||
assert(data.isFrozen)
|
if (hasToBeFrozen) {
|
||||||
|
assertTrue(data.isFrozen)
|
||||||
|
}
|
||||||
worker.execute(TransferMode.SAFE, { data } ) {
|
worker.execute(TransferMode.SAFE, { data } ) {
|
||||||
input -> println("Worker 1: $input")
|
input -> println("Worker 1: $input")
|
||||||
}.result
|
}.result
|
||||||
|
|
||||||
data = 42
|
data = 42
|
||||||
assert(data.isFrozen)
|
if (hasToBeFrozen) {
|
||||||
|
assertTrue(data.isFrozen)
|
||||||
|
}
|
||||||
worker.execute(TransferMode.SAFE, { data } ) {
|
worker.execute(TransferMode.SAFE, { data } ) {
|
||||||
input -> println("Worker2: $input")
|
input -> println("Worker2: $input")
|
||||||
}.result
|
}.result
|
||||||
|
|
||||||
data = 239.0
|
data = 239.0
|
||||||
assert(data.isFrozen)
|
if (hasToBeFrozen) {
|
||||||
|
assertTrue(data.isFrozen)
|
||||||
|
}
|
||||||
worker.execute(TransferMode.SAFE, { data } ) {
|
worker.execute(TransferMode.SAFE, { data } ) {
|
||||||
input -> println("Worker3: $input")
|
input -> println("Worker3: $input")
|
||||||
}.result
|
}.result
|
||||||
|
|
||||||
data = 'a'
|
data = 'a'
|
||||||
assert(data.isFrozen)
|
if (hasToBeFrozen) {
|
||||||
|
assertTrue(data.isFrozen)
|
||||||
|
}
|
||||||
worker.execute(TransferMode.SAFE, { data } ) {
|
worker.execute(TransferMode.SAFE, { data } ) {
|
||||||
input -> println("Worker4: $input")
|
input -> println("Worker4: $input")
|
||||||
}.result
|
}.result
|
||||||
@@ -87,4 +96,4 @@ data class Data(var int: Int)
|
|||||||
worker.requestTermination().result
|
worker.requestTermination().result
|
||||||
|
|
||||||
println("OK")
|
println("OK")
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -44,7 +44,7 @@ open class CompileToBitcode @Inject constructor(
|
|||||||
|
|
||||||
// Source files and headers are registered as inputs by the `inputFiles` and `headers` properties.
|
// Source files and headers are registered as inputs by the `inputFiles` and `headers` properties.
|
||||||
var srcDirs: FileCollection = project.files(srcRoot.resolve("cpp"))
|
var srcDirs: FileCollection = project.files(srcRoot.resolve("cpp"))
|
||||||
var headersDirs: FileCollection = project.files(srcRoot.resolve("headers"))
|
var headersDirs: FileCollection = srcDirs + project.files(srcRoot.resolve("headers"))
|
||||||
|
|
||||||
@Input
|
@Input
|
||||||
var language = Language.CPP
|
var language = Language.CPP
|
||||||
|
|||||||
@@ -11,6 +11,11 @@
|
|||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
|
|
||||||
namespace kotlin {
|
namespace kotlin {
|
||||||
|
|
||||||
|
namespace mm {
|
||||||
|
class ThreadData;
|
||||||
|
}
|
||||||
|
|
||||||
namespace gc {
|
namespace gc {
|
||||||
|
|
||||||
// No-op GC is a GC that does not free memory.
|
// No-op GC is a GC that does not free memory.
|
||||||
@@ -23,7 +28,7 @@ public:
|
|||||||
public:
|
public:
|
||||||
using ObjectData = NoOpGC::ObjectData;
|
using ObjectData = NoOpGC::ObjectData;
|
||||||
|
|
||||||
explicit ThreadData(NoOpGC& gc) noexcept {}
|
explicit ThreadData(NoOpGC& gc, mm::ThreadData& threadData) noexcept {}
|
||||||
~ThreadData() = default;
|
~ThreadData() = default;
|
||||||
|
|
||||||
void SafePointFunctionEpilogue() noexcept {}
|
void SafePointFunctionEpilogue() noexcept {}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace gc {
|
|||||||
|
|
||||||
using GC = kotlin::gc::SingleThreadMarkAndSweep;
|
using GC = kotlin::gc::SingleThreadMarkAndSweep;
|
||||||
|
|
||||||
inline constexpr bool kSupportsMultipleMutators = false;
|
inline constexpr bool kSupportsMultipleMutators = true;
|
||||||
|
|
||||||
} // namespace gc
|
} // namespace gc
|
||||||
} // namespace kotlin
|
} // namespace kotlin
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "Runtime.h"
|
#include "Runtime.h"
|
||||||
#include "ThreadData.hpp"
|
#include "ThreadData.hpp"
|
||||||
#include "ThreadRegistry.hpp"
|
#include "ThreadRegistry.hpp"
|
||||||
|
#include "ThreadSuspension.hpp"
|
||||||
|
|
||||||
using namespace kotlin;
|
using namespace kotlin;
|
||||||
|
|
||||||
@@ -49,49 +50,76 @@ struct FinalizeTraits {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void gc::SingleThreadMarkAndSweep::ThreadData::SafePointFunctionEpilogue() noexcept {
|
void gc::SingleThreadMarkAndSweep::ThreadData::SafePointFunctionEpilogue() noexcept {
|
||||||
if (gc_.GetThreshold() == 0 || safePointsCounter_ % gc_.GetThreshold() == 0) {
|
SafePointRegular(1);
|
||||||
PerformFullGC();
|
|
||||||
}
|
|
||||||
++safePointsCounter_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void gc::SingleThreadMarkAndSweep::ThreadData::SafePointLoopBody() noexcept {
|
void gc::SingleThreadMarkAndSweep::ThreadData::SafePointLoopBody() noexcept {
|
||||||
if (gc_.GetThreshold() == 0 || safePointsCounter_ % gc_.GetThreshold() == 0) {
|
SafePointRegular(1);
|
||||||
PerformFullGC();
|
|
||||||
}
|
|
||||||
++safePointsCounter_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void gc::SingleThreadMarkAndSweep::ThreadData::SafePointExceptionUnwind() noexcept {
|
void gc::SingleThreadMarkAndSweep::ThreadData::SafePointExceptionUnwind() noexcept {
|
||||||
if (gc_.GetThreshold() == 0 || safePointsCounter_ % gc_.GetThreshold() == 0) {
|
SafePointRegular(1);
|
||||||
PerformFullGC();
|
|
||||||
}
|
|
||||||
++safePointsCounter_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void gc::SingleThreadMarkAndSweep::ThreadData::SafePointAllocation(size_t size) noexcept {
|
void gc::SingleThreadMarkAndSweep::ThreadData::SafePointAllocation(size_t size) noexcept {
|
||||||
size_t allocationOverhead =
|
size_t allocationOverhead =
|
||||||
gc_.GetAllocationThresholdBytes() == 0 ? allocatedBytes_ : allocatedBytes_ % gc_.GetAllocationThresholdBytes();
|
gc_.GetAllocationThresholdBytes() == 0 ? allocatedBytes_ : allocatedBytes_ % gc_.GetAllocationThresholdBytes();
|
||||||
if (allocationOverhead + size >= gc_.GetAllocationThresholdBytes()) {
|
if (threadData_.suspensionData().suspendIfRequested()) {
|
||||||
|
allocatedBytes_ = 0;
|
||||||
|
} else if (allocationOverhead + size >= gc_.GetAllocationThresholdBytes()) {
|
||||||
|
allocatedBytes_ = 0;
|
||||||
PerformFullGC();
|
PerformFullGC();
|
||||||
}
|
}
|
||||||
allocatedBytes_ += size;
|
allocatedBytes_ += size;
|
||||||
}
|
}
|
||||||
|
|
||||||
void gc::SingleThreadMarkAndSweep::ThreadData::PerformFullGC() noexcept {
|
void gc::SingleThreadMarkAndSweep::ThreadData::PerformFullGC() noexcept {
|
||||||
gc_.PerformFullGC();
|
mm::ObjectFactory<gc::SingleThreadMarkAndSweep>::FinalizerQueue finalizerQueue;
|
||||||
|
{
|
||||||
|
// Switch state to native to simulate this thread being a GC thread.
|
||||||
|
// As a bonus, if we failed to suspend threads (which means some other thread asked for a GC),
|
||||||
|
// we will automatically suspend at the scope exit.
|
||||||
|
// TODO: Cannot use `threadData_` here, because there's no way to transform `mm::ThreadData` into `MemoryState*`.
|
||||||
|
ThreadStateGuard guard(ThreadState::kNative);
|
||||||
|
finalizerQueue = gc_.PerformFullGC();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalizers are run after threads are resumed, because finalizers may request GC themselves, which would
|
||||||
|
// try to suspend threads again. Also, we run finalizers in the runnable state, because they may be executing
|
||||||
|
// kotlin code.
|
||||||
|
|
||||||
|
// TODO: These will actually need to be run on a separate thread.
|
||||||
|
// TODO: Cannot use `threadData_` here, because there's no way to transform `mm::ThreadData` into `MemoryState*`.
|
||||||
|
AssertThreadState(ThreadState::kRunnable);
|
||||||
|
finalizerQueue.Finalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
void gc::SingleThreadMarkAndSweep::ThreadData::OnOOM(size_t size) noexcept {
|
void gc::SingleThreadMarkAndSweep::ThreadData::OnOOM(size_t size) noexcept {
|
||||||
PerformFullGC();
|
PerformFullGC();
|
||||||
}
|
}
|
||||||
|
|
||||||
void gc::SingleThreadMarkAndSweep::PerformFullGC() noexcept {
|
void gc::SingleThreadMarkAndSweep::ThreadData::SafePointRegular(size_t weight) noexcept {
|
||||||
RuntimeAssert(running_ == false, "Cannot have been called during another collection");
|
size_t counterOverhead = gc_.GetThreshold() == 0 ? safePointsCounter_ : safePointsCounter_ % gc_.GetThreshold();
|
||||||
running_ = true;
|
if (threadData_.suspensionData().suspendIfRequested()) {
|
||||||
|
safePointsCounter_ = 0;
|
||||||
|
} else if (counterOverhead + weight >= gc_.GetThreshold()) {
|
||||||
|
safePointsCounter_ = 0;
|
||||||
|
PerformFullGC();
|
||||||
|
}
|
||||||
|
safePointsCounter_ += weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
mm::ObjectFactory<gc::SingleThreadMarkAndSweep>::FinalizerQueue gc::SingleThreadMarkAndSweep::PerformFullGC() noexcept {
|
||||||
|
bool didSuspend = mm::SuspendThreads();
|
||||||
|
if (!didSuspend) {
|
||||||
|
// Somebody else suspended the threads, and so ran a GC.
|
||||||
|
// TODO: This breaks if suspension is used by something apart from GC.
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
KStdVector<ObjHeader*> graySet;
|
KStdVector<ObjHeader*> graySet;
|
||||||
for (auto& thread : mm::GlobalData::Instance().threadRegistry().Iter()) {
|
for (auto& thread : mm::GlobalData::Instance().threadRegistry().Iter()) {
|
||||||
|
// TODO: Maybe it's more efficient to do by the suspending thread?
|
||||||
thread.Publish();
|
thread.Publish();
|
||||||
for (auto* object : mm::ThreadRootSet(thread)) {
|
for (auto* object : mm::ThreadRootSet(thread)) {
|
||||||
if (!isNullOrMarker(object)) {
|
if (!isNullOrMarker(object)) {
|
||||||
@@ -109,10 +137,7 @@ void gc::SingleThreadMarkAndSweep::PerformFullGC() noexcept {
|
|||||||
gc::Mark<MarkTraits>(std::move(graySet));
|
gc::Mark<MarkTraits>(std::move(graySet));
|
||||||
auto finalizerQueue = gc::Sweep<SweepTraits>(mm::GlobalData::Instance().objectFactory());
|
auto finalizerQueue = gc::Sweep<SweepTraits>(mm::GlobalData::Instance().objectFactory());
|
||||||
|
|
||||||
running_ = false;
|
mm::ResumeThreads();
|
||||||
|
|
||||||
// TODO: These will actually need to be run on a separate thread.
|
return finalizerQueue;
|
||||||
// TODO: This probably should check for the existence of runtime itself, but unit tests initialize only memory.
|
|
||||||
RuntimeAssert(mm::ThreadRegistry::Instance().CurrentThreadData() != nullptr, "Finalizers need a Kotlin runtime");
|
|
||||||
finalizerQueue.Finalize();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,20 @@
|
|||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include "ObjectFactory.hpp"
|
||||||
#include "Types.h"
|
#include "Types.h"
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
|
|
||||||
namespace kotlin {
|
namespace kotlin {
|
||||||
|
|
||||||
|
namespace mm {
|
||||||
|
class ThreadData;
|
||||||
|
}
|
||||||
|
|
||||||
namespace gc {
|
namespace gc {
|
||||||
|
|
||||||
// Stop-the-world Mark-and-Sweep for a single mutator
|
// Stop-the-world Mark-and-Sweep that runs on mutator threads. Can support targets that do not have threads.
|
||||||
|
// TODO: Rename it away from SingleThreadMarkAndSweep, but keep it STMS.
|
||||||
class SingleThreadMarkAndSweep : private Pinned {
|
class SingleThreadMarkAndSweep : private Pinned {
|
||||||
public:
|
public:
|
||||||
class ObjectData {
|
class ObjectData {
|
||||||
@@ -36,7 +43,7 @@ public:
|
|||||||
public:
|
public:
|
||||||
using ObjectData = SingleThreadMarkAndSweep::ObjectData;
|
using ObjectData = SingleThreadMarkAndSweep::ObjectData;
|
||||||
|
|
||||||
explicit ThreadData(SingleThreadMarkAndSweep& gc) noexcept : gc_(gc) {}
|
explicit ThreadData(SingleThreadMarkAndSweep& gc, mm::ThreadData& threadData) noexcept : gc_(gc), threadData_(threadData) {}
|
||||||
~ThreadData() = default;
|
~ThreadData() = default;
|
||||||
|
|
||||||
void SafePointFunctionEpilogue() noexcept;
|
void SafePointFunctionEpilogue() noexcept;
|
||||||
@@ -49,7 +56,10 @@ public:
|
|||||||
void OnOOM(size_t size) noexcept;
|
void OnOOM(size_t size) noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void SafePointRegular(size_t weight) noexcept;
|
||||||
|
|
||||||
SingleThreadMarkAndSweep& gc_;
|
SingleThreadMarkAndSweep& gc_;
|
||||||
|
mm::ThreadData& threadData_;
|
||||||
size_t allocatedBytes_ = 0;
|
size_t allocatedBytes_ = 0;
|
||||||
size_t safePointsCounter_ = 0;
|
size_t safePointsCounter_ = 0;
|
||||||
};
|
};
|
||||||
@@ -67,9 +77,7 @@ public:
|
|||||||
bool GetAutoTune() noexcept { return autoTune_; }
|
bool GetAutoTune() noexcept { return autoTune_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void PerformFullGC() noexcept;
|
mm::ObjectFactory<SingleThreadMarkAndSweep>::FinalizerQueue PerformFullGC() noexcept;
|
||||||
|
|
||||||
bool running_ = false;
|
|
||||||
|
|
||||||
size_t threshold_ = 1000;
|
size_t threshold_ = 1000;
|
||||||
size_t allocationThresholdBytes_ = 10000;
|
size_t allocationThresholdBytes_ = 10000;
|
||||||
|
|||||||
@@ -5,6 +5,11 @@
|
|||||||
|
|
||||||
#include "SingleThreadMarkAndSweep.hpp"
|
#include "SingleThreadMarkAndSweep.hpp"
|
||||||
|
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <future>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include "gmock/gmock.h"
|
#include "gmock/gmock.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
@@ -58,6 +63,10 @@ public:
|
|||||||
mm::AllocateObject(&threadData, typeHolder.typeInfo(), &location_);
|
mm::AllocateObject(&threadData, typeHolder.typeInfo(), &location_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GlobalObjectHolder(mm::ThreadData& threadData, ObjHeader* object) : location_(object) {
|
||||||
|
mm::GlobalsRegistry::Instance().RegisterStorageForGlobal(&threadData, &location_);
|
||||||
|
}
|
||||||
|
|
||||||
ObjHeader* header() { return location_; }
|
ObjHeader* header() { return location_; }
|
||||||
|
|
||||||
test_support::Object<Payload>& operator*() { return test_support::Object<Payload>::FromObjHeader(location_); }
|
test_support::Object<Payload>& operator*() { return test_support::Object<Payload>::FromObjHeader(location_); }
|
||||||
@@ -126,6 +135,7 @@ class StackObjectHolder : private Pinned {
|
|||||||
public:
|
public:
|
||||||
explicit StackObjectHolder(mm::ThreadData& threadData) { mm::AllocateObject(&threadData, typeHolder.typeInfo(), holder_.slot()); }
|
explicit StackObjectHolder(mm::ThreadData& threadData) { mm::AllocateObject(&threadData, typeHolder.typeInfo(), holder_.slot()); }
|
||||||
explicit StackObjectHolder(test_support::Object<Payload>& object) : holder_(object.header()) {}
|
explicit StackObjectHolder(test_support::Object<Payload>& object) : holder_(object.header()) {}
|
||||||
|
explicit StackObjectHolder(ObjHeader* object) : holder_(object) {}
|
||||||
|
|
||||||
ObjHeader* header() { return holder_.obj(); }
|
ObjHeader* header() { return holder_.obj(); }
|
||||||
|
|
||||||
@@ -629,3 +639,404 @@ TEST_F(SingleThreadMarkAndSweepTest, SameObjectInRootSet) {
|
|||||||
EXPECT_THAT(GetColor(object.header()), Color::kWhite);
|
EXPECT_THAT(GetColor(object.header()), Color::kWhite);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class Mutator : private Pinned {
|
||||||
|
public:
|
||||||
|
Mutator() : thread_(&Mutator::RunLoop, this) {}
|
||||||
|
|
||||||
|
~Mutator() {
|
||||||
|
{
|
||||||
|
std::unique_lock guard(queueMutex_);
|
||||||
|
shutdownRequested_ = true;
|
||||||
|
}
|
||||||
|
queueCV_.notify_one();
|
||||||
|
thread_.join();
|
||||||
|
RuntimeAssert(queue_.empty(), "The queue must be empty, has size=%zu", queue_.size());
|
||||||
|
RuntimeAssert(memory_ == nullptr, "Memory must have been deinitialized");
|
||||||
|
RuntimeAssert(stackRoots_.empty(), "Stack roots must be empty, has size=%zu", stackRoots_.size());
|
||||||
|
RuntimeAssert(globalRoots_.empty(), "Global roots must be empty, has size=%zu", globalRoots_.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
[[nodiscard]] std::future<void> Execute(F&& f) {
|
||||||
|
std::packaged_task<void()> task([this, f = std::forward<F>(f)]() { f(*memory_->memoryState()->GetThreadData(), *this); });
|
||||||
|
auto future = task.get_future();
|
||||||
|
{
|
||||||
|
std::unique_lock guard(queueMutex_);
|
||||||
|
queue_.push_back(std::move(task));
|
||||||
|
}
|
||||||
|
queueCV_.notify_one();
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
StackObjectHolder& AddStackRoot() {
|
||||||
|
RuntimeAssert(std::this_thread::get_id() == thread_.get_id(), "AddStackRoot can only be called in the mutator thread");
|
||||||
|
auto holder = make_unique<StackObjectHolder>(*memory_->memoryState()->GetThreadData());
|
||||||
|
auto& holderRef = *holder;
|
||||||
|
stackRoots_.push_back(std::move(holder));
|
||||||
|
return holderRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
StackObjectHolder& AddStackRoot(ObjHeader* object) {
|
||||||
|
RuntimeAssert(std::this_thread::get_id() == thread_.get_id(), "AddStackRoot can only be called in the mutator thread");
|
||||||
|
auto holder = make_unique<StackObjectHolder>(object);
|
||||||
|
auto& holderRef = *holder;
|
||||||
|
stackRoots_.push_back(std::move(holder));
|
||||||
|
return holderRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalObjectHolder& AddGlobalRoot() {
|
||||||
|
RuntimeAssert(std::this_thread::get_id() == thread_.get_id(), "AddGlobalRoot can only be called in the mutator thread");
|
||||||
|
auto holder = make_unique<GlobalObjectHolder>(*memory_->memoryState()->GetThreadData());
|
||||||
|
auto& holderRef = *holder;
|
||||||
|
globalRoots_.push_back(std::move(holder));
|
||||||
|
return holderRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalObjectHolder& AddGlobalRoot(ObjHeader* object) {
|
||||||
|
RuntimeAssert(std::this_thread::get_id() == thread_.get_id(), "AddGlobalRoot can only be called in the mutator thread");
|
||||||
|
auto holder = make_unique<GlobalObjectHolder>(*memory_->memoryState()->GetThreadData(), object);
|
||||||
|
auto& holderRef = *holder;
|
||||||
|
globalRoots_.push_back(std::move(holder));
|
||||||
|
return holderRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
KStdVector<ObjHeader*> Alive() { return ::Alive(*memory_->memoryState()->GetThreadData()); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void RunLoop() {
|
||||||
|
memory_ = make_unique<ScopedMemoryInit>();
|
||||||
|
AssertThreadState(memory_->memoryState(), ThreadState::kRunnable);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
std::packaged_task<void()> task;
|
||||||
|
{
|
||||||
|
std::unique_lock guard(queueMutex_);
|
||||||
|
queueCV_.wait(guard, [this]() { return !queue_.empty() || shutdownRequested_; });
|
||||||
|
if (shutdownRequested_) {
|
||||||
|
globalRoots_.clear();
|
||||||
|
stackRoots_.clear();
|
||||||
|
memory_.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
task = std::move(queue_.front());
|
||||||
|
queue_.pop_front();
|
||||||
|
}
|
||||||
|
task();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KStdUniquePtr<ScopedMemoryInit> memory_;
|
||||||
|
|
||||||
|
// TODO: Consider full runtime init instead, and interact with initialized worker
|
||||||
|
std::condition_variable queueCV_;
|
||||||
|
std::mutex queueMutex_;
|
||||||
|
KStdDeque<std::packaged_task<void()>> queue_;
|
||||||
|
bool shutdownRequested_ = false;
|
||||||
|
std::thread thread_;
|
||||||
|
|
||||||
|
KStdVector<KStdUniquePtr<GlobalObjectHolder>> globalRoots_;
|
||||||
|
KStdVector<KStdUniquePtr<StackObjectHolder>> stackRoots_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_F(SingleThreadMarkAndSweepTest, MultipleMutatorsCollect) {
|
||||||
|
KStdVector<Mutator> mutators(kDefaultThreadCount);
|
||||||
|
KStdVector<ObjHeader*> globals(kDefaultThreadCount);
|
||||||
|
KStdVector<ObjHeader*> locals(kDefaultThreadCount);
|
||||||
|
KStdVector<ObjHeader*> reachables(kDefaultThreadCount);
|
||||||
|
|
||||||
|
auto expandRootSet = [&globals, &locals, &reachables](mm::ThreadData& threadData, Mutator& mutator, int i) {
|
||||||
|
auto& global = mutator.AddGlobalRoot();
|
||||||
|
auto& local = mutator.AddStackRoot();
|
||||||
|
auto& reachable = AllocateObject(threadData);
|
||||||
|
AllocateObject(threadData);
|
||||||
|
local->field1 = reachable.header();
|
||||||
|
globals[i] = global.header();
|
||||||
|
locals[i] = local.header();
|
||||||
|
reachables[i] = reachable.header();
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < kDefaultThreadCount; ++i) {
|
||||||
|
mutators[i]
|
||||||
|
.Execute([i, expandRootSet](mm::ThreadData& threadData, Mutator& mutator) { expandRootSet(threadData, mutator, i); })
|
||||||
|
.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
KStdVector<std::future<void>> gcFutures(kDefaultThreadCount);
|
||||||
|
|
||||||
|
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().PerformFullGC(); });
|
||||||
|
|
||||||
|
// Spin until thread suspension is requested.
|
||||||
|
while (!mm::IsThreadSuspensionRequested()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < kDefaultThreadCount; ++i) {
|
||||||
|
gcFutures[i] =
|
||||||
|
mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().SafePointFunctionEpilogue(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& future : gcFutures) {
|
||||||
|
future.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
KStdVector<ObjHeader*> expectedAlive;
|
||||||
|
for (auto& global : globals) {
|
||||||
|
expectedAlive.push_back(global);
|
||||||
|
}
|
||||||
|
for (auto& local : locals) {
|
||||||
|
expectedAlive.push_back(local);
|
||||||
|
}
|
||||||
|
for (auto& reachable : reachables) {
|
||||||
|
expectedAlive.push_back(reachable);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& mutator : mutators) {
|
||||||
|
EXPECT_THAT(mutator.Alive(), testing::UnorderedElementsAreArray(expectedAlive));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SingleThreadMarkAndSweepTest, MultipleMutatorsAllCollect) {
|
||||||
|
KStdVector<Mutator> mutators(kDefaultThreadCount);
|
||||||
|
KStdVector<ObjHeader*> globals(kDefaultThreadCount);
|
||||||
|
KStdVector<ObjHeader*> locals(kDefaultThreadCount);
|
||||||
|
KStdVector<ObjHeader*> reachables(kDefaultThreadCount);
|
||||||
|
|
||||||
|
auto expandRootSet = [&globals, &locals, &reachables](mm::ThreadData& threadData, Mutator& mutator, int i) {
|
||||||
|
auto& global = mutator.AddGlobalRoot();
|
||||||
|
auto& local = mutator.AddStackRoot();
|
||||||
|
auto& reachable = AllocateObject(threadData);
|
||||||
|
AllocateObject(threadData);
|
||||||
|
local->field1 = reachable.header();
|
||||||
|
globals[i] = global.header();
|
||||||
|
locals[i] = local.header();
|
||||||
|
reachables[i] = reachable.header();
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < kDefaultThreadCount; ++i) {
|
||||||
|
mutators[i]
|
||||||
|
.Execute([i, expandRootSet](mm::ThreadData& threadData, Mutator& mutator) { expandRootSet(threadData, mutator, i); })
|
||||||
|
.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
KStdVector<std::future<void>> gcFutures(kDefaultThreadCount);
|
||||||
|
|
||||||
|
// TODO: Maybe check that only one GC is performed.
|
||||||
|
for (int i = 0; i < kDefaultThreadCount; ++i) {
|
||||||
|
gcFutures[i] = mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().PerformFullGC(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& future : gcFutures) {
|
||||||
|
future.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
KStdVector<ObjHeader*> expectedAlive;
|
||||||
|
for (auto& global : globals) {
|
||||||
|
expectedAlive.push_back(global);
|
||||||
|
}
|
||||||
|
for (auto& local : locals) {
|
||||||
|
expectedAlive.push_back(local);
|
||||||
|
}
|
||||||
|
for (auto& reachable : reachables) {
|
||||||
|
expectedAlive.push_back(reachable);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& mutator : mutators) {
|
||||||
|
EXPECT_THAT(mutator.Alive(), testing::UnorderedElementsAreArray(expectedAlive));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SingleThreadMarkAndSweepTest, MultipleMutatorsAddToRootSetAfterCollectionRequested) {
|
||||||
|
KStdVector<Mutator> mutators(kDefaultThreadCount);
|
||||||
|
KStdVector<ObjHeader*> globals(kDefaultThreadCount);
|
||||||
|
KStdVector<ObjHeader*> locals(kDefaultThreadCount);
|
||||||
|
KStdVector<ObjHeader*> reachables(kDefaultThreadCount);
|
||||||
|
|
||||||
|
auto allocateInHeap = [&globals, &locals, &reachables](mm::ThreadData& threadData, Mutator& mutator, int i) {
|
||||||
|
auto& global = AllocateObject(threadData);
|
||||||
|
auto& local = AllocateObject(threadData);
|
||||||
|
auto& reachable = AllocateObject(threadData);
|
||||||
|
AllocateObject(threadData);
|
||||||
|
|
||||||
|
local->field1 = reachable.header();
|
||||||
|
|
||||||
|
globals[i] = global.header();
|
||||||
|
locals[i] = local.header();
|
||||||
|
reachables[i] = reachable.header();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto expandRootSet = [&globals, &locals](mm::ThreadData& threadData, Mutator& mutator, int i) {
|
||||||
|
mutator.AddGlobalRoot(globals[i]);
|
||||||
|
mutator.AddStackRoot(locals[i]);
|
||||||
|
};
|
||||||
|
|
||||||
|
mutators[0]
|
||||||
|
.Execute([expandRootSet, allocateInHeap](mm::ThreadData& threadData, Mutator& mutator) {
|
||||||
|
allocateInHeap(threadData, mutator, 0);
|
||||||
|
expandRootSet(threadData, mutator, 0);
|
||||||
|
})
|
||||||
|
.wait();
|
||||||
|
|
||||||
|
// Allocate everything in heap before scheduling the GC.
|
||||||
|
for (int i = 1; i < kDefaultThreadCount; ++i) {
|
||||||
|
mutators[i]
|
||||||
|
.Execute([allocateInHeap, i](mm::ThreadData& threadData, Mutator& mutator) { allocateInHeap(threadData, mutator, i); })
|
||||||
|
.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
KStdVector<std::future<void>> gcFutures(kDefaultThreadCount);
|
||||||
|
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().PerformFullGC(); });
|
||||||
|
|
||||||
|
// Spin until thread suspension is requested.
|
||||||
|
while (!mm::IsThreadSuspensionRequested()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < kDefaultThreadCount; ++i) {
|
||||||
|
gcFutures[i] = mutators[i].Execute([i, expandRootSet](mm::ThreadData& threadData, Mutator& mutator) {
|
||||||
|
expandRootSet(threadData, mutator, i);
|
||||||
|
threadData.gc().SafePointFunctionEpilogue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& future : gcFutures) {
|
||||||
|
future.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
KStdVector<ObjHeader*> expectedAlive;
|
||||||
|
for (auto& global : globals) {
|
||||||
|
expectedAlive.push_back(global);
|
||||||
|
}
|
||||||
|
for (auto& local : locals) {
|
||||||
|
expectedAlive.push_back(local);
|
||||||
|
}
|
||||||
|
for (auto& reachable : reachables) {
|
||||||
|
expectedAlive.push_back(reachable);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& mutator : mutators) {
|
||||||
|
EXPECT_THAT(mutator.Alive(), testing::UnorderedElementsAreArray(expectedAlive));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SingleThreadMarkAndSweepTest, CrossThreadReference) {
|
||||||
|
KStdVector<Mutator> mutators(kDefaultThreadCount);
|
||||||
|
KStdVector<ObjHeader*> globals(kDefaultThreadCount);
|
||||||
|
KStdVector<ObjHeader*> locals(kDefaultThreadCount);
|
||||||
|
KStdVector<ObjHeader*> reachables(2 * kDefaultThreadCount);
|
||||||
|
|
||||||
|
auto expandRootSet = [&globals, &locals, &reachables](mm::ThreadData& threadData, Mutator& mutator, int i) {
|
||||||
|
auto& global = mutator.AddGlobalRoot();
|
||||||
|
auto& local = mutator.AddStackRoot();
|
||||||
|
auto& reachable1 = AllocateObject(threadData);
|
||||||
|
auto& reachable2 = AllocateObject(threadData);
|
||||||
|
globals[i] = global.header();
|
||||||
|
locals[i] = local.header();
|
||||||
|
reachables[2 * i] = reachable1.header();
|
||||||
|
reachables[2 * i + 1] = reachable2.header();
|
||||||
|
|
||||||
|
// Expected to be run consequtively, so `reachables` for `j < i` are set.
|
||||||
|
if (i != 0) {
|
||||||
|
global->field1 = reachables[2 * (i - 1)];
|
||||||
|
local->field1 = reachables[2 * (i - 1) + 1];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < kDefaultThreadCount; ++i) {
|
||||||
|
// `expandRootSet` is expected to be run consequtively for each thread, so `.wait()` is required below.
|
||||||
|
mutators[i]
|
||||||
|
.Execute([i, expandRootSet](mm::ThreadData& threadData, Mutator& mutator) { expandRootSet(threadData, mutator, i); })
|
||||||
|
.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
KStdVector<std::future<void>> gcFutures(kDefaultThreadCount);
|
||||||
|
|
||||||
|
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().PerformFullGC(); });
|
||||||
|
|
||||||
|
// Spin until thread suspension is requested.
|
||||||
|
while (!mm::IsThreadSuspensionRequested()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < kDefaultThreadCount; ++i) {
|
||||||
|
gcFutures[i] =
|
||||||
|
mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().SafePointFunctionEpilogue(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& future : gcFutures) {
|
||||||
|
future.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
KStdVector<ObjHeader*> expectedAlive;
|
||||||
|
for (auto& global : globals) {
|
||||||
|
expectedAlive.push_back(global);
|
||||||
|
}
|
||||||
|
for (auto& local : locals) {
|
||||||
|
expectedAlive.push_back(local);
|
||||||
|
}
|
||||||
|
// The last two are in fact unreachable. Their absence allows us to check that GC was in fact performed.
|
||||||
|
reachables.pop_back();
|
||||||
|
reachables.pop_back();
|
||||||
|
for (auto& reachable : reachables) {
|
||||||
|
expectedAlive.push_back(reachable);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& mutator : mutators) {
|
||||||
|
EXPECT_THAT(mutator.Alive(), testing::UnorderedElementsAreArray(expectedAlive));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SingleThreadMarkAndSweepTest, MultipleMutatorsWeaks) {
|
||||||
|
KStdVector<Mutator> mutators(kDefaultThreadCount);
|
||||||
|
ObjHeader* globalRoot = nullptr;
|
||||||
|
WeakCounter* weak = nullptr;
|
||||||
|
|
||||||
|
mutators[0]
|
||||||
|
.Execute([&weak, &globalRoot](mm::ThreadData& threadData, Mutator& mutator) {
|
||||||
|
auto& global = mutator.AddGlobalRoot();
|
||||||
|
|
||||||
|
auto& object = AllocateObject(threadData);
|
||||||
|
auto& objectWeak = ([&threadData, &object]() -> WeakCounter& {
|
||||||
|
ObjHolder holder;
|
||||||
|
return InstallWeakCounter(threadData, object.header(), holder.slot());
|
||||||
|
})();
|
||||||
|
global->field1 = objectWeak.header();
|
||||||
|
weak = &objectWeak;
|
||||||
|
globalRoot = global.header();
|
||||||
|
})
|
||||||
|
.wait();
|
||||||
|
|
||||||
|
// Make sure all mutators are initialized.
|
||||||
|
for (int i = 1; i < kDefaultThreadCount; ++i) {
|
||||||
|
mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) {}).wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
KStdVector<std::future<void>> gcFutures(kDefaultThreadCount);
|
||||||
|
|
||||||
|
gcFutures[0] = mutators[0].Execute([weak](mm::ThreadData& threadData, Mutator& mutator) {
|
||||||
|
threadData.gc().PerformFullGC();
|
||||||
|
EXPECT_THAT((*weak)->referred, nullptr);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Spin until thread suspension is requested.
|
||||||
|
while (!mm::IsThreadSuspensionRequested()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < kDefaultThreadCount; ++i) {
|
||||||
|
gcFutures[i] = mutators[i].Execute([weak](mm::ThreadData& threadData, Mutator& mutator) {
|
||||||
|
threadData.gc().SafePointFunctionEpilogue();
|
||||||
|
EXPECT_THAT((*weak)->referred, nullptr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& future : gcFutures) {
|
||||||
|
future.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& mutator : mutators) {
|
||||||
|
EXPECT_THAT(mutator.Alive(), testing::UnorderedElementsAre(globalRoot, weak->header()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Attaching new threads while GC is in progress.
|
||||||
|
|||||||
@@ -79,7 +79,10 @@ void mm::ExtraObjectData::ClearWeakReferenceCounter() noexcept {
|
|||||||
if (!HasWeakReferenceCounter()) return;
|
if (!HasWeakReferenceCounter()) return;
|
||||||
|
|
||||||
WeakReferenceCounterClear(weakReferenceCounter_);
|
WeakReferenceCounterClear(weakReferenceCounter_);
|
||||||
mm::SetHeapRef(&weakReferenceCounter_, nullptr);
|
// Not using `mm::SetHeapRef here`, because this code is called during sweep phase by the GC thread,
|
||||||
|
// and so cannot affect marking.
|
||||||
|
// TODO: Asserts on the above?
|
||||||
|
weakReferenceCounter_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
mm::ExtraObjectData::~ExtraObjectData() {
|
mm::ExtraObjectData::~ExtraObjectData() {
|
||||||
|
|||||||
@@ -515,6 +515,7 @@ public:
|
|||||||
auto* heapObject = new (node.Data()) HeapObjHeader();
|
auto* heapObject = new (node.Data()) HeapObjHeader();
|
||||||
auto* object = &heapObject->object;
|
auto* object = &heapObject->object;
|
||||||
object->typeInfoOrMeta_ = const_cast<TypeInfo*>(typeInfo);
|
object->typeInfoOrMeta_ = const_cast<TypeInfo*>(typeInfo);
|
||||||
|
// TODO: Consider supporting TF_IMMUTABLE: mark instance as frozen upon creation.
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,6 +529,7 @@ public:
|
|||||||
auto* array = &heapArray->array;
|
auto* array = &heapArray->array;
|
||||||
array->typeInfoOrMeta_ = const_cast<TypeInfo*>(typeInfo);
|
array->typeInfoOrMeta_ = const_cast<TypeInfo*>(typeInfo);
|
||||||
array->count_ = count;
|
array->count_ = count;
|
||||||
|
// TODO: Consider supporting TF_IMMUTABLE: mark instance as frozen upon creation.
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public:
|
|||||||
threadId_(threadId),
|
threadId_(threadId),
|
||||||
globalsThreadQueue_(GlobalsRegistry::Instance()),
|
globalsThreadQueue_(GlobalsRegistry::Instance()),
|
||||||
stableRefThreadQueue_(StableRefRegistry::Instance()),
|
stableRefThreadQueue_(StableRefRegistry::Instance()),
|
||||||
gc_(GlobalData::Instance().gc()),
|
gc_(GlobalData::Instance().gc(), *this),
|
||||||
objectFactoryThreadQueue_(GlobalData::Instance().objectFactory(), gc_),
|
objectFactoryThreadQueue_(GlobalData::Instance().objectFactory(), gc_),
|
||||||
suspensionData_(ThreadState::kRunnable) {}
|
suspensionData_(ThreadState::kRunnable) {}
|
||||||
|
|
||||||
|
|||||||
@@ -42,4 +42,4 @@ ALWAYS_INLINE void kotlin::AssertThreadState(MemoryState* thread, std::initializ
|
|||||||
|
|
||||||
ThreadState kotlin::GetThreadState(MemoryState* thread) noexcept {
|
ThreadState kotlin::GetThreadState(MemoryState* thread) noexcept {
|
||||||
return thread->GetThreadData()->state();
|
return thread->GetThreadData()->state();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ kotlin::test_support::TypeInfoHolder theObjCObjectWrapperTypeInfoHolder{
|
|||||||
kotlin::test_support::TypeInfoHolder::ObjectBuilder<EmptyPayload>()};
|
kotlin::test_support::TypeInfoHolder::ObjectBuilder<EmptyPayload>()};
|
||||||
kotlin::test_support::TypeInfoHolder theOpaqueFunctionTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ObjectBuilder<EmptyPayload>()};
|
kotlin::test_support::TypeInfoHolder theOpaqueFunctionTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ObjectBuilder<EmptyPayload>()};
|
||||||
kotlin::test_support::TypeInfoHolder theShortArrayTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder<KShort>()};
|
kotlin::test_support::TypeInfoHolder theShortArrayTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder<KShort>()};
|
||||||
kotlin::test_support::TypeInfoHolder theStringTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder<KChar>()};
|
kotlin::test_support::TypeInfoHolder theStringTypeInfoHolder{
|
||||||
|
kotlin::test_support::TypeInfoHolder::ArrayBuilder<KChar>().addFlag(TF_IMMUTABLE)};
|
||||||
kotlin::test_support::TypeInfoHolder theThrowableTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ObjectBuilder<EmptyPayload>()};
|
kotlin::test_support::TypeInfoHolder theThrowableTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ObjectBuilder<EmptyPayload>()};
|
||||||
kotlin::test_support::TypeInfoHolder theUnitTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ObjectBuilder<EmptyPayload>()};
|
kotlin::test_support::TypeInfoHolder theUnitTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ObjectBuilder<EmptyPayload>()};
|
||||||
kotlin::test_support::TypeInfoHolder theWorkerBoundReferenceTypeInfoHolder{
|
kotlin::test_support::TypeInfoHolder theWorkerBoundReferenceTypeInfoHolder{
|
||||||
|
|||||||
Reference in New Issue
Block a user