Skip to main content

shuyi_playdate/
heap.rs

1//! The arithmetic of the allocator over the SDK's `realloc`
2//! (`crate::device`).
3//!
4//! The SDK's `realloc` returns blocks aligned to [`MIN_ALIGN`] bytes. A
5//! bigger alignment gets a block [`padded_size`] long; the pointer handed
6//! out is [`offset`] bytes into it, and the block's own address is kept in
7//! the word just before that pointer, so freeing finds it again.
8
9/// The alignment every block from the SDK's `realloc` has.
10pub const MIN_ALIGN: usize = 8;
11
12/// Whether a block of alignment `align` needs padding.
13pub fn needs_padding(align: usize) -> bool {
14    align > MIN_ALIGN
15}
16
17/// The size to ask the SDK for, for `size` bytes aligned to `align`
18/// (padded); `None` when that overflows.
19pub fn padded_size(size: usize, align: usize) -> Option<usize> {
20    size.checked_add(align)
21}
22
23/// How far into a padded block at address `raw` the pointer handed out
24/// is, for alignment `align` (a power of two above [`MIN_ALIGN`]).
25///
26/// The result is between [`MIN_ALIGN`] and `align`: the pointer is aligned,
27/// stays inside the padded block, and leaves room before it for the
28/// block's address.
29pub fn offset(raw: usize, align: usize) -> usize {
30    align - raw % align
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36
37    #[test]
38    fn only_alignments_above_the_sdks_are_padded() {
39        assert!(!needs_padding(1));
40        assert!(!needs_padding(MIN_ALIGN));
41        assert!(needs_padding(16));
42    }
43
44    #[test]
45    fn the_padded_size_adds_the_alignment() {
46        assert_eq!(padded_size(100, 16), Some(116));
47        assert_eq!(padded_size(usize::MAX, 16), None);
48    }
49
50    #[test]
51    fn the_pointer_is_aligned_inside_the_block_with_room_for_an_address() {
52        for align in [16, 32, 64, 4096] {
53            for raw in (0..2 * align).step_by(MIN_ALIGN).map(|r| 0x2000_0000 + r) {
54                let at = offset(raw, align);
55                assert_eq!((raw + at) % align, 0, "raw {raw:#x} align {align}");
56                assert!(at >= MIN_ALIGN && at >= size_of::<usize>(), "{at}");
57                assert!(at <= align, "{at}");
58            }
59        }
60    }
61
62    #[test]
63    fn an_already_aligned_block_still_moves_a_whole_alignment() {
64        assert_eq!(offset(0x1000, 16), 16);
65        assert_eq!(offset(0x1008, 16), 8);
66    }
67}