diff --git a/backend.native/tests/runtime/workers/atomic0.kt b/backend.native/tests/runtime/workers/atomic0.kt index 17d55d23d5c..a0db629698a 100644 --- a/backend.native/tests/runtime/workers/atomic0.kt +++ b/backend.native/tests/runtime/workers/atomic0.kt @@ -103,6 +103,24 @@ fun test6() { assertEquals(239L, long.value) } + +fun test7() { + val ref = FreezableAtomicReference(Array(1) { "hey" }) + ref.value[0] = "ho" + assertEquals(ref.value[0], "ho") + ref.value = Array(1) { "po" } + assertEquals(ref.value[0], "po") + ref.freeze() + assertFailsWith { + ref.value = Array(1) { "no" } + } + assertFailsWith { + ref.value[0] = "go" + } + ref.value = Array(1) { "so" }.freeze() + assertEquals(ref.value[0], "so") +} + @Test fun runTest() { val COUNT = 20 val workers = Array(COUNT, { _ -> Worker.start()}) @@ -113,9 +131,11 @@ fun test6() { test4() test5() test6() + test7() workers.forEach { it.requestTermination().result } println("OK") -} \ No newline at end of file +} + diff --git a/runtime/src/main/kotlin/kotlin/native/concurrent/Atomics.kt b/runtime/src/main/kotlin/kotlin/native/concurrent/Atomics.kt index f3f4db9f900..71dec6ebbde 100644 --- a/runtime/src/main/kotlin/kotlin/native/concurrent/Atomics.kt +++ b/runtime/src/main/kotlin/kotlin/native/concurrent/Atomics.kt @@ -235,6 +235,7 @@ public class AtomicReference(private var value_: T) { /** * Compares value with [expected] and replaces it with [new] value if values matches. + * Note that comparison is identity-based, not value-based. * If [new] value is not null, it must be frozen or permanent object. * * @param expected the expected value @@ -247,6 +248,7 @@ public class AtomicReference(private var value_: T) { /** * Compares value with [expected] and replaces it with [new] value if values matches. + * Note that comparison is identity-based, not value-based. * * @param expected the expected value * @param new the new value @@ -269,4 +271,87 @@ public class AtomicReference(private var value_: T) { @SymbolName("Kotlin_AtomicReference_get") private external fun getImpl(): Any? +} + + +/** + * An atomic reference to a Kotlin object. Can be used in concurrent scenarious, but must be frozen first, + * otherwise behaves as regular box for the value. + */ +@NoReorderFields +public class FreezableAtomicReference(private var value_: T) { + // A spinlock to fix potential ARC race. + private var lock: Int = 0 + + /** + * The referenced value. + * Gets the value or sets the [new] value. If [new] value is not null, + * and `this` is frozen - it must be frozen or permanent object. + * + * @throws InvalidMutabilityException if the value is not frozen or a permanent object + */ + public var value: T + get() = @Suppress("UNCHECKED_CAST")(getImpl() as T) + set(new) { + if (this.isFrozen) + setImpl(new) + else + value_ = new + } + + /** + * Compares value with [expected] and replaces it with [new] value if values matches. + * If [new] value is not null and object is frozen, it must be frozen or permanent object. + * + * @param expected the expected value + * @param new the new value + * @throws InvalidMutabilityException if the value is not frozen or a permanent object + * @return the old value + */ + public fun compareAndSwap(expected: T, new: T): T { + return if (this.isFrozen) @Suppress("UNCHECKED_CAST")(compareAndSwapImpl(expected, new) as T) else { + val old = value_ + if (old === expected) value_ = new + old + } + } + + /** + * Compares value with [expected] and replaces it with [new] value if values matches. + * Note that comparison is identity-based, not value-based. + * + * @param expected the expected value + * @param new the new value + * @return true if successful + */ + public fun compareAndSet(expected: T, new: T): Boolean { + if (this.isFrozen) return compareAndSetImpl(expected, new) + val old = value_ + if (old === expected) { + value_ = new + return true + } else { + return false + } + } + + /** + * Returns the string representation of this object. + * + * @return string representation of this object + */ + public override fun toString(): String = "Freezable atomic reference to $value" + + // Implementation details. + @SymbolName("Kotlin_AtomicReference_set") + private external fun setImpl(new: Any?): Unit + + @SymbolName("Kotlin_AtomicReference_get") + private external fun getImpl(): Any? + + @SymbolName("Kotlin_AtomicReference_compareAndSwap") + private external fun compareAndSwapImpl(expected: Any?, new: Any?): Any? + + @SymbolName("Kotlin_AtomicReference_compareAndSet") + private external fun compareAndSetImpl(expected: Any?, new: Any?): Boolean } \ No newline at end of file