diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index a7309246aad..ba85260a25c 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -1468,8 +1468,12 @@ task sort1(type: RunKonanTest) { source = "runtime/collections/sort1.kt" } -task sortWith(type: RunKonanTest) { +// TODO: Enable devirtualization and make the test not standalone +// when KT-26315 is fixed. +task sortWith(type: RunStandaloneKonanTest) { source = "runtime/collections/SortWith.kt" + flags = ['-tr', '--disable', 'devirtualization'] + arguments = ['--ktest_logger=SILENT'] } task if_else(type: RunKonanTest) { diff --git a/backend.native/tests/runtime/collections/SortWith.kt b/backend.native/tests/runtime/collections/SortWith.kt index 38f0f6a98cc..5fe250d9e58 100644 --- a/backend.native/tests/runtime/collections/SortWith.kt +++ b/backend.native/tests/runtime/collections/SortWith.kt @@ -24,8 +24,18 @@ fun Array.assertSorted(cmp: Comparator, message: String = "") { } } +fun Array.assertSorted(message: String = "") { + for (i in 1 until size) { + assertTrue(this[i - 1] <= this[i], message) + } +} + data class ComparatorInfo(val name: String, val comparator: Comparator, val isCorrect: Boolean) +class MyComparable (val value: Int, val comparator: Comparator): Comparable { + override fun compareTo(other: MyComparable): Int = comparator.compare(value, other.value) +} + // Assert that the array is sorted in terms of a comparator only for correct/partially correct cases val comparators = listOf( ComparatorInfo("Correct increasing", correctIncreasing , true), @@ -68,8 +78,8 @@ val arrays = listOf>( @Test fun runTest() { arrays.forEach { array -> comparators.forEach { + // Test with custom comparator val arrayUnderTest = array.copyOf() - arrayUnderTest.sortWith(it.comparator) if (it.isCorrect) { arrayUnderTest.assertSorted(it.comparator, """ @@ -78,6 +88,20 @@ val arrays = listOf>( Array after sorting: ${arrayUnderTest.joinToString()} """.trimIndent()) } + + // Test of a custom comparable + val comparableArrayUnderTest = Array(array.size) { i -> + MyComparable(array[i], it.comparator) + } + comparableArrayUnderTest.sort() + + if (it.isCorrect) { + comparableArrayUnderTest.assertSorted(""" + Assert sorted failed for Comparable: "${it.name}" + Array: ${array.joinToString()} + Array after sorting: ${comparableArrayUnderTest.joinToString()} + """.trimIndent()) + } } } } diff --git a/runtime/src/main/kotlin/kotlin/util/Sort.kt b/runtime/src/main/kotlin/kotlin/util/Sort.kt index 03476c9ae9d..8f688c7efb1 100644 --- a/runtime/src/main/kotlin/kotlin/util/Sort.kt +++ b/runtime/src/main/kotlin/kotlin/util/Sort.kt @@ -20,41 +20,64 @@ import kotlin.comparisons.* // TODO: Implement sort for primitives with a custom comparator. -// Yes, rather naive qsort for now. // Array ============================================================================= -private fun partition(array: Array, left: Int, right: Int): Int { - var i = left - var j = right +// We use merge becuase the quick sort may cause segfaults if +// the comparator or the comparable implementation is incorrect (e.g. if it never returns 0). + +// Sort of comparables. +private fun > mergeSort(array: Array, start: Int, endInclusive: Int) { @Suppress("UNCHECKED_CAST") - val pivot = array[(left + right) / 2] as Comparable - while (i <= j) { - while (pivot.compareTo(array[i]) > 0) { - i++ - } - while (pivot.compareTo(array[j]) < 0) { - j-- - } - if (i <= j) { - val tmp = array[i] - array[i] = array[j] - array[j] = tmp - i++ - j-- + val buffer = arrayOfNulls(array.size) as Array + val result = mergeSort(array, buffer, start, endInclusive) + if (result !== array) { + result.forEachIndexed { i, v-> array[i] = v } + } +} + +// Both start and end are inclusive indices. +private fun > mergeSort(array: Array, buffer: Array, start: Int, end: Int): Array { + if (start == end) { + return array + } + + val median = (start + end) / 2 + val left = mergeSort(array, buffer, start, median) + val right = mergeSort(array, buffer, median + 1, end) + + val target = if (left === buffer) array else buffer + + // Merge + var leftIndex = start + var rightIndex = median + 1 + for (i in start..end) { + when { + leftIndex <= median && rightIndex <= end -> { + val leftValue = left[leftIndex] + val rightValue = right[rightIndex] + + if (leftValue < rightValue) { + target[i] = leftValue + leftIndex++ + } else { + target[i] = rightValue + rightIndex++ + } + } + leftIndex <= median -> { + target[i] = left[leftIndex] + leftIndex++ + } + else /* rightIndex <= end */ -> { + target[i] = right[rightIndex] + rightIndex++ + } } } - return i + + return target } -private fun quickSort(array: Array, left: Int, right: Int) { - val index = partition(array, left, right) - if (left < index - 1) - quickSort(array, left, index - 1) - if (index < right) - quickSort(array, index, right) -} - -// We use merge sort with comparators becuase the quick sort may cause segfaults if -// the comparator is incorrect (e.g. if it never returns 0). +// Sort with comparator private fun mergeSort(array: Array, start: Int, endInclusive: Int, comparator: Comparator) { @Suppress("UNCHECKED_CAST") val buffer = arrayOfNulls(array.size) as Array @@ -73,7 +96,7 @@ private fun mergeSort(array: Array, buffer: Array, start: Int, end: In val median = (start + end) / 2 val left = mergeSort(array, buffer, start, median, comparator) val right = mergeSort(array, buffer, median + 1, end, comparator) - + val target = if (left === buffer) array else buffer // Merge @@ -370,9 +393,9 @@ internal fun sortArrayWith( * Sorts a subarray of [Comparable] elements specified by [fromIndex] (inclusive) and * [toIndex] (exclusive) parameters using the qsort algorithm. */ -internal fun sortArrayComparable(array: Array) { +internal fun > sortArrayComparable(array: Array) { @Suppress("UNCHECKED_CAST") - quickSort(array as Array, 0, array.size - 1) + mergeSort(array as Array, 0, array.size - 1) } /**