diff --git a/kotlin-native/runtime/src/main/cpp/KString.cpp b/kotlin-native/runtime/src/main/cpp/KString.cpp index 23cbf3ea286..da58589dd61 100644 --- a/kotlin-native/runtime/src/main/cpp/KString.cpp +++ b/kotlin-native/runtime/src/main/cpp/KString.cpp @@ -78,6 +78,30 @@ OBJ_GETTER(utf8ToUtf16, const char* rawString, size_t rawStringLength) { RETURN_RESULT_OF(utf8ToUtf16Impl, rawString, end, charCount); } +uint32_t mismatch(const uint16_t* first, const uint16_t* second, uint32_t size) { + const long* firstLong = reinterpret_cast(first); + const long* secondLong = reinterpret_cast(second); + constexpr int step = sizeof(long) / sizeof(uint16_t); + uint32_t sizeLong = size / step; + uint32_t iLong; + for (iLong = 0; iLong < sizeLong; iLong++) { + if (firstLong[iLong] != secondLong[iLong]) { + break; + } + } + for (uint32_t i = iLong * step; i < size; i++) { + if (first[i] != second[i]) { + return i; + } + } + return size; +} + +template +int threeWayCompare(T a, T b) { + return (a == b) ? 0 : (a < b ? -1 : 1); +} + } // namespace extern "C" { @@ -183,17 +207,16 @@ OBJ_GETTER(Kotlin_String_subSequence, KString thiz, KInt startIndex, KInt endInd } KInt Kotlin_String_compareTo(KString thiz, KString other) { - int result = memcmp( - CharArrayAddressOfElementAt(thiz, 0), - CharArrayAddressOfElementAt(other, 0), - (thiz->count_ < other->count_ ? thiz->count_ : other->count_) * sizeof(KChar)); - if (result != 0) return result; - int diff = thiz->count_ - other->count_; - if (diff == 0) return 0; - return diff < 0 ? -1 : 1; + const uint16_t *first = CharArrayAddressOfElementAt(thiz, 0); + const uint16_t *second = CharArrayAddressOfElementAt(other, 0); + uint32_t minSize = std::min(thiz->count_, other->count_); + uint32_t mismatch_position = mismatch(first, second, minSize); + if (mismatch_position != minSize) { + return threeWayCompare(first[mismatch_position], second[mismatch_position]); + } + return threeWayCompare(thiz->count_, other->count_); } - KChar Kotlin_String_get(KString thiz, KInt index) { // We couldn't have created a string bigger than max KInt value. // So if index is < 0, conversion to an unsigned value would make it bigger diff --git a/libraries/stdlib/test/text/StringTest.kt b/libraries/stdlib/test/text/StringTest.kt index a3a168fc01f..7d4e4910548 100644 --- a/libraries/stdlib/test/text/StringTest.kt +++ b/libraries/stdlib/test/text/StringTest.kt @@ -923,6 +923,46 @@ class StringTest { assertCompareResult(GT, s1, s2, ignoreCase = true) } + @Test fun compareToUnicode() { + (Char.MIN_VALUE..Char.MAX_VALUE) + .map { it.toString() } + .zipWithNext() + .forEach { (first, second) -> + assertTrue(first.compareTo(second) < 0) + assertTrue(first.compareTo(first) == 0) + assertTrue(second.compareTo(second) == 0) + assertTrue(second.compareTo(first) > 0) + } + } + + @Test fun orderUnicodeLongString() { + val range = Char.MIN_VALUE..Char.MAX_VALUE + val chars = buildList { + repeat(10) { + add(range.random()) + } + } + val strings = buildList { + repeat(10000) { + add(buildString { + repeat(Random.nextInt(8)) { + append(chars.random()) + } + }) + } + }.sorted() + + assertTrue(strings.zipWithNext().all { (s1, s2) -> + val chars1 = s1.toCharArray() + val chars2 = s2.toCharArray() + for (i in 0 until minOf(chars1.size, chars2.size)) { + if (chars1[i] != chars2[i]) + return@all chars1[i] < chars2[i] + } + return@all chars1.size <= chars2.size + }) + } + @Test fun orderIgnoringCase() { val list = listOf("Beast", "Ast", "asterisk", "[]")