Remove allocation for ZSTs - #92
Baptistemontan wants to merge 9 commits into
Conversation
emilio
left a comment
There was a problem hiding this comment.
Curious, can you elaborate on what's the rationale for this? It seems somewhat complicated and having an array of ZSTs is already pretty edgecasey.
In general, I'd rather keep the differences between the gecko-ffi path and the other as small as possible.
|
Deliberately constructing a Vec of ZSTs is very niche, but it's actually pretty common when generics comes into play, deeply nested in data structures. This lib might be a bridge for Gecko but it's used a lot outside of that space by other people, and removing allocations for ZSTs is basically free. Well I'm that someone lol And for keeping the same paths, it could be enabled on both since ZSTs should'nt be passed to C++ anyway |
| unsafe fn reallocate(&mut self, new_cap: usize) { | ||
| debug_assert!(new_cap > 0); | ||
| debug_assert!(!Self::is_zst()); | ||
| if self.has_allocation() { |
There was a problem hiding this comment.
has_allocation still returns true for ZSTs, which seems rather footgunny.
There was a problem hiding this comment.
totally footgunny, I'm gonna update has_allocation.
But in the case of reallocate even if has_allocation return false it still take a bad path in case T is ZST.
| #[cold] | ||
| #[inline(never)] | ||
| fn drop_zsts<T>(this: &mut ThinVec<T>) { |
There was a problem hiding this comment.
Why is this cold? is_singleton is false for zst so aren't you forcing a function call for no reason?
There was a problem hiding this comment.
Honestly I was probably just following drop_non_singleton, but yeah you're correct, unlike singletons ZSTs don't have a branch, I should just put the drop in place inside the drop function
|
Humm, tests are going to fail for "const_new", I can't make |
|
I think we should just bump the MSRV, see #93. Can you rebase atop? |
…place inside the drop impl
56c9eb0 to
4edd4dd
Compare
|
@emilio done |
emilio
left a comment
There was a problem hiding this comment.
Thanks, mostly nits, looks good with this!
| } | ||
| } else { | ||
| // See the comment in with_capacity(). | ||
| let _ = padding::<T>(); |
There was a problem hiding this comment.
I was planning to disallow ZSTs in gecko-ffi mode altogether, see #94. I think you need to keep calling padding unconditionally.
| if old_len == self.capacity() { | ||
| if Self::is_zst() { | ||
| assert!(old_len < MAX_CAP); | ||
| } else if old_len == self.capacity() { |
There was a problem hiding this comment.
I think this would be cleaner like this:
if old_len == self.capacity() {
if Self::is_zst() {
capacity_overflow();
}
self.reserve(1);
}
That gives consistent panic messages regardless of ZST.
| self.set_len_non_singleton(old_len - 1); | ||
| Some(ptr::read(self.data_raw().add(old_len - 1))) | ||
| if Self::is_zst() { | ||
| Some(mem::zeroed()) |
There was a problem hiding this comment.
Do you know if this matches what Vec or so does? It seems ok-ish to me but worth double-checking.
There was a problem hiding this comment.
just checked, and Vec actually don't branch on ZSTs and just read from the pointer, I'll do that then, and my guess is that it's going to compile to the same thing as a pointer read of ZSTs are noop anyway
|
|
||
| assert!(idx <= old_len, "Index out of bounds"); | ||
| if Self::is_zst() { | ||
| assert!(old_len < MAX_CAP); |
There was a problem hiding this comment.
Same remark about reserve() and the panic message. In fact maybe reserve() should just have a if Self::is_zst() { capacity_overflow(); }? Then the specialness can be reduced.
There was a problem hiding this comment.
make sense, and with the ocmment about pop I checked Vec for that too and they don't have special handling of ZSTs either for insert, just write the value to the offseted ptr, so I'll reduce the special handling there too
| return; | ||
| } | ||
| // only way to get here is if min_cap == usize::MAX, which we can't handle. | ||
| if Self::is_zst() { |
There was a problem hiding this comment.
Ah, there we go, so I think some of the special cases elsewhere aren't really needed.
Baptistemontan
left a comment
There was a problem hiding this comment.
Alright I removed a good chunk of unnecessary zst checks (either unneeded or checked afterward anyway).
I think this should wait for #94 to merge, there might be some conflicts as those two PRs both adresses ZSTs
| } | ||
| } else { | ||
| // See the comment in with_capacity(). | ||
| let _ = padding::<T>(); |
| if old_len == self.capacity() { | ||
| if Self::is_zst() { | ||
| assert!(old_len < MAX_CAP); | ||
| } else if old_len == self.capacity() { |
| self.set_len_non_singleton(old_len - 1); | ||
| Some(ptr::read(self.data_raw().add(old_len - 1))) | ||
| if Self::is_zst() { | ||
| Some(mem::zeroed()) |
There was a problem hiding this comment.
just checked, and Vec actually don't branch on ZSTs and just read from the pointer, I'll do that then, and my guess is that it's going to compile to the same thing as a pointer read of ZSTs are noop anyway
|
|
||
| assert!(idx <= old_len, "Index out of bounds"); | ||
| if Self::is_zst() { | ||
| assert!(old_len < MAX_CAP); |
There was a problem hiding this comment.
make sense, and with the ocmment about pop I checked Vec for that too and they don't have special handling of ZSTs either for insert, just write the value to the offseted ptr, so I'll reduce the special handling there too
emilio
left a comment
There was a problem hiding this comment.
Last pass, I promise. Fairly minor things :)
| /// len must be != 0, this uses the `NonNull` to store a length, so the length must be stored offset by one. | ||
| /// This function expect the len to be already shifted | ||
| #[inline(always)] | ||
| const unsafe fn len_to_ptr_unchecked<T: Sized>(len: usize) -> NonNull<T> { |
There was a problem hiding this comment.
Worth a debug_assert!(len != 0); at least.
| // `Drop` impl, trippng an assertion along that code path causes a | ||
| // double panic. We duplicate the assertion here so that it is | ||
| // testable, | ||
|
|
| /// | ||
| /// // Only true **without** the gecko-ffi feature! | ||
| /// // assert_eq!(vec_units.capacity(), usize::MAX); | ||
| /// // assert_eq!(vec_units.capacity(), usize::MAX - 1); |
There was a problem hiding this comment.
Ok, I enabled this in the other PR so will need a trivial-ish rebase.
| debug_assert!( | ||
| len <= MAX_CAP, | ||
| "invalid set_len(usize::MAX) on ZST ThinVec (max cap is usize::MAX - 1)" | ||
| ); |
There was a problem hiding this comment.
I guess this assert isn't super useful, because len + 1 would debug-assert anyway. But fine to keep.
| fn is_singleton(&self) -> bool { | ||
| unsafe { self.ptr.as_ptr() as *const Header == &EMPTY_HEADER } | ||
| // could technicaly remove this branch | ||
| // but there is a 1/2^64 chance of the number of ZST being equal to &EMPTY_HEADER |
There was a problem hiding this comment.
I'd remove this comment, as given that chance you can't remove the branch, right? :)
| /// # Safety | ||
| /// | ||
| /// This function drop and deallocates the inner values of the `ThinVec`, | ||
| /// invariants are therefore brokens and the value must be considered dropped and should not be accessed again. |
| impl<T> MallocShallowSizeOf for ThinVec<T> { | ||
| fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { | ||
| if self.capacity() == 0 || self.uses_stack_allocated_buffer() { | ||
| if self.capacity() == 0 || self.uses_stack_allocated_buffer() || Self::is_zst() { |
There was a problem hiding this comment.
This could be simplified to if !self.has_allocation() {
Store the length as a
NonZero<usize>inside theNonNull<Header>, the pointer is actually never read for ZSTs, when one is needed just create a dangling one.The only problem is that now the length is off by one (as it can't be 0), this makes the maximum capacity
usize::MAX - 1instead, this might be a breaking change ?Did'nt enabled those optimizations for
feature = "gecko-ffi", as it can't be turned intonsTArray, tho from my understanding ZSTs are undefined in C++ so it would be UB to turn a ThinVec of ZSTs into a nsTArray anyway, but I prefer to get confirmation first