diff --git a/stdlib/private/StdlibCollectionUnittest/CheckMutableCollectionType.swift b/stdlib/private/StdlibCollectionUnittest/CheckMutableCollectionType.swift index 3ff7fc32fb035..891cf8090bdf6 100644 --- a/stdlib/private/StdlibCollectionUnittest/CheckMutableCollectionType.swift +++ b/stdlib/private/StdlibCollectionUnittest/CheckMutableCollectionType.swift @@ -438,13 +438,13 @@ if resiliencyChecks.subscriptRangeOnOutOfBoundsRangesBehavior != .none { } //===----------------------------------------------------------------------===// -// _withUnsafeMutableBufferPointerIfSupported() +// withContiguousMutableStorageIfAvailable() //===----------------------------------------------------------------------===// -self.test("\(testNamePrefix)._withUnsafeMutableBufferPointerIfSupported()/semantics") { +self.test("\(testNamePrefix).withContiguousMutableStorageIfAvailable()/semantics") { for test in subscriptRangeTests { var c = makeWrappedCollection(test.collection) - var result = c._withUnsafeMutableBufferPointerIfSupported { + var result = c.withContiguousMutableStorageIfAvailable { (bufferPointer) -> OpaqueValue>> in let value = OpaqueValue(bufferPointer.map(extractValue)) return value diff --git a/stdlib/private/StdlibCollectionUnittest/LoggingWrappers.swift b/stdlib/private/StdlibCollectionUnittest/LoggingWrappers.swift index 244a7aff01498..e8946fe26fdae 100644 --- a/stdlib/private/StdlibCollectionUnittest/LoggingWrappers.swift +++ b/stdlib/private/StdlibCollectionUnittest/LoggingWrappers.swift @@ -83,6 +83,7 @@ public class SequenceLog { public static var prefixWhile = TypeIndexed(0) public static var prefixMaxLength = TypeIndexed(0) public static var suffixMaxLength = TypeIndexed(0) + public static var withContiguousStorageIfAvailable = TypeIndexed(0) public static var _customContainsEquatableElement = TypeIndexed(0) public static var _copyToContiguousArray = TypeIndexed(0) public static var _copyContents = TypeIndexed(0) @@ -112,6 +113,9 @@ public class SequenceLog { public static var _withUnsafeMutableBufferPointerIfSupported = TypeIndexed(0) public static var _withUnsafeMutableBufferPointerIfSupportedNonNilReturns = TypeIndexed(0) + public static var withContiguousMutableStorageIfAvailable = TypeIndexed(0) + public static var withContiguousMutableStorageIfAvailableNonNilReturns = + TypeIndexed(0) // RangeReplaceableCollection public static var init_ = TypeIndexed(0) public static var initRepeating = TypeIndexed(0) @@ -210,6 +214,13 @@ extension LoggingSequence: Sequence { SequenceLog.underestimatedCount[selfType] += 1 return base.underestimatedCount } + + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + SequenceLog.withContiguousStorageIfAvailable[selfType] += 1 + return try base.withContiguousStorageIfAvailable(body) + } public func _customContainsEquatableElement(_ element: Element) -> Bool? { SequenceLog._customContainsEquatableElement[selfType] += 1 @@ -385,6 +396,17 @@ extension LoggingMutableCollection: MutableCollection { return result } + public mutating func withContiguousMutableStorageIfAvailable( + _ body: (inout UnsafeMutableBufferPointer) throws -> R + ) rethrows -> R? { + MutableCollectionLog.withContiguousMutableStorageIfAvailable[selfType] += 1 + let result = try base.withContiguousMutableStorageIfAvailable(body) + if result != nil { + Log.withContiguousMutableStorageIfAvailable[selfType] += 1 + } + return result + } + } public typealias LoggingMutableBidirectionalCollection< @@ -501,10 +523,10 @@ public typealias LoggingRangeReplaceableRandomAccessCollection< > = LoggingRangeReplaceableCollection //===----------------------------------------------------------------------===// -// Collections that count calls to `_withUnsafeMutableBufferPointerIfSupported` +// Collections that count calls to `withContiguousMutableStorageIfAvailable` //===----------------------------------------------------------------------===// -/// Interposes between `_withUnsafeMutableBufferPointerIfSupported` method calls +/// Interposes between `withContiguousMutableStorageIfAvailable` method calls /// to increment a counter. Calls to this method from within dispatched methods /// are uncounted by the standard logging collection wrapper. public struct BufferAccessLoggingMutableCollection< @@ -587,6 +609,17 @@ extension BufferAccessLoggingMutableCollection: MutableCollection { } return result } + + public mutating func withContiguousMutableStorageIfAvailable( + _ body: (inout UnsafeMutableBufferPointer) throws -> R + ) rethrows -> R? { + Log.withContiguousMutableStorageIfAvailable[selfType] += 1 + let result = try base.withContiguousMutableStorageIfAvailable(body) + if result != nil { + Log.withContiguousMutableStorageIfAvailable[selfType] += 1 + } + return result + } } public typealias BufferAccessLoggingMutableBidirectionalCollection< diff --git a/stdlib/public/core/Array.swift b/stdlib/public/core/Array.swift index 311b8001250b3..eda6e1f7f083e 100644 --- a/stdlib/public/core/Array.swift +++ b/stdlib/public/core/Array.swift @@ -1281,6 +1281,26 @@ extension Array: RangeReplaceableCollection { } } + @inlinable + public mutating func withContiguousMutableStorageIfAvailable( + _ body: (inout UnsafeMutableBufferPointer) throws -> R + ) rethrows -> R? { + return try withUnsafeMutableBufferPointer { + (bufferPointer) -> R in + return try body(&bufferPointer) + } + } + + @inlinable + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + return try withUnsafeBufferPointer { + (bufferPointer) -> R in + return try body(bufferPointer) + } + } + @inlinable public __consuming func _copyToContiguousArray() -> ContiguousArray { if let n = _buffer.requestNativeBuffer() { diff --git a/stdlib/public/core/ArraySlice.swift b/stdlib/public/core/ArraySlice.swift index 9ed58d5572980..cff2d7781994a 100644 --- a/stdlib/public/core/ArraySlice.swift +++ b/stdlib/public/core/ArraySlice.swift @@ -1075,6 +1075,26 @@ extension ArraySlice: RangeReplaceableCollection { } } + @inlinable + public mutating func withContiguousMutableStorageIfAvailable( + _ body: (inout UnsafeMutableBufferPointer) throws -> R + ) rethrows -> R? { + return try withUnsafeMutableBufferPointer { + (bufferPointer) -> R in + return try body(&bufferPointer) + } + } + + @inlinable + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + return try withUnsafeBufferPointer { + (bufferPointer) -> R in + return try body(bufferPointer) + } + } + @inlinable public __consuming func _copyToContiguousArray() -> ContiguousArray { if let n = _buffer.requestNativeBuffer() { diff --git a/stdlib/public/core/ContiguousArray.swift b/stdlib/public/core/ContiguousArray.swift index 0ce2f3dcc181d..ff3487759d4d3 100644 --- a/stdlib/public/core/ContiguousArray.swift +++ b/stdlib/public/core/ContiguousArray.swift @@ -923,6 +923,26 @@ extension ContiguousArray: RangeReplaceableCollection { } } + @inlinable + public mutating func withContiguousMutableStorageIfAvailable( + _ body: (inout UnsafeMutableBufferPointer) throws -> R + ) rethrows -> R? { + return try withUnsafeMutableBufferPointer { + (bufferPointer) -> R in + return try body(&bufferPointer) + } + } + + @inlinable + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + return try withUnsafeBufferPointer { + (bufferPointer) -> R in + return try body(bufferPointer) + } + } + @inlinable public __consuming func _copyToContiguousArray() -> ContiguousArray { if let n = _buffer.requestNativeBuffer() { diff --git a/stdlib/public/core/MutableCollection.swift b/stdlib/public/core/MutableCollection.swift index d66b4d108cba9..3fdd4e6cdc85a 100644 --- a/stdlib/public/core/MutableCollection.swift +++ b/stdlib/public/core/MutableCollection.swift @@ -180,6 +180,20 @@ where SubSequence: MutableCollection mutating func _withUnsafeMutableBufferPointerIfSupported( _ body: (inout UnsafeMutableBufferPointer) throws -> R ) rethrows -> R? + + /// Call `body(p)`, where `p` is a pointer to the collection's + /// mutable contiguous storage. If no such storage exists, it is + /// first created. If the collection does not support an internal + /// representation in a form of mutable contiguous storage, `body` is not + /// called and `nil` is returned. + /// + /// Often, the optimizer can eliminate bounds- and uniqueness-checks + /// within an algorithm, but when that fails, invoking the + /// same algorithm on `body`\ 's argument lets you trade safety for + /// speed. + mutating func withContiguousMutableStorageIfAvailable( + _ body: (inout UnsafeMutableBufferPointer) throws -> R + ) rethrows -> R? } // TODO: swift-3-indexing-model - review the following @@ -191,6 +205,13 @@ extension MutableCollection { return nil } + @inlinable + public mutating func withContiguousMutableStorageIfAvailable( + _ body: (inout UnsafeMutableBufferPointer) throws -> R + ) rethrows -> R? { + return nil + } + /// Accesses a contiguous subrange of the collection's elements. /// /// The accessed slice uses the same indices for the same elements as the diff --git a/stdlib/public/core/Sequence.swift b/stdlib/public/core/Sequence.swift index 2aa8777e17832..f4d9125370d48 100644 --- a/stdlib/public/core/Sequence.swift +++ b/stdlib/public/core/Sequence.swift @@ -362,6 +362,10 @@ public protocol Sequence { __consuming func _copyContents( initializing ptr: UnsafeMutableBufferPointer ) -> (Iterator,UnsafeMutableBufferPointer.Index) + + func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? } // Provides a default associated type witness for Iterator when the @@ -1092,17 +1096,24 @@ extension Sequence { public __consuming func _copyContents( initializing buffer: UnsafeMutableBufferPointer ) -> (Iterator,UnsafeMutableBufferPointer.Index) { - var it = self.makeIterator() - guard var ptr = buffer.baseAddress else { return (it,buffer.startIndex) } - for idx in buffer.startIndex..( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + return nil + } } // FIXME(ABI)#182 diff --git a/stdlib/public/core/UnsafeBufferPointer.swift.gyb b/stdlib/public/core/UnsafeBufferPointer.swift.gyb index 4f5c5e7e7381e..6b882ad1f7311 100644 --- a/stdlib/public/core/UnsafeBufferPointer.swift.gyb +++ b/stdlib/public/core/UnsafeBufferPointer.swift.gyb @@ -432,6 +432,25 @@ extension Unsafe${Mutable}BufferPointer { return try body(&self) } + @inlinable + public mutating func withContiguousMutableStorageIfAvailable( + _ body: (inout UnsafeMutableBufferPointer) throws -> R + ) rethrows -> R? { + let (oldBase, oldCount) = (self.baseAddress, self.count) + defer { + _debugPrecondition((oldBase, oldCount) == (self.baseAddress, self.count), + "UnsafeMutableBufferPointer.withUnsafeMutableBufferPointer: replacing the buffer is not allowed") + } + return try body(&self) + } + + @inlinable + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + return try body(UnsafeBufferPointer(self)) + } + % else: /// Creates an immutable typed buffer pointer referencing the same memory as the @@ -444,6 +463,13 @@ extension Unsafe${Mutable}BufferPointer { count = other.count } + @inlinable + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + return try body(self) + } + % end % if not Mutable: diff --git a/test/api-digester/Outputs/stability-stdlib-abi.swift.expected b/test/api-digester/Outputs/stability-stdlib-abi.swift.expected index 2ad47b5f03a43..991dd3da98814 100644 --- a/test/api-digester/Outputs/stability-stdlib-abi.swift.expected +++ b/test/api-digester/Outputs/stability-stdlib-abi.swift.expected @@ -541,3 +541,6 @@ Class _StringStorage has changed its super class from _AbstractStringStorage to Constructor _AbstractStringStorage.init() has been removed Func _AbstractStringStorage.copy(with:) has been removed Func _AbstractStringStorage.getOrComputeBreadcrumbs() has been removed + +Func MutableCollection.withContiguousMutableStorageIfAvailable(_:) has been added as a protocol requirement +Func Sequence.withContiguousStorageIfAvailable(_:) has been added as a protocol requirement diff --git a/test/api-digester/Outputs/stability-stdlib-source.swift.expected b/test/api-digester/Outputs/stability-stdlib-source.swift.expected index 5a618cfdbe0c5..96faf028889a2 100644 --- a/test/api-digester/Outputs/stability-stdlib-source.swift.expected +++ b/test/api-digester/Outputs/stability-stdlib-source.swift.expected @@ -213,3 +213,6 @@ Func LazyCollectionProtocol.reversed() has been removed Var LazyCollectionProtocol.lazy has declared type change from LazyCollection to LazySequence Func Sequence.reduce(into:_:) has parameter 0 changing from Default to Owned + +Func MutableCollection.withContiguousMutableStorageIfAvailable(_:) has been added as a protocol requirement +Func Sequence.withContiguousStorageIfAvailable(_:) has been added as a protocol requirement diff --git a/test/stdlib/Inputs/CommonArrayTests.gyb b/test/stdlib/Inputs/CommonArrayTests.gyb index ee8f2bf2550ee..cf4f099f175f7 100644 --- a/test/stdlib/Inputs/CommonArrayTests.gyb +++ b/test/stdlib/Inputs/CommonArrayTests.gyb @@ -324,6 +324,128 @@ ${Suite}.test("${ArrayType}/_withUnsafeMutableBufferPointerIfSupported/Replacing } } +//===----------------------------------------------------------------------===// +// withContiguousStorageIfAvailableTests() +//===----------------------------------------------------------------------===// + +struct WithContiguousStorageIfAvailableTest { + let sequence: [Int] + let loc: SourceLoc + + init( + _ sequence: [Int], + file: String = #file, line: UInt = #line + ) { + self.sequence = sequence + self.loc = SourceLoc(file, line, comment: "test data") + } +} + +let withContiguousStorageIfAvailableTests = [ + WithContiguousStorageIfAvailableTest([]), + WithContiguousStorageIfAvailableTest([ 10 ]), + WithContiguousStorageIfAvailableTest([ 10, 20, 30, 40, 50 ]), +] + +${Suite}.test("${ArrayType}/withContiguousStorageIfAvailable") + .code { + for test in withContiguousStorageIfAvailableTests { + var a = getFresh${ArrayType}(test.sequence.map(OpaqueValue.init)) + do { + // Read. + var result = a.withContiguousStorageIfAvailable { + (bufferPointer) -> OpaqueValue<[OpaqueValue]> in + return OpaqueValue(Array(bufferPointer)) + } + expectType(Optional>>>.self, &result) + expectEqualSequence(test.sequence, result!.value.map { $0.value }) + expectEqualSequence(test.sequence, a.map { $0.value }) + } + } + // FIXME: tests for arrays bridged from Objective-C. +} + +//===----------------------------------------------------------------------===// +// withContiguousMutableStorageIfAvailableTests() +//===----------------------------------------------------------------------===// + +struct WithContiguousMutableStorageIfAvailableTest { + let sequence: [Int] + let loc: SourceLoc + + init( + _ sequence: [Int], + file: String = #file, line: UInt = #line + ) { + self.sequence = sequence + self.loc = SourceLoc(file, line, comment: "test data") + } +} + +let withContiguousMutableStorageIfAvailableTests = [ + WithContiguousMutableStorageIfAvailableTest([]), + WithContiguousMutableStorageIfAvailableTest([ 10 ]), + WithContiguousMutableStorageIfAvailableTest([ 10, 20, 30, 40, 50 ]), +] + +${Suite}.test("${ArrayType}/withContiguousMutableStorageIfAvailable") + .code { + for test in withContiguousMutableStorageIfAvailableTests { + var a = getFresh${ArrayType}(test.sequence.map(OpaqueValue.init)) + do { + // Read. + var result = a.withContiguousMutableStorageIfAvailable { + (bufferPointer) -> OpaqueValue<[OpaqueValue]> in + return OpaqueValue(Array(bufferPointer)) + } + expectType(Optional>>>.self, &result) + expectEqualSequence(test.sequence, result!.value.map { $0.value }) + expectEqualSequence(test.sequence, a.map { $0.value }) + } + do { + // Read and write. + var result = a.withContiguousMutableStorageIfAvailable { + (bufferPointer) -> OpaqueValue>> in + let result = OpaqueValue(Array(bufferPointer)) + for i in bufferPointer.indices { + bufferPointer[i] = OpaqueValue(bufferPointer[i].value * 10) + } + return result + } + expectType(Optional>>>.self, &result) + expectEqualSequence(test.sequence, result!.value.map { $0.value }) + expectEqualSequence( + test.sequence.map { $0 * 10 }, + a.map { $0.value }) + } + } + // FIXME: tests for arrays bridged from Objective-C. +} + +${Suite}.test("${ArrayType}/_withUnsafeMutableBufferPointerIfSupported/ReplacingTheBufferTraps/1") + .code { + var a = getFresh${ArrayType}([ OpaqueValue(10) ]) + var result = a._withUnsafeMutableBufferPointerIfSupported { + (bufferPointer) -> OpaqueValue in + // buffer = UnsafeMutableBufferPointer(start: buffer.baseAddress, count: 0) + // FIXME: does not trap since the buffer is not passed inout. + // expectCrashLater() + return OpaqueValue(42) + } +} + +${Suite}.test("${ArrayType}/_withUnsafeMutableBufferPointerIfSupported/ReplacingTheBufferTraps/2") + .code { + var a = getFresh${ArrayType}([ OpaqueValue(10) ]) + var result = a._withUnsafeMutableBufferPointerIfSupported { + (bufferPointer) -> OpaqueValue in + // buffer = UnsafeMutableBufferPointer(start: nil, count: 1) + // FIXME: does not trap since the buffer is not passed inout. + // expectCrashLater() + return OpaqueValue(42) + } +} + //===----------------------------------------------------------------------===// // withUnsafeMutableBytes //===----------------------------------------------------------------------===// diff --git a/validation-test/stdlib/CollectionType.swift.gyb b/validation-test/stdlib/CollectionType.swift.gyb index f8d00998869c3..2bfa70a0cd8ad 100644 --- a/validation-test/stdlib/CollectionType.swift.gyb +++ b/validation-test/stdlib/CollectionType.swift.gyb @@ -693,6 +693,16 @@ CollectionTypeTests.test("_withUnsafeMutableBufferPointerIfSupported/dispatch") expectCustomizable(tester, tester.log._withUnsafeMutableBufferPointerIfSupported) } +//===----------------------------------------------------------------------===// +// withContiguousMutableStorageIfAvailable() +//===----------------------------------------------------------------------===// + +CollectionTypeTests.test("withContiguousMutableStorageIfAvailable/dispatch") { + var tester = MutableCollectionLog.dispatchTester([OpaqueValue(1)]) + _ = tester.withContiguousMutableStorageIfAvailable { _ in () } + expectCustomizable(tester, tester.log.withContiguousMutableStorageIfAvailable) +} + //===----------------------------------------------------------------------===// // Associated Type Inference diff --git a/validation-test/stdlib/SequenceType.swift.gyb b/validation-test/stdlib/SequenceType.swift.gyb index 1cdc855927348..f4402c6aa5dda 100644 --- a/validation-test/stdlib/SequenceType.swift.gyb +++ b/validation-test/stdlib/SequenceType.swift.gyb @@ -1049,6 +1049,20 @@ SequenceTypeTests.test("underestimatedCount/Sequence/CustomImplementation") { } } +//===----------------------------------------------------------------------===// +// withContiguousStorageIfAvailable() +//===----------------------------------------------------------------------===// +SequenceTypeTests.test("withContiguousMutableStorageIfAvailable") +.code { + let s = [ 1, 2, 3 ].map(OpaqueValue.init) + let tester = SequenceLog.dispatchTester(s) + let result = tester.withContiguousStorageIfAvailable { buf in + buf.reduce(0) { $0 + $1.value } + } + expectEqual(6, result) + expectCustomizable(tester, tester.log.withContiguousStorageIfAvailable) +} + //===----------------------------------------------------------------------===// // _copyToContiguousArray() //===----------------------------------------------------------------------===// diff --git a/validation-test/stdlib/UnsafeBufferPointer.swift.gyb b/validation-test/stdlib/UnsafeBufferPointer.swift.gyb index ddf8783509191..9c1982f6eb650 100644 --- a/validation-test/stdlib/UnsafeBufferPointer.swift.gyb +++ b/validation-test/stdlib/UnsafeBufferPointer.swift.gyb @@ -325,17 +325,27 @@ UnsafeMutableBufferPointerTestSuite.test("changeElementViaBuffer") { expectEqual(-1.0, allocated[count-1]) } -UnsafeMutableBufferPointerTestSuite.test("_withUnsafeMutableBufferPointerIfSupported") { +UnsafeMutableBufferPointerTestSuite.test("withContiguous(Mutable)StorageIfAvailable") { let count = 4 let allocated = UnsafeMutablePointer.allocate(capacity: count) defer { allocated.deallocate() } allocated.initialize(repeating: 1, count: count) - var buffer = UnsafeMutableBufferPointer(start: allocated, count: count) - let result = buffer._withUnsafeMutableBufferPointerIfSupported { buffer in - return buffer.reduce(0, +) + var mutableBuffer = UnsafeMutableBufferPointer(start: allocated, count: count) + let executed: ()? = mutableBuffer.withContiguousMutableStorageIfAvailable { buf in + for i in 0..