heapdump_vmo/
stack_trace_compression.rs
1pub const fn max_compressed_size(num_frames: usize) -> usize {
8 num_frames * VARINT_MAX_ENCODED_LEN
9}
10
11pub fn compress_into(src: &[u64], dest: &mut [u8]) -> usize {
19 assert!(dest.len() >= max_compressed_size(src.len()), "dest buffer is not big enough");
20
21 let mut offset = 0;
22 let mut prev = 0;
23 for value in src {
24 let zigzag = zigzag_encode(value.wrapping_sub(prev));
25 offset += varint_encode(zigzag, &mut dest[offset..]);
26 prev = *value;
27 }
28
29 offset
30}
31
32pub fn compress(src: &[u64]) -> Vec<u8> {
34 let mut buf = vec![0; max_compressed_size(src.len())];
35 let compressed_size = compress_into(src, &mut buf);
36 buf.truncate(compressed_size);
37 buf
38}
39
40pub fn uncompress(src: &[u8]) -> Result<Vec<u64>, crate::Error> {
42 let mut result = Vec::new();
43
44 let mut offset = 0;
45 let mut value = 0;
46 while offset != src.len() {
47 let (zigzag, num_bytes) =
48 varint_decode(&src[offset..]).ok_or(crate::Error::InvalidInput)?;
49 offset += num_bytes;
50 value = zigzag_decode(zigzag).wrapping_add(value);
51 result.push(value);
52 }
53
54 Ok(result)
55}
56
57const VARINT_SHIFT: u32 = 7;
58const VARINT_VALUE_MASK: u8 = 0x7f;
59const VARINT_CONT_BIT: u8 = 0x80;
60const VARINT_MAX_ENCODED_LEN: usize = 10; fn varint_encode(mut value: u64, dest: &mut [u8]) -> usize {
64 assert!(dest.len() >= VARINT_MAX_ENCODED_LEN, "dest buffer is not big enough");
65
66 let mut offset = 0;
67 loop {
68 dest[offset] = value as u8 & VARINT_VALUE_MASK;
69 value >>= VARINT_SHIFT;
70
71 if value != 0 {
72 dest[offset] |= VARINT_CONT_BIT;
73 offset += 1;
74 } else {
75 return offset + 1;
76 }
77 }
78}
79
80fn varint_decode(src: &[u8]) -> Option<(u64, usize)> {
83 let mut result = 0;
84 let mut offset = 0;
85 let mut shift = 0;
86 loop {
87 let input = src.get(offset)?;
89
90 let value = (input & VARINT_VALUE_MASK) as u64;
93 let value_shifted = value.checked_shl(shift)?;
94 if (value_shifted >> shift) != value {
95 return None; }
97
98 result |= value_shifted;
99 if (input & VARINT_CONT_BIT) != 0 {
100 shift += VARINT_SHIFT;
101 offset += 1;
102 } else {
103 return Some((result, offset + 1));
104 }
105 }
106}
107
108const ZIGZAG_VALUE_MASK: u64 = !1;
109const ZIGZAG_SIGN_BIT: u64 = 1;
110
111fn zigzag_encode(mut value: u64) -> u64 {
112 value = value.rotate_left(1);
113 if (value & ZIGZAG_SIGN_BIT) != 0 {
114 value ^= ZIGZAG_VALUE_MASK;
115 }
116 value
117}
118
119fn zigzag_decode(mut value: u64) -> u64 {
120 if (value & ZIGZAG_SIGN_BIT) != 0 {
121 value ^= ZIGZAG_VALUE_MASK;
122 }
123 value.rotate_right(1)
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use test_case::test_case;
130
131 const A: u64 = 0x0000_008a_b3fe_9821;
133 const B: u64 = 0x0000_812c_6a4a_682e;
134 const C: u64 = 0x0048_b5a0_2d45_9e5a;
135 const D: u64 = 0xcccc_f148_39a5_9c5a;
136 const F: u64 = 0xffff_ffff_ffff_ffff; const Z: u64 = 0x0000_0000_0000_0000; #[test_case(&[] ; "empty")]
140 #[test_case(&[A] ; "A")]
141 #[test_case(&[B] ; "B")]
142 #[test_case(&[C] ; "C")]
143 #[test_case(&[D] ; "D")]
144 #[test_case(&[A, B] ; "AB")]
145 #[test_case(&[A, A] ; "AA")]
146 #[test_case(&[A, A, B] ; "AAB")]
147 #[test_case(&[A, B, C, D] ; "ABCD")]
148 #[test_case(&[A, A, B, B, C, C, D, D] ; "AABBCCDD")]
149 #[test_case(&[D, D, C, C, B, B, A, A] ; "DDCCBBAA")]
150 #[test_case(&[F, F, A, F, F] ; "FFAFF")]
151 #[test_case(&[Z, Z, A, Z, Z] ; "ZZAFF")]
152 #[test_case(&[F, Z, F] ; "FZF")]
153 #[test_case(&[Z, F, Z] ; "ZFZ")]
154 fn test_compress_and_uncompress(stack_trace: &[u64]) {
155 let compressed_data = compress(stack_trace);
156 assert_eq!(stack_trace, &uncompress(&compressed_data).unwrap());
157 }
158
159 #[test_case(&[] ; "empty")]
160 #[test_case(&[0xff, 0xff] ; "ends with CONT bit set")]
161 #[test_case(&[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x02] ;
162 "valid encoding but too big (u64::MAX + 1)")]
163 #[test_case(&[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01] ;
164 "valid encoding but too big (u64::MAX * 2^14)")]
165 fn test_varint_decode_bad(data: &[u8]) {
166 assert_eq!(varint_decode(data), None);
167 }
168
169 #[test_case(0 ; "0")]
170 #[test_case(1 ; "1")]
171 #[test_case(-1 ; "negative 1")]
172 #[test_case(1000 ; "1000")]
173 #[test_case(-1000 ; "negative 1000")]
174 #[test_case(i64::MIN ; "min")]
175 #[test_case(i64::MAX ; "max")]
176 fn test_zigzag(value: i64) {
177 let encoded_value = zigzag_encode(value as u64);
178 let decoded_value = zigzag_decode(encoded_value) as i64;
179 assert_eq!(decoded_value, value, "encoded value: {:x}", encoded_value);
180 }
181}