catallaxy_runtime/
stack.rs1use std::cell::Cell;
19
20use crate::mutator::{RtResult, Trap};
21
22thread_local! {
23 static LIMIT: Cell<usize> = const { Cell::new(0) };
25}
26
27pub const OVERFLOW: &str = "SystemStackError: stack level too deep";
29
30pub fn margin(size: usize) -> usize {
33 (size / 4).min(256 << 10)
34}
35
36pub fn limit_for(lo: usize, hi: usize) -> usize {
38 lo + margin(hi.saturating_sub(lo))
39}
40
41#[inline(always)]
43fn sp() -> usize {
44 let probe = 0u8;
45 std::hint::black_box(&probe) as *const u8 as usize
46}
47
48#[inline]
50pub fn check() -> RtResult<()> {
51 let lim = LIMIT.with(|l| l.get());
52 if sp() < lim || lim == 0 {
53 return check_slow();
54 }
55 Ok(())
56}
57
58pub fn check_extra(extra: usize) -> RtResult<()> {
61 let mut lim = LIMIT.with(|l| l.get());
62 if lim == 0 {
63 lim = init_thread();
64 }
65 if lim > 1 && sp() < lim.saturating_add(extra) {
66 return Err(Trap::Panic(OVERFLOW.into()));
67 }
68 Ok(())
69}
70
71#[cold]
72#[inline(never)]
73fn check_slow() -> RtResult<()> {
74 let mut lim = LIMIT.with(|l| l.get());
75 if lim == 0 {
76 lim = init_thread();
77 }
78 if sp() < lim {
79 return Err(Trap::Panic(OVERFLOW.into()));
80 }
81 Ok(())
82}
83
84pub fn init_thread() -> usize {
86 let lim = match thread_bounds() {
87 Some((lo, hi)) if lo < hi => limit_for(lo, hi),
88 _ => 1,
89 };
90 LIMIT.with(|l| l.set(lim));
91 lim
92}
93
94pub fn set_limit(lim: usize) -> usize {
97 LIMIT.with(|l| l.replace(lim))
98}
99
100#[cfg(all(not(miri), any(target_os = "linux", target_os = "android")))]
102fn thread_bounds() -> Option<(usize, usize)> {
103 unsafe {
106 let mut attr: libc::pthread_attr_t = std::mem::zeroed();
107 if libc::pthread_getattr_np(libc::pthread_self(), &mut attr) != 0 {
108 return None;
109 }
110 let mut addr: *mut libc::c_void = std::ptr::null_mut();
111 let mut size: libc::size_t = 0;
112 let r = libc::pthread_attr_getstack(&attr, &mut addr, &mut size);
113 libc::pthread_attr_destroy(&mut attr);
114 (r == 0 && !addr.is_null()).then(|| (addr as usize, addr as usize + size))
115 }
116}
117
118#[cfg(all(not(miri), any(target_os = "macos", target_os = "ios")))]
119fn thread_bounds() -> Option<(usize, usize)> {
120 unsafe {
122 let t = libc::pthread_self();
123 let hi = libc::pthread_get_stackaddr_np(t) as usize;
124 let size = libc::pthread_get_stacksize_np(t);
125 (hi > size).then(|| (hi - size, hi))
126 }
127}
128
129#[cfg(all(not(miri), windows))]
130fn thread_bounds() -> Option<(usize, usize)> {
131 #[link(name = "kernel32")]
132 extern "system" {
133 fn GetCurrentThreadStackLimits(low: *mut usize, high: *mut usize);
134 }
135 let (mut lo, mut hi) = (0usize, 0usize);
136 unsafe { GetCurrentThreadStackLimits(&mut lo, &mut hi) };
138 Some((lo, hi))
139}
140
141#[cfg(any(
142 miri,
143 not(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios", windows))
144))]
145fn thread_bounds() -> Option<(usize, usize)> {
146 None
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn shallow_check_passes() {
155 assert!(check().is_ok());
156 }
157
158 #[test]
159 fn limit_above_the_stack_fires() {
160 let prev = set_limit(usize::MAX);
161 let r = check();
162 set_limit(prev);
163 assert!(matches!(r, Err(Trap::Panic(m)) if m == OVERFLOW));
164 }
165
166 #[cfg(not(miri))]
167 #[test]
168 fn deep_native_recursion_is_caught() {
169 fn down(n: u64) -> RtResult<u64> {
170 check()?;
171 let pad = std::hint::black_box([n; 32]);
172 Ok(down(n + 1)? + pad[0])
173 }
174 let r = std::thread::Builder::new().stack_size(1 << 20).spawn(|| down(0)).unwrap().join().unwrap();
176 assert!(r.is_err());
177 }
178}