vortex_array/arrays/varbinview/
view.rs1use std::fmt;
7use std::hash::Hash;
8use std::hash::Hasher;
9use std::ops::Range;
10
11use static_assertions::assert_eq_align;
12use static_assertions::assert_eq_size;
13use vortex_error::VortexExpect;
14
15#[derive(Clone, Copy)]
20#[repr(C, align(16))]
21pub union BinaryView {
22 pub(crate) le_bytes: [u8; 16],
25
26 pub(crate) inlined: Inlined,
28
29 pub(crate) _ref: Ref,
31}
32
33assert_eq_align!(BinaryView, u128);
34assert_eq_size!(BinaryView, [u8; 16]);
35assert_eq_size!(Inlined, [u8; 16]);
36assert_eq_size!(Ref, [u8; 16]);
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40#[repr(C, align(8))]
41pub struct Inlined {
42 pub size: u32,
44 pub data: [u8; BinaryView::MAX_INLINED_SIZE],
46}
47
48impl Inlined {
49 fn new<const N: usize>(value: &[u8]) -> Self {
51 debug_assert_eq!(value.len(), N);
52 let mut inlined = Self {
53 size: N.try_into().vortex_expect("inlined size must fit in u32"),
54 data: [0u8; BinaryView::MAX_INLINED_SIZE],
55 };
56 inlined.data[..N].copy_from_slice(&value[..N]);
57 inlined
58 }
59
60 #[inline]
62 pub fn value(&self) -> &[u8] {
63 &self.data[0..(self.size as usize)]
64 }
65}
66
67#[derive(Clone, Copy, Debug)]
69#[repr(C, align(8))]
70pub struct Ref {
71 pub size: u32,
73 pub prefix: [u8; 4],
75 pub buffer_index: u32,
77 pub offset: u32,
79}
80
81impl Ref {
82 #[inline]
84 pub fn as_range(&self) -> Range<usize> {
85 self.offset as usize..(self.offset + self.size) as usize
86 }
87
88 #[inline]
90 pub fn with_buffer_and_offset(&self, buffer_index: u32, offset: u32) -> Ref {
91 Self {
92 size: self.size,
93 prefix: self.prefix,
94 buffer_index,
95 offset,
96 }
97 }
98}
99
100impl BinaryView {
101 pub const MAX_INLINED_SIZE: usize = 12;
105
106 #[inline(never)]
114 pub fn make_view(value: &[u8], block: u32, offset: u32) -> Self {
115 match value.len() {
116 0 => Self {
117 inlined: Inlined::new::<0>(value),
118 },
119 1 => Self {
120 inlined: Inlined::new::<1>(value),
121 },
122 2 => Self {
123 inlined: Inlined::new::<2>(value),
124 },
125 3 => Self {
126 inlined: Inlined::new::<3>(value),
127 },
128 4 => Self {
129 inlined: Inlined::new::<4>(value),
130 },
131 5 => Self {
132 inlined: Inlined::new::<5>(value),
133 },
134 6 => Self {
135 inlined: Inlined::new::<6>(value),
136 },
137 7 => Self {
138 inlined: Inlined::new::<7>(value),
139 },
140 8 => Self {
141 inlined: Inlined::new::<8>(value),
142 },
143 9 => Self {
144 inlined: Inlined::new::<9>(value),
145 },
146 10 => Self {
147 inlined: Inlined::new::<10>(value),
148 },
149 11 => Self {
150 inlined: Inlined::new::<11>(value),
151 },
152 12 => Self {
153 inlined: Inlined::new::<12>(value),
154 },
155 _ => Self::new_ref(
156 u32::try_from(value.len()).vortex_expect("value length must fit in u32"),
157 value[0..4]
158 .try_into()
159 .ok()
160 .vortex_expect("prefix must be exactly 4 bytes"),
161 block,
162 offset,
163 ),
164 }
165 }
166
167 #[inline]
169 pub fn empty_view() -> Self {
170 Self { le_bytes: [0; 16] }
171 }
172
173 #[inline]
182 pub fn new_ref(size: u32, prefix: [u8; 4], buffer_index: u32, offset: u32) -> Self {
183 debug_assert!(size as usize > Self::MAX_INLINED_SIZE);
184 Self::from(
187 u128::from(size)
188 | (u128::from(u32::from_le_bytes(prefix)) << 32)
189 | (u128::from(buffer_index) << 64)
190 | (u128::from(offset) << 96),
191 )
192 }
193
194 #[inline]
200 pub fn new_inlined(value: &[u8]) -> Self {
201 assert!(
202 value.len() <= Self::MAX_INLINED_SIZE,
203 "expected inlined value to be <= 12 bytes, was {}",
204 value.len()
205 );
206
207 Self::make_view(value, 0, 0)
208 }
209
210 #[inline]
212 pub fn len(&self) -> u32 {
213 unsafe { self.inlined.size }
214 }
215
216 #[inline]
218 pub fn is_empty(&self) -> bool {
219 self.len() == 0
220 }
221
222 #[inline]
224 #[expect(
225 clippy::cast_possible_truncation,
226 reason = "MAX_INLINED_SIZE is a small constant"
227 )]
228 pub fn is_inlined(&self) -> bool {
229 self.len() <= (Self::MAX_INLINED_SIZE as u32)
230 }
231
232 pub fn as_inlined(&self) -> &Inlined {
234 debug_assert!(self.is_inlined());
235 unsafe { &self.inlined }
236 }
237
238 pub fn as_view(&self) -> &Ref {
240 debug_assert!(!self.is_inlined());
241 unsafe { &self._ref }
242 }
243
244 pub fn as_view_mut(&mut self) -> &mut Ref {
246 unsafe { &mut self._ref }
247 }
248
249 #[inline]
260 pub fn bytes<'a>(&'a self, buffers: &[&'a [u8]]) -> &'a [u8] {
261 if self.is_inlined() {
262 self.as_inlined().value()
263 } else {
264 let view = self.as_view();
265 &buffers[view.buffer_index as usize][view.as_range()]
266 }
267 }
268
269 pub fn as_u128(&self) -> u128 {
271 unsafe { u128::from_le_bytes(self.le_bytes) }
273 }
274}
275
276impl From<u128> for BinaryView {
277 fn from(value: u128) -> Self {
278 BinaryView {
279 le_bytes: value.to_le_bytes(),
280 }
281 }
282}
283
284impl From<Ref> for BinaryView {
285 fn from(value: Ref) -> Self {
286 BinaryView { _ref: value }
287 }
288}
289
290impl PartialEq for BinaryView {
291 fn eq(&self, other: &Self) -> bool {
292 let a = unsafe { std::mem::transmute::<&BinaryView, &u128>(self) };
293 let b = unsafe { std::mem::transmute::<&BinaryView, &u128>(other) };
294 a == b
295 }
296}
297impl Eq for BinaryView {}
298
299impl Hash for BinaryView {
300 fn hash<H: Hasher>(&self, state: &mut H) {
301 unsafe { std::mem::transmute::<&BinaryView, &u128>(self) }.hash(state);
302 }
303}
304
305impl Default for BinaryView {
306 fn default() -> Self {
307 Self::make_view(&[], 0, 0)
308 }
309}
310
311impl fmt::Debug for BinaryView {
312 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
313 let mut s = f.debug_struct("BinaryView");
314 if self.is_inlined() {
315 s.field("inline", &self.as_inlined());
316 } else {
317 s.field("ref", &self.as_view());
318 }
319 s.finish()
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326
327 #[rstest::rstest]
328 #[case(13, 7, 42)]
330 #[case(20, 7, 42)]
331 #[case(255, 7, 42)]
332 #[case(4096, 7, 42)]
333 #[case(13, 0, 0)]
336 #[case(13, u32::MAX, u32::MAX)]
337 fn new_ref_matches_make_view(#[case] len: u32, #[case] buffer_index: u32, #[case] offset: u32) {
338 let value: Vec<u8> = (0..len)
341 .map(|i| u8::try_from(i % 251).vortex_expect("i % 251 fits in u8"))
342 .collect();
343 let prefix = [value[0], value[1], value[2], value[3]];
344 let made = BinaryView::make_view(&value, buffer_index, offset);
345 let built = BinaryView::new_ref(len, prefix, buffer_index, offset);
346 assert_eq!(made.as_u128(), built.as_u128(), "mismatch at len {len}");
347 assert!(!built.is_inlined());
348 let r = built.as_view();
349 assert_eq!(r.size, len);
350 assert_eq!(r.prefix, prefix);
351 assert_eq!(r.buffer_index, buffer_index);
352 assert_eq!(r.offset, offset);
353 }
354}