From 704a60fc7a07018678be443333bedd2a088b6d8a Mon Sep 17 00:00:00 2001 From: Nate Cook Date: Sat, 23 Mar 2019 13:18:10 -0500 Subject: [PATCH] [stdlib] Make unsafe array initializer public (#23134) [stdlib] Make unsafe array initializer public This implements SE-0245. The public versions of this initializer call into the existing, underscored version, which avoids the need for availability constraints. --- CHANGELOG.md | 6 +++ stdlib/public/core/Array.swift | 61 ++++++++++++++-------- stdlib/public/core/ContiguousArray.swift | 37 ++++++++++++++ test/stdlib/Inputs/CommonArrayTests.gyb | 65 ++++++++++++++++-------- 4 files changed, 127 insertions(+), 42 deletions(-) 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]: diff --git a/stdlib/public/core/Array.swift b/stdlib/public/core/Array.swift index edf6b3f85a854..55af7249b6b32 100644 --- a/stdlib/public/core/Array.swift +++ b/stdlib/public/core/Array.swift @@ -1366,6 +1366,37 @@ extension Array { } extension Array { + /// Implementation for Array(unsafeUninitializedCapacity:initializingWith:) + /// and ContiguousArray(unsafeUninitializedCapacity:initializingWith:) + @inlinable + internal 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 + 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 /// closure with a buffer covering the array's uninitialized memory. /// @@ -1373,14 +1404,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) - - 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. /// /// 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..06e85bfeb14aa 100644 --- a/stdlib/public/core/ContiguousArray.swift +++ b/stdlib/public/core/ContiguousArray.swift @@ -985,6 +985,43 @@ 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 { + self = try ContiguousArray(Array( + _unsafeUninitializedCapacity: unsafeUninitializedCapacity, + initializingWith: initializer)) + } + /// 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/test/stdlib/Inputs/CommonArrayTests.gyb b/test/stdlib/Inputs/CommonArrayTests.gyb index cf4f099f175f7..6110e6337c628 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,14 +548,37 @@ ${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) } +${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 //===---