From 56da8c0e75b6e2f7213ed118765b3687360538d3 Mon Sep 17 00:00:00 2001 From: Rick van Voorden Date: Mon, 23 Jun 2025 18:57:20 -0700 Subject: [PATCH 1/2] [stdlib] string identical --- benchmark/single-source/StringTests.swift | 14 +++ benchmark/single-source/SubstringTest.swift | 9 ++ stdlib/public/core/String.swift | 20 ++- stdlib/public/core/Substring.swift | 21 ++++ test/stdlib/StringAPI.swift | 37 ++++++ test/stdlib/subString.swift | 132 ++++++++++++++++++++ 6 files changed, 232 insertions(+), 1 deletion(-) diff --git a/benchmark/single-source/StringTests.swift b/benchmark/single-source/StringTests.swift index bcc43a2633777..f21be1eb02d41 100644 --- a/benchmark/single-source/StringTests.swift +++ b/benchmark/single-source/StringTests.swift @@ -40,6 +40,10 @@ public var benchmarks: [BenchmarkInfo] { runFunction: run_StringHasSuffixUnicode, tags: [.validation, .api, .String], legacyFactor: 1000), + BenchmarkInfo( + name: "StringIdentical", + runFunction: run_StringIdentical, + tags: [.validation, .api, .String]), ] if #available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) { @@ -1676,3 +1680,13 @@ public func run_iterateWords(_ n: Int) { blackHole(swiftOrgHTML._words) } } + +public func run_StringIdentical(_ n: Int) { + let str1 = "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. " + let str2 = str1 + for _ in 0 ..< n { + for _ in 0 ..< 100_000 { + check(str1.isIdentical(to: str2)) + } + } +} diff --git a/benchmark/single-source/SubstringTest.swift b/benchmark/single-source/SubstringTest.swift index 71ef0c774f26f..44a7ec8579f32 100644 --- a/benchmark/single-source/SubstringTest.swift +++ b/benchmark/single-source/SubstringTest.swift @@ -30,6 +30,7 @@ public let benchmarks = [ BenchmarkInfo(name: "SubstringFromLongString2", runFunction: run_SubstringFromLongString, tags: [.validation, .api, .String]), BenchmarkInfo(name: "SubstringFromLongStringGeneric2", runFunction: run_SubstringFromLongStringGeneric, tags: [.validation, .api, .String]), BenchmarkInfo(name: "SubstringTrimmingASCIIWhitespace", runFunction: run_SubstringTrimmingASCIIWhitespace, tags: [.validation, .api, .String]), + BenchmarkInfo(name: "SubstringIdentical", runFunction: run_SubstringIdentical, tags: [.validation, .String]), ] // A string that doesn't fit in small string storage and doesn't fit in Latin-1 @@ -332,3 +333,11 @@ public func run _LessSubstringSubstringGenericStringProtocol(_ n: Int) { } } */ + +@inline(never) +public func run_SubstringIdentical(_ n: Int) { + let (a, b) = (ss1, ss1) + for _ in 1...n*500 { + blackHole(a.isIdentical(to: b)) + } +} diff --git a/stdlib/public/core/String.swift b/stdlib/public/core/String.swift index e6715e91cc6ed..f17e67b97b15c 100644 --- a/stdlib/public/core/String.swift +++ b/stdlib/public/core/String.swift @@ -1112,4 +1112,22 @@ extension String { } } - +extension String { + /// Returns a boolean value indicating whether this string is identical to + /// `other`. + /// + /// Two string values are identical if there is no way to distinguish between + /// them. + /// + /// Comparing strings this way includes comparing (normally) hidden + /// implementation details such as the memory location of any underlying + /// string storage object. Therefore, identical strings are guaranteed to + /// compare equal with `==`, but not all equal strings are considered + /// identical. + /// + /// - Performance: O(1) + @backDeployed(before: SwiftStdlib 6.3) + public func isIdentical(to other: Self) -> Bool { + self._guts.rawBits == other._guts.rawBits + } +} diff --git a/stdlib/public/core/Substring.swift b/stdlib/public/core/Substring.swift index be818e9698dba..352dc740a1568 100644 --- a/stdlib/public/core/Substring.swift +++ b/stdlib/public/core/Substring.swift @@ -1307,3 +1307,24 @@ extension Substring { return Substring(_unchecked: Slice(base: base, bounds: r)) } } + +extension Substring { + /// Returns a boolean value indicating whether this substring is identical to + /// `other`. + /// + /// Two substring values are identical if there is no way to distinguish + /// between them. + /// + /// Comparing substrings this way includes comparing (normally) hidden + /// implementation details such as the memory location of any underlying + /// substring storage object. Therefore, identical substrings are guaranteed + /// to compare equal with `==`, but not all equal substrings are considered + /// identical. + /// + /// - Performance: O(1) + @backDeployed(before: SwiftStdlib 6.3) + public func isIdentical(to other: Self) -> Bool { + self._wholeGuts.rawBits == other._wholeGuts.rawBits && + self._offsetRange == other._offsetRange + } +} diff --git a/test/stdlib/StringAPI.swift b/test/stdlib/StringAPI.swift index fb33db9b53ee7..2aac96ac41f7b 100644 --- a/test/stdlib/StringAPI.swift +++ b/test/stdlib/StringAPI.swift @@ -533,4 +533,41 @@ StringTests.test("hasPrefix/hasSuffix vs Character boundaries") { expectFalse(s2.hasSuffix("\n")) } +StringTests.test("isIdentical(to:) small ascii") { + let a = "Hello" + let b = "Hello" + + precondition(a == b) + + expectTrue(a.isIdentical(to: a)) + expectTrue(b.isIdentical(to: b)) + expectTrue(a.isIdentical(to: b)) // Both small ASCII strings + expectTrue(b.isIdentical(to: a)) +} + +StringTests.test("isIdentical(to:) small unicode") { + let a = "Cafe\u{301}" + let b = "Cafe\u{301}" + let c = "Café" + + precondition(a == b) + precondition(b == c) + + expectTrue(a.isIdentical(to: b)) + expectTrue(b.isIdentical(to: a)) + expectFalse(a.isIdentical(to: c)) + expectFalse(b.isIdentical(to: c)) +} + +StringTests.test("isIdentical(to:) large ascii") { + let a = String(repeating: "foo", count: 1000) + let b = String(repeating: "foo", count: 1000) + + precondition(a == b) + + expectFalse(a.isIdentical(to: b)) // Two large, distinct native strings + expectTrue(a.isIdentical(to: a)) + expectTrue(b.isIdentical(to: b)) +} + runAllTests() diff --git a/test/stdlib/subString.swift b/test/stdlib/subString.swift index 2a7b9e58db2d8..d57e6c415fd01 100644 --- a/test/stdlib/subString.swift +++ b/test/stdlib/subString.swift @@ -31,6 +31,41 @@ func checkHasContiguousStorageSubstring(_ x: Substring.UTF8View) { expectTrue(hasStorage) } +fileprivate func slices( + _ s: String, + from: Int, + to: Int +) -> ( + Substring, + Substring, + Substring +) { + let s1 = s[s.index(s.startIndex, offsetBy: from) ..< + s.index(s.startIndex, offsetBy: to)] + let s2 = s1[s1.startIndex.. Bool { + s.allSatisfy { $0.isEmpty == false } +} + +fileprivate func allEqual( + _ s: Substring... +) -> Bool { + for i in 0.. Date: Tue, 24 Jun 2025 13:27:23 -0700 Subject: [PATCH 2/2] [stdlib] string identical aeic --- stdlib/public/core/String.swift | 2 +- stdlib/public/core/Substring.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/public/core/String.swift b/stdlib/public/core/String.swift index f17e67b97b15c..9f6828c371579 100644 --- a/stdlib/public/core/String.swift +++ b/stdlib/public/core/String.swift @@ -1126,7 +1126,7 @@ extension String { /// identical. /// /// - Performance: O(1) - @backDeployed(before: SwiftStdlib 6.3) + @_alwaysEmitIntoClient public func isIdentical(to other: Self) -> Bool { self._guts.rawBits == other._guts.rawBits } diff --git a/stdlib/public/core/Substring.swift b/stdlib/public/core/Substring.swift index 352dc740a1568..be6d9e0a0a6af 100644 --- a/stdlib/public/core/Substring.swift +++ b/stdlib/public/core/Substring.swift @@ -1322,7 +1322,7 @@ extension Substring { /// identical. /// /// - Performance: O(1) - @backDeployed(before: SwiftStdlib 6.3) + @_alwaysEmitIntoClient public func isIdentical(to other: Self) -> Bool { self._wholeGuts.rawBits == other._wholeGuts.rawBits && self._offsetRange == other._offsetRange