Skip to content
Open
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
21 changes: 19 additions & 2 deletions src/dense_byte_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,13 @@ impl<V: Clone + Send + Sync, A: Allocator, Cf: CoFree<V=V, A=A>> TrieNode<V, A>
requested_mask.clear_bit(byte);
match self.get(byte) {
Some(cf) => {
// An exact value-only request does not enumerate an onward link stored in the
// same CoFree. The preceding stashed-value fast path means this branch is
// reached only when that link was not requested separately.
if key.len() == 1 && *expect_val && cf.has_rec() {
unrequested_cofree_half = true;
}

//A key longer than 1 byte or an explicit request for a rec link can be answered with a Child
if key.len() > 1 || !*expect_val {
match cf.rec() {
Expand Down Expand Up @@ -1914,7 +1921,12 @@ impl<V: Clone + Send + Sync, A: Allocator, Cf: CoFree<V=V, A=A>, OtherCf: CoFree
if new_mask > 0 {
AlgebraicResult::Identity(new_mask)
} else {
AlgebraicResult::Element(Self::new(None, self.val().cloned()))
let val = if val_mask & SELF_IDENT > 0 {
self.val().cloned()
} else {
other.val().cloned()
};
AlgebraicResult::Element(Self::new(None, val))
}
},
(AlgebraicResult::Identity(rec_mask), AlgebraicResult::None) => {
Expand All @@ -1928,7 +1940,12 @@ impl<V: Clone + Send + Sync, A: Allocator, Cf: CoFree<V=V, A=A>, OtherCf: CoFree
if new_mask > 0 {
AlgebraicResult::Identity(new_mask)
} else {
AlgebraicResult::Element(Self::new(self.rec().cloned(), None))
let rec = if rec_mask & SELF_IDENT > 0 {
self.rec().cloned()
} else {
other.rec().cloned()
};
AlgebraicResult::Element(Self::new(rec, None))
Comment on lines 704 to +1948

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry I meant DenseNode -- i.e. the motivation behind these.

}
},
(rec_el, val_el) => {
Expand Down
60 changes: 52 additions & 8 deletions src/experimental/serialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -817,8 +817,8 @@ pub fn deserialize_file<V: TrieValue>(file_path : impl AsRef<std::path::Path>, d

let [path_idx, node_idx] = node_buf.map(|x| x as usize);

let Deserialized::Path(path) = &deserialized[path_idx] else { return Err(std::io::Error::other("Malformed serialized ByteTrie, expected path")); };
let Deserialized::Node(node) = &deserialized[node_idx] else { return Err(std::io::Error::other("Malformed serialized ByteTrie, expected node")); };
let Deserialized::Path(path) = deserialized.get(path_idx).ok_or_else(|| std::io::Error::other("Malformed serialized ByteTrie, path offset out of bounds"))? else { return Err(std::io::Error::other("Malformed serialized ByteTrie, expected path")); };
let Deserialized::Node(node) = deserialized.get(node_idx).ok_or_else(|| std::io::Error::other("Malformed serialized ByteTrie, node offset out of bounds"))? else { return Err(std::io::Error::other("Malformed serialized ByteTrie, expected node")); };

let mut path_node = PathMap::new();

Expand All @@ -838,8 +838,8 @@ pub fn deserialize_file<V: TrieValue>(file_path : impl AsRef<std::path::Path>, d

let [val_idx, node_idx] = node_buf.map(|x| x as usize);

let Deserialized::Value(value) = &deserialized[val_idx] else { return Err(std::io::Error::other("Malformed serialized ByteTrie, expected value")); };
let Deserialized::Node(node) = &deserialized[node_idx] else { return Err(std::io::Error::other("Malformed serialized ByteTrie, expected node")); };
let Deserialized::Value(value) = deserialized.get(val_idx).ok_or_else(|| std::io::Error::other("Malformed serialized ByteTrie, value offset out of bounds"))? else { return Err(std::io::Error::other("Malformed serialized ByteTrie, expected value")); };
let Deserialized::Node(node) = deserialized.get(node_idx).ok_or_else(|| std::io::Error::other("Malformed serialized ByteTrie, node offset out of bounds"))? else { return Err(std::io::Error::other("Malformed serialized ByteTrie, expected node")); };

let mut value_node = node.clone();
value_node.set_val_at(&[], value.clone());
Expand All @@ -852,10 +852,10 @@ pub fn deserialize_file<V: TrieValue>(file_path : impl AsRef<std::path::Path>, d

let [mask_idx, branches_idx] = node_buf.map(|x| x as usize);

let Deserialized::ChildMask(mask) = &deserialized[mask_idx] else { return Err(std::io::Error::other("Malformed serialized ByteTrie, expected childmask as `(/?<hex_top><Hex_bot>)*`")); };
let Deserialized::ChildMask(mask) = deserialized.get(mask_idx).ok_or_else(|| std::io::Error::other("Malformed serialized ByteTrie, childmask offset out of bounds"))? else { return Err(std::io::Error::other("Malformed serialized ByteTrie, expected childmask as `(/?<hex_top><Hex_bot>)*`")); };
let iter: crate::utils::ByteMaskIter = (*mask).into();

let Deserialized::Branches(r) = &deserialized[branches_idx] else { return Err(std::io::Error::other("Malformed serialized ByteTrie, expected branches")); };
let Deserialized::Branches(r) = deserialized.get(branches_idx).ok_or_else(|| std::io::Error::other("Malformed serialized ByteTrie, branches offset out of bounds"))? else { return Err(std::io::Error::other("Malformed serialized ByteTrie, expected branches")); };
let branches = &branches_buffer[r.start..r.end];

core::debug_assert_eq!(mask.into_iter().copied().map(u64::count_ones).sum::<u32>() as usize, branches.len());
Expand All @@ -864,7 +864,7 @@ pub fn deserialize_file<V: TrieValue>(file_path : impl AsRef<std::path::Path>, d
let mut wz = branch_node.write_zipper();

for (byte, &idx) in iter.into_iter().zip(branches) {
let Deserialized::Node(node) = &deserialized[idx as usize] else { return Err(std::io::Error::other("Malformed serialized ByteTrie, expected node")); };
let Deserialized::Node(node) = deserialized.get(idx as usize).ok_or_else(|| std::io::Error::other("Malformed serialized ByteTrie, child node offset out of bounds"))? else { return Err(std::io::Error::other("Malformed serialized ByteTrie, expected node")); };

core::debug_assert!(!node.is_empty());

Expand Down Expand Up @@ -986,6 +986,50 @@ mod test {
use super::*;
use std::sync::Arc;

fn write_serialized_fixture(name : &str, data : &[u8])->Option<PathBuf> {
let manifest_dir = match std::env::var("CARGO_MANIFEST_DIR") {
Ok(manifest_dir) => manifest_dir,
_ => {
#[cfg(not(miri))]
panic!("Test should be running under Cargo");
return None;
}
};
let dir = PathBuf::from(manifest_dir).join(".tmp");
let _ = std::fs::create_dir(&dir);
let path = dir.join(name);
std::fs::write(&path, data).unwrap();
Some(path)
}

#[test]
fn deserialize_rejects_malformed_records() {
let Some(bad_tag) = write_serialized_fixture("serialization_bad_tag.data", b"header\n? bad\n") else {
return;
};
let err = deserialize_file::<Arc<[u8]>>(&bad_tag, |b| Arc::<[u8]>::from(b)).unwrap_err();
assert!(err.to_string().contains("expected `<tag byte><space>`"));

let Some(odd_path_hex) = write_serialized_fixture("serialization_odd_path_hex.data", b"header\np A\n") else {
return;
};
let err = deserialize_file::<Arc<[u8]>>(&odd_path_hex, |b| Arc::<[u8]>::from(b)).unwrap_err();
assert!(err.to_string().contains("expected path"));
}

#[test]
fn deserialize_rejects_forward_offsets_without_panic() {
let Some(path) = write_serialized_fixture(
"serialization_forward_offset.data",
b"header\nP x0000000000000001x0000000000000002\n"
) else {
return;
};

let err = deserialize_file::<Arc<[u8]>>(&path, |b| Arc::<[u8]>::from(b)).unwrap_err();
assert!(err.to_string().contains("offset out of bounds"));
}

#[test]
fn serialization_trivial_test() {
const LEN : usize = 0x_80;
Expand Down Expand Up @@ -1598,4 +1642,4 @@ pub fn dbg_hex_line_numbers(f : &std::fs::File, path : impl AsRef<std::path::Pat
}

Ok(out.into_inner().unwrap())
}
}
4 changes: 2 additions & 2 deletions src/experimental/tree_serialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ mod tests {
use crate::morphisms::Catamorphism;
use crate::PathMap;

#[ignore] //GOAT, re-enable if/when this code is ready.
#[ignore] //GOAT, re-enable if/when this code is ready. Fails under Miri due to Stacked Borrows UB in deserialize_fork's ptr::read.
#[test]
fn tree_serde_2() {
let keys = [vec![12, 13, 14], vec![12, 13, 14, 100, 101]];
Expand All @@ -72,4 +72,4 @@ mod tests {
deserialize_fork(top_node, &mut recovered.write_zipper(), &v[..], |_, _p| ()).unwrap();
assert_eq!(btm.hash_with(|_| 0), recovered.hash_with(|_| 0));
}
}
}
24 changes: 12 additions & 12 deletions src/experimental/zipper_algebra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1423,23 +1423,23 @@ fn with_k<const K: usize, T, R>(
) -> R {
debug_assert!(bits.count_ones() as usize >= K);

// collect raw pointers first (safe)
let mut ptrs: [*mut T; K] = [std::ptr::null_mut(); K];

// Extract the K distinct active indices from the bitmask, then take K
// disjoint &mut into the slice with the safe checked API. The previous
// version built raw pointers via `xs.as_mut_ptr().add(idx)` inside the
// loop, but each `as_mut_ptr()` re-borrowed `xs` and invalidated the
// pointers from earlier iterations under Stacked Borrows (a real UB Miri
// flags). `get_disjoint_mut` proves distinctness + in-bounds and hands
// back the disjoint references; for small K its check is negligible.
let mut indices = [0usize; K];
let mut i = 0;
while i < K {
let idx = bits.trailing_zeros() as usize;
indices[i] = bits.trailing_zeros() as usize;
bits &= bits - 1;
ptrs[i] = unsafe { xs.as_mut_ptr().add(idx) };
i += 1;
}

// SAFETY:
// - indices are distinct (bitmask)
// - derived from same slice

// should be zero-cost after inlining
let refs = unsafe { ptrs.map(|p| &mut *p) };
let refs = xs
.get_disjoint_mut(indices)
.expect("active bitmask indices are distinct and in bounds");

f(refs)
}
Expand Down
10 changes: 10 additions & 0 deletions src/line_list_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ pub(crate) const KEY_BYTES_CNT: usize = 42;
#[cfg(not(feature = "slim_ptrs"))]
pub(crate) const KEY_BYTES_CNT: usize = 14;

// Only the slim_ptrs layout is asserted. The not(slim_ptrs) TrieNodeODRc has no
// empty-sentinel representation yet (`new_empty`/`is_empty`/`make_unique`/`==`
// exist only on the slim variant; the allocator prevents a free sentinel for the
// Arc form, the open design in the note at trie_node.rs on TaggedNodeRefMut's
// EmptyNode arm), so that configuration does not compile and its size cannot be
// asserted until the sentinel design lands.
#[cfg(all(feature = "slim_ptrs", target_arch = "x86_64", not(miri)))]
const _: [(); core::mem::size_of::<LineListNode<[u8; 1024], crate::alloc::GlobalAlloc>>()] =
[(); 64];

const SLOT_0_USED_MASK: u16 = 1 << 15;
const SLOT_1_USED_MASK: u16 = 1 << 14;
const BOTH_SLOTS_USED_MASK: u16 = SLOT_0_USED_MASK | SLOT_1_USED_MASK;
Expand Down
Loading