From 7e201e0553869b8d56ca99c8501ec9d1e37b9791 Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Fri, 15 Jun 2018 12:30:55 +0300 Subject: [PATCH 1/4] Atomic values support. --- backend.native/tests/build.gradle | 6 + .../tests/runtime/workers/atomic0.kt | 41 ++++ runtime/src/main/cpp/Atomic.cpp | 56 ++++++ runtime/src/main/cpp/Atomic.h | 17 +- runtime/src/main/cpp/Worker.cpp | 3 - .../main/kotlin/konan/internal/Annotations.kt | 5 +- .../src/main/kotlin/konan/worker/Atomics.kt | 181 ++++++++++++++++++ 7 files changed, 303 insertions(+), 6 deletions(-) create mode 100644 backend.native/tests/runtime/workers/atomic0.kt create mode 100644 runtime/src/main/cpp/Atomic.cpp create mode 100644 runtime/src/main/kotlin/konan/worker/Atomics.kt diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 8027c3d41f1..0cdd57a45aa 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -671,6 +671,12 @@ task freeze2(type: RunKonanTest) { source = "runtime/workers/freeze2.kt" } +task atomic0(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // Workers need pthreads. + goldValue = "35\n" + "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n" + source = "runtime/workers/atomic0.kt" +} + task enumIdentity(type: RunKonanTest) { disabled = (project.testTarget == 'wasm32') // Workers need pthreads. goldValue = "true\n" diff --git a/backend.native/tests/runtime/workers/atomic0.kt b/backend.native/tests/runtime/workers/atomic0.kt new file mode 100644 index 00000000000..f7a241a091c --- /dev/null +++ b/backend.native/tests/runtime/workers/atomic0.kt @@ -0,0 +1,41 @@ +package runtime.workers.atomic0 + +import kotlin.test.* + +import konan.worker.* + +fun test1(workers: Array) { + val atomic = AtomicInt(15) + val futures = Array(workers.size, { workerIndex -> + workers[workerIndex].schedule(TransferMode.CHECKED, { atomic }) { + input -> input.increment() + } + }) + futures.forEach { + it.result() + } + println(atomic.get()) +} + +fun test2(workers: Array) { + val atomic = AtomicInt(0) + val futures = Array(workers.size, { workerIndex -> + workers[workerIndex].schedule(TransferMode.CHECKED, { atomic to workerIndex }) { + (place, index) -> + while (place.compareAndSwap(index, index + 1) != index) {} + println(index) + } + }) + futures.forEach { + it.result() + } + println(atomic.get()) +} + +@Test fun runTest() { + val COUNT = 20 + val workers = Array(COUNT, { _ -> startWorker()}) + + test1(workers) + test2(workers) +} \ No newline at end of file diff --git a/runtime/src/main/cpp/Atomic.cpp b/runtime/src/main/cpp/Atomic.cpp new file mode 100644 index 00000000000..481ee3dbeb7 --- /dev/null +++ b/runtime/src/main/cpp/Atomic.cpp @@ -0,0 +1,56 @@ +/* + * 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. + */ + +#include "Atomic.h" +#include "Types.h" + +namespace { + +template T addAndGetImpl(KRef thiz, T delta) { + volatile T* location = reinterpret_cast(thiz + 1); + return atomicAdd(location, delta); +} + +template T compareAndSwapImpl(KRef thiz, T expectedValue, T newValue) { + volatile T* location = reinterpret_cast(thiz + 1); + return compareAndSwap(location, expectedValue, newValue); +} + +} // namespace + +extern "C" { + +KInt Kotlin_AtomicInt_addAndGet(KRef thiz, KInt delta) { + return addAndGetImpl(thiz, delta); +} + +KInt Kotlin_AtomicInt_compareAndSwap(KRef thiz, KInt expectedValue, KInt newValue) { + return compareAndSwapImpl(thiz, expectedValue, newValue); +} + +KLong Kotlin_AtomicLong_addAndGet(KRef thiz, KLong delta) { + return addAndGetImpl(thiz, delta); +} + +KLong Kotlin_AtomicLong_compareAndSwap(KRef thiz, KLong expectedValue, KLong newValue) { + return compareAndSwapImpl(thiz, expectedValue, newValue); +} + +KNativePtr Kotlin_AtomicNativePtr_compareAndSwap(KRef thiz, KNativePtr expectedValue, KNativePtr newValue) { + return compareAndSwapImpl(thiz, expectedValue, newValue); +} + +} // extern "C" \ No newline at end of file diff --git a/runtime/src/main/cpp/Atomic.h b/runtime/src/main/cpp/Atomic.h index ffffe804e0c..fd507bce53f 100644 --- a/runtime/src/main/cpp/Atomic.h +++ b/runtime/src/main/cpp/Atomic.h @@ -3,7 +3,8 @@ #include "Common.h" -ALWAYS_INLINE inline int atomicAdd(int* where, int what) { +template +ALWAYS_INLINE inline T atomicAdd(volatile T* where, T what) { #ifndef KONAN_NO_THREADS return __sync_add_and_fetch(where, what); #else @@ -11,5 +12,19 @@ ALWAYS_INLINE inline int atomicAdd(int* where, int what) { #endif } +template +ALWAYS_INLINE inline T compareAndSwap(volatile T* where, T expectedValue, T newValue) { +#ifndef KONAN_NO_THREADS + return __sync_val_compare_and_swap(where, expectedValue, newValue); +#else + T oldValue = *location; + if (oldValue == expectedValue) { + *location = newValue; + } + return oldValue; + return *where += what; +#endif +} + #endif // RUNTIME_ATOMIC_H \ No newline at end of file diff --git a/runtime/src/main/cpp/Worker.cpp b/runtime/src/main/cpp/Worker.cpp index 2c6718acccd..bc3681c5ccd 100644 --- a/runtime/src/main/cpp/Worker.cpp +++ b/runtime/src/main/cpp/Worker.cpp @@ -25,9 +25,6 @@ #if WITH_WORKERS #include #include - -#include -#include #endif #include "Alloc.h" diff --git a/runtime/src/main/kotlin/konan/internal/Annotations.kt b/runtime/src/main/kotlin/konan/internal/Annotations.kt index d069654d177..25c0668ba81 100644 --- a/runtime/src/main/kotlin/konan/internal/Annotations.kt +++ b/runtime/src/main/kotlin/konan/internal/Annotations.kt @@ -59,8 +59,9 @@ annotation class ExportForCompiler annotation class InlineConstructor /** - * Class is immutable and is frozen by default. + * Class is immutable and is frozen by default. Also this annotation is (ab)used for marking objects + * where mutability checks are not needed. */ @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY) -annotation class Immutable +internal annotation class Immutable diff --git a/runtime/src/main/kotlin/konan/worker/Atomics.kt b/runtime/src/main/kotlin/konan/worker/Atomics.kt new file mode 100644 index 00000000000..eeda755534c --- /dev/null +++ b/runtime/src/main/kotlin/konan/worker/Atomics.kt @@ -0,0 +1,181 @@ +/* + * 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 konan.worker + +import konan.internal.Immutable +import konan.SymbolName +import kotlinx.cinterop.NativePtr + +@Immutable +class AtomicInt(private var value: Int = 0) : Number() { + /* Atomic operations. */ + + /** + * Increments the value by [delta] and returns the new value. + */ + @SymbolName("Kotlin_AtomicInt_addAndGet") + external fun addAndGet(delta: Int): Int + + /** + * Compares value with [expected] and replaces it with [new] value if values matches. + * Returns the old value. + */ + @SymbolName("Kotlin_AtomicInt_compareAndSwap") + external fun compareAndSwap(expected: Int, new: Int): Int + + /** + * Increments value by one. + */ + fun increment(): Int = addAndGet(1) + + /** + * Decrements value by one. + */ + fun decrement(): Int = addAndGet(-1) + + /** + * Returns the current value. + */ + fun get(): Int = value + + /* Operations from Number. */ + + /** + * Returns the value of this number as a [Double], which may involve rounding. + */ + public override fun toDouble(): Double = value.toDouble() + + /** + * Returns the value of this number as a [Float], which may involve rounding. + */ + public override fun toFloat(): Float = value.toFloat() + + /** + * Returns the value of this number as a [Long], which may involve rounding or truncation. + */ + public override fun toLong(): Long = value.toLong() + + /** + * Returns the value of this number as an [Int], which may involve rounding or truncation. + */ + public override fun toInt(): Int = value.toInt() + + /** + * Returns the [Char] with the numeric value equal to this number, truncated to 16 bits if appropriate. + */ + public override fun toChar(): Char = value.toChar() + + /** + * Returns the value of this number as a [Short], which may involve rounding or truncation. + */ + public override fun toShort(): Short = value.toShort() + + /** + * Returns the value of this number as a [Byte], which may involve rounding or truncation. + */ + public override fun toByte(): Byte = value.toByte() +} + +@Immutable +class AtomicLong(private var value: Long = 0) : Number() { + /* Atomic operations. */ + + /** + * Increments the value by [delta] and returns the new value. + */ + @SymbolName("Kotlin_AtomicLong_addAndGet") + external fun addAndGet(delta: Long): Long + + /** + * Increments the value by [delta] and returns the new value. + */ + fun addAndGet(delta: Int): Long = addAndGet(delta.toLong()) + + /** + * Compares value with [expected] and replaces it with [new] value if values matches. + * Returns the old value. + */ + @SymbolName("Kotlin_AtomicLong_compareAndSwap") + external fun compareAndSwap(expected: Long, new: Long): Long + + /** + * Increments value by one. + */ + fun increment(): Long = addAndGet(1L) + + /** + * Decrements value by one. + */ + fun decrement(): Long = addAndGet(-1L) + + /** + * Returns the current value. + */ + fun get(): Long = value + + /* Operations from Number. */ + + /** + * Returns the value of this number as a [Double], which may involve rounding. + */ + public override fun toDouble(): Double = value.toDouble() + + /** + * Returns the value of this number as a [Float], which may involve rounding. + */ + public override fun toFloat(): Float = value.toFloat() + + /** + * Returns the value of this number as a [Long], which may involve rounding or truncation. + */ + public override fun toLong(): Long = value.toLong() + + /** + * Returns the value of this number as an [Int], which may involve rounding or truncation. + */ + public override fun toInt(): Int = value.toInt() + + /** + * Returns the [Char] with the numeric value equal to this number, truncated to 16 bits if appropriate. + */ + public override fun toChar(): Char = value.toChar() + + /** + * Returns the value of this number as a [Short], which may involve rounding or truncation. + */ + public override fun toShort(): Short = value.toShort() + + /** + * Returns the value of this number as a [Byte], which may involve rounding or truncation. + */ + public override fun toByte(): Byte = value.toByte() +} + +@Immutable +class AtomicNativePtr(private var value: NativePtr) { + /** + * Compares value with [expected] and replaces it with [new] value if values matches. + * Returns the old value. + */ + @SymbolName("Kotlin_AtomicNativePtr_compareAndSwap") + external fun compareAndSwap(expected: NativePtr, new: NativePtr): NativePtr + + /** + * Returns the current value. + */ + fun get(): NativePtr = value +} \ No newline at end of file From 4028ae48e3ea3553b1709c63d4de11e0e1896d6f Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Fri, 15 Jun 2018 13:44:48 +0300 Subject: [PATCH 2/4] Immutable -> Frozen --- .../backend/konan/descriptors/DescriptorUtils.kt | 10 +++++----- .../kotlin/backend/konan/llvm/IrToBitcode.kt | 3 +-- .../kotlin/backend/konan/llvm/RTTIGenerator.kt | 3 +-- runtime/src/main/kotlin/konan/BinaryBlob.kt | 2 +- .../main/kotlin/konan/internal/Annotations.kt | 6 +++--- runtime/src/main/kotlin/konan/internal/Boxing.kt | 16 ++++++++-------- .../main/kotlin/konan/internal/InteropBoxing.kt | 6 +++--- runtime/src/main/kotlin/konan/worker/Atomics.kt | 8 ++++---- runtime/src/main/kotlin/kotlin/String.kt | 2 +- 9 files changed, 27 insertions(+), 29 deletions(-) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt index 528135f8910..76c47d114ea 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt @@ -83,17 +83,17 @@ internal fun IrSimpleFunction.resolveFakeOverride(): IrSimpleFunction { } private val intrinsicAnnotation = FqName("konan.internal.Intrinsic") -private val immutableAnnotation = FqName("konan.internal.Immutable") +private val frozenAnnotation = FqName("konan.internal.Frozen") // TODO: don't forget to remove descriptor access here. internal val FunctionDescriptor.isIntrinsic: Boolean get() = this.descriptor.annotations.hasAnnotation(intrinsicAnnotation) -internal val org.jetbrains.kotlin.descriptors.DeclarationDescriptor.isImmutable: Boolean - get() = this.annotations.hasAnnotation(immutableAnnotation) +internal val org.jetbrains.kotlin.descriptors.DeclarationDescriptor.isFrozen: Boolean + get() = this.annotations.hasAnnotation(frozenAnnotation) -internal val DeclarationDescriptor.isImmutable: Boolean - get() = this.descriptor.isImmutable +internal val DeclarationDescriptor.isFrozen: Boolean + get() = this.descriptor.isFrozen private val intrinsicTypes = setOf( "kotlin.Boolean", "kotlin.Char", diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index 74c2d5fc409..9a727dbcf25 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -42,7 +42,6 @@ import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.ir.visitors.acceptVoid import org.jetbrains.kotlin.konan.target.CompilerOutputKind import org.jetbrains.kotlin.konan.target.KonanTarget -import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils @@ -1413,7 +1412,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map { override fun equals(other: Any?): Boolean { if (other !is BooleanBox) { @@ -65,7 +65,7 @@ fun boxBoolean(value: Boolean) = if (inBooleanBoxCache(value)) { BooleanBox(value) } -@Immutable +@Frozen class CharBox(val value: Char) : Comparable { override fun equals(other: Any?): Boolean { if (other !is CharBox) { @@ -89,7 +89,7 @@ fun boxChar(value: Char) = if (inCharBoxCache(value)) { CharBox(value) } -@Immutable +@Frozen class ByteBox(val value: Byte) : Number(), Comparable { override fun equals(other: Any?): Boolean { if (other !is ByteBox) { @@ -121,7 +121,7 @@ fun boxByte(value: Byte) = if (inByteBoxCache(value)) { ByteBox(value) } -@Immutable +@Frozen class ShortBox(val value: Short) : Number(), Comparable { override fun equals(other: Any?): Boolean { if (other !is ShortBox) { @@ -153,7 +153,7 @@ fun boxShort(value: Short) = if (inShortBoxCache(value)) { ShortBox(value) } -@Immutable +@Frozen class IntBox(val value: Int) : Number(), Comparable { override fun equals(other: Any?): Boolean { if (other !is IntBox) { @@ -185,7 +185,7 @@ fun boxInt(value: Int) = if (inIntBoxCache(value)) { IntBox(value) } -@Immutable +@Frozen class LongBox(val value: Long) : Number(), Comparable { override fun equals(other: Any?): Boolean { if (other !is LongBox) { @@ -217,7 +217,7 @@ fun boxLong(value: Long) = if (inLongBoxCache(value)) { LongBox(value) } -@Immutable +@Frozen class FloatBox(val value: Float) : Number(), Comparable { override fun equals(other: Any?): Boolean { if (other !is FloatBox) { @@ -245,7 +245,7 @@ class FloatBox(val value: Float) : Number(), Comparable { @ExportForCppRuntime("Kotlin_boxFloat") fun boxFloat(value: Float) = FloatBox(value) -@Immutable +@Frozen class DoubleBox(val value: Double) : Number(), Comparable { override fun equals(other: Any?): Boolean { if (other !is DoubleBox) { diff --git a/runtime/src/main/kotlin/konan/internal/InteropBoxing.kt b/runtime/src/main/kotlin/konan/internal/InteropBoxing.kt index 530229d449b..57a9b0747e4 100644 --- a/runtime/src/main/kotlin/konan/internal/InteropBoxing.kt +++ b/runtime/src/main/kotlin/konan/internal/InteropBoxing.kt @@ -18,7 +18,7 @@ package konan.internal import kotlinx.cinterop.* -@Immutable +@Frozen class NativePtrBox(val value: NativePtr) { override fun equals(other: Any?): Boolean { if (other !is NativePtrBox) { @@ -35,7 +35,7 @@ class NativePtrBox(val value: NativePtr) { fun boxNativePtr(value: NativePtr) = NativePtrBox(value) -@Immutable +@Frozen class NativePointedBox(val value: NativePointed) { override fun equals(other: Any?): Boolean { if (other !is NativePointedBox) { @@ -56,7 +56,7 @@ class NativePointedBox(val value: NativePointed) { fun boxNativePointed(value: NativePointed?) = if (value != null) NativePointedBox(value) else null fun unboxNativePointed(box: NativePointedBox?) = box?.value -@Immutable +@Frozen class CPointerBox(val value: CPointer) : CValuesRef() { override fun equals(other: Any?): Boolean { if (other !is CPointerBox) { diff --git a/runtime/src/main/kotlin/konan/worker/Atomics.kt b/runtime/src/main/kotlin/konan/worker/Atomics.kt index eeda755534c..12ab28f4e97 100644 --- a/runtime/src/main/kotlin/konan/worker/Atomics.kt +++ b/runtime/src/main/kotlin/konan/worker/Atomics.kt @@ -16,11 +16,11 @@ package konan.worker -import konan.internal.Immutable +import konan.internal.Frozen import konan.SymbolName import kotlinx.cinterop.NativePtr -@Immutable +@Frozen class AtomicInt(private var value: Int = 0) : Number() { /* Atomic operations. */ @@ -90,7 +90,7 @@ class AtomicInt(private var value: Int = 0) : Number() { public override fun toByte(): Byte = value.toByte() } -@Immutable +@Frozen class AtomicLong(private var value: Long = 0) : Number() { /* Atomic operations. */ @@ -165,7 +165,7 @@ class AtomicLong(private var value: Long = 0) : Number() { public override fun toByte(): Byte = value.toByte() } -@Immutable +@Frozen class AtomicNativePtr(private var value: NativePtr) { /** * Compares value with [expected] and replaces it with [new] value if values matches. diff --git a/runtime/src/main/kotlin/kotlin/String.kt b/runtime/src/main/kotlin/kotlin/String.kt index bf0944f0cd7..ef2f57986f5 100644 --- a/runtime/src/main/kotlin/kotlin/String.kt +++ b/runtime/src/main/kotlin/kotlin/String.kt @@ -17,7 +17,7 @@ package kotlin @ExportTypeInfo("theStringTypeInfo") -@konan.internal.Immutable +@konan.internal.Frozen public final class String : Comparable, CharSequence { public companion object { } From e61deac5ab4444cb6f7da1137def3fa8422486c5 Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Fri, 15 Jun 2018 14:41:27 +0300 Subject: [PATCH 3/4] Fix WASM build. --- runtime/src/main/cpp/Atomic.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/runtime/src/main/cpp/Atomic.h b/runtime/src/main/cpp/Atomic.h index fd507bce53f..8499a13fa28 100644 --- a/runtime/src/main/cpp/Atomic.h +++ b/runtime/src/main/cpp/Atomic.h @@ -17,12 +17,11 @@ ALWAYS_INLINE inline T compareAndSwap(volatile T* where, T expectedValue, T newV #ifndef KONAN_NO_THREADS return __sync_val_compare_and_swap(where, expectedValue, newValue); #else - T oldValue = *location; + T oldValue = *where; if (oldValue == expectedValue) { - *location = newValue; - } - return oldValue; - return *where += what; + *where = newValue; + } + return oldValue; #endif } From b9050263bfe4ed6ed47fa13c029609ec3083297c Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Fri, 15 Jun 2018 19:18:38 +0300 Subject: [PATCH 4/4] Add atomic references. --- backend.native/tests/build.gradle | 2 +- .../tests/runtime/workers/atomic0.kt | 50 +++++++++++++++++-- runtime/src/main/cpp/Atomic.cpp | 14 ++++++ .../src/main/kotlin/konan/worker/Atomics.kt | 28 +++++++++++ 4 files changed, 89 insertions(+), 5 deletions(-) diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 0cdd57a45aa..5a67f241814 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -673,7 +673,7 @@ task freeze2(type: RunKonanTest) { task atomic0(type: RunKonanTest) { disabled = (project.testTarget == 'wasm32') // Workers need pthreads. - goldValue = "35\n" + "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n" + goldValue = "35\n" + "20\n" source = "runtime/workers/atomic0.kt" } diff --git a/backend.native/tests/runtime/workers/atomic0.kt b/backend.native/tests/runtime/workers/atomic0.kt index f7a241a091c..685d009fc7e 100644 --- a/backend.native/tests/runtime/workers/atomic0.kt +++ b/backend.native/tests/runtime/workers/atomic0.kt @@ -19,17 +19,57 @@ fun test1(workers: Array) { fun test2(workers: Array) { val atomic = AtomicInt(0) + val counter = AtomicInt(0) val futures = Array(workers.size, { workerIndex -> - workers[workerIndex].schedule(TransferMode.CHECKED, { atomic to workerIndex }) { - (place, index) -> + workers[workerIndex].schedule(TransferMode.CHECKED, { Triple(atomic, workerIndex, counter) }) { + (place, index, result) -> while (place.compareAndSwap(index, index + 1) != index) {} - println(index) + result.increment() == index + 1 + } + }) + futures.forEach { + assertEquals(it.result(), true) + } + println(counter.get()) +} + +data class Data(val value: Int) + +fun test3(workers: Array) { + val common = AtomicReference() + val futures = Array(workers.size, { workerIndex -> + workers[workerIndex].schedule(TransferMode.CHECKED, { Pair(common, workerIndex) }) { + (place, index) -> + val mine = Data(index).freeze() + // Try to publish our own data, until successful, in a tight loop. + while (place.compareAndSwap(null, mine) != null) {} } }) + val seen = mutableSetOf() + for (i in 0 until workers.size) { + do { + val current = common.get() + if (current != null && !seen.contains(current)) { + seen += current + // Let others publish. + assertEquals(common.compareAndSwap(current, null), current) + break + } + } while (true) + } futures.forEach { it.result() } - println(atomic.get()) + assertEquals(seen.size, workers.size) +} + +fun test4() { + assertFailsWith { + AtomicReference(Data(1)) + } + assertFailsWith { + AtomicReference().compareAndSwap(null, Data(2)) + } } @Test fun runTest() { @@ -38,4 +78,6 @@ fun test2(workers: Array) { test1(workers) test2(workers) + test3(workers) + test4() } \ No newline at end of file diff --git a/runtime/src/main/cpp/Atomic.cpp b/runtime/src/main/cpp/Atomic.cpp index 481ee3dbeb7..0fd8941a3a0 100644 --- a/runtime/src/main/cpp/Atomic.cpp +++ b/runtime/src/main/cpp/Atomic.cpp @@ -15,6 +15,7 @@ */ #include "Atomic.h" +#include "Common.h" #include "Types.h" namespace { @@ -33,6 +34,8 @@ template T compareAndSwapImpl(KRef thiz, T expectedValue, T newValu extern "C" { +RUNTIME_NORETURN void ThrowInvalidMutabilityException(); + KInt Kotlin_AtomicInt_addAndGet(KRef thiz, KInt delta) { return addAndGetImpl(thiz, delta); } @@ -53,4 +56,15 @@ KNativePtr Kotlin_AtomicNativePtr_compareAndSwap(KRef thiz, KNativePtr expectedV return compareAndSwapImpl(thiz, expectedValue, newValue); } +void Kotlin_AtomicReference_checkIfFrozen(KRef value) { + if (value != nullptr && !value->container()->permanentOrFrozen()) { + ThrowInvalidMutabilityException(); + } +} + +KRef Kotlin_AtomicReference_compareAndSwap(KRef thiz, KRef expectedValue, KRef newValue) { + Kotlin_AtomicReference_checkIfFrozen(newValue); + return compareAndSwapImpl(thiz, expectedValue, newValue); +} + } // extern "C" \ No newline at end of file diff --git a/runtime/src/main/kotlin/konan/worker/Atomics.kt b/runtime/src/main/kotlin/konan/worker/Atomics.kt index 12ab28f4e97..081b432ed12 100644 --- a/runtime/src/main/kotlin/konan/worker/Atomics.kt +++ b/runtime/src/main/kotlin/konan/worker/Atomics.kt @@ -178,4 +178,32 @@ class AtomicNativePtr(private var value: NativePtr) { * Returns the current value. */ fun get(): NativePtr = value +} + +@SymbolName("Kotlin_AtomicReference_checkIfFrozen") +external private fun checkIfFrozen(ref: Any?) + +@Frozen +class AtomicReference(private var value: T? = null) { + /** + * Creates a new atomic reference pointing to given [ref]. If reference is not frozen, + * @InvalidMutabilityException is thrown. + */ + init { + checkIfFrozen(value) + } + + /** + * Compares value with [expected] and replaces it with [new] value if values matches. + * If [new] value is not null, it must be frozen or permanent object, otherwise an + * @InvalidMutabilityException is thrown. + * Returns the old value. + */ + @SymbolName("Kotlin_AtomicReference_compareAndSwap") + external fun compareAndSwap(expected: T?, new: T?): T? + + /** + * Returns the current value. + */ + public fun get(): T? = value } \ No newline at end of file