From 8f8eb30fb05d10f64e5b409fc89648a84c387d73 Mon Sep 17 00:00:00 2001 From: Xiaodi Wu <13952+xwu@users.noreply.github.com> Date: Mon, 29 Mar 2021 18:58:11 -0400 Subject: [PATCH 1/6] [stdlib][SR-9438] Re-implement integer-to-string conversion. --- stdlib/public/core/Integers.swift | 211 +++++++++++++++++++++++------- 1 file changed, 162 insertions(+), 49 deletions(-) diff --git a/stdlib/public/core/Integers.swift b/stdlib/public/core/Integers.swift index e9cf86c526851..3a114fc1282ff 100644 --- a/stdlib/public/core/Integers.swift +++ b/stdlib/public/core/Integers.swift @@ -1495,64 +1495,177 @@ extension BinaryInteger { //===--- CustomStringConvertible conformance ------------------------------===// //===----------------------------------------------------------------------===// -extension BinaryInteger { - internal func _description(radix: Int, uppercase: Bool) -> String { - _precondition(2...36 ~= radix, "Radix must be between 2 and 36") - - if bitWidth <= 64 { - let radix_ = Int64(radix) - return Self.isSigned - ? _int64ToString( - Int64(truncatingIfNeeded: self), radix: radix_, uppercase: uppercase) - : _uint64ToString( - UInt64(truncatingIfNeeded: self), radix: radix_, uppercase: uppercase) - } +@_alwaysEmitIntoClient +internal func _overestimatedBufferCapacity( + _ value: T, radix: Int +) -> Int { + assert(radix >= 2 && radix <= 36) + let bitWidth = value.bitWidth + // We have to *overestimate* the buffer capacity required, and we'll do so by + // finding the nearest power-of-two base that's equal to or less than `radix`. + // + // This function could be optimized by looking to the *value* rather than the + // value's bit width, but consider that this may involve multiple + // generic heterogeneous comparisons. + let divisor = Int.bitWidth &- radix.leadingZeroBitCount &- 1 + let result = (bitWidth &- 1) / divisor &+ 1 + return T.isSigned ? result &+ 1 : result +} - if self == (0 as Self) { return "0" } +@_alwaysEmitIntoClient +internal func _convertSignedInteger( + _ buffer: UnsafeMutableBufferPointer, _ value: T, + radix: Int, uppercase: Bool +) -> /* initializedCount: */ Int { + var initializedCount = _convertUnsignedInteger( + buffer, value.magnitude, radix: radix, uppercase: uppercase) + if value < (0 as T) { + initializedCount += 1 + buffer[buffer.count &- initializedCount] = 45 /* "-" */ + } + return initializedCount +} - // Bit shifting can be faster than division when `radix` is a power of two - // (although not necessarily the case for builtin types). - let isRadixPowerOfTwo = radix.nonzeroBitCount == 1 - let radix_ = Magnitude(radix) - func _quotientAndRemainder(_ value: Magnitude) -> (Magnitude, Magnitude) { - return isRadixPowerOfTwo - ? (value >> radix.trailingZeroBitCount, value & (radix_ - 1)) - : value.quotientAndRemainder(dividingBy: radix_) - } +@_alwaysEmitIntoClient +internal func _convertUnsignedInteger( + _ buffer: UnsafeMutableBufferPointer, _ value: T, + radix: Int, uppercase: Bool +) -> /* initializedCount: */ Int { + if value == (0 as T) { + buffer[buffer.count &- 1] = 48 /* "0" */ + return 1 + } + if value.bitWidth <= 64 { + return _convertNonzeroUInt64( + buffer, UInt64(truncatingIfNeeded: value), + radix: radix, uppercase: uppercase) + } + return _convertNonzeroUnsignedInteger( + buffer, value, radix: radix, uppercase: uppercase) +} - let hasLetters = radix > 10 - func _ascii(_ digit: UInt8) -> UInt8 { - let base: UInt8 - if !hasLetters || digit < 10 { - base = UInt8(("0" as Unicode.Scalar).value) - } else if uppercase { - base = UInt8(("A" as Unicode.Scalar).value) &- 10 - } else { - base = UInt8(("a" as Unicode.Scalar).value) &- 10 - } - return base &+ digit - } +@_alwaysEmitIntoClient +internal func _convertNonzeroUInt64( + _ buffer: UnsafeMutableBufferPointer, _ value: UInt64, + radix: Int, uppercase: Bool +) -> /* initializedCount: */ Int { + switch radix { + case 10: + return _convertNonzeroUInt64ToDecimal(buffer, value) + case 16: + return _convertNonzeroUInt64ToHexadecimal( + buffer, value, uppercase: uppercase) + default: + return _convertNonzeroUnsignedInteger( + buffer, value, radix: radix, uppercase: uppercase) + } +} - let isNegative = Self.isSigned && self < (0 as Self) - var value = magnitude +@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +@usableFromInline +internal let _decimalDigitsLookupTable: [(UInt8, UInt8)] = [ + (0x30, 0x30), (0x31, 0x30), (0x32, 0x30), (0x33, 0x30), (0x34, 0x30), + (0x35, 0x30), (0x36, 0x30), (0x37, 0x30), (0x38, 0x30), (0x39, 0x30), + (0x30, 0x31), (0x31, 0x31), (0x32, 0x31), (0x33, 0x31), (0x34, 0x31), + (0x35, 0x31), (0x36, 0x31), (0x37, 0x31), (0x38, 0x31), (0x39, 0x31), + (0x30, 0x32), (0x31, 0x32), (0x32, 0x32), (0x33, 0x32), (0x34, 0x32), + (0x35, 0x32), (0x36, 0x32), (0x37, 0x32), (0x38, 0x32), (0x39, 0x32), + (0x30, 0x33), (0x31, 0x33), (0x32, 0x33), (0x33, 0x33), (0x34, 0x33), + (0x35, 0x33), (0x36, 0x33), (0x37, 0x33), (0x38, 0x33), (0x39, 0x33), + (0x30, 0x34), (0x31, 0x34), (0x32, 0x34), (0x33, 0x34), (0x34, 0x34), + (0x35, 0x34), (0x36, 0x34), (0x37, 0x34), (0x38, 0x34), (0x39, 0x34), + (0x30, 0x35), (0x31, 0x35), (0x32, 0x35), (0x33, 0x35), (0x34, 0x35), + (0x35, 0x35), (0x36, 0x35), (0x37, 0x35), (0x38, 0x35), (0x39, 0x35), + (0x30, 0x36), (0x31, 0x36), (0x32, 0x36), (0x33, 0x36), (0x34, 0x36), + (0x35, 0x36), (0x36, 0x36), (0x37, 0x36), (0x38, 0x36), (0x39, 0x36), + (0x30, 0x37), (0x31, 0x37), (0x32, 0x37), (0x33, 0x37), (0x34, 0x37), + (0x35, 0x37), (0x36, 0x37), (0x37, 0x37), (0x38, 0x37), (0x39, 0x37), + (0x30, 0x38), (0x31, 0x38), (0x32, 0x38), (0x33, 0x38), (0x34, 0x38), + (0x35, 0x38), (0x36, 0x38), (0x37, 0x38), (0x38, 0x38), (0x39, 0x38), + (0x30, 0x39), (0x31, 0x39), (0x32, 0x39), (0x33, 0x39), (0x34, 0x39), + (0x35, 0x39), (0x36, 0x39), (0x37, 0x39), (0x38, 0x39), (0x39, 0x39), +] + +@_alwaysEmitIntoClient +internal func _convertNonzeroUInt64ToDecimal( + _ buffer: UnsafeMutableBufferPointer, _ value: UInt64 +) -> /* initializedCount: */ Int { + guard #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) else { + return _convertNonzeroUnsignedInteger( + buffer, value, radix: 10, uppercase: false) + } + var value = value + var i = buffer.count + while value >= 10 { + i -= 2 + let digits: UInt64 + (value, digits) = value.quotientAndRemainder(dividingBy: 100) + let digits_ = Int(truncatingIfNeeded: digits) + (buffer[i &+ 1], buffer[i]) = _decimalDigitsLookupTable[digits_] + } + if value > 0 { + i -= 1 + buffer[i] = 48 /* "0" */ &+ UInt8(truncatingIfNeeded: value) + } + return buffer.count &- i +} - // TODO(FIXME JIRA): All current stdlib types fit in small. Use a stack - // buffer instead of an array on the heap. +@_alwaysEmitIntoClient +internal func _convertNonzeroUInt64ToHexadecimal( + _ buffer: UnsafeMutableBufferPointer, _ value: UInt64, + uppercase: Bool +) -> /* initializedCount: */ Int { + //TODO: Implement fast path. + return _convertNonzeroUnsignedInteger( + buffer, value, radix: 16, uppercase: uppercase) +} - var result: [UInt8] = [] - while value != 0 { - let (quotient, remainder) = _quotientAndRemainder(value) - result.append(_ascii(UInt8(truncatingIfNeeded: remainder))) - value = quotient +@_alwaysEmitIntoClient +@_specialize(where T == UInt64) +@inline(never) +internal func _convertNonzeroUnsignedInteger( + _ buffer: UnsafeMutableBufferPointer, _ value: T, + radix: Int, uppercase: Bool +) -> /* initializedCount: */ Int { + _internalInvariant(radix >= 2 && radix <= 36) + let radix = T(radix) + var value = value + var i = buffer.count + while value > (0 as T) { + i -= 1 + let digit: T + (value, digit) = value.quotientAndRemainder(dividingBy: radix) + if digit < 10 { + buffer[i] = 48 /* "0" */ &+ UInt8(truncatingIfNeeded: digit) + } else if uppercase { + buffer[i] = 65 /* "A" */ &+ (UInt8(truncatingIfNeeded: digit) &- 10) + } else { + buffer[i] = 97 /* "a" */ &+ (UInt8(truncatingIfNeeded: digit) &- 10) } + } + return buffer.count &- i +} - if isNegative { - result.append(UInt8(("-" as Unicode.Scalar).value)) - } +extension BinaryInteger { + internal func _description(radix: Int, uppercase: Bool) -> String { + _precondition(2...36 ~= radix, "Radix must be between 2 and 36") - result.reverse() - return result.withUnsafeBufferPointer { - return String._fromASCII($0) + let capacity = _overestimatedBufferCapacity(self, radix: radix) + return String(_uninitializedCapacity: capacity) { + let initializedCount = Self.isSigned + ? _convertSignedInteger($0, self, radix: radix, uppercase: uppercase) + : _convertUnsignedInteger($0, self, radix: radix, uppercase: uppercase) + // The last `initializedCount` bytes of the buffer have been initialized, + // but we need the first `initializedCount` bytes of the buffer to be + // initialized instead, so we move the initialized bytes if necessary. + let count = $0.count + if initializedCount < count { + let baseAddress = $0.baseAddress! + baseAddress.moveInitialize( + from: baseAddress + (count &- initializedCount), + count: initializedCount) + } + return initializedCount } } From ca584a387fa761a78caa3a533632bfe20c6cf4ae Mon Sep 17 00:00:00 2001 From: Xiaodi Wu <13952+xwu@users.noreply.github.com> Date: Mon, 29 Mar 2021 19:34:27 -0400 Subject: [PATCH 2/6] [stdlib] Remove now unused '{u}intToString' free functions and related code. --- stdlib/public/core/MigrationSupport.swift | 8 +- stdlib/public/core/Runtime.swift | 171 ++++------------------ test/stdlib/Runtime.swift.gyb | 16 +- 3 files changed, 37 insertions(+), 158 deletions(-) diff --git a/stdlib/public/core/MigrationSupport.swift b/stdlib/public/core/MigrationSupport.swift index 16124058138bc..386497a411dfe 100644 --- a/stdlib/public/core/MigrationSupport.swift +++ b/stdlib/public/core/MigrationSupport.swift @@ -408,7 +408,7 @@ extension UnsafeRawPointer: _CustomPlaygroundQuickLookable { bitPattern: Int64(Int(Builtin.ptrtoint_Word(_rawValue)))) return ptrValue == 0 ? "UnsafeRawPointer(nil)" - : "UnsafeRawPointer(0x\(_uint64ToString(ptrValue, radix:16, uppercase:true)))" + : "UnsafeRawPointer(0x\(ptrValue._description(radix: 16, uppercase: true)))" } @available(swift, deprecated: 4.2/*, obsoleted: 5.0*/, message: "UnsafeRawPointer.customPlaygroundQuickLook will be removed in a future Swift version") @@ -423,7 +423,7 @@ extension UnsafeMutableRawPointer: _CustomPlaygroundQuickLookable { bitPattern: Int64(Int(Builtin.ptrtoint_Word(_rawValue)))) return ptrValue == 0 ? "UnsafeMutableRawPointer(nil)" - : "UnsafeMutableRawPointer(0x\(_uint64ToString(ptrValue, radix:16, uppercase:true)))" + : "UnsafeMutableRawPointer(0x\(ptrValue._description(radix: 16, uppercase: true)))" } @available(swift, deprecated: 4.2/*, obsoleted: 5.0*/, message: "UnsafeMutableRawPointer.customPlaygroundQuickLook will be removed in a future Swift version") @@ -437,7 +437,7 @@ extension UnsafePointer: _CustomPlaygroundQuickLookable { let ptrValue = UInt64(bitPattern: Int64(Int(Builtin.ptrtoint_Word(_rawValue)))) return ptrValue == 0 ? "UnsafePointer(nil)" - : "UnsafePointer(0x\(_uint64ToString(ptrValue, radix:16, uppercase:true)))" + : "UnsafePointer(0x\(ptrValue._description(radix: 16, uppercase: true)))" } @available(swift, deprecated: 4.2/*, obsoleted: 5.0*/, message: "UnsafePointer.customPlaygroundQuickLook will be removed in a future Swift version") @@ -451,7 +451,7 @@ extension UnsafeMutablePointer: _CustomPlaygroundQuickLookable { let ptrValue = UInt64(bitPattern: Int64(Int(Builtin.ptrtoint_Word(_rawValue)))) return ptrValue == 0 ? "UnsafeMutablePointer(nil)" - : "UnsafeMutablePointer(0x\(_uint64ToString(ptrValue, radix:16, uppercase:true)))" + : "UnsafeMutablePointer(0x\(ptrValue._description(radix: 16, uppercase: true)))" } @available(swift, deprecated: 4.2/*, obsoleted: 5.0*/, message: "UnsafeMutablePointer.customPlaygroundQuickLook will be removed in a future Swift version") diff --git a/stdlib/public/core/Runtime.swift b/stdlib/public/core/Runtime.swift index 7df18bd78a232..f1d00d6d1e6d5 100644 --- a/stdlib/public/core/Runtime.swift +++ b/stdlib/public/core/Runtime.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2021 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 @@ -207,92 +207,6 @@ internal struct _Buffer32 { } } -/// A 72 byte buffer. -internal struct _Buffer72 { - internal init() {} - - internal var _x0: UInt8 = 0 - internal var _x1: UInt8 = 0 - internal var _x2: UInt8 = 0 - internal var _x3: UInt8 = 0 - internal var _x4: UInt8 = 0 - internal var _x5: UInt8 = 0 - internal var _x6: UInt8 = 0 - internal var _x7: UInt8 = 0 - internal var _x8: UInt8 = 0 - internal var _x9: UInt8 = 0 - internal var _x10: UInt8 = 0 - internal var _x11: UInt8 = 0 - internal var _x12: UInt8 = 0 - internal var _x13: UInt8 = 0 - internal var _x14: UInt8 = 0 - internal var _x15: UInt8 = 0 - internal var _x16: UInt8 = 0 - internal var _x17: UInt8 = 0 - internal var _x18: UInt8 = 0 - internal var _x19: UInt8 = 0 - internal var _x20: UInt8 = 0 - internal var _x21: UInt8 = 0 - internal var _x22: UInt8 = 0 - internal var _x23: UInt8 = 0 - internal var _x24: UInt8 = 0 - internal var _x25: UInt8 = 0 - internal var _x26: UInt8 = 0 - internal var _x27: UInt8 = 0 - internal var _x28: UInt8 = 0 - internal var _x29: UInt8 = 0 - internal var _x30: UInt8 = 0 - internal var _x31: UInt8 = 0 - internal var _x32: UInt8 = 0 - internal var _x33: UInt8 = 0 - internal var _x34: UInt8 = 0 - internal var _x35: UInt8 = 0 - internal var _x36: UInt8 = 0 - internal var _x37: UInt8 = 0 - internal var _x38: UInt8 = 0 - internal var _x39: UInt8 = 0 - internal var _x40: UInt8 = 0 - internal var _x41: UInt8 = 0 - internal var _x42: UInt8 = 0 - internal var _x43: UInt8 = 0 - internal var _x44: UInt8 = 0 - internal var _x45: UInt8 = 0 - internal var _x46: UInt8 = 0 - internal var _x47: UInt8 = 0 - internal var _x48: UInt8 = 0 - internal var _x49: UInt8 = 0 - internal var _x50: UInt8 = 0 - internal var _x51: UInt8 = 0 - internal var _x52: UInt8 = 0 - internal var _x53: UInt8 = 0 - internal var _x54: UInt8 = 0 - internal var _x55: UInt8 = 0 - internal var _x56: UInt8 = 0 - internal var _x57: UInt8 = 0 - internal var _x58: UInt8 = 0 - internal var _x59: UInt8 = 0 - internal var _x60: UInt8 = 0 - internal var _x61: UInt8 = 0 - internal var _x62: UInt8 = 0 - internal var _x63: UInt8 = 0 - internal var _x64: UInt8 = 0 - internal var _x65: UInt8 = 0 - internal var _x66: UInt8 = 0 - internal var _x67: UInt8 = 0 - internal var _x68: UInt8 = 0 - internal var _x69: UInt8 = 0 - internal var _x70: UInt8 = 0 - internal var _x71: UInt8 = 0 - - internal mutating func withBytes( - _ body: (UnsafeMutablePointer) throws -> Result - ) rethrows -> Result { - return try withUnsafeMutablePointer(to: &self) { - try body(UnsafeMutableRawPointer($0).assumingMemoryBound(to: UInt8.self)) - } - } -} - #if !((os(macOS) || targetEnvironment(macCatalyst)) && arch(x86_64)) // Note that this takes a Float32 argument instead of Float16, because clang // doesn't have _Float16 on all platforms yet. @@ -366,9 +280,7 @@ internal func _float64ToString( return (buffer, length) } - #if !(os(Windows) || os(Android)) && (arch(i386) || arch(x86_64)) - // Returns a UInt64, but that value is the length of the string, so it's // guaranteed to fit into an Int. This is part of the ABI, so we can't // trivially change it to Int. Callers can safely convert the result @@ -394,6 +306,20 @@ internal func _float80ToString( } #endif +@inlinable +internal func _rawPointerToString(_ value: Builtin.RawPointer) -> String { + var result = String( + UInt64(UInt(bitPattern: UnsafeRawPointer(value))), + radix: 16, + uppercase: false) + let count = 2 * MemoryLayout.size - result.utf8.count + return "0x" + String(repeating: "0", count: count) + result +} + +//===----------------------------------------------------------------------===// +// Old entry points preserved for ABI compatibility. +//===----------------------------------------------------------------------===// + // Returns a UInt64, but that value is the length of the string, so it's // guaranteed to fit into an Int. This is part of the ABI, so we can't // trivially change it to Int. Callers can safely convert the result @@ -407,30 +333,6 @@ internal func _int64ToStringImpl( _ uppercase: Bool ) -> UInt64 -internal func _int64ToString( - _ value: Int64, - radix: Int64 = 10, - uppercase: Bool = false -) -> String { - if radix >= 10 { - var buffer = _Buffer32() - return buffer.withBytes { (bufferPtr) in - let actualLength = _int64ToStringImpl(bufferPtr, 32, value, radix, uppercase) - return String._fromASCII(UnsafeBufferPointer( - start: bufferPtr, count: Int(truncatingIfNeeded: actualLength) - )) - } - } else { - var buffer = _Buffer72() - return buffer.withBytes { (bufferPtr) in - let actualLength = _int64ToStringImpl(bufferPtr, 72, value, radix, uppercase) - return String._fromASCII(UnsafeBufferPointer( - start: bufferPtr, count: Int(truncatingIfNeeded: actualLength) - )) - } - } -} - // Returns a UInt64, but that value is the length of the string, so it's // guaranteed to fit into an Int. This is part of the ABI, so we can't // trivially change it to Int. Callers can safely convert the result @@ -444,44 +346,21 @@ internal func _uint64ToStringImpl( _ uppercase: Bool ) -> UInt64 +@available(*, unavailable) public // @testable func _uint64ToString( - _ value: UInt64, - radix: Int64 = 10, - uppercase: Bool = false + _ value: UInt64, + radix: Int64 = 10, + uppercase: Bool = false ) -> String { - if radix >= 10 { - var buffer = _Buffer32() - return buffer.withBytes { (bufferPtr) in - let actualLength = _uint64ToStringImpl(bufferPtr, 32, value, radix, uppercase) - return String._fromASCII(UnsafeBufferPointer( - start: bufferPtr, count: Int(truncatingIfNeeded: actualLength) - )) - } - } else { - var buffer = _Buffer72() - return buffer.withBytes { (bufferPtr) in - let actualLength = _uint64ToStringImpl(bufferPtr, 72, value, radix, uppercase) - return String._fromASCII(UnsafeBufferPointer( - start: bufferPtr, count: Int(truncatingIfNeeded: actualLength) - )) - } - } + return value._description( + radix: Int(truncatingIfNeeded: radix), + uppercase: uppercase) } -@inlinable -internal func _rawPointerToString(_ value: Builtin.RawPointer) -> String { - var result = _uint64ToString( - UInt64( - UInt(bitPattern: UnsafeRawPointer(value))), - radix: 16, - uppercase: false - ) - for _ in 0..<(2 * MemoryLayout.size - result.utf16.count) { - result = "0" + result - } - return "0x" + result -} +//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------===// #if _runtime(_ObjC) // At runtime, these classes are derived from `__SwiftNativeNSXXXBase`, diff --git a/test/stdlib/Runtime.swift.gyb b/test/stdlib/Runtime.swift.gyb index b57137c622cf3..5ecb83a227f35 100644 --- a/test/stdlib/Runtime.swift.gyb +++ b/test/stdlib/Runtime.swift.gyb @@ -119,10 +119,10 @@ struct Struct3ConformsToP2 : CustomStringConvertible, Q1 { // Don't rely on string interpolation, it uses the casts that we are trying // to test. var result = "" - result += _uint64ToString(a) + " " - result += _uint64ToString(b) + " " - result += _uint64ToString(c) + " " - result += _uint64ToString(d) + result += a.description + " " + result += b.description + " " + result += c.description + " " + result += d.description return result } } @@ -143,10 +143,10 @@ struct Struct4ConformsToP2 : CustomStringConvertibl // Don't rely on string interpolation, it uses the casts that we are trying // to test. var result = value.description + " " - result += _uint64ToString(e) + " " - result += _uint64ToString(f) + " " - result += _uint64ToString(g) + " " - result += _uint64ToString(h) + result += e.description + " " + result += f.description + " " + result += g.description + " " + result += h.description return result } } From 263e4fb2bf5092840add91454d63c6277267b978 Mon Sep 17 00:00:00 2001 From: Xiaodi Wu <13952+xwu@users.noreply.github.com> Date: Tue, 30 Mar 2021 09:37:18 -0400 Subject: [PATCH 3/6] [stdlib] Work around a '_SmallString' bug for integer-to-string conversion. --- stdlib/public/core/Integers.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/stdlib/public/core/Integers.swift b/stdlib/public/core/Integers.swift index 3a114fc1282ff..5722a3fa89b15 100644 --- a/stdlib/public/core/Integers.swift +++ b/stdlib/public/core/Integers.swift @@ -1661,9 +1661,14 @@ extension BinaryInteger { let count = $0.count if initializedCount < count { let baseAddress = $0.baseAddress! + let unusedCapacity = count &- initializedCount baseAddress.moveInitialize( - from: baseAddress + (count &- initializedCount), + from: baseAddress + unusedCapacity, count: initializedCount) + // Work around a `_SmallString` bug. + let ptr = baseAddress + initializedCount + ptr.initialize(repeating: 0, count: unusedCapacity) + ptr.deinitialize(count: unusedCapacity) } return initializedCount } From 9dd687c5cbac8cea6f30e72f0661f771b2571f7d Mon Sep 17 00:00:00 2001 From: Xiaodi Wu <13952+xwu@users.noreply.github.com> Date: Tue, 30 Mar 2021 09:43:29 -0400 Subject: [PATCH 4/6] [stdlib] Fix up availability to preserve ABI compatibility. --- stdlib/public/core/Runtime.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/public/core/Runtime.swift b/stdlib/public/core/Runtime.swift index f1d00d6d1e6d5..b3810624457b7 100644 --- a/stdlib/public/core/Runtime.swift +++ b/stdlib/public/core/Runtime.swift @@ -346,7 +346,7 @@ internal func _uint64ToStringImpl( _ uppercase: Bool ) -> UInt64 -@available(*, unavailable) +@available(*, deprecated, message: "Use 'String(_:radix:uppercase:)' instead") public // @testable func _uint64ToString( _ value: UInt64, From f843741d1fef7180c295b417f4d06b773079cd4c Mon Sep 17 00:00:00 2001 From: Xiaodi Wu <13952+xwu@users.noreply.github.com> Date: Tue, 30 Mar 2021 12:57:32 -0400 Subject: [PATCH 5/6] [stdlib] Simplify decimal fast path for integer-to-string conversion and implement a hexadecimal one. --- stdlib/public/core/Integers.swift | 74 +++++++++++++------------------ 1 file changed, 32 insertions(+), 42 deletions(-) diff --git a/stdlib/public/core/Integers.swift b/stdlib/public/core/Integers.swift index 5722a3fa89b15..84e774c6f15c5 100644 --- a/stdlib/public/core/Integers.swift +++ b/stdlib/public/core/Integers.swift @@ -1496,6 +1496,7 @@ extension BinaryInteger { //===----------------------------------------------------------------------===// @_alwaysEmitIntoClient +@inline(__always) internal func _overestimatedBufferCapacity( _ value: T, radix: Int ) -> Int { @@ -1513,6 +1514,7 @@ internal func _overestimatedBufferCapacity( } @_alwaysEmitIntoClient +@inline(__always) internal func _convertSignedInteger( _ buffer: UnsafeMutableBufferPointer, _ value: T, radix: Int, uppercase: Bool @@ -1527,6 +1529,7 @@ internal func _convertSignedInteger( } @_alwaysEmitIntoClient +@inline(__always) internal func _convertUnsignedInteger( _ buffer: UnsafeMutableBufferPointer, _ value: T, radix: Int, uppercase: Bool @@ -1545,6 +1548,7 @@ internal func _convertUnsignedInteger( } @_alwaysEmitIntoClient +@inline(__always) internal func _convertNonzeroUInt64( _ buffer: UnsafeMutableBufferPointer, _ value: UInt64, radix: Int, uppercase: Bool @@ -1561,68 +1565,54 @@ internal func _convertNonzeroUInt64( } } -@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) -@usableFromInline -internal let _decimalDigitsLookupTable: [(UInt8, UInt8)] = [ - (0x30, 0x30), (0x31, 0x30), (0x32, 0x30), (0x33, 0x30), (0x34, 0x30), - (0x35, 0x30), (0x36, 0x30), (0x37, 0x30), (0x38, 0x30), (0x39, 0x30), - (0x30, 0x31), (0x31, 0x31), (0x32, 0x31), (0x33, 0x31), (0x34, 0x31), - (0x35, 0x31), (0x36, 0x31), (0x37, 0x31), (0x38, 0x31), (0x39, 0x31), - (0x30, 0x32), (0x31, 0x32), (0x32, 0x32), (0x33, 0x32), (0x34, 0x32), - (0x35, 0x32), (0x36, 0x32), (0x37, 0x32), (0x38, 0x32), (0x39, 0x32), - (0x30, 0x33), (0x31, 0x33), (0x32, 0x33), (0x33, 0x33), (0x34, 0x33), - (0x35, 0x33), (0x36, 0x33), (0x37, 0x33), (0x38, 0x33), (0x39, 0x33), - (0x30, 0x34), (0x31, 0x34), (0x32, 0x34), (0x33, 0x34), (0x34, 0x34), - (0x35, 0x34), (0x36, 0x34), (0x37, 0x34), (0x38, 0x34), (0x39, 0x34), - (0x30, 0x35), (0x31, 0x35), (0x32, 0x35), (0x33, 0x35), (0x34, 0x35), - (0x35, 0x35), (0x36, 0x35), (0x37, 0x35), (0x38, 0x35), (0x39, 0x35), - (0x30, 0x36), (0x31, 0x36), (0x32, 0x36), (0x33, 0x36), (0x34, 0x36), - (0x35, 0x36), (0x36, 0x36), (0x37, 0x36), (0x38, 0x36), (0x39, 0x36), - (0x30, 0x37), (0x31, 0x37), (0x32, 0x37), (0x33, 0x37), (0x34, 0x37), - (0x35, 0x37), (0x36, 0x37), (0x37, 0x37), (0x38, 0x37), (0x39, 0x37), - (0x30, 0x38), (0x31, 0x38), (0x32, 0x38), (0x33, 0x38), (0x34, 0x38), - (0x35, 0x38), (0x36, 0x38), (0x37, 0x38), (0x38, 0x38), (0x39, 0x38), - (0x30, 0x39), (0x31, 0x39), (0x32, 0x39), (0x33, 0x39), (0x34, 0x39), - (0x35, 0x39), (0x36, 0x39), (0x37, 0x39), (0x38, 0x39), (0x39, 0x39), -] - @_alwaysEmitIntoClient +@inline(__always) internal func _convertNonzeroUInt64ToDecimal( _ buffer: UnsafeMutableBufferPointer, _ value: UInt64 ) -> /* initializedCount: */ Int { - guard #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) else { - return _convertNonzeroUnsignedInteger( - buffer, value, radix: 10, uppercase: false) - } var value = value var i = buffer.count - while value >= 10 { - i -= 2 - let digits: UInt64 - (value, digits) = value.quotientAndRemainder(dividingBy: 100) - let digits_ = Int(truncatingIfNeeded: digits) - (buffer[i &+ 1], buffer[i]) = _decimalDigitsLookupTable[digits_] - } - if value > 0 { + while value > 0 { i -= 1 - buffer[i] = 48 /* "0" */ &+ UInt8(truncatingIfNeeded: value) + let digit: UInt64 + (value, digit) = value.quotientAndRemainder(dividingBy: 10) + buffer[i] = 48 /* "0" */ &+ UInt8(truncatingIfNeeded: digit) } return buffer.count &- i } @_alwaysEmitIntoClient +@inline(__always) internal func _convertNonzeroUInt64ToHexadecimal( _ buffer: UnsafeMutableBufferPointer, _ value: UInt64, uppercase: Bool ) -> /* initializedCount: */ Int { - //TODO: Implement fast path. - return _convertNonzeroUnsignedInteger( - buffer, value, radix: 16, uppercase: uppercase) + var value = value + var i = buffer.count + if uppercase { + while value > 0 { + i -= 1 + let digit = value & 15 + value &>>= 4 + buffer[i] = digit < 10 + ? 48 /* "0" */ &+ UInt8(truncatingIfNeeded: digit) + : 65 /* "A" */ &+ (UInt8(truncatingIfNeeded: digit) &- 10) + } + } else { + while value > 0 { + i -= 1 + let digit = value & 15 + value &>>= 4 + buffer[i] = digit < 10 + ? 48 /* "0" */ &+ UInt8(truncatingIfNeeded: digit) + : 97 /* "a" */ &+ (UInt8(truncatingIfNeeded: digit) &- 10) + } + } + return buffer.count &- i } @_alwaysEmitIntoClient @_specialize(where T == UInt64) -@inline(never) internal func _convertNonzeroUnsignedInteger( _ buffer: UnsafeMutableBufferPointer, _ value: T, radix: Int, uppercase: Bool From ad0e0f8f1a8c0a328ff928d2b92e3864189049d5 Mon Sep 17 00:00:00 2001 From: Xiaodi Wu <13952+xwu@users.noreply.github.com> Date: Tue, 30 Mar 2021 17:33:54 -0400 Subject: [PATCH 6/6] [stdlib] Let's see if inlining improves integer-to-string performance. --- stdlib/public/core/Integers.swift | 58 +++++++++++++++++++------------ 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/stdlib/public/core/Integers.swift b/stdlib/public/core/Integers.swift index 84e774c6f15c5..75fe4e29e4d21 100644 --- a/stdlib/public/core/Integers.swift +++ b/stdlib/public/core/Integers.swift @@ -1515,13 +1515,34 @@ internal func _overestimatedBufferCapacity( @_alwaysEmitIntoClient @inline(__always) -internal func _convertSignedInteger( +internal func _moveInitializedBytesFromEnd( + _ buffer: UnsafeMutableBufferPointer, + initializedCount: Int +) { + let count = buffer.count + let unusedCapacity = count &- initializedCount + guard unusedCapacity > 0 else { return } + // The last `initializedCount` bytes of the buffer have been initialized, but + // we need the first `initializedCount` bytes of the buffer to be initialized + // instead, so we move the initialized bytes if necessary. + _internalInvariant(buffer.baseAddress != nil) + let ptr1 = buffer.baseAddress! + ptr1.moveInitialize(from: ptr1 + unusedCapacity, count: initializedCount) + // Work around a `_SmallString` bug. + let ptr2 = ptr1 + initializedCount + ptr2.initialize(repeating: 0, count: unusedCapacity) + ptr2.deinitialize(count: unusedCapacity) +} + +@_alwaysEmitIntoClient +@inline(__always) +internal func _convertInteger( _ buffer: UnsafeMutableBufferPointer, _ value: T, radix: Int, uppercase: Bool ) -> /* initializedCount: */ Int { var initializedCount = _convertUnsignedInteger( buffer, value.magnitude, radix: radix, uppercase: uppercase) - if value < (0 as T) { + if T.isSigned && value < (0 as T) { initializedCount += 1 buffer[buffer.count &- initializedCount] = 45 /* "-" */ } @@ -1637,35 +1658,28 @@ internal func _convertNonzeroUnsignedInteger( } extension BinaryInteger { + @_alwaysEmitIntoClient + @inline(__always) internal func _description(radix: Int, uppercase: Bool) -> String { _precondition(2...36 ~= radix, "Radix must be between 2 and 36") - let capacity = _overestimatedBufferCapacity(self, radix: radix) - return String(_uninitializedCapacity: capacity) { - let initializedCount = Self.isSigned - ? _convertSignedInteger($0, self, radix: radix, uppercase: uppercase) - : _convertUnsignedInteger($0, self, radix: radix, uppercase: uppercase) - // The last `initializedCount` bytes of the buffer have been initialized, - // but we need the first `initializedCount` bytes of the buffer to be - // initialized instead, so we move the initialized bytes if necessary. - let count = $0.count - if initializedCount < count { - let baseAddress = $0.baseAddress! - let unusedCapacity = count &- initializedCount - baseAddress.moveInitialize( - from: baseAddress + unusedCapacity, - count: initializedCount) - // Work around a `_SmallString` bug. - let ptr = baseAddress + initializedCount - ptr.initialize(repeating: 0, count: unusedCapacity) - ptr.deinitialize(count: unusedCapacity) + if #available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) { + return String(unsafeUninitializedCapacity: capacity) { + let i = _convertInteger($0, self, radix: radix, uppercase: uppercase) + _moveInitializedBytesFromEnd($0, initializedCount: i) + return i } - return initializedCount } + let buffer = + UnsafeMutableBufferPointer.allocate(capacity: capacity) + defer { buffer.deallocate() } + let i = _convertInteger(buffer, self, radix: radix, uppercase: uppercase) + return String._fromASCII(UnsafeBufferPointer(rebasing: buffer.suffix(i))) } /// A textual representation of this value. @_semantics("binaryInteger.description") + @inlinable public var description: String { return _description(radix: 10, uppercase: false) }