Skip to content

Commit 4f99f37

Browse files
committed
Auto merge of #50880 - glandium:oom, r=SimonSapin
OOM handling changes As discussed in #49668 (comment) and subsequent. This does have codegen implications. Even without the hooks, and with a handler that ignores the arguments, the compiler doesn't eliminate calling `rust_oom` with the `Layout`. Even if it managed to eliminate that, with the hooks, I don't know if the compiler would be able to figure out it can skip it if the hook is never set. A couple implementation notes: - I went with explicit enums rather than bools because it makes it clearer in callers what is being requested. - I didn't know what `feature` to put the hook setting functions behind. (and surprisingly, the compile went through without any annotation on the functions) - There's probably some bikeshedding to do on the naming. Cc: @SimonSapin, @sfackler
2 parents 20af72b + a4d899b commit 4f99f37

File tree

10 files changed

+206
-113
lines changed

10 files changed

+206
-113
lines changed

src/liballoc/alloc.rs

+7-6
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ unsafe fn exchange_malloc(size: usize, align: usize) -> *mut u8 {
115115
if !ptr.is_null() {
116116
ptr as *mut u8
117117
} else {
118-
oom()
118+
oom(layout)
119119
}
120120
}
121121
}
@@ -134,12 +134,13 @@ pub(crate) unsafe fn box_free<T: ?Sized>(ptr: Unique<T>) {
134134
}
135135

136136
#[rustc_allocator_nounwind]
137-
pub fn oom() -> ! {
138-
extern {
137+
pub fn oom(layout: Layout) -> ! {
138+
#[allow(improper_ctypes)]
139+
extern "Rust" {
139140
#[lang = "oom"]
140-
fn oom_impl() -> !;
141+
fn oom_impl(layout: Layout) -> !;
141142
}
142-
unsafe { oom_impl() }
143+
unsafe { oom_impl(layout) }
143144
}
144145

145146
#[cfg(test)]
@@ -154,7 +155,7 @@ mod tests {
154155
unsafe {
155156
let layout = Layout::from_size_align(1024, 1).unwrap();
156157
let ptr = Global.alloc_zeroed(layout.clone())
157-
.unwrap_or_else(|_| oom());
158+
.unwrap_or_else(|_| oom(layout));
158159

159160
let mut i = ptr.cast::<u8>().as_ptr();
160161
let end = i.offset(layout.size() as isize);

src/liballoc/arc.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ impl<T: ?Sized> Arc<T> {
553553
let layout = Layout::for_value(&*fake_ptr);
554554

555555
let mem = Global.alloc(layout)
556-
.unwrap_or_else(|_| oom());
556+
.unwrap_or_else(|_| oom(layout));
557557

558558
// Initialize the real ArcInner
559559
let inner = set_data_ptr(ptr as *mut T, mem.as_ptr() as *mut u8) as *mut ArcInner<T>;

src/liballoc/raw_vec.rs

+82-74
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,15 @@ impl<T, A: Alloc> RawVec<T, A> {
9696
NonNull::<T>::dangling().as_opaque()
9797
} else {
9898
let align = mem::align_of::<T>();
99+
let layout = Layout::from_size_align(alloc_size, align).unwrap();
99100
let result = if zeroed {
100-
a.alloc_zeroed(Layout::from_size_align(alloc_size, align).unwrap())
101+
a.alloc_zeroed(layout)
101102
} else {
102-
a.alloc(Layout::from_size_align(alloc_size, align).unwrap())
103+
a.alloc(layout)
103104
};
104105
match result {
105106
Ok(ptr) => ptr,
106-
Err(_) => oom(),
107+
Err(_) => oom(layout),
107108
}
108109
};
109110

@@ -318,7 +319,7 @@ impl<T, A: Alloc> RawVec<T, A> {
318319
new_size);
319320
match ptr_res {
320321
Ok(ptr) => (new_cap, ptr.cast().into()),
321-
Err(_) => oom(),
322+
Err(_) => oom(Layout::from_size_align_unchecked(new_size, cur.align())),
322323
}
323324
}
324325
None => {
@@ -327,7 +328,7 @@ impl<T, A: Alloc> RawVec<T, A> {
327328
let new_cap = if elem_size > (!0) / 8 { 1 } else { 4 };
328329
match self.a.alloc_array::<T>(new_cap) {
329330
Ok(ptr) => (new_cap, ptr.into()),
330-
Err(_) => oom(),
331+
Err(_) => oom(Layout::array::<T>(new_cap).unwrap()),
331332
}
332333
}
333334
};
@@ -389,37 +390,7 @@ impl<T, A: Alloc> RawVec<T, A> {
389390
pub fn try_reserve_exact(&mut self, used_cap: usize, needed_extra_cap: usize)
390391
-> Result<(), CollectionAllocErr> {
391392

392-
unsafe {
393-
// NOTE: we don't early branch on ZSTs here because we want this
394-
// to actually catch "asking for more than usize::MAX" in that case.
395-
// If we make it past the first branch then we are guaranteed to
396-
// panic.
397-
398-
// Don't actually need any more capacity.
399-
// Wrapping in case they gave a bad `used_cap`.
400-
if self.cap().wrapping_sub(used_cap) >= needed_extra_cap {
401-
return Ok(());
402-
}
403-
404-
// Nothing we can really do about these checks :(
405-
let new_cap = used_cap.checked_add(needed_extra_cap).ok_or(CapacityOverflow)?;
406-
let new_layout = Layout::array::<T>(new_cap).map_err(|_| CapacityOverflow)?;
407-
408-
alloc_guard(new_layout.size())?;
409-
410-
let res = match self.current_layout() {
411-
Some(layout) => {
412-
debug_assert!(new_layout.align() == layout.align());
413-
self.a.realloc(NonNull::from(self.ptr).as_opaque(), layout, new_layout.size())
414-
}
415-
None => self.a.alloc(new_layout),
416-
};
417-
418-
self.ptr = res?.cast().into();
419-
self.cap = new_cap;
420-
421-
Ok(())
422-
}
393+
self.reserve_internal(used_cap, needed_extra_cap, Fallible, Exact)
423394
}
424395

425396
/// Ensures that the buffer contains at least enough space to hold
@@ -443,9 +414,9 @@ impl<T, A: Alloc> RawVec<T, A> {
443414
///
444415
/// Aborts on OOM
445416
pub fn reserve_exact(&mut self, used_cap: usize, needed_extra_cap: usize) {
446-
match self.try_reserve_exact(used_cap, needed_extra_cap) {
417+
match self.reserve_internal(used_cap, needed_extra_cap, Infallible, Exact) {
447418
Err(CapacityOverflow) => capacity_overflow(),
448-
Err(AllocErr) => oom(),
419+
Err(AllocErr) => unreachable!(),
449420
Ok(()) => { /* yay */ }
450421
}
451422
}
@@ -467,37 +438,7 @@ impl<T, A: Alloc> RawVec<T, A> {
467438
/// The same as `reserve`, but returns on errors instead of panicking or aborting.
468439
pub fn try_reserve(&mut self, used_cap: usize, needed_extra_cap: usize)
469440
-> Result<(), CollectionAllocErr> {
470-
unsafe {
471-
// NOTE: we don't early branch on ZSTs here because we want this
472-
// to actually catch "asking for more than usize::MAX" in that case.
473-
// If we make it past the first branch then we are guaranteed to
474-
// panic.
475-
476-
// Don't actually need any more capacity.
477-
// Wrapping in case they give a bad `used_cap`
478-
if self.cap().wrapping_sub(used_cap) >= needed_extra_cap {
479-
return Ok(());
480-
}
481-
482-
let new_cap = self.amortized_new_size(used_cap, needed_extra_cap)?;
483-
let new_layout = Layout::array::<T>(new_cap).map_err(|_| CapacityOverflow)?;
484-
485-
// FIXME: may crash and burn on over-reserve
486-
alloc_guard(new_layout.size())?;
487-
488-
let res = match self.current_layout() {
489-
Some(layout) => {
490-
debug_assert!(new_layout.align() == layout.align());
491-
self.a.realloc(NonNull::from(self.ptr).as_opaque(), layout, new_layout.size())
492-
}
493-
None => self.a.alloc(new_layout),
494-
};
495-
496-
self.ptr = res?.cast().into();
497-
self.cap = new_cap;
498-
499-
Ok(())
500-
}
441+
self.reserve_internal(used_cap, needed_extra_cap, Fallible, Amortized)
501442
}
502443

503444
/// Ensures that the buffer contains at least enough space to hold
@@ -553,12 +494,12 @@ impl<T, A: Alloc> RawVec<T, A> {
553494
/// # }
554495
/// ```
555496
pub fn reserve(&mut self, used_cap: usize, needed_extra_cap: usize) {
556-
match self.try_reserve(used_cap, needed_extra_cap) {
497+
match self.reserve_internal(used_cap, needed_extra_cap, Infallible, Amortized) {
557498
Err(CapacityOverflow) => capacity_overflow(),
558-
Err(AllocErr) => oom(),
499+
Err(AllocErr) => unreachable!(),
559500
Ok(()) => { /* yay */ }
560-
}
561-
}
501+
}
502+
}
562503
/// Attempts to ensure that the buffer contains at least enough space to hold
563504
/// `used_cap + needed_extra_cap` elements. If it doesn't already have
564505
/// enough capacity, will reallocate in place enough space plus comfortable slack
@@ -670,14 +611,81 @@ impl<T, A: Alloc> RawVec<T, A> {
670611
old_layout,
671612
new_size) {
672613
Ok(p) => self.ptr = p.cast().into(),
673-
Err(_) => oom(),
614+
Err(_) => oom(Layout::from_size_align_unchecked(new_size, align)),
674615
}
675616
}
676617
self.cap = amount;
677618
}
678619
}
679620
}
680621

622+
enum Fallibility {
623+
Fallible,
624+
Infallible,
625+
}
626+
627+
use self::Fallibility::*;
628+
629+
enum ReserveStrategy {
630+
Exact,
631+
Amortized,
632+
}
633+
634+
use self::ReserveStrategy::*;
635+
636+
impl<T, A: Alloc> RawVec<T, A> {
637+
fn reserve_internal(
638+
&mut self,
639+
used_cap: usize,
640+
needed_extra_cap: usize,
641+
fallibility: Fallibility,
642+
strategy: ReserveStrategy,
643+
) -> Result<(), CollectionAllocErr> {
644+
unsafe {
645+
use alloc::AllocErr;
646+
647+
// NOTE: we don't early branch on ZSTs here because we want this
648+
// to actually catch "asking for more than usize::MAX" in that case.
649+
// If we make it past the first branch then we are guaranteed to
650+
// panic.
651+
652+
// Don't actually need any more capacity.
653+
// Wrapping in case they gave a bad `used_cap`.
654+
if self.cap().wrapping_sub(used_cap) >= needed_extra_cap {
655+
return Ok(());
656+
}
657+
658+
// Nothing we can really do about these checks :(
659+
let new_cap = match strategy {
660+
Exact => used_cap.checked_add(needed_extra_cap).ok_or(CapacityOverflow)?,
661+
Amortized => self.amortized_new_size(used_cap, needed_extra_cap)?,
662+
};
663+
let new_layout = Layout::array::<T>(new_cap).map_err(|_| CapacityOverflow)?;
664+
665+
alloc_guard(new_layout.size())?;
666+
667+
let res = match self.current_layout() {
668+
Some(layout) => {
669+
debug_assert!(new_layout.align() == layout.align());
670+
self.a.realloc(NonNull::from(self.ptr).as_opaque(), layout, new_layout.size())
671+
}
672+
None => self.a.alloc(new_layout),
673+
};
674+
675+
match (&res, fallibility) {
676+
(Err(AllocErr), Infallible) => oom(new_layout),
677+
_ => {}
678+
}
679+
680+
self.ptr = res?.cast().into();
681+
self.cap = new_cap;
682+
683+
Ok(())
684+
}
685+
}
686+
687+
}
688+
681689
impl<T> RawVec<T, Global> {
682690
/// Converts the entire buffer into `Box<[T]>`.
683691
///

src/liballoc/rc.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,7 @@ impl<T: ?Sized> Rc<T> {
668668
let layout = Layout::for_value(&*fake_ptr);
669669

670670
let mem = Global.alloc(layout)
671-
.unwrap_or_else(|_| oom());
671+
.unwrap_or_else(|_| oom(layout));
672672

673673
// Initialize the real RcBox
674674
let inner = set_data_ptr(ptr as *mut T, mem.as_ptr() as *mut u8) as *mut RcBox<T>;

src/libstd/alloc.rs

+47-3
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,59 @@
1313
#![unstable(issue = "32838", feature = "allocator_api")]
1414

1515
#[doc(inline)] #[allow(deprecated)] pub use alloc_crate::alloc::Heap;
16-
#[doc(inline)] pub use alloc_crate::alloc::{Global, oom};
16+
#[doc(inline)] pub use alloc_crate::alloc::{Global, Layout, oom};
1717
#[doc(inline)] pub use alloc_system::System;
1818
#[doc(inline)] pub use core::alloc::*;
1919

20+
use core::sync::atomic::{AtomicPtr, Ordering};
21+
use core::{mem, ptr};
22+
23+
static HOOK: AtomicPtr<()> = AtomicPtr::new(ptr::null_mut());
24+
25+
/// Registers a custom OOM hook, replacing any that was previously registered.
26+
///
27+
/// The OOM hook is invoked when an infallible memory allocation fails.
28+
/// The default hook prints a message to standard error and aborts the
29+
/// execution, but this behavior can be customized with the [`set_oom_hook`]
30+
/// and [`take_oom_hook`] functions.
31+
///
32+
/// The hook is provided with a `Layout` struct which contains information
33+
/// about the allocation that failed.
34+
///
35+
/// The OOM hook is a global resource.
36+
pub fn set_oom_hook(hook: fn(Layout) -> !) {
37+
HOOK.store(hook as *mut (), Ordering::SeqCst);
38+
}
39+
40+
/// Unregisters the current OOM hook, returning it.
41+
///
42+
/// *See also the function [`set_oom_hook`].*
43+
///
44+
/// If no custom hook is registered, the default hook will be returned.
45+
pub fn take_oom_hook() -> fn(Layout) -> ! {
46+
let hook = HOOK.swap(ptr::null_mut(), Ordering::SeqCst);
47+
if hook.is_null() {
48+
default_oom_hook
49+
} else {
50+
unsafe { mem::transmute(hook) }
51+
}
52+
}
53+
54+
fn default_oom_hook(layout: Layout) -> ! {
55+
rtabort!("memory allocation of {} bytes failed", layout.size())
56+
}
57+
2058
#[cfg(not(test))]
2159
#[doc(hidden)]
2260
#[lang = "oom"]
23-
pub extern fn rust_oom() -> ! {
24-
rtabort!("memory allocation failed");
61+
pub extern fn rust_oom(layout: Layout) -> ! {
62+
let hook = HOOK.load(Ordering::SeqCst);
63+
let hook: fn(Layout) -> ! = if hook.is_null() {
64+
default_oom_hook
65+
} else {
66+
unsafe { mem::transmute(hook) }
67+
};
68+
hook(layout)
2569
}
2670

2771
#[cfg(not(test))]

0 commit comments

Comments
 (0)