-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
slices: fix ZST slice iterators making up pointers; debug_assert alignment in from_raw_parts #52206
Conversation
r? @shepmaster (rust_highfive has picked a reviewer for you, use r? to override) |
src/libcore/slice/mod.rs
Outdated
@@ -581,7 +607,7 @@ impl<T> [T] { | |||
pub fn iter(&self) -> Iter<T> { | |||
unsafe { | |||
let p = if mem::size_of::<T>() == 0 { | |||
1 as *const _ | |||
NonNull::dangling().as_ptr() as *const _ |
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 makes me think that Iter::ptr
should be a NonNull<T>
instead of a *const T
...
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.
It could be. Same for end
. (Though I have plans for #42789 that would need tweaking if end
were to become a NonNull
.)
Do you want me to change that in this PR?
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 have no strong feelings about it. If it's easy and it's mostly lines that were changing anyway, might as well; if it's annoying or makes the PR a bunch longer, then probably not worth doing right now. (end
not being NonNull
is also totally fine by me.)
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.
It's annoying because tons of code here does raw pointer operations, so there'd be a lot of to_ptr
and new_unchecked
and so on.
The job Click to expand the log.
I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact |
I should add that -- given that ZST slices seem to have zero test coverage before my change -- I am somewhat worried I broke something somewhere... |
src/libcore/slice/mod.rs
Outdated
@@ -80,7 +80,7 @@ macro_rules! slice_offset { | |||
($ptr:expr, $by:expr) => {{ | |||
let ptr = $ptr; | |||
if size_from_ptr(ptr) == 0 { | |||
(ptr as *mut i8).wrapping_offset($by) as _ | |||
(ptr as *mut i8).wrapping_offset($by.wrapping_mul(align_from_ptr(ptr) as isize)) as _ |
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.
Is there enough address space to do this, given the capacities claimed by RawVec
? I can apparently make a usize::MAX
-length Vec
of a align-8 ZST: https://play.rust-lang.org/?gist=c0b3d266bfbd34448d97e00f1408c946&version=stable&mode=release&edition=2015
Should the following change to !0 / mem::align_of::<T>()
?
Lines 215 to 221 in 2c1a715
pub fn cap(&self) -> usize { | |
if mem::size_of::<T>() == 0 { | |
!0 | |
} else { | |
self.cap | |
} | |
} |
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.
Ah, good point.
Is it enough to change that in raw_vec
or are there other ways to create huge slices?
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.
Doesn't this problem disappear if we fix #42789 by using start
as a real non-fabricated pointer and end
as a counter? I assume that's the plan you mentioned, so if we're likely doing that anyway, maybe avoid some churn in user visible behavior by going straight to that solution.
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 it does. In fact I am working on that next step already. I thought it would be easier for reviewers etc. to split this into two PRs, but if you prefer I can certainly finish that up and add it here (or make a new PR because there are going to be drastically more changes).
All right, I added "no longer make up pointers" to this PR. Instead of just putting the length into This is based on @bluss's work in #46223. In that PR, quite a few benchmarks were done to make sure this does not regress performance. How would I run these benchmarks? |
src/libcore/slice/mod.rs
Outdated
// iterator, this works for both ZST and non-ZST. | ||
// For a ZST we would usually do `self.end = self.ptr`, but since | ||
// we will not give out an address any more after this there is no | ||
// way to observe the difference. |
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.
Couldn't I still get an address from .as_slice().as_ptr()
?
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.
Hm... okay it is observable but only as a raw pointer, not as a reference. So there's no soundness problem.
I can still change it, if you prefer.
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.
Up to you; the comment tweak is probably fine since I'm not sure why anyone would be looking at the address of a post-iteration slice anyway.
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.
Okay, then I'd prefer to keep it this way.
src/libcore/slice/mod.rs
Outdated
#[inline(always)] | ||
unsafe fn post_inc_start(&mut self, offset: isize) -> * $raw_mut T { | ||
if mem::size_of::<T>() == 0 { | ||
self.end = (self.end as isize).wrapping_sub(offset) as * $raw_mut T; |
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.
wrapping_sub
looks like a typo in a method named inc
?
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 thought so too at first, but no, for ZST slices the start pointer should not change as the iterator advances, and this wrapping_sub is decrementing the length. However, since it's apparently somewhat subtle, there should be a comment spelling this out.
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.
Yeah in fact I used wrapping_add
at first and wondered why everything exploded. ;)
I'll add a comment.
Should I be doing benchmarks before we land this? How would I go about that? |
I think that checking the performance of slices would be a great idea, seeing as how fundamental they are. An accidental extra conditional or missed |
I've not done benchmarking like it was done for #46223 before (or any kind of benchmarking with Rust ever, really), so I'd need some pointers for how to do that. EDIT: Hm, seems like they just ran some crate's bench suite? But how does that get the old/new numbers...? |
Generally speaking you can probably write up some |
I don't have any experience coming up with bench tests, is there something I should be on the watch for? I noticed there is some stuff in
How do I get these tables with a "+/- %" column? |
Probably via cargo-benchcmp |
@bors: try Can't hurt at least to have a try build! |
⌛ Trying commit f567aea with merge 3e9fa0ba8d51c2a5fe7cf53be75b7e4bf1370bd4... |
src/libcore/slice/mod.rs
Outdated
// For a ZST we would usually do `self.end = self.ptr`, but since | ||
// we will not give out an reference any more after this there is no | ||
// way to observe the difference except for raw pointers. | ||
self.ptr = self.end; |
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.
Previously in next
there's an assume(!self.end.is_null())
but only if T
has a nonzero size. That implies to me that if T is a ZST it's possible for self.end
to be null (I think wraparound?). In this case, this is setting self.ptr
to null, right? Wouldn't that break the in-memory representation of Option<&[T]>
as well as the first assume
above in the call to next
?
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 good point, I don't think we can rule that out.
I guess I'll hope for the optimizer to remove the conditional here as well, then. :)
src/libcore/slice/mod.rs
Outdated
assume(!ptr.is_null()); | ||
|
||
let end = if mem::size_of::<T>() == 0 { | ||
(ptr as usize).wrapping_add(self.len()) as *mut _ |
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-so-long-ago the casting here between integers and pointers was found to inhibit optimizations when it comes to ZSTs, so would it be possible to use a similar strategy as that PR to avoid the pointer<->int conversions here?
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 just tried that, and it makes the tests fail...?!?
EDIT: Ah, it still increments in multiples of size:of::<T>
, which the docs don't really say...
☀️ Test successful - status-travis |
@rust-timer build 3e9fa0ba8d51c2a5fe7cf53be75b7e4bf1370bd4 |
Success: Queued 3e9fa0ba8d51c2a5fe7cf53be75b7e4bf1370bd4 with parent 3d5753f, comparison URL. |
Heh, looks like there's actual code that which creates unaligned slices? This is in the Should I back out the |
This also changes the IR for nth(), but the new IR actually looks nicer that the old (and it is one instruction shorter).
Also use ident, not expr, to avoid accidental side-effects
@bors r=alexcrichton |
📌 Commit 9fcf2c9 has been approved by |
slices: fix ZST slice iterators making up pointers; debug_assert alignment in from_raw_parts This fixes the problem that we are fabricating pointers out of thin air. I also managed to share more code between the mutable and shared iterators, while reducing the amount of macros. I am not sure how useful it really is to add a `debug_assert!` in libcore. Everybody gets a release version of that anyway, right? Is there at least a CI job that runs the test suite with a debug version? Fixes #42789
☀️ Test successful - status-appveyor, status-travis |
So what's the answer to this? If there isn't there should be one. |
We could run some tests (up to 30 minutes) in the |
Give that I got a CI failure when I tried to land this with the |
Ah right. We do test with Lines 55 to 76 in 40e4b6e
x86_64-gnu-debug job enables debug symbols in additional to debug assertions.
|
This turned out to be a clear win on a few benchmarks, of up to 3.9%: Nice job! |
Thanks! No idea how that happened, though. ;) |
This fixes the problem that we are fabricating pointers out of thin air. I also managed to share more code between the mutable and shared iterators, while reducing the amount of macros.
I am not sure how useful it really is to add a
debug_assert!
in libcore. Everybody gets a release version of that anyway, right? Is there at least a CI job that runs the test suite with a debug version?Fixes #42789