-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[stdlib] non-mutating range subscript setter on UnsafeMutableBufferPointer #12504
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
Conversation
c6354c9
to
479c94b
Compare
4223418
to
d72b1ac
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for picking this up! Couple of comments inline. This also needs some tests confirming it's working as expected against immutable buffer variables (and tests in general, if that isn't already isn't already covered by existing unit tests).
// FIXME: swift-3-indexing-model: tests. | ||
_writeBackMutableSlice(&self, bounds: bounds, slice: newValue) | ||
if newValue.count > 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be !newValue.isEmpty
. I realize it's not a big deal in this case since we're talking about integer-indexed collections here but we should never use count
to check for emptiness.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feel free to change UnsafeMutableRawBufferPointer
implementation as well.
if newValue.count > 0 { | ||
var sliceIndex = newValue.startIndex | ||
var index = bounds.lowerBound | ||
while index != bounds.upperBound && sliceIndex != newValue.endIndex { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given we know these are two unsafe buffers (unlike _writeBackMutableSlice
, which is generic) we ought to be able to just do a straight memory copy, once we've checked the count is the same. Something like...
baseAddress!.initialize(from: newValue.base.baseAddress!, count: bounds.count)
The only thing that would worry me about if this is correct is the possibility of overlapping assignment from self
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UnsafeMutablePointer.assign
supports overlapping ranges. That's the thing to use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, exactly but use assignment.
baseAddress!.assign(from: newValue.base.baseAddress!, count: bounds.count)
That does the correct forward or backward copy depending on the relative pointer positions.
_debugPrecondition(bounds.lowerBound >= startIndex) | ||
_debugPrecondition(bounds.upperBound <= endIndex) | ||
_debugPrecondition(bounds.count == newValue.count) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this one should be a full _precondition
(similar to the one found in _writeBackMutableSlice
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The analog line in UnsafeRawBufferPointer uses a _debugPrecondition
: https://github.com/apple/swift/blob/ffd34239a1b6f046346769a55ae8e717f64e13eb/stdlib/public/core/UnsafeRawBufferPointer.swift.gyb#L468
Is there a reason to make it stronger in the typed buffers, or should it be a _precondition
in both places?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reasoning behind _debugPrecondition
is that this is already an Unsafe
type and being at the the lowest level of abstraction will be used in performance critical code.
/// the range must be valid indices of the buffer. | ||
/// | ||
%if Mutable: | ||
/// - Complexity: O(1) for reading, O(N) for writing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see where the complexity bounds comes from, but I think it's misleading. It subtly reflects the fact that "reading" is deferred until the slice is used. And N
will likely be mistaken for the buffer count. Since there's nothing magic or unexpected happening here, why talk about complexity? @airspeedswift, is this something we need to document?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the same complexity that's documented at the protocol level, so we can remove it here. We only document on types when there's a deviation from the expectation (like Set.count
).
/// the range must be valid indices of the buffer. | ||
/// | ||
%if Mutable: | ||
/// - Complexity: O(1) for reading, O(N) for writing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the same complexity that's documented at the protocol level, so we can remove it here. We only document on types when there's a deviation from the expectation (like Set.count
).
%if Mutable: | ||
/// var streets = ["Adams", "Bryant", "Channing", "Douglas", "Evarts"] | ||
/// streets.withUnsafeMutableBufferPointer { buffer in | ||
/// let streetSlice = buffer[2..<buffer.endIndex] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adapting these docs! Just to keep everyone on their toes, we use four-space indentation in documentation examples, instead of the two used in code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh? I hadn't noticed!
Regarding tests: I had assumed that when the tests passed it had been covered. |
|
I just realized, the setter should account for the fact that a mutation of i.e.:
will generate first a call to |
Is the pointer identity not checked by the builtin function called by |
count: newValue.count) | ||
let source = newValue.base._position! + newValue.startIndex | ||
let destination = _position! + bounds.lowerBound | ||
if source != destination { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's appropriate to do this check, is there a reason not to do it directly as an else if
in assign
?
https://github.com/apple/swift/blob/e4236405279f42345e12eebbc023a3409a034b7a/stdlib/public/core/UnsafePointer.swift.gyb#L562-L564
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or even all the way down in array_copy_operation
?
https://github.com/apple/swift/blob/e4236405279f42345e12eebbc023a3409a034b7a/stdlib/public/runtime/Array.cpp#L86-L87
UnsafeMutableBufferPointer and UnsafeMutableRawBufferPointer both need a non-mutating |
Possibly somewhere in the runtime or libc. The point is, we don't even want to make that series of runtime calls. Checking in the |
7db7a1c
to
a9daabd
Compare
👍 |
@swift-ci please test |
Build failed |
Build failed |
The CollectionType validation test is expecting a particular crash message in |
The addition of debug messages seems fine to me. |
Upon looking at it again, the validation tests that failed are called "subscript(_: Range)/defaultImplementation". They currently work by modifying an array through |
e8869e2
to
26b3de1
Compare
8f7daf7
to
24ab86c
Compare
24ab86c
to
b0f66f8
Compare
Squashed commits. Do we want fewer still? |
Either way, 1 commit or 4 commits, is fine with me. |
OK then; to my eye it seems ready to merge. |
@swift-ci please test |
93912aa
to
e2b8520
Compare
@swift-ci please test |
Build failed |
@swift-ci please test source compatibility |
Build failed |
@swift-ci please smoke test compiler performance |
e2b8520
to
0fb0d51
Compare
@@ -580,21 +580,17 @@ CollectionTypeTests.test("subscript(_: Range<Index>)/writeback") { | |||
CollectionTypeTests.test("subscript(_: Range<Index>)/defaultImplementation/sliceTooLarge") | |||
.crashOutputMatches("Cannot replace a slice of a MutableCollection with a slice of a larger size") | |||
.code { | |||
var x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] | |||
var x = Slice(base: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], bounds: 0..<10) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed from MutableSlice
to Slice
. Conditional Conformances are nice.
@swift-ci please test |
@atrick Sorry to bother, but is this PR waiting on something else I'm not aware of? |
@glessard It looks like I was the first to give this a thumbs up but was waiting for @airspeedswift review. @lorentey, you last ran a successful test here, do you want to retest and merge? |
@swift-ci please test and merge |
Thanks |
In an apparent oversight (?), the setter for
UnsafeMutableBufferPointer
subscript(bounds: Range<Int>)
isn't marked asnonmutating
. Given thatUnsafeMutableRawBufferPointer
's equivalent isnonmutating
, here is a non-mutating implementation.I believe this shouldn't require swift-evolution involvement, but if this gets merged some new "un-mutated var" warnings may appear.
SR-3782 mentions this. Someone mentioned working on it months ago, but there did not appear to be a pull request.