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