Skip to content
Merged
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Changed
- Implement `FusedIterator` for `TreeMap` iterators `IntoKeys` and `IntoValues`.

### Fixed
- `TreeMap::range` iterator had bugs in a couple of places, specifically around determining bounds when the initial lookup terminated in an inner node. I added some better fuzz coverage for this iterator that helped with finding these issues.

## [0.5.0] - 2026-04-16

### Added
Expand Down
20 changes: 12 additions & 8 deletions benches/node/match_prefix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,22 @@ fn bench(c: &mut Criterion) {
];

let p0 = &[0, 0, 0, 0, 0, 0, 0, 0];
let mut node48_small = InnerNode48::<Box<[u8]>, usize, 16>::from_prefix(p0, p0.len());
let mut node256_small = InnerNodeDirect::<Box<[u8]>, usize, 16>::from_prefix(p0, p0.len());
node48_small.write_child(99, leaf_opaque);
node256_small.write_child(99, leaf_opaque);
let node48_small = InnerNode48::<Box<[u8]>, usize, 16>::builder(p0, p0.len())
.write_child(99, leaf_opaque)
.build();
let node256_small = InnerNodeDirect::<Box<[u8]>, usize, 16>::builder(p0, p0.len())
.write_child(99, leaf_opaque)
.build();

let p1 = &[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
let mut node48_large = InnerNode48::<Box<[u8]>, usize, 16>::from_prefix(p1, p1.len());
let mut node256_large = InnerNodeDirect::<Box<[u8]>, usize, 16>::from_prefix(p1, p1.len());
node48_large.write_child(99, leaf_opaque);
node256_large.write_child(99, leaf_opaque);
let node48_large = InnerNode48::<Box<[u8]>, usize, 16>::builder(p1, p1.len())
.write_child(99, leaf_opaque)
.build();
let node256_large = InnerNodeDirect::<Box<[u8]>, usize, 16>::builder(p1, p1.len())
.write_child(99, leaf_opaque)
.build();

macro_rules! generate_benches {
(single_bench $match_func:ident $b:ident $node:ident $key:ident $($current_depth:literal)?) => {
Expand Down
10 changes: 6 additions & 4 deletions benches/node/min_max.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,18 @@ fn bench(c: &mut Criterion) {
let nodes48: Vec<_> = (0..count)
.map(|i| {
let idx = i * skip;
let mut node = InnerNode48::<CString, usize, 16>::empty();
node.write_child(idx, dangling_opaque);
let node = InnerNode48::<CString, usize, 16>::builder(&[], 0)
.write_child(idx, dangling_opaque)
.build();
(idx, node)
})
.collect();
let nodes256: Vec<_> = (0..count)
.map(|i| {
let idx = i * skip;
let mut node = InnerNodeDirect::<CString, usize, 16>::empty();
node.write_child(idx, dangling_opaque);
let node = InnerNodeDirect::<CString, usize, 16>::builder(&[], 0)
.write_child(idx, dangling_opaque)
.build();
(idx, node)
})
.collect();
Expand Down
16 changes: 13 additions & 3 deletions benches/tree/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ fn iter_node<const PREFIX_LEN: usize, M: Measurement, N: InnerNode<PREFIX_LEN>>(
ty: &str,
sizes: &[u16],
) {
for (idx, size) in sizes.iter().enumerate() {
assert!(
*size > 0,
"size {size} in index {idx} must be greater than zero"
);
}

let mut rng = StdRng::seed_from_u64(69420);
let bytes: Vec<_> = (0..=255u8).collect();

Expand All @@ -20,11 +27,14 @@ fn iter_node<const PREFIX_LEN: usize, M: Measurement, N: InnerNode<PREFIX_LEN>>(

let mut group = c.benchmark_group(format!("iter_node/{ty}"));
for size in sizes {
let mut node = N::empty();
for key in bytes.choose_multiple(&mut rng, *size as usize) {
node.write_child(*key, dangling_opaque)
let mut iter = bytes.choose_multiple(&mut rng, *size as usize);
let mut builder = N::builder(&[], 0).write_child(*iter.next().unwrap(), dangling_opaque);
for key in iter {
builder = builder.write_child(*key, dangling_opaque);
}

let node = builder.build();

group.bench_function(format!("{size}").as_str(), |b| {
b.iter(|| {
node.iter().for_each(|(k, n)| {
Expand Down
149 changes: 88 additions & 61 deletions src/raw/operations/insert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,50 +354,60 @@ impl<K, V, const PREFIX_LEN: usize> InsertPoint<K, V, PREFIX_LEN> {
NodePtr::allocate_node_ptr(LeafNode::with_no_siblings(key, value), alloc);
let new_leaf_pointer_opaque = new_leaf_pointer.to_opaque();

let mut new_n4 = {
// SAFETY: The lifetime of the header reference is bounded to this block and no
// current mutation happens. Also, we know this is an inner node pointer because
// of the specific insert case
let n4_builder = {
// SAFETY: The `header` pointer is bounded to this block and must be pointing to
// an existing inner node.
let header = unsafe { mismatched_inner_node_ptr.header_ref_unchecked() };

// prefix mismatch, need to split prefix into two separate nodes and take the
// common prefix into a new parent node
let prefix = header.read_prefix();
let prefix = &prefix[..prefix.len().min(mismatch.matched_bytes)];
InnerNode4::from_prefix(prefix, mismatch.matched_bytes)
InnerNode4::builder(prefix, mismatch.matched_bytes)
};

unsafe {
// SAFETY: This is a new node 4 so it's empty and we have
// space for writing new children. We also check the order
// of the keys before writing
if mismatch.prefix_byte < key_byte {
new_n4
.write_child_unchecked(mismatch.prefix_byte, mismatched_inner_node_ptr);
new_n4.write_child_unchecked(key_byte, new_leaf_pointer_opaque);
let new_n4 = if mismatch.prefix_byte < key_byte {
// SAFETY: The `write_child_unchecked` are safe because we know the builder
// starts empty and we're adding 2 children (out of a capacity of 4)
// in the correct sorted order (given by the if-else condition).
let new_n4 = unsafe {
n4_builder
.write_child_unchecked(mismatch.prefix_byte, mismatched_inner_node_ptr)
.write_child_unchecked(key_byte, new_leaf_pointer_opaque)
.build()
};

// SAFETY: There are no concurrent modifications, the `apply` safety doc
// covers this
let previous_leaf_ptr = maximum_unchecked(mismatched_inner_node_ptr);
// SAFETY: There are no concurrent modifications, the `apply` safety doc
// covers this
let previous_leaf_ptr = unsafe { maximum_unchecked(mismatched_inner_node_ptr) };

// SAFETY: There is no concurrent modification of the new leaf node, the
// the `apply` function.
LeafNode::insert_after(new_leaf_pointer, previous_leaf_ptr);
} else {
new_n4.write_child_unchecked(key_byte, new_leaf_pointer_opaque);
new_n4
.write_child_unchecked(mismatch.prefix_byte, mismatched_inner_node_ptr);
// SAFETY: There is no concurrent modification of the new leaf node, the
// the `apply` function.
unsafe { LeafNode::insert_after(new_leaf_pointer, previous_leaf_ptr) };

// SAFETY: There are no concurrent modifications, the `apply` safety doc
// covers this
let next_leaf_ptr = minimum_unchecked(mismatched_inner_node_ptr);
new_n4
} else {
// SAFETY: The `write_child_unchecked` are safe because we know the builder
// starts empty and we're adding 2 children (out of a capacity of 4)
// in the correct sorted order (given by the if-else condition).
let new_n4 = unsafe {
n4_builder
.write_child_unchecked(key_byte, new_leaf_pointer_opaque)
.write_child_unchecked(mismatch.prefix_byte, mismatched_inner_node_ptr)
.build()
};

// SAFETY: There is no concurrent modification of the new leaf node, the
// existing leaf node, or its siblings because of the safety requirements of
// the `apply` function.
LeafNode::insert_before(new_leaf_pointer, next_leaf_ptr);
}
}
// SAFETY: There are no concurrent modifications, the `apply` safety doc
// covers this
let next_leaf_ptr = unsafe { minimum_unchecked(mismatched_inner_node_ptr) };

// SAFETY: There is no concurrent modification of the new leaf node, the
// existing leaf node, or its siblings because of the safety requirements of
// the `apply` function.
unsafe { LeafNode::insert_before(new_leaf_pointer, next_leaf_ptr) };

new_n4
};

{
// Scope header mutation so that the mutable reference is held for the minimum
Expand Down Expand Up @@ -481,44 +491,61 @@ impl<K, V, const PREFIX_LEN: usize> InsertPoint<K, V, PREFIX_LEN> {
core::hint::assert_unchecked(key_bytes_used <= new_key_bytes_used);
}

let mut new_n4 = InnerNode4::from_prefix(
let leaf_node_key_byte = leaf_bytes[new_key_bytes_used];
let new_leaf_node_key_byte = key_bytes[new_key_bytes_used];

// Build the node with the existing leaf as the first child. This
// exhausts the `key_bytes` borrow of `key` so that `key` can be
// moved into the new leaf below.
let n4_builder = InnerNode4::builder(
&key_bytes[key_bytes_used..new_key_bytes_used],
new_key_bytes_used - key_bytes_used,
);

let leaf_node_key_byte = leaf_bytes[new_key_bytes_used];
let new_leaf_node_key_byte = key_bytes[new_key_bytes_used];
let new_leaf_node_pointer =
NodePtr::allocate_node_ptr(LeafNode::with_no_siblings(key, value), alloc);

unsafe {
// SAFETY: This is a new node 4 so it's empty and we have
// space for writing new children. We also check the order
// of the keys before writing
if leaf_node_key_byte < new_leaf_node_key_byte {
new_n4.write_child_unchecked(leaf_node_key_byte, leaf_node_ptr.to_opaque());
new_n4.write_child_unchecked(
new_leaf_node_key_byte,
new_leaf_node_pointer.to_opaque(),
);
let new_n4 = if leaf_node_key_byte < new_leaf_node_key_byte {
// SAFETY: The `write_child_unchecked` are safe because we know the builder
// starts empty and we're adding 2 children (out of a capacity of 4)
// in the correct sorted order (given by the if-else condition).
let new_n4 = unsafe {
n4_builder
.write_child_unchecked(leaf_node_key_byte, leaf_node_ptr.to_opaque())
.write_child_unchecked(
new_leaf_node_key_byte,
new_leaf_node_pointer.to_opaque(),
)
.build()
};

// SAFETY: There is no concurrent modification of the new leaf node, the
// existing leaf node, or its siblings because of the safety requirements of
// the `apply` function.
LeafNode::insert_after(new_leaf_node_pointer, leaf_node_ptr);
} else {
new_n4.write_child_unchecked(
new_leaf_node_key_byte,
new_leaf_node_pointer.to_opaque(),
);
new_n4.write_child_unchecked(leaf_node_key_byte, leaf_node_ptr.to_opaque());
// SAFETY: There is no concurrent modification of the new leaf node, the
// existing leaf node, or its siblings because of the safety requirements of
// the `apply` function.
unsafe { LeafNode::insert_after(new_leaf_node_pointer, leaf_node_ptr) };

// SAFETY: There is no concurrent modification of the new leaf node, the
// existing leaf node, or its siblings because of the safety requirements of
// the `apply` function.
LeafNode::insert_before(new_leaf_node_pointer, leaf_node_ptr);
}
}
new_n4
} else {
// SAFETY: The `write_child_unchecked` are safe because we know the builder
// starts empty and we're adding 2 children (out of a capacity of 4)
// in the correct sorted order (given by the if-else condition).
let new_n4 = unsafe {
n4_builder
.write_child_unchecked(
new_leaf_node_key_byte,
new_leaf_node_pointer.to_opaque(),
)
.write_child_unchecked(leaf_node_key_byte, leaf_node_ptr.to_opaque())
.build()
};

// SAFETY: There is no concurrent modification of the new leaf node, the
// existing leaf node, or its siblings because of the safety requirements of
// the `apply` function.
unsafe { LeafNode::insert_before(new_leaf_node_pointer, leaf_node_ptr) };

new_n4
};

(
NodePtr::allocate_node_ptr(new_n4, alloc).to_opaque(),
Expand Down
Loading
Loading