Files
swift-mirror/stdlib/public/core/StringLegacy.swift
Erik Eckstein 9a961208ec stdlib: remove some @inlineables from String API functions.
Beside the general goal to remove inlinable functions, this reduces code size and also improves performance for several benchmarks.
The performance problem was that by inlining top-level String API functions into client code (like String.count) it ended up calling non-inlinable internal String functions eventually.
This is much slower than to make a single call at the top-level API boundary into the library. Inside the library all the internal String functions can be specialized and inlined.

rdar://problem/39921548
2018-05-03 14:37:11 -07:00

311 lines
10 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import SwiftShims
extension _StringVariant {
@usableFromInline
func _repeated(_ count: Int) -> _SwiftStringStorage<CodeUnit> {
_sanityCheck(count > 0)
let c = self.count
let storage = _copyToNativeStorage(
of: CodeUnit.self,
unusedCapacity: (count - 1) * c)
var p = storage.start + c
for _ in 1 ..< count {
p.initialize(from: storage.start, count: c)
p += c
}
_sanityCheck(p == storage.start + count * c)
storage.count = p - storage.start
return storage
}
}
extension String {
/// Creates a new string representing the given string repeated the specified
/// number of times.
///
/// For example, you can use this initializer to create a string with ten
/// `"ab"` strings in a row.
///
/// let s = String(repeating: "ab", count: 10)
/// print(s)
/// // Prints "abababababababababab"
///
/// - Parameters:
/// - repeatedValue: The string to repeat.
/// - count: The number of times to repeat `repeatedValue` in the resulting
/// string.
@inlinable // FIXME(sil-serialize-all)
public init(repeating repeatedValue: String, count: Int) {
precondition(count >= 0, "Negative count not allowed")
guard count > 1 else {
self = count == 0 ? "" : repeatedValue
return
}
self = String(repeatedValue._guts._repeated(count))
}
/// A Boolean value indicating whether a string has no characters.
@inlinable // FIXME(sil-serialize-all)
public var isEmpty: Bool {
return _guts.count == 0
}
}
// TODO: since this is generally useful, make public via evolution proposal.
extension BidirectionalCollection {
@inlinable
internal func _ends<Suffix: BidirectionalCollection>(
with suffix: Suffix, by areEquivalent: (Element,Element) -> Bool
) -> Bool where Suffix.Element == Element {
var (i,j) = (self.endIndex,suffix.endIndex)
while i != self.startIndex, j != suffix.startIndex {
self.formIndex(before: &i)
suffix.formIndex(before: &j)
if !areEquivalent(self[i],suffix[j]) { return false }
}
return j == suffix.startIndex
}
}
extension BidirectionalCollection where Element: Equatable {
@inlinable
internal func _ends<Suffix: BidirectionalCollection>(
with suffix: Suffix
) -> Bool where Suffix.Element == Element {
return _ends(with: suffix, by: ==)
}
}
extension StringProtocol {
/// Returns a Boolean value indicating whether the string begins with the
/// specified prefix.
///
/// The comparison is both case sensitive and Unicode safe. The
/// case-sensitive comparison will only match strings whose corresponding
/// characters have the same case.
///
/// let cafe = "Café du Monde"
///
/// // Case sensitive
/// print(cafe.hasPrefix("café"))
/// // Prints "false"
///
/// The Unicode-safe comparison matches Unicode scalar values rather than the
/// code points used to compose them. The example below uses two strings
/// with different forms of the `"é"` character---the first uses the composed
/// form and the second uses the decomposed form.
///
/// // Unicode safe
/// let composedCafe = "Café"
/// let decomposedCafe = "Cafe\u{0301}"
///
/// print(cafe.hasPrefix(composedCafe))
/// // Prints "true"
/// print(cafe.hasPrefix(decomposedCafe))
/// // Prints "true"
///
/// - Parameter prefix: A possible prefix to test against this string.
/// - Returns: `true` if the string begins with `prefix`; otherwise, `false`.
@inlinable
public func hasPrefix<Prefix: StringProtocol>(_ prefix: Prefix) -> Bool {
return self.starts(with: prefix)
}
/// Returns a Boolean value indicating whether the string ends with the
/// specified suffix.
///
/// The comparison is both case sensitive and Unicode safe. The
/// case-sensitive comparison will only match strings whose corresponding
/// characters have the same case.
///
/// let plans = "Let's meet at the café"
///
/// // Case sensitive
/// print(plans.hasSuffix("Café"))
/// // Prints "false"
///
/// The Unicode-safe comparison matches Unicode scalar values rather than the
/// code points used to compose them. The example below uses two strings
/// with different forms of the `"é"` character---the first uses the composed
/// form and the second uses the decomposed form.
///
/// // Unicode safe
/// let composedCafe = "café"
/// let decomposedCafe = "cafe\u{0301}"
///
/// print(plans.hasSuffix(composedCafe))
/// // Prints "true"
/// print(plans.hasSuffix(decomposedCafe))
/// // Prints "true"
///
/// - Parameter suffix: A possible suffix to test against this string.
/// - Returns: `true` if the string ends with `suffix`; otherwise, `false`.
@inlinable
public func hasSuffix<Suffix: StringProtocol>(_ suffix: Suffix) -> Bool {
return self._ends(with: suffix)
}
}
extension String {
public func hasPrefix(_ prefix: String) -> Bool {
let prefixCount = prefix._guts.count
if prefixCount == 0 { return true }
// TODO: replace with 2-way vistor
if self._guts._isSmall && prefix._guts._isSmall {
let selfSmall = self._guts._smallUTF8String
let prefixSmall = prefix._guts._smallUTF8String
if selfSmall.isASCII && prefixSmall.isASCII {
return selfSmall.withUnmanagedASCII { selfASCII in
return prefixSmall.withUnmanagedASCII { prefixASCII in
if prefixASCII.count > selfASCII.count { return false }
return (0 as CInt) == _stdlib_memcmp(
selfASCII.rawStart,
prefixASCII.rawStart,
prefixASCII.count)
}
}
}
}
if _fastPath(!self._guts._isOpaque && !prefix._guts._isOpaque) {
if self._guts.isASCII && prefix._guts.isASCII {
let result: Bool
let selfASCII = self._guts._unmanagedASCIIView
let prefixASCII = prefix._guts._unmanagedASCIIView
if prefixASCII.count > selfASCII.count {
// Prefix is longer than self.
result = false
} else {
result = (0 as CInt) == _stdlib_memcmp(
selfASCII.rawStart,
prefixASCII.rawStart,
prefixASCII.count)
}
_fixLifetime(self)
_fixLifetime(prefix)
return result
}
else {
}
}
return self.starts(with: prefix)
}
public func hasSuffix(_ suffix: String) -> Bool {
let suffixCount = suffix._guts.count
if suffixCount == 0 { return true }
// TODO: replace with 2-way vistor
if self._guts._isSmall && suffix._guts._isSmall {
let selfSmall = self._guts._smallUTF8String
let suffixSmall = suffix._guts._smallUTF8String
if selfSmall.isASCII && suffixSmall.isASCII {
return selfSmall.withUnmanagedASCII { selfASCII in
return suffixSmall.withUnmanagedASCII { suffixASCII in
if suffixASCII.count > selfASCII.count { return false }
return (0 as CInt) == _stdlib_memcmp(
selfASCII.rawStart + (selfASCII.count - suffixASCII.count),
suffixASCII.rawStart,
suffixASCII.count)
}
}
}
}
if _fastPath(!self._guts._isOpaque && !suffix._guts._isOpaque) {
if self._guts.isASCII && suffix._guts.isASCII {
let result: Bool
let selfASCII = self._guts._unmanagedASCIIView
let suffixASCII = suffix._guts._unmanagedASCIIView
if suffixASCII.count > selfASCII.count {
// Suffix is longer than self.
result = false
} else {
result = (0 as CInt) == _stdlib_memcmp(
selfASCII.rawStart + (selfASCII.count - suffixASCII.count),
suffixASCII.rawStart,
suffixASCII.count)
}
_fixLifetime(self)
_fixLifetime(suffix)
return result
}
}
return self._ends(with: suffix)
}
}
// Conversions to string from other types.
extension String {
/// Creates a string representing the given value in base 10, or some other
/// specified base.
///
/// The following example converts the maximal `Int` value to a string and
/// prints its length:
///
/// let max = String(Int.max)
/// print("\(max) has \(max.count) digits.")
/// // Prints "9223372036854775807 has 19 digits."
///
/// Numerals greater than 9 are represented as Roman letters. These letters
/// start with `"A"` if `uppercase` is `true`; otherwise, with `"a"`.
///
/// let v = 999_999
/// print(String(v, radix: 2))
/// // Prints "11110100001000111111"
///
/// print(String(v, radix: 16))
/// // Prints "f423f"
/// print(String(v, radix: 16, uppercase: true))
/// // Prints "F423F"
///
/// - Parameters:
/// - value: The value to convert to a string.
/// - radix: The base to use for the string representation. `radix` must be
/// at least 2 and at most 36. The default is 10.
/// - uppercase: Pass `true` to use uppercase letters to represent numerals
/// greater than 9, or `false` to use lowercase letters. The default is
/// `false`.
@inlinable // FIXME(sil-serialize-all)
public init<T : BinaryInteger>(
_ value: T, radix: Int = 10, uppercase: Bool = false
) {
self = value._description(radix: radix, uppercase: uppercase)
}
}
extension _StringGuts {
@inlinable
func _repeated(_ n: Int) -> _StringGuts {
_sanityCheck(n > 1)
if self._isSmall {
// TODO: visitor pattern for something like this...
if let small = self._smallUTF8String._repeated(n) {
return _StringGuts(small)
}
}
return _visitGuts(self, range: nil, args: n,
ascii: { ascii, n in return _StringGuts(_large: ascii._repeated(n)) },
utf16: { utf16, n in return _StringGuts(_large: utf16._repeated(n)) },
opaque: { opaque, n in return _StringGuts(_large: opaque._repeated(n)) })
}
}