From 659cf1dcdfdd712cc9e6a23779adb0442513a4a2 Mon Sep 17 00:00:00 2001 From: Nate Cook Date: Sun, 3 Mar 2019 15:13:18 -0600 Subject: [PATCH 1/6] [stdlib] Make unsafe array initializer public The original underscored version of this initializer remains, with a deprecation notice. Also add the initializer to ContiguousArray. --- stdlib/public/core/Array.swift | 28 +++++++++---- stdlib/public/core/ContiguousArray.swift | 50 ++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/stdlib/public/core/Array.swift b/stdlib/public/core/Array.swift index edf6b3f85a854..ed9907631c67e 100644 --- a/stdlib/public/core/Array.swift +++ b/stdlib/public/core/Array.swift @@ -1366,6 +1366,19 @@ extension Array { } extension Array { + @available(swift, deprecated: 5.1, renamed: "init(unsafeUninitializedCapacity:initializingWith:)") + @inlinable + public init( + _unsafeUninitializedCapacity: Int, + initializingWith initializer: ( + _ buffer: inout UnsafeMutableBufferPointer, + _ initializedCount: inout Int) throws -> Void + ) rethrows { + try self.init( + unsafeUninitializedCapacity: _unsafeUninitializedCapacity, + initializingWith: initializer) + } + /// Creates an array with the specified capacity, then calls the given /// closure with a buffer covering the array's uninitialized memory. /// @@ -1373,14 +1386,15 @@ extension Array { /// elements that are initialized by the closure. The memory in the range /// `buffer[0.., _ initializedCount: inout Int) throws -> Void ) rethrows { var firstElementAddress: UnsafeMutablePointer (self, firstElementAddress) = - Array._allocateUninitialized(_unsafeUninitializedCapacity) + Array._allocateUninitialized(unsafeUninitializedCapacity) var initializedCount = 0 defer { // Update self.count even if initializer throws an error. _precondition( - initializedCount <= _unsafeUninitializedCapacity, + initializedCount <= unsafeUninitializedCapacity, "Initialized count set to greater than specified capacity." ) self._buffer.count = initializedCount } var buffer = UnsafeMutableBufferPointer( - start: firstElementAddress, count: _unsafeUninitializedCapacity) + start: firstElementAddress, count: unsafeUninitializedCapacity) try initializer(&buffer, &initializedCount) } - + /// Calls a closure with a pointer to the array's contiguous storage. /// /// Often, the optimizer can eliminate bounds checks within an array diff --git a/stdlib/public/core/ContiguousArray.swift b/stdlib/public/core/ContiguousArray.swift index 79dbcd81f2f35..762d4f277c5f5 100644 --- a/stdlib/public/core/ContiguousArray.swift +++ b/stdlib/public/core/ContiguousArray.swift @@ -985,6 +985,56 @@ extension ContiguousArray { } extension ContiguousArray { + /// Creates an array with the specified capacity, then calls the given + /// closure with a buffer covering the array's uninitialized memory. + /// + /// Inside the closure, set the `initializedCount` parameter to the number of + /// elements that are initialized by the closure. The memory in the range + /// `buffer[0.., + _ initializedCount: inout Int) throws -> Void + ) rethrows { + var firstElementAddress: UnsafeMutablePointer + (self, firstElementAddress) = + ContiguousArray._allocateUninitialized(unsafeUninitializedCapacity) + + var initializedCount = 0 + defer { + // Update self.count even if initializer throws an error. + _precondition( + initializedCount <= unsafeUninitializedCapacity, + "Initialized count set to greater than specified capacity." + ) + self._buffer.count = initializedCount + } + var buffer = UnsafeMutableBufferPointer( + start: firstElementAddress, count: unsafeUninitializedCapacity) + try initializer(&buffer, &initializedCount) + } + /// Calls a closure with a pointer to the array's contiguous storage. /// /// Often, the optimizer can eliminate bounds checks within an array From 68e8d7829a2d196f8a0810be8f4bf5e011ed1ede Mon Sep 17 00:00:00 2001 From: Nate Cook Date: Mon, 18 Mar 2019 11:55:55 -0500 Subject: [PATCH 2/6] Update uninitialized array tests. --- test/stdlib/Inputs/CommonArrayTests.gyb | 39 ++++++++++++------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/test/stdlib/Inputs/CommonArrayTests.gyb b/test/stdlib/Inputs/CommonArrayTests.gyb index cf4f099f175f7..d5648ecc89bbd 100644 --- a/test/stdlib/Inputs/CommonArrayTests.gyb +++ b/test/stdlib/Inputs/CommonArrayTests.gyb @@ -462,10 +462,6 @@ ${Suite}.test("${ArrayType}/withUnsafeMutableBytes") expectEqual(10, b[0]) } -// FIXME: Implement these changes for ArraySlice and ContiguousArray - -%if ArrayType == 'Array': - //===----------------------------------------------------------------------===// // reserveCapacity semantics //===----------------------------------------------------------------------===// @@ -478,28 +474,31 @@ ${Suite}.test("${ArrayType}/reserveCapacity") { } } +%if ArrayType in ['Array', 'ContiguousArray']: + //===----------------------------------------------------------------------===// -// init(_unsafeUninitializedCapacity:initializingWith:) +// init(unsafeUninitializedCapacity:initializingWith:) //===----------------------------------------------------------------------===// extension Collection { func stablyPartitioned( by belongsInFirstPartition: (Element) -> Bool ) -> ${ArrayType} { - let result = ${ArrayType}(_unsafeUninitializedCapacity: self.count) { + let result = ${ArrayType}(unsafeUninitializedCapacity: self.count) { buffer, initializedCount in - var lowIndex = 0 - var highIndex = buffer.count + var low = buffer.baseAddress! + var high = low + buffer.count for element in self { if belongsInFirstPartition(element) { - buffer[lowIndex] = element - lowIndex += 1 + low.initialize(to: element) + low += 1 } else { - highIndex -= 1 - buffer[highIndex] = element + high -= 1 + high.initialize(to: element) } } - + + let highIndex = high - buffer.baseAddress! buffer[highIndex...].reverse() initializedCount = buffer.count } @@ -507,7 +506,7 @@ extension Collection { } } -${Suite}.test("${ArrayType}/init(_unsafeUninitializedCapacity:...:)") { +${Suite}.test("${ArrayType}/init(unsafeUninitializedCapacity:...:)") { var a = ${ArrayType}(0..<300) let p = a.stablyPartitioned(by: { $0 % 2 == 0 }) expectEqualSequence( @@ -516,7 +515,7 @@ ${Suite}.test("${ArrayType}/init(_unsafeUninitializedCapacity:...:)") { ) } -${Suite}.test("${ArrayType}/init(_unsafeUninitializedCapacity:...:)/throwing") { +${Suite}.test("${ArrayType}/init(unsafeUninitializedCapacity:...:)/throwing") { final class InstanceCountedClass { static var instanceCounter = 0 @@ -526,7 +525,8 @@ ${Suite}.test("${ArrayType}/init(_unsafeUninitializedCapacity:...:)/throwing") { enum E: Error { case error } do { - var a = Array(_unsafeUninitializedCapacity: 10) { buffer, c in + var a = ${ArrayType}(unsafeUninitializedCapacity: 10) { + buffer, c in let p = buffer.baseAddress! for i in 0..<5 { (p + i).initialize(to: InstanceCountedClass()) @@ -539,7 +539,7 @@ ${Suite}.test("${ArrayType}/init(_unsafeUninitializedCapacity:...:)/throwing") { a = [] expectEqual(0, InstanceCountedClass.instanceCounter) - a = try Array(_unsafeUninitializedCapacity: 10) { buffer, c in + a = try ${ArrayType}(unsafeUninitializedCapacity: 10) { buffer, c in let p = buffer.baseAddress! for i in 0..<5 { (p + i).initialize(to: InstanceCountedClass()) @@ -548,10 +548,7 @@ ${Suite}.test("${ArrayType}/init(_unsafeUninitializedCapacity:...:)/throwing") { throw E.error } - // The throw above should prevent reaching here, which should mean the - // instances created in the closure should get deallocated before the final - // expectation outside the do/catch block. - expectTrue(false) + expectUnreachable() } catch {} expectEqual(0, InstanceCountedClass.instanceCounter) } From b9620481a7957158a52a65daa1835d1855a7f58a Mon Sep 17 00:00:00 2001 From: Nate Cook Date: Thu, 21 Mar 2019 11:15:07 -0500 Subject: [PATCH 3/6] Switch uninitialized Array inits to call original implementation This strategy lets us avoid availability constraints on the new public methods, since the original underscored version is part of the permanent ABI. --- stdlib/public/core/Array.swift | 49 +++++++++++++----------- stdlib/public/core/ContiguousArray.swift | 19 ++------- 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/stdlib/public/core/Array.swift b/stdlib/public/core/Array.swift index ed9907631c67e..55af7249b6b32 100644 --- a/stdlib/public/core/Array.swift +++ b/stdlib/public/core/Array.swift @@ -1366,17 +1366,35 @@ extension Array { } extension Array { - @available(swift, deprecated: 5.1, renamed: "init(unsafeUninitializedCapacity:initializingWith:)") + /// Implementation for Array(unsafeUninitializedCapacity:initializingWith:) + /// and ContiguousArray(unsafeUninitializedCapacity:initializingWith:) @inlinable - public init( + internal init( _unsafeUninitializedCapacity: Int, initializingWith initializer: ( _ buffer: inout UnsafeMutableBufferPointer, _ initializedCount: inout Int) throws -> Void ) rethrows { - try self.init( - unsafeUninitializedCapacity: _unsafeUninitializedCapacity, - initializingWith: initializer) + var firstElementAddress: UnsafeMutablePointer + (self, firstElementAddress) = + Array._allocateUninitialized(_unsafeUninitializedCapacity) + + var initializedCount = 0 + var buffer = UnsafeMutableBufferPointer( + start: firstElementAddress, count: _unsafeUninitializedCapacity) + defer { + // Update self.count even if initializer throws an error. + _precondition( + initializedCount <= _unsafeUninitializedCapacity, + "Initialized count set to greater than specified capacity." + ) + _precondition( + buffer.baseAddress == firstElementAddress, + "Can't reassign buffer in Array(unsafeUninitializedCapacity:initializingWith:)" + ) + self._buffer.count = initializedCount + } + try initializer(&buffer, &initializedCount) } /// Creates an array with the specified capacity, then calls the given @@ -1404,29 +1422,16 @@ extension Array { /// - initializedCount: The count of initialized elements in the array, /// which begins as zero. Set `initializedCount` to the number of /// elements you initialize. - @inlinable + @_alwaysEmitIntoClient @inlinable public init( unsafeUninitializedCapacity: Int, initializingWith initializer: ( _ buffer: inout UnsafeMutableBufferPointer, _ initializedCount: inout Int) throws -> Void ) rethrows { - var firstElementAddress: UnsafeMutablePointer - (self, firstElementAddress) = - Array._allocateUninitialized(unsafeUninitializedCapacity) - - var initializedCount = 0 - defer { - // Update self.count even if initializer throws an error. - _precondition( - initializedCount <= unsafeUninitializedCapacity, - "Initialized count set to greater than specified capacity." - ) - self._buffer.count = initializedCount - } - var buffer = UnsafeMutableBufferPointer( - start: firstElementAddress, count: unsafeUninitializedCapacity) - try initializer(&buffer, &initializedCount) + self = try Array( + _unsafeUninitializedCapacity: unsafeUninitializedCapacity, + initializingWith: initializer) } /// Calls a closure with a pointer to the array's contiguous storage. diff --git a/stdlib/public/core/ContiguousArray.swift b/stdlib/public/core/ContiguousArray.swift index 762d4f277c5f5..b7a31978f200f 100644 --- a/stdlib/public/core/ContiguousArray.swift +++ b/stdlib/public/core/ContiguousArray.swift @@ -1017,22 +1017,9 @@ extension ContiguousArray { _ buffer: inout UnsafeMutableBufferPointer, _ initializedCount: inout Int) throws -> Void ) rethrows { - var firstElementAddress: UnsafeMutablePointer - (self, firstElementAddress) = - ContiguousArray._allocateUninitialized(unsafeUninitializedCapacity) - - var initializedCount = 0 - defer { - // Update self.count even if initializer throws an error. - _precondition( - initializedCount <= unsafeUninitializedCapacity, - "Initialized count set to greater than specified capacity." - ) - self._buffer.count = initializedCount - } - var buffer = UnsafeMutableBufferPointer( - start: firstElementAddress, count: unsafeUninitializedCapacity) - try initializer(&buffer, &initializedCount) + self = try ContiguousArray(Array( + _unsafeUninitializedCapacity: unsafeUninitializedCapacity, + initializingWith: initializer)) } /// Calls a closure with a pointer to the array's contiguous storage. From e37f7d5c39089b63e4f2a7cb2b93a9efec6d8bcc Mon Sep 17 00:00:00 2001 From: Nate Cook Date: Thu, 21 Mar 2019 11:16:27 -0500 Subject: [PATCH 4/6] Add additional tests for uninitialized Array init. --- test/stdlib/Inputs/CommonArrayTests.gyb | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/stdlib/Inputs/CommonArrayTests.gyb b/test/stdlib/Inputs/CommonArrayTests.gyb index d5648ecc89bbd..6110e6337c628 100644 --- a/test/stdlib/Inputs/CommonArrayTests.gyb +++ b/test/stdlib/Inputs/CommonArrayTests.gyb @@ -553,6 +553,32 @@ ${Suite}.test("${ArrayType}/init(unsafeUninitializedCapacity:...:)/throwing") { expectEqual(0, InstanceCountedClass.instanceCounter) } +${Suite}.test("${ArrayType}/init(unsafeUninitializedCapacity:...:)/noCopy") { + var storageAddress: UnsafeMutablePointer? + var array = ${ArrayType}(unsafeUninitializedCapacity: 20) { buffer, _ in + storageAddress = buffer.baseAddress + } + array.withUnsafeMutableBufferPointer { buffer in + expectEqual(storageAddress, buffer.baseAddress) + } +} + +${Suite}.test("${ArrayType}/init(unsafeUninitializedCapacity:...:)/validCount") { + expectCrashLater() + let array = ${ArrayType}(unsafeUninitializedCapacity: 10) { buffer, c in + for i in 0..<10 { buffer[i] = i } + c = 20 + } +} + +${Suite}.test("${ArrayType}/init(unsafeUninitializedCapacity:...:)/reassignBuffer") { + expectCrashLater() + let otherBuffer = UnsafeMutableBufferPointer.allocate(capacity: 1) + let array = ${ArrayType}(unsafeUninitializedCapacity: 10) { buffer, _ in + buffer = otherBuffer + } +} + %end //===--- From 387b8f2a4628d2e3681333eaf851575c104f9cff Mon Sep 17 00:00:00 2001 From: Nate Cook Date: Thu, 21 Mar 2019 14:13:50 -0500 Subject: [PATCH 5/6] Add @_alwaysEmitIntoClient to ContiguousArray init. --- stdlib/public/core/ContiguousArray.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/public/core/ContiguousArray.swift b/stdlib/public/core/ContiguousArray.swift index b7a31978f200f..06e85bfeb14aa 100644 --- a/stdlib/public/core/ContiguousArray.swift +++ b/stdlib/public/core/ContiguousArray.swift @@ -1010,7 +1010,7 @@ extension ContiguousArray { /// - initializedCount: The count of initialized elements in the array, /// which begins as zero. Set `initializedCount` to the number of /// elements you initialize. - @inlinable + @_alwaysEmitIntoClient @inlinable public init( unsafeUninitializedCapacity: Int, initializingWith initializer: ( From ce83d15537c53fd204b3e328aeeecc824280c55d Mon Sep 17 00:00:00 2001 From: Nate Cook Date: Sat, 23 Mar 2019 07:45:56 -0500 Subject: [PATCH 6/6] Update CHANGELOG for Array unsafe initializer. --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dd294a966f9e..51a6c12321e59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -99,6 +99,11 @@ Swift 5.1 foo(0) // prints "Any" in Swift < 5.1, "T" in Swift 5.1 ``` +* [SE-0245][]: + + `Array` and `ContiguousArray` now have `init(unsafeUninitializedCapacity:initializingWith:)`, + which provides access to the array's uninitialized storage. + **Add new entries to the top of this section, not here!** Swift 5.0 @@ -7506,6 +7511,7 @@ Swift 1.0 [SE-0228]: [SE-0230]: [SE-0235]: +[SE-0245]: [SR-106]: [SR-419]: