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;
103
104 #[inline(never)]
112 pub fn make_view(value: &[u8], block: u32, offset: u32) -> Self {
113 match value.len() {
114 0 => Self {
115 inlined: Inlined::new::<0>(value),
116 },
117 1 => Self {
118 inlined: Inlined::new::<1>(value),
119 },
120 2 => Self {
121 inlined: Inlined::new::<2>(value),
122 },
123 3 => Self {
124 inlined: Inlined::new::<3>(value),
125 },
126 4 => Self {
127 inlined: Inlined::new::<4>(value),
128 },
129 5 => Self {
130 inlined: Inlined::new::<5>(value),
131 },
132 6 => Self {
133 inlined: Inlined::new::<6>(value),
134 },
135 7 => Self {
136 inlined: Inlined::new::<7>(value),
137 },
138 8 => Self {
139 inlined: Inlined::new::<8>(value),
140 },
141 9 => Self {
142 inlined: Inlined::new::<9>(value),
143 },
144 10 => Self {
145 inlined: Inlined::new::<10>(value),
146 },
147 11 => Self {
148 inlined: Inlined::new::<11>(value),
149 },
150 12 => Self {
151 inlined: Inlined::new::<12>(value),
152 },
153 _ => Self::new_ref(
154 u32::try_from(value.len()).vortex_expect("value length must fit in u32"),
155 value[0..4]
156 .try_into()
157 .ok()
158 .vortex_expect("prefix must be exactly 4 bytes"),
159 block,
160 offset,
161 ),
162 }
163 }
164
165 #[inline]
167 pub fn empty_view() -> Self {
168 Self { le_bytes: [0; 16] }
169 }
170
171 #[inline]
180 pub fn new_ref(size: u32, prefix: [u8; 4], buffer_index: u32, offset: u32) -> Self {
181 debug_assert!(size as usize > Self::MAX_INLINED_SIZE);
182 Self::from(
185 u128::from(size)
186 | (u128::from(u32::from_le_bytes(prefix)) << 32)
187 | (u128::from(buffer_index) << 64)
188 | (u128::from(offset) << 96),
189 )
190 }
191
192 #[inline]
198 pub fn new_inlined(value: &[u8]) -> Self {
199 assert!(
200 value.len() <= Self::MAX_INLINED_SIZE,
201 "expected inlined value to be <= 12 bytes, was {}",
202 value.len()
203 );
204
205 Self::make_view(value, 0, 0)
206 }
207
208 #[inline]
210 pub fn len(&self) -> u32 {
211 unsafe { self.inlined.size }
212 }
213
214 #[inline]
216 pub fn is_empty(&self) -> bool {
217 self.len() == 0
218 }
219
220 #[inline]
222 #[expect(
223 clippy::cast_possible_truncation,
224 reason = "MAX_INLINED_SIZE is a small constant"
225 )]
226 pub fn is_inlined(&self) -> bool {
227 self.len() <= (Self::MAX_INLINED_SIZE as u32)
228 }
229
230 pub fn as_inlined(&self) -> &Inlined {
232 debug_assert!(self.is_inlined());
233 unsafe { &self.inlined }
234 }
235
236 pub fn as_view(&self) -> &Ref {
238 debug_assert!(!self.is_inlined());
239 unsafe { &self._ref }
240 }
241
242 pub fn as_view_mut(&mut self) -> &mut Ref {
244 unsafe { &mut self._ref }
245 }
246
247 pub fn as_u128(&self) -> u128 {
249 unsafe { u128::from_le_bytes(self.le_bytes) }
251 }
252}
253
254impl From<u128> for BinaryView {
255 fn from(value: u128) -> Self {
256 BinaryView {
257 le_bytes: value.to_le_bytes(),
258 }
259 }
260}
261
262impl From<Ref> for BinaryView {
263 fn from(value: Ref) -> Self {
264 BinaryView { _ref: value }
265 }
266}
267
268impl PartialEq for BinaryView {
269 fn eq(&self, other: &Self) -> bool {
270 let a = unsafe { std::mem::transmute::<&BinaryView, &u128>(self) };
271 let b = unsafe { std::mem::transmute::<&BinaryView, &u128>(other) };
272 a == b
273 }
274}
275impl Eq for BinaryView {}
276
277impl Hash for BinaryView {
278 fn hash<H: Hasher>(&self, state: &mut H) {
279 unsafe { std::mem::transmute::<&BinaryView, &u128>(self) }.hash(state);
280 }
281}
282
283impl Default for BinaryView {
284 fn default() -> Self {
285 Self::make_view(&[], 0, 0)
286 }
287}
288
289impl fmt::Debug for BinaryView {
290 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291 let mut s = f.debug_struct("BinaryView");
292 if self.is_inlined() {
293 s.field("inline", &self.as_inlined());
294 } else {
295 s.field("ref", &self.as_view());
296 }
297 s.finish()
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 #[rstest::rstest]
306 #[case(13, 7, 42)]
308 #[case(20, 7, 42)]
309 #[case(255, 7, 42)]
310 #[case(4096, 7, 42)]
311 #[case(13, 0, 0)]
314 #[case(13, u32::MAX, u32::MAX)]
315 fn new_ref_matches_make_view(#[case] len: u32, #[case] buffer_index: u32, #[case] offset: u32) {
316 let value: Vec<u8> = (0..len)
319 .map(|i| u8::try_from(i % 251).vortex_expect("i % 251 fits in u8"))
320 .collect();
321 let prefix = [value[0], value[1], value[2], value[3]];
322 let made = BinaryView::make_view(&value, buffer_index, offset);
323 let built = BinaryView::new_ref(len, prefix, buffer_index, offset);
324 assert_eq!(made.as_u128(), built.as_u128(), "mismatch at len {len}");
325 assert!(!built.is_inlined());
326 let r = built.as_view();
327 assert_eq!(r.size, len);
328 assert_eq!(r.prefix, prefix);
329 assert_eq!(r.buffer_index, buffer_index);
330 assert_eq!(r.offset, offset);
331 }
332}