Skip to content

Commit 550a07c

Browse files
authored
Add insertion methods with fallible allocation to cranelift_bforest::{Map,Set} (#12685)
1 parent 58633f3 commit 550a07c

8 files changed

Lines changed: 110 additions & 23 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cranelift/bforest/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ workspace = true
1717

1818
[dependencies]
1919
cranelift-entity = { workspace = true }
20+
wasmtime-core = { workspace = true }

cranelift/bforest/src/map.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use alloc::string::String;
77
#[cfg(test)]
88
use core::fmt;
99
use core::marker::PhantomData;
10+
use wasmtime_core::{alloc::PanicOnOom as _, error::OutOfMemory};
1011

1112
/// Tag type defining forest types for a map.
1213
struct MapTypes<K, V>(PhantomData<(K, V)>);
@@ -135,6 +136,17 @@ where
135136
self.cursor(forest, comp).insert(key, value)
136137
}
137138

139+
/// Like `insert` but returns an error on allocation failure.
140+
pub fn try_insert<C: Comparator<K>>(
141+
&mut self,
142+
key: K,
143+
value: V,
144+
forest: &mut MapForest<K, V>,
145+
comp: &C,
146+
) -> Result<Option<V>, OutOfMemory> {
147+
self.cursor(forest, comp).try_insert(key, value)
148+
}
149+
138150
/// Remove `key` from the map and return the removed value for `key`, if any.
139151
pub fn remove<C: Comparator<K>>(
140152
&mut self,
@@ -345,22 +357,27 @@ where
345357
///
346358
/// If `key` is already present, replace the existing with `value` and return the old value.
347359
pub fn insert(&mut self, key: K, value: V) -> Option<V> {
360+
self.try_insert(key, value).panic_on_oom()
361+
}
362+
363+
/// Like `insert` but returns an error on allocation failure.
364+
pub fn try_insert(&mut self, key: K, value: V) -> Result<Option<V>, OutOfMemory> {
348365
match self.root.expand() {
349366
None => {
350-
let root = self.pool.alloc_node(NodeData::leaf(key, value));
367+
let root = self.pool.alloc_node(NodeData::leaf(key, value))?;
351368
*self.root = root.into();
352369
self.path.set_root_node(root);
353-
None
370+
Ok(None)
354371
}
355372
Some(root) => {
356373
// TODO: Optimize the case where `self.path` is already at the correct insert pos.
357374
let old = self.path.find(key, root, self.pool, self.comp);
358375
if old.is_some() {
359376
*self.path.value_mut(self.pool) = value;
360377
} else {
361-
*self.root = self.path.insert(key, value, self.pool).into();
378+
*self.root = self.path.insert(key, value, self.pool)?.into();
362379
}
363-
old
380+
Ok(old)
364381
}
365382
}
366383
}

cranelift/bforest/src/path.rs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use super::node::Removed;
44
use super::{Comparator, Forest, MAX_PATH, Node, NodeData, NodePool, slice_insert, slice_shift};
55
use core::borrow::Borrow;
66
use core::marker::PhantomData;
7+
use wasmtime_core::error::OutOfMemory;
78

89
#[cfg(test)]
910
use core::fmt;
@@ -260,11 +261,16 @@ impl<F: Forest> Path<F> {
260261
/// The current position must be the correct insertion location for the key.
261262
/// This function does not check for duplicate keys. Use `find` or similar for that.
262263
/// Returns the new root node.
263-
pub fn insert(&mut self, key: F::Key, value: F::Value, pool: &mut NodePool<F>) -> Node {
264+
pub fn insert(
265+
&mut self,
266+
key: F::Key,
267+
value: F::Value,
268+
pool: &mut NodePool<F>,
269+
) -> Result<Node, OutOfMemory> {
264270
if !self.try_leaf_insert(key, value, pool) {
265-
self.split_and_insert(key, value, pool);
271+
self.split_and_insert(key, value, pool)?;
266272
}
267-
self.node[0]
273+
Ok(self.node[0])
268274
}
269275

270276
/// Try to insert `key, value` at the current position, but fail and return false if the leaf
@@ -282,7 +288,12 @@ impl<F: Forest> Path<F> {
282288

283289
/// Split the current leaf node and then insert `key, value`.
284290
/// This should only be used if `try_leaf_insert()` fails.
285-
fn split_and_insert(&mut self, mut key: F::Key, value: F::Value, pool: &mut NodePool<F>) {
291+
fn split_and_insert(
292+
&mut self,
293+
mut key: F::Key,
294+
value: F::Value,
295+
pool: &mut NodePool<F>,
296+
) -> Result<(), OutOfMemory> {
286297
let orig_root = self.node[0];
287298

288299
// Loop invariant: We need to split the node at `level` and then retry a failed insertion.
@@ -294,7 +305,7 @@ impl<F: Forest> Path<F> {
294305
let mut node = self.node[level];
295306
let mut entry = self.entry[level].into();
296307
split = pool[node].split(entry);
297-
let rhs_node = pool.alloc_node(split.rhs_data);
308+
let rhs_node = pool.alloc_node(split.rhs_data)?;
298309

299310
// Should the path be moved to the new RHS node?
300311
// Prefer the smaller node if we're right in the middle.
@@ -347,18 +358,19 @@ impl<F: Forest> Path<F> {
347358
if node == rhs_node {
348359
self.entry[level - 1] += 1;
349360
}
350-
return;
361+
return Ok(());
351362
}
352363
}
353364
}
354365

355366
// If we get here we have split the original root node and need to add an extra level.
356367
let rhs_node = ins_node.expect("empty path");
357-
let root = pool.alloc_node(NodeData::inner(orig_root, key, rhs_node));
368+
let root = pool.alloc_node(NodeData::inner(orig_root, key, rhs_node))?;
358369
let entry = if self.node[0] == rhs_node { 1 } else { 0 };
359370
self.size += 1;
360371
slice_insert(&mut self.node[0..self.size], 0, root);
361372
slice_insert(&mut self.entry[0..self.size], 0, entry);
373+
Ok(())
362374
}
363375

364376
/// Remove the key-value pair at the current position and advance the path to the next
@@ -699,6 +711,8 @@ impl<F: Forest> fmt::Display for Path<F> {
699711

700712
#[cfg(test)]
701713
mod tests {
714+
use wasmtime_core::alloc::PanicOnOom;
715+
702716
use super::*;
703717
use core::cmp::Ordering;
704718

@@ -731,7 +745,7 @@ mod tests {
731745
fn search_single_leaf() {
732746
// Testing Path::new() for trees with a single leaf node.
733747
let mut pool = NodePool::<TF>::new();
734-
let root = pool.alloc_node(NodeData::leaf(10, 'a'));
748+
let root = pool.alloc_node(NodeData::leaf(10, 'a')).panic_on_oom();
735749
let mut p = Path::default();
736750
let comp = TC();
737751

@@ -784,9 +798,11 @@ mod tests {
784798
fn search_single_inner() {
785799
// Testing Path::new() for trees with a single inner node and two leaves.
786800
let mut pool = NodePool::<TF>::new();
787-
let leaf1 = pool.alloc_node(NodeData::leaf(10, 'a'));
788-
let leaf2 = pool.alloc_node(NodeData::leaf(20, 'b'));
789-
let root = pool.alloc_node(NodeData::inner(leaf1, 20, leaf2));
801+
let leaf1 = pool.alloc_node(NodeData::leaf(10, 'a')).panic_on_oom();
802+
let leaf2 = pool.alloc_node(NodeData::leaf(20, 'b')).panic_on_oom();
803+
let root = pool
804+
.alloc_node(NodeData::inner(leaf1, 20, leaf2))
805+
.panic_on_oom();
790806
let mut p = Path::default();
791807
let comp = TC();
792808

cranelift/bforest/src/pool.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::entity::PrimaryMap;
77
#[cfg(test)]
88
use core::fmt;
99
use core::ops::{Index, IndexMut};
10+
use wasmtime_core::error::OutOfMemory;
1011

1112
/// A pool of nodes, including a free list.
1213
pub(super) struct NodePool<F: Forest> {
@@ -30,7 +31,7 @@ impl<F: Forest> NodePool<F> {
3031
}
3132

3233
/// Allocate a new node containing `data`.
33-
pub fn alloc_node(&mut self, data: NodeData<F>) -> Node {
34+
pub fn alloc_node(&mut self, data: NodeData<F>) -> Result<Node, OutOfMemory> {
3435
debug_assert!(!data.is_free(), "can't allocate free node");
3536
match self.freelist {
3637
Some(node) => {
@@ -40,11 +41,12 @@ impl<F: Forest> NodePool<F> {
4041
_ => panic!("Invalid {} on free list", node),
4142
}
4243
self.nodes[node] = data;
43-
node
44+
Ok(node)
4445
}
4546
None => {
4647
// The free list is empty. Allocate a new node.
47-
self.nodes.push(data)
48+
self.nodes.try_reserve(1)?;
49+
Ok(self.nodes.push(data))
4850
}
4951
}
5052
}

cranelift/bforest/src/set.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use alloc::string::String;
77
#[cfg(test)]
88
use core::fmt;
99
use core::marker::PhantomData;
10+
use wasmtime_core::{alloc::PanicOnOom, error::OutOfMemory};
1011

1112
/// Tag type defining forest types for a set.
1213
struct SetTypes<K>(PhantomData<K>);
@@ -112,6 +113,16 @@ where
112113
self.cursor(forest, comp).insert(key)
113114
}
114115

116+
/// Like `insert` but returns an error on allocation failure.
117+
pub fn try_insert<C: Comparator<K>>(
118+
&mut self,
119+
key: K,
120+
forest: &mut SetForest<K>,
121+
comp: &C,
122+
) -> Result<bool, OutOfMemory> {
123+
self.cursor(forest, comp).try_insert(key)
124+
}
125+
115126
/// Remove `key` from the set and return true.
116127
///
117128
/// If `key` was not present in the set, return false.
@@ -277,20 +288,25 @@ where
277288
/// If `elem` is already present, don't change the set, place the cursor at `goto(elem)`, and
278289
/// return false.
279290
pub fn insert(&mut self, elem: K) -> bool {
291+
self.try_insert(elem).panic_on_oom()
292+
}
293+
294+
/// Like `insert` but returns an error on allocation failure.
295+
pub fn try_insert(&mut self, elem: K) -> Result<bool, OutOfMemory> {
280296
match self.root.expand() {
281297
None => {
282-
let root = self.pool.alloc_node(NodeData::leaf(elem, SetValue()));
298+
let root = self.pool.alloc_node(NodeData::leaf(elem, SetValue()))?;
283299
*self.root = root.into();
284300
self.path.set_root_node(root);
285-
true
301+
Ok(true)
286302
}
287303
Some(root) => {
288304
// TODO: Optimize the case where `self.path` is already at the correct insert pos.
289305
if self.path.find(elem, root, self.pool, self.comp).is_none() {
290-
*self.root = self.path.insert(elem, SetValue(), self.pool).into();
291-
true
306+
*self.root = self.path.insert(elem, SetValue(), self.pool)?.into();
307+
Ok(true)
292308
} else {
293-
false
309+
Ok(false)
294310
}
295311
}
296312
}

crates/fuzzing/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ v8 = "139.0.0"
7171
wat = { workspace = true }
7272
rand = { version = "0.8.0", features = ["small_rng"] }
7373
wasmtime-environ = { workspace = true }
74+
cranelift-bforest = { workspace = true }
7475
cranelift-bitset = { workspace = true }
7576

7677
# Only enable the `build-libinterpret` feature when fuzzing is enabled, enabling

crates/fuzzing/tests/oom.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,3 +1036,35 @@ fn index_map_shift_insert() -> Result<()> {
10361036
Ok(())
10371037
})
10381038
}
1039+
1040+
#[test]
1041+
fn bforest_map() -> Result<()> {
1042+
use cranelift_bforest::*;
1043+
OomTest::new().test(|| {
1044+
let mut forest = MapForest::new();
1045+
let mut map = Map::new();
1046+
for i in 0..100 {
1047+
map.try_insert(Key(i), i, &mut forest, &())?;
1048+
}
1049+
for i in 0..100 {
1050+
assert_eq!(map.get(Key(i), &forest, &()), Some(i));
1051+
}
1052+
Ok(())
1053+
})
1054+
}
1055+
1056+
#[test]
1057+
fn bforest_set() -> Result<()> {
1058+
use cranelift_bforest::*;
1059+
OomTest::new().test(|| {
1060+
let mut forest = SetForest::new();
1061+
let mut set = Set::new();
1062+
for i in 0..100 {
1063+
set.try_insert(Key(i), &mut forest, &())?;
1064+
}
1065+
for i in 0..100 {
1066+
assert!(set.contains(Key(i), &forest, &()));
1067+
}
1068+
Ok(())
1069+
})
1070+
}

0 commit comments

Comments
 (0)