Skip to main content

pathmap/
paths_serialization.rs

1//! Functionality for working with the `.paths` data format
2//!
3//! `.paths` is a compressed trie-based representation suitable for writing to a file.
4//!
5//! `.paths` data does not contain values, so the `_auxdata` functions allow values to
6//! be associated with path indices.
7
8use libz_ng_sys::*;
9use crate::PathMap;
10use crate::TrieValue;
11use crate::alloc::Allocator;
12use crate::zipper::{
13  ZipperReadOnlyConditionalIteration,
14  ZipperWriting,
15  ZipperIteration,
16  ZipperValues,
17};
18
19#[cfg(feature="nightly")]
20#[path="paths_serialization_nightly.rs"]
21mod paths_serialization_nightly;
22#[cfg(feature="nightly")]
23pub use paths_serialization_nightly::*;
24
25/// Statistics from a `serialize` operation
26#[derive(Debug, Clone, Copy)]
27pub struct SerializationStats {
28  /// The number of output bytes written to the target
29  pub bytes_out  : usize,
30  /// The number of serialized (uncompressed) bytes
31  pub bytes_in   : usize,
32  /// The total number of paths that were serialized
33  pub path_count : usize
34}
35
36/// Statistics from a `deserialize` operation
37#[derive(Debug, Clone, Copy)]
38pub struct DeserializationStats {
39  /// The number of input bytes read from the source
40  pub bytes_in   : usize,
41  /// The number of deserialized (uncompressed) path bytes
42  pub bytes_out  : usize,
43  /// The total number of path insert attempts (i.e. paths in the source)
44  pub path_count : usize
45}
46
47/// Serializes each value's path from the focus of `rz` into `.paths` data written to `target`
48pub fn serialize_paths<'a, V, W, RZ>(rz: RZ, target: &mut W) -> std::io::Result<SerializationStats>
49  where
50    V: TrieValue,
51    RZ: ZipperReadOnlyConditionalIteration<'a, V>,
52    W: std::io::Write
53{
54  serialize_paths_with_auxdata(rz, target, |_, _, _| {})
55}
56
57/// Serializes each value's path from the focus of `rz` into `.paths` data written to `target`
58///
59/// The `fv` closure is called for each path, permitting values to be serialized separately
60/// and associated with path indices
61pub fn serialize_paths_with_auxdata<'a, V : TrieValue, RZ : ZipperValues<V> + ZipperIteration, W: std::io::Write, F: FnMut(usize, &[u8], &V) -> ()>(mut rz: RZ, target: &mut W, mut fv: F) -> std::io::Result<SerializationStats> {
62  let mut k = 0;
63  //GOAT, old implementation.  Delete.
64  // serialize_paths_from_func(target, &mut rz, |rz| {
65  //    match rz.to_next_get_val_with_witness(&witness) {
66  //      None => { Ok(None) }
67  //      Some(v) => {
68  //        let path = rz.path();
69  //        fv(k, path, v);
70  //        k += 1;
71
72  //        //SAFETY: `for_each_path_serialize` finishes with the path returned from this closure
73  //        // before it invokes the closure again.  The `rz` path() will remain valid until the `rz`
74  //        // is modified again, and we have ownership of the rz and only modify it within this closure
75  //        //
76  //        Ok(Some(unsafe { std::mem::transmute(path) }))
77  //      }
78  //    }
79  // })
80  serialize_paths_from_funcs(target, &mut rz, |rz| Ok(rz.to_next_val()), |rz| {
81    let path = rz.path();
82    fv(k, path, rz.val().unwrap());
83    k += 1;
84    Some(path)
85  })
86}
87
88/// Generates `.paths` data by invoking arbitrary closures
89///
90/// Warning: the size of the individual path serialization can be double exponential in the size of the PathMap
91///
92///NOTE: This function takes two closures because of a limitation in the borrow checker.
93/// borrowck isn't smart enough to allow one closure to take a mutable borrow of the `PathSrc`
94/// object, and return a const reborrow, and then allow the original mutable reference to
95/// be used again after the reborrow was dropped.  Doing the reborrow in the loop works around
96/// this limitation.
97///
98///When the borrow checker becomes more capable, we can try to collapse this function to take
99/// one closure instead of two.
100pub fn serialize_paths_from_funcs<PathSrc, AdvanceF, PathF, W>(target: &mut W, src: &mut PathSrc, mut advance_f: AdvanceF, mut path_f: PathF) -> std::io::Result<SerializationStats>
101where
102  AdvanceF: FnMut(&mut PathSrc) -> std::io::Result<bool>,
103  PathF: FnMut(&PathSrc) -> Option<&[u8]>,
104  W: std::io::Write,
105{
106  const CHUNK: usize = 4096; // not tuned yet
107  let mut buffer = [0u8; CHUNK];
108
109  #[allow(invalid_value)] //Squish the warning about a Null function ptr, because zlib uses a default allocator if the the ptr is NULL
110  //I filed https://github.com/rust-lang/libz-sys/issues/243 to track this issue, and I confirmed the easy fix works, but I didn't submit
111  // a PR because their build and validation process is very confusing.
112  let mut strm: z_stream = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
113  let mut ret = unsafe { zng_deflateInit(&mut strm, 7) };
114  assert_eq!(ret, Z_OK);
115
116  let mut total_paths : usize = 0;
117  while advance_f(src)? {
118    let p = match path_f(src) {
119      Some(p) => p,
120      None => continue,
121    };
122
123    // println!("healthy {:?}", unsafe { slice_from_raw_parts(&strm as *const z_stream as *const u8, 104).as_ref() });
124    let l = p.len();
125    // println!("({l}) {:?}", p);
126    let mut lin = (l as u32).to_le_bytes();
127    strm.avail_in = 4;
128    strm.next_in = lin.as_mut_ptr();
129
130    // todo (Adam): this is stupid/simple code; the following two blocks should be merged and write out the path length and path together
131    loop {
132      strm.avail_out = CHUNK as _;
133      strm.next_out = buffer.as_mut_ptr();
134      ret = unsafe { deflate(&mut strm, Z_NO_FLUSH) };
135      assert_ne!(ret, Z_STREAM_ERROR);
136      let have = CHUNK - strm.avail_out as usize;
137      target.write_all(&mut buffer[..have])?;
138      if strm.avail_out != 0 { break }
139    }
140    assert_eq!(strm.avail_in, 0);
141
142    strm.avail_in = l as _;
143    strm.next_in = p.as_ptr().cast_mut();
144    loop {
145      strm.avail_out = CHUNK as _;
146      strm.next_out = buffer.as_mut_ptr();
147      ret = unsafe { deflate(&mut strm, Z_NO_FLUSH) };
148      assert_ne!(ret, Z_STREAM_ERROR);
149      let have = CHUNK - strm.avail_out as usize;
150      target.write_all(&mut buffer[..have])?;
151      if strm.avail_out != 0 { break }
152    }
153    assert_eq!(strm.avail_in, 0);
154
155
156    total_paths += 1;
157  }
158  loop {
159    strm.avail_out = CHUNK as _;
160    strm.next_out = buffer.as_mut_ptr();
161    ret = unsafe { deflate(&mut strm, Z_FINISH) };
162    let have = CHUNK - strm.avail_out as usize;
163    target.write_all(&buffer[..have])?;
164    if ret == Z_STREAM_END { break; }
165    assert_eq!(ret, Z_OK);
166  }
167  ret = unsafe { deflateEnd(&mut strm) };
168  assert_eq!(ret, Z_OK);
169
170  Ok(SerializationStats {
171    bytes_out  : strm.total_out, 
172    bytes_in   : strm.total_in,
173    path_count : total_paths
174  })
175}
176
177/// Deserializes each path from the `.paths` data in `source`, and grafts the resulting data at
178/// the focus of `wz`
179pub fn deserialize_paths<V: TrieValue, A: Allocator, WZ : ZipperWriting<V, A>, R: std::io::Read>(wz: WZ, source: R, v: V) -> std::io::Result<DeserializationStats> {
180  deserialize_paths_with_auxdata(wz, source, |_, _| v.clone())
181}
182
183/// Deserializes each path from the `.paths` data in `source`, and grafts the resulting data at
184/// the focus of `wz`
185///
186/// Values are constructed with the supplied `fv` closure.
187/// See [serialize_paths_with_auxdata]
188pub fn deserialize_paths_with_auxdata<V: TrieValue, A: Allocator, WZ : ZipperWriting<V, A>, R: std::io::Read, F: Fn(usize, &[u8]) -> V>(mut wz: WZ, source: R, fv: F) -> std::io::Result<DeserializationStats> {
189  let mut submap = PathMap::new_in(wz.alloc());
190  let r = for_each_deserialized_path(source, |k, p| {
191    let v = fv(k, p);
192    submap.set_val_at(p, v);
193    Ok(())
194  });
195  wz.graft_map(submap);
196  r
197}
198
199/// Deserializes each path from the `.paths` data in `source`, calling `f` for each path
200pub fn for_each_deserialized_path<R: std::io::Read, F: FnMut(usize, &[u8]) -> std::io::Result<()>>(mut source: R, mut f: F) -> std::io::Result<DeserializationStats> {
201  use libz_ng_sys::*;
202  const IN: usize = 1024;
203  const OUT: usize = 2048;
204  let mut ibuffer = [0u8; IN];
205  let mut obuffer = [0u8; OUT];
206  let mut l = 0u32;
207  let mut lbuf = [0u8; 4];
208  let mut lbuf_offset = 0;
209  let mut finished_path = true;
210  let mut total_paths : usize = 0usize;
211  #[allow(invalid_value)] //Squish the warning about a Null function ptr, because zlib uses a default allocator if the the ptr is NULL
212  let mut strm: z_stream = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
213  let mut ret = unsafe { zng_inflateInit(&mut strm) };
214  if ret != Z_OK { return Err(std::io::Error::new(std::io::ErrorKind::Other, "failed to init zlib-ng inflate")) }
215  let mut wz_buf = vec![];
216  // if statement in loop that emulates goto for the many to many ibuffer-obuffer relation
217  'reading: loop {
218    strm.avail_in = source.read(&mut ibuffer)? as _;
219    if strm.avail_in == 0 { break; }
220    strm.next_in = &mut ibuffer as _;
221
222    'decompressing: loop {
223      strm.avail_out = OUT as _;
224      strm.next_out = obuffer.as_mut_ptr();
225      let mut pos = 0usize;
226
227      ret = unsafe { inflate(&mut strm, Z_NO_FLUSH) };
228      if ret == Z_STREAM_ERROR { return Err(std::io::Error::new(std::io::ErrorKind::Other, "Z_STREAM_ERROR")) }
229      if strm.avail_out as usize == OUT {
230        if ret == Z_STREAM_END { break 'reading }
231        else { continue 'reading }
232      }
233      let end = OUT - strm.avail_out as usize;
234
235      'descending: loop {
236        if finished_path {
237          let have = (end - pos).min(4-lbuf_offset);
238          lbuf[lbuf_offset..lbuf_offset+have].copy_from_slice(&obuffer[pos..pos+have]);
239          pos += have;
240          lbuf_offset += have;
241          if lbuf_offset == 4 {
242            l = u32::from_le_bytes(lbuf);
243            lbuf_offset = 0;
244          } else {
245            if strm.avail_in == 0 { continue 'reading }
246            else { continue 'decompressing }
247          }
248        }
249
250        if pos + l as usize <= end {
251          wz_buf.extend(&obuffer[pos..pos + l as usize]);
252          f(total_paths, &wz_buf[..])?;
253          wz_buf.clear();
254          total_paths += 1;
255          pos += l as usize;
256          finished_path = true;
257          if pos == end { continue 'decompressing }
258          else { continue 'descending }
259        } else {
260          wz_buf.extend(&obuffer[pos..end]);
261          finished_path = false;
262          l -= (end-pos) as u32;
263          if strm.avail_in == 0 { continue 'reading }
264          else { continue 'decompressing }
265        }
266      }
267    }
268  }
269
270  unsafe { inflateEnd(&mut strm) };
271
272  Ok(DeserializationStats {
273    bytes_in   : strm.total_in,
274    bytes_out  : strm.total_out, 
275    path_count : total_paths
276  })
277}
278
279#[cfg(test)]
280mod test {
281  use crate::zipper::{ZipperIteration, ZipperValues, ZipperMoving};
282  use super::*;
283
284  #[cfg(not(miri))] // miri really hates the zlib-ng-sys C API
285  #[test]
286  fn path_serialize_deserialize() {
287    let mut btm = PathMap::new();
288    let rs = ["arrow", "bow", "cannon", "roman", "romane", "romanus", "romulus", "rubens", "ruber", "rubicon", "rubicundus", "rom'i"];
289    rs.iter().for_each(|r| { btm.set_val_at(r.as_bytes(), ()); });
290    let mut v = vec![];
291    match serialize_paths(btm.read_zipper(), &mut v) {
292      Ok(SerializationStats { bytes_out : c, bytes_in : bw, path_count : pw}) => {
293        println!("ser {} {} {}", c, bw, pw);
294        println!("vlen {}", v.len());
295
296        let mut restored_btm = PathMap::new();
297        match deserialize_paths(restored_btm.write_zipper(), v.as_slice(), ()) {
298          Ok(DeserializationStats { bytes_in : c, bytes_out : bw, path_count : pw}) => {
299            println!("de {} {} {}", c, bw, pw);
300
301            let mut lrz = restored_btm.read_zipper();
302            while lrz.to_next_val() {
303              assert!(btm.contains(lrz.path()), "{}", std::str::from_utf8(lrz.path()).unwrap());
304            }
305
306            let mut rrz = btm.read_zipper();
307            while rrz.to_next_val() {
308              assert!(restored_btm.contains(rrz.path()));
309            }
310          }
311          Err(e) => { println!("de e {}", e) }
312        }
313      }
314      Err(e) => { println!("ser e {}", e) }
315    }
316  }
317
318  #[cfg(not(miri))] // miri really hates the zlib-ng-sys C API
319  #[test]
320  fn path_serialize_deserialize_blow_out_buffer() {
321    for zeros in 0..10 {
322      println!("{zeros} zeros");
323      let mut btm = PathMap::new();
324      let mut rs = vec![];
325      for i in 0..400 {
326        rs.push(format!("{}{}{}{}", "0".repeat(zeros), i/100, (i/10)%10, i%10))
327      }
328      rs.iter().for_each(|r| { btm.set_val_at(r.as_bytes(), ()); });
329
330      let mut v = vec![];
331      match serialize_paths(btm.read_zipper(), &mut v) {
332        Ok(SerializationStats { bytes_out : c, bytes_in : bw, path_count : pw}) => {
333          println!("ser {} {} {}", c, bw, pw);
334          println!("vlen {}", v.len());
335
336          let mut restored_btm = PathMap::new();
337          match deserialize_paths(restored_btm.write_zipper(), v.as_slice(), ()) {
338          Ok(DeserializationStats { bytes_in : c, bytes_out : bw, path_count : pw}) => {
339              println!("de {} {} {}", c, bw, pw);
340
341              let mut lrz = restored_btm.read_zipper();
342              while lrz.to_next_val() {
343                assert!(btm.contains(lrz.path()), "{}", std::str::from_utf8(lrz.path()).unwrap());
344              }
345
346              let mut rrz = btm.read_zipper();
347              while rrz.to_next_val() {
348                assert!(restored_btm.contains(rrz.path()));
349              }
350            }
351            Err(e) => { println!("de e {}", e) }
352          }
353        }
354        Err(e) => { println!("ser e {}", e) }
355      }
356    }
357  }
358
359  #[cfg(not(miri))] // miri really hates the zlib-ng-sys C API
360  #[test]
361  fn path_serialize_deserialize_values() {
362    let mut btm = PathMap::new();
363    let rs = ["arrow", "bow", "cannon", "roman", "romane", "romanus", "romulus", "rubens", "ruber", "rubicon", "rubicundus", "rom'i"];
364    rs.iter().enumerate().for_each(|(i, r)| { btm.set_val_at(r.as_bytes(), i); });
365    let mut values = vec![];
366    let mut v = vec![];
367    match serialize_paths_with_auxdata(btm.read_zipper(), &mut v,
368                          |c, _p, value| { assert_eq!(values.len(), c); values.push(*value) }) {
369      Ok(SerializationStats { bytes_out : c, bytes_in : bw, path_count : pw}) => {
370        println!("ser {} {} {}", c, bw, pw);
371        println!("vlen {}", v.len());
372
373        let mut restored_btm = PathMap::new();
374        match deserialize_paths_with_auxdata(restored_btm.write_zipper(), v.as_slice(), |c, _p| values[c]) {
375          Ok(DeserializationStats { bytes_in : c, bytes_out : bw, path_count : pw}) => {
376            println!("de {} {} {}", c, bw, pw);
377
378            let mut lrz = restored_btm.read_zipper();
379            while lrz.to_next_val() {
380              assert_eq!(btm.get_val_at(lrz.path()), Some(lrz.val().unwrap()));
381            }
382
383            let mut rrz = btm.read_zipper();
384            while rrz.to_next_val() {
385              assert_eq!(restored_btm.get_val_at(rrz.path()), Some(rrz.val().unwrap()));
386            }
387          }
388          Err(e) => { println!("de e {}", e) }
389        }
390      }
391      Err(e) => { println!("ser e {}", e) }
392    }
393  }
394
395  #[cfg(not(miri))] // miri really hates the zlib-ng-sys C API
396  #[test]
397  fn path_serialize_deserialize_long_paths_cross_chunk_boundary() {
398    // Paths chosen around the internal 4096-byte compression CHUNK. The dense
399    // representation cannot safely build a single path much longer than 2048
400    // bytes on the test thread, so its fixture uses two such paths to cross the
401    // chunk while still exercising the decompressor's 2048-byte output buffer.
402    // Deterministic xorshift contents make failures reproduce.
403    let mut state = 0x9e3779b97f4a7c15_u64;
404    let mut next = move || { state ^= state << 13; state ^= state >> 7; state ^= state << 17; state };
405    #[cfg(feature = "all_dense_nodes")]
406    let lengths = [7usize, 2049, 2049];
407    #[cfg(not(feature = "all_dense_nodes"))]
408    let lengths = [7usize, 2049, 4097, 24000];
409    let mut btm = PathMap::new();
410    let mut paths = vec![];
411    for &len in &lengths {
412      let path: Vec<u8> = (0..len).map(|_| (next() >> 33) as u8).collect();
413      btm.set_val_at(&path[..], ());
414      paths.push(path);
415    }
416
417    let mut v = vec![];
418    let ser = serialize_paths(btm.read_zipper(), &mut v).unwrap();
419    assert_eq!(ser.path_count, lengths.len());
420    assert!(ser.bytes_in > 4096, "the input must span the compression chunk size");
421
422    let mut restored = PathMap::new();
423    let de = deserialize_paths(restored.write_zipper(), v.as_slice(), ()).unwrap();
424    assert_eq!(de.path_count, lengths.len());
425
426    for path in &paths {
427      assert!(restored.contains(&path[..]), "path of len {} lost in round-trip", path.len());
428    }
429    let mut rz = restored.read_zipper();
430    let mut restored_count = 0;
431    while rz.to_next_val() { restored_count += 1; }
432    assert_eq!(restored_count, lengths.len(), "no extra paths may appear");
433  }
434
435  #[cfg(all(not(miri), not(feature = "all_dense_nodes")))]
436  #[test]
437  fn path_serialize_deserialize_very_long_paths_cross_multiple_chunks() {
438    // Keep coverage for paths that individually span multiple decompressor
439    // output buffers. Dense byte nodes cannot construct these paths without
440    // overflowing the test thread's stack.
441    let mut state = 0x9e3779b97f4a7c15_u64;
442    let mut next = move || { state ^= state << 13; state ^= state >> 7; state ^= state << 17; state };
443    let lengths = [7usize, 2049, 4097, 24000];
444    let mut btm = PathMap::new();
445    let mut paths = vec![];
446    for &len in &lengths {
447      let path: Vec<u8> = (0..len).map(|_| (next() >> 33) as u8).collect();
448      btm.set_val_at(&path[..], ());
449      paths.push(path);
450    }
451
452    let mut v = vec![];
453    let ser = serialize_paths(btm.read_zipper(), &mut v).unwrap();
454    assert_eq!(ser.path_count, lengths.len());
455    assert!(ser.bytes_in > 4096, "the input must span the compression chunk size");
456
457    let mut restored = PathMap::new();
458    let de = deserialize_paths(restored.write_zipper(), v.as_slice(), ()).unwrap();
459    assert_eq!(de.path_count, lengths.len());
460
461    for path in &paths {
462      assert!(restored.contains(&path[..]), "path of len {} lost in round-trip", path.len());
463    }
464    let mut rz = restored.read_zipper();
465    let mut restored_count = 0;
466    while rz.to_next_val() { restored_count += 1; }
467    assert_eq!(restored_count, lengths.len(), "no extra paths may appear");
468  }
469}