Skip to main content

read_fonts/
mem.rs

1//! Short lived memory.
2//!
3//! For work whose size is not known until it runs, and which is finished with
4//! the memory by the time it returns. Allocating for each such call can cost
5//! more than the work itself.
6
7use alloc::{vec, vec::Vec};
8use bytemuck::{AnyBitPattern, NoUninit};
9use core::mem::{align_of, size_of};
10
11/// Calls `f` with a zeroed block of `size` bytes.
12///
13/// The block comes from the stack where it is small enough, and from the heap
14/// otherwise. Sizes are bucketed so that a small request does not pay to zero
15/// a block sized for the largest one.
16pub fn with_scratch<R>(size: usize, mut f: impl FnMut(&mut [u8]) -> R) -> R {
17    let mut result = None;
18    with_scratch_dyn(size, &mut |block| result = Some(f(block)));
19    // The block is always handed to `f`, so there is always a result.
20    result.unwrap()
21}
22
23/// Calls `f` with a zeroed block of `size` bytes, through a trait object so
24/// that the buckets below are compiled once rather than once per caller.
25fn with_scratch_dyn(size: usize, f: &mut dyn FnMut(&mut [u8])) {
26    // Not inlined, so a caller that lands in another bucket neither reserves
27    // this one's stack nor zeroes it.
28    #[inline(never)]
29    fn on_stack<const SIZE: usize>(size: usize, f: &mut dyn FnMut(&mut [u8])) {
30        f(&mut [0u8; SIZE][..size])
31    }
32    if size <= 512 {
33        on_stack::<512>(size, f)
34    } else if size <= 1024 {
35        on_stack::<1024>(size, f)
36    } else if size <= 2048 {
37        on_stack::<2048>(size, f)
38    } else if size <= 4096 {
39        on_stack::<4096>(size, f)
40    } else if size <= 8192 {
41        on_stack::<8192>(size, f)
42    } else if size <= 16384 {
43        on_stack::<16384>(size, f)
44    } else {
45        let mut block: Vec<u8> = vec![0; size];
46        f(&mut block)
47    }
48}
49
50/// Takes a `len` element slice of `T` off the front of `bytes`, aligning
51/// first, and returns it along with what is left.
52///
53/// For carving one block into the several typed slices a caller needs, so that
54/// the whole of it is one allocation rather than one each.
55///
56/// Returns `None` when the block is of insufficient size.
57pub fn take_slice<T>(bytes: &mut [u8], len: usize) -> Option<(&mut [T], &mut [u8])>
58where
59    T: AnyBitPattern + NoUninit,
60{
61    if len == 0 {
62        return Some((Default::default(), bytes));
63    }
64    let padding = (bytes.as_ptr() as usize).wrapping_neg() & (align_of::<T>() - 1);
65    let bytes = bytes.get_mut(padding..)?;
66    // A count that cannot be expressed in bytes is one no block can hold.
67    let byte_len = len.checked_mul(size_of::<T>())?;
68    if byte_len > bytes.len() {
69        return None;
70    }
71    let (mine, rest) = bytes.split_at_mut(byte_len);
72    // Bytemuck checks size and alignment; both hold by construction.
73    let mine = bytemuck::try_cast_slice_mut(mine).ok()?;
74    Some((mine, rest))
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn every_bucket_hands_back_the_size_asked_for() {
83        // A block from a larger bucket is trimmed to the request, so a caller
84        // carving it into typed slices reads none of the slack.
85        for size in [0, 1, 512, 513, 4096, 16384, 16385, 40000] {
86            assert_eq!(with_scratch(size, |block| block.len()), size);
87        }
88    }
89
90    #[test]
91    fn a_block_arrives_zeroed() {
92        // A caller may write every field before reading it, but carving a
93        // block into typed slices reads it as those types first.
94        for size in [8, 700, 20000] {
95            assert!(with_scratch(size, |block| block
96                .iter()
97                .all(|byte| *byte == 0)));
98        }
99    }
100
101    #[test]
102    fn a_block_is_carved_into_aligned_slices() {
103        with_scratch(64, |block| {
104            let (first, rest) = take_slice::<u32>(block, 3).unwrap();
105            assert_eq!(first.len(), 3);
106            assert_eq!(first.as_ptr() as usize % align_of::<u32>(), 0);
107            let (second, rest) = take_slice::<u16>(rest, 5).unwrap();
108            assert_eq!(second.len(), 5);
109            assert_eq!(second.as_ptr() as usize % align_of::<u16>(), 0);
110            // Three `u32` and five `u16` leave 42 of the 64 bytes, less
111            // whatever aligning the second slice cost.
112            assert!(rest.len() <= 42, "{} left", rest.len());
113            first[0] = 7;
114            second[4] = 9;
115        });
116    }
117
118    #[test]
119    fn an_empty_slice_takes_nothing() {
120        // A caller whose slice is unused still asks for it, so asking for
121        // none of something is an answer rather than a failure.
122        let mut block = [0u8; 4];
123        let (taken, rest) = take_slice::<u64>(&mut block, 0).unwrap();
124        assert!(taken.is_empty());
125        assert_eq!(rest.len(), 4);
126    }
127
128    /// A block whose first byte is deliberately not aligned for `i32`.
129    fn unaligned(block: &mut [u8]) -> &mut [u8] {
130        let base = block.as_ptr() as usize;
131        let offset = usize::from(base % align_of::<i32>() == 0);
132        let block = &mut block[offset..];
133        assert_ne!(block.as_ptr() as usize % align_of::<i32>(), 0);
134        block
135    }
136
137    #[test]
138    fn an_unaligned_block_is_aligned_before_anything_is_taken() {
139        let mut block = [0u8; 40];
140        let (taken, _) = take_slice::<i32>(unaligned(&mut block), 8).unwrap();
141        assert_eq!(taken.as_ptr() as usize % align_of::<i32>(), 0);
142    }
143
144    #[test]
145    fn aligning_comes_out_of_the_block() {
146        // 40 bytes hold ten `i32`, but not once some of them have gone to
147        // aligning the first.
148        let mut block = [0u8; 40];
149        assert!(take_slice::<i32>(unaligned(&mut block), 10).is_none());
150    }
151
152    #[test]
153    fn a_block_too_small_for_what_is_asked_refuses() {
154        let mut block = [0u8; 8];
155        assert!(take_slice::<u32>(&mut block, 3).is_none());
156        // What one slice takes is gone for the next, padding included, so a
157        // block holding two of something holds neither twice.
158        let (first, rest) = take_slice::<u32>(&mut block, 1).unwrap();
159        assert_eq!(first.len(), 1);
160        assert!(take_slice::<u32>(rest, 2).is_none());
161        // Nor can it hold a count that is not a size at all.
162        assert!(take_slice::<u32>(&mut block, usize::MAX).is_none());
163    }
164}