Skip to content

Commit b6bb9d2

Browse files
authored
[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.
1 parent a985f7b commit b6bb9d2

File tree

4 files changed

+127
-42
lines changed

4 files changed

+127
-42
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ Swift 5.1
9999
foo(0) // prints "Any" in Swift < 5.1, "T" in Swift 5.1
100100
```
101101

102+
* [SE-0245][]:
103+
104+
`Array` and `ContiguousArray` now have `init(unsafeUninitializedCapacity:initializingWith:)`,
105+
which provides access to the array's uninitialized storage.
106+
102107
**Add new entries to the top of this section, not here!**
103108

104109
Swift 5.0
@@ -7506,6 +7511,7 @@ Swift 1.0
75067511
[SE-0228]: <https://github.com/apple/swift-evolution/blob/master/proposals/0228-fix-expressiblebystringinterpolation.md>
75077512
[SE-0230]: <https://github.com/apple/swift-evolution/blob/master/proposals/0230-flatten-optional-try.md>
75087513
[SE-0235]: <https://github.com/apple/swift-evolution/blob/master/proposals/0235-add-result.md>
7514+
[SE-0245]: <https://github.com/apple/swift-evolution/blob/master/proposals/0245-array-uninitialized-initializer.md>
75097515

75107516
[SR-106]: <https://bugs.swift.org/browse/SR-106>
75117517
[SR-419]: <https://bugs.swift.org/browse/SR-419>

stdlib/public/core/Array.swift

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,21 +1366,53 @@ extension Array {
13661366
}
13671367

13681368
extension Array {
1369+
/// Implementation for Array(unsafeUninitializedCapacity:initializingWith:)
1370+
/// and ContiguousArray(unsafeUninitializedCapacity:initializingWith:)
1371+
@inlinable
1372+
internal init(
1373+
_unsafeUninitializedCapacity: Int,
1374+
initializingWith initializer: (
1375+
_ buffer: inout UnsafeMutableBufferPointer<Element>,
1376+
_ initializedCount: inout Int) throws -> Void
1377+
) rethrows {
1378+
var firstElementAddress: UnsafeMutablePointer<Element>
1379+
(self, firstElementAddress) =
1380+
Array._allocateUninitialized(_unsafeUninitializedCapacity)
1381+
1382+
var initializedCount = 0
1383+
var buffer = UnsafeMutableBufferPointer<Element>(
1384+
start: firstElementAddress, count: _unsafeUninitializedCapacity)
1385+
defer {
1386+
// Update self.count even if initializer throws an error.
1387+
_precondition(
1388+
initializedCount <= _unsafeUninitializedCapacity,
1389+
"Initialized count set to greater than specified capacity."
1390+
)
1391+
_precondition(
1392+
buffer.baseAddress == firstElementAddress,
1393+
"Can't reassign buffer in Array(unsafeUninitializedCapacity:initializingWith:)"
1394+
)
1395+
self._buffer.count = initializedCount
1396+
}
1397+
try initializer(&buffer, &initializedCount)
1398+
}
1399+
13691400
/// Creates an array with the specified capacity, then calls the given
13701401
/// closure with a buffer covering the array's uninitialized memory.
13711402
///
13721403
/// Inside the closure, set the `initializedCount` parameter to the number of
13731404
/// elements that are initialized by the closure. The memory in the range
13741405
/// `buffer[0..<initializedCount]` must be initialized at the end of the
13751406
/// closure's execution, and the memory in the range
1376-
/// `buffer[initializedCount...]` must be uninitialized.
1407+
/// `buffer[initializedCount...]` must be uninitialized. This postcondition
1408+
/// must hold even if the `initializer` closure throws an error.
13771409
///
13781410
/// - Note: While the resulting array may have a capacity larger than the
13791411
/// requested amount, the buffer passed to the closure will cover exactly
13801412
/// the requested number of elements.
13811413
///
13821414
/// - Parameters:
1383-
/// - _unsafeUninitializedCapacity: The number of elements to allocate
1415+
/// - unsafeUninitializedCapacity: The number of elements to allocate
13841416
/// space for in the new array.
13851417
/// - initializer: A closure that initializes elements and sets the count
13861418
/// of the new array.
@@ -1390,31 +1422,18 @@ extension Array {
13901422
/// - initializedCount: The count of initialized elements in the array,
13911423
/// which begins as zero. Set `initializedCount` to the number of
13921424
/// elements you initialize.
1393-
@inlinable
1425+
@_alwaysEmitIntoClient @inlinable
13941426
public init(
1395-
_unsafeUninitializedCapacity: Int,
1427+
unsafeUninitializedCapacity: Int,
13961428
initializingWith initializer: (
13971429
_ buffer: inout UnsafeMutableBufferPointer<Element>,
13981430
_ initializedCount: inout Int) throws -> Void
13991431
) rethrows {
1400-
var firstElementAddress: UnsafeMutablePointer<Element>
1401-
(self, firstElementAddress) =
1402-
Array._allocateUninitialized(_unsafeUninitializedCapacity)
1403-
1404-
var initializedCount = 0
1405-
defer {
1406-
// Update self.count even if initializer throws an error.
1407-
_precondition(
1408-
initializedCount <= _unsafeUninitializedCapacity,
1409-
"Initialized count set to greater than specified capacity."
1410-
)
1411-
self._buffer.count = initializedCount
1412-
}
1413-
var buffer = UnsafeMutableBufferPointer<Element>(
1414-
start: firstElementAddress, count: _unsafeUninitializedCapacity)
1415-
try initializer(&buffer, &initializedCount)
1432+
self = try Array(
1433+
_unsafeUninitializedCapacity: unsafeUninitializedCapacity,
1434+
initializingWith: initializer)
14161435
}
1417-
1436+
14181437
/// Calls a closure with a pointer to the array's contiguous storage.
14191438
///
14201439
/// Often, the optimizer can eliminate bounds checks within an array

stdlib/public/core/ContiguousArray.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,43 @@ extension ContiguousArray {
985985
}
986986

987987
extension ContiguousArray {
988+
/// Creates an array with the specified capacity, then calls the given
989+
/// closure with a buffer covering the array's uninitialized memory.
990+
///
991+
/// Inside the closure, set the `initializedCount` parameter to the number of
992+
/// elements that are initialized by the closure. The memory in the range
993+
/// `buffer[0..<initializedCount]` must be initialized at the end of the
994+
/// closure's execution, and the memory in the range
995+
/// `buffer[initializedCount...]` must be uninitialized. This postcondition
996+
/// must hold even if the `initializer` closure throws an error.
997+
///
998+
/// - Note: While the resulting array may have a capacity larger than the
999+
/// requested amount, the buffer passed to the closure will cover exactly
1000+
/// the requested number of elements.
1001+
///
1002+
/// - Parameters:
1003+
/// - unsafeUninitializedCapacity: The number of elements to allocate
1004+
/// space for in the new array.
1005+
/// - initializer: A closure that initializes elements and sets the count
1006+
/// of the new array.
1007+
/// - Parameters:
1008+
/// - buffer: A buffer covering uninitialized memory with room for the
1009+
/// specified number of of elements.
1010+
/// - initializedCount: The count of initialized elements in the array,
1011+
/// which begins as zero. Set `initializedCount` to the number of
1012+
/// elements you initialize.
1013+
@_alwaysEmitIntoClient @inlinable
1014+
public init(
1015+
unsafeUninitializedCapacity: Int,
1016+
initializingWith initializer: (
1017+
_ buffer: inout UnsafeMutableBufferPointer<Element>,
1018+
_ initializedCount: inout Int) throws -> Void
1019+
) rethrows {
1020+
self = try ContiguousArray(Array(
1021+
_unsafeUninitializedCapacity: unsafeUninitializedCapacity,
1022+
initializingWith: initializer))
1023+
}
1024+
9881025
/// Calls a closure with a pointer to the array's contiguous storage.
9891026
///
9901027
/// Often, the optimizer can eliminate bounds checks within an array

test/stdlib/Inputs/CommonArrayTests.gyb

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -462,10 +462,6 @@ ${Suite}.test("${ArrayType}/withUnsafeMutableBytes")
462462
expectEqual(10, b[0])
463463
}
464464

465-
// FIXME: Implement these changes for ArraySlice and ContiguousArray
466-
467-
%if ArrayType == 'Array':
468-
469465
//===----------------------------------------------------------------------===//
470466
// reserveCapacity semantics
471467
//===----------------------------------------------------------------------===//
@@ -478,36 +474,39 @@ ${Suite}.test("${ArrayType}/reserveCapacity") {
478474
}
479475
}
480476

477+
%if ArrayType in ['Array', 'ContiguousArray']:
478+
481479
//===----------------------------------------------------------------------===//
482-
// init(_unsafeUninitializedCapacity:initializingWith:)
480+
// init(unsafeUninitializedCapacity:initializingWith:)
483481
//===----------------------------------------------------------------------===//
484482

485483
extension Collection {
486484
func stablyPartitioned(
487485
by belongsInFirstPartition: (Element) -> Bool
488486
) -> ${ArrayType}<Element> {
489-
let result = ${ArrayType}<Element>(_unsafeUninitializedCapacity: self.count) {
487+
let result = ${ArrayType}<Element>(unsafeUninitializedCapacity: self.count) {
490488
buffer, initializedCount in
491-
var lowIndex = 0
492-
var highIndex = buffer.count
489+
var low = buffer.baseAddress!
490+
var high = low + buffer.count
493491
for element in self {
494492
if belongsInFirstPartition(element) {
495-
buffer[lowIndex] = element
496-
lowIndex += 1
493+
low.initialize(to: element)
494+
low += 1
497495
} else {
498-
highIndex -= 1
499-
buffer[highIndex] = element
496+
high -= 1
497+
high.initialize(to: element)
500498
}
501499
}
502-
500+
501+
let highIndex = high - buffer.baseAddress!
503502
buffer[highIndex...].reverse()
504503
initializedCount = buffer.count
505504
}
506505
return result
507506
}
508507
}
509508

510-
${Suite}.test("${ArrayType}/init(_unsafeUninitializedCapacity:...:)") {
509+
${Suite}.test("${ArrayType}/init(unsafeUninitializedCapacity:...:)") {
511510
var a = ${ArrayType}(0..<300)
512511
let p = a.stablyPartitioned(by: { $0 % 2 == 0 })
513512
expectEqualSequence(
@@ -516,7 +515,7 @@ ${Suite}.test("${ArrayType}/init(_unsafeUninitializedCapacity:...:)") {
516515
)
517516
}
518517

519-
${Suite}.test("${ArrayType}/init(_unsafeUninitializedCapacity:...:)/throwing") {
518+
${Suite}.test("${ArrayType}/init(unsafeUninitializedCapacity:...:)/throwing") {
520519
final class InstanceCountedClass {
521520
static var instanceCounter = 0
522521

@@ -526,7 +525,8 @@ ${Suite}.test("${ArrayType}/init(_unsafeUninitializedCapacity:...:)/throwing") {
526525
enum E: Error { case error }
527526

528527
do {
529-
var a = Array<InstanceCountedClass>(_unsafeUninitializedCapacity: 10) { buffer, c in
528+
var a = ${ArrayType}<InstanceCountedClass>(unsafeUninitializedCapacity: 10) {
529+
buffer, c in
530530
let p = buffer.baseAddress!
531531
for i in 0..<5 {
532532
(p + i).initialize(to: InstanceCountedClass())
@@ -539,7 +539,7 @@ ${Suite}.test("${ArrayType}/init(_unsafeUninitializedCapacity:...:)/throwing") {
539539
a = []
540540
expectEqual(0, InstanceCountedClass.instanceCounter)
541541

542-
a = try Array(_unsafeUninitializedCapacity: 10) { buffer, c in
542+
a = try ${ArrayType}(unsafeUninitializedCapacity: 10) { buffer, c in
543543
let p = buffer.baseAddress!
544544
for i in 0..<5 {
545545
(p + i).initialize(to: InstanceCountedClass())
@@ -548,14 +548,37 @@ ${Suite}.test("${ArrayType}/init(_unsafeUninitializedCapacity:...:)/throwing") {
548548
throw E.error
549549
}
550550

551-
// The throw above should prevent reaching here, which should mean the
552-
// instances created in the closure should get deallocated before the final
553-
// expectation outside the do/catch block.
554-
expectTrue(false)
551+
expectUnreachable()
555552
} catch {}
556553
expectEqual(0, InstanceCountedClass.instanceCounter)
557554
}
558555

556+
${Suite}.test("${ArrayType}/init(unsafeUninitializedCapacity:...:)/noCopy") {
557+
var storageAddress: UnsafeMutablePointer<Int>?
558+
var array = ${ArrayType}<Int>(unsafeUninitializedCapacity: 20) { buffer, _ in
559+
storageAddress = buffer.baseAddress
560+
}
561+
array.withUnsafeMutableBufferPointer { buffer in
562+
expectEqual(storageAddress, buffer.baseAddress)
563+
}
564+
}
565+
566+
${Suite}.test("${ArrayType}/init(unsafeUninitializedCapacity:...:)/validCount") {
567+
expectCrashLater()
568+
let array = ${ArrayType}<Int>(unsafeUninitializedCapacity: 10) { buffer, c in
569+
for i in 0..<10 { buffer[i] = i }
570+
c = 20
571+
}
572+
}
573+
574+
${Suite}.test("${ArrayType}/init(unsafeUninitializedCapacity:...:)/reassignBuffer") {
575+
expectCrashLater()
576+
let otherBuffer = UnsafeMutableBufferPointer<Int>.allocate(capacity: 1)
577+
let array = ${ArrayType}<Int>(unsafeUninitializedCapacity: 10) { buffer, _ in
578+
buffer = otherBuffer
579+
}
580+
}
581+
559582
%end
560583

561584
//===---

0 commit comments

Comments
 (0)