Skip to content

Commit 92900d4

Browse files
authored
Add an array initializer with access to uninitialized storage (#17774)
* Add an Array initializer for using an uninitialized capacity. * Add tests for reserveCapacity & init(_unsafeUninitializedCapacity:...:)
1 parent c468ca1 commit 92900d4

File tree

2 files changed

+145
-0
lines changed

2 files changed

+145
-0
lines changed

stdlib/public/core/Array.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,6 +1353,55 @@ extension Array {
13531353
}
13541354

13551355
extension Array {
1356+
/// Creates an array with the specified capacity, then calls the given
1357+
/// closure with a buffer covering the array's uninitialized memory.
1358+
///
1359+
/// Inside the closure, set the `initializedCount` parameter to the number of
1360+
/// elements that are initialized by the closure. The memory in the range
1361+
/// `buffer[0..<initializedCount]` must be initialized at the end of the
1362+
/// closure's execution, and the memory in the range
1363+
/// `buffer[initializedCount...]` must be uninitialized.
1364+
///
1365+
/// - Note: While the resulting array may have a capacity larger than the
1366+
/// requested amount, the buffer passed to the closure will cover exactly
1367+
/// the requested number of elements.
1368+
///
1369+
/// - Parameters:
1370+
/// - _unsafeUninitializedCapacity: The number of elements to allocate
1371+
/// space for in the new array.
1372+
/// - initializer: A closure that initializes elements and sets the count
1373+
/// of the new array.
1374+
/// - Parameters:
1375+
/// - buffer: A buffer covering uninitialized memory with room for the
1376+
/// specified number of of elements.
1377+
/// - initializedCount: The count of initialized elements in the array,
1378+
/// which begins as zero. Set `initializedCount` to the number of
1379+
/// elements you initialize.
1380+
@inlinable
1381+
public init(
1382+
_unsafeUninitializedCapacity: Int,
1383+
initializingWith initializer: (
1384+
_ buffer: inout UnsafeMutableBufferPointer<Element>,
1385+
_ initializedCount: inout Int) throws -> Void
1386+
) rethrows {
1387+
var firstElementAddress: UnsafeMutablePointer<Element>
1388+
(self, firstElementAddress) =
1389+
Array._allocateUninitialized(_unsafeUninitializedCapacity)
1390+
1391+
var initializedCount = 0
1392+
defer {
1393+
// Update self.count even if initializer throws an error.
1394+
_precondition(
1395+
initializedCount <= _unsafeUninitializedCapacity,
1396+
"Initialized count set to greater than specified capacity."
1397+
)
1398+
self._buffer.count = initializedCount
1399+
}
1400+
var buffer = UnsafeMutableBufferPointer<Element>(
1401+
start: firstElementAddress, count: _unsafeUninitializedCapacity)
1402+
try initializer(&buffer, &initializedCount)
1403+
}
1404+
13561405
/// Calls a closure with a pointer to the array's contiguous storage.
13571406
///
13581407
/// Often, the optimizer can eliminate bounds checks within an array

test/stdlib/Inputs/CommonArrayTests.gyb

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,102 @@ ${Suite}.test("${ArrayType}/withUnsafeMutableBytes")
340340
expectEqual(10, b[0])
341341
}
342342

343+
// FIXME: Implement these changes for ArraySlice and ContiguousArray
344+
345+
%if ArrayType == 'Array':
346+
347+
//===----------------------------------------------------------------------===//
348+
// reserveCapacity semantics
349+
//===----------------------------------------------------------------------===//
350+
351+
${Suite}.test("${ArrayType}/reserveCapacity") {
352+
var a = ${ArrayType}<Int>()
353+
for capacity in stride(from: 0, to: 1000, by: 53) {
354+
a.reserveCapacity(capacity)
355+
expectLE(capacity, a.capacity)
356+
}
357+
}
358+
359+
//===----------------------------------------------------------------------===//
360+
// init(_unsafeUninitializedCapacity:initializingWith:)
361+
//===----------------------------------------------------------------------===//
362+
363+
extension Collection {
364+
func stablyPartitioned(
365+
by belongsInFirstPartition: (Element) -> Bool
366+
) -> ${ArrayType}<Element> {
367+
let result = ${ArrayType}<Element>(_unsafeUninitializedCapacity: self.count) {
368+
buffer, initializedCount in
369+
var lowIndex = 0
370+
var highIndex = buffer.count
371+
for element in self {
372+
if belongsInFirstPartition(element) {
373+
buffer[lowIndex] = element
374+
lowIndex += 1
375+
} else {
376+
highIndex -= 1
377+
buffer[highIndex] = element
378+
}
379+
}
380+
381+
buffer[highIndex...].reverse()
382+
initializedCount = buffer.count
383+
}
384+
return result
385+
}
386+
}
387+
388+
${Suite}.test("${ArrayType}/init(_unsafeUninitializedCapacity:...:)") {
389+
var a = ${ArrayType}(0..<300)
390+
let p = a.stablyPartitioned(by: { $0 % 2 == 0 })
391+
expectEqualSequence(
392+
[stride(from: 0, to: 300, by: 2), stride(from: 1, to: 300, by: 2)].joined(),
393+
p
394+
)
395+
}
396+
397+
${Suite}.test("${ArrayType}/init(_unsafeUninitializedCapacity:...:)/throwing") {
398+
final class InstanceCountedClass {
399+
static var instanceCounter = 0
400+
401+
init() { InstanceCountedClass.instanceCounter += 1 }
402+
deinit { InstanceCountedClass.instanceCounter -= 1 }
403+
}
404+
enum E: Error { case error }
405+
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)
419+
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+
expectTrue(false)
433+
} catch {}
434+
expectEqual(0, InstanceCountedClass.instanceCounter)
435+
}
436+
437+
%end
438+
343439
//===---
344440
// Check that iterators traverse a snapshot of the collection.
345441
//===---

0 commit comments

Comments
 (0)