diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index a9a6b5d7de2..700d72b7cd6 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -493,6 +493,15 @@ task runtime_basic_exit(type: RunStandaloneKonanTest) { expectedExitStatus = 42 } +task runtime_random(type: RunKonanTest) { + source = "runtime/basic/random.kt" +} + +task runtime_worker_random(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') + source = "runtime/basic/worker_random.kt" +} + task hello0(type: RunKonanTest) { goldValue = "Hello, world!\n" source = "runtime/basic/hello0.kt" diff --git a/backend.native/tests/runtime/basic/random.kt b/backend.native/tests/runtime/basic/random.kt new file mode 100644 index 00000000000..c4383c6c7fd --- /dev/null +++ b/backend.native/tests/runtime/basic/random.kt @@ -0,0 +1,65 @@ +package runtime.basic.random + +import kotlin.collections.* +import kotlin.random.* +import kotlin.system.* +import kotlin.test.* + +/** + * Tests that setting the same seed make random generate the same sequence + */ +private inline fun testReproducibility(seed: Long, generator: () -> T) { + // Reset seed. This will make Random to start a new sequence + Random.seed = seed + val first = Array(50, { i -> generator() }).toList() + + // Reset seed and try again + Random.seed = seed + val second = Array(50, { i -> generator() }).toList() + assertTrue(first == second, "FAIL: got different sequences of generated values " + + "first: $first, second: $second") +} + +/** + * Tests that setting seed makes random generate different sequence. + */ +private inline fun testDifference(generator: () -> T) { + Random.seed = 12345678L + val first = Array(100, { i -> generator() }).toList() + + Random.seed = 87654321L + val second = Array(100, { i -> generator() }).toList() + assertTrue(first != second, "FAIL: got the same sequence of generated values " + + "first: $first, second: $second") +} + +@Test +fun testInts() { + testReproducibility(getTimeMillis(), { Random.nextInt() }) + testReproducibility(Long.MAX_VALUE, { Random.nextInt() }) +} + +@Test +fun testLong() { + testReproducibility(getTimeMillis(), { Random.nextLong() }) + testReproducibility(Long.MAX_VALUE, { Random.nextLong() }) +} + +@Test +fun testDiffInt() = testDifference { Random.nextInt() } + +@Test +fun testDiffLong() = testDifference { Random.nextLong() } + +@Test +fun testBoundInt() { + testReproducibility(1000L, { Random.nextInt(1000) }) + + // test bounds + val bound = 5000 + val a = Array(100, { Random.nextInt(bound) }) + a.forEach { + assertTrue(it >= 0, "Should be: $it >= 0") + assertTrue(it < bound, "Should be: $it < $bound") + } +} diff --git a/backend.native/tests/runtime/basic/worker_random.kt b/backend.native/tests/runtime/basic/worker_random.kt new file mode 100644 index 00000000000..6416d778a3f --- /dev/null +++ b/backend.native/tests/runtime/basic/worker_random.kt @@ -0,0 +1,35 @@ +package runtime.basic.worker_random + +import konan.worker.* +import kotlin.collections.* +import kotlin.random.* +import kotlin.system.* +import kotlin.test.* + +@Test +fun testRandomWorkers() { + val seed = getTimeMillis() + val workers = Array(5, { _ -> startWorker()}) + + val attempts = 3 + val results = Array(attempts, { ArrayList() } ) + for (attempt in 0 until attempts) { + Random.seed = seed + // Produce a list of random numbers in each worker + val futures = Array(workers.size, { workerIndex -> + workers[workerIndex].schedule(TransferMode.CHECKED, { workerIndex }) { input -> + Array(10, { Random.nextInt() }).toList() + } + }) + // Now collect all results into current attempt's list + val futureSet = futures.toSet() + for (i in 0 until futureSet.size) { + val ready = futureSet.waitForMultipleFutures(10000) + ready.forEach { results[attempt].addAll(it.result()) } + } + } + + workers.forEach { + it.requestTermination().consume { _ -> } + } +} \ No newline at end of file diff --git a/backend.native/tests/stdlib_external/collections/MutableCollectionsTest.kt b/backend.native/tests/stdlib_external/collections/MutableCollectionsTest.kt index 068b4a7a6af..a7d32387c46 100644 --- a/backend.native/tests/stdlib_external/collections/MutableCollectionsTest.kt +++ b/backend.native/tests/stdlib_external/collections/MutableCollectionsTest.kt @@ -94,17 +94,12 @@ class MutableCollectionTest { } } -/* - // Kotlin/JVM only: java.util.Collections.fill(this, value) @Test fun listFill() { val list = MutableList(3) { it } list.fill(42) assertEquals(listOf(42, 42, 42), list) } -*/ -/* - // shuffle is Kotlin/JVM only method @Test fun shuffled() { val list = MutableList(100) { it } val shuffled = list.shuffled() @@ -113,5 +108,4 @@ class MutableCollectionTest { assertEquals(list.toSet(), shuffled.toSet()) assertEquals(list.size, shuffled.distinct().size) } -*/ } diff --git a/runtime/src/main/kotlin/kotlin/collections/Collections.kt b/runtime/src/main/kotlin/kotlin/collections/Collections.kt index 2295f9f21c1..adab827884a 100644 --- a/runtime/src/main/kotlin/kotlin/collections/Collections.kt +++ b/runtime/src/main/kotlin/kotlin/collections/Collections.kt @@ -17,6 +17,7 @@ package kotlin.collections import kotlin.comparisons.* +import kotlin.random.* // Copies typed varargs array to an array of objects internal actual fun Array.copyToArrayOfAny(isVarargs: Boolean): Array = @@ -229,13 +230,10 @@ public actual fun MutableList.fill(value: T): Unit { * * See: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm */ -@FixmeRandom @SinceKotlin("1.2") public actual fun MutableList.shuffle(): Unit { for (i in lastIndex downTo 1) { - // FIXME: implement a good random - //val j = rand(i + 1) - val j = i / 2 + val j = Random.nextInt(i + 1) val copy = this[i] this[i] = this[j] this[j] = copy diff --git a/runtime/src/main/kotlin/kotlin/random/Random.kt b/runtime/src/main/kotlin/kotlin/random/Random.kt new file mode 100644 index 00000000000..097680e5bda --- /dev/null +++ b/runtime/src/main/kotlin/kotlin/random/Random.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package kotlin.random + +import konan.internal.* +import konan.worker.AtomicLong +import kotlin.random.Random.Companion.MULTIPLIER +import kotlin.system.getTimeNanos + +abstract class Random { + abstract fun nextInt(): Int + abstract fun nextInt(bound: Int): Int + + abstract fun nextLong(): Long + + /** + * A default pseudo-random linear congruential generator. + */ + companion object : Random() { + private const val MULTIPLIER = 0x5deece66dL + private val _seed = AtomicLong(mult(getTimeNanos())) + + /** + * Random generator seed value. + */ + var seed: Long + get() = _seed.get() + set(value) = update(mult(value)) + + private fun mult(value: Long) = (value xor MULTIPLIER) and ((1L shl 48) - 1) + + private fun update(seed: Long) { + _seed.compareAndSwap(_seed.get(), seed) + } + + /** + * Returns a pseudo-random Int number. + */ + override fun nextInt(): Int { + update((seed * MULTIPLIER + 0xbL) and ((1L shl 48) - 1)); + return (seed ushr 16).toInt(); + } + + /** + * Returns a pseudo-random Int value between 0 and specified value (exclusive) + */ + override fun nextInt(bound: Int): Int { + if (bound <= 0) throw IllegalArgumentException("Incorrect bound: $bound") + + if (bound and (bound - 1) == 0) { + return ((bound * nextInt().toLong()) shr 31).toInt(); + } + + var m: Int + do { + var r = nextInt() + m = r % bound + } while (r - m + (bound - 1) < 0) + return m + } + + /** + * Returns a pseudo-random Long number. + */ + override fun nextLong(): Long = (nextInt().toLong() shl 32) + nextInt().toLong() + } +} \ No newline at end of file