Skip to content
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

alloc: add some try_* methods Rust-for-Linux needs #86938

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions library/alloc/src/collections/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ pub use linked_list::LinkedList;
#[doc(no_inline)]
pub use vec_deque::VecDeque;

#[cfg(not(no_global_oom_handling))]
use crate::alloc::handle_alloc_error;
use crate::alloc::{Layout, LayoutError};
#[cfg(not(no_global_oom_handling))]
use crate::raw_vec::capacity_overflow;
use core::fmt::Display;

/// The error type for `try_reserve` methods.
Expand Down Expand Up @@ -105,6 +109,19 @@ pub enum TryReserveErrorKind {
},
}

#[cfg(not(no_global_oom_handling))]
impl TryReserveError {
/// Panic, or worse, according to the global handlers for each case.
pub(crate) fn handle(self) -> ! {
Comment on lines +114 to +115
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be useful to expose this outside the crate, but I didn't get into that for now.

match self {
TryReserveErrorKind::CapacityOverflow => capacity_overflow(),
TryReserveErrorKind::AllocError { layout, non_exhaustive: () } => {
handle_alloc_error(layout)
}
}
}
}

#[unstable(
feature = "try_reserve_kind",
reason = "Uncertain how much info should be exposed",
Expand Down
1 change: 1 addition & 0 deletions library/alloc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
#![feature(iter_zip)]
#![feature(lang_items)]
#![feature(layout_for_ptr)]
#![feature(more_fallible_allocation_methods)]
#![feature(negative_impls)]
#![feature(never_type)]
#![feature(nll)]
Expand Down
128 changes: 83 additions & 45 deletions library/alloc/src/raw_vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ use crate::collections::TryReserveErrorKind::*;
#[cfg(test)]
mod tests;

#[cfg(not(no_global_oom_handling))]
enum AllocInit {
/// The contents of the new memory are uninitialized.
Uninitialized,
Expand Down Expand Up @@ -94,6 +93,16 @@ impl<T> RawVec<T, Global> {
Self::with_capacity_in(capacity, Global)
}

/// Tries to create a `RawVec` (on the system heap) with exactly the
/// capacity and alignment requirements for a `[T; capacity]`. This is
/// equivalent to calling `RawVec::new` when `capacity` is `0` or `T` is
/// zero-sized. Note that if `T` is zero-sized this means you will
/// *not* get a `RawVec` with the requested capacity.
#[inline]
pub fn try_with_capacity(capacity: usize) -> Result<Self, TryReserveError> {
Self::try_with_capacity_in(capacity, Global)
}

/// Like `with_capacity`, but guarantees the buffer is zeroed.
#[cfg(not(no_global_oom_handling))]
#[must_use]
Expand All @@ -102,6 +111,12 @@ impl<T> RawVec<T, Global> {
Self::with_capacity_zeroed_in(capacity, Global)
}

/// Like `try_with_capacity`, but guarantees a successfully allocated buffer is zeroed.
#[inline]
pub fn try_with_capacity_zeroed(capacity: usize) -> Result<Self, TryReserveError> {
Self::try_with_capacity_zeroed_in(capacity, Global)
}

/// Reconstitutes a `RawVec` from a pointer and capacity.
///
/// # Safety
Expand Down Expand Up @@ -146,6 +161,13 @@ impl<T, A: Allocator> RawVec<T, A> {
Self::allocate_in(capacity, AllocInit::Uninitialized, alloc)
}

/// Like `try_with_capacity`, but parameterized over the choice of
/// allocator for the returned `RawVec`.
#[inline]
pub fn try_with_capacity_in(capacity: usize, alloc: A) -> Result<Self, TryReserveError> {
Self::try_allocate_in(capacity, AllocInit::Uninitialized, alloc)
}

/// Like `with_capacity_zeroed`, but parameterized over the choice
/// of allocator for the returned `RawVec`.
#[cfg(not(no_global_oom_handling))]
Expand All @@ -154,6 +176,13 @@ impl<T, A: Allocator> RawVec<T, A> {
Self::allocate_in(capacity, AllocInit::Zeroed, alloc)
}

/// Like `with_capacity_zeroed`, but parameterized over the choice
/// of allocator for the returned `RawVec`.
#[inline]
pub fn try_with_capacity_zeroed_in(capacity: usize, alloc: A) -> Result<Self, TryReserveError> {
Self::try_allocate_in(capacity, AllocInit::Zeroed, alloc)
}

/// Converts a `Box<[T]>` into a `RawVec<T>`.
pub fn from_box(slice: Box<[T], A>) -> Self {
unsafe {
Expand Down Expand Up @@ -190,34 +219,37 @@ impl<T, A: Allocator> RawVec<T, A> {

#[cfg(not(no_global_oom_handling))]
fn allocate_in(capacity: usize, init: AllocInit, alloc: A) -> Self {
match Self::try_allocate_in(capacity, init, alloc) {
Ok(r) => r,
Err(e) => e.handle(),
}
Comment on lines +222 to +225
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw some comment about unwrap_or_else slowing doing the LLVM portion of compilation so I didn't use it, but if that is no longer the case I am happy to change these.

}

fn try_allocate_in(
capacity: usize,
init: AllocInit,
alloc: A,
) -> Result<Self, TryReserveError> {
if mem::size_of::<T>() == 0 {
Self::new_in(alloc)
} else {
// We avoid `unwrap_or_else` here because it bloats the amount of
// LLVM IR generated.
let layout = match Layout::array::<T>(capacity) {
Ok(layout) => layout,
Err(_) => capacity_overflow(),
};
match alloc_guard(layout.size()) {
Ok(_) => {}
Err(_) => capacity_overflow(),
}
let result = match init {
AllocInit::Uninitialized => alloc.allocate(layout),
AllocInit::Zeroed => alloc.allocate_zeroed(layout),
};
let ptr = match result {
Ok(ptr) => ptr,
Err(_) => handle_alloc_error(layout),
};

Self {
ptr: unsafe { Unique::new_unchecked(ptr.cast().as_ptr()) },
cap: Self::capacity_from_bytes(ptr.len()),
alloc,
}
return Ok(Self::new_in(alloc));
}

let layout = Layout::array::<T>(capacity)?;
alloc_guard(layout.size())?;
let result = match init {
AllocInit::Uninitialized => alloc.allocate(layout),
AllocInit::Zeroed => alloc.allocate_zeroed(layout),
};
let ptr = match result {
Ok(ptr) => ptr,
Err(_) => return Err(TryReserveErrorKind::AllocError { layout, non_exhaustive: () }),
};

Ok(Self {
ptr: unsafe { Unique::new_unchecked(ptr.cast().as_ptr()) },
cap: Self::capacity_from_bytes(ptr.len()),
alloc,
})
}

/// Reconstitutes a `RawVec` from a pointer, capacity, and allocator.
Expand Down Expand Up @@ -394,7 +426,29 @@ impl<T, A: Allocator> RawVec<T, A> {
/// Aborts on OOM.
#[cfg(not(no_global_oom_handling))]
pub fn shrink_to_fit(&mut self, amount: usize) {
handle_reserve(self.shrink(amount));
handle_reserve(self.try_shrink_to_fit(amount));
}

/// Tries to shrink the allocation down to the specified amount. If the given amount
/// is 0, actually completely deallocates.
///
/// # Panics
///
/// Panics if the given amount is *larger* than the current capacity.
pub fn try_shrink_to_fit(&mut self, amount: usize) -> Result<(), TryReserveError> {
assert!(amount <= self.capacity(), "Tried to shrink to a larger capacity");

let (ptr, layout) = if let Some(mem) = self.current_memory() { mem } else { return Ok(()) };
let new_size = amount * mem::size_of::<T>();

let ptr = unsafe {
let new_layout = Layout::from_size_align_unchecked(new_size, layout.align());
self.alloc
.shrink(ptr, layout, new_layout)
.map_err(|_| AllocError { layout: new_layout, non_exhaustive: () })?
};
self.set_ptr(ptr);
Ok(())
}
}

Expand Down Expand Up @@ -466,22 +520,6 @@ impl<T, A: Allocator> RawVec<T, A> {
self.set_ptr(ptr);
Ok(())
}

fn shrink(&mut self, amount: usize) -> Result<(), TryReserveError> {
assert!(amount <= self.capacity(), "Tried to shrink to a larger capacity");

let (ptr, layout) = if let Some(mem) = self.current_memory() { mem } else { return Ok(()) };
let new_size = amount * mem::size_of::<T>();

let ptr = unsafe {
let new_layout = Layout::from_size_align_unchecked(new_size, layout.align());
self.alloc
.shrink(ptr, layout, new_layout)
.map_err(|_| AllocError { layout: new_layout, non_exhaustive: () })?
};
self.set_ptr(ptr);
Ok(())
}
}

// This function is outside `RawVec` to minimize compile times. See the comment
Expand Down Expand Up @@ -558,6 +596,6 @@ fn alloc_guard(alloc_size: usize) -> Result<(), TryReserveError> {
// ensure that the code generation related to these panics is minimal as there's
// only one location which panics rather than a bunch throughout the module.
#[cfg(not(no_global_oom_handling))]
fn capacity_overflow() -> ! {
pub(crate) fn capacity_overflow() -> ! {
panic!("capacity overflow");
}
97 changes: 96 additions & 1 deletion library/alloc/src/slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@ use core::mem::size_of;
use core::ptr;

use crate::alloc::Allocator;
#[cfg(not(no_global_oom_handling))]
use crate::alloc::Global;
#[cfg(not(no_global_oom_handling))]
use crate::borrow::ToOwned;
use crate::boxed::Box;
use crate::collections::TryReserveError;
use crate::vec::Vec;

#[unstable(feature = "slice_range", issue = "76393")]
Expand Down Expand Up @@ -153,6 +153,7 @@ mod hack {
use core::alloc::Allocator;

use crate::boxed::Box;
use crate::collections::TryReserveError;
use crate::vec::Vec;

// We shouldn't add inline attribute to this since this is used in
Expand All @@ -172,13 +173,27 @@ mod hack {
T::to_vec(s, alloc)
}

#[inline]
pub fn try_to_vec<T: TryConvertVec, A: Allocator>(
s: &[T],
alloc: A,
) -> Result<Vec<T, A>, TryReserveError> {
T::try_to_vec(s, alloc)
}

#[cfg(not(no_global_oom_handling))]
pub trait ConvertVec {
fn to_vec<A: Allocator>(s: &[Self], alloc: A) -> Vec<Self, A>
where
Self: Sized;
}

pub trait TryConvertVec {
fn try_to_vec<A: Allocator>(s: &[Self], alloc: A) -> Result<Vec<Self, A>, TryReserveError>
where
Self: Sized;
}

#[cfg(not(no_global_oom_handling))]
impl<T: Clone> ConvertVec for T {
#[inline]
Expand Down Expand Up @@ -231,6 +246,45 @@ mod hack {
v
}
}

impl<T: Clone> TryConvertVec for T {
#[inline]
default fn try_to_vec<A: Allocator>(
s: &[Self],
alloc: A,
) -> Result<Vec<Self, A>, TryReserveError> {
struct DropGuard<'a, T, A: Allocator> {
vec: &'a mut Vec<T, A>,
num_init: usize,
}
impl<'a, T, A: Allocator> Drop for DropGuard<'a, T, A> {
#[inline]
fn drop(&mut self) {
// SAFETY:
// items were marked initialized in the loop below
unsafe {
self.vec.set_len(self.num_init);
}
}
}
let mut vec = Vec::try_with_capacity_in(s.len(), alloc)?;
let mut guard = DropGuard { vec: &mut vec, num_init: 0 };
let slots = guard.vec.spare_capacity_mut();
// .take(slots.len()) is necessary for LLVM to remove bounds checks
// and has better codegen than zip.
for (i, b) in s.iter().enumerate().take(slots.len()) {
guard.num_init = i;
slots[i].write(b.clone());
}
core::mem::forget(guard);
// SAFETY:
// the vec was allocated and initialized above to at least this length.
unsafe {
vec.set_len(s.len());
}
Ok(vec)
}
}
}

#[lang = "slice_alloc"]
Expand Down Expand Up @@ -470,6 +524,24 @@ impl<T> [T] {
self.to_vec_in(Global)
}

/// Tries to copy `self` into a new `Vec`.
///
/// # Examples
///
/// ```
/// let s = [10, 40, 30];
/// let x = s.try_to_vec().unwrap();
/// // Here, `s` and `x` can be modified independently.
/// ```
#[inline]
#[unstable(feature = "more_fallible_allocation_methods", issue = "86942")]
pub fn try_to_vec(&self) -> Result<Vec<T>, TryReserveError>
where
T: Clone,
{
self.try_to_vec_in(Global)
}

/// Copies `self` into a new `Vec` with an allocator.
///
/// # Examples
Expand All @@ -494,6 +566,29 @@ impl<T> [T] {
hack::to_vec(self, alloc)
}

/// Tries to copy `self` into a new `Vec` with an allocator.
///
/// # Examples
///
/// ```
/// #![feature(allocator_api)]
///
/// use std::alloc::System;
///
/// let s = [10, 40, 30];
/// let x = s.try_to_vec_in(System).unwrap();
/// // Here, `s` and `x` can be modified independently.
/// ```
#[inline]
#[unstable(feature = "more_fallible_allocation_methods", issue = "86942")]
pub fn try_to_vec_in<A: Allocator>(&self, alloc: A) -> Result<Vec<T, A>, TryReserveError>
where
T: Clone,
{
// N.B., see the `hack` module in this file for more details.
hack::try_to_vec(self, alloc)
}

/// Converts `self` into a vector without clones or allocation.
///
/// The resulting vector can be converted back into a box via
Expand Down
Loading