Skip to content

Commit 28f6f5e

Browse files
authored
Add fallible try_collect to collections (#12505)
* Add fallible `try_collect` to collections This adds a new `TryCollect` extension trait to `wasmtime-core` which enables `iter.try_collect()` to fallibly collect into a `Vec<T>` or `Box<[T]>` while handling OOM. This is intended to serve as a replacement for the `new_boxed_slice_*` helpers in appropriate situations since it more closely matches what Wasmtime likely already does today where we throw `.collect()` on iterators. * Extend oom tests a bit more * Use a TryExtend trait * Fix oom tests
1 parent c09aa38 commit 28f6f5e

File tree

5 files changed

+386
-19
lines changed

5 files changed

+386
-19
lines changed

cranelift/bitset/src/compound.rs

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use crate::scalar::{self, ScalarBitSet, ScalarBitSetStorage};
44
use alloc::boxed::Box;
55
use core::{cmp, iter, mem};
6+
use wasmtime_core::alloc::{TryExtend, Vec};
67
use wasmtime_core::error::OutOfMemory;
78

89
/// A large bit set backed by dynamically-sized storage.
@@ -294,20 +295,11 @@ impl<T: ScalarBitSetStorage> CompoundBitSet<T> {
294295
// Don't make ridiculously small allocations.
295296
let to_grow = cmp::max(to_grow, 4);
296297

297-
let new_len = self.elems.len() + to_grow;
298-
match wasmtime_core::alloc::new_boxed_slice_from_iter_with_len(
299-
new_len,
300-
self.elems
301-
.iter()
302-
.copied()
303-
.chain(iter::repeat(ScalarBitSet::new())),
304-
) {
305-
Ok(new_elems) => {
306-
self.elems = new_elems;
307-
Ok(())
308-
}
309-
Err(e) => Err(e.unwrap_oom()),
310-
}
298+
let mut new_elems = Vec::from(mem::take(&mut self.elems));
299+
new_elems.reserve_exact(to_grow)?;
300+
new_elems.try_extend(iter::repeat(ScalarBitSet::new()).take(to_grow))?;
301+
self.elems = new_elems.into_boxed_slice()?;
302+
Ok(())
311303
}
312304

313305
/// Insert `i` into this bitset.

crates/core/src/alloc.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
mod arc;
44
mod boxed;
5+
mod try_collect;
56
mod try_new;
67
mod vec;
78

@@ -10,6 +11,7 @@ pub use boxed::{
1011
new_boxed_slice_from_fallible_iter, new_boxed_slice_from_iter,
1112
new_boxed_slice_from_iter_with_len, new_uninit_boxed_slice,
1213
};
14+
pub use try_collect::{TryCollect, TryExtend, TryFromIterator};
1315
pub use try_new::{TryNew, try_new};
1416
pub use vec::Vec;
1517

@@ -35,6 +37,32 @@ unsafe fn try_alloc(layout: Layout) -> Result<NonNull<u8>, OutOfMemory> {
3537
}
3638
}
3739

40+
/// Tries to reallocate a block of memory, returning `OutOfMemory` on failure.
41+
///
42+
/// Analogue of [`alloc::alloc::realloc`].
43+
///
44+
/// # Safety
45+
///
46+
/// Same as `alloc::alloc::realloc`: `ptr` must be allocated with `layout`,
47+
/// `layout` must be nonzero in size, and `new_size` must be nonzero and valid.
48+
#[inline]
49+
unsafe fn try_realloc(
50+
ptr: *mut u8,
51+
layout: Layout,
52+
new_size: usize,
53+
) -> Result<NonNull<u8>, OutOfMemory> {
54+
// Safety: same as our safety conditions.
55+
debug_assert!(layout.size() > 0);
56+
debug_assert!(new_size > 0);
57+
let ptr = unsafe { std_alloc::alloc::realloc(ptr, layout, new_size) };
58+
59+
if let Some(ptr) = NonNull::new(ptr) {
60+
Ok(ptr)
61+
} else {
62+
Err(OutOfMemory::new(new_size))
63+
}
64+
}
65+
3866
/// An extension trait for ignoring `OutOfMemory` errors.
3967
///
4068
/// Use this to unwrap a `Result<T, OutOfMemory>` into its inner `T` or
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
use crate::alloc::Vec;
2+
use crate::error::OutOfMemory;
3+
use std_alloc::boxed::Box;
4+
5+
/// Extension trait for an `Iterator` to fallibly collect into a container.
6+
pub trait TryCollect: Iterator {
7+
/// Attempts to collect the iterator `self` into `B`.
8+
///
9+
/// Same as [`Iterator::collect`] except returns OOM instead of aborting.
10+
fn try_collect<B, E>(self) -> Result<B, E>
11+
where
12+
B: TryFromIterator<Self::Item, E>,
13+
Self: Sized,
14+
{
15+
B::try_from_iter(self)
16+
}
17+
}
18+
19+
impl<I: Iterator> TryCollect for I {}
20+
21+
/// Analogue of [`FromIterator`] in the standard library, but used with
22+
/// [`TryCollect::try_collect`] instead.
23+
pub trait TryFromIterator<T, E>: Sized {
24+
/// Creates an intance of this collection from the `iter` provided.
25+
///
26+
/// Does not abort on OOM but insteads return an error.
27+
fn try_from_iter<I>(iter: I) -> Result<Self, E>
28+
where
29+
I: Iterator<Item = T>;
30+
}
31+
32+
impl<T> TryFromIterator<T, OutOfMemory> for Vec<T> {
33+
fn try_from_iter<I>(iter: I) -> Result<Self, OutOfMemory>
34+
where
35+
I: Iterator<Item = T>,
36+
{
37+
let mut result = Vec::with_capacity(iter.size_hint().0)?;
38+
for item in iter {
39+
result.push(item)?;
40+
}
41+
Ok(result)
42+
}
43+
}
44+
45+
impl<T> TryFromIterator<T, OutOfMemory> for Box<[T]> {
46+
fn try_from_iter<I>(iter: I) -> Result<Self, OutOfMemory>
47+
where
48+
I: Iterator<Item = T>,
49+
{
50+
let vec = Vec::try_from_iter(iter)?;
51+
vec.into_boxed_slice()
52+
}
53+
}
54+
55+
impl<T, E> TryFromIterator<Result<T, E>, E> for Vec<T>
56+
where
57+
E: From<OutOfMemory>,
58+
{
59+
fn try_from_iter<I>(iter: I) -> Result<Self, E>
60+
where
61+
I: Iterator<Item = Result<T, E>>,
62+
{
63+
let mut result = Vec::with_capacity(iter.size_hint().0)?;
64+
for item in iter {
65+
result.push(item?)?;
66+
}
67+
Ok(result)
68+
}
69+
}
70+
71+
impl<T, E> TryFromIterator<Result<T, E>, E> for Box<[T]>
72+
where
73+
E: From<OutOfMemory>,
74+
{
75+
fn try_from_iter<I>(iter: I) -> Result<Self, E>
76+
where
77+
I: Iterator<Item = Result<T, E>>,
78+
{
79+
let vec = iter.try_collect::<Vec<_>, E>()?;
80+
Ok(vec.into_boxed_slice()?)
81+
}
82+
}
83+
84+
/// Analogue of [`Extend`] except handles OOM conditions.
85+
pub trait TryExtend<T> {
86+
/// Extends `self` with the items from `iter`.
87+
///
88+
/// Returns an error if allocation fails while adding items to `self`. If an
89+
/// OOM happens then some items from `iter` may have been added to `self`
90+
/// already. On OOM no further items from the iterator will be consumed.
91+
fn try_extend<I>(&mut self, iter: I) -> Result<(), OutOfMemory>
92+
where
93+
I: IntoIterator<Item = T>;
94+
}
95+
96+
impl<T> TryExtend<T> for Vec<T> {
97+
fn try_extend<I>(&mut self, iter: I) -> Result<(), OutOfMemory>
98+
where
99+
I: IntoIterator<Item = T>,
100+
{
101+
let iter = iter.into_iter();
102+
self.reserve(iter.size_hint().0)?;
103+
for item in iter {
104+
self.push(item)?;
105+
}
106+
Ok(())
107+
}
108+
}
109+
110+
#[cfg(test)]
111+
mod tests {
112+
use super::{Box, TryCollect, TryExtend, Vec};
113+
use crate::error::{OutOfMemory, Result};
114+
115+
#[test]
116+
fn test_vec_collect() -> Result<(), OutOfMemory> {
117+
let v: Vec<i32> = (0..10).try_collect()?;
118+
assert_eq!(&*v, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
119+
Ok(())
120+
}
121+
122+
#[test]
123+
fn test_box_collect() -> Result<(), OutOfMemory> {
124+
let v: Box<[i32]> = (0..10).try_collect()?;
125+
assert_eq!(&*v, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
126+
Ok(())
127+
}
128+
129+
#[test]
130+
fn test_vec_result_collect() -> Result<()> {
131+
let v: Result<Vec<i32>> = [].into_iter().try_collect();
132+
assert!(v?.is_empty());
133+
134+
let v: Result<Vec<i32>> = [Ok(1), Ok(2)].into_iter().try_collect();
135+
assert_eq!(&*v?, &[1, 2]);
136+
137+
let v: Result<Vec<i32>> = [Ok(1), Err(crate::format_err!("hi"))]
138+
.into_iter()
139+
.try_collect();
140+
assert!(v.is_err());
141+
142+
let v: Result<Vec<i32>> = [Err(crate::format_err!("hi")), Ok(1)]
143+
.into_iter()
144+
.try_collect();
145+
assert!(v.is_err());
146+
Ok(())
147+
}
148+
149+
#[test]
150+
fn test_box_result_collect() -> Result<()> {
151+
let v: Result<Box<[i32]>> = [].into_iter().try_collect();
152+
assert!(v?.is_empty());
153+
154+
let v: Result<Box<[i32]>> = [Ok(1), Ok(2)].into_iter().try_collect();
155+
assert_eq!(&*v?, &[1, 2]);
156+
157+
let v: Result<Box<[i32]>> = [Ok(1), Err(crate::format_err!("hi"))]
158+
.into_iter()
159+
.try_collect();
160+
assert!(v.is_err());
161+
162+
let v: Result<Box<[i32]>> = [Err(crate::format_err!("hi")), Ok(1)]
163+
.into_iter()
164+
.try_collect();
165+
assert!(v.is_err());
166+
Ok(())
167+
}
168+
169+
#[test]
170+
fn test_try_extend() -> Result<(), OutOfMemory> {
171+
let mut vec = Vec::new();
172+
vec.try_extend([1, 2, 3].iter().cloned())?;
173+
assert_eq!(&*vec, &[1, 2, 3]);
174+
175+
vec.try_extend([])?;
176+
assert_eq!(&*vec, &[1, 2, 3]);
177+
Ok(())
178+
}
179+
}

0 commit comments

Comments
 (0)