Skip to content

Commit 6f3da25

Browse files
authored
Merge pull request #73 from emilio/auto-array-tweaks
gecko: Keep the auto-bit across relocations.
2 parents faa01eb + d3d7475 commit 6f3da25

File tree

1 file changed

+98
-83
lines changed

1 file changed

+98
-83
lines changed

src/lib.rs

Lines changed: 98 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,22 @@ mod impl_details {
178178
pub fn assert_size(x: usize) -> SizeType {
179179
x
180180
}
181+
182+
#[inline(always)]
183+
pub fn pack_capacity_and_auto(cap: SizeType, auto: bool) -> SizeType {
184+
debug_assert!(!auto);
185+
cap
186+
}
187+
188+
#[inline(always)]
189+
pub fn unpack_capacity(cap: SizeType) -> usize {
190+
cap
191+
}
192+
193+
#[inline(always)]
194+
pub fn is_auto(_: SizeType) -> bool {
195+
false
196+
}
181197
}
182198

183199
#[cfg(feature = "gecko-ffi")]
@@ -193,7 +209,7 @@ mod impl_details {
193209
// struct {
194210
// uint32_t mLength;
195211
// uint32_t mCapacity: 31;
196-
// uint32_t mIsAutoBuffer: 1;
212+
// uint32_t mIsAutoArray : 1;
197213
// }
198214
// ```
199215
//
@@ -213,15 +229,14 @@ mod impl_details {
213229

214230
pub const MAX_CAP: usize = i32::max_value() as usize;
215231

232+
// See kAutoTArrayHeaderOffset
233+
pub const AUTO_ARRAY_HEADER_OFFSET: usize = 8;
234+
216235
// Little endian: the auto bit is the high bit, and the capacity is
217236
// verbatim. So we just need to mask off the high bit. Note that
218237
// this masking is unnecessary when packing, because assert_size
219238
// guards against the high bit being set.
220239
#[cfg(target_endian = "little")]
221-
pub fn pack_capacity(cap: SizeType) -> SizeType {
222-
cap
223-
}
224-
#[cfg(target_endian = "little")]
225240
pub fn unpack_capacity(cap: SizeType) -> usize {
226241
(cap as usize) & !(1 << 31)
227242
}
@@ -238,10 +253,6 @@ mod impl_details {
238253
// shifted up one bit. Masking out the auto bit is unnecessary,
239254
// as rust shifts always shift in 0's for unsigned integers.
240255
#[cfg(target_endian = "big")]
241-
pub fn pack_capacity(cap: SizeType) -> SizeType {
242-
cap << 1
243-
}
244-
#[cfg(target_endian = "big")]
245256
pub fn unpack_capacity(cap: SizeType) -> usize {
246257
(cap >> 1) as usize
247258
}
@@ -323,41 +334,23 @@ impl Header {
323334
fn set_len(&mut self, len: usize) {
324335
self._len = assert_size(len);
325336
}
326-
}
327337

328-
#[cfg(feature = "gecko-ffi")]
329-
impl Header {
330338
fn cap(&self) -> usize {
331339
unpack_capacity(self._cap)
332340
}
333341

334-
fn set_cap(&mut self, cap: usize) {
342+
fn set_cap_and_auto(&mut self, cap: usize, is_auto: bool) {
335343
// debug check that our packing is working
336-
debug_assert_eq!(unpack_capacity(pack_capacity(cap as SizeType)), cap);
337-
// FIXME: this assert is busted because it reads uninit memory
338-
// debug_assert!(!self.uses_stack_allocated_buffer());
339-
340-
// NOTE: this always stores a cleared auto bit, because set_cap
341-
// is only invoked by Rust, and Rust doesn't create auto arrays.
342-
self._cap = pack_capacity(assert_size(cap));
343-
}
344-
345-
fn uses_stack_allocated_buffer(&self) -> bool {
346-
is_auto(self._cap)
347-
}
348-
}
349-
350-
#[cfg(not(feature = "gecko-ffi"))]
351-
impl Header {
352-
#[inline]
353-
#[allow(clippy::unnecessary_cast)]
354-
fn cap(&self) -> usize {
355-
self._cap as usize
344+
debug_assert_eq!(
345+
unpack_capacity(pack_capacity_and_auto(cap as SizeType, is_auto)),
346+
cap
347+
);
348+
self._cap = pack_capacity_and_auto(assert_size(cap), is_auto);
356349
}
357350

358351
#[inline]
359-
fn set_cap(&mut self, cap: usize) {
360-
self._cap = assert_size(cap);
352+
fn is_auto(&self) -> bool {
353+
is_auto(self._cap)
361354
}
362355
}
363356

@@ -445,7 +438,7 @@ fn layout<T>(cap: usize) -> Layout {
445438
/// # Panics
446439
///
447440
/// Panics if the required size overflows `isize::MAX`.
448-
fn header_with_capacity<T>(cap: usize) -> NonNull<Header> {
441+
fn header_with_capacity<T>(cap: usize, is_auto: bool) -> NonNull<Header> {
449442
debug_assert!(cap > 0);
450443
unsafe {
451444
let layout = layout::<T>(cap);
@@ -463,7 +456,7 @@ fn header_with_capacity<T>(cap: usize) -> NonNull<Header> {
463456
// "Infinite" capacity for zero-sized types:
464457
MAX_CAP as SizeType
465458
} else {
466-
assert_size(cap)
459+
pack_capacity_and_auto(assert_size(cap), is_auto)
467460
},
468461
},
469462
);
@@ -600,7 +593,7 @@ impl<T> ThinVec<T> {
600593
}
601594
} else {
602595
ThinVec {
603-
ptr: header_with_capacity::<T>(cap),
596+
ptr: header_with_capacity::<T>(cap, false),
604597
boo: PhantomData,
605598
}
606599
}
@@ -1208,13 +1201,32 @@ impl<T> ThinVec<T> {
12081201
pub fn shrink_to_fit(&mut self) {
12091202
let old_cap = self.capacity();
12101203
let new_cap = self.len();
1211-
if new_cap < old_cap {
1212-
if new_cap == 0 {
1213-
*self = ThinVec::new();
1214-
} else {
1215-
unsafe {
1216-
self.reallocate(new_cap);
1204+
if new_cap >= old_cap {
1205+
return;
1206+
}
1207+
#[cfg(feature = "gecko-ffi")]
1208+
unsafe {
1209+
let stack_buf = self.auto_array_header_mut();
1210+
if !stack_buf.is_null() && (*stack_buf).cap() >= new_cap {
1211+
// Try to switch to our auto-buffer.
1212+
if stack_buf == self.ptr.as_ptr() {
1213+
return;
12171214
}
1215+
stack_buf
1216+
.add(1)
1217+
.cast::<T>()
1218+
.copy_from_nonoverlapping(self.data_raw(), new_cap);
1219+
dealloc(self.ptr() as *mut u8, layout::<T>(old_cap));
1220+
self.ptr = NonNull::new_unchecked(stack_buf);
1221+
self.ptr.as_mut().set_len(new_cap);
1222+
return;
1223+
}
1224+
}
1225+
if new_cap == 0 {
1226+
*self = ThinVec::new();
1227+
} else {
1228+
unsafe {
1229+
self.reallocate(new_cap);
12181230
}
12191231
}
12201232
}
@@ -1690,10 +1702,10 @@ impl<T> ThinVec<T> {
16901702
if ptr.is_null() {
16911703
handle_alloc_error(layout::<T>(new_cap))
16921704
}
1693-
(*ptr).set_cap(new_cap);
1705+
(*ptr).set_cap_and_auto(new_cap, (*ptr).is_auto());
16941706
self.ptr = NonNull::new_unchecked(ptr);
16951707
} else {
1696-
let new_header = header_with_capacity::<T>(new_cap);
1708+
let new_header = header_with_capacity::<T>(new_cap, self.is_auto_array());
16971709

16981710
// If we get here and have a non-zero len, then we must be handling
16991711
// a gecko auto array, and we have items in a stack buffer. We shouldn't
@@ -1720,33 +1732,46 @@ impl<T> ThinVec<T> {
17201732
}
17211733
}
17221734

1723-
#[cfg(feature = "gecko-ffi")]
17241735
#[inline]
17251736
#[allow(unused_unsafe)]
17261737
fn is_singleton(&self) -> bool {
1727-
// NOTE: the tests will complain that this "unsafe" isn't needed, but it *IS*!
1728-
// In production this refers to an *extern static* which *is* unsafe to reference.
1729-
// In tests this refers to a local static because we don't have Firefox's codebase
1730-
// providing the symbol!
17311738
unsafe { self.ptr.as_ptr() as *const Header == &EMPTY_HEADER }
17321739
}
17331740

1734-
#[cfg(not(feature = "gecko-ffi"))]
1741+
#[cfg(feature = "gecko-ffi")]
17351742
#[inline]
1736-
fn is_singleton(&self) -> bool {
1737-
self.ptr.as_ptr() as *const Header == &EMPTY_HEADER
1743+
fn auto_array_header_mut(&mut self) -> *mut Header {
1744+
if !self.is_auto_array() {
1745+
return ptr::null_mut();
1746+
}
1747+
unsafe { (self as *mut Self).byte_add(AUTO_ARRAY_HEADER_OFFSET) as *mut Header }
17381748
}
17391749

17401750
#[cfg(feature = "gecko-ffi")]
17411751
#[inline]
1742-
fn has_allocation(&self) -> bool {
1743-
unsafe { !self.is_singleton() && !self.ptr.as_ref().uses_stack_allocated_buffer() }
1752+
fn auto_array_header(&self) -> *const Header {
1753+
if !self.is_auto_array() {
1754+
return ptr::null_mut();
1755+
}
1756+
unsafe { (self as *const Self).byte_add(AUTO_ARRAY_HEADER_OFFSET) as *const Header }
1757+
}
1758+
1759+
#[inline]
1760+
fn is_auto_array(&self) -> bool {
1761+
unsafe { self.ptr.as_ref().is_auto() }
1762+
}
1763+
1764+
#[inline]
1765+
fn uses_stack_allocated_buffer(&self) -> bool {
1766+
#[cfg(feature = "gecko-ffi")]
1767+
return self.auto_array_header() == self.ptr.as_ptr();
1768+
#[cfg(not(feature = "gecko-ffi"))]
1769+
return false;
17441770
}
17451771

1746-
#[cfg(not(feature = "gecko-ffi"))]
17471772
#[inline]
17481773
fn has_allocation(&self) -> bool {
1749-
!self.is_singleton()
1774+
!self.is_singleton() && !self.uses_stack_allocated_buffer()
17501775
}
17511776
}
17521777

@@ -1850,8 +1875,7 @@ impl<T> Drop for ThinVec<T> {
18501875
unsafe {
18511876
ptr::drop_in_place(&mut this[..]);
18521877

1853-
#[cfg(feature = "gecko-ffi")]
1854-
if this.ptr.as_ref().uses_stack_allocated_buffer() {
1878+
if this.uses_stack_allocated_buffer() {
18551879
return;
18561880
}
18571881

@@ -2764,7 +2788,7 @@ impl<I: Iterator> Drop for Splice<'_, I> {
27642788
}
27652789

27662790
#[cfg(feature = "gecko-ffi")]
2767-
#[repr(C)]
2791+
#[repr(C, align(8))]
27682792
struct AutoBuffer<T, const N: usize> {
27692793
header: Header,
27702794
buffer: mem::MaybeUninit<[T; N]>,
@@ -2790,6 +2814,7 @@ impl<T, const N: usize> AutoThinVec<T, N> {
27902814
std::mem::align_of::<T>() <= 8,
27912815
"Can't handle alignments greater than 8"
27922816
);
2817+
assert_eq!(std::mem::offset_of!(Self, buffer), AUTO_ARRAY_HEADER_OFFSET);
27932818
Self {
27942819
inner: ThinVec::new(),
27952820
buffer: AutoBuffer {
@@ -2807,6 +2832,7 @@ impl<T, const N: usize> AutoThinVec<T, N> {
28072832
/// need to make sure not to move the ThinVec manually via something like
28082833
/// `std::mem::take(&mut auto_vec)`.
28092834
pub fn as_mut_ptr(self: std::pin::Pin<&mut Self>) -> *mut ThinVec<T> {
2835+
debug_assert!(self.is_auto_array());
28102836
unsafe { &mut self.get_unchecked_mut().inner }
28112837
}
28122838

@@ -2817,31 +2843,14 @@ impl<T, const N: usize> AutoThinVec<T, N> {
28172843
this.buffer.header.set_len(0);
28182844
// TODO(emilio): Use NonNull::from_mut when msrv allows.
28192845
this.inner.ptr = NonNull::new_unchecked(&mut this.buffer.header);
2846+
debug_assert!(this.inner.is_auto_array());
2847+
debug_assert!(this.inner.uses_stack_allocated_buffer());
28202848
}
28212849

28222850
pub fn shrink_to_fit(self: std::pin::Pin<&mut Self>) {
2823-
if self.inner.is_singleton() {
2824-
return unsafe { self.shrink_to_fit_known_singleton() };
2825-
}
28262851
let this = unsafe { self.get_unchecked_mut() };
2827-
if !this.inner.has_allocation() {
2828-
return;
2829-
}
2830-
let len = this.len();
2831-
if len > N {
2832-
return this.inner.shrink_to_fit();
2833-
}
2834-
let old_header = this.inner.ptr();
2835-
let old_cap = this.inner.capacity();
2836-
unsafe {
2837-
(this.buffer.buffer.as_mut_ptr() as *mut T)
2838-
.copy_from_nonoverlapping(this.inner.data_raw(), len);
2839-
}
2840-
this.buffer.header.set_len(len);
2841-
unsafe {
2842-
this.inner.ptr = NonNull::new_unchecked(&mut this.buffer.header);
2843-
dealloc(old_header as *mut u8, layout::<T>(old_cap));
2844-
}
2852+
this.inner.shrink_to_fit();
2853+
debug_assert!(this.inner.is_auto_array());
28452854
}
28462855
}
28472856

@@ -4563,11 +4572,13 @@ mod std_tests {
45634572
}
45644573
*/
45654574

4566-
#[cfg(all(feature = "gecko-ffi"))]
4575+
#[cfg(feature = "gecko-ffi")]
45674576
#[test]
45684577
fn auto_t_array_basic() {
45694578
crate::auto_thin_vec!(let t: [u8; 10]);
45704579
assert_eq!(t.capacity(), 10);
4580+
assert!(t.is_auto_array());
4581+
assert!(t.uses_stack_allocated_buffer());
45714582
assert!(!t.has_allocation());
45724583
{
45734584
let inner = unsafe { &mut *t.as_mut().as_mut_ptr() };
@@ -4576,6 +4587,8 @@ mod std_tests {
45764587
}
45774588
}
45784589

4590+
assert!(t.is_auto_array());
4591+
assert!(!t.uses_stack_allocated_buffer());
45794592
assert_eq!(t.len(), 30);
45804593
assert!(t.has_allocation());
45814594
assert_eq!(t[5], 5);
@@ -4592,6 +4605,8 @@ mod std_tests {
45924605
assert!(t.has_allocation());
45934606
t.as_mut().shrink_to_fit();
45944607
assert!(!t.has_allocation());
4608+
assert!(t.is_auto_array());
4609+
assert!(t.uses_stack_allocated_buffer());
45954610
assert_eq!(t.capacity(), 10);
45964611
}
45974612

0 commit comments

Comments
 (0)