diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 700d72b7cd6..d86b263dcdf 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -696,6 +696,12 @@ task atomic0(type: RunKonanTest) { source = "runtime/workers/atomic0.kt" } +task lazy0(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // Workers need pthreads. + goldValue = "OK\n" + source = "runtime/workers/lazy0.kt" +} + task enumIdentity(type: RunKonanTest) { disabled = (project.testTarget == 'wasm32') // Workers need pthreads. goldValue = "true\n" diff --git a/backend.native/tests/runtime/workers/lazy0.kt b/backend.native/tests/runtime/workers/lazy0.kt new file mode 100644 index 00000000000..db7baaee5cd --- /dev/null +++ b/backend.native/tests/runtime/workers/lazy0.kt @@ -0,0 +1,38 @@ +package runtime.workers.lazy0 + +import kotlin.test.* + +import konan.worker.* + +data class Data(val x: Int, val y: String) + +object Immutable { + val x by atomicLazy { + 42 + } +} + +object Immutable2 { + val y by atomicLazy { + Data(239, "Kotlin") + } +} + +@Test fun runTest() { + assertEquals(42, Immutable.x) + + val COUNT = 5 + val workers = Array(COUNT, { _ -> startWorker()}) + + val set = mutableSetOf() + for (attempt in 1 .. 3) { + val futures = Array(workers.size, { workerIndex -> + workers[workerIndex].schedule(TransferMode.CHECKED, { "" }) { _ -> Immutable2.y } + }) + futures.forEach { set += it.result() } + } + assertEquals(set.size, 1) + assertEquals(set.single(), Immutable2.y) + + println("OK") +} diff --git a/runtime/src/main/kotlin/konan/worker/Atomics.kt b/runtime/src/main/kotlin/konan/worker/Atomics.kt index 313a1ce4ff7..68b4323e4c5 100644 --- a/runtime/src/main/kotlin/konan/worker/Atomics.kt +++ b/runtime/src/main/kotlin/konan/worker/Atomics.kt @@ -149,4 +149,50 @@ class AtomicReference(private var value: T? = null) { */ @SymbolName("Kotlin_AtomicReference_get") external public fun get(): T? -} \ No newline at end of file +} + +internal object UNINITIALIZED + +internal object INITIALIZING + +@Frozen +internal class AtomicLazyImpl(initializer: () -> T) : Lazy { + private val initializer_ = AtomicReference?>(initializer.freeze()) + private val value_ = AtomicReference(UNINITIALIZED) + + override val value: T + get() { + if (value_.compareAndSwap(UNINITIALIZED, INITIALIZING) === UNINITIALIZED) { + // We execute exclusively here. + val ctor = initializer_.get() + if (ctor != null && initializer_.compareAndSwap(ctor, null) === ctor) { + value_.compareAndSwap(INITIALIZING, ctor().freeze()) + } else { + // Something wrong. + assert(false) + } + } + var result: Any? + do { + result = value_.get() + } while (result === INITIALIZING) + + assert(result !== UNINITIALIZED && result != INITIALIZING) + @Suppress("UNCHECKED_CAST") + return result as T + } + + // Racy! + override fun isInitialized(): Boolean = value_.get() !== UNINITIALIZED + + override fun toString(): String = if (isInitialized()) + value_.get().toString() else "Lazy value not initialized yet." +} + +/** + * Atomic lazy initializer, could be used in frozen objects, freezes initializing lambda, + * so use very carefully. Also, as with other uses of an @AtomicReference may potentially + * leak memory, so it is recommended to use `atomicLazy` in cases of objects living forever, + * such as object signletons, or in cases where it's guaranteed not to have cyclical garbage. + */ +public fun atomicLazy(initializer: () -> T): Lazy = AtomicLazyImpl(initializer) \ No newline at end of file