diff --git a/compiler/testData/codegen/box/topLevelInitializtion/concurrent.kt b/compiler/testData/codegen/box/topLevelInitializtion/concurrent.kt index 5729ac267ba..30b25899cd9 100644 --- a/compiler/testData/codegen/box/topLevelInitializtion/concurrent.kt +++ b/compiler/testData/codegen/box/topLevelInitializtion/concurrent.kt @@ -8,6 +8,7 @@ val K = if (true) "K" else "A" // to avoid const init // FILE: main.kt import kotlin.native.concurrent.* +import kotlin.concurrent.AtomicInt val sem = AtomicInt(0) @@ -18,7 +19,7 @@ fun box() : String { mode = TransferMode.SAFE, { }, { - sem.increment(); + sem.incrementAndGet(); while (sem.value != 3) {} O } @@ -27,7 +28,7 @@ fun box() : String { mode = TransferMode.SAFE, { }, { - sem.increment(); + sem.incrementAndGet(); while (sem.value != 3) {} K } @@ -38,4 +39,4 @@ fun box() : String { w1.requestTermination().result w2.requestTermination().result return result -} \ No newline at end of file +} diff --git a/compiler/testData/codegen/box/volatile/crossModuleIntrinsic.kt b/compiler/testData/codegen/box/volatile/crossModuleIntrinsic.kt index 3ea7b1f0055..c436b585637 100644 --- a/compiler/testData/codegen/box/volatile/crossModuleIntrinsic.kt +++ b/compiler/testData/codegen/box/volatile/crossModuleIntrinsic.kt @@ -22,5 +22,5 @@ import kotlin.concurrent.* fun box() : String { val o = "O" val x = Box(o) - return x::value.compareAndSwapField(o, "K") + x.value + return x::value.compareAndExchangeField(o, "K") + x.value } \ No newline at end of file diff --git a/compiler/testData/codegen/box/volatile/intrinsics.kt b/compiler/testData/codegen/box/volatile/intrinsics.kt index 45f2b9c36bc..e3855073ede 100644 --- a/compiler/testData/codegen/box/volatile/intrinsics.kt +++ b/compiler/testData/codegen/box/volatile/intrinsics.kt @@ -38,28 +38,28 @@ interface RefWrapper : Wrapper { class IntWrapper(@Volatile var x : Int) : IncWrapper { - override fun compareAndSwap(expected: Int, new: Int) = this::x.compareAndSwapField(expected, new) + override fun compareAndSwap(expected: Int, new: Int) = this::x.compareAndExchangeField(expected, new) override fun compareAndSet(expected: Int, new: Int) = this::x.compareAndSetField(expected, new) override fun getAndSet(new: Int) = this::x.getAndSetField(new) override fun getAndAdd(delta: Int) = this::x.getAndAddFieldLocal(delta) } class LongWrapper(@Volatile var x : Long) : IncWrapper { - override fun compareAndSwap(expected: Long, new: Long) = this::x.compareAndSwapField(expected, new) + override fun compareAndSwap(expected: Long, new: Long) = this::x.compareAndExchangeField(expected, new) override fun compareAndSet(expected: Long, new: Long) = this::x.compareAndSetField(expected, new) override fun getAndSet(new: Long) = this::x.getAndSetField(new) override fun getAndAdd(delta: Long) = this::x.getAndAddFieldLocal(delta) } class ShortWrapper(@Volatile var x : Short) : IncWrapper { - override fun compareAndSwap(expected: Short, new: Short) = this::x.compareAndSwapField(expected, new) + override fun compareAndSwap(expected: Short, new: Short) = this::x.compareAndExchangeField(expected, new) override fun compareAndSet(expected: Short, new: Short) = this::x.compareAndSetField(expected, new) override fun getAndSet(new: Short) = this::x.getAndSetField(new) override fun getAndAdd(delta: Short) = this::x.getAndAddFieldLocal(delta) } class ByteWrapper(@Volatile var x : Byte) : IncWrapper { - override fun compareAndSwap(expected: Byte, new: Byte) = this::x.compareAndSwapField(expected, new) + override fun compareAndSwap(expected: Byte, new: Byte) = this::x.compareAndExchangeField(expected, new) override fun compareAndSet(expected: Byte, new: Byte) = this::x.compareAndSetField(expected, new) override fun getAndSet(new: Byte) = this::x.getAndSetField(new) override fun getAndAdd(delta: Byte) = this::x.getAndAddFieldLocal(delta) @@ -67,13 +67,13 @@ class ByteWrapper(@Volatile var x : Byte) : IncWrapper { class StringWrapper(@Volatile var x : String) : RefWrapper { - override fun compareAndSwap(expected: String, new: String) = this::x.compareAndSwapField(expected, new) + override fun compareAndSwap(expected: String, new: String) = this::x.compareAndExchangeField(expected, new) override fun compareAndSet(expected: String, new: String) = this::x.compareAndSetField(expected, new) override fun getAndSet(new: String) = this::x.getAndSetField(new) } class GenericWrapper(@Volatile var x : T) : RefWrapper { - override fun compareAndSwap(expected: T, new: T) = this::x.compareAndSwapField(expected, new) + override fun compareAndSwap(expected: T, new: T) = this::x.compareAndExchangeField(expected, new) override fun compareAndSet(expected: T, new: T) = this::x.compareAndSetField(expected, new) override fun getAndSet(new: T) = this::x.getAndSetField(new) } diff --git a/compiler/testData/codegen/box/volatile/intrinsicsOnGlobal.kt b/compiler/testData/codegen/box/volatile/intrinsicsOnGlobal.kt index 6e621e81494..110c3e4f0fa 100644 --- a/compiler/testData/codegen/box/volatile/intrinsicsOnGlobal.kt +++ b/compiler/testData/codegen/box/volatile/intrinsicsOnGlobal.kt @@ -35,8 +35,8 @@ val c = "3" fun box() : String { if (::x.compareAndSetField(1, 2) != true) return "FAIL Int: 1" if (::x.compareAndSetField(1, 2) != false) return "FAIL Int: 2" - if (::x.compareAndSwapField(2, 1) != 2) return "FAIL Int: 3" - if (::x.compareAndSwapField(2, 1) != 1) return "FAIL Int: 4" + if (::x.compareAndExchangeField(2, 1) != 2) return "FAIL Int: 3" + if (::x.compareAndExchangeField(2, 1) != 1) return "FAIL Int: 4" if (::x.getAndSetField(3) != 1) return "FAIL Int: 5" if (::x.getAndSetField(1) != 3) return "FAIL Int: 6" if (::x.getAndAddFieldLocal(1) != 1) return "FAIL Int: 7" @@ -45,8 +45,8 @@ fun box() : String { if (::y.compareAndSetField(1L, 2L) != true) return "FAIL Long: 1" if (::y.compareAndSetField(1L, 2L) != false) return "FAIL Long: 2" - if (::y.compareAndSwapField(2L, 1L) != 2L) return "FAIL Long: 3" - if (::y.compareAndSwapField(2L, 1L) != 1L) return "FAIL Long: 4" + if (::y.compareAndExchangeField(2L, 1L) != 2L) return "FAIL Long: 3" + if (::y.compareAndExchangeField(2L, 1L) != 1L) return "FAIL Long: 4" if (::y.getAndSetField(3L) != 1L) return "FAIL Long: 5" if (::y.getAndSetField(1L) != 3L) return "FAIL Long: 6" if (::y.getAndAddFieldLocal(1L) != 1L) return "FAIL Long: 7" @@ -57,8 +57,8 @@ fun box() : String { if (isExperimentalMM()) { if (::z.compareAndSetField(a, b) != true) return "FAIL String: 1" if (::z.compareAndSetField(a, b) != false) return "FAIL String: 2" - if (::z.compareAndSwapField(b, a) != b) return "FAIL String: 3" - if (::z.compareAndSwapField(b, a) != a) return "FAIL String: 4" + if (::z.compareAndExchangeField(b, a) != b) return "FAIL String: 3" + if (::z.compareAndExchangeField(b, a) != a) return "FAIL String: 4" if (::z.getAndSetField(c) != a) return "FAIL String: 5" if (::z.getAndSetField(a) != c) return "FAIL String: 6" if (z != a) return "FAIL String: 7" @@ -66,8 +66,8 @@ fun box() : String { if (::t.compareAndSetField(true, false) != true) return "FAIL Bool: 1" if (::t.compareAndSetField(true, false) != false) return "FAIL Bool: 2" - if (::t.compareAndSwapField(false, true) != false) return "FAIL Bool: 3" - if (::t.compareAndSwapField(false, true) != true) return "FAIL Bool: 4" + if (::t.compareAndExchangeField(false, true) != false) return "FAIL Bool: 3" + if (::t.compareAndExchangeField(false, true) != true) return "FAIL Bool: 4" if (::t.getAndSetField(false) != true) return "FAIL Bool: 5" if (::t.getAndSetField(true) != false) return "FAIL Bool: 6" if (t != true) return "FAIL Bool: 7" diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt index be1ad6f2606..0f7853beeea 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt @@ -37,7 +37,7 @@ import java.util.concurrent.ConcurrentHashMap internal class NativeMapping : DefaultMapping() { data class BridgeKey(val target: IrSimpleFunction, val bridgeDirections: BridgeDirections) enum class AtomicFunctionType { - COMPARE_AND_SWAP, COMPARE_AND_SET, GET_AND_SET, GET_AND_ADD; + COMPARE_AND_EXCHANGE, COMPARE_AND_SET, GET_AND_SET, GET_AND_ADD; } data class AtomicFunctionKey(val field: IrField, val type: AtomicFunctionType) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IntrinsicGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IntrinsicGenerator.kt index c866f28cb1a..0426fd79522 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IntrinsicGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IntrinsicGenerator.kt @@ -94,11 +94,11 @@ internal enum class IntrinsicType { WORKER_EXECUTE, // Atomics COMPARE_AND_SET_FIELD, - COMPARE_AND_SWAP_FIELD, + COMPARE_AND_EXCHANGE_FIELD, GET_AND_SET_FIELD, GET_AND_ADD_FIELD, COMPARE_AND_SET, - COMPARE_AND_SWAP, + COMPARE_AND_EXCHANGE, GET_AND_SET, GET_AND_ADD, } @@ -255,7 +255,7 @@ internal class IntrinsicGenerator(private val environment: IntrinsicGeneratorEnv IntrinsicType.IS_EXPERIMENTAL_MM -> emitIsExperimentalMM() IntrinsicType.THE_UNIT_INSTANCE -> theUnitInstanceRef.llvm IntrinsicType.COMPARE_AND_SET -> emitCompareAndSet(callSite, args) - IntrinsicType.COMPARE_AND_SWAP -> emitCompareAndSwap(callSite, args, resultSlot) + IntrinsicType.COMPARE_AND_EXCHANGE -> emitCompareAndSwap(callSite, args, resultSlot) IntrinsicType.GET_AND_SET -> emitGetAndSet(callSite, args, resultSlot) IntrinsicType.GET_AND_ADD -> emitGetAndAdd(callSite, args) IntrinsicType.GET_CONTINUATION, @@ -271,7 +271,7 @@ internal class IntrinsicGenerator(private val environment: IntrinsicGeneratorEnv IntrinsicType.ENUM_VALUE_OF, IntrinsicType.WORKER_EXECUTE, IntrinsicType.COMPARE_AND_SET_FIELD, - IntrinsicType.COMPARE_AND_SWAP_FIELD, + IntrinsicType.COMPARE_AND_EXCHANGE_FIELD, IntrinsicType.GET_AND_SET_FIELD, IntrinsicType.GET_AND_ADD_FIELD -> reportNonLoweredIntrinsic(intrinsicType) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/VolatileFieldsLowering.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/VolatileFieldsLowering.kt index 34e5cc8cc1d..e7438e1b8ec 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/VolatileFieldsLowering.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/VolatileFieldsLowering.kt @@ -110,8 +110,8 @@ internal class VolatileFieldsLowering(val context: Context) : FileLoweringPass { private fun compareAndSetFunction(irField: IrField) = atomicFunction(irField, NativeMapping.AtomicFunctionType.COMPARE_AND_SET) { this.buildCasFunction(irField, IntrinsicType.COMPARE_AND_SET, this.context.irBuiltIns.booleanType) } - private fun compareAndSwapFunction(irField: IrField) = atomicFunction(irField, NativeMapping.AtomicFunctionType.COMPARE_AND_SWAP) { - this.buildCasFunction(irField, IntrinsicType.COMPARE_AND_SWAP, irField.type) + private fun compareAndExchangeFunction(irField: IrField) = atomicFunction(irField, NativeMapping.AtomicFunctionType.COMPARE_AND_EXCHANGE) { + this.buildCasFunction(irField, IntrinsicType.COMPARE_AND_EXCHANGE, irField.type) } private fun getAndSetFunction(irField: IrField) = atomicFunction(irField, NativeMapping.AtomicFunctionType.GET_AND_SET) { this.buildAtomicRWMFunction(irField, IntrinsicType.GET_AND_SET) @@ -152,7 +152,7 @@ internal class VolatileFieldsLowering(val context: Context) : FileLoweringPass { } else { listOfNotNull(it, compareAndSetFunction(field), - compareAndSwapFunction(field), + compareAndExchangeFunction(field), getAndSetFunction(field), if (field.isInteger()) getAndAddFunction(field) else null ) @@ -198,7 +198,7 @@ internal class VolatileFieldsLowering(val context: Context) : FileLoweringPass { private val intrinsicMap = mapOf( IntrinsicType.COMPARE_AND_SET_FIELD to ::compareAndSetFunction, - IntrinsicType.COMPARE_AND_SWAP_FIELD to ::compareAndSwapFunction, + IntrinsicType.COMPARE_AND_EXCHANGE_FIELD to ::compareAndExchangeFunction, IntrinsicType.GET_AND_SET_FIELD to ::getAndSetFunction, IntrinsicType.GET_AND_ADD_FIELD to ::getAndAddFunction, ) @@ -221,7 +221,7 @@ internal class VolatileFieldsLowering(val context: Context) : FileLoweringPass { return builder.irCall(function).apply { dispatchReceiver = reference.dispatchReceiver putValueArgument(0, expression.getValueArgument(0)) - if (intrinsicType == IntrinsicType.COMPARE_AND_SET_FIELD || intrinsicType == IntrinsicType.COMPARE_AND_SWAP_FIELD) { + if (intrinsicType == IntrinsicType.COMPARE_AND_SET_FIELD || intrinsicType == IntrinsicType.COMPARE_AND_EXCHANGE_FIELD) { putValueArgument(1, expression.getValueArgument(1)) } }.let { @@ -229,7 +229,7 @@ internal class VolatileFieldsLowering(val context: Context) : FileLoweringPass { for (arg in 0 until it.valueArgumentsCount) { it.putValueArgument(arg, builder.irBoolToByte(it.getValueArgument(arg)!!)) } - if (intrinsicType == IntrinsicType.COMPARE_AND_SWAP_FIELD || intrinsicType == IntrinsicType.GET_AND_SET_FIELD) { + if (intrinsicType == IntrinsicType.COMPARE_AND_EXCHANGE_FIELD || intrinsicType == IntrinsicType.GET_AND_SET_FIELD) { builder.irByteToBool(it) } else { it diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt index 120ac8589ff..b536b3de471 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt @@ -346,7 +346,7 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag expressions += jobInvocation to currentLoop } val intrinsicType = tryGetIntrinsicType(expression) - if (intrinsicType == IntrinsicType.COMPARE_AND_SET || intrinsicType == IntrinsicType.COMPARE_AND_SWAP) { + if (intrinsicType == IntrinsicType.COMPARE_AND_SET || intrinsicType == IntrinsicType.COMPARE_AND_EXCHANGE) { expressions += IrSetFieldImpl( expression.startOffset, expression.endOffset, context.mapping.functionToVolatileField[expression.symbol.owner]!!.symbol, diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/EscapeAnalysis.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/EscapeAnalysis.kt index 14314d87202..2da18ac5eaa 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/EscapeAnalysis.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/EscapeAnalysis.kt @@ -803,7 +803,8 @@ internal object EscapeAnalysis { context.log { "An external call: $callee" } if (callee.name?.startsWith("kfun:kotlin.") == true // TODO: Is it possible to do it in a more fine-grained fashion? - && !callee.name.startsWith("kfun:kotlin.native.concurrent")) { + && !callee.name.startsWith("kfun:kotlin.native.concurrent") + && !callee.name.startsWith("kfun:kotlin.concurrent")) { context.log { "A function from K/N runtime - can use annotations" } FunctionEscapeAnalysisResult.fromBits( callee.escapes ?: 0, diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index 9cb15e333dd..203f93c0d75 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -1165,21 +1165,26 @@ standaloneTest("freeze_disabled") { testLogger = KonanTest.Logger.SILENT } +task atomicSmokeTest(type: KonanLocalTest) { + enabled = isExperimentalMM // do not run the test on the legacy MM + source = "runtime/atomics/atomic_smoke.kt" +} + +task atomicStressTest(type: KonanLocalTest) { + enabled = isExperimentalMM // do not run the test on the legacy MM + source = "runtime/atomics/atomic_stress.kt" +} task atomic0(type: KonanLocalTest) { enabled = (project.testTarget != 'wasm32') // Workers need pthreads. useGoldenData = true - source = "runtime/workers/atomic0.kt" + source = "runtime/atomics/atomic0.kt" } standaloneTest("atomic1") { // Note: This test reproduces a race, so it'll start flaking if problem is reintroduced. enabled = false // Needs USE_CYCLIC_GC, which is disabled. - source = "runtime/workers/atomic1.kt" -} - -task atomic2(type: KonanLocalTest) { - source = "runtime/workers/atomic2.kt" + source = "runtime/atomics/atomic1.kt" } task lazy0(type: KonanLocalTest) { diff --git a/kotlin-native/backend.native/tests/runtime/workers/atomic0.kt b/kotlin-native/backend.native/tests/runtime/atomics/atomic0.kt similarity index 85% rename from kotlin-native/backend.native/tests/runtime/workers/atomic0.kt rename to kotlin-native/backend.native/tests/runtime/atomics/atomic0.kt index c1bd6deb337..2d241525ed2 100644 --- a/kotlin-native/backend.native/tests/runtime/workers/atomic0.kt +++ b/kotlin-native/backend.native/tests/runtime/atomics/atomic0.kt @@ -1,14 +1,17 @@ /* - * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license - * that can be found in the LICENSE file. + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ @file:OptIn(FreezingIsDeprecated::class, ObsoleteWorkersApi::class) -package runtime.workers.atomic0 +package runtime.atomics.atomic0 import kotlin.test.* import kotlin.native.concurrent.* +import kotlin.concurrent.AtomicInt +import kotlin.concurrent.AtomicLong +import kotlin.concurrent.AtomicReference fun test1(workers: Array) { val atomic = AtomicInt(15) @@ -32,10 +35,10 @@ fun test2(workers: Array) { // Here we simulate mutex using [place] location to store tag of the current worker. // When it is negative - worker executes exclusively. val tag = index + 1 - while (place.compareAndSwap(tag, -tag) != tag) {} + while (place.compareAndExchange(tag, -tag) != tag) {} val ok1 = result.addAndGet(1) == index + 1 // Now, let the next worker run. - val ok2 = place.compareAndSwap(-tag, tag + 1) == -tag + val ok2 = place.compareAndExchange(-tag, tag + 1) == -tag ok1 && ok2 } }) @@ -64,7 +67,7 @@ fun test3(workers: Array) { if (current != null && !seen.contains(current)) { seen += current // Let others publish. - assertEquals(common.compareAndSwap(current, null), current) + assertEquals(common.compareAndExchange(current, null), current) break } } while (true) @@ -80,7 +83,7 @@ fun test4LegacyMM() { AtomicReference(Data(1)) } assertFailsWith { - AtomicReference(null).compareAndSwap(null, Data(2)) + AtomicReference(null).compareAndExchange(null, Data(2)) } } @@ -91,14 +94,14 @@ fun test4() { } run { val ref = AtomicReference(null) - ref.compareAndSwap(null, Data(2)) + ref.compareAndExchange(null, Data(2)) assertEquals(2, ref.value!!.value) } if (Platform.isFreezingEnabled) { run { val ref = AtomicReference(null).freeze() assertFailsWith { - ref.compareAndSwap(null, Data(2)) + ref.compareAndExchange(null, Data(2)) } } } @@ -132,7 +135,7 @@ fun test6() { assertEquals(239L, long.value) } - +@Suppress("DEPRECATION_ERROR") fun test7() { val ref = FreezableAtomicReference(Array(1) { "hey" }) ref.value[0] = "ho" diff --git a/kotlin-native/backend.native/tests/runtime/workers/atomic0.out b/kotlin-native/backend.native/tests/runtime/atomics/atomic0.out similarity index 100% rename from kotlin-native/backend.native/tests/runtime/workers/atomic0.out rename to kotlin-native/backend.native/tests/runtime/atomics/atomic0.out diff --git a/kotlin-native/backend.native/tests/runtime/workers/atomic1.kt b/kotlin-native/backend.native/tests/runtime/atomics/atomic1.kt similarity index 100% rename from kotlin-native/backend.native/tests/runtime/workers/atomic1.kt rename to kotlin-native/backend.native/tests/runtime/atomics/atomic1.kt diff --git a/kotlin-native/backend.native/tests/runtime/atomics/atomic_smoke.kt b/kotlin-native/backend.native/tests/runtime/atomics/atomic_smoke.kt new file mode 100644 index 00000000000..d730a6a11ed --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/atomics/atomic_smoke.kt @@ -0,0 +1,189 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +@file:OptIn(FreezingIsDeprecated::class) +package runtime.atomics.atomic_smoke + +import kotlin.test.* +import kotlin.concurrent.AtomicInt +import kotlin.concurrent.AtomicLong +import kotlin.concurrent.AtomicReference +import kotlin.concurrent.AtomicNativePtr +import kotlin.native.internal.NativePtr + +@Test +fun ctor_Int() { + val x = AtomicInt(0) + assertEquals(x.value, 0) +} + +@Test +fun ctor_Long() { + val x = AtomicLong(0) + assertEquals(x.value, 0) +} + +@Test +fun setter_Int() { + val x = AtomicInt(0) + x.value = 1 + assertEquals(x.value, 1) +} + +@Test +fun setter_Long() { + val x = AtomicLong(0) + x.value = 1 + assertEquals(x.value, 1) +} + +@Test +fun addAndGet_Int() { + val x = AtomicInt(1) + val result = x.addAndGet(2) + assertEquals(result, 1 + 2) + assertEquals(x.value, result) +} + +@Test +fun addAndGet_Long() { + val x = AtomicLong(1) + val result = x.addAndGet(2L) + assertEquals(result, 1 + 2) + assertEquals(x.value, result) +} + +@Test +fun compareAndSwap_Int() { + val x = AtomicInt(0) + val successValue = x.compareAndExchange(0, 1) + assertEquals(successValue, 0) + assertEquals(x.value, 1) + val failValue = x.compareAndExchange(0, 2) + assertEquals(failValue, 1) + assertEquals(x.value, 1) +} + +@Test +fun compareAndSwap_Long() { + val x = AtomicLong(0) + val successValue = x.compareAndExchange(0, 1) + assertEquals(successValue, 0) + assertEquals(x.value, 1) + val failValue = x.compareAndExchange(0, 2) + assertEquals(failValue, 1) + assertEquals(x.value, 1) +} + +@Test +fun compareAndSet_Int() { + val x = AtomicInt(0) + val successValue = x.compareAndSet(0, 1) + assertTrue(successValue) + assertEquals(x.value, 1) + val failValue = x.compareAndSet(0, 2) + assertFalse(failValue) + assertEquals(x.value, 1) +} + +@Test +fun compareAndSet_Long() { + val x = AtomicLong(0) + val successValue = x.compareAndSet(0, 1) + assertTrue(successValue) + assertEquals(x.value, 1) + val failValue = x.compareAndSet(0, 2) + assertFalse(failValue) + assertEquals(x.value, 1) +} + +@Test +fun testAtomicInt() { + val atomic = AtomicInt(3) + assertEquals(3, atomic.value) + atomic.value = 5 + assertEquals(5, atomic.value) + assertEquals(5, atomic.getAndSet(6)) + assertEquals(6, atomic.value) + assertTrue(atomic.compareAndSet(6, 8)) + assertFalse(atomic.compareAndSet(9, 1)) + assertEquals(8, atomic.value) + assertEquals(8, atomic.getAndAdd(5)) + assertEquals(13, atomic.value) + assertEquals(18, atomic.addAndGet(5)) + assertEquals(18, atomic.getAndIncrement()) + assertEquals(19, atomic.value) + assertEquals(20, atomic.incrementAndGet()) + assertEquals(20, atomic.value) + assertEquals(20, atomic.getAndDecrement()) + assertEquals(19, atomic.value) + assertEquals(18, atomic.decrementAndGet()) + assertEquals(18, atomic.value) + assertEquals(18, atomic.compareAndExchange(18, 56)) + assertEquals(56, atomic.compareAndExchange(18, 56)) + assertEquals(56, atomic.compareAndExchange(18, 56)) +} + +@Test +fun testAtomicLong() { + val atomic = AtomicLong(1424920024888900000) + assertEquals(1424920024888900000, atomic.value) + atomic.value = 2424920024888900000 + assertEquals(2424920024888900000, atomic.value) + assertEquals(2424920024888900000, atomic.getAndSet(3424920024888900000)) + assertEquals(3424920024888900000, atomic.value) + assertTrue(atomic.compareAndSet(3424920024888900000, 4424920024888900000)) + assertFalse(atomic.compareAndSet(9, 1)) + assertEquals(4424920024888900000, atomic.value) + assertEquals(4424920024888900000, atomic.getAndAdd(100000)) + assertEquals(4424920024889000000, atomic.value) + assertEquals(4424920024890000000, atomic.addAndGet(1000000L)) + assertEquals(4424920024890000000, atomic.getAndIncrement()) + assertEquals(4424920024890000001, atomic.value) + assertEquals(4424920024890000002, atomic.incrementAndGet()) + assertEquals(4424920024890000002, atomic.value) + assertEquals(4424920024890000002, atomic.getAndDecrement()) + assertEquals(4424920024890000001, atomic.value) + assertEquals(4424920024890000000, atomic.decrementAndGet()) + assertEquals(4424920024890000000, atomic.value) + assertEquals(4424920024890000000, atomic.compareAndExchange(4424920024890000000, 5424920024890000000)) + assertEquals(5424920024890000000, atomic.compareAndExchange(18, 56)) + assertEquals(5424920024890000000, atomic.compareAndExchange(18, 56)) +} + +@Test +fun testAtomicRef() { + val atomic = AtomicReference>(listOf("a", "b", "c")) + assertEquals(listOf("a", "b", "c"), atomic.value) + atomic.value = listOf("a", "b", "a") + assertEquals(listOf("a", "b", "a"), atomic.value) + assertEquals(listOf("a", "b", "a"), atomic.getAndSet(listOf("a", "a", "a"))) + assertEquals(listOf("a", "a", "a"), atomic.value) + var cur = atomic.value + assertTrue(atomic.compareAndSet(cur, listOf("b", "b", "b"))) + assertFalse(atomic.compareAndSet(listOf("a", "a", "a"), listOf("b", "b", "b"))) + assertEquals(listOf("b", "b", "b"), atomic.value) + cur = atomic.value + assertEquals(listOf("b", "b", "b"), atomic.compareAndExchange(cur, listOf("c", "c", "c"))) + assertEquals(listOf("c", "c", "c"), atomic.compareAndExchange(cur, listOf("d", "d", "d"))) + assertEquals(listOf("c", "c", "c"), atomic.compareAndExchange(cur, listOf("c", "c", "c"))) +} + +@Test +fun testNativePtr() { + val atomic = AtomicNativePtr(NativePtr.NULL) + assertEquals(NativePtr.NULL, atomic.value) + atomic.value = NativePtr.NULL.plus(10L) + assertEquals(10L, atomic.value.toLong()) + assertTrue(atomic.compareAndSet(NativePtr.NULL.plus(10L), NativePtr.NULL.plus(20L))) + assertEquals(20L, atomic.value.toLong()) + assertFalse(atomic.compareAndSet(NativePtr.NULL.plus(10L), NativePtr.NULL.plus(20L))) + assertEquals(20L, atomic.value.toLong()) + assertEquals(NativePtr.NULL.plus(20L), atomic.compareAndExchange(NativePtr.NULL.plus(20L), NativePtr.NULL.plus(30L))) + assertEquals(NativePtr.NULL.plus(30L), atomic.compareAndExchange(NativePtr.NULL.plus(20L), NativePtr.NULL.plus(40L))) + assertEquals(NativePtr.NULL.plus(30L), atomic.compareAndExchange(NativePtr.NULL.plus(20L), NativePtr.NULL.plus(50L))) + assertEquals(30L, atomic.getAndSet(NativePtr.NULL.plus(55L)).toLong()) + assertEquals(55L, atomic.value.toLong()) +} diff --git a/kotlin-native/backend.native/tests/runtime/atomics/atomic_stress.kt b/kotlin-native/backend.native/tests/runtime/atomics/atomic_stress.kt new file mode 100644 index 00000000000..9e96baedc14 --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/atomics/atomic_stress.kt @@ -0,0 +1,92 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +@file:OptIn(FreezingIsDeprecated::class) +package runtime.atomics.atomic_stress + +import kotlin.test.* +import kotlin.native.concurrent.* +import kotlin.concurrent.* +import kotlin.concurrent.AtomicInt +import kotlin.concurrent.AtomicLong +import kotlin.concurrent.AtomicReference +import kotlin.native.internal.NativePtr + +fun testAtomicIntStress(workers: Array) { + val atomic = AtomicInt(10) + val futures = Array(workers.size, { workerIndex -> + workers[workerIndex].execute(TransferMode.SAFE, { atomic }) { + atomic -> atomic.addAndGet(1000) + } + }) + futures.forEach { + it.result + } + assertEquals(10 + 1000 * workers.size, atomic.value) +} + +fun testAtomicLongStress(workers: Array) { + val atomic = AtomicLong(10L) + val futures = Array(workers.size, { workerIndex -> + workers[workerIndex].execute(TransferMode.SAFE, { atomic }) { + atomic -> atomic.addAndGet(9999999999) + } + }) + futures.forEach { + it.result + } + assertEquals(10L + 9999999999 * workers.size, atomic.value) +} + +private class LockFreeStack { + private val top = AtomicReference?>(null) + + private class Node(val value: T, val next: Node?) + + fun isEmpty(): Boolean = top.value == null + + fun push(value: T) { + while(true) { + val cur = top.value + val upd = Node(value, cur) + if (top.compareAndSet(cur, upd)) return + } + } + + fun pop(): T? { + while(true) { + val cur = top.value + if (cur == null) return null + if (top.compareAndSet(cur, cur.next)) return cur.value + } + } +} + +fun testAtomicReferenceStress(workers: Array) { + val stack = LockFreeStack() + val writers = Array(workers.size, { workerIndex -> + workers[workerIndex].execute(TransferMode.SAFE, { stack to workerIndex}) { + (stack, workerIndex) -> stack.push(workerIndex) + } + }) + writers.forEach { it.result } + + val seen = mutableSetOf() + while(!stack.isEmpty()) { + val value = stack.pop() + assertNotNull(value) + seen.add(value) + } + assertEquals(workers.size, seen.size) +} + +@Test +fun runStressTest() { + val COUNT = 20 + val workers = Array(COUNT, { _ -> Worker.start()}) + testAtomicIntStress(workers) + testAtomicLongStress(workers) + testAtomicReferenceStress(workers) +} diff --git a/kotlin-native/backend.native/tests/runtime/basic/initializers6.kt b/kotlin-native/backend.native/tests/runtime/basic/initializers6.kt index 32d382b4e1b..5e5345c8e9b 100644 --- a/kotlin-native/backend.native/tests/runtime/basic/initializers6.kt +++ b/kotlin-native/backend.native/tests/runtime/basic/initializers6.kt @@ -9,6 +9,8 @@ package runtime.basic.initializers6 import kotlin.test.* import kotlin.native.concurrent.* +import kotlin.concurrent.* +import kotlin.concurrent.AtomicInt val aWorkerId = AtomicInt(0) val bWorkersCount = 3 @@ -21,7 +23,7 @@ object A { // Must be called by aWorker only. assertEquals(aWorkerId.value, Worker.current.id) // Only allow b workers to run, when a worker has started initialization. - bWorkerUnlocker.increment() + bWorkerUnlocker.incrementAndGet() // Only proceed with initialization, when all b workers have started executing. while (aWorkerUnlocker.value < bWorkersCount) {} // And now wait a bit, to increase probability of races. @@ -57,7 +59,7 @@ fun produceB(): String { // Wait until A has started to initialize. while (bWorkerUnlocker.value < 1) {} // Now allow A initialization to continue. - aWorkerUnlocker.increment() + aWorkerUnlocker.incrementAndGet() // And this should not've tried to init A itself. A.a + A.b }) diff --git a/kotlin-native/backend.native/tests/runtime/workers/atomic2.kt b/kotlin-native/backend.native/tests/runtime/workers/atomic2.kt deleted file mode 100644 index 0713458958f..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/atomic2.kt +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license - * that can be found in the LICENSE file. - */ - -import kotlin.test.* - -import kotlin.native.concurrent.* - -@Test -fun ctor_Int() { - val x = AtomicInt(0) - assertEquals(x.value, 0) -} - -@Test -fun ctor_Long() { - val x = AtomicLong(0) - assertEquals(x.value, 0) -} - -@Test -fun setter_Int() { - val x = AtomicInt(0) - x.value = 1 - assertEquals(x.value, 1) -} - -@Test -fun setter_Long() { - val x = AtomicLong(0) - x.value = 1 - assertEquals(x.value, 1) -} - -@Test -fun addAndGet_Int() { - val x = AtomicInt(1) - val result = x.addAndGet(2) - assertEquals(result, 1 + 2) - assertEquals(x.value, result) -} - -@Test -fun addAndGet_Long() { - val x = AtomicLong(1) - val result = x.addAndGet(2) - assertEquals(result, 1 + 2) - assertEquals(x.value, result) -} - -@Test -fun compareAndSwap_Int() { - val x = AtomicInt(0) - val successValue = x.compareAndSwap(0, 1) - assertEquals(successValue, 0) - assertEquals(x.value, 1) - val failValue = x.compareAndSwap(0, 2) - assertEquals(failValue, 1) - assertEquals(x.value, 1) -} - -@Test -fun compareAndSwap_Long() { - val x = AtomicLong(0) - val successValue = x.compareAndSwap(0, 1) - assertEquals(successValue, 0) - assertEquals(x.value, 1) - val failValue = x.compareAndSwap(0, 2) - assertEquals(failValue, 1) - assertEquals(x.value, 1) -} - -@Test -fun compareAndSet_Int() { - val x = AtomicInt(0) - val successValue = x.compareAndSet(0, 1) - assertTrue(successValue) - assertEquals(x.value, 1) - val failValue = x.compareAndSet(0, 2) - assertFalse(failValue) - assertEquals(x.value, 1) -} - -@Test -fun compareAndSet_Long() { - val x = AtomicLong(0) - val successValue = x.compareAndSet(0, 1) - assertTrue(successValue) - assertEquals(x.value, 1) - val failValue = x.compareAndSet(0, 2) - assertFalse(failValue) - assertEquals(x.value, 1) -} diff --git a/kotlin-native/backend.native/tests/runtime/workers/lazy4.kt b/kotlin-native/backend.native/tests/runtime/workers/lazy4.kt index 30279bdd185..e1ab33f2922 100644 --- a/kotlin-native/backend.native/tests/runtime/workers/lazy4.kt +++ b/kotlin-native/backend.native/tests/runtime/workers/lazy4.kt @@ -9,6 +9,8 @@ package runtime.workers.lazy4 import kotlin.test.* import kotlin.native.concurrent.* +import kotlin.concurrent.* +import kotlin.concurrent.AtomicInt const val WORKERS_COUNT = 20 @@ -24,7 +26,7 @@ fun concurrentLazyAccess(freeze: Boolean, mode: LazyThreadSafetyMode) { val initializerCallCount = AtomicInt(0) val c = C(argumentMode) { - initializerCallCount.increment() + initializerCallCount.incrementAndGet() IntHolder(42) } if (freeze) { @@ -36,7 +38,7 @@ fun concurrentLazyAccess(freeze: Boolean, mode: LazyThreadSafetyMode) { val canStart = AtomicInt(0) val futures = Array(workers.size) { i -> workers[i].execute(TransferMode.SAFE, { Triple(inited, canStart, c) }) { (inited, canStart, c) -> - inited.increment() + inited.incrementAndGet() while (canStart.value != 1) {} c.data } diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker10.kt b/kotlin-native/backend.native/tests/runtime/workers/worker10.kt index ee4ba379002..28ddd7e105f 100644 --- a/kotlin-native/backend.native/tests/runtime/workers/worker10.kt +++ b/kotlin-native/backend.native/tests/runtime/workers/worker10.kt @@ -4,6 +4,9 @@ package runtime.workers.worker10 import kotlin.test.* import kotlin.native.concurrent.* +import kotlin.concurrent.* +import kotlin.concurrent.AtomicInt +import kotlin.concurrent.AtomicReference import kotlin.native.ref.WeakReference import kotlinx.cinterop.StableRef @@ -93,14 +96,14 @@ val semaphore = AtomicInt(0) val worker = Worker.start() val future = worker.execute(TransferMode.SAFE, { null }) { val value = atomicRef.value - semaphore.increment() + semaphore.incrementAndGet() while (semaphore.value != 2) {} println(value.toString() != "") } while (semaphore.value != 1) {} atomicRef.value = null kotlin.native.runtime.GC.collect() - semaphore.increment() + semaphore.incrementAndGet() future.result worker.requestTermination().result } @@ -110,14 +113,14 @@ val semaphore = AtomicInt(0) val worker = Worker.start() val future = worker.execute(TransferMode.SAFE, { null }) { val value = stableRef.get() - semaphore.increment() + semaphore.incrementAndGet() while (semaphore.value != 2) {} println(value.toString() != "") } while (semaphore.value != 1) {} stableRef.dispose() kotlin.native.runtime.GC.collect() - semaphore.increment() + semaphore.incrementAndGet() future.result worker.requestTermination().result } @@ -133,7 +136,7 @@ val stableHolder1 = StableRef.create(("hello" to "world").freeze()) semaphore.value = 0 val future = worker.execute(TransferMode.SAFE, { WeakReference(stableHolder1.get()) }) { ensureWeakIs(it, "hello" to "world") - semaphore.increment() + semaphore.incrementAndGet() while (semaphore.value != 2) {} kotlin.native.runtime.GC.collect() ensureWeakIs(it, null) @@ -141,7 +144,7 @@ val stableHolder1 = StableRef.create(("hello" to "world").freeze()) while (semaphore.value != 1) {} stableHolder1.dispose() kotlin.native.runtime.GC.collect() - semaphore.increment() + semaphore.incrementAndGet() future.result worker.requestTermination().result } @@ -153,7 +156,7 @@ val stableHolder2 = StableRef.create(("hello" to "world").freeze()) semaphore.value = 0 val future = worker.execute(TransferMode.SAFE, { WeakReference(stableHolder2.get()) }) { val value = it.get() - semaphore.increment() + semaphore.incrementAndGet() while (semaphore.value != 2) {} kotlin.native.runtime.GC.collect() assertEquals("hello" to "world", value) @@ -161,7 +164,7 @@ val stableHolder2 = StableRef.create(("hello" to "world").freeze()) while (semaphore.value != 1) {} stableHolder2.dispose() kotlin.native.runtime.GC.collect() - semaphore.increment() + semaphore.incrementAndGet() future.result worker.requestTermination().result } @@ -171,15 +174,15 @@ val atomicRef2 = AtomicReference(Any().freeze()) semaphore.value = 0 val worker = Worker.start() val future = worker.execute(TransferMode.SAFE, { null }) { - val value = atomicRef2.compareAndSwap(null, null) - semaphore.increment() + val value = atomicRef2.compareAndExchange(null, null) + semaphore.incrementAndGet() while (semaphore.value != 2) {} assertEquals(true, value.toString() != "") } while (semaphore.value != 1) {} atomicRef2.value = null kotlin.native.runtime.GC.collect() - semaphore.increment() + semaphore.incrementAndGet() future.result worker.requestTermination().result } diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker11.kt b/kotlin-native/backend.native/tests/runtime/workers/worker11.kt index 748520270c8..3b225c0ae0e 100644 --- a/kotlin-native/backend.native/tests/runtime/workers/worker11.kt +++ b/kotlin-native/backend.native/tests/runtime/workers/worker11.kt @@ -9,6 +9,8 @@ package runtime.workers.worker11 import kotlin.test.* import kotlin.native.concurrent.* +import kotlin.concurrent.* +import kotlin.concurrent.AtomicInt import kotlinx.cinterop.convert data class Job(val index: Int, var input: Int, var counter: Int) @@ -79,7 +81,7 @@ val counters = Array(COUNT) { AtomicInt(0) } workerIndex }) { index -> assertEquals(0, counters[index].value) - counters[index].increment() + counters[index].incrementAndGet() } } futures2.forEach { it.result } diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker4.kt b/kotlin-native/backend.native/tests/runtime/workers/worker4.kt index 21fbdff2339..956e8aa9af9 100644 --- a/kotlin-native/backend.native/tests/runtime/workers/worker4.kt +++ b/kotlin-native/backend.native/tests/runtime/workers/worker4.kt @@ -7,8 +7,8 @@ package runtime.workers.worker4 import kotlin.test.* - import kotlin.native.concurrent.* +import kotlin.concurrent.AtomicInt @Test fun runTest1() { withWorker { @@ -32,13 +32,14 @@ import kotlin.native.concurrent.* assertTrue(Worker.current.processQueue()) assertEquals(1, counter.value) // Let main proceed. - counter.increment() // counter becomes 2 here. + counter.incrementAndGet() // counter becomes 2 here. assertTrue(Worker.current.park(10_000_000, true)) assertEquals(3, counter.value) }.freeze()) executeAfter(0, { - counter.increment() + counter.incrementAndGet() + Unit }.freeze()) while (counter.value < 2) { @@ -46,7 +47,8 @@ import kotlin.native.concurrent.* } executeAfter(0, { - counter.increment() + counter.incrementAndGet() + Unit }.freeze()) while (counter.value == 2) { @@ -60,7 +62,8 @@ import kotlin.native.concurrent.* val counter = AtomicInt(0) worker.executeAfter(0, { assertEquals("Lumberjack", Worker.current.name) - counter.increment() + counter.incrementAndGet() + Unit }.freeze()) while (counter.value == 0) { @@ -76,7 +79,8 @@ import kotlin.native.concurrent.* @Test fun runTest4() { val counter = AtomicInt(0) Worker.current.executeAfter(10_000, { - counter.increment() + counter.incrementAndGet() + Unit }.freeze()) assertTrue(Worker.current.park(1_000_000, process = true)) assertEquals(1, counter.value) @@ -88,7 +92,8 @@ import kotlin.native.concurrent.* withWorker { executeAfter(1000, { main.executeAfter(1, { - counter.increment() + counter.incrementAndGet() + Unit }.freeze()) }.freeze()) assertTrue(main.park(1000L * 1000 * 1000, process = true)) @@ -106,13 +111,13 @@ import kotlin.native.concurrent.* withWorker { val f1 = execute(TransferMode.SAFE, { counter }) { counter -> Worker.current.park(Long.MAX_VALUE / 1000L, process = true) - counter.increment() + counter.incrementAndGet() } // wait a bit Worker.current.park(10_000L) // submit a task val f2 = execute(TransferMode.SAFE, { counter }) { counter -> - counter.increment() + counter.incrementAndGet() } f1.consume {} f2.consume {} @@ -139,14 +144,15 @@ import kotlin.native.concurrent.* submitters.forEach { it.executeAfter(0L, { - readySubmittersCounter.increment() + readySubmittersCounter.incrementAndGet() // Wait for other submitters, to make them all start at the same time: while (readySubmittersCounter.value != numberOfSubmitters) {} // Concurrently submit tasks with matching scheduled execution time: repeat(numberOfTasks) { targetWorker.executeAfter(delayInMicroseconds, { - executedTasksCounter.increment() + executedTasksCounter.incrementAndGet() + Unit }.freeze()) } @@ -157,7 +163,8 @@ import kotlin.native.concurrent.* // the test still might hang without a fix. targetWorker.executeAfter(delayInMicroseconds + 1, { mainWorker.executeAfter(0L, { - finishedBatchesCounter.increment() + finishedBatchesCounter.incrementAndGet() + Unit }.freeze()) }.freeze()) }.freeze()) diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/concurrent/Atomics.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/concurrent/Atomics.kt new file mode 100644 index 00000000000..526984df52e --- /dev/null +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/concurrent/Atomics.kt @@ -0,0 +1,489 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +package kotlin.concurrent + +import kotlinx.cinterop.NativePtr +import kotlin.native.internal.* +import kotlin.reflect.* +import kotlin.concurrent.* +import kotlin.native.concurrent.* + +/** + * An [Int] value that is always updated atomically. + * For additional details about atomicity guarantees for reads and writes see [kotlin.concurrent.Volatile]. + * + * Legacy MM: Atomic values and freezing: this type is unique with regard to freezing. + * Namely, it provides mutating operations, while can participate in frozen subgraphs. + * So shared frozen objects can have mutable fields of [AtomicInt] type. + */ +@Frozen +@OptIn(FreezingIsDeprecated::class, ExperimentalStdlibApi::class) +@SinceKotlin("1.9") +public class AtomicInt(@Volatile public var value: Int) { + /** + * Atomically sets the value to the given [new value][newValue] and returns the old value. + */ + public fun getAndSet(newValue: Int): Int = this::value.getAndSetField(newValue) + + /** + * Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected], + * returns true if the operation was successful and false only if the current value was not equal to the expected value. + * + * Provides sequential consistent ordering guarantees and cannot fail spuriously. + */ + public fun compareAndSet(expected: Int, newValue: Int): Boolean = this::value.compareAndSetField(expected, newValue) + + /** + * Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected] + * and returns the old value in any case. + * + * Provides sequential consistent ordering guarantees and cannot fail spuriously. + */ + public fun compareAndExchange(expected: Int, newValue: Int): Int = this::value.compareAndExchangeField(expected, newValue) + + /** + * Atomically adds the [given value][delta] to the current value and returns the old value. + */ + public fun getAndAdd(delta: Int): Int = this::value.getAndAddField(delta) + + /** + * Atomically adds the [given value][delta] to the current value and returns the new value. + */ + public fun addAndGet(delta: Int): Int = this::value.getAndAddField(delta) + delta + + /** + * Atomically increments the current value by one and returns the old value. + */ + public fun getAndIncrement(): Int = this::value.getAndAddField(1) + + /** + * Atomically increments the current value by one and returns the new value. + */ + public fun incrementAndGet(): Int = this::value.getAndAddField(1) + 1 + + /** + * Atomically decrements the current value by one and returns the new value. + */ + public fun decrementAndGet(): Int = this::value.getAndAddField(-1) - 1 + + /** + * Atomically decrements the current value by one and returns the old value. + */ + public fun getAndDecrement(): Int = this::value.getAndAddField(-1) + + /** + * Atomically increments the current value by one. + */ + @Deprecated(level = DeprecationLevel.ERROR, message = "Use incrementAndGet() or getAndIncrement() instead.", + replaceWith = ReplaceWith("this.incrementAndGet()")) + public fun increment(): Unit { + addAndGet(1) + } + + /** + * Atomically decrements the current value by one. + */ + @Deprecated(level = DeprecationLevel.ERROR, message = "Use decrementAndGet() or getAndDecrement() instead.", + replaceWith = ReplaceWith("this.decrementAndGet()")) + public fun decrement(): Unit { + addAndGet(-1) + } + + /** + * Returns the string representation of the current [value]. + */ + public override fun toString(): String = value.toString() +} + +/** + * A [Long] value that is always updated atomically. + * For additional details about atomicity guarantees for reads and writes see [kotlin.concurrent.Volatile]. + * + * Legacy MM: Atomic values and freezing: this type is unique with regard to freezing. + * Namely, it provides mutating operations, while can participate in frozen subgraphs. + * So shared frozen objects can have mutable fields of [AtomicLong] type. + */ +@Frozen +@OptIn(FreezingIsDeprecated::class, ExperimentalStdlibApi::class) +@SinceKotlin("1.9") +public class AtomicLong(@Volatile public var value: Long) { + /** + * Atomically sets the value to the given [new value][newValue] and returns the old value. + */ + public fun getAndSet(newValue: Long): Long = this::value.getAndSetField(newValue) + + /** + * Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected], + * returns true if the operation was successful and false only if the current value was not equal to the expected value. + * + * Provides sequential consistent ordering guarantees and cannot fail spuriously. + */ + public fun compareAndSet(expected: Long, newValue: Long): Boolean = this::value.compareAndSetField(expected, newValue) + + /** + * Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected] + * and returns the old value in any case. + * + * Provides sequential consistent ordering guarantees and cannot fail spuriously. + */ + public fun compareAndExchange(expected: Long, newValue: Long): Long = this::value.compareAndExchangeField(expected, newValue) + + /** + * Atomically adds the [given value][delta] to the current value and returns the old value. + */ + public fun getAndAdd(delta: Long): Long = this::value.getAndAddField(delta) + + /** + * Atomically adds the [given value][delta] to the current value and returns the new value. + */ + public fun addAndGet(delta: Long): Long = this::value.getAndAddField(delta) + delta + + /** + * Atomically increments the current value by one and returns the old value. + */ + public fun getAndIncrement(): Long = this::value.getAndAddField(1L) + + /** + * Atomically increments the current value by one and returns the new value. + */ + public fun incrementAndGet(): Long = this::value.getAndAddField(1L) + 1L + + /** + * Atomically decrements the current value by one and returns the new value. + */ + public fun decrementAndGet(): Long = this::value.getAndAddField(-1L) - 1L + + /** + * Atomically decrements the current value by one and returns the old value. + */ + public fun getAndDecrement(): Long = this::value.getAndAddField(-1L) + + /** + * Atomically adds the [given value][delta] to the current value and returns the new value. + */ + @Deprecated(level = DeprecationLevel.ERROR, message = "Use addAndGet(delta: Long) instead.") + public fun addAndGet(delta: Int): Long = addAndGet(delta.toLong()) + + /** + * Atomically increments the current value by one. + */ + @Deprecated(level = DeprecationLevel.ERROR, message = "Use incrementAndGet() or getAndIncrement() instead.", + replaceWith = ReplaceWith("this.incrementAndGet()")) + public fun increment(): Unit { + addAndGet(1L) + } + + /** + * Atomically decrements the current value by one. + */ + @Deprecated(level = DeprecationLevel.ERROR, message = "Use decrementAndGet() or getAndDecrement() instead.", + replaceWith = ReplaceWith("this.decrementAndGet()")) + public fun decrement(): Unit { + addAndGet(-1L) + } + + /** + * Returns the string representation of the current [value]. + */ + public override fun toString(): String = value.toString() +} + +/** + * An object reference that is always updated atomically. + * + * Legacy MM: An atomic reference to a frozen Kotlin object. Can be used in concurrent scenarious + * but frequently shall be of nullable type and be zeroed out once no longer needed. + * Otherwise memory leak could happen. To detect such leaks [kotlin.native.internal.GC.detectCycles] + * in debug mode could be helpful. + */ +@FrozenLegacyMM +@LeakDetectorCandidate +@NoReorderFields +@OptIn(FreezingIsDeprecated::class) +@SinceKotlin("1.9") +public class AtomicReference { + private var value_: T + + // A spinlock to fix potential ARC race. + private var lock: Int = 0 + + // Optimization for speeding up access. + private var cookie: Int = 0 + + /** + * Creates a new atomic reference pointing to the [given value][value]. + * + * @throws InvalidMutabilityException with legacy MM if reference is not frozen. + */ + constructor(value: T) { + if (this.isFrozen) { + checkIfFrozen(value) + } + value_ = value + } + + /** + * The current value. + * Gets the current value or sets to the given [new value][newValue]. + * + * Legacy MM: if the [new value][newValue] value is not null, it must be frozen or permanent object. + * + * @throws InvalidMutabilityException with legacy MM if the value is not frozen or a permanent object + */ + public var value: T + get() = @Suppress("UNCHECKED_CAST")(getImpl() as T) + set(newValue) = setImpl(newValue) + + /** + * Atomically sets the value to the given [new value][newValue] and returns the old value. + */ + public fun getAndSet(newValue: T): T { + while (true) { + val old = value + if (old === newValue) { + return old + } + if (compareAndSet(old, newValue)) { + return old + } + } + } + + /** + * Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected], + * returns true if the operation was successful and false only if the current value was not equal to the expected value. + * + * Provides sequential consistent ordering guarantees and cannot fail spuriously. + * + * Comparison of values is done by reference. + */ + @GCUnsafeCall("Kotlin_AtomicReference_compareAndSet") + external public fun compareAndSet(expected: T, newValue: T): Boolean + + /** + * Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected] + * and returns the old value in any case. + * + * Provides sequential consistent ordering guarantees and cannot fail spuriously. + * + * Comparison of values is done by reference. + * + * Legacy MM: if the [new value][newValue] value is not null, it must be frozen or permanent object. + * + * @throws InvalidMutabilityException with legacy MM if the value is not frozen or a permanent object + */ + @GCUnsafeCall("Kotlin_AtomicReference_compareAndSwap") + external public fun compareAndExchange(expected: T, newValue: T): T + + /** + * Returns the string representation of the current [value]. + */ + public override fun toString(): String = + "${debugString(this)} -> ${debugString(value)}" + + // Implementation details. + @GCUnsafeCall("Kotlin_AtomicReference_set") + private external fun setImpl(newValue: Any?): Unit + + @GCUnsafeCall("Kotlin_AtomicReference_get") + private external fun getImpl(): Any? +} + +/** + * A [kotlinx.cinterop.NativePtr] value that is always updated atomically. + * For additional details about atomicity guarantees for reads and writes see [kotlin.concurrent.Volatile]. + * + * [kotlinx.cinterop.NativePtr] is a value type, hence it is stored in [AtomicNativePtr] without boxing + * and [compareAndSet], [compareAndExchange] operations perform comparison by value. + * + * Legacy MM: Atomic values and freezing: this type is unique with regard to freezing. + * Namely, it provides mutating operations, while can participate in frozen subgraphs. + * So shared frozen objects can have mutable fields of [AtomicNativePtr] type. + */ +@Frozen +@OptIn(FreezingIsDeprecated::class, ExperimentalStdlibApi::class) +@SinceKotlin("1.9") +public class AtomicNativePtr(@Volatile public var value: NativePtr) { + /** + * Atomically sets the value to the given [new value][newValue] and returns the old value. + */ + public fun getAndSet(newValue: NativePtr): NativePtr { + // Pointer types are allowed for atomicrmw xchg operand since LLVM 15.0, + // after LLVM version update, it may be implemented via getAndSetField intrinsic. + // Check: https://youtrack.jetbrains.com/issue/KT-57557 + while (true) { + val old = value + if (this::value.compareAndSetField(old, newValue)) { + return old + } + } + } + + /** + * Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected], + * returns true if the operation was successful and false only if the current value was not equal to the expected value. + * + * Provides sequential consistent ordering guarantees and cannot fail spuriously. + * + * Comparison of values is done by value. + */ + public fun compareAndSet(expected: NativePtr, newValue: NativePtr): Boolean = + this::value.compareAndSetField(expected, newValue) + + /** + * Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected] + * and returns the old value in any case. + * + * Provides sequential consistent ordering guarantees and cannot fail spuriously. + * + * Comparison of values is done by value. + */ + public fun compareAndExchange(expected: NativePtr, newValue: NativePtr): NativePtr = + this::value.compareAndExchangeField(expected, newValue) + + /** + * Returns the string representation of the current [value]. + */ + public override fun toString(): String = value.toString() +} + + +private fun idString(value: Any) = "${value.hashCode().toUInt().toString(16)}" + +private fun debugString(value: Any?): String { + if (value == null) return "null" + return "${value::class.qualifiedName}: ${idString(value)}" +} + +/** + * Compares the value of the field referenced by [this] to [expectedValue], and if they are equal, + * atomically replaces it with [newValue]. + * + * For now, it can be used only within the same file, where property is defined. + * Check https://youtrack.jetbrains.com/issue/KT-55426 for details. + * + * Comparison is done by reference or value depending on field representation. + * + * If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException] + * would be thrown. + * + * If property referenced by [this] has nontrivial setter it will not be called. + * + * Returns true if the actual field value matched [expectedValue] + * + * Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown. + */ +@PublishedApi +@TypedIntrinsic(IntrinsicType.COMPARE_AND_SET_FIELD) +internal external fun KMutableProperty0.compareAndSetField(expectedValue: T, newValue: T): Boolean + +/** + * Compares the value of the field referenced by [this] to [expectedValue], and if they are equal, + * atomically replaces it with [newValue]. + * + * For now, it can be used only within the same file, where property is defined. + * Check https://youtrack.jetbrains.com/issue/KT-55426 for details. + * + * Comparison is done by reference or value depending on field representation. + * + * If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException] + * would be thrown. + * + * If property referenced by [this] has nontrivial setter it will not be called. + * + * Returns the field value before operation. + * + * Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown. + */ +@PublishedApi +@TypedIntrinsic(IntrinsicType.COMPARE_AND_EXCHANGE_FIELD) +internal external fun KMutableProperty0.compareAndExchangeField(expectedValue: T, newValue: T): T + +/** + * Atomically sets value of the field referenced by [this] to [newValue] and returns old field value. + * + * For now, it can be used only within the same file, where property is defined. + * Check https://youtrack.jetbrains.com/issue/KT-55426 for details. + * + * If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException] + * would be thrown. + * + * If property referenced by [this] has nontrivial setter it will not be called. + * + * Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown. + */ +@PublishedApi +@TypedIntrinsic(IntrinsicType.GET_AND_SET_FIELD) +internal external fun KMutableProperty0.getAndSetField(newValue: T): T + + +/** + * Atomically increments value of the field referenced by [this] by [delta] and returns old field value. + * + * For now, it can be used only within the same file, where property is defined. + * Check https://youtrack.jetbrains.com/issue/KT-55426 for details. + * + * If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException] + * would be thrown. + * + * If property referenced by [this] has nontrivial setter it will not be called. + * + * Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown. + */ +@PublishedApi +@TypedIntrinsic(IntrinsicType.GET_AND_ADD_FIELD) +internal external fun KMutableProperty0.getAndAddField(delta: Short): Short + +/** + * Atomically increments value of the field referenced by [this] by [delta] and returns old field value. + * + * For now, it can be used only within the same file, where property is defined. + * Check https://youtrack.jetbrains.com/issue/KT-55426 for details. + * + * If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException] + * would be thrown. + * + * If property referenced by [this] has nontrivial setter it will not be called. + * + * Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown. + */ +@PublishedApi +@TypedIntrinsic(IntrinsicType.GET_AND_ADD_FIELD) +internal external fun KMutableProperty0.getAndAddField(newValue: Int): Int + +/** + * Atomically increments value of the field referenced by [this] by [delta] and returns old field value. + * + * For now, it can be used only within the same file, where property is defined. + * Check https://youtrack.jetbrains.com/issue/KT-55426 for details. + * + * If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException] + * would be thrown. + * + * If property referenced by [this] has nontrivial setter it will not be called. + * + * Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown. + */ +@PublishedApi +@TypedIntrinsic(IntrinsicType.GET_AND_ADD_FIELD) +internal external fun KMutableProperty0.getAndAddField(newValue: Long): Long + +/** + * Atomically increments value of the field referenced by [this] by [delta] and returns old field value. + * + * For now, it can be used only within the same file, where property is defined. + * Check https://youtrack.jetbrains.com/issue/KT-55426 for details. + * + * If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException] + * would be thrown. + * + * If property referenced by [this] has nontrivial setter it will not be called. + * + * Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown. + */ +@PublishedApi +@TypedIntrinsic(IntrinsicType.GET_AND_ADD_FIELD) +internal external fun KMutableProperty0.getAndAddField(newValue: Byte): Byte diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/coroutines/SafeContinuationNative.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/coroutines/SafeContinuationNative.kt index 491fa5fb211..9edf1fa8309 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/coroutines/SafeContinuationNative.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/coroutines/SafeContinuationNative.kt @@ -25,6 +25,7 @@ internal actual constructor( public actual override val context: CoroutineContext get() = delegate.context + @Suppress("DEPRECATION") private var resultRef = FreezableAtomicReference(initialResult) public actual override fun resumeWith(result: Result) { @@ -54,4 +55,4 @@ internal actual constructor( else -> result // either COROUTINE_SUSPENDED or data } } -} \ No newline at end of file +} diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Atomics.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Atomics.kt index a64e98e6f70..e5ee27fab32 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Atomics.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Atomics.kt @@ -11,7 +11,8 @@ import kotlin.reflect.* import kotlin.concurrent.* /** - * Wrapper around [Int] with atomic synchronized operations. + * An [Int] value that is always updated atomically. + * For additional details about atomicity guarantees for reads and writes see [kotlin.concurrent.Volatile]. * * Legacy MM: Atomic values and freezing: this type is unique with regard to freezing. * Namely, it provides mutating operations, while can participate in frozen subgraphs. @@ -19,57 +20,85 @@ import kotlin.concurrent.* */ @Frozen @OptIn(FreezingIsDeprecated::class, ExperimentalStdlibApi::class) +@Deprecated("Use kotlin.concurrent.AtomicInt instead.", ReplaceWith("kotlin.concurrent.AtomicInt")) +@DeprecatedSinceKotlin(warningSince = "1.9") public class AtomicInt(public @Volatile var value: Int) { /** - * Increments the value by [delta] and returns the new value. + * Atomically sets the value to the given [new value][newValue] and returns the old value. + */ + public fun getAndSet(newValue: Int): Int = this::value.getAndSetField(newValue) + + /** + * Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected], + * returns true if the operation was successful and false only if the current value was not equal to the expected value. * - * @param delta the value to add - * @return the new value + * Provides sequential consistent ordering guarantees and cannot fail spuriously. + */ + public fun compareAndSet(expected: Int, newValue: Int): Boolean = this::value.compareAndSetField(expected, newValue) + + /** + * Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected] + * and returns the old value in any case. + * + * Provides sequential consistent ordering guarantees and cannot fail spuriously. + */ + public fun compareAndSwap(expected: Int, newValue: Int): Int = this::value.compareAndExchangeField(expected, newValue) + + /** + * Atomically adds the [given value][delta] to the current value and returns the old value. + */ + public fun getAndAdd(delta: Int): Int = this::value.getAndAddField(delta) + + /** + * Atomically adds the [given value][delta] to the current value and returns the new value. */ public fun addAndGet(delta: Int): Int = this::value.getAndAddField(delta) + delta /** - * Compares value with [expected] and replaces it with [new] value if values matches. - * - * @param expected the expected value - * @param new the new value - * @return the old value + * Atomically increments the current value by one and returns the old value. */ - public fun compareAndSwap(expected: Int, new: Int): Int = this::value.compareAndSwapField(expected, new) + public fun getAndIncrement(): Int = this::value.getAndAddField(1) /** - * Compares value with [expected] and replaces it with [new] value if values matches. - * - * @param expected the expected value - * @param new the new value - * @return true if successful + * Atomically increments the current value by one and returns the new value. */ - public fun compareAndSet(expected: Int, new: Int): Boolean = this::value.compareAndSetField(expected, new) + public fun incrementAndGet(): Int = this::value.getAndAddField(1) + 1 /** - * Increments value by one. + * Atomically decrements the current value by one and returns the new value. */ + public fun decrementAndGet(): Int = this::value.getAndAddField(-1) - 1 + + /** + * Atomically decrements the current value by one and returns the old value. + */ + public fun getAndDecrement(): Int = this::value.getAndAddField(-1) + + /** + * Atomically increments the current value by one. + */ + @Deprecated("Use incrementAndGet() or getAndIncrement() instead.", ReplaceWith("this.incrementAndGet()")) public fun increment(): Unit { addAndGet(1) } /** - * Decrements value by one. + * Atomically decrements the current value by one. */ + @Deprecated("Use decrementAndGet() or getAndDecrement() instead.", ReplaceWith("this.decrementAndGet()")) public fun decrement(): Unit { addAndGet(-1) } /** * Returns the string representation of this object. - * - * @return the string representation */ public override fun toString(): String = value.toString() } /** - * Wrapper around [Long] with atomic synchronized operations. + * A [Long] value that is always updated atomically. + * For additional details about atomicity guarantees for reads and writes see [kotlin.concurrent.Volatile]. * * Legacy MM: Atomic values and freezing: this type is unique with regard to freezing. * Namely, it provides mutating operations, while can participate in frozen subgraphs. @@ -77,113 +106,90 @@ public class AtomicInt(public @Volatile var value: Int) { */ @Frozen @OptIn(FreezingIsDeprecated::class, ExperimentalStdlibApi::class) -public class AtomicLong(public @Volatile var value: Long = 0) { +@Deprecated("Use kotlin.concurrent.AtomicLong instead.", ReplaceWith("kotlin.concurrent.AtomicLong")) +@DeprecatedSinceKotlin(warningSince = "1.9") +public class AtomicLong(public @Volatile var value: Long = 0L) { + /** + * Atomically sets the value to the given [new value][newValue] and returns the old value. + */ + public fun getAndSet(newValue: Long): Long = this::value.getAndSetField(newValue) /** - * Increments the value by [delta] and returns the new value. + * Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected], + * returns true if the operation was successful and false only if the current value was not equal to the expected value. * - * @param delta the value to add - * @return the new value + * Provides sequential consistent ordering guarantees and cannot fail spuriously. + */ + public fun compareAndSet(expected: Long, newValue: Long): Boolean = this::value.compareAndSetField(expected, newValue) + + /** + * Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected] + * and returns the old value in any case. + * + * Provides sequential consistent ordering guarantees and cannot fail spuriously. + */ + public fun compareAndSwap(expected: Long, newValue: Long): Long = this::value.compareAndExchangeField(expected, newValue) + + /** + * Atomically adds the [given value][delta] to the current value and returns the old value. + */ + public fun getAndAdd(delta: Long): Long = this::value.getAndAddField(delta) + + /** + * Atomically adds the [given value][delta] to the current value and returns the new value. */ public fun addAndGet(delta: Long): Long = this::value.getAndAddField(delta) + delta /** - * Increments the value by [delta] and returns the new value. - * - * @param delta the value to add - * @return the new value + * Atomically increments the current value by one and returns the old value. */ + public fun getAndIncrement(): Long = this::value.getAndAddField(1L) + + /** + * Atomically increments the current value by one and returns the new value. + */ + public fun incrementAndGet(): Long = this::value.getAndAddField(1L) + 1L + + /** + * Atomically decrements the current value by one and returns the new value. + */ + public fun decrementAndGet(): Long = this::value.getAndAddField(-1L) - 1L + + /** + * Atomically decrements the current value by one and returns the old value. + */ + public fun getAndDecrement(): Long = this::value.getAndAddField(-1L) + + /** + * Atomically adds the [given value][delta] to the current value and returns the new value. + */ + @Deprecated("Use addAndGet(delta: Long) instead.") public fun addAndGet(delta: Int): Long = addAndGet(delta.toLong()) /** - * Compares value with [expected] and replaces it with [new] value if values matches. - * - * @param expected the expected value - * @param new the new value - * @return the old value - */ - public fun compareAndSwap(expected: Long, new: Long): Long = this::value.compareAndSwapField(expected, new) - - /** - * Compares value with [expected] and replaces it with [new] value if values matches. - * - * @param expected the expected value - * @param new the new value - * @return true if successful, false if state is unchanged - */ - public fun compareAndSet(expected: Long, new: Long): Boolean = this::value.compareAndSetField(expected, new) - - /** - * Increments value by one. + * Atomically increments the current value by one. */ + @Deprecated("Use incrementAndGet() or getAndIncrement() instead.", ReplaceWith("this.incrementAndGet()")) public fun increment(): Unit { addAndGet(1L) } /** - * Decrements value by one. + * Atomically decrements the current value by one. */ + @Deprecated("Use decrementAndGet() or getAndDecrement() instead.", ReplaceWith("this.decrementAndGet()")) fun decrement(): Unit { addAndGet(-1L) } /** * Returns the string representation of this object. - * - * @return the string representation of this object */ public override fun toString(): String = value.toString() } /** - * Wrapper around [kotlinx.cinterop.NativePtr] with atomic synchronized operations. - * - * Legacy MM: Atomic values and freezing: this type is unique with regard to freezing. - * Namely, it provides mutating operations, while can participate in frozen subgraphs. - * So shared frozen objects can have mutable fields of [AtomicNativePtr] type. - */ -@Frozen -@OptIn(FreezingIsDeprecated::class, ExperimentalStdlibApi::class) -public class AtomicNativePtr(public @Volatile var value: NativePtr) { - - /** - * Compares value with [expected] and replaces it with [new] value if values matches. - * - * @param expected the expected value - * @param new the new value - * @return the old value - */ - public fun compareAndSwap(expected: NativePtr, new: NativePtr): NativePtr = - this::value.compareAndSwapField(expected, new) - - /** - * Compares value with [expected] and replaces it with [new] value if values matches. - * - * @param expected the expected value - * @param new the new value - * @return true if successful - */ - public fun compareAndSet(expected: NativePtr, new: NativePtr): Boolean = - this::value.compareAndSetField(expected, new) - - /** - * Returns the string representation of this object. - * - * @return string representation of this object - */ - public override fun toString(): String = value.toString() -} - - -private fun idString(value: Any) = "${value.hashCode().toUInt().toString(16)}" - -private fun debugString(value: Any?): String { - if (value == null) return "null" - return "${value::class.qualifiedName}: ${idString(value)}" -} - -/** - * Wrapper around Kotlin object with atomic operations. + * An object reference that is always updated atomically. * * Legacy MM: An atomic reference to a frozen Kotlin object. Can be used in concurrent scenarious * but frequently shall be of nullable type and be zeroed out once no longer needed. @@ -194,6 +200,8 @@ private fun debugString(value: Any?): String { @LeakDetectorCandidate @NoReorderFields @OptIn(FreezingIsDeprecated::class) +@Deprecated("Use kotlin.concurrent.AtomicReference instead.", ReplaceWith("kotlin.concurrent.AtomicReference")) +@DeprecatedSinceKotlin(warningSince = "1.9") public class AtomicReference { private var value_: T @@ -204,7 +212,8 @@ public class AtomicReference { private var cookie: Int = 0 /** - * Creates a new atomic reference pointing to given [ref]. + * Creates a new atomic reference pointing to the [given value][value]. + * * @throws InvalidMutabilityException with legacy MM if reference is not frozen. */ constructor(value: T) { @@ -215,70 +224,139 @@ public class AtomicReference { } /** - * The referenced value. - * Gets the value or sets the [new] value. - * Legacy MM: if [new] value is not null, it must be frozen or permanent object. + * The current value. + * Gets the current value or sets to the given [new value][newValue]. + * + * Legacy MM: if the [new value][newValue] value is not null, it must be frozen or permanent object. * * @throws InvalidMutabilityException with legacy MM if the value is not frozen or a permanent object */ public var value: T get() = @Suppress("UNCHECKED_CAST")(getImpl() as T) - set(new) = setImpl(new) + set(newValue) = setImpl(newValue) /** - * Compares value with [expected] and replaces it with [new] value if values matches. - * Note that comparison is identity-based, not value-based. - * - * Legacy MM: if [new] value is not null, it must be frozen or permanent object. - * - * @param expected the expected value - * @param new the new value - * @throws InvalidMutabilityException with legacy MM if the value is not frozen or a permanent object - * @return the old value + * Atomically sets the value to the given [new value][newValue] and returns the old value. */ - @GCUnsafeCall("Kotlin_AtomicReference_compareAndSwap") - external public fun compareAndSwap(expected: T, new: T): 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 - * @return true if successful - */ - @GCUnsafeCall("Kotlin_AtomicReference_compareAndSet") - external public fun compareAndSet(expected: T, new: T): Boolean - - /** - * Returns the string representation of this object. - * - * @return string representation of this object - */ - public override fun toString(): String = - "${debugString(this)} -> ${debugString(value)}" - - // TODO: Consider making this public. - internal fun swap(new: T): T { + public fun getAndSet(newValue: T): T { while (true) { val old = value - if (old === new) { + if (old === newValue) { return old } - if (compareAndSet(old, new)) { + if (compareAndSet(old, newValue)) { return old } } } + /** + * Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected], + * returns true if the operation was successful and false only if the current value was not equal to the expected value. + * + * Provides sequential consistent ordering guarantees and cannot fail spuriously. + * + * Comparison of values is done by reference. + */ + @GCUnsafeCall("Kotlin_AtomicReference_compareAndSet") + external public fun compareAndSet(expected: T, newValue: T): Boolean + + /** + * Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected] + * and returns the old value in any case. + * + * Provides sequential consistent ordering guarantees and cannot fail spuriously. + * + * Comparison of values is done by reference. + * + * Legacy MM: if the [new value][newValue] value is not null, it must be frozen or permanent object. + * + * @throws InvalidMutabilityException with legacy MM if the value is not frozen or a permanent object + */ + @GCUnsafeCall("Kotlin_AtomicReference_compareAndSwap") + external public fun compareAndSwap(expected: T, newValue: T): T + + /** + * Returns the string representation of this object. + */ + public override fun toString(): String = + "${debugString(this)} -> ${debugString(value)}" + // Implementation details. @GCUnsafeCall("Kotlin_AtomicReference_set") - private external fun setImpl(new: Any?): Unit + private external fun setImpl(newValue: Any?): Unit @GCUnsafeCall("Kotlin_AtomicReference_get") private external fun getImpl(): Any? } +/** + * A [kotlinx.cinterop.NativePtr] value that is always updated atomically. + * For additional details about atomicity guarantees for reads and writes see [kotlin.concurrent.Volatile]. + * + * [kotlinx.cinterop.NativePtr] is a value type, hence it is stored in [AtomicNativePtr] without boxing + * and [compareAndSet], [compareAndSwap] operations perform comparison by value. + * + * Legacy MM: Atomic values and freezing: this type is unique with regard to freezing. + * Namely, it provides mutating operations, while can participate in frozen subgraphs. + * So shared frozen objects can have mutable fields of [AtomicNativePtr] type. + */ +@Frozen +@OptIn(FreezingIsDeprecated::class, ExperimentalStdlibApi::class) +@Deprecated("Use kotlin.concurrent.AtomicNativePtr instead.", ReplaceWith("kotlin.concurrent.AtomicNativePtr")) +@DeprecatedSinceKotlin(warningSince = "1.9") +public class AtomicNativePtr(public @Volatile var value: NativePtr) { + /** + * Atomically sets the value to the given [new value][newValue] and returns the old value. + */ + public fun getAndSet(newValue: NativePtr): NativePtr { + // Pointer types are allowed for atomicrmw xchg operand since LLVM 15.0, + // after LLVM version update, it may be implemented via getAndSetField intrinsic. + // Check: https://youtrack.jetbrains.com/issue/KT-57557 + while (true) { + val old = value + if (this::value.compareAndSetField(old, newValue)) { + return old + } + } + } + + /** + * Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected], + * returns true if the operation was successful and false only if the current value was not equal to the expected value. + * + * Provides sequential consistent ordering guarantees and cannot fail spuriously. + * + * Comparison of values is done by value. + */ + public fun compareAndSet(expected: NativePtr, newValue: NativePtr): Boolean = + this::value.compareAndSetField(expected, newValue) + + /** + * Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected] + * and returns the old value in any case. + * + * Provides sequential consistent ordering guarantees and cannot fail spuriously. + * + * Comparison of values is done by value. + */ + public fun compareAndSwap(expected: NativePtr, newValue: NativePtr): NativePtr = + this::value.compareAndExchangeField(expected, newValue) + + /** + * Returns the string representation of this object. + */ + public override fun toString(): String = value.toString() +} + + +private fun idString(value: Any) = "${value.hashCode().toUInt().toString(16)}" + +private fun debugString(value: Any?): String { + if (value == null) return "null" + return "${value::class.qualifiedName}: ${idString(value)}" +} + /** * Note: this class is useful only with legacy memory manager. Please use [AtomicReference] instead. * @@ -291,6 +369,8 @@ public class AtomicReference { @LeakDetectorCandidate @ExportTypeInfo("theFreezableAtomicReferenceTypeInfo") @FreezingIsDeprecated +@Deprecated("Use kotlin.concurrent.AtomicReference instead.", ReplaceWith("kotlin.concurrent.AtomicReference")) +@DeprecatedSinceKotlin(warningSince = "1.9") public class FreezableAtomicReference(private var value_: T) { // A spinlock to fix potential ARC race. private var lock: Int = 0 @@ -300,53 +380,57 @@ public class FreezableAtomicReference(private var value_: T) { /** * The referenced value. - * Gets the value or sets the [new] value. If [new] value is not null, + * Gets the value or sets to the given [new value][newValue]. If the [new value][newValue] 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) { + set(newValue) { if (this.isShareable()) - setImpl(new) + setImpl(newValue) else - value_ = new + value_ = newValue } /** - * Compares value with [expected] and replaces it with [new] value if values matches. - * Legacy MM: If [new] value is not null and object is frozen, it must be frozen or permanent object. + * Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected] + * and returns the old value in any case. + * + * Legacy MM: If the [new value][newValue] 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 + * @param newValue the new value * @throws InvalidMutabilityException with legacy MM if the value is not frozen or a permanent object * @return the old value */ - public fun compareAndSwap(expected: T, new: T): T { + public fun compareAndSwap(expected: T, newValue: T): T { return if (this.isShareable()) { - @Suppress("UNCHECKED_CAST")(compareAndSwapImpl(expected, new) as T) + @Suppress("UNCHECKED_CAST")(compareAndSwapImpl(expected, newValue) as T) } else { val old = value_ - if (old === expected) value_ = new + if (old === expected) value_ = newValue old } } /** - * Compares value with [expected] and replaces it with [new] value if values matches. + * Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected] + * and returns true if operation was successful. + * * Note that comparison is identity-based, not value-based. * * @param expected the expected value - * @param new the new value + * @param newValue the new value * @return true if successful */ - public fun compareAndSet(expected: T, new: T): Boolean { + public fun compareAndSet(expected: T, newValue: T): Boolean { if (this.isShareable()) - return compareAndSetImpl(expected, new) + return compareAndSetImpl(expected, newValue) val old = value_ if (old === expected) { - value_ = new + value_ = newValue return true } else { return false @@ -362,13 +446,13 @@ public class FreezableAtomicReference(private var value_: T) { "${debugString(this)} -> ${debugString(value)}" // TODO: Consider making this public. - internal fun swap(new: T): T { + internal fun swap(newValue: T): T { while (true) { val old = value - if (old === new) { + if (old === newValue) { return old } - if (compareAndSet(old, new)) { + if (compareAndSet(old, newValue)) { return old } } @@ -376,145 +460,14 @@ public class FreezableAtomicReference(private var value_: T) { // Implementation details. @GCUnsafeCall("Kotlin_AtomicReference_set") - private external fun setImpl(new: Any?): Unit + private external fun setImpl(newValue: Any?): Unit @GCUnsafeCall("Kotlin_AtomicReference_get") private external fun getImpl(): Any? @GCUnsafeCall("Kotlin_AtomicReference_compareAndSwap") - private external fun compareAndSwapImpl(expected: Any?, new: Any?): Any? + private external fun compareAndSwapImpl(expected: Any?, newValue: Any?): Any? @GCUnsafeCall("Kotlin_AtomicReference_compareAndSet") - private external fun compareAndSetImpl(expected: Any?, new: Any?): Boolean + private external fun compareAndSetImpl(expected: Any?, newValue: Any?): Boolean } - - -/** - * Compares the value of the field referenced by [this] to [expectedValue], and if they are equal, - * atomically replaces it with [newValue]. - * - * For now, it can be used only within the same file, where property is defined. - * Check https://youtrack.jetbrains.com/issue/KT-55426 for details. - * - * Comparison is done by reference or value depending on field representation. - * - * If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException] - * would be thrown. - * - * If property referenced by [this] has nontrivial setter it will not be called. - * - * Returns true if the actual field value matched [expectedValue] - * - * Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown. - */ -@PublishedApi -@TypedIntrinsic(IntrinsicType.COMPARE_AND_SET_FIELD) -internal external fun KMutableProperty0.compareAndSetField(expectedValue: T, newValue: T): Boolean - -/** - * Compares the value of the field referenced by [this] to [expectedValue], and if they are equal, - * atomically replaces it with [newValue]. - * - * For now, it can be used only within the same file, where property is defined. - * Check https://youtrack.jetbrains.com/issue/KT-55426 for details. - * - * Comparison is done by reference or value depending on field representation. - * - * If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException] - * would be thrown. - * - * If property referenced by [this] has nontrivial setter it will not be called. - * - * Returns true if the actual field value before operation. - * - * Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown. - */ -@PublishedApi -@TypedIntrinsic(IntrinsicType.COMPARE_AND_SWAP_FIELD) -internal external fun KMutableProperty0.compareAndSwapField(expectedValue: T, newValue: T): T - -/** - * Atomically sets value of the field referenced by [this] to [newValue] and returns old field value. - * - * For now, it can be used only within the same file, where property is defined. - * Check https://youtrack.jetbrains.com/issue/KT-55426 for details. - * - * If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException] - * would be thrown. - * - * If property referenced by [this] has nontrivial setter it will not be called. - * - * Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown. - */ -@PublishedApi -@TypedIntrinsic(IntrinsicType.GET_AND_SET_FIELD) -internal external fun KMutableProperty0.getAndSetField(newValue: T): T - - -/** - * Atomically increments value of the field referenced by [this] by [delta] and returns old field value. - * - * For now, it can be used only within the same file, where property is defined. - * Check https://youtrack.jetbrains.com/issue/KT-55426 for details. - * - * If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException] - * would be thrown. - * - * If property referenced by [this] has nontrivial setter it will not be called. - * - * Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown. - */ -@PublishedApi -@TypedIntrinsic(IntrinsicType.GET_AND_ADD_FIELD) -internal external fun KMutableProperty0.getAndAddField(delta: Short): Short - -/** - * Atomically increments value of the field referenced by [this] by [delta] and returns old field value. - * - * For now, it can be used only within the same file, where property is defined. - * Check https://youtrack.jetbrains.com/issue/KT-55426 for details. - * - * If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException] - * would be thrown. - * - * If property referenced by [this] has nontrivial setter it will not be called. - * - * Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown. - */ -@PublishedApi -@TypedIntrinsic(IntrinsicType.GET_AND_ADD_FIELD) -internal external fun KMutableProperty0.getAndAddField(newValue: Int): Int - -/** - * Atomically increments value of the field referenced by [this] by [delta] and returns old field value. - * - * For now, it can be used only within the same file, where property is defined. - * Check https://youtrack.jetbrains.com/issue/KT-55426 for details. - * - * If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException] - * would be thrown. - * - * If property referenced by [this] has nontrivial setter it will not be called. - * - * Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown. - */ -@PublishedApi -@TypedIntrinsic(IntrinsicType.GET_AND_ADD_FIELD) -internal external fun KMutableProperty0.getAndAddField(newValue: Long): Long - -/** - * Atomically increments value of the field referenced by [this] by [delta] and returns old field value. - * - * For now, it can be used only within the same file, where property is defined. - * Check https://youtrack.jetbrains.com/issue/KT-55426 for details. - * - * If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException] - * would be thrown. - * - * If property referenced by [this] has nontrivial setter it will not be called. - * - * Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown. - */ -@PublishedApi -@TypedIntrinsic(IntrinsicType.GET_AND_ADD_FIELD) -internal external fun KMutableProperty0.getAndAddField(newValue: Byte): Byte diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Lazy.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Lazy.kt index 115c0f3b102..a342be2eea0 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Lazy.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Lazy.kt @@ -3,10 +3,12 @@ * that can be found in the LICENSE file. */ +@file:Suppress("DEPRECATION") package kotlin.native.concurrent import kotlin.experimental.ExperimentalNativeApi import kotlin.native.internal.Frozen +import kotlin.concurrent.AtomicReference @FreezingIsDeprecated internal class FreezeAwareLazyImpl(initializer: () -> T) : Lazy { @@ -94,7 +96,7 @@ internal class AtomicLazyImpl(initializer: () -> T) : Lazy { override val value: T get() { - if (value_.compareAndSwap(UNINITIALIZED, INITIALIZING) === UNINITIALIZED) { + if (value_.compareAndExchange(UNINITIALIZED, INITIALIZING) === UNINITIALIZED) { // We execute exclusively here. val ctor = initializer_.value if (ctor != null && initializer_.compareAndSet(ctor, null)) { @@ -204,4 +206,4 @@ internal class SafePublicationLazyImpl(initializer: () -> T) : Lazy { override fun isInitialized(): Boolean = valueRef.value !== UNINITIALIZED override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet." -} \ No newline at end of file +} diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Lock.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Lock.kt index 9cbbbb0f91f..8fb0c1b99e8 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Lock.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Lock.kt @@ -7,6 +7,7 @@ package kotlin.native.concurrent import kotlin.experimental.ExperimentalNativeApi import kotlin.native.internal.Frozen +import kotlin.concurrent.AtomicInt @ThreadLocal @OptIn(FreezingIsDeprecated::class) @@ -24,11 +25,11 @@ internal class Lock { fun lock() { val lockData = CurrentThread.id.hashCode() loop@ do { - val old = locker_.compareAndSwap(0, lockData) + val old = locker_.compareAndExchange(0, lockData) when (old) { lockData -> { // Was locked by us already. - reenterCount_.increment() + reenterCount_.incrementAndGet() break@loop } 0 -> { @@ -42,10 +43,10 @@ internal class Lock { fun unlock() { if (reenterCount_.value > 0) { - reenterCount_.decrement() + reenterCount_.decrementAndGet() } else { val lockData = CurrentThread.id.hashCode() - val old = locker_.compareAndSwap(lockData, 0) + val old = locker_.compareAndExchange(lockData, 0) assert(old == lockData) } } @@ -58,4 +59,4 @@ internal inline fun locked(lock: Lock, block: () -> R): R { } finally { lock.unlock() } -} \ No newline at end of file +} diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/ObjectTransfer.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/ObjectTransfer.kt index f22772d25c7..794400c0a54 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/ObjectTransfer.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/ObjectTransfer.kt @@ -7,6 +7,7 @@ package kotlin.native.concurrent import kotlinx.cinterop.* import kotlin.native.internal.Frozen +import kotlin.concurrent.AtomicNativePtr /** * Note: modern Kotlin/Native memory manager allows to share objects between threads without additional ceremonies, diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/IntrinsicType.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/IntrinsicType.kt index f284ebf8d22..e0115795d49 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/IntrinsicType.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/IntrinsicType.kt @@ -83,11 +83,11 @@ internal class IntrinsicType { // Atomic const val COMPARE_AND_SET_FIELD = "COMPARE_AND_SET_FIELD" - const val COMPARE_AND_SWAP_FIELD = "COMPARE_AND_SWAP_FIELD" + const val COMPARE_AND_EXCHANGE_FIELD = "COMPARE_AND_EXCHANGE_FIELD" const val GET_AND_SET_FIELD = "GET_AND_SET_FIELD" const val GET_AND_ADD_FIELD = "GET_AND_ADD_FIELD" const val COMPARE_AND_SET = "COMPARE_AND_SET" - const val COMPARE_AND_SWAP = "COMPARE_AND_SWAP" + const val COMPARE_AND_EXCHANGE = "COMPARE_AND_EXCHANGE" const val GET_AND_SET = "GET_AND_SET" const val GET_AND_ADD = "GET_AND_ADD" } diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt index f7e1cb29f6d..e2d199073b4 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt @@ -3,13 +3,14 @@ * that can be found in the LICENSE file. */ +@file:Suppress("DEPRECATION", "DEPRECATION_ERROR") // Char.toInt() package kotlin.native.internal import kotlin.experimental.ExperimentalNativeApi import kotlin.internal.getProgressionLastElement import kotlin.reflect.KClass -import kotlin.native.concurrent.FreezableAtomicReference import kotlin.native.concurrent.freeze +import kotlin.native.concurrent.FreezableAtomicReference @ExportForCppRuntime @PublishedApi diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/random/Random.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/random/Random.kt index c2720545b88..8184728bd24 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/random/Random.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/random/Random.kt @@ -4,7 +4,7 @@ */ package kotlin.random -import kotlin.native.concurrent.AtomicLong +import kotlin.concurrent.AtomicLong import kotlin.system.getTimeNanos /** diff --git a/libraries/stdlib/native-wasm/src/kotlin/text/regex/AbstractCharClass.kt b/libraries/stdlib/native-wasm/src/kotlin/text/regex/AbstractCharClass.kt index e6a85835bb3..62ec3413ab5 100644 --- a/libraries/stdlib/native-wasm/src/kotlin/text/regex/AbstractCharClass.kt +++ b/libraries/stdlib/native-wasm/src/kotlin/text/regex/AbstractCharClass.kt @@ -25,7 +25,7 @@ package kotlin.text.regex import kotlin.experimental.ExperimentalNativeApi import kotlin.collections.associate -import kotlin.native.concurrent.AtomicReference +import kotlin.concurrent.AtomicReference import kotlin.native.concurrent.freeze import kotlin.native.BitSet import kotlin.native.FreezingIsDeprecated @@ -653,7 +653,7 @@ internal abstract class AbstractCharClass : SpecialToken() { fun getPredefinedClass(name: String, negative: Boolean): AbstractCharClass { val charClass = classCacheMap[name] ?: throw PatternSyntaxException("No such character class") val cachedClass = classCache[charClass.ordinal].value ?: run { - classCache[charClass.ordinal].compareAndSwap(null, charClass.factory().freeze()) + classCache[charClass.ordinal].compareAndExchange(null, charClass.factory().freeze()) classCache[charClass.ordinal].value!! } return cachedClass.getValue(negative) diff --git a/libraries/stdlib/wasm/stubs/atomics.kt b/libraries/stdlib/wasm/stubs/atomics.kt new file mode 100644 index 00000000000..c8789699d1a --- /dev/null +++ b/libraries/stdlib/wasm/stubs/atomics.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package kotlin.concurrent + +// Only for compatibility with shared K/N stdlib code + +internal class AtomicReference(public var value: T) { + public fun compareAndExchange(expected: T, new: T): T { + if (value == expected) { + val old = value + value = new + return old + } + return value + } + public fun compareAndSet(expected: T, new: T): Boolean { + if (value == expected) { + value = new + return true + } + return false + } +} diff --git a/libraries/stdlib/wasm/stubs/native/concurrent.kt b/libraries/stdlib/wasm/stubs/native/concurrent.kt index 8c388627704..e65fd609240 100644 --- a/libraries/stdlib/wasm/stubs/native/concurrent.kt +++ b/libraries/stdlib/wasm/stubs/native/concurrent.kt @@ -12,21 +12,3 @@ internal val Any?.isFrozen @Suppress("NOTHING_TO_INLINE") internal inline fun T.freeze(): T = this - -internal class AtomicReference(public var value: T) { - public fun compareAndSwap(expected: T, new: T): T { - if (value == expected) { - val old = value - value = new - return old - } - return value - } - public fun compareAndSet(expected: T, new: T): Boolean { - if (value == expected) { - value = new - return true - } - return false - } -}