Skip to content

Commit 6292c4b

Browse files
committed
Add method and init for working with uninitialized array storage
1 parent db1fb38 commit 6292c4b

File tree

2 files changed

+122
-20
lines changed

2 files changed

+122
-20
lines changed

stdlib/public/core/Array.swift

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,8 +1373,8 @@ extension Array {
13731373
/// the requested number of elements.
13741374
///
13751375
/// - Parameters:
1376-
/// - _unsafeUninitializedCapacity: The number of elements to allocate
1377-
/// space for in the new array.
1376+
/// - unsafeUninitializedCapacity: The number of elements to allocate space
1377+
/// for in the new array.
13781378
/// - initializer: A closure that initializes elements and sets the count
13791379
/// of the new array.
13801380
/// - Parameters:
@@ -1385,26 +1385,26 @@ extension Array {
13851385
/// elements you initialize.
13861386
@inlinable
13871387
public init(
1388-
_unsafeUninitializedCapacity: Int,
1388+
unsafeUninitializedCapacity: Int,
13891389
initializingWith initializer: (
13901390
_ buffer: inout UnsafeMutableBufferPointer<Element>,
13911391
_ initializedCount: inout Int) throws -> Void
13921392
) rethrows {
13931393
var firstElementAddress: UnsafeMutablePointer<Element>
13941394
(self, firstElementAddress) =
1395-
Array._allocateUninitialized(_unsafeUninitializedCapacity)
1395+
Array._allocateUninitialized(unsafeUninitializedCapacity)
13961396

13971397
var initializedCount = 0
13981398
defer {
13991399
// Update self.count even if initializer throws an error.
14001400
_precondition(
1401-
initializedCount <= _unsafeUninitializedCapacity,
1401+
initializedCount <= unsafeUninitializedCapacity,
14021402
"Initialized count set to greater than specified capacity."
14031403
)
14041404
self._buffer.count = initializedCount
14051405
}
14061406
var buffer = UnsafeMutableBufferPointer<Element>(
1407-
start: firstElementAddress, count: _unsafeUninitializedCapacity)
1407+
start: firstElementAddress, count: unsafeUninitializedCapacity)
14081408
try initializer(&buffer, &initializedCount)
14091409
}
14101410

@@ -1553,6 +1553,59 @@ extension Array {
15531553
it._position = endIndex
15541554
return (it,buffer.index(buffer.startIndex, offsetBy: self.count))
15551555
}
1556+
1557+
/// Calls the given closure with a pointer to the full capacity of the
1558+
/// array's mutable contiguous storage.
1559+
///
1560+
/// - Parameters:
1561+
/// - capacity: The minimum capacity to reserve for the array. `capacity`
1562+
/// must be greater than or equal to the array's current `count`.
1563+
/// - body: A closure that can modify or deinitialize existing elements or
1564+
/// initialize new elements.
1565+
/// - Parameters:
1566+
/// - buffer: An unsafe mutable buffer of the array's full storage,
1567+
/// including any uninitialized capacity after the initialized
1568+
/// elements. Only the elements in `buffer[0..<initializedCount]` are
1569+
/// initialized. `buffer` covers the memory for exactly the number of
1570+
/// elements specified in the `capacity` parameter.
1571+
/// - initializedCount: The count of the array's initialized elements. If
1572+
/// you initialize or deinitialize any elements inside `body`, update
1573+
/// `initializedCount` with the new count for the array.
1574+
/// - Returns: The return value, if any, of the `body` closure parameter.
1575+
public mutating func withUnsafeMutableBufferPointerToFullCapacity<R>(
1576+
capacity: Int,
1577+
_ body: (
1578+
_ buffer: UnsafeMutableBufferPointer<Element>,
1579+
_ initializedCount: inout Int
1580+
) throws -> R
1581+
) rethrows -> R {
1582+
// Ensure unique storage and requested capacity
1583+
reserveCapacity(capacity)
1584+
_precondition(capacity >= self.count)
1585+
1586+
var initializedCount = self.count
1587+
1588+
// Ensure that body can't invalidate the storage or its bounds by
1589+
// moving self into a temporary working array.
1590+
// See further notes above in withUnsafeMutableBuffer
1591+
var work = Array()
1592+
(work, self) = (self, work)
1593+
1594+
// Create an UnsafeBufferPointer over the full capacity of `work`
1595+
// that we can pass to `body`.
1596+
let pointer = work._buffer.firstElementAddress
1597+
let bufferPointer = UnsafeMutableBufferPointer(
1598+
start: pointer, count: capacity)
1599+
1600+
// Put the working array back before returning and update the count.
1601+
defer {
1602+
(work, self) = (self, work)
1603+
_buffer.count = initializedCount
1604+
}
1605+
1606+
// Invoke the body.
1607+
return try body(bufferPointer, &initializedCount)
1608+
}
15561609
}
15571610

15581611
extension Array {

test/stdlib/Inputs/CommonArrayTests.gyb

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -357,14 +357,14 @@ ${Suite}.test("${ArrayType}/reserveCapacity") {
357357
}
358358

359359
//===----------------------------------------------------------------------===//
360-
// init(_unsafeUninitializedCapacity:initializingWith:)
360+
// init(unsafeUninitializedCapacity:initializingWith:)
361361
//===----------------------------------------------------------------------===//
362362

363363
extension Collection {
364364
func stablyPartitioned(
365365
by belongsInFirstPartition: (Element) -> Bool
366366
) -> ${ArrayType}<Element> {
367-
let result = ${ArrayType}<Element>(_unsafeUninitializedCapacity: self.count) {
367+
let result = ${ArrayType}<Element>(unsafeUninitializedCapacity: self.count) {
368368
buffer, initializedCount in
369369
var lowIndex = 0
370370
var highIndex = buffer.count
@@ -385,7 +385,15 @@ extension Collection {
385385
}
386386
}
387387

388-
${Suite}.test("${ArrayType}/init(_unsafeUninitializedCapacity:...:)") {
388+
final class InstanceCountedClass {
389+
static var instanceCounter = 0
390+
391+
init() { InstanceCountedClass.instanceCounter += 1 }
392+
deinit { InstanceCountedClass.instanceCounter -= 1 }
393+
}
394+
enum E: Error { case error }
395+
396+
${Suite}.test("${ArrayType}/init(unsafeUninitializedCapacity:...:)") {
389397
var a = ${ArrayType}(0..<300)
390398
let p = a.stablyPartitioned(by: { $0 % 2 == 0 })
391399
expectEqualSequence(
@@ -394,17 +402,59 @@ ${Suite}.test("${ArrayType}/init(_unsafeUninitializedCapacity:...:)") {
394402
)
395403
}
396404

397-
${Suite}.test("${ArrayType}/init(_unsafeUninitializedCapacity:...:)/throwing") {
398-
final class InstanceCountedClass {
399-
static var instanceCounter = 0
405+
${Suite}.test("${ArrayType}/init(unsafeUninitializedCapacity:...:)/throwing") {
406+
do {
407+
var a = Array<InstanceCountedClass>(unsafeUninitializedCapacity: 10) { buffer, c in
408+
let p = buffer.baseAddress!
409+
for i in 0..<5 {
410+
(p + i).initialize(to: InstanceCountedClass())
411+
}
412+
c = 5
413+
}
414+
expectEqual(5, a.count)
415+
expectEqual(5, InstanceCountedClass.instanceCounter)
416+
417+
a = []
418+
expectEqual(0, InstanceCountedClass.instanceCounter)
400419

401-
init() { InstanceCountedClass.instanceCounter += 1 }
402-
deinit { InstanceCountedClass.instanceCounter -= 1 }
420+
a = try Array(unsafeUninitializedCapacity: 10) { buffer, c in
421+
let p = buffer.baseAddress!
422+
for i in 0..<5 {
423+
(p + i).initialize(to: InstanceCountedClass())
424+
}
425+
c = 5
426+
throw E.error
427+
}
428+
429+
// The throw above should prevent reaching here, which should mean the
430+
// instances created in the closure should get deallocated before the final
431+
// expectation outside the do/catch block.
432+
expectUnreachable()
433+
} catch {}
434+
expectEqual(0, InstanceCountedClass.instanceCounter)
435+
}
436+
437+
${Suite}.test("${ArrayType}/withUnsafeMutableBufferPointerToFullCapacity") {
438+
var a = Array<Int>()
439+
let result: Int = a.withUnsafeMutableBufferPointerToFullCapacity(reservingCapacity: 10) {
440+
buffer, initializedCount in
441+
expectEqual(10, buffer.count)
442+
expectEqual(0, initializedCount)
443+
444+
for i in 0..<5 {
445+
buffer[i] = i
446+
}
447+
initializedCount = 5
448+
return buffer[0..<5].reduce(0, +)
403449
}
404-
enum E: Error { case error }
450+
expectEqual(10, a.reduce(0, +))
451+
expectEqual(5, a.count)
452+
}
405453

454+
${Suite}.test("${ArrayType}/withUnsafeMutableBufferPointerToFullCapacity/throwing") {
406455
do {
407-
var a = Array<InstanceCountedClass>(_unsafeUninitializedCapacity: 10) { buffer, c in
456+
var a = Array<InstanceCountedClass>()
457+
a.withUnsafeMutableBufferPointerToFullCapacity(capacity: 10) { buffer, c in
408458
let p = buffer.baseAddress!
409459
for i in 0..<5 {
410460
(p + i).initialize(to: InstanceCountedClass())
@@ -416,8 +466,7 @@ ${Suite}.test("${ArrayType}/init(_unsafeUninitializedCapacity:...:)/throwing") {
416466

417467
a = []
418468
expectEqual(0, InstanceCountedClass.instanceCounter)
419-
420-
a = try Array(_unsafeUninitializedCapacity: 10) { buffer, c in
469+
try a.withUnsafeMutableBufferPointerToFullCapacity(capacity: 10) { buffer, c in
421470
let p = buffer.baseAddress!
422471
for i in 0..<5 {
423472
(p + i).initialize(to: InstanceCountedClass())
@@ -426,10 +475,10 @@ ${Suite}.test("${ArrayType}/init(_unsafeUninitializedCapacity:...:)/throwing") {
426475
throw E.error
427476
}
428477

429-
// The throw above should prevent reaching here, which should mean the
478+
// The throw above should prevent reaching here, which means that the
430479
// instances created in the closure should get deallocated before the final
431480
// expectation outside the do/catch block.
432-
expectTrue(false)
481+
expectUnreachable()
433482
} catch {}
434483
expectEqual(0, InstanceCountedClass.instanceCounter)
435484
}

0 commit comments

Comments
 (0)