1use core::sync::atomic::{AtomicBool, Ordering};
9
10pub const DEFAULT_BYTES: usize = 64 * 1024;
13
14pub const PAINT: u8 = 0xA5;
16
17pub const RED_ZONE: usize = 256;
20
21pub fn high_water(size: usize, bytes: impl IntoIterator<Item = u8>) -> usize {
24 let untouched = bytes.into_iter().take_while(|&byte| byte == PAINT).count();
25 size.saturating_sub(untouched)
26}
27
28pub fn overflowed(bytes: impl IntoIterator<Item = u8>) -> bool {
31 bytes.into_iter().take(RED_ZONE).any(|byte| byte != PAINT)
32}
33
34pub fn top(base: usize, size: usize) -> usize {
37 (base + size) & !7
38}
39
40pub fn run<R>(
44 inside: &AtomicBool,
45 f: impl FnOnce() -> R,
46 switch: impl FnOnce(&mut dyn FnMut()),
47) -> R {
48 if inside.swap(true, Ordering::Relaxed) {
49 return f();
50 }
51 let mut f = Some(f);
52 let mut out = None;
53 switch(&mut || out = f.take().map(|f| f()));
54 inside.store(false, Ordering::Relaxed);
55 match out {
56 Some(out) => out,
57 None => panic!("the game's stack came back without making the call"),
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use core::cell::Cell;
65
66 use super::*;
67
68 fn bytes(stack: &[u8]) -> impl Iterator<Item = u8> {
70 stack.iter().copied()
71 }
72
73 #[test]
74 fn an_untouched_stack_has_used_nothing() {
75 assert_eq!(high_water(64, bytes(&[PAINT; 64])), 0);
76 }
77
78 #[test]
79 fn a_use_of_3_kb_reads_3_kb() {
80 let mut stack = alloc::vec![PAINT; DEFAULT_BYTES];
81 for byte in &mut stack[DEFAULT_BYTES - 3 * 1024..] {
82 *byte = 0;
83 }
84 assert_eq!(high_water(DEFAULT_BYTES, bytes(&stack)), 3 * 1024);
85 }
86
87 #[test]
88 fn the_used_bytes_are_those_above_the_deepest_write() {
89 let mut stack = [PAINT; 64];
90 stack[40] = 0;
91 stack[63] = 1;
92 assert_eq!(high_water(64, bytes(&stack)), 24);
94 stack[0] = 7;
95 assert_eq!(high_water(64, bytes(&stack)), 64);
96 }
97
98 #[test]
99 fn the_red_zone_is_the_bottom_of_the_stack() {
100 let mut stack = [PAINT; 1024];
101 assert!(!overflowed(bytes(&stack)));
102 stack[RED_ZONE] = 0;
103 assert!(!overflowed(bytes(&stack)));
104 stack[RED_ZONE - 1] = 0;
105 assert!(overflowed(bytes(&stack)));
106 stack[RED_ZONE - 1] = PAINT;
107 stack[0] = 0;
108 assert!(overflowed(bytes(&stack)));
109 }
110
111 #[test]
112 fn the_top_is_eight_byte_aligned_and_inside_the_block() {
113 assert_eq!(top(0x2000_0000, DEFAULT_BYTES), 0x2001_0000);
114 assert_eq!(top(0x2000_0004, 100), 0x2000_0068);
115 assert_eq!(top(0x2000_0003, 16), 0x2000_0010);
116 }
117
118 fn counted(switches: &Cell<u32>) -> impl Fn(&mut dyn FnMut()) {
120 |call| {
121 switches.set(switches.get() + 1);
122 call();
123 }
124 }
125
126 #[test]
127 fn a_call_moves_to_the_stack_and_hands_back_what_it_returned() {
128 let inside = AtomicBool::new(false);
129 let switches = Cell::new(0);
130 let seen = run(
131 &inside,
132 || inside.load(Ordering::Relaxed),
133 counted(&switches),
134 );
135 assert!(seen, "the guard is up during the call");
136 assert_eq!(switches.get(), 1);
137 assert!(!inside.load(Ordering::Relaxed), "and down after it");
138 run(&inside, || (), counted(&switches));
139 assert_eq!(switches.get(), 2, "the next call moves again");
140 }
141
142 #[test]
143 fn a_nested_call_does_not_start_again_from_the_top() {
144 let inside = AtomicBool::new(false);
145 let switches = Cell::new(0);
146 let (outer, still_inside) = run(
147 &inside,
148 || {
149 let inner = run(&inside, || 7, counted(&switches));
150 (inner + 1, inside.load(Ordering::Relaxed))
151 },
152 counted(&switches),
153 );
154 assert_eq!(outer, 8);
155 assert_eq!(switches.get(), 1);
156 assert!(still_inside, "the inner call leaves the guard up");
157 assert!(!inside.load(Ordering::Relaxed));
158 }
159}