Skip to content

Commit 3a83165

Browse files
committed
std: Unsafe-away runtime checks in Vec
The `RawVec` type has a number of invariants that it upholds throughout its execution, and as a result many of the runtime checks imposed by using `Layout` in a "raw" fashion aren't actually necessary. For example a `RawVec`'s capacity is intended to always match the layout which "fits" the allocation, so we don't need any runtime checks when retrieving the current `Layout` for a vector. Consequently, this adds a safe `current_layout` function which internally uses the `from_size_align_unchecked` function. Along the same lines we know that most construction of new layouts will not overflow. All allocations in `RawVec` are kept below `isize::MAX` and valid alignments are also kept low enough that we're guaranteed that `Layout` for a doubled vector will never overflow and will always succeed construction. Consequently a few locations can use `from_size_align_unchecked` in addition when constructing the *new* layout to allocate (or reallocate), which allows for eliding some more runtime checks. Overall this should significant improve performance for an important function, `RawVec::double`. This commit removes four runtime jumps before `__rust_realloc` is called, as well as one after it's called.
1 parent fae60b3 commit 3a83165

File tree

1 file changed

+127
-76
lines changed

1 file changed

+127
-76
lines changed

src/liballoc/raw_vec.rs

+127-76
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
use allocator::{Alloc, Layout};
12-
use core::ptr::{self, Unique};
11+
use core::cmp;
1312
use core::mem;
13+
use core::ops::Drop;
14+
use core::ptr::{self, Unique};
1415
use core::slice;
15-
use heap::Heap;
16+
use heap::{Alloc, Layout, Heap};
1617
use super::boxed::Box;
17-
use core::ops::Drop;
18-
use core::cmp;
1918

2019
/// A low-level utility for more ergonomically allocating, reallocating, and deallocating
2120
/// a buffer of memory on the heap without having to worry about all the corner cases
@@ -222,6 +221,20 @@ impl<T, A: Alloc> RawVec<T, A> {
222221
&mut self.a
223222
}
224223

224+
fn current_layout(&self) -> Option<Layout> {
225+
if self.cap == 0 {
226+
None
227+
} else {
228+
// We have an allocated chunk of memory, so we can bypass runtime
229+
// checks to get our current layout.
230+
unsafe {
231+
let align = mem::align_of::<T>();
232+
let size = mem::size_of::<T>() * self.cap;
233+
Some(Layout::from_size_align_unchecked(size, align))
234+
}
235+
}
236+
}
237+
225238
/// Doubles the size of the type's backing allocation. This is common enough
226239
/// to want to do that it's easiest to just have a dedicated method. Slightly
227240
/// more efficient logic can be provided for this than the general case.
@@ -280,27 +293,40 @@ impl<T, A: Alloc> RawVec<T, A> {
280293
// 0, getting to here necessarily means the RawVec is overfull.
281294
assert!(elem_size != 0, "capacity overflow");
282295

283-
let (new_cap, ptr_res) = if self.cap == 0 {
284-
// skip to 4 because tiny Vec's are dumb; but not if that would cause overflow
285-
let new_cap = if elem_size > (!0) / 8 { 1 } else { 4 };
286-
let ptr_res = self.a.alloc_array::<T>(new_cap);
287-
(new_cap, ptr_res)
288-
} else {
289-
// Since we guarantee that we never allocate more than isize::MAX bytes,
290-
// `elem_size * self.cap <= isize::MAX` as a precondition, so this can't overflow
291-
let new_cap = 2 * self.cap;
292-
let new_alloc_size = new_cap * elem_size;
293-
alloc_guard(new_alloc_size);
294-
let ptr_res = self.a.realloc_array(self.ptr, self.cap, new_cap);
295-
(new_cap, ptr_res)
296-
};
297-
298-
// If allocate or reallocate fail, we'll get `null` back
299-
let uniq = match ptr_res {
300-
Err(err) => self.a.oom(err),
301-
Ok(uniq) => uniq,
296+
let (new_cap, uniq) = match self.current_layout() {
297+
Some(cur) => {
298+
// Since we guarantee that we never allocate more than
299+
// isize::MAX bytes, `elem_size * self.cap <= isize::MAX` as
300+
// a precondition, so this can't overflow. Additionally the
301+
// alignment will never be too large as to "not be
302+
// satisfiable", so `Layout::from_size_align` will always
303+
// return `Some`.
304+
//
305+
// tl;dr; we bypass runtime checks due to dynamic assertions
306+
// in this module, allowing us to use
307+
// `from_size_align_unchecked`.
308+
let new_cap = 2 * self.cap;
309+
let new_size = new_cap * elem_size;
310+
let new_layout = Layout::from_size_align_unchecked(new_size, cur.align());
311+
alloc_guard(new_size);
312+
let ptr_res = self.a.realloc(self.ptr.as_ptr() as *mut u8,
313+
cur,
314+
new_layout);
315+
match ptr_res {
316+
Ok(ptr) => (new_cap, Unique::new_unchecked(ptr as *mut T)),
317+
Err(e) => self.a.oom(e),
318+
}
319+
}
320+
None => {
321+
// skip to 4 because tiny Vec's are dumb; but not if that
322+
// would cause overflow
323+
let new_cap = if elem_size > (!0) / 8 { 1 } else { 4 };
324+
match self.a.alloc_array::<T>(new_cap) {
325+
Ok(ptr) => (new_cap, ptr),
326+
Err(e) => self.a.oom(e),
327+
}
328+
}
302329
};
303-
304330
self.ptr = uniq;
305331
self.cap = new_cap;
306332
}
@@ -323,21 +349,27 @@ impl<T, A: Alloc> RawVec<T, A> {
323349
pub fn double_in_place(&mut self) -> bool {
324350
unsafe {
325351
let elem_size = mem::size_of::<T>();
352+
let old_layout = match self.current_layout() {
353+
Some(layout) => layout,
354+
None => return false, // nothing to double
355+
};
326356

327357
// since we set the capacity to usize::MAX when elem_size is
328358
// 0, getting to here necessarily means the RawVec is overfull.
329359
assert!(elem_size != 0, "capacity overflow");
330360

331-
// Since we guarantee that we never allocate more than isize::MAX bytes,
332-
// `elem_size * self.cap <= isize::MAX` as a precondition, so this can't overflow
361+
// Since we guarantee that we never allocate more than isize::MAX
362+
// bytes, `elem_size * self.cap <= isize::MAX` as a precondition, so
363+
// this can't overflow.
364+
//
365+
// Similarly like with `double` above we can go straight to
366+
// `Layout::from_size_align_unchecked` as we know this won't
367+
// overflow and the alignment is sufficiently small.
333368
let new_cap = 2 * self.cap;
334-
let new_alloc_size = new_cap * elem_size;
335-
336-
alloc_guard(new_alloc_size);
337-
369+
let new_size = new_cap * elem_size;
370+
alloc_guard(new_size);
338371
let ptr = self.ptr() as *mut _;
339-
let old_layout = Layout::new::<T>().repeat(self.cap).unwrap().0;
340-
let new_layout = Layout::new::<T>().repeat(new_cap).unwrap().0;
372+
let new_layout = Layout::from_size_align_unchecked(new_size, old_layout.align());
341373
match self.a.grow_in_place(ptr, old_layout, new_layout) {
342374
Ok(_) => {
343375
// We can't directly divide `size`.
@@ -373,8 +405,6 @@ impl<T, A: Alloc> RawVec<T, A> {
373405
/// Aborts on OOM
374406
pub fn reserve_exact(&mut self, used_cap: usize, needed_extra_cap: usize) {
375407
unsafe {
376-
let elem_size = mem::size_of::<T>();
377-
378408
// NOTE: we don't early branch on ZSTs here because we want this
379409
// to actually catch "asking for more than usize::MAX" in that case.
380410
// If we make it past the first branch then we are guaranteed to
@@ -388,21 +418,22 @@ impl<T, A: Alloc> RawVec<T, A> {
388418

389419
// Nothing we can really do about these checks :(
390420
let new_cap = used_cap.checked_add(needed_extra_cap).expect("capacity overflow");
391-
let new_alloc_size = new_cap.checked_mul(elem_size).expect("capacity overflow");
392-
alloc_guard(new_alloc_size);
393-
394-
let result = if self.cap == 0 {
395-
self.a.alloc_array::<T>(new_cap)
396-
} else {
397-
self.a.realloc_array(self.ptr, self.cap, new_cap)
421+
let new_layout = match Layout::array::<T>(new_cap) {
422+
Some(layout) => layout,
423+
None => panic!("capacity overflow"),
398424
};
399-
400-
// If allocate or reallocate fail, we'll get `null` back
401-
let uniq = match result {
402-
Err(err) => self.a.oom(err),
403-
Ok(uniq) => uniq,
425+
alloc_guard(new_layout.size());
426+
let res = match self.current_layout() {
427+
Some(layout) => {
428+
let old_ptr = self.ptr.as_ptr() as *mut u8;
429+
self.a.realloc(old_ptr, layout, new_layout)
430+
}
431+
None => self.a.alloc(new_layout),
432+
};
433+
let uniq = match res {
434+
Ok(ptr) => Unique::new_unchecked(ptr as *mut T),
435+
Err(e) => self.a.oom(e),
404436
};
405-
406437
self.ptr = uniq;
407438
self.cap = new_cap;
408439
}
@@ -411,17 +442,14 @@ impl<T, A: Alloc> RawVec<T, A> {
411442
/// Calculates the buffer's new size given that it'll hold `used_cap +
412443
/// needed_extra_cap` elements. This logic is used in amortized reserve methods.
413444
/// Returns `(new_capacity, new_alloc_size)`.
414-
fn amortized_new_size(&self, used_cap: usize, needed_extra_cap: usize) -> (usize, usize) {
415-
let elem_size = mem::size_of::<T>();
445+
fn amortized_new_size(&self, used_cap: usize, needed_extra_cap: usize) -> usize {
416446
// Nothing we can really do about these checks :(
417447
let required_cap = used_cap.checked_add(needed_extra_cap)
418448
.expect("capacity overflow");
419449
// Cannot overflow, because `cap <= isize::MAX`, and type of `cap` is `usize`.
420450
let double_cap = self.cap * 2;
421451
// `double_cap` guarantees exponential growth.
422-
let new_cap = cmp::max(double_cap, required_cap);
423-
let new_alloc_size = new_cap.checked_mul(elem_size).expect("capacity overflow");
424-
(new_cap, new_alloc_size)
452+
cmp::max(double_cap, required_cap)
425453
}
426454

427455
/// Ensures that the buffer contains at least enough space to hold
@@ -489,21 +517,25 @@ impl<T, A: Alloc> RawVec<T, A> {
489517
return;
490518
}
491519

492-
let (new_cap, new_alloc_size) = self.amortized_new_size(used_cap, needed_extra_cap);
493-
// FIXME: may crash and burn on over-reserve
494-
alloc_guard(new_alloc_size);
520+
let new_cap = self.amortized_new_size(used_cap, needed_extra_cap);
495521

496-
let result = if self.cap == 0 {
497-
self.a.alloc_array::<T>(new_cap)
498-
} else {
499-
self.a.realloc_array(self.ptr, self.cap, new_cap)
522+
let new_layout = match Layout::array::<T>(new_cap) {
523+
Some(layout) => layout,
524+
None => panic!("capacity overflow"),
500525
};
501-
502-
let uniq = match result {
503-
Err(err) => self.a.oom(err),
504-
Ok(uniq) => uniq,
526+
// FIXME: may crash and burn on over-reserve
527+
alloc_guard(new_layout.size());
528+
let res = match self.current_layout() {
529+
Some(layout) => {
530+
let old_ptr = self.ptr.as_ptr() as *mut u8;
531+
self.a.realloc(old_ptr, layout, new_layout)
532+
}
533+
None => self.a.alloc(new_layout),
534+
};
535+
let uniq = match res {
536+
Ok(ptr) => Unique::new_unchecked(ptr as *mut T),
537+
Err(e) => self.a.oom(e),
505538
};
506-
507539
self.ptr = uniq;
508540
self.cap = new_cap;
509541
}
@@ -536,21 +568,24 @@ impl<T, A: Alloc> RawVec<T, A> {
536568
// Don't actually need any more capacity. If the current `cap` is 0, we can't
537569
// reallocate in place.
538570
// Wrapping in case they give a bad `used_cap`
539-
if self.cap().wrapping_sub(used_cap) >= needed_extra_cap || self.cap == 0 {
571+
let old_layout = match self.current_layout() {
572+
Some(layout) => layout,
573+
None => return false,
574+
};
575+
if self.cap().wrapping_sub(used_cap) >= needed_extra_cap {
540576
return false;
541577
}
542578

543-
let (new_cap, new_alloc_size) = self.amortized_new_size(used_cap, needed_extra_cap);
544-
// FIXME: may crash and burn on over-reserve
545-
alloc_guard(new_alloc_size);
579+
let new_cap = self.amortized_new_size(used_cap, needed_extra_cap);
546580

547581
// Here, `cap < used_cap + needed_extra_cap <= new_cap`
548582
// (regardless of whether `self.cap - used_cap` wrapped).
549583
// Therefore we can safely call grow_in_place.
550584

551585
let ptr = self.ptr() as *mut _;
552-
let old_layout = Layout::new::<T>().repeat(self.cap).unwrap().0;
553586
let new_layout = Layout::new::<T>().repeat(new_cap).unwrap().0;
587+
// FIXME: may crash and burn on over-reserve
588+
alloc_guard(new_layout.size());
554589
match self.a.grow_in_place(ptr, old_layout, new_layout) {
555590
Ok(_) => {
556591
self.cap = new_cap;
@@ -599,9 +634,24 @@ impl<T, A: Alloc> RawVec<T, A> {
599634
}
600635
} else if self.cap != amount {
601636
unsafe {
602-
match self.a.realloc_array(self.ptr, self.cap, amount) {
637+
// We know here that our `amount` is greater than zero. This
638+
// implies, via the assert above, that capacity is also greater
639+
// than zero, which means that we've got a current layout that
640+
// "fits"
641+
//
642+
// We also know that `self.cap` is greater than `amount`, and
643+
// consequently we don't need runtime checks for creating either
644+
// layout
645+
let old_size = elem_size * self.cap;
646+
let new_size = elem_size * amount;
647+
let align = mem::align_of::<T>();
648+
let old_layout = Layout::from_size_align_unchecked(old_size, align);
649+
let new_layout = Layout::from_size_align_unchecked(new_size, align);
650+
match self.a.realloc(self.ptr.as_ptr() as *mut u8,
651+
old_layout,
652+
new_layout) {
653+
Ok(p) => self.ptr = Unique::new_unchecked(p as *mut T),
603654
Err(err) => self.a.oom(err),
604-
Ok(uniq) => self.ptr = uniq,
605655
}
606656
}
607657
self.cap = amount;
@@ -631,10 +681,11 @@ impl<T, A: Alloc> RawVec<T, A> {
631681
/// Frees the memory owned by the RawVec *without* trying to Drop its contents.
632682
pub unsafe fn dealloc_buffer(&mut self) {
633683
let elem_size = mem::size_of::<T>();
634-
if elem_size != 0 && self.cap != 0 {
635-
let ptr = self.ptr() as *mut u8;
636-
let layout = Layout::new::<T>().repeat(self.cap).unwrap().0;
637-
self.a.dealloc(ptr, layout);
684+
if elem_size != 0 {
685+
if let Some(layout) = self.current_layout() {
686+
let ptr = self.ptr() as *mut u8;
687+
self.a.dealloc(ptr, layout);
688+
}
638689
}
639690
}
640691
}

0 commit comments

Comments
 (0)