Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stdlib] [WIP] Add UnsafeRawBufferPointer.initialize(as:from:) #5718

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
54 changes: 54 additions & 0 deletions stdlib/public/core/UnsafeRawBufferPointer.swift.gyb
Expand Up @@ -348,6 +348,60 @@ public struct Unsafe${Mutable}RawBufferPointer
return 0
}

% if mutable:
/// Initializes memory in the buffer with the elements of
/// `source` and binds the initialized memory to type `T`.
///
/// Returns an iterator to any elements of `source` that didn't fit in the
/// buffer, and an index into the buffer one past the last byte written.
///
/// - Precondition: The memory in `startIndex..<(source.count *
/// MemoryLayout<T>.stride)` is uninitialized or initialized to a
/// trivial type.
///
/// - Precondition: The buffer must contain sufficient memory to
/// accommodate at least `source.underestimateCount` elements.
///
/// - Postcondition: The memory at `self[startIndex..<returned index]
/// is bound to type `T`.
///
/// - Postcondition: The `T` values at `self[startIndex..<returned index]`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it fair game to refer to initializedUpTo here?

/// are initialized.
///
// TODO: Optimize where `C` is a `ContiguousArrayBuffer`.
public func initializeMemory<S: Sequence>(
as: S.Iterator.Element.Type, from source: S
) -> (unwritten: S.Iterator, initializedUpTo: Index) {

var it = source.makeIterator()
var idx = startIndex
let elementStride = MemoryLayout<S.Iterator.Element>.stride

// This has to be a debug precondition due to the cost of walking over some collections.
_debugPrecondition(source.underestimatedCount <= (count / elementStride),
"insufficient space to accommodate source.underestimatedCount elements")
guard let base = baseAddress else {
// this can be a precondition since only an invalid argument should be costly
_precondition(source.underestimatedCount == 0, "no memory available to initialize from source")
return (it, startIndex)
}

for p in stride(from: base,
// only advance to as far as the last element that will fit
to: base + count - elementStride + 1,
by: elementStride
) {
// underflow is permitted – e.g. a sequence into
// the spare capacity of an Array buffer
guard let x = it.next() else { break }
p.initializeMemory(as: S.Iterator.Element.self, to: x)
formIndex(&idx, offsetBy: elementStride)
}

return (it, idx)
}
% end # mutable

let _position, _end: Unsafe${Mutable}RawPointer?
}

Expand Down
3 changes: 2 additions & 1 deletion stdlib/public/core/UnsafeRawPointer.swift.gyb
Expand Up @@ -294,7 +294,8 @@ public struct Unsafe${Mutable}RawPointer : Strideable, Hashable, _Pointer {
/// - Postcondition: The `T` values at `self..<self + source.count *
/// MemoryLayout<T>.stride` are initialized.
///
/// TODO: Optimize where `C` is a `ContiguousArrayBuffer`.
// This is fundamentally unsafe since collections can underreport their count.
@available(*, deprecated, message: "it will be removed in Swift 4.0. Please use 'UnsafeMutableRawBufferPointer.initialize(from:)' instead")
@discardableResult
public func initializeMemory<C : Collection>(
as: C.Iterator.Element.Type, from source: C
Expand Down
53 changes: 53 additions & 0 deletions test/stdlib/UnsafeRawBufferPointer.swift
Expand Up @@ -75,6 +75,59 @@ UnsafeRawBufferPointerTestSuite.test("initFromArray") {
expectEqual(array2, array1)
}

UnsafeRawBufferPointerTestSuite.test("initializeMemory(as:from:).underflow") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused by the underflow terminology here. This sequence still overflows the buffer right? Isn't it an overflow with underestimated count?

I don't see a test case for actual underflow!

let buffer = UnsafeMutableRawBufferPointer.allocate(count: 30)
defer { buffer.deallocate() }
let source = stride(from: 5 as Int64, to: 0, by: -1)
var (it,idx) = buffer.initializeMemory(as: Int64.self, from: source)
expectEqual(it.next()!, 2)
expectEqual(idx, 24)
([5, 4, 3] as [Int64]).withUnsafeBytes {
expectEqualSequence($0,buffer[0..<idx])
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this testing the situation where the underEstimate is actually too small? (Not sure how accurate stride is)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but that's allowed (it is an underestimate after all). Your point about testing exactly-rightly-sized buffers too is taken tho.


UnsafeRawBufferPointerTestSuite.test("initializeMemory(as:from:).overflow") {
let buffer = UnsafeMutableRawBufferPointer.allocate(count: 30)
defer { buffer.deallocate() }
let source: [Int64] = [5, 4, 3, 2, 1]
if _isDebugAssertConfiguration() {
expectCrashLater()
}
var (it, idx) = buffer.initializeMemory(as: Int64.self, from: source)
expectEqual(it.next()!, 2)
expectEqual(idx, 24)
([5, 4, 3] as [Int64]).withUnsafeBytes {
expectEqualSequence($0,buffer[0..<idx])
}
}

UnsafeRawBufferPointerTestSuite.test("initializeMemory(as:from:).exact") {
let buffer = UnsafeMutableRawBufferPointer.allocate(count: 24)
defer { buffer.deallocate() }
let source: [Int64] = [5, 4, 3]
var (it, idx) = buffer.initializeMemory(as: Int64.self, from: source)
expectNil(it.next())
expectEqual(idx, buffer.endIndex)
source.withUnsafeBytes { expectEqualSequence($0,buffer) }
}

UnsafeRawBufferPointerTestSuite.test("initializeMemory(as:from:).invalidNilPtr") {
let buffer = UnsafeMutableRawBufferPointer(start: nil, count: 0)
let source: [Int64] = [5, 4, 3, 2, 1]
expectCrashLater()
var (it, idx) = buffer.initializeMemory(as: Int64.self, from: source)
}

UnsafeRawBufferPointerTestSuite.test("initializeMemory(as:from:).validNilPtr") {
let buffer = UnsafeMutableRawBufferPointer(start: nil, count: 0)
let source: [Int64] = []
var (it, idx) = buffer.initializeMemory(as: Int64.self, from: source)
expectNil(it.next())
expectEqual(idx, source.endIndex)
}


// Directly test the byte Sequence produced by withUnsafeBytes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should have tests verifying both of the null pointer behaviours.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should have a test verifying the case where the iterator is exhausted with slack space. (the final sequence is empty)

Possibly a test that verifies that the "exactly enough space" case works (off by ones will getcha!).

UnsafeRawBufferPointerTestSuite.test("withUnsafeBytes.Sequence") {
let array1: [Int32] = [0, 1, 2, 3]
Expand Down