From 298c4f8c322baad9616167605696bdb8c32ca77f Mon Sep 17 00:00:00 2001 From: mvicsokolova Date: Fri, 21 Jul 2023 15:30:20 +0000 Subject: [PATCH] [K/N] Introduce atomic arrays in K/N stdlib MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces API for AtomicIntArray, AtomicLongArray and AtomicArray. The current set of functions is implemented via atomic arrays intrinsics (see KT-58360) and provides sequentially consistent memory ordering guarantees and no spurious failures in compareAndSet/compareAndExchange operations. For details see: KT-60608 Merge-request: KT-MR-11071 Merged-by: Maria Sokolova --- .../backend.native/tests/build.gradle | 8 + .../runtime/atomics/atomic_array_smoke.kt | 596 ++++++++++++++++++ .../runtime/atomics/atomic_array_stress.kt | 87 +++ .../kotlin/kotlin/concurrent/AtomicArrays.kt | 517 ++++++++++++++- 4 files changed, 1206 insertions(+), 2 deletions(-) create mode 100644 kotlin-native/backend.native/tests/runtime/atomics/atomic_array_smoke.kt create mode 100644 kotlin-native/backend.native/tests/runtime/atomics/atomic_array_stress.kt diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index ed55817c150..47178c14d9c 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -1044,6 +1044,14 @@ standaloneTest("atomic1") { source = "runtime/atomics/atomic1.kt" } +tasks.register("atomicArraySmokeTest", KonanLocalTest) { + source = "runtime/atomics/atomic_array_smoke.kt" +} + +tasks.register("atomicArrayStressTest", KonanLocalTest) { + source = "runtime/atomics/atomic_array_stress.kt" +} + tasks.register("lazy0", KonanLocalTest) { useGoldenData = true source = "runtime/workers/lazy0.kt" diff --git a/kotlin-native/backend.native/tests/runtime/atomics/atomic_array_smoke.kt b/kotlin-native/backend.native/tests/runtime/atomics/atomic_array_smoke.kt new file mode 100644 index 00000000000..23b807a56a7 --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/atomics/atomic_array_smoke.kt @@ -0,0 +1,596 @@ +/* + * 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(kotlin.ExperimentalStdlibApi::class) + +package runtime.atomics.atomic_array_smoke + +import kotlin.test.* +import kotlin.concurrent.* + +@Test +fun testAtomicIntArrayConstructor() { + val arr1 = AtomicIntArray(6) + assertEquals(arr1[4], 0) + assertEquals(arr1.length, 6) + + val arr2 = AtomicIntArray(10) { i: Int -> i * 10 } + assertEquals(arr2[4], 40) + assertEquals(arr2.length, 10) + + val emptyArr = AtomicIntArray(0) + assertEquals(emptyArr.length, 0) + + assertFailsWith { + val arrNegativeSize = AtomicIntArray(-5) + } +} + +@Test +fun testAtomicIntArrayGetElement() { + val atomicIntArr = AtomicIntArray(10) { i: Int -> i * 10 } + for (i in 0 until atomicIntArr.length) { + assertEquals(i * 10, atomicIntArr[i], "testAtomicIntArrayGetElement: FAIL $i") + } + assertFailsWith { + val res = atomicIntArr[22] + }.let { ex -> + assertEquals("The index 22 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } + assertFailsWith { + val res = atomicIntArr[-1] + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicIntArraySetElement() { + val atomicIntArr = AtomicIntArray(10) + for (i in 0 until atomicIntArr.length) { + atomicIntArr[i] = i * 10 + } + for (i in 0 until atomicIntArr.length) { + assertEquals(i * 10, atomicIntArr[i], "testAtomicIntArraySetElement: FAIL $i") + } + assertFailsWith { + atomicIntArr[22] = 100 + }.let { ex -> + assertEquals("The index 22 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } + assertFailsWith { + atomicIntArr[-1] = 100 + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicIntArrayCompareAndExchange() { + val atomicIntArr = AtomicIntArray(10) { i: Int -> i * 10 } + val res1 = atomicIntArr.compareAndExchange(2, 20, 222) // success + assertTrue(res1 == 20 && atomicIntArr[2] == 222, "testAtomicIntArrayCompareAndExchange: FAIL 1") + val res2 = atomicIntArr.compareAndExchange(2, 222, 2222) // success + assertTrue(res2 == 222 && atomicIntArr[2] == 2222, "testAtomicIntArrayCompareAndExchange: FAIL 2") + val res3 = atomicIntArr.compareAndExchange(2, 223, 22222) // should fail + assertTrue(res3 == 2222 && atomicIntArr[2] == 2222, "testAtomicIntArrayCompareAndExchange: FAIL 3") + val res4 = atomicIntArr.compareAndExchange(9, 10, 999) // should fail + assertTrue(res4 == 90 && atomicIntArr[9] == 90, "testAtomicIntArrayCompareAndExchange: FAIL 4") + assertFailsWith { + val res = atomicIntArr.compareAndExchange(10, 33535, 39530) + }.let { ex -> + assertEquals("The index 10 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } + assertFailsWith { + val res = atomicIntArr.compareAndExchange(-1, 33535, 39530) + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicIntArrayCompareAndSet() { + val atomicIntArr = AtomicIntArray(10) { i: Int -> i * 10 } + val res1 = atomicIntArr.compareAndSet(2, 20, 222) // success + assertTrue(res1 && atomicIntArr[2] == 222, "testAtomicIntArrayCompareAndSet: FAIL 1") + val res2 = atomicIntArr.compareAndSet(2, 222, 2222) // success + assertTrue(res2 && atomicIntArr[2] == 2222, "testAtomicIntArrayCompareAndSet: FAIL 2") + val res3 = atomicIntArr.compareAndSet(2, 223, 22222) // should fail + assertTrue(!res3 && atomicIntArr[2] == 2222, "testAtomicIntArrayCompareAndSet: FAIL 3") + val res4 = atomicIntArr.compareAndSet(9, 10, 999) // should fail + assertTrue(!res4 && atomicIntArr[9] == 90, "testAtomicIntArrayCompareAndSet: FAIL 4") + assertFailsWith { + val res = atomicIntArr.compareAndSet(10, 33535, 39530) + }.let { ex -> + assertEquals("The index 10 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } + assertFailsWith { + val res = atomicIntArr.compareAndSet(-1, 33535, 39530) + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicIntArrayGetAndSet() { + val atomicIntArr = AtomicIntArray(10) { i: Int -> i * 10 } + assertEquals(20, atomicIntArr.getAndSet(2, 200), "testAtomicIntArrayGetAndSet: FAIL 1") + assertEquals(200, atomicIntArr.getAndSet(2, 2000), "testAtomicIntArrayGetAndSet: FAIL 2") + assertEquals(2000, atomicIntArr[2], "testAtomicIntArrayGetAndSet: FAIL 3") + assertFailsWith { + val res = atomicIntArr.getAndSet(22, 33535) + }.let { ex -> + assertEquals("The index 22 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } + assertFailsWith { + val res = atomicIntArr.getAndSet(-1, 33535) + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicIntArrayGetAndAdd() { + val atomicIntArr = AtomicIntArray(10) { i: Int -> i * 10 } + assertEquals(10, atomicIntArr.getAndAdd(1, 100), "testAtomicIntArrayGetAndAdd: FAIL 1") + assertEquals(110, atomicIntArr[1], "testAtomicIntArrayGetAndAdd: FAIL 2") + assertEquals(110, atomicIntArr.getAndAdd(1, -100), "testAtomicIntArrayGetAndAdd: FAIL 3") + assertEquals(10, atomicIntArr[1], "testAtomicIntArrayGetAndAdd: FAIL 4") + assertFailsWith { + val res = atomicIntArr.getAndAdd(22, 33535) + }.let { ex -> + assertEquals("The index 22 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } + assertFailsWith { + val res = atomicIntArr.getAndAdd(-1, 33535) + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicIntArrayAddAndGet() { + val atomicIntArr = AtomicIntArray(10) { i: Int -> i * 10 } + assertEquals(110, atomicIntArr.addAndGet(1, 100), "testAtomicIntArrayAddAndGet: FAIL 1") + assertEquals(110, atomicIntArr[1], "testAtomicIntArrayAddAndGet: FAIL 2") + assertEquals(10, atomicIntArr.addAndGet(1, -100), "testAtomicIntArrayAddAndGet: FAIL 3") + assertEquals(10, atomicIntArr[1], "testAtomicIntArrayAddAndGet: FAIL 4") + assertFailsWith { + val res = atomicIntArr.addAndGet(22, 33535) + }.let { ex -> + assertEquals("The index 22 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } + assertFailsWith { + val res = atomicIntArr.addAndGet(-1, 33535) + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicIntArrayGetAndIncrement() { + val atomicIntArr = AtomicIntArray(10) { i: Int -> i * 10 } + assertEquals(10, atomicIntArr.getAndIncrement(1), "testAtomicIntArrayGetAndIncrement: FAIL 1") + assertEquals(11, atomicIntArr[1], "testAtomicIntArrayGetAndIncrement: FAIL 2") + assertEquals(11, atomicIntArr.getAndIncrement(1), "testAtomicIntArrayGetAndIncrement: FAIL 3") + assertEquals(12, atomicIntArr[1], "testAtomicIntArrayGetAndIncrement: FAIL 4") + assertFailsWith { + val res = atomicIntArr.getAndIncrement(22) + }.let { ex -> + assertEquals("The index 22 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } + assertFailsWith { + val res = atomicIntArr.getAndIncrement(-1) + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicIntArrayIncrementAndGet() { + val atomicIntArr = AtomicIntArray(10) { i: Int -> i * 10 } + assertEquals(11, atomicIntArr.incrementAndGet(1), "testAtomicIntArrayIncrementAndGet: FAIL 1") + assertEquals(11, atomicIntArr[1], "testAtomicIntArrayIncrementAndGet: FAIL 2") + assertEquals(12, atomicIntArr.incrementAndGet(1), "testAtomicIntArrayIncrementAndGet: FAIL 3") + assertEquals(12, atomicIntArr[1], "testAtomicIntArrayIncrementAndGet: FAIL 4") + assertFailsWith { + val res = atomicIntArr.incrementAndGet(22) + }.let { ex -> + assertEquals("The index 22 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } + assertFailsWith { + val res = atomicIntArr.incrementAndGet(-1) + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicIntArrayGetAndDecrement() { + val atomicIntArr = AtomicIntArray(10) { i: Int -> i * 10 } + assertEquals(10, atomicIntArr.getAndDecrement(1), "testAtomicIntArrayGetAndDecrement: FAIL 1") + assertEquals(9, atomicIntArr[1], "testAtomicIntArrayGetAndDecrement: FAIL 2") + assertEquals(9, atomicIntArr.getAndDecrement(1), "testAtomicIntArrayGetAndDecrement: FAIL 3") + assertEquals(8, atomicIntArr[1], "testAtomicIntArrayGetAndDecrement: FAIL 4") + assertFailsWith { + val res = atomicIntArr.getAndDecrement(22) + }.let { ex -> + assertEquals("The index 22 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } + assertFailsWith { + val res = atomicIntArr.getAndDecrement(-1) + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicIntArrayDecrementAndGet() { + val atomicIntArr = AtomicIntArray(10) { i: Int -> i * 10 } + assertEquals(9, atomicIntArr.decrementAndGet(1), "testAtomicIntArrayDecrementAndGet: FAIL 1") + assertEquals(9, atomicIntArr[1], "testAtomicIntArrayDecrementAndGet: FAIL 2") + assertEquals(8, atomicIntArr.decrementAndGet(1), "testAtomicIntArrayDecrementAndGet: FAIL 3") + assertEquals(8, atomicIntArr[1], "testAtomicIntArrayDecrementAndGet: FAIL 4") + assertFailsWith { + val res = atomicIntArr.decrementAndGet(22) + }.let { ex -> + assertEquals("The index 22 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } + assertFailsWith { + val res = atomicIntArr.decrementAndGet(-1) + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicIntArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicLongArrayConstructor() { + val arr1 = AtomicLongArray(6) + assertEquals(arr1[4], 0L) + assertEquals(arr1.length, 6) + val arr2 = AtomicLongArray(10) { i: Int -> i * 10L } + assertEquals(arr2[4], 40L) + assertEquals(arr2.length, 10) + + val emptyArr = AtomicLongArray(0) + assertEquals(emptyArr.length, 0) + + assertFailsWith { + val arrNegativeSize = AtomicLongArray(-5) + } +} + +@Test +fun testAtomicLongArrayGetElement() { + val atomicLongArr = AtomicLongArray(10) { i: Int -> i * 10L } + for (i in 0 until atomicLongArr.length) { + assertEquals(i * 10L, atomicLongArr[i], "testAtomicLongArrayGetElement: FAIL $i") + } + assertFailsWith { + val res = atomicLongArr[22] + }.let { ex -> + assertEquals("The index 22 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } + assertFailsWith { + val res = atomicLongArr[-1] + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicLongArraySetElement() { + val atomicLongArr = AtomicLongArray(10) + for (i in 0 until atomicLongArr.length) { + atomicLongArr[i] = i * 10L + } + for (i in 0 until atomicLongArr.length) { + assertEquals(i * 10L, atomicLongArr[i], "testAtomicLongArraySetElement: FAIL $i") + } + assertFailsWith { + atomicLongArr[10] = 3998009 + }.let { ex -> + assertEquals("The index 10 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } + assertFailsWith { + atomicLongArr[-1] = 3998009 + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicLongArrayCompareAndExchange() { + val atomicLongArr = AtomicLongArray(10) { i: Int -> i * 10L } + val res1 = atomicLongArr.compareAndExchange(2, 20L, 222L) // success + assertTrue(res1 == 20L && atomicLongArr[2] == 222L, "testAtomicLongArrayCompareAndExchange: FAIL 1") + val res2 = atomicLongArr.compareAndExchange(2, 222L, 2222L) // success + assertTrue(res2 == 222L && atomicLongArr[2] == 2222L, "testAtomicLongArrayCompareAndExchange: FAIL 2") + val res3 = atomicLongArr.compareAndExchange(2, 223L, 22222L) // should fail + assertTrue(res3 == 2222L && atomicLongArr[2] == 2222L, "testAtomicLongArrayCompareAndExchange: FAIL 3") + val res4 = atomicLongArr.compareAndExchange(9, 10L, 999L) // should fail + assertTrue(res4 == 90L && atomicLongArr[9] == 90L, "testAtomicLongArrayCompareAndExchange: FAIL 4") + assertFailsWith { + atomicLongArr.compareAndExchange(10, 9353, 39058308) + }.let { ex -> + assertEquals("The index 10 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } + assertFailsWith { + atomicLongArr.compareAndExchange(-1, 9353, 39058308) + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicLongArrayCompareAndSet() { + val atomicLongArr = AtomicLongArray(10) { i: Int -> i * 10L } + val res1 = atomicLongArr.compareAndSet(2, 20L, 222L) // success + assertTrue(res1 && atomicLongArr[2] == 222L, "testAtomicLongArrayCompareAndSet: FAIL 1") + val res2 = atomicLongArr.compareAndSet(2, 222L, 2222L) // success + assertTrue(res2 && atomicLongArr[2] == 2222L, "testAtomicLongArrayCompareAndSet: FAIL 2") + val res3 = atomicLongArr.compareAndSet(2, 223L, 22222L) // should fail + assertTrue(!res3 && atomicLongArr[2] == 2222L, "testAtomicLongArrayCompareAndSet: FAIL 3") + val res4 = atomicLongArr.compareAndSet(9, 10L, 999L) // should fail + assertTrue(!res4 && atomicLongArr[9] == 90L, "testAtomicLongArrayCompareAndSet: FAIL 4") + assertFailsWith { + atomicLongArr.compareAndSet(10, 9353, 39058308) + }.let { ex -> + assertEquals("The index 10 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } + assertFailsWith { + atomicLongArr.compareAndSet(-1, 9353, 39058308) + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicLongArrayGetAndSet() { + val atomicLongArr = AtomicLongArray(10) { i: Int -> i * 10L } + assertEquals(20L, atomicLongArr.getAndSet(2, 200L), "testAtomicLongArrayGetAndSet: FAIL 1") + assertEquals(200L, atomicLongArr.getAndSet(2, 2000L), "testAtomicLongArrayGetAndSet: FAIL 2") + assertEquals(2000L, atomicLongArr[2], "testAtomicLongArrayGetAndSet: FAIL 3") + assertFailsWith { + val res = atomicLongArr.getAndSet(22, 9353) + }.let { ex -> + assertEquals("The index 22 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } + assertFailsWith { + val res = atomicLongArr.getAndSet(-1, 9353) + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicLongArrayGetAndAdd() { + val atomicLongArr = AtomicLongArray(10) { i: Int -> i * 10L } + assertEquals(10L, atomicLongArr.getAndAdd(1, 100L), "testAtomicLongArrayGetAndAdd: FAIL 1") + assertEquals(110L, atomicLongArr[1], "testAtomicLongArrayGetAndAdd: FAIL 2") + assertEquals(110L, atomicLongArr.getAndAdd(1, -100L), "testAtomicLongArrayGetAndAdd: FAIL 3") + assertEquals(10L, atomicLongArr[1], "testAtomicLongArrayGetAndAdd: FAIL 4") + assertFailsWith { + val res = atomicLongArr.getAndAdd(100, 9353) + }.let { ex -> + assertEquals("The index 100 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } + assertFailsWith { + val res = atomicLongArr.getAndAdd(-1, 9353) + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicLongArrayAddAndGet() { + val atomicLongArr = AtomicLongArray(10) { i: Int -> i * 10L } + assertEquals(110L, atomicLongArr.addAndGet(1, 100L), "testAtomicLongArrayAddAndGet: FAIL 1") + assertEquals(110L, atomicLongArr[1], "testAtomicLongArrayAddAndGet: FAIL 2") + assertEquals(10L, atomicLongArr.addAndGet(1, -100L), "testAtomicLongArrayAddAndGet: FAIL 3") + assertEquals(10L, atomicLongArr[1], "testAtomicLongArrayAddAndGet: FAIL 4") + assertFailsWith { + val res = atomicLongArr.addAndGet(22, 33535) + }.let { ex -> + assertEquals("The index 22 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } + assertFailsWith { + val res = atomicLongArr.addAndGet(-1, 33535) + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicLongArrayGetAndIncrement() { + val atomicLongArr = AtomicLongArray(10) { i: Int -> i * 10L } + assertEquals(10L, atomicLongArr.getAndIncrement(1), "testAtomicLongArrayGetAndIncrement: FAIL 1") + assertEquals(11L, atomicLongArr[1], "testAtomicLongArrayGetAndIncrement: FAIL 2") + assertEquals(11L, atomicLongArr.getAndIncrement(1), "testAtomicLongArrayGetAndIncrement: FAIL 3") + assertEquals(12L, atomicLongArr[1], "testAtomicLongArrayGetAndIncrement: FAIL 4") + assertFailsWith { + val res = atomicLongArr.getAndIncrement(22) + }.let { ex -> + assertEquals("The index 22 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } + assertFailsWith { + val res = atomicLongArr.addAndGet(-1, 33535) + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicLongArrayIncrementAndGet() { + val atomicLongArr = AtomicLongArray(10) { i: Int -> i * 10L } + assertEquals(11L, atomicLongArr.incrementAndGet(1), "testAtomicLongArrayIncrementAndGet: FAIL 1") + assertEquals(11L, atomicLongArr[1], "testAtomicLongArrayIncrementAndGet: FAIL 2") + assertEquals(12L, atomicLongArr.incrementAndGet(1), "testAtomicLongArrayIncrementAndGet: FAIL 3") + assertEquals(12L, atomicLongArr[1], "testAtomicLongArrayIncrementAndGet: FAIL 4") + assertFailsWith { + val res = atomicLongArr.incrementAndGet(22) + }.let { ex -> + assertEquals("The index 22 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } + assertFailsWith { + val res = atomicLongArr.incrementAndGet(-1) + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicLongArrayGetAndDecrement() { + val atomicLongArr = AtomicLongArray(10) { i: Int -> i * 10L } + assertEquals(10L, atomicLongArr.getAndDecrement(1), "testAtomicLongArrayGetAndDecrement: FAIL 1") + assertEquals(9L, atomicLongArr[1], "testAtomicLongArrayGetAndDecrement: FAIL 2") + assertEquals(9L, atomicLongArr.getAndDecrement(1), "testAtomicLongArrayGetAndDecrement: FAIL 3") + assertEquals(8L, atomicLongArr[1], "testAtomicLongArrayGetAndDecrement: FAIL 4") + assertFailsWith { + val res = atomicLongArr.getAndDecrement(22) + }.let { ex -> + assertEquals("The index 22 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } + assertFailsWith { + val res = atomicLongArr.getAndDecrement(-1) + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicLongArrayDecrementAndGet() { + val atomicLongArr = AtomicLongArray(10) { i: Int -> i * 10L } + assertEquals(9L, atomicLongArr.decrementAndGet(1), "testAtomicLongArrayDecrementAndGet: FAIL 1") + assertEquals(9L, atomicLongArr[1], "testAtomicLongArrayDecrementAndGet: FAIL 2") + assertEquals(8L, atomicLongArr.decrementAndGet(1), "testAtomicLongArrayDecrementAndGet: FAIL 3") + assertEquals(8L, atomicLongArr[1], "testAtomicLongArrayDecrementAndGet: FAIL 4") + assertFailsWith { + val res = atomicLongArr.decrementAndGet(22) + }.let { ex -> + assertEquals("The index 22 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } + assertFailsWith { + val res = atomicLongArr.decrementAndGet(-1) + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicLongArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicArrayConstructor() { + val arr2 = AtomicArray(10) { null } + assertEquals(arr2[4], null) + assertEquals(arr2.length, 10) + + val emptyArr = AtomicArray(0) { "aa" } + assertEquals(emptyArr.length, 0) + + assertFailsWith { + val arrNegativeSize = AtomicArray(-5) { "aa" } + } +} + +@Test +fun testAtomicArrayGetElement() { + val refArr = AtomicArray(10) { i -> "a$i" } + for (i in 0 until refArr.length) { + assertEquals("a$i", refArr[i], "testAtomicArrayGetElement: FAIL $i") + } + assertFailsWith { + val res = refArr[100] + }.let { ex -> + assertEquals("The index 100 is out of the bounds of the AtomicArray with size 10.", ex.message) + } + assertFailsWith { + val res = refArr[-1] + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicArraySetElement() { + val refArr = AtomicArray(10) { null } + for (i in 0 until refArr.length) { + refArr[i] = "a$i" + } + for (i in 0 until refArr.length) { + assertEquals("a$i", refArr[i], "testAtomicArraySetElement: FAIL $i") + } + assertFailsWith { + refArr[100] = "aaa" + }.let { ex -> + assertEquals("The index 100 is out of the bounds of the AtomicArray with size 10.", ex.message) + } + assertFailsWith { + refArr[-1] = "aaa" + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicArrayCompareAndExchange() { + val refArr = AtomicArray(10) { null } + val newValue = "aaa" + val res1 = refArr.compareAndExchange(3, null, newValue) + assertTrue(res1 == null && refArr[3] == newValue, "testAtomicArrayCompareAndExchange: FAIL 1") + val res2 = refArr.compareAndExchange(3, newValue, "bbb") + assertTrue(res2 == newValue && refArr[3] == "bbb", "testAtomicArrayCompareAndExchange: FAIL 2") + val res3 = refArr.compareAndExchange(3, newValue, "ccc") + assertTrue(res3 == "bbb" && refArr[3] == "bbb", "testAtomicArrayCompareAndExchange: FAIL 3") + assertFailsWith { + refArr.compareAndExchange(10, "aaa", "jdndkj") + }.let { ex -> + assertEquals("The index 10 is out of the bounds of the AtomicArray with size 10.", ex.message) + } + assertFailsWith { + refArr.compareAndExchange(-1, "aaa", "jdndkj") + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicArrayCompareAndSet() { + val refArr = AtomicArray(10) { null } + val newValue = "aaa" + val res1 = refArr.compareAndSet(3, null, newValue) + assertTrue(res1 && refArr[3] == newValue, "testAtomicArrayCompareAndSet: FAIL 1") + val res2 = refArr.compareAndSet(3, newValue, "bbb") + assertTrue(res2 && refArr[3] == "bbb", "testAtomicArrayCompareAndSet: FAIL 2") + val res3 = refArr.compareAndSet(3, newValue, "ccc") + assertTrue(!res3 && refArr[3] == "bbb", "testAtomicArrayCompareAndSet: FAIL 3") + assertFailsWith { + refArr.compareAndSet(10, "aaa", "jdndkj") + }.let { ex -> + assertEquals("The index 10 is out of the bounds of the AtomicArray with size 10.", ex.message) + } + assertFailsWith { + refArr.compareAndSet(-1, "aaa", "jdndkj") + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicArray with size 10.", ex.message) + } +} + +@Test +fun testAtomicArrayGetAndSet() { + val refArr = AtomicArray(10) { null } + val res4 = refArr.getAndSet(4, "aaa") + assertEquals(null, res4, "testAtomicArrayGetAndSet: FAIL 1") + val res5 = refArr.getAndSet(4, "bbb") + assertEquals("aaa", res5, "testAtomicArrayGetAndSet: FAIL 2") + assertFailsWith { + refArr.getAndSet(10, "aaa") + }.let { ex -> + assertEquals("The index 10 is out of the bounds of the AtomicArray with size 10.", ex.message) + } + assertFailsWith { + refArr.getAndSet(-1, "aaa") + }.let { ex -> + assertEquals("The index -1 is out of the bounds of the AtomicArray with size 10.", ex.message) + } +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/atomics/atomic_array_stress.kt b/kotlin-native/backend.native/tests/runtime/atomics/atomic_array_stress.kt new file mode 100644 index 00000000000..9d324656f33 --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/atomics/atomic_array_stress.kt @@ -0,0 +1,87 @@ +/* + * 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(kotlin.ExperimentalStdlibApi::class) + +package runtime.atomics.atomic_array_stress + +import kotlin.test.* +import kotlin.concurrent.* +import kotlin.native.concurrent.* + +fun testAtomicIntArrayStress(workers: Array) { + val intArr = AtomicIntArray(10) + val futures = Array(workers.size, { workerIndex -> + workers[workerIndex].execute(TransferMode.SAFE, { intArr }) { intArr -> + for (i in 0 until 500) { + val index = (0 until intArr.length).random() + intArr.incrementAndGet(index) + } + } + }) + futures.forEach { + it.result + } + var sum = 0 + for (i in 0 until intArr.length) { + sum += intArr[i] + } + assertEquals(workers.size * 500, sum) +} + +fun testAtomicLongArrayStress(workers: Array) { + val longArr = AtomicLongArray(10) + val futures = Array(workers.size, { workerIndex -> + workers[workerIndex].execute(TransferMode.SAFE, { longArr }) { longArr -> + for (i in 0 until 500) { + val index = (0 until longArr.length).random() + longArr.incrementAndGet(index) + } + } + }) + futures.forEach { + it.result + } + var sum = 0L + for (i in 0 until longArr.length) { + sum += longArr[i] + } + assertEquals(workers.size.toLong() * 500, sum) +} + +private class A(val n: Int) + +fun testAtomicArrayStress(workers: Array) { + val refArr = AtomicArray(10) { A(0) } + val futures = Array(workers.size, { workerIndex -> + workers[workerIndex].execute(TransferMode.SAFE, { refArr }) { refArr -> + for (i in 0 until 500) { + val index = (0 until refArr.length).random() + while(true) { + val cur = refArr[index] + val newValue = A(((cur as A).n + 1)) + if (refArr.compareAndSet(index, cur, newValue)) break + } + } + } + }) + futures.forEach { + it.result + } + var sum = 0 + for (i in 0 until refArr.length) { + sum += (refArr[i] as A).n + } + assertEquals(workers.size * 500, sum) +} + +@Test +fun runStressTest() { + val COUNT = 20 + val workers = Array(COUNT, { _ -> Worker.start()}) + testAtomicIntArrayStress(workers) + testAtomicLongArrayStress(workers) + testAtomicArrayStress(workers) +} \ No newline at end of file diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/concurrent/AtomicArrays.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/concurrent/AtomicArrays.kt index 99c179ced38..793e43ae25f 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/concurrent/AtomicArrays.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/concurrent/AtomicArrays.kt @@ -9,6 +9,519 @@ import kotlin.native.internal.* import kotlin.reflect.* import kotlin.concurrent.* import kotlin.native.concurrent.* +import kotlin.internal.RequireKotlin +import kotlin.internal.RequireKotlinVersionKind + +/** + * An [IntArray] in which elements are always updated atomically. + * For additional details about atomicity guarantees for reads and writes see [kotlin.concurrent.Volatile]. + */ +@SinceKotlin("1.9") +@RequireKotlin(version = "1.9.20", versionKind = RequireKotlinVersionKind.COMPILER_VERSION) +@ExperimentalStdlibApi +public class AtomicIntArray { + private val array: IntArray + + /** + * Creates a new [AtomicIntArray] of the given [size], with all elements initialized to zero. + * + * @throws RuntimeException if the specified [size] is negative. + */ + public constructor(size: Int) { + array = IntArray(size) + } + + /** + * Creates a new [AtomicIntArray] filled with elements of the given [array]. + */ + @PublishedApi + internal constructor(array: IntArray) { + this.array = array.copyOf() + } + + /** + * Returns the number of elements in the array. + */ + public val length: Int get() = array.size + + /** + * Atomically gets the value of the element at the given [index]. + * + * Provides sequential consistent ordering guarantees. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public operator fun get(index: Int): Int { + checkBounds(index) + return array.atomicGet(index) + } + + /** + * Atomically sets the value of the element at the given [index] to the [new value][newValue]. + * + * Provides sequential consistent ordering guarantees. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public operator fun set(index: Int, newValue: Int): Unit { + checkBounds(index) + array.atomicSet(index, newValue) + } + + /** + * Atomically sets the value of the element at the given [index] to the [new value][newValue] + * and returns the old value of the element. + * + * Provides sequential consistent ordering guarantees. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public fun getAndSet(index: Int, newValue: Int): Int { + checkBounds(index) + return array.getAndSet(index, newValue) + } + + /** + * Atomically sets the value of the element at the given [index] to the [new value][newValue] + * if the current value equals the [expected value][expectedValue]. + * Returns true if the operation was successful and false only if the current value of the element was not equal to the expected value. + * + * Provides sequential consistent ordering guarantees and never fails spuriously. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public fun compareAndSet(index: Int, expectedValue: Int, newValue: Int): Boolean { + checkBounds(index) + return array.compareAndSet(index, expectedValue, newValue) + } + + /** + * Atomically sets the value of the element at the given [index] to the [new value][newValue] + * if the current value equals the [expected value][expectedValue] and returns the old value of the element in any case. + * + * Provides sequential consistent ordering guarantees and never fails spuriously. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public fun compareAndExchange(index: Int, expectedValue: Int, newValue: Int): Int { + checkBounds(index) + return array.compareAndExchange(index, expectedValue, newValue) + } + + /** + * Atomically adds the given [delta] to the element at the given [index] and returns the old value of the element. + * + * Provides sequential consistent ordering guarantees. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public fun getAndAdd(index: Int, delta: Int): Int { + checkBounds(index) + return array.getAndAdd(index, delta) + } + + /** + * Atomically adds the given [delta] to the element at the given [index] and returns the new value of the element. + * + * Provides sequential consistent ordering guarantees. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public fun addAndGet(index: Int, delta: Int): Int { + checkBounds(index) + return array.getAndAdd(index, delta) + delta + } + + /** + * Atomically increments the element at the given [index] by one and returns the old value of the element. + * + * Provides sequential consistent ordering guarantees. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public fun getAndIncrement(index: Int): Int { + checkBounds(index) + return array.getAndAdd(index, 1) + } + + /** + * Atomically increments the element at the given [index] by one and returns the new value of the element. + * + * Provides sequential consistent ordering guarantees. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public fun incrementAndGet(index: Int): Int { + checkBounds(index) + return array.getAndAdd(index, 1) + 1 + } + + /** + * Atomically decrements the element at the given [index] by one and returns the old value of the element. + * + * Provides sequential consistent ordering guarantees. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public fun getAndDecrement(index: Int): Int { + checkBounds(index) + return array.getAndAdd(index, -1) + } + + /** + * Atomically decrements the element at the given [index] by one and returns the new value of the element. + * + * Provides sequential consistent ordering guarantees. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public fun decrementAndGet(index: Int): Int { + checkBounds(index) + return array.getAndAdd(index, -1) - 1 + } + + /** + * Returns the string representation of the underlying [IntArray][array]. + */ + public override fun toString(): String = array.toString() + + private fun checkBounds(index: Int) { + if (index < 0 || index >= array.size) throw IndexOutOfBoundsException("The index $index is out of the bounds of the AtomicIntArray with size ${array.size}.") + } +} + +/** + * Creates a new [AtomicIntArray] of the given [size], where each element is initialized by calling the given [init] function. + * + * The function [init] is called for each array element sequentially starting from the first one. + * It should return the value for an array element given its index. + * + * @throws RuntimeException if the specified [size] is negative. + */ +@SinceKotlin("1.9") +@RequireKotlin(version = "1.9.20", versionKind = RequireKotlinVersionKind.COMPILER_VERSION) +@ExperimentalStdlibApi +public inline fun AtomicIntArray(size: Int, init: (Int) -> Int): AtomicIntArray { + val inner = IntArray(size) + for (index in 0 until size) { + inner[index] = init(index) + } + return AtomicIntArray(inner) +} + +/** + * An [LongArray] in which elements are always updated atomically. + * For additional details about atomicity guarantees for reads and writes see [kotlin.concurrent.Volatile]. + */ +@SinceKotlin("1.9") +@RequireKotlin(version = "1.9.20", versionKind = RequireKotlinVersionKind.COMPILER_VERSION) +@ExperimentalStdlibApi +public class AtomicLongArray { + private val array: LongArray + + /** + * Creates a new [AtomicLongArray] of the given [size], with all elements initialized to zero. + * + * @throws RuntimeException if the specified [size] is negative. + */ + public constructor(size: Int) { + array = LongArray(size) + } + + /** + * Creates a new [AtomicLongArray] filled with elements of the given [array]. + */ + @PublishedApi + internal constructor(array: LongArray) { + this.array = array.copyOf() + } + + /** + * Returns the number of elements in the array. + */ + public val length: Int get() = array.size + + /** + * Atomically gets the value of the element at the given [index]. + * + * Provides sequential consistent ordering guarantees. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public operator fun get(index: Int): Long { + checkBounds(index) + return array.atomicGet(index) + } + + /** + * Atomically sets the value of the element at the given [index] to the [new value][newValue]. + * + * Provides sequential consistent ordering guarantees. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public operator fun set(index: Int, newValue: Long): Unit { + checkBounds(index) + array.atomicSet(index, newValue) + } + + /** + * Atomically sets the value of the element at the given [index] to the [new value][newValue] + * and returns the old value of the element. + * + * Provides sequential consistent ordering guarantees. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public fun getAndSet(index: Int, newValue: Long): Long { + checkBounds(index) + return array.getAndSet(index, newValue) + } + + /** + * Atomically sets the value of the element at the given [index] to the [new value][newValue] + * if the current value equals the [expected value][expectedValue]. + * Returns true if the operation was successful and false only if the current value of the element was not equal to the expected value. + * + * Provides sequential consistent ordering guarantees and never fails spuriously. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public fun compareAndSet(index: Int, expectedValue: Long, newValue: Long): Boolean { + checkBounds(index) + return array.compareAndSet(index, expectedValue, newValue) + } + + /** + * Atomically sets the value of the element at the given [index] to the [new value][newValue] + * if the current value equals the [expected value][expectedValue] and returns the old value of the element in any case. + * + * Provides sequential consistent ordering guarantees and never fails spuriously. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public fun compareAndExchange(index: Int, expectedValue: Long, newValue: Long): Long { + checkBounds(index) + return array.compareAndExchange(index, expectedValue, newValue) + } + + /** + * Atomically adds the given [delta] to the element at the given [index] and returns the old value of the element. + * + * Provides sequential consistent ordering guarantees. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public fun getAndAdd(index: Int, delta: Long): Long { + checkBounds(index) + return array.getAndAdd(index, delta) + } + + /** + * Atomically adds the given [delta] to the element at the given [index] and returns the new value of the element. + * + * Provides sequential consistent ordering guarantees. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public fun addAndGet(index: Int, delta: Long): Long { + checkBounds(index) + return array.getAndAdd(index, delta) + delta + } + + /** + * Atomically increments the element at the given [index] by one and returns the old value of the element. + * + * Provides sequential consistent ordering guarantees. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public fun getAndIncrement(index: Int): Long { + checkBounds(index) + return array.getAndAdd(index, 1L) + } + + /** + * Atomically increments the element at the given [index] by one and returns the new value of the element. + * + * Provides sequential consistent ordering guarantees. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public fun incrementAndGet(index: Int): Long { + checkBounds(index) + return array.getAndAdd(index, 1L) + 1L + } + + /** + * Atomically decrements the element at the given [index] by one and returns the old value of the element. + * + * Provides sequential consistent ordering guarantees. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public fun getAndDecrement(index: Int): Long { + checkBounds(index) + return array.getAndAdd(index, -1L) + } + + /** + * Atomically decrements the element at the given [index] by one and returns the new value of the element. + * + * Provides sequential consistent ordering guarantees. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public fun decrementAndGet(index: Int): Long { + checkBounds(index) + return array.getAndAdd(index, -1L) - 1L + } + + /** + * Returns the string representation of the underlying [IntArray][array]. + */ + public override fun toString(): String = array.toString() + + private fun checkBounds(index: Int) { + if (index < 0 || index >= array.size) throw IndexOutOfBoundsException("The index $index is out of the bounds of the AtomicLongArray with size ${array.size}.") + } +} + +/** + * Creates a new [AtomicLongArray] of the given [size], where each element is initialized by calling the given [init] function. + * + * The function [init] is called for each array element sequentially starting from the first one. + * It should return the value for an array element given its index. + * + * @throws RuntimeException if the specified [size] is negative. + */ +@SinceKotlin("1.9") +@RequireKotlin(version = "1.9.20", versionKind = RequireKotlinVersionKind.COMPILER_VERSION) +@ExperimentalStdlibApi +public inline fun AtomicLongArray(size: Int, init: (Int) -> Long): AtomicLongArray { + val inner = LongArray(size) + for (index in 0 until size) { + inner[index] = init(index) + } + return AtomicLongArray(inner) +} + +/** + * An [Array] in which elements are always updated atomically. + * For additional details about atomicity guarantees for reads and writes see [kotlin.concurrent.Volatile]. + */ +@SinceKotlin("1.9") +@RequireKotlin(version = "1.9.20", versionKind = RequireKotlinVersionKind.COMPILER_VERSION) +@ExperimentalStdlibApi +public class AtomicArray { + private val array: Array + + /** + * Creates a new [AtomicArray] filled with elements of the given [array]. + */ + @PublishedApi + internal constructor(array: Array) { + this.array = array.copyOf() + } + + /** + * Returns the number of elements in the array. + */ + public val length: Int get() = array.size + + /** + * Atomically gets the value of the element at the given [index]. + * + * Provides sequential consistent ordering guarantees. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public operator fun get(index: Int): T { + checkBounds(index) + return array.atomicGet(index) + } + + /** + * Atomically sets the value of the element at the given [index] to the [new value][newValue]. + * + * Provides sequential consistent ordering guarantees. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public operator fun set(index: Int, newValue: T): Unit { + checkBounds(index) + array.atomicSet(index, newValue) + } + + /** + * Atomically sets the value of the element at the given [index] to the [new value][newValue] + * and returns the old value of the element. + * + * Provides sequential consistent ordering guarantees. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public fun getAndSet(index: Int, newValue: T): T { + checkBounds(index) + return array.getAndSet(index, newValue) + } + + /** + * Atomically sets the value of the element at the given [index] to the [new value][newValue] + * if the current value equals the [expected value][expectedValue]. + * Returns true if the operation was successful and false only if the current value of the element was not equal to the expected value. + * + * Provides sequential consistent ordering guarantees and never fails spuriously. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public fun compareAndSet(index: Int, expectedValue: T, newValue: T): Boolean { + checkBounds(index) + return array.compareAndSet(index, expectedValue, newValue) + } + + /** + * Atomically sets the value of the element at the given [index] to the [new value][newValue] + * if the current value equals the [expected value][expectedValue] and returns the old value of the element in any case. + * + * Provides sequential consistent ordering guarantees and never fails spuriously. + * + * @throws [IndexOutOfBoundsException] if the [index] is out of bounds of this array. + */ + public fun compareAndExchange(index: Int, expectedValue: T, newValue: T): T { + checkBounds(index) + return array.compareAndExchange(index, expectedValue, newValue) + } + + /** + * Returns the string representation of the underlying [IntArray][array]. + */ + public override fun toString(): String = array.toString() + + private fun checkBounds(index: Int) { + if (index < 0 || index >= array.size) throw IndexOutOfBoundsException("The index $index is out of the bounds of the AtomicArray with size ${array.size}.") + } +} + +/** + * Creates a new [AtomicArray] of the given [size], where each element is initialized by calling the given [init] function. + * + * The function [init] is called for each array element sequentially starting from the first one. + * It should return the value for an array element given its index. + * + * @throws RuntimeException if the specified [size] is negative. + */ +@SinceKotlin("1.9") +@RequireKotlin(version = "1.9.20", versionKind = RequireKotlinVersionKind.COMPILER_VERSION) +@ExperimentalStdlibApi +@Suppress("UNCHECKED_CAST") +public inline fun AtomicArray(size: Int, init: (Int) -> T): AtomicArray { + val inner = arrayOfNulls(size) + for (index in 0 until size) { + inner[index] = init(index) + } + return AtomicArray(inner as Array) +} /** * Atomically gets the value of the [IntArray][this] element at the given [index]. @@ -42,7 +555,7 @@ internal external fun IntArray.atomicSet(index: Int, newValue: Int) internal external fun IntArray.getAndSet(index: Int, newValue: Int): Int /** - * Atomically adds the [given value][delta] to the [IntArray][this] element at the given [index] + * Atomically adds the given [delta] to the [IntArray][this] element at the given [index] * and returns the old value of the element. * * Provides sequential consistent ordering guarantees. @@ -107,7 +620,7 @@ internal external fun LongArray.atomicSet(index: Int, newValue: Long) internal external fun LongArray.getAndSet(index: Int, newValue: Long): Long /** - * Atomically adds the [given value][delta] to the [LongArray][this] element at the given [index] + * Atomically adds the given [delta] to the [LongArray][this] element at the given [index] * and returns the old value of the element. * * Provides sequential consistent ordering guarantees.