Skip to content

Commit b7e08ac

Browse files
lorenteystephentyrone
authored andcommitted
[stdlib] Slice: customize withContiguous[Mutable]StorageIfAvailable (swiftlang#28883)
* [stdlib] Slice: customize withContiguous[Mutable]StorageIfAvailable We can easily make an UnsafeBufferPointer that slices another UnsafeBufferPointer, so let’s allow Slice to vend a slice of the base collection’s contiguous storage, if it provides access to one. We need to do some index distance calculations to implement this, but those will be constant-time in the usual case where the base collection is a RAC. https://bugs.swift.org/browse/SR-11957 rdar://58090587 * [test] UnsafeBufferPointer: fix some warnings * [stdlib] Slice: don’t calculate index distances unless the base provides contiguous mutable storage
1 parent 51bbaeb commit b7e08ac

File tree

3 files changed

+87
-17
lines changed

3 files changed

+87
-17
lines changed

stdlib/public/core/Slice.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,18 @@ extension Slice: Collection {
215215
public func _failEarlyRangeCheck(_ range: Range<Index>, bounds: Range<Index>) {
216216
_base._failEarlyRangeCheck(range, bounds: bounds)
217217
}
218+
219+
@_alwaysEmitIntoClient @inlinable
220+
public func withContiguousStorageIfAvailable<R>(
221+
_ body: (UnsafeBufferPointer<Element>) throws -> R
222+
) rethrows -> R? {
223+
try _base.withContiguousStorageIfAvailable { buffer in
224+
let start = _base.distance(from: _base.startIndex, to: _startIndex)
225+
let count = _base.distance(from: _startIndex, to: _endIndex)
226+
let slice = UnsafeBufferPointer(rebasing: buffer[start ..< start + count])
227+
return try body(slice)
228+
}
229+
}
218230
}
219231

220232
extension Slice: BidirectionalCollection where Base: BidirectionalCollection {
@@ -258,6 +270,34 @@ extension Slice: MutableCollection where Base: MutableCollection {
258270
_writeBackMutableSlice(&self, bounds: bounds, slice: newValue)
259271
}
260272
}
273+
274+
@_alwaysEmitIntoClient @inlinable
275+
public mutating func withContiguousMutableStorageIfAvailable<R>(
276+
_ body: (inout UnsafeMutableBufferPointer<Element>) throws -> R
277+
) rethrows -> R? {
278+
// We're calling `withContiguousMutableStorageIfAvailable` twice here so
279+
// that we don't calculate index distances unless we know we'll use them.
280+
// The expectation here is that the base collection will make itself
281+
// contiguous on the first try and the second call will be relatively cheap.
282+
guard _base.withContiguousMutableStorageIfAvailable({ _ in }) != nil
283+
else {
284+
return nil
285+
}
286+
let start = _base.distance(from: _base.startIndex, to: _startIndex)
287+
let count = _base.distance(from: _startIndex, to: _endIndex)
288+
return try _base.withContiguousMutableStorageIfAvailable { buffer in
289+
var slice = UnsafeMutableBufferPointer(
290+
rebasing: buffer[start ..< start + count])
291+
let copy = slice
292+
defer {
293+
_precondition(
294+
slice.baseAddress == copy.baseAddress &&
295+
slice.count == copy.count,
296+
"Slice.withUnsafeMutableBufferPointer: replacing the buffer is not allowed")
297+
}
298+
return try body(&slice)
299+
}
300+
}
261301
}
262302

263303

validation-test/stdlib/Slice.swift.gyb

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -113,30 +113,35 @@ SliceTests.test("${Collection}.Slice.{startIndex,endIndex}") {
113113
% end
114114
% end
115115

116+
SliceTests.test("${Collection}.Slice.withContiguousStorageIfAvailable") {
117+
for test in subscriptRangeTests {
118+
let c = ${Collection}(elements: test.collection)
119+
let bounds = test.bounds(in: c)
120+
var slice = Slice(base: c, bounds: bounds)
121+
let r = slice.withContiguousStorageIfAvailable { _ in }
122+
expectNil(r) // None of the minimal collection types implement this.
123+
}
124+
}
125+
116126
//===----------------------------------------------------------------------===//
117127
// MutableSlice<Base>
118128
//===----------------------------------------------------------------------===//
119129

120-
/*
121-
FIXME: uncomment this test when the following bug is fixed:
122-
123-
<rdar://problem/21935030> Recast Slice and MutableSlice in terms of Collection
124-
and MutableCollection
125-
126-
extension MutableSlice {
130+
extension Slice {
127131
func _checkBaseSubSequenceElementIsElement() {
132+
expectEqualType(
128133
Element.self,
129134
Iterator.Element.self)
130135
expectEqualType(
131136
Element.self,
132-
Iterator.Element.self,
133137
Base.Iterator.Element.self)
134138
expectEqualType(
135139
Element.self,
136-
Iterator.Element.self,
140+
Iterator.Element.self)
141+
expectEqualType(
142+
Element.self,
137143
Base.SubSequence.Iterator.Element.self)
138144
}
139145
}
140-
*/
141146

142147
runAllTests()

validation-test/stdlib/UnsafeBufferPointer.swift.gyb

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ ${SelfName}TestSuite.test("AssociatedTypes") {
6363
% if IsRaw:
6464
iteratorType: ${SelfType}.Iterator.self,
6565
% else:
66-
iteratorType: UnsafeBufferPointerIterator<Float>.self,
66+
iteratorType: UnsafeBufferPointer<Float>.Iterator.self,
6767
% end
6868
subSequenceType: Slice<${SelfType}>.self,
6969
indexType: Int.self,
@@ -361,6 +361,31 @@ UnsafeMutableBufferPointerTestSuite.test("withContiguous(Mutable)StorageIfAvaila
361361
expectEqual(result1, result2)
362362
}
363363

364+
UnsafeMutableBufferPointerTestSuite.test("Slice.withContiguousStorageIfAvailable") {
365+
guard #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) else {
366+
return
367+
}
368+
369+
for test in subscriptRangeTests {
370+
let c = test.collection.map { MinimalEquatableValue($0.value) }
371+
let buffer = UnsafeMutableBufferPointer<MinimalEquatableValue>.allocate(
372+
capacity: test.collection.count)
373+
var (it, idx) = buffer.initialize(from: c)
374+
expectEqual(buffer.endIndex, idx)
375+
expectNil(it.next())
376+
377+
let expected = test.expected.map { MinimalEquatableValue($0.value) }
378+
let r1: Void? = buffer[test.bounds].withContiguousStorageIfAvailable { b in
379+
expectTrue(expected.elementsEqual(b))
380+
}
381+
expectNotNil(r1)
382+
let r2: Void? = buffer[test.bounds].withContiguousMutableStorageIfAvailable { b in
383+
expectTrue(expected.elementsEqual(b))
384+
}
385+
expectNotNil(r2)
386+
}
387+
}
388+
364389
UnsafeMutableBufferPointerTestSuite.test("sort") {
365390
var values = (0..<1000).map({ _ in Int.random(in: 0..<100) })
366391
let sortedValues = values.sorted()
@@ -389,7 +414,7 @@ for testIndex in (0..<bufCount) {
389414
% Type = 'Unsafe' + ('Mutable' if mutable else '') + ('Raw' if raw else '') + 'BufferPointer'
390415
${Type}TestSuite.test("${action}Element\(testIndex)ViaSlice") {
391416
% if raw:
392-
let allocated = UnsafeMutableRawPointer.allocate(bytes: bufCount, alignedTo: 8)
417+
let allocated = UnsafeMutableRawPointer.allocate(byteCount: bufCount, alignment: 8)
393418
for i in 0..<bufCount {
394419
allocated.storeBytes(of: UInt8(i), toByteOffset: i, as: UInt8.self)
395420
}
@@ -400,7 +425,7 @@ for testIndex in (0..<bufCount) {
400425
defer { allocated.deallocate() }
401426

402427
let buffer = ${Type}(start: allocated, count: bufCount)
403-
${'var' if mutable else 'let'} slice = buffer[sliceRange]
428+
${'var' if mutable and action <> 'read' else 'let'} slice = buffer[sliceRange]
404429

405430
if _isDebugAssertConfiguration(),
406431
testIndex < sliceRange.lowerBound ||
@@ -430,7 +455,7 @@ for testRange in testRanges {
430455
% Type = 'Unsafe' + ('Mutable' if mutable else '') + ('Raw' if raw else '') + 'BufferPointer'
431456
${Type}TestSuite.test("${action}Slice\(testRange)ViaSlice") {
432457
% if raw:
433-
let allocated = UnsafeMutableRawPointer.allocate(bytes: bufCount+2, alignedTo: 8)
458+
let allocated = UnsafeMutableRawPointer.allocate(byteCount: bufCount+2, alignment: 8)
434459
for i in 0..<bufCount+2 {
435460
allocated.storeBytes(of: UInt8(i), toByteOffset: i, as: UInt8.self)
436461
}
@@ -441,7 +466,7 @@ for testRange in testRanges {
441466
defer { allocated.deallocate() }
442467

443468
let buffer = ${Type}(start: allocated, count: bufCount+2)
444-
${'var' if mutable else 'let'} slice = buffer[sliceRange]
469+
${'var' if mutable and action <> 'read' else 'let'} slice = buffer[sliceRange]
445470

446471
if _isDebugAssertConfiguration(),
447472
testRange.lowerBound < sliceRange.lowerBound ||
@@ -937,7 +962,7 @@ UnsafeMutable${'Raw' if IsRaw else ''}BufferPointerTestSuite.test("subscript/${R
937962

938963
UnsafeMutable${'Raw' if IsRaw else ''}BufferPointerTestSuite.test("subscript/set/overlaps") {
939964
% if IsRaw:
940-
let buffer = UnsafeMutableRawBufferPointer.allocate(count: 4)
965+
let buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 4, alignment: 1)
941966
% else:
942967
let buffer = UnsafeMutableBufferPointer<Int>.allocate(capacity: 4)
943968
% end
@@ -970,7 +995,7 @@ UnsafeMutable${'Raw' if IsRaw else ''}BufferPointerTestSuite.test("subscript/set
970995

971996
UnsafeMutable${'Raw' if IsRaw else ''}BufferPointerTestSuite.test("nonmutating-swapAt") {
972997
% if IsRaw:
973-
let allocated = UnsafeMutableRawPointer.allocate(bytes: 4, alignedTo: 8)
998+
let allocated = UnsafeMutableRawPointer.allocate(byteCount: 4, alignment: 8)
974999
let buffer = UnsafeMutableRawBufferPointer(start: allocated, count: 3)
9751000
allocated.storeBytes(of: UInt8.max, toByteOffset: 3, as: UInt8.self)
9761001
% else:

0 commit comments

Comments
 (0)