Skip to content

[String] Custom Iterators for String Views #20438

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 30 additions & 38 deletions stdlib/public/core/StringCharacterView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -197,16 +197,8 @@ extension String: BidirectionalCollection {

let i = _guts.scalarAlign(i)
let distance = _characterStride(startingAt: i)

if _fastPath(_guts.isFastUTF8) {
let start = i.encodedOffset
let end = start + distance
return _guts.withFastUTF8(range: start..<end) { utf8 in
return Character(unchecked: String._uncheckedFromUTF8(utf8))
}
}

return _foreignSubscript(position: i, distance: distance)
return _guts.errorCorrectedCharacter(
startingAt: i.encodedOffset, endingAt: i.encodedOffset &+ distance)
}
}

Expand All @@ -228,40 +220,40 @@ extension String: BidirectionalCollection {
}
}

// Foreign string support
extension String {
@usableFromInline @inline(never)
@_effects(releasenone)
internal func _foreignSubscript(position: Index, distance: Int) -> Character {
#if _runtime(_ObjC)
_sanityCheck(_guts.isForeign)
@_fixed_layout
public struct Iterator: IteratorProtocol {
@usableFromInline
internal var _guts: _StringGuts

// Both a fast-path for single-code-unit graphemes and validation:
// ICU treats isolated surrogates as isolated graphemes
if distance == 1 {
return Character(
String(_guts.foreignErrorCorrectedScalar(startingAt: position).0))
}
@usableFromInline
internal var _position: Int = 0

let start = position.encodedOffset
let end = start + distance
let count = end - start
@usableFromInline
internal var _end: Int

// TODO(String performance): Stack buffer if small enough

var cus = Array<UInt16>(repeating: 0, count: count)
cus.withUnsafeMutableBufferPointer {
_cocoaStringCopyCharacters(
from: _guts._object.cocoaObject,
range: start..<end,
into: $0.baseAddress._unsafelyUnwrappedUnchecked)
@inlinable
internal init(_ guts: _StringGuts) {
self._guts = guts
self._end = guts.count
}
return cus.withUnsafeBufferPointer {
return Character(String._uncheckedFromUTF16($0))

@inlinable
public mutating func next() -> Character? {
guard _fastPath(_position < _end) else { return nil }

let len = _guts._opaqueCharacterStride(startingAt: _position)
let nextPosition = _position &+ len
let result = _guts.errorCorrectedCharacter(
startingAt: _position, endingAt: nextPosition)
_position = nextPosition
return result
}
#else
fatalError("No foreign strings on Linux in this version of Swift")
#endif
}

@inlinable
public __consuming func makeIterator() -> Iterator {
return Iterator(_guts)
}
}

47 changes: 33 additions & 14 deletions stdlib/public/core/StringUnicodeScalarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,42 @@ extension String.UnicodeScalarView: BidirectionalCollection {
@inline(__always) get {
String(_guts)._boundsCheck(position)
let i = _guts.scalarAlign(position)
if _fastPath(_guts.isFastUTF8) {
return _guts.fastUTF8Scalar(startingAt: i.encodedOffset)
}
return _guts.errorCorrectedScalar(startingAt: i.encodedOffset).0
}
}
}

extension String.UnicodeScalarView {
@_fixed_layout
public struct Iterator: IteratorProtocol {
@usableFromInline
internal var _guts: _StringGuts

@usableFromInline
internal var _position: Int = 0

@usableFromInline
internal var _end: Int

@inlinable
internal init(_ guts: _StringGuts) {
self._guts = guts
self._end = guts.count
}

return _foreignSubscript(aligned: i)
@inlinable
public mutating func next() -> Unicode.Scalar? {
guard _fastPath(_position < _end) else { return nil }

let (result, len) = _guts.errorCorrectedScalar(startingAt: _position)
_position &+= len
return result
}
}
@inlinable
public __consuming func makeIterator() -> Iterator {
return Iterator(_guts)
}
}

extension String.UnicodeScalarView: CustomStringConvertible {
Expand Down Expand Up @@ -403,14 +432,4 @@ extension String.UnicodeScalarView {

return i.encoded(offsetBy: -len)
}

@usableFromInline @inline(never)
@_effects(releasenone)
internal func _foreignSubscript(aligned i: Index) -> Unicode.Scalar {
_sanityCheck(_guts.isForeign)
_sanityCheck(_guts.isOnUnicodeScalarBoundary(i),
"should of been aligned prior")

return _guts.foreignErrorCorrectedScalar(startingAt: i).0
}
}
62 changes: 62 additions & 0 deletions stdlib/public/core/UnicodeHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ extension _StringGuts {
#endif
}

@usableFromInline
@_effects(releasenone)
internal func foreignErrorCorrectedScalar(
startingAt idx: String.Index
Expand Down Expand Up @@ -374,4 +375,65 @@ extension _StringGuts {
"Error-correction shouldn't give trailing surrogate at position zero")
return String.Index(encodedOffset: idx.encodedOffset &- 1)
}

@usableFromInline @inline(never)
@_effects(releasenone)
internal func foreignErrorCorrectedGrapheme(
startingAt start: Int, endingAt end: Int
) -> Character {
#if _runtime(_ObjC)
_sanityCheck(self.isForeign)

// Both a fast-path for single-code-unit graphemes and validation:
// ICU treats isolated surrogates as isolated graphemes
let count = end &- start
if start &- end == 1 {
return Character(String(self.foreignErrorCorrectedScalar(
startingAt: String.Index(encodedOffset: start)
).0))
}

// TODO(String performance): Stack buffer if small enough
var cus = Array<UInt16>(repeating: 0, count: count)
cus.withUnsafeMutableBufferPointer {
_cocoaStringCopyCharacters(
from: self._object.cocoaObject,
range: start..<end,
into: $0.baseAddress._unsafelyUnwrappedUnchecked)
}
return cus.withUnsafeBufferPointer {
return Character(String._uncheckedFromUTF16($0))
}
#else
fatalError("No foreign strings on Linux in this version of Swift")
#endif
}
}

// Higher level aggregate operations. These should only be called when the
// result is the sole operation done by a caller, otherwise it's always more
// efficient to use `withFastUTF8` in the caller.
extension _StringGuts {
@inlinable @inline(__always)
internal func errorCorrectedScalar(
startingAt i: Int
) -> (Unicode.Scalar, scalarLength: Int) {
if _fastPath(isFastUTF8) {
return withFastUTF8 { _decodeScalar($0, startingAt: i) }
}
return foreignErrorCorrectedScalar(
startingAt: String.Index(encodedOffset: i))
}
@inlinable @inline(__always)
internal func errorCorrectedCharacter(
startingAt start: Int, endingAt end: Int
) -> Character {
if _fastPath(isFastUTF8) {
return withFastUTF8(range: start..<end) { utf8 in
return Character(unchecked: String._uncheckedFromUTF8(utf8))
}
}

return foreignErrorCorrectedGrapheme(startingAt: start, endingAt: end)
}
}
14 changes: 8 additions & 6 deletions test/SILOptimizer/licm_exclusivity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ func run_ReversedArray(_ N: Int) {
// TEST2: Hoist and Sink pairs attempt
// TEST2: Hoisted

// TESTSIL-LABEL: sil @$s16licm_exclusivity20count_unicodeScalarsyySS17UnicodeScalarViewVF : $@convention(thin) (@guaranteed String.UnicodeScalarView) -> () {
// TESTSIL: bb0(%0 : $String.UnicodeScalarView)
// TESTSIL-NEXT: %1 = global_addr @$s16licm_exclusivity5countSivp : $*Int
// TESTSIL: begin_access [modify] [dynamic] [no_nested_conflict] %1 : $*Int
// TESTSIL: end_access
// TESTSIL: return
// FIXME: <rdar://problem/45931225> Re-enable the below
//
// xTESTSIL-LABEL: sil @$s16licm_exclusivity20count_unicodeScalarsyySS17UnicodeScalarViewVF : $@convention(thin) (@guaranteed String.UnicodeScalarView) -> () {
// xTESTSIL: bb0(%0 : $String.UnicodeScalarView)
// xTESTSIL-NEXT: %1 = global_addr @$s16licm_exclusivity5countSivp : $*Int
// xTESTSIL: begin_access [modify] [dynamic] [no_nested_conflict] %1 : $*Int
// xTESTSIL: end_access
// xTESTSIL: return
var count: Int = 0
public func count_unicodeScalars(_ s: String.UnicodeScalarView) {
for _ in s {
Expand Down
6 changes: 6 additions & 0 deletions test/api-digester/Outputs/stability-stdlib-abi.swift.expected
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ Class _SharedStringStorage has removed conformance to _NSStringCore
Class _StringStorage has removed conformance to _NSStringCore
Protocol _NSStringCore has been removed

Func String.UnicodeScalarView._foreignSubscript(aligned:) has been removed
Struct String.UnicodeScalarView has type witness type for Collection.Iterator changing from IndexingIterator<String.UnicodeScalarView> to String.UnicodeScalarView.Iterator
Struct String.UnicodeScalarView has type witness type for Sequence.Iterator changing from IndexingIterator<String.UnicodeScalarView> to String.UnicodeScalarView.Iterator
Func String._foreignSubscript(position:distance:) has been removed
Struct String has type witness type for Collection.Iterator changing from IndexingIterator<String> to String.Iterator
Struct String has type witness type for Sequence.Iterator changing from IndexingIterator<String> to String.Iterator
Func Unicode.UTF32._decode(_:) has been removed
Func _UnicodeParser._decode(_:repairingIllFormedSequences:into:) has been removed
Func _UnicodeParser._parse(_:repairingIllFormedSequences:into:) has been removed
Expand Down
4 changes: 2 additions & 2 deletions validation-test/stdlib/String.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ StringTests.test("AssociatedTypes-UTF16View") {
typealias View = String.UTF16View
expectCollectionAssociatedTypes(
collectionType: View.self,
iteratorType: IndexingIterator<View>.self,
iteratorType: View.Iterator.self,
subSequenceType: Substring.UTF16View.self,
indexType: View.Index.self,
indicesType: View.Indices.self)
Expand All @@ -145,7 +145,7 @@ StringTests.test("AssociatedTypes-UnicodeScalarView") {
StringTests.test("AssociatedTypes-CharacterView") {
expectCollectionAssociatedTypes(
collectionType: String.self,
iteratorType: IndexingIterator<String>.self,
iteratorType: String.Iterator.self,
subSequenceType: Substring.self,
indexType: String.Index.self,
indicesType: DefaultIndices<String>.self)
Expand Down