From 061ea53853fb96737bcbdf9a8e698b299c29d975 Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Tue, 14 Aug 2018 15:45:10 +0300 Subject: [PATCH] Make lazy freezing-aware. (#1878) --- backend.native/tests/runtime/workers/lazy0.kt | 53 +++++++++++-- runtime/src/main/kotlin/kotlin/Lazy.kt | 4 +- .../kotlin/kotlin/native/worker/Atomics.kt | 2 +- .../main/kotlin/kotlin/native/worker/Lazy.kt | 75 +++++++++++++++++++ 4 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 runtime/src/main/kotlin/kotlin/native/worker/Lazy.kt diff --git a/backend.native/tests/runtime/workers/lazy0.kt b/backend.native/tests/runtime/workers/lazy0.kt index 1d370582672..74296f734d8 100644 --- a/backend.native/tests/runtime/workers/lazy0.kt +++ b/backend.native/tests/runtime/workers/lazy0.kt @@ -18,12 +18,16 @@ object Immutable2 { } } -@Test fun runTest() { - assertEquals(42, Immutable.x) - - val COUNT = 5 - val workers = Array(COUNT, { _ -> startWorker()}) +object Immutable3 { + val x by lazy { + var result = 0 + for (i in 1 .. 1000) + result += i + result + } +} +fun testSingleData(workers: Array) { val set = mutableSetOf() for (attempt in 1 .. 3) { val futures = Array(workers.size, { workerIndex -> @@ -33,6 +37,45 @@ object Immutable2 { } assertEquals(set.size, 1) assertEquals(set.single(), Immutable2.y) +} + +fun testFrozenLazy(workers: Array) { + val set = mutableSetOf() + for (attempt in 1 .. 3) { + val futures = Array(workers.size, { workerIndex -> + workers[workerIndex].schedule(TransferMode.CHECKED, { "" }) { _ -> Immutable3.x } + }) + futures.forEach { set += it.result() } + } + assertEquals(1, set.size) + assertEquals(Immutable3.x, set.single()) + assertEquals(1001 * 500, set.single()) +} + +fun testLiquidLazy() { + class L { + val value by lazy { + 17 + } + } + val l1 = L() + for (i in 1 .. 100) + assertEquals(l1.value, 17) + + val l2 = L() + l2.freeze() + for (i in 1 .. 100) + assertEquals(l2.value, 17) +} + +@Test fun runTest() { + assertEquals(42, Immutable.x) + + val COUNT = 5 + val workers = Array(COUNT, { _ -> startWorker()}) + testSingleData(workers) + testFrozenLazy(workers) + testLiquidLazy() println("OK") } diff --git a/runtime/src/main/kotlin/kotlin/Lazy.kt b/runtime/src/main/kotlin/kotlin/Lazy.kt index 1ff38677c0a..d9b4b408995 100644 --- a/runtime/src/main/kotlin/kotlin/Lazy.kt +++ b/runtime/src/main/kotlin/kotlin/Lazy.kt @@ -28,7 +28,7 @@ import kotlin.reflect.KProperty * the returned instance as it may cause accidental deadlock. Also this behavior can be changed in the future. */ @FixmeConcurrency -public actual fun lazy(initializer: () -> T): Lazy = UnsafeLazyImpl(initializer)//SynchronizedLazyImpl(initializer) +public actual fun lazy(initializer: () -> T): Lazy = kotlin.native.worker.FreezeAwareLazyImpl(initializer) /** * Creates a new instance of the [Lazy] that uses the specified initialization function [initializer] @@ -61,4 +61,4 @@ public actual fun lazy(mode: LazyThreadSafetyMode, initializer: () -> T): La */ @FixmeConcurrency @Suppress("UNUSED_PARAMETER") -public actual fun lazy(lock: Any?, initializer: () -> T): Lazy = TODO()//SynchronizedLazyImpl(initializer, lock) +public actual fun lazy(lock: Any?, initializer: () -> T): Lazy = TODO()//SynchronizedLazyImpl(initializer, lock) \ No newline at end of file diff --git a/runtime/src/main/kotlin/kotlin/native/worker/Atomics.kt b/runtime/src/main/kotlin/kotlin/native/worker/Atomics.kt index c5e6f8328a0..fafaa086620 100644 --- a/runtime/src/main/kotlin/kotlin/native/worker/Atomics.kt +++ b/runtime/src/main/kotlin/kotlin/native/worker/Atomics.kt @@ -179,7 +179,7 @@ internal class AtomicLazyImpl(initializer: () -> T) : Lazy { result = value_.get() } while (result === INITIALIZING) - assert(result !== UNINITIALIZED && result != INITIALIZING) + assert(result !== UNINITIALIZED && result !== INITIALIZING) @Suppress("UNCHECKED_CAST") return result as T } diff --git a/runtime/src/main/kotlin/kotlin/native/worker/Lazy.kt b/runtime/src/main/kotlin/kotlin/native/worker/Lazy.kt new file mode 100644 index 00000000000..b154fe5bdad --- /dev/null +++ b/runtime/src/main/kotlin/kotlin/native/worker/Lazy.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kotlin.native.worker + +internal class FreezeAwareLazyImpl(initializer: () -> T) : Lazy { + private var initializer_: (() -> T)? = initializer + private var value_: Any? = UNINITIALIZED + // Objects are not frozen by default on single-threaded targets, shall they? + private val valueFrozen_ = AtomicReference(UNINITIALIZED.freeze()) + + override val value: T + get() { + var result: Any? = value_ + if (isFrozen) { + if (result !== UNINITIALIZED) { + assert(result !== INITIALIZING) + @Suppress("UNCHECKED_CAST") + return result as T + } + // Barrier. + // Note that recursive lazy initializers will hang here. + do { + result = valueFrozen_.get() + } while (result === INITIALIZING) + + if (result !== UNINITIALIZED) { + @Suppress("UNCHECKED_CAST") + return result as T + } + // TODO: maybe release initializer in frozen case. + if (valueFrozen_.compareAndSwap(UNINITIALIZED, INITIALIZING) === UNINITIALIZED) { + result = initializer_!!().freeze() + val old = valueFrozen_.compareAndSwap(INITIALIZING, result) + assert(old === INITIALIZING) + } else { + do { + result = valueFrozen_.get() + } while (result === INITIALIZING) + } + assert(result !== UNINITIALIZED && result !== INITIALIZING) + @Suppress("UNCHECKED_CAST") + return result as T + } else { + if (result === UNINITIALIZED) { + result = initializer_!!() + if (isFrozen) + throw InvalidMutabilityException(this) + value_ = result + initializer_ = null + } + @Suppress("UNCHECKED_CAST") + return result as T + } + } + + // Racy! + override fun isInitialized(): Boolean = (value_ !== UNINITIALIZED) || (valueFrozen_.get() !== UNINITIALIZED) + + override fun toString(): String = if (isInitialized()) + value.toString() else "Lazy value not initialized yet." +} \ No newline at end of file