1#![warn(missing_docs)]
29
30#[cfg(target_endian = "big")]
31compile_error!("kevy-bytes requires little-endian: heap-tag byte overlaps inline length byte");
32
33use std::alloc::{Layout, alloc, dealloc, handle_alloc_error};
34use std::cmp::Ordering;
35use std::fmt;
36use std::hash::{Hash, Hasher};
37use std::mem::{self, ManuallyDrop};
38use std::ptr::NonNull;
39use std::slice;
40
41const INLINE_CAP: usize = 23;
42const INLINE_LEN_MAX: u8 = (INLINE_CAP - 1) as u8;
43
44#[cfg(target_pointer_width = "64")]
45const TAG_HEAP_BIT: usize = 0xFFusize << 56;
46#[cfg(target_pointer_width = "64")]
47const CAP_MASK: usize = (1usize << 56) - 1;
48
49#[cfg(target_pointer_width = "32")]
53const HEAP_TAG_BYTE: u8 = 0xFF;
54
55#[repr(C)]
56#[derive(Copy, Clone)]
57struct Inline {
58 data: [u8; INLINE_CAP],
59 tag: u8,
63}
64
65#[cfg(target_pointer_width = "64")]
70#[repr(C)]
71#[derive(Copy, Clone)]
72struct Heap {
73 ptr: NonNull<u8>,
74 len: usize,
75 cap_and_tag: usize,
78}
79
80#[cfg(target_pointer_width = "32")]
86#[repr(C)]
87#[derive(Copy, Clone)]
88struct Heap {
89 ptr: NonNull<u8>,
90 len: u32,
91 cap: u32,
92 _pad: [u8; 11],
93 tag: u8,
94}
95
96impl Heap {
97 #[cfg(target_pointer_width = "64")]
100 #[inline]
101 fn new(ptr: NonNull<u8>, len: usize, cap: usize) -> Self {
102 debug_assert!(cap <= CAP_MASK, "kevy-bytes: capacity exceeds 56-bit field");
103 Self {
104 ptr,
105 len,
106 cap_and_tag: TAG_HEAP_BIT | (cap & CAP_MASK),
107 }
108 }
109 #[cfg(target_pointer_width = "32")]
110 #[inline]
111 fn new(ptr: NonNull<u8>, len: usize, cap: usize) -> Self {
112 debug_assert!(
116 len <= u32::MAX as usize && cap <= u32::MAX as usize,
117 "kevy-bytes: len/cap exceeds u32 on 32-bit platform"
118 );
119 Self {
120 ptr,
121 len: len as u32,
122 cap: cap as u32,
123 _pad: [0; 11],
124 tag: HEAP_TAG_BYTE,
125 }
126 }
127
128 #[cfg(target_pointer_width = "64")]
131 #[inline]
132 fn capacity(&self) -> usize {
133 self.cap_and_tag & CAP_MASK
134 }
135 #[cfg(target_pointer_width = "32")]
136 #[inline]
137 fn capacity(&self) -> usize {
138 self.cap as usize
139 }
140
141 #[cfg(target_pointer_width = "64")]
143 #[inline]
144 fn length(&self) -> usize {
145 self.len
146 }
147 #[cfg(target_pointer_width = "32")]
148 #[inline]
149 fn length(&self) -> usize {
150 self.len as usize
151 }
152}
153
154#[repr(C)]
163pub union SmallBytes {
164 inline: Inline,
165 heap: Heap,
166}
167
168const _: () = {
169 assert!(mem::size_of::<SmallBytes>() == 24);
170 assert!(mem::align_of::<SmallBytes>() == mem::align_of::<usize>());
171};
172
173unsafe impl Send for SmallBytes {}
174unsafe impl Sync for SmallBytes {}
175
176impl SmallBytes {
177 pub const fn new() -> Self {
179 Self {
180 inline: Inline {
181 data: [0; INLINE_CAP],
182 tag: 0,
183 },
184 }
185 }
186
187 pub fn from_slice(bytes: &[u8]) -> Self {
189 if bytes.len() <= INLINE_LEN_MAX as usize {
190 let mut data = [0u8; INLINE_CAP];
191 unsafe {
193 std::ptr::copy_nonoverlapping(bytes.as_ptr(), data.as_mut_ptr(), bytes.len());
194 }
195 Self {
196 inline: Inline {
197 data,
198 tag: bytes.len() as u8,
199 },
200 }
201 } else {
202 Self::alloc_heap(bytes)
203 }
204 }
205
206 pub fn from_vec(vec: Vec<u8>) -> Self {
209 if vec.len() <= INLINE_LEN_MAX as usize {
210 Self::from_slice(&vec)
211 } else {
212 let mut v = ManuallyDrop::new(vec);
213 let ptr = unsafe { NonNull::new_unchecked(v.as_mut_ptr()) };
217 let len = v.len();
218 let cap = v.capacity();
219 Self {
220 heap: Heap::new(ptr, len, cap),
221 }
222 }
223 }
224
225 #[inline]
226 fn alloc_heap(bytes: &[u8]) -> Self {
227 let len = bytes.len();
228 let layout = unsafe { Layout::from_size_align_unchecked(len, 1) };
233 let raw = unsafe { alloc(layout) };
235 let ptr = match NonNull::new(raw) {
236 Some(p) => p,
237 None => handle_alloc_error(layout),
238 };
239 unsafe {
242 std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.as_ptr(), len);
243 }
244 Self {
245 heap: Heap::new(ptr, len, len),
246 }
247 }
248
249 #[inline]
252 fn is_inline(&self) -> bool {
253 unsafe { self.inline.tag <= INLINE_LEN_MAX }
257 }
258
259 #[inline]
261 pub fn len(&self) -> usize {
262 if self.is_inline() {
263 unsafe { self.inline.tag as usize }
265 } else {
266 unsafe { self.heap.length() }
268 }
269 }
270
271 #[inline]
273 pub fn is_empty(&self) -> bool {
274 self.len() == 0
275 }
276
277 #[inline]
281 pub fn heap_bytes(&self) -> usize {
282 if self.is_inline() { 0 } else { self.len() }
283 }
284
285 #[inline]
287 pub fn as_slice(&self) -> &[u8] {
288 if self.is_inline() {
289 unsafe {
291 slice::from_raw_parts(self.inline.data.as_ptr(), self.inline.tag as usize)
292 }
293 } else {
294 unsafe { slice::from_raw_parts(self.heap.ptr.as_ptr(), self.heap.length()) }
296 }
297 }
298
299 pub fn to_vec(&self) -> Vec<u8> {
301 self.as_slice().to_vec()
302 }
303
304 pub fn into_vec(self) -> Vec<u8> {
307 if self.is_inline() {
308 self.as_slice().to_vec()
309 } else {
311 let (ptr, len, cap) = unsafe {
313 (
314 self.heap.ptr.as_ptr(),
315 self.heap.length(),
316 self.heap.capacity(),
317 )
318 };
319 let _do_not_drop = ManuallyDrop::new(self);
321 unsafe { Vec::from_raw_parts(ptr, len, cap) }
325 }
326 }
327}
328
329impl Default for SmallBytes {
330 fn default() -> Self {
331 Self::new()
332 }
333}
334
335impl Drop for SmallBytes {
336 fn drop(&mut self) {
337 if self.is_inline() {
338 return;
339 }
340 unsafe {
344 let cap = self.heap.capacity();
345 let layout = Layout::array::<u8>(cap).expect("kevy-bytes: drop layout");
346 dealloc(self.heap.ptr.as_ptr(), layout);
347 }
348 }
349}
350
351impl Clone for SmallBytes {
352 #[inline]
357 fn clone(&self) -> Self {
358 if self.is_inline() {
359 unsafe { Self { inline: self.inline } }
364 } else {
365 unsafe { self.clone_heap() }
367 }
368 }
369}
370
371impl SmallBytes {
372 #[inline]
379 unsafe fn clone_heap(&self) -> Self {
380 let (src_ptr, len) = unsafe { (self.heap.ptr.as_ptr(), self.heap.length()) };
383 let layout = unsafe { Layout::from_size_align_unchecked(len, 1) };
387 let raw = unsafe { alloc(layout) };
389 let ptr = match NonNull::new(raw) {
390 Some(p) => p,
391 None => handle_alloc_error(layout),
392 };
393 unsafe { std::ptr::copy_nonoverlapping(src_ptr, ptr.as_ptr(), len) };
396 Self {
397 heap: Heap::new(ptr, len, len),
398 }
399 }
400}
401
402impl fmt::Debug for SmallBytes {
403 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404 f.debug_list().entries(self.as_slice().iter()).finish()
406 }
407}
408
409impl PartialEq for SmallBytes {
410 #[inline]
416 fn eq(&self, other: &Self) -> bool {
417 let self_tag = unsafe { self.inline.tag };
421 let other_tag = unsafe { other.inline.tag };
422 let self_inline = self_tag <= INLINE_LEN_MAX;
423 let other_inline = other_tag <= INLINE_LEN_MAX;
424 match (self_inline, other_inline) {
425 (true, true) => {
426 let len = self_tag as usize;
427 if len != other_tag as usize {
428 return false;
429 }
430 let a = unsafe {
432 slice::from_raw_parts(self.inline.data.as_ptr(), len)
433 };
434 let b = unsafe {
435 slice::from_raw_parts(other.inline.data.as_ptr(), len)
436 };
437 a == b
438 }
439 (false, false) => {
440 let (a_len, b_len) =
442 unsafe { (self.heap.length(), other.heap.length()) };
443 if a_len != b_len {
444 return false;
445 }
446 let a = unsafe {
448 slice::from_raw_parts(self.heap.ptr.as_ptr(), a_len)
449 };
450 let b = unsafe {
451 slice::from_raw_parts(other.heap.ptr.as_ptr(), b_len)
452 };
453 a == b
454 }
455 _ => unreachable!(
461 "kevy-bytes invariant: a heap variant never carries len ≤ 22"
462 ),
463 }
464 }
465}
466impl Eq for SmallBytes {}
467
468impl PartialOrd for SmallBytes {
469 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
470 Some(self.cmp(other))
471 }
472}
473impl Ord for SmallBytes {
474 fn cmp(&self, other: &Self) -> Ordering {
475 self.as_slice().cmp(other.as_slice())
476 }
477}
478
479impl Hash for SmallBytes {
480 fn hash<H: Hasher>(&self, state: &mut H) {
481 self.as_slice().hash(state);
482 }
483}
484
485impl AsRef<[u8]> for SmallBytes {
486 fn as_ref(&self) -> &[u8] {
487 self.as_slice()
488 }
489}
490
491impl std::borrow::Borrow<[u8]> for SmallBytes {
492 fn borrow(&self) -> &[u8] {
493 self.as_slice()
494 }
495}
496
497impl kevy_hash::KevyHash for SmallBytes {
500 #[inline]
501 fn kevy_hash(&self) -> u64 {
502 self.as_slice().kevy_hash()
503 }
504}
505
506impl From<&[u8]> for SmallBytes {
507 fn from(bytes: &[u8]) -> Self {
508 Self::from_slice(bytes)
509 }
510}
511
512impl From<Vec<u8>> for SmallBytes {
513 fn from(vec: Vec<u8>) -> Self {
514 Self::from_vec(vec)
515 }
516}
517
518#[cfg(test)]
519mod tests {
520 use super::*;
521 use kevy_hash::KevyHash as _;
522
523 #[test]
524 fn size_and_align() {
525 assert_eq!(mem::size_of::<SmallBytes>(), 24);
526 assert_eq!(mem::align_of::<SmallBytes>(), mem::align_of::<usize>());
527 }
528
529 #[test]
530 fn empty_is_inline() {
531 let s = SmallBytes::new();
532 assert!(s.is_inline());
533 assert_eq!(s.len(), 0);
534 assert!(s.is_empty());
535 assert_eq!(s.as_slice(), b"");
536 }
537
538 #[test]
539 fn inline_one_byte() {
540 let s = SmallBytes::from_slice(b"x");
541 assert!(s.is_inline());
542 assert_eq!(s.len(), 1);
543 assert_eq!(s.as_slice(), b"x");
544 }
545
546 #[test]
547 fn inline_at_boundary_22() {
548 let v: Vec<u8> = (0u8..22).collect();
549 let s = SmallBytes::from_slice(&v);
550 assert!(s.is_inline());
551 assert_eq!(s.len(), 22);
552 assert_eq!(s.as_slice(), v);
553 }
554
555 #[test]
556 fn heap_at_boundary_23() {
557 let v: Vec<u8> = (0u8..23).collect();
558 let s = SmallBytes::from_slice(&v);
559 assert!(!s.is_inline());
560 assert_eq!(s.len(), 23);
561 assert_eq!(s.as_slice(), v);
562 }
563
564 #[test]
565 fn heap_large() {
566 let v: Vec<u8> = (0..4096).map(|i| (i & 0xFF) as u8).collect();
567 let s = SmallBytes::from_slice(&v);
568 assert!(!s.is_inline());
569 assert_eq!(s.len(), 4096);
570 assert_eq!(s.as_slice(), v.as_slice());
571 }
572
573 #[test]
574 fn from_vec_inline() {
575 let s = SmallBytes::from_vec(vec![1u8, 2, 3]);
576 assert!(s.is_inline());
577 assert_eq!(s.as_slice(), &[1, 2, 3]);
578 }
579
580 #[test]
581 fn from_vec_heap_reuses_alloc() {
582 let mut v: Vec<u8> = (0u8..100).collect();
583 v.reserve(200);
584 let ptr_before = v.as_ptr();
585 let cap_before = v.capacity();
586 let s = SmallBytes::from_vec(v);
587 assert!(!s.is_inline());
588 unsafe {
590 assert_eq!(s.heap.ptr.as_ptr() as *const u8, ptr_before);
591 assert_eq!(s.heap.capacity(), cap_before);
592 }
593 }
594
595 #[test]
596 fn into_vec_inline_copies() {
597 let s = SmallBytes::from_slice(b"hello");
598 let v = s.into_vec();
599 assert_eq!(v, b"hello");
600 }
601
602 #[test]
603 fn into_vec_heap_reuses_alloc() {
604 let original: Vec<u8> = (0u8..200).collect();
605 let ptr = original.as_ptr();
606 let cap = original.capacity();
607 let s = SmallBytes::from_vec(original);
608 let v = s.into_vec();
609 assert_eq!(v.as_ptr(), ptr);
610 assert_eq!(v.capacity(), cap);
611 assert_eq!(v.len(), 200);
612 }
613
614 #[test]
615 fn clone_inline() {
616 let s = SmallBytes::from_slice(b"abc");
617 let c = s.clone();
618 assert_eq!(s, c);
619 assert!(c.is_inline());
620 }
621
622 #[test]
623 fn clone_heap() {
624 let v: Vec<u8> = (0u8..50).collect();
625 let s = SmallBytes::from_slice(&v);
626 let c = s.clone();
627 assert_eq!(s, c);
628 assert!(!c.is_inline());
629 }
630
631 #[test]
632 fn eq_by_content() {
633 let a = SmallBytes::from_slice(b"short");
634 let b = SmallBytes::from_slice(b"short");
635 assert_eq!(a, b);
636 let c: Vec<u8> = (0u8..30).collect();
637 let d: Vec<u8> = (0u8..30).collect();
638 assert_eq!(SmallBytes::from_slice(&c), SmallBytes::from_slice(&d));
639 }
640
641 #[test]
642 fn ord_lex() {
643 let a = SmallBytes::from_slice(b"abc");
644 let b = SmallBytes::from_slice(b"abd");
645 assert!(a < b);
646 }
647
648 #[test]
649 fn debug_format_matches_slice() {
650 let s = SmallBytes::from_slice(&[1u8, 2, 3]);
651 let dbg = format!("{s:?}");
652 let exp = format!("{:?}", &[1u8, 2, 3][..]);
653 assert_eq!(dbg, exp);
654 }
655
656 #[test]
657 fn default_is_empty_inline() {
658 let s = SmallBytes::default();
659 assert!(s.is_inline());
660 assert_eq!(s.len(), 0);
661 }
662
663 #[test]
664 fn drop_heap_does_not_leak_or_double_free() {
665 for n in [23usize, 64, 1024, 65536] {
667 let v: Vec<u8> = (0..n).map(|i| (i & 0xFF) as u8).collect();
668 let s = SmallBytes::from_slice(&v);
669 drop(s);
670 }
671 }
672
673 #[test]
676 fn eq_is_reflexive_and_symmetric_inline() {
677 let a = SmallBytes::from_slice(b"hi");
678 let b = SmallBytes::from_slice(b"hi");
679 let c = SmallBytes::from_slice(b"no");
680 assert_eq!(a, a);
681 assert_eq!(a, b);
682 assert_eq!(b, a);
683 assert_ne!(a, c);
684 }
685
686 #[test]
687 fn eq_is_reflexive_and_symmetric_heap() {
688 let v: Vec<u8> = (0u8..40).collect();
689 let a = SmallBytes::from_slice(&v);
690 let b = SmallBytes::from_slice(&v);
691 let mut w = v.clone();
692 w[0] = w[0].wrapping_add(1);
693 let c = SmallBytes::from_slice(&w);
694 assert_eq!(a, a);
695 assert_eq!(a, b);
696 assert_eq!(b, a);
697 assert_ne!(a, c);
698 }
699
700 #[test]
701 fn partial_cmp_matches_cmp_inline() {
702 let a = SmallBytes::from_slice(b"abc");
703 let b = SmallBytes::from_slice(b"abd");
704 assert_eq!(a.partial_cmp(&b), Some(std::cmp::Ordering::Less));
705 assert_eq!(b.partial_cmp(&a), Some(std::cmp::Ordering::Greater));
706 assert_eq!(a.partial_cmp(&a), Some(std::cmp::Ordering::Equal));
707 assert_eq!(a.cmp(&b), std::cmp::Ordering::Less);
709 assert_eq!(a.cmp(&a), std::cmp::Ordering::Equal);
710 }
711
712 #[test]
713 fn hash_agrees_with_byte_slice() {
714 use std::collections::hash_map::DefaultHasher;
715 let v: Vec<u8> = (0u8..40).collect();
716 let s = SmallBytes::from_slice(&v);
717 let mut h_slice = DefaultHasher::new();
718 v.as_slice().hash(&mut h_slice);
719 let mut h_sb = DefaultHasher::new();
720 s.hash(&mut h_sb);
721 assert_eq!(h_slice.finish(), h_sb.finish());
724 }
725
726 #[test]
727 fn kevy_hash_agrees_with_byte_slice() {
728 let v: Vec<u8> = (0u8..40).collect();
729 let s = SmallBytes::from_slice(&v);
730 assert_eq!(
731 s.kevy_hash(),
732 v.as_slice().kevy_hash(),
733 "KevyHash impl must agree with &[u8] so a KevyMap<SmallBytes, V> can be queried by Borrow<[u8]>"
734 );
735 let small = SmallBytes::from_slice(b"foo");
736 assert_eq!(small.kevy_hash(), (b"foo" as &[u8]).kevy_hash());
737 }
738
739 #[test]
740 fn as_ref_is_zero_copy_view() {
741 let s = SmallBytes::from_slice(b"abcdef");
742 let r: &[u8] = s.as_ref();
743 assert_eq!(r, b"abcdef");
744 assert!(std::ptr::eq(r.as_ptr(), s.as_slice().as_ptr()));
746 }
747
748 #[test]
749 fn borrow_lookup_works_in_collection() {
750 use std::collections::HashMap;
751 let mut m: HashMap<SmallBytes, i32> = HashMap::new();
752 m.insert(SmallBytes::from_slice(b"key1"), 1);
753 m.insert(SmallBytes::from_slice(b"key2"), 2);
754 assert_eq!(m.get(b"key1".as_slice()), Some(&1));
756 assert_eq!(m.get(b"key2".as_slice()), Some(&2));
757 assert_eq!(m.get(b"none".as_slice()), None);
758 }
759
760 #[test]
761 fn from_byte_slice_round_trip() {
762 let a: SmallBytes = (&b"short"[..]).into();
763 assert_eq!(a.as_slice(), b"short");
764 let v: Vec<u8> = (0u8..40).collect();
765 let b: SmallBytes = v.as_slice().into();
766 assert_eq!(b.as_slice(), v.as_slice());
767 assert!(!b.is_inline());
768 }
769
770 #[test]
771 fn from_vec_dispatches_inline_or_heap() {
772 let inline_src: SmallBytes = vec![1u8, 2, 3].into();
774 assert!(inline_src.is_inline());
775 assert_eq!(inline_src.as_slice(), &[1, 2, 3]);
776 let v: Vec<u8> = (0u8..30).collect();
778 let heap_src: SmallBytes = v.clone().into();
779 assert!(!heap_src.is_inline());
780 assert_eq!(heap_src.as_slice(), v.as_slice());
781 }
782
783 #[test]
784 fn clone_heap_keeps_data_and_is_independent() {
785 let v: Vec<u8> = (0u8..50).collect();
788 let src = SmallBytes::from_slice(&v);
789 let dup = src.clone();
790 unsafe {
792 assert_ne!(
793 src.heap.ptr.as_ptr(),
794 dup.heap.ptr.as_ptr(),
795 "clone must allocate a fresh buffer"
796 );
797 }
798 drop(src);
799 assert_eq!(dup.as_slice(), v.as_slice());
801 }
802
803 #[test]
804 fn drop_inline_is_noop() {
805 for &n in &[0usize, 1, 5, 22] {
808 let s = SmallBytes::from_slice(&vec![b'x'; n]);
809 assert!(s.is_inline());
810 drop(s);
811 }
812 }
813
814 #[test]
815 fn into_vec_zero_size_path() {
816 let s = SmallBytes::new();
818 let v = s.into_vec();
819 assert!(v.is_empty());
820 }
821
822 #[test]
823 fn to_vec_copies_inline_and_heap() {
824 let inline = SmallBytes::from_slice(b"hi");
825 assert_eq!(inline.to_vec(), b"hi");
826 let v: Vec<u8> = (0u8..30).collect();
827 let heap = SmallBytes::from_slice(&v);
828 let copy = heap.to_vec();
829 assert_eq!(copy, v);
830 assert_eq!(heap.as_slice(), v.as_slice());
834 }
835
836 use std::alloc::{GlobalAlloc, Layout, System};
852 use std::cell::Cell;
853
854 struct CountingAlloc {
855 inner: System,
856 }
857
858 thread_local! {
859 static THREAD_RECORDING: Cell<bool> = const { Cell::new(false) };
862 static THREAD_ALLOC_CALLS: Cell<usize> = const { Cell::new(0) };
863 }
864
865 unsafe impl GlobalAlloc for CountingAlloc {
866 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
867 let _ = THREAD_RECORDING.try_with(|r| {
870 if r.get() {
871 let _ = THREAD_ALLOC_CALLS.try_with(|c| c.set(c.get() + 1));
872 }
873 });
874 unsafe { self.inner.alloc(layout) }
876 }
877 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
878 unsafe { self.inner.dealloc(ptr, layout) }
880 }
881 }
882
883 #[global_allocator]
884 static COUNTING: CountingAlloc = CountingAlloc { inner: System };
885
886 fn measure_allocs<F: FnOnce()>(f: F) -> usize {
887 THREAD_ALLOC_CALLS.with(|c| c.set(0));
888 THREAD_RECORDING.with(|r| r.set(true));
889 f();
890 THREAD_RECORDING.with(|r| r.set(false));
891 THREAD_ALLOC_CALLS.with(|c| c.get())
892 }
893
894 #[test]
895 fn inline_payload_does_not_allocate() {
896 let max_inline = INLINE_LEN_MAX as usize;
901 let allocs = measure_allocs(|| {
902 for n in 0..=max_inline {
903 let s = SmallBytes::from_slice(&[0u8; INLINE_CAP][..n]);
904 std::hint::black_box(&s);
905 std::hint::black_box(s.as_slice());
906 std::hint::black_box(s.len());
907 let c = s.clone(); std::hint::black_box(&c);
909 drop(c);
910 drop(s);
911 }
912 });
913 assert_eq!(
914 allocs, 0,
915 "expected SSO inline path to be alloc-free, got {allocs} allocs"
916 );
917 }
918
919 #[test]
920 fn heap_payload_does_allocate() {
921 let max_inline = INLINE_LEN_MAX as usize;
925 let allocs = measure_allocs(|| {
926 let s = SmallBytes::from_slice(&[7u8; INLINE_CAP + 8][..max_inline + 1]);
927 std::hint::black_box(&s);
928 drop(s);
929 });
930 assert!(
931 allocs >= 1,
932 "expected the heap path to allocate at least once, got {allocs}"
933 );
934 }
935}