From 74a8254d402738cdb057df65c4ab8d06eb2398c7 Mon Sep 17 00:00:00 2001 From: Karl <5254025+karwa@users.noreply.github.com> Date: Sat, 14 Nov 2020 21:31:31 +0100 Subject: [PATCH 01/15] Make UBP nil/count checks debug-mode only Every other Unsafe(Mutable)BufferPointer precondition in this file is debug-mode only. This information is already part of the documentation for this function, and it's really hard to get rid of these branches and traps otherwise. --- stdlib/public/core/UnsafeBufferPointer.swift.gyb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/public/core/UnsafeBufferPointer.swift.gyb b/stdlib/public/core/UnsafeBufferPointer.swift.gyb index ee85c9de54d33..0528cbadcef97 100644 --- a/stdlib/public/core/UnsafeBufferPointer.swift.gyb +++ b/stdlib/public/core/UnsafeBufferPointer.swift.gyb @@ -401,9 +401,9 @@ extension Unsafe${Mutable}BufferPointer { public init( @_nonEphemeral start: Unsafe${Mutable}Pointer?, count: Int ) { - _precondition( + _debugPrecondition( count >= 0, "Unsafe${Mutable}BufferPointer with negative count") - _precondition( + _debugPrecondition( count == 0 || start != nil, "Unsafe${Mutable}BufferPointer has a nil start and nonzero count") _position = start From 83c14bca9cf3fae99b3bc6ae8c739ac37ec1b759 Mon Sep 17 00:00:00 2001 From: Karl <5254025+karwa@users.noreply.github.com> Date: Sat, 14 Nov 2020 22:28:40 +0100 Subject: [PATCH 02/15] Make raw buffer nil/count checks debug-mode only --- stdlib/public/core/UnsafeRawBufferPointer.swift.gyb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb b/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb index 1a7508ccf50a3..c3aaed135cc29 100644 --- a/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb +++ b/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb @@ -432,8 +432,8 @@ extension Unsafe${Mutable}RawBufferPointer { public init( @_nonEphemeral start: Unsafe${Mutable}RawPointer?, count: Int ) { - _precondition(count >= 0, "${Self} with negative count") - _precondition(count == 0 || start != nil, + _debugPrecondition(count >= 0, "${Self} with negative count") + _debugPrecondition(count == 0 || start != nil, "${Self} has a nil start and nonzero count") _position = start _end = start.map { $0 + count } From 90d58fef8c05861153654fafa96a0a63f4f3c20a Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 4 Dec 2020 01:49:50 -0800 Subject: [PATCH 03/15] =?UTF-8?q?[stdlib]=20Encourage=20more=20inlining=20?= =?UTF-8?q?on=20String=E2=80=99s=20UTF-8=20decoding=20fast=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- stdlib/public/core/String.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/stdlib/public/core/String.swift b/stdlib/public/core/String.swift index 9176dcfbef9e0..04651d0d78cff 100644 --- a/stdlib/public/core/String.swift +++ b/stdlib/public/core/String.swift @@ -458,6 +458,7 @@ extension String { contigBytes._providesContiguousBytesNoCopy { self = contigBytes.withUnsafeBytes { rawBufPtr in + Builtin.onFastPath() // encourage SIL Optimizer to inline this closure return String._fromUTF8Repairing( UnsafeBufferPointer( start: rawBufPtr.baseAddress?.assumingMemoryBound(to: UInt8.self), From 7f63b424965303ecfbea42404a720a96ddf063be Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 4 Dec 2020 01:53:06 -0800 Subject: [PATCH 04/15] [test] ArrayTraps: Skip trap test on negative UBP counts in release builds --- validation-test/stdlib/ArrayTraps.swift.gyb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/validation-test/stdlib/ArrayTraps.swift.gyb b/validation-test/stdlib/ArrayTraps.swift.gyb index b632fa0609edf..ca46edbe48881 100644 --- a/validation-test/stdlib/ArrayTraps.swift.gyb +++ b/validation-test/stdlib/ArrayTraps.swift.gyb @@ -172,8 +172,8 @@ ${ArrayTy}Traps.test("${'remove(at:)/%s' % index}") ArrayTraps.test("unsafeLength") .skip(.custom( - { _isFastAssertConfiguration() }, - reason: "this trap is not guaranteed to happen in -Ounchecked")) + { !_isDebugAssertConfiguration() }, + reason: "this trap is not guaranteed to happen in -O or -Ounchecked")) .crashOutputMatches(_isDebugAssertConfiguration() ? "UnsafeBufferPointer with negative count" : "") .code { From c5c27c7c693aed45ad228d70755088474412da47 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 4 Dec 2020 01:56:06 -0800 Subject: [PATCH 05/15] [stdlib] Add a missing debug precondition to [Closed]Range.init(uncheckedBounds:) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unchecked APIs must still perform checks in debug builds to ensure that invariants aren’t violated. (I.e., these aren’t a license to perform invalid operations — they just let us get rid of the checks when we know for sure they are unnecessary.) --- stdlib/public/core/ClosedRange.swift | 13 ++++++++++--- stdlib/public/core/Range.swift | 13 ++++++++++--- test/stdlib/RangeTraps.swift | 18 ++++++++++++++++++ validation-test/stdlib/Range.swift.gyb | 20 +++++++++++++++++--- 4 files changed, 55 insertions(+), 9 deletions(-) diff --git a/stdlib/public/core/ClosedRange.swift b/stdlib/public/core/ClosedRange.swift index b368c64ac64fc..f594514109f85 100644 --- a/stdlib/public/core/ClosedRange.swift +++ b/stdlib/public/core/ClosedRange.swift @@ -68,6 +68,12 @@ public struct ClosedRange { /// The range's upper bound. public let upperBound: Bound + @_alwaysEmitIntoClient @inline(__always) + internal init(_uncheckedBounds bounds: (lower: Bound, upper: Bound)) { + self.lowerBound = bounds.lower + self.upperBound = bounds.upper + } + /// Creates an instance with the given bounds. /// /// Because this initializer does not perform any checks, it should be used @@ -78,8 +84,9 @@ public struct ClosedRange { /// - Parameter bounds: A tuple of the lower and upper bounds of the range. @inlinable public init(uncheckedBounds bounds: (lower: Bound, upper: Bound)) { - self.lowerBound = bounds.lower - self.upperBound = bounds.upper + _debugPrecondition(bounds.lower <= bounds.upper, + "ClosedRange requires lowerBound <= upperBound") + self.init(_uncheckedBounds: (lower: bounds.lower, upper: bounds.upper)) } } @@ -336,7 +343,7 @@ extension Comparable { public static func ... (minimum: Self, maximum: Self) -> ClosedRange { _precondition( minimum <= maximum, "Range requires lowerBound <= upperBound") - return ClosedRange(uncheckedBounds: (lower: minimum, upper: maximum)) + return ClosedRange(_uncheckedBounds: (lower: minimum, upper: maximum)) } } diff --git a/stdlib/public/core/Range.swift b/stdlib/public/core/Range.swift index 4bcde92bf8153..81da1cdea823e 100644 --- a/stdlib/public/core/Range.swift +++ b/stdlib/public/core/Range.swift @@ -157,6 +157,12 @@ public struct Range { /// instance does not contain its upper bound. public let upperBound: Bound + @_alwaysEmitIntoClient @inline(__always) + internal init(_uncheckedBounds bounds: (lower: Bound, upper: Bound)) { + self.lowerBound = bounds.lower + self.upperBound = bounds.upper + } + /// Creates an instance with the given bounds. /// /// Because this initializer does not perform any checks, it should be used @@ -167,8 +173,9 @@ public struct Range { /// - Parameter bounds: A tuple of the lower and upper bounds of the range. @inlinable public init(uncheckedBounds bounds: (lower: Bound, upper: Bound)) { - self.lowerBound = bounds.lower - self.upperBound = bounds.upper + _debugPrecondition(bounds.lower <= bounds.upper, + "Range requires lowerBound <= upperBound") + self.init(_uncheckedBounds: (lower: bounds.lower, upper: bounds.upper)) } /// Returns a Boolean value indicating whether the given element is contained @@ -729,7 +736,7 @@ extension Comparable { public static func ..< (minimum: Self, maximum: Self) -> Range { _precondition(minimum <= maximum, "Range requires lowerBound <= upperBound") - return Range(uncheckedBounds: (lower: minimum, upper: maximum)) + return Range(_uncheckedBounds: (lower: minimum, upper: maximum)) } /// Returns a partial range up to, but not including, its upper bound. diff --git a/test/stdlib/RangeTraps.swift b/test/stdlib/RangeTraps.swift index c8e265416ef66..594a329b82ecc 100644 --- a/test/stdlib/RangeTraps.swift +++ b/test/stdlib/RangeTraps.swift @@ -116,5 +116,23 @@ RangeTraps.test("throughNaN") _ = ...Double.nan } +RangeTraps.test("UncheckedHalfOpen") + .xfail(.custom( + { !_isDebugAssertConfiguration() }, + reason: "assertions are disabled in Release and Unchecked mode")) + .code { + expectCrashLater() + var range = Range(uncheckedBounds: (lower: 1, upper: 0)) + } + +RangeTraps.test("UncheckedClosed") + .xfail(.custom( + { !_isDebugAssertConfiguration() }, + reason: "assertions are disabled in Release and Unchecked mode")) + .code { + expectCrashLater() + var range = ClosedRange(uncheckedBounds: (lower: 1, upper: 0)) + } + runAllTests() diff --git a/validation-test/stdlib/Range.swift.gyb b/validation-test/stdlib/Range.swift.gyb index b8fa3838e0f7f..f5898c73eb9fa 100644 --- a/validation-test/stdlib/Range.swift.gyb +++ b/validation-test/stdlib/Range.swift.gyb @@ -335,17 +335,31 @@ extension ${Self} where Bound : TestProtocol1 { var ${TestSuite} = TestSuite("${Self}") ${TestSuite}.test("init(uncheckedBounds:)") - .forEach(in: [(1, 2), (1, 1), (2, 1)]) { + .forEach(in: [(1, 2), (1, 1)]) { (lowerInt, upperInt) in - // Check that 'init(uncheckedBounds:)' does not perform precondition checks, - // allowing to create ranges that break invariants. let r = ${Self}( uncheckedBounds: (lower: ${Bound}(lowerInt), upper: ${Bound}(upperInt))) expectEqual(lowerInt, r.lowerBound.value) expectEqual(upperInt, r.upperBound.value) } +${TestSuite}.test("init(uncheckedBounds:) with invalid values") + .xfail( + .custom( + { _isDebugAssertConfiguration() }, + reason: "unchecked initializer still checks its input in Debug mode")) + .code { + let low = 2 + let up = 1 + // Check that 'init(uncheckedBounds:)' does not perform precondition checks, + // allowing to create ranges that break invariants. + let r = ${Self}( + uncheckedBounds: (lower: ${Bound}(low), upper: ${Bound}(up))) + expectEqual(low, r.lowerBound.value) + expectEqual(up, r.upperBound.value) +} + % for (DestinationSelf, _, _, _) in all_range_types: ${TestSuite}.test("init(${DestinationSelf})/whereBoundIsStrideable") .forEach(in: [(0, 0), (1, 2), (10, 20), (Int.min, Int.max)]) { From d52cd8243afe9e54941a4de02590303c8114ef57 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 4 Dec 2020 01:58:28 -0800 Subject: [PATCH 06/15] [stdlib] Add a missing debug mode check to U*BP.init(rebasing:) `Slice` does not technically guarantee that its indices are valid in its base, so these initializers accidentally allowed the creation of obviously out-of-bounds buffers. --- stdlib/public/core/UnsafeBufferPointer.swift.gyb | 11 +++++++++++ stdlib/public/core/UnsafeRawBufferPointer.swift.gyb | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/stdlib/public/core/UnsafeBufferPointer.swift.gyb b/stdlib/public/core/UnsafeBufferPointer.swift.gyb index 0528cbadcef97..b49b719e88cb6 100644 --- a/stdlib/public/core/UnsafeBufferPointer.swift.gyb +++ b/stdlib/public/core/UnsafeBufferPointer.swift.gyb @@ -498,6 +498,17 @@ extension Unsafe${Mutable}BufferPointer { /// - Parameter slice: The buffer slice to rebase. @inlinable // unsafe-performance public init(rebasing slice: Slice>) { + // NOTE: `Slice` does not guarantee that its start/end indices are valid + // in `base` -- it merely ensures that `startIndex <= endIndex`. + // We need manually check that we aren't given an invalid slice, + // or the resulting collection would allow access that was + // out-of-bounds with respect to the original base buffer. + // We only do this in debug builds to prevent a measurable performance + // degradation wrt passing around pointers not wrapped in a BufferPointer + // construct. + _debugPrecondition( + slice.startIndex >= 0 && slice.endIndex <= slice.base.count, + "Invalid slice") let base = slice.base.baseAddress?.advanced(by: slice.startIndex) let count = slice.endIndex &- slice.startIndex self.init(start: base, count: count) diff --git a/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb b/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb index c3aaed135cc29..2e26d1f9d5a9a 100644 --- a/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb +++ b/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb @@ -511,6 +511,17 @@ extension Unsafe${Mutable}RawBufferPointer { /// - Parameter slice: The raw buffer slice to rebase. @inlinable public init(rebasing slice: Slice) { + // NOTE: `Slice` does not guarantee that its start/end indices are valid + // in `base` -- it merely ensures that `startIndex <= endIndex`. + // We need manually check that we aren't given an invalid slice, + // or the resulting collection would allow access that was + // out-of-bounds with respect to the original base buffer. + // We only do this in debug builds to prevent a measurable performance + // degradation wrt passing around pointers not wrapped in a BufferPointer + // construct. + _debugPrecondition( + slice.startIndex >= 0 && slice.endIndex <= slice.base.count, + "Invalid slice") let base = slice.base.baseAddress?.advanced(by: slice.startIndex) let count = slice.endIndex &- slice.startIndex self.init(start: base, count: count) From 27978d16b9530a7f5f2a40489d943a229445b418 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 4 Dec 2020 02:00:44 -0800 Subject: [PATCH 07/15] [stdlib] Eliminate an overflow check in U[M]BP.distance --- stdlib/public/core/UnsafeBufferPointer.swift.gyb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/stdlib/public/core/UnsafeBufferPointer.swift.gyb b/stdlib/public/core/UnsafeBufferPointer.swift.gyb index b49b719e88cb6..8ec5f93567ced 100644 --- a/stdlib/public/core/UnsafeBufferPointer.swift.gyb +++ b/stdlib/public/core/UnsafeBufferPointer.swift.gyb @@ -207,7 +207,14 @@ extension Unsafe${Mutable}BufferPointer: ${Mutable}Collection, RandomAccessColle // optimizer is not capable of creating partial specializations yet. // NOTE: Range checks are not performed here, because it is done later by // the subscript function. - return end - start + // NOTE: We allow the subtraction to silently overflow in release builds + // to eliminate a superflous check when `start` and `end` are both valid + // indices. (The operation can only overflow if `start` is negative, which + // implies it's an invalid index.) `Collection` does not specify what + // `distance` should return when given an invalid index pair. + let result = end.subtractingReportingOverflow(start) + _debugPrecondition(!result.overflow) + return result.partialValue } @inlinable // unsafe-performance From 2607428347bb53b9c3bb11bbbce705b15b6b8cca Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 4 Dec 2020 02:01:30 -0800 Subject: [PATCH 08/15] [stdlib] U[M]RBP.count: Let the compiler assume that the result is nonnegative --- stdlib/public/core/UnsafeRawBufferPointer.swift.gyb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb b/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb index 2e26d1f9d5a9a..96c1a32390b41 100644 --- a/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb +++ b/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb @@ -253,7 +253,7 @@ extension Unsafe${Mutable}RawBufferPointer: ${Mutable}Collection { @inlinable public var count: Int { if let pos = _position { - return _end! - pos + return _assumeNonNegative(_end! - pos) } return 0 } From 513a7d9e1904f0662e18b8af090c11a91e01c105 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 4 Dec 2020 18:35:08 -0800 Subject: [PATCH 09/15] [stdlib] Substring: Restore original Range paths This eliminates a couple of _debugPreconditions which seem to change inlining enough to interfere with some benchmarks. --- stdlib/public/core/Substring.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/public/core/Substring.swift b/stdlib/public/core/Substring.swift index a4a7029898955..23d9fd85f1740 100644 --- a/stdlib/public/core/Substring.swift +++ b/stdlib/public/core/Substring.swift @@ -105,7 +105,7 @@ public struct Substring { self._slice = Slice( base: slice.base, - bounds: Range(uncheckedBounds: (start, end))) + bounds: Range(_uncheckedBounds: (start, end))) _invariantCheck() } @@ -132,7 +132,7 @@ extension Substring { @inlinable @inline(__always) internal var _offsetRange: Range { return Range( - uncheckedBounds: (startIndex._encodedOffset, endIndex._encodedOffset)) + _uncheckedBounds: (startIndex._encodedOffset, endIndex._encodedOffset)) } #if !INTERNAL_CHECKS_ENABLED From d1ef4c43862ac553335a5956496dc2fb8fab93c4 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 4 Dec 2020 19:27:19 -0800 Subject: [PATCH 10/15] [stdlib] Use [Closed]Range.init(_uncheckedBounds:) in more places MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the stdlib says not to check, it’s a good idea to actually not have any checking, so that we leave existing code paths unchanged. (And because we do trust that the stdlib is responsible about such things.) --- stdlib/public/core/ClosedRange.swift | 11 ++++++----- stdlib/public/core/Range.swift | 8 ++++---- stdlib/public/core/StringProtocol.swift | 2 +- stdlib/public/core/StringStorageBridge.swift | 2 +- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/stdlib/public/core/ClosedRange.swift b/stdlib/public/core/ClosedRange.swift index f594514109f85..133790ca0c05d 100644 --- a/stdlib/public/core/ClosedRange.swift +++ b/stdlib/public/core/ClosedRange.swift @@ -107,8 +107,9 @@ extension ClosedRange: RangeExpression { public func relative(to collection: C) -> Range where C.Index == Bound { return Range( - uncheckedBounds: ( - lower: lowerBound, upper: collection.index(after: self.upperBound))) + _uncheckedBounds: ( + lower: lowerBound, + upper: collection.index(after: self.upperBound))) } /// Returns a Boolean value indicating whether the given element is contained @@ -430,7 +431,7 @@ extension ClosedRange { limits.upperBound < self.upperBound ? limits.upperBound : limits.lowerBound > self.upperBound ? limits.lowerBound : self.upperBound - return ClosedRange(uncheckedBounds: (lower: lower, upper: upper)) + return ClosedRange(_uncheckedBounds: (lower: lower, upper: upper)) } } @@ -446,7 +447,7 @@ extension ClosedRange where Bound: Strideable, Bound.Stride: SignedInteger { public init(_ other: Range) { _precondition(!other.isEmpty, "Can't form an empty closed range") let upperBound = other.upperBound.advanced(by: -1) - self.init(uncheckedBounds: (lower: other.lowerBound, upper: upperBound)) + self.init(_uncheckedBounds: (lower: other.lowerBound, upper: upperBound)) } } @@ -483,7 +484,7 @@ extension ClosedRange: Decodable where Bound: Decodable { codingPath: decoder.codingPath, debugDescription: "Cannot initialize \(ClosedRange.self) with a lowerBound (\(lowerBound)) greater than upperBound (\(upperBound))")) } - self.init(uncheckedBounds: (lower: lowerBound, upper: upperBound)) + self.init(_uncheckedBounds: (lower: lowerBound, upper: upperBound)) } } diff --git a/stdlib/public/core/Range.swift b/stdlib/public/core/Range.swift index 81da1cdea823e..543cc4c9ea146 100644 --- a/stdlib/public/core/Range.swift +++ b/stdlib/public/core/Range.swift @@ -315,7 +315,7 @@ extension Range where Bound: Strideable, Bound.Stride: SignedInteger { /// require an upper bound of `Int.max + 1`, which is not representable as public init(_ other: ClosedRange) { let upperBound = other.upperBound.advanced(by: 1) - self.init(uncheckedBounds: (lower: other.lowerBound, upper: upperBound)) + self.init(_uncheckedBounds: (lower: other.lowerBound, upper: upperBound)) } } @@ -332,7 +332,7 @@ extension Range: RangeExpression { @inlinable // trivial-implementation public func relative(to collection: C) -> Range where C.Index == Bound { - return Range(uncheckedBounds: (lower: lowerBound, upper: upperBound)) + self } } @@ -366,7 +366,7 @@ extension Range { limits.upperBound < self.upperBound ? limits.upperBound : limits.lowerBound > self.upperBound ? limits.lowerBound : self.upperBound - return Range(uncheckedBounds: (lower: lower, upper: upper)) + return Range(_uncheckedBounds: (lower: lower, upper: upper)) } } @@ -442,7 +442,7 @@ extension Range: Decodable where Bound: Decodable { codingPath: decoder.codingPath, debugDescription: "Cannot initialize \(Range.self) with a lowerBound (\(lowerBound)) greater than upperBound (\(upperBound))")) } - self.init(uncheckedBounds: (lower: lowerBound, upper: upperBound)) + self.init(_uncheckedBounds: (lower: lowerBound, upper: upperBound)) } } diff --git a/stdlib/public/core/StringProtocol.swift b/stdlib/public/core/StringProtocol.swift index 3acd16db29bf7..9523b3db7f360 100644 --- a/stdlib/public/core/StringProtocol.swift +++ b/stdlib/public/core/StringProtocol.swift @@ -153,7 +153,7 @@ extension StringProtocol { let end = endIndex _internalInvariant( start.transcodedOffset == 0 && end.transcodedOffset == 0) - return Range(uncheckedBounds: (start._encodedOffset, end._encodedOffset)) + return Range(_uncheckedBounds: (start._encodedOffset, end._encodedOffset)) } } diff --git a/stdlib/public/core/StringStorageBridge.swift b/stdlib/public/core/StringStorageBridge.swift index 467b5c9d07f9d..083b6810c2f30 100644 --- a/stdlib/public/core/StringStorageBridge.swift +++ b/stdlib/public/core/StringStorageBridge.swift @@ -30,7 +30,7 @@ extension _AbstractStringStorage { "Range out of bounds") let range = Range( - uncheckedBounds: (aRange.location, aRange.location+aRange.length)) + _uncheckedBounds: (aRange.location, aRange.location+aRange.length)) let str = asString str._copyUTF16CodeUnits( into: UnsafeMutableBufferPointer(start: buffer, count: range.count), From 93368213cf09a2a95495cb4f75a35b7c11862ea0 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 4 Dec 2020 19:29:08 -0800 Subject: [PATCH 11/15] =?UTF-8?q?[stdlib]=20Don=E2=80=99t=20use=20unbound?= =?UTF-8?q?=20ranges=20(`foo[=E2=80=A6]`)=20in=20String=20=E2=86=92=20Subs?= =?UTF-8?q?tring=20conversions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The UnboundRange → Range conversion path is complicated and it involves at least one unnecessary _precondition check for startIndex ..< endIndex. --- stdlib/public/core/Substring.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/stdlib/public/core/Substring.swift b/stdlib/public/core/Substring.swift index 23d9fd85f1740..47469337f0be6 100644 --- a/stdlib/public/core/Substring.swift +++ b/stdlib/public/core/Substring.swift @@ -322,7 +322,8 @@ extension Substring: CustomDebugStringConvertible { extension Substring: LosslessStringConvertible { public init(_ content: String) { - self = content[...] + let range = Range(_uncheckedBounds: (content.startIndex, content.endIndex)) + self.init(Slice(base: content, bounds: range)) } } @@ -727,14 +728,14 @@ extension Substring: RangeReplaceableCollection { public init(_ elements: S) where S.Element == Character { if let str = elements as? String { - self = str[...] + self.init(str) return } if let subStr = elements as? Substring { self = subStr return } - self = String(elements)[...] + self.init(String(elements)) } @inlinable // specialize From 282a1408dea2916d9f3594b4cacec8f5c0101b94 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 8 Dec 2020 22:25:16 -0800 Subject: [PATCH 12/15] [stdlib] U[R]BP: Restore compile-time condition lost with removed precondition --- stdlib/public/core/UnsafeBufferPointer.swift.gyb | 2 +- stdlib/public/core/UnsafeRawBufferPointer.swift.gyb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/public/core/UnsafeBufferPointer.swift.gyb b/stdlib/public/core/UnsafeBufferPointer.swift.gyb index 8ec5f93567ced..6ce4f52569569 100644 --- a/stdlib/public/core/UnsafeBufferPointer.swift.gyb +++ b/stdlib/public/core/UnsafeBufferPointer.swift.gyb @@ -414,7 +414,7 @@ extension Unsafe${Mutable}BufferPointer { count == 0 || start != nil, "Unsafe${Mutable}BufferPointer has a nil start and nonzero count") _position = start - self.count = count + self.count = _assumeNonNegative(count) } @inlinable // unsafe-performance diff --git a/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb b/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb index 96c1a32390b41..1a9d71289b41c 100644 --- a/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb +++ b/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb @@ -436,7 +436,7 @@ extension Unsafe${Mutable}RawBufferPointer { _debugPrecondition(count == 0 || start != nil, "${Self} has a nil start and nonzero count") _position = start - _end = start.map { $0 + count } + _end = start.map { $0 + _assumeNonNegative(count) } } /// Creates a new buffer over the same memory as the given buffer. From 1f92df093cc34874b7aa8152c99f32bdb152783b Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 9 Dec 2020 19:31:28 -0800 Subject: [PATCH 13/15] [stdlib] Add an unsafe U[M]BP initializer to work around some inliner test failures --- stdlib/public/core/SmallString.swift | 2 +- stdlib/public/core/StringObject.swift | 2 +- stdlib/public/core/UnsafeBufferPointer.swift.gyb | 12 ++++++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/stdlib/public/core/SmallString.swift b/stdlib/public/core/SmallString.swift index 8ef19f5765228..f23619ea980ea 100644 --- a/stdlib/public/core/SmallString.swift +++ b/stdlib/public/core/SmallString.swift @@ -208,7 +208,7 @@ extension _SmallString { // Restore the memory type of self._storage _ = rawPtr.bindMemory(to: RawBitPattern.self, capacity: 1) } - return try f(UnsafeBufferPointer(start: ptr, count: count)) + return try f(UnsafeBufferPointer(_uncheckedStart: ptr, count: count)) } } diff --git a/stdlib/public/core/StringObject.swift b/stdlib/public/core/StringObject.swift index ebc2ce510be99..89fe1baf12b59 100644 --- a/stdlib/public/core/StringObject.swift +++ b/stdlib/public/core/StringObject.swift @@ -902,7 +902,7 @@ extension _StringObject { return sharedUTF8 } return UnsafeBufferPointer( - start: self.nativeUTF8Start, count: self.largeCount) + _uncheckedStart: self.nativeUTF8Start, count: self.largeCount) } // Whether the object stored can be bridged directly as a NSString diff --git a/stdlib/public/core/UnsafeBufferPointer.swift.gyb b/stdlib/public/core/UnsafeBufferPointer.swift.gyb index 6ce4f52569569..124fcf28bab8d 100644 --- a/stdlib/public/core/UnsafeBufferPointer.swift.gyb +++ b/stdlib/public/core/UnsafeBufferPointer.swift.gyb @@ -394,6 +394,15 @@ extension Unsafe${Mutable}BufferPointer: ${Mutable}Collection, RandomAccessColle } extension Unsafe${Mutable}BufferPointer { + @_alwaysEmitIntoClient + internal init( + @_nonEphemeral _uncheckedStart start: Unsafe${Mutable}Pointer?, + count: Int + ) { + _position = start + self.count = count + } + /// Creates a new buffer pointer over the specified number of contiguous /// instances beginning at the given pointer. /// @@ -413,8 +422,7 @@ extension Unsafe${Mutable}BufferPointer { _debugPrecondition( count == 0 || start != nil, "Unsafe${Mutable}BufferPointer has a nil start and nonzero count") - _position = start - self.count = _assumeNonNegative(count) + self.init(_uncheckedStart: start, count: _assumeNonNegative(count)) } @inlinable // unsafe-performance From 94a7eeebe55cbf2cbfd553b5de76a2eedaaae01d Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 11 Dec 2020 18:29:34 -0800 Subject: [PATCH 14/15] [stdlib] Document sources of code bloat --- stdlib/public/core/ClosedRange.swift | 2 ++ stdlib/public/core/Range.swift | 2 ++ stdlib/public/core/UnsafeBufferPointer.swift.gyb | 2 ++ 3 files changed, 6 insertions(+) diff --git a/stdlib/public/core/ClosedRange.swift b/stdlib/public/core/ClosedRange.swift index 133790ca0c05d..7841db6c41f61 100644 --- a/stdlib/public/core/ClosedRange.swift +++ b/stdlib/public/core/ClosedRange.swift @@ -68,6 +68,8 @@ public struct ClosedRange { /// The range's upper bound. public let upperBound: Bound + // This works around _debugPrecondition() impacting the performance of + // optimized code. (rdar://72246338) @_alwaysEmitIntoClient @inline(__always) internal init(_uncheckedBounds bounds: (lower: Bound, upper: Bound)) { self.lowerBound = bounds.lower diff --git a/stdlib/public/core/Range.swift b/stdlib/public/core/Range.swift index 543cc4c9ea146..24fba64e9c245 100644 --- a/stdlib/public/core/Range.swift +++ b/stdlib/public/core/Range.swift @@ -157,6 +157,8 @@ public struct Range { /// instance does not contain its upper bound. public let upperBound: Bound + // This works around _debugPrecondition() impacting the performance of + // optimized code. (rdar://72246338) @_alwaysEmitIntoClient @inline(__always) internal init(_uncheckedBounds bounds: (lower: Bound, upper: Bound)) { self.lowerBound = bounds.lower diff --git a/stdlib/public/core/UnsafeBufferPointer.swift.gyb b/stdlib/public/core/UnsafeBufferPointer.swift.gyb index 124fcf28bab8d..33ceb5b769a8a 100644 --- a/stdlib/public/core/UnsafeBufferPointer.swift.gyb +++ b/stdlib/public/core/UnsafeBufferPointer.swift.gyb @@ -394,6 +394,8 @@ extension Unsafe${Mutable}BufferPointer: ${Mutable}Collection, RandomAccessColle } extension Unsafe${Mutable}BufferPointer { + // This works around _debugPrecondition() impacting the performance of + // optimized code. (rdar://72246338) @_alwaysEmitIntoClient internal init( @_nonEphemeral _uncheckedStart start: Unsafe${Mutable}Pointer?, From 8cc91b28d3cf8683e57e929922fed38025af5ecf Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 11 Dec 2020 18:32:21 -0800 Subject: [PATCH 15/15] [stdlib] Reintroduce a precondition for count >= 0 --- stdlib/public/core/UnsafeBufferPointer.swift.gyb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/public/core/UnsafeBufferPointer.swift.gyb b/stdlib/public/core/UnsafeBufferPointer.swift.gyb index 33ceb5b769a8a..dedc8ea05fc57 100644 --- a/stdlib/public/core/UnsafeBufferPointer.swift.gyb +++ b/stdlib/public/core/UnsafeBufferPointer.swift.gyb @@ -419,12 +419,12 @@ extension Unsafe${Mutable}BufferPointer { public init( @_nonEphemeral start: Unsafe${Mutable}Pointer?, count: Int ) { - _debugPrecondition( + _precondition( count >= 0, "Unsafe${Mutable}BufferPointer with negative count") _debugPrecondition( count == 0 || start != nil, "Unsafe${Mutable}BufferPointer has a nil start and nonzero count") - self.init(_uncheckedStart: start, count: _assumeNonNegative(count)) + self.init(_uncheckedStart: start, count: count) } @inlinable // unsafe-performance