Skip to main content

forge/os/
thread.rs

1use alloc::{
2    alloc::{alloc, dealloc, handle_alloc_error},
3    boxed::Box,
4};
5use core::{
6    alloc::Layout,
7    cell::UnsafeCell,
8    ffi::{c_char, c_void},
9    ptr::NonNull,
10};
11
12use sys::os::thread::*;
13
14pub const STACK_ALIGNMENT: usize = 0x1000;
15
16/// An OS thread handle.
17///
18/// The thread is joined and destroyed automatically when the handle is dropped.
19pub struct Thread {
20    started: UnsafeCell<bool>,
21    inner: Box<UnsafeCell<ThreadType>>,
22    _stack: Stack,
23}
24
25/// A builder for configuring and spawning threads.
26///
27/// # Examples
28/// ```ignore
29/// let t = Builder::new()
30///     .name("worker")
31///     .priority(16)
32///     .stack_size(0x4000)
33///     .spawn(|| { /* ... */ });
34/// ```
35pub struct Builder {
36    name: Option<[c_char; 32]>,
37    halted: bool,
38    stack: EitherOr<&'static mut [u8], usize>,
39    priority: i32,
40}
41
42struct AlignedStack {
43    ptr: NonNull<u8>,
44    size: usize,
45}
46
47enum EitherOr<A, B> {
48    A(A),
49    B(B),
50}
51
52#[allow(dead_code)]
53enum Stack {
54    Static(&'static mut [u8]),
55    Dynamic(AlignedStack),
56}
57
58/// Spawns a new thread that runs `f`, allocating a dynamic stack of `stack_size` bytes.
59pub fn spawn<F: FnOnce() + Send + 'static>(f: F, stack_size: usize, priority: i32) -> Thread {
60    let t = spawn_halted(f, stack_size, priority);
61    t.start();
62    t
63}
64
65/// Spawns a new thread that runs `f`, using the provided static `stack` buffer.
66pub fn spawn_with<F: FnOnce() + Send + 'static>(f: F, stack: &'static mut [u8], priority: i32) -> Thread {
67    let t = spawn_halted_with(f, stack, priority);
68    t.start();
69    t
70}
71
72/// Like [`spawn`], but the thread is created in a halted state and must be started with [`Thread::start`].
73pub fn spawn_halted<F: FnOnce() + Send + 'static>(f: F, stack_size: usize, priority: i32) -> Thread {
74    let thread = Box::new(UnsafeCell::new(unsafe { core::mem::zeroed() }));
75    let arg = Box::into_raw(Box::new(f)) as *mut c_void;
76    let mut stack = AlignedStack::new(stack_size);
77
78    unsafe {
79        if nnosCreateThread(
80            thread.get(),
81            thread_entry::<F>,
82            arg,
83            stack.as_mut_ptr() as *mut c_void,
84            stack.len(),
85            priority,
86        ) != 0
87        {
88            drop(Box::<F>::from_raw(arg as *mut F));
89            panic!("Failed to create thread");
90        }
91    }
92
93    Thread {
94        started: UnsafeCell::new(false),
95        inner: thread,
96        _stack: Stack::Dynamic(stack),
97    }
98}
99
100/// Like [`spawn_with`], but the thread is created in a halted state and must be started with [`Thread::start`].
101pub fn spawn_halted_with<F: FnOnce() + Send + 'static>(f: F, stack: &'static mut [u8], priority: i32) -> Thread {
102    let thread = Box::new(UnsafeCell::new(unsafe { core::mem::zeroed() }));
103    let arg = Box::into_raw(Box::new(f)) as *mut c_void;
104
105    unsafe {
106        if nnosCreateThread(
107            thread.get(),
108            thread_entry::<F>,
109            arg,
110            stack.as_mut_ptr() as *mut c_void,
111            stack.len(),
112            priority,
113        ) != 0
114        {
115            drop(Box::<F>::from_raw(arg as *mut F));
116            panic!("Failed to create thread");
117        }
118    }
119
120    Thread {
121        started: UnsafeCell::new(false),
122        inner: thread,
123        _stack: Stack::Static(stack),
124    }
125}
126
127unsafe extern "C" fn thread_entry<F: FnOnce()>(arg: *mut c_void) {
128    let f = unsafe { Box::from_raw(arg as *mut F) };
129    f();
130}
131
132impl Thread {
133    /// Returns `true` if the thread has finished executing.
134    pub fn is_finished(&self) -> bool {
135        unsafe { nnosTryWaitThread(self.inner.get()) }
136    }
137
138    /// Blocks until the thread finishes executing.
139    pub fn join(&self) {
140        unsafe { nnosWaitThread(self.inner.get()) };
141    }
142
143    /// Returns the OS thread ID.
144    pub fn id(&self) -> u64 {
145        unsafe { nnosGetThreadId(self.inner.get()) }
146    }
147
148    /// Sets the thread's name, truncated to 31 bytes.
149    pub fn set_name(&self, name: &str) {
150        let mut buffer = [0 as c_char; 32];
151        let bytes = name.as_bytes();
152        let len = bytes.len().min(31);
153        for i in 0..len {
154            buffer[i] = bytes[i] as c_char;
155        }
156
157        unsafe {
158            nnosSetThreadName(self.inner.get(), buffer.as_ptr());
159        }
160    }
161
162    /// Starts the thread. Only meaningful for threads created with [`spawn_halted`] or [`Builder::halted`].
163    pub fn start(&self) {
164        unsafe {
165            nnosStartThread(self.inner.get());
166            *self.started.get() = true;
167        }
168    }
169}
170
171impl Drop for Thread {
172    fn drop(&mut self) {
173        unsafe {
174            nnosWaitThread(self.inner.get());
175            nnosDestroyThread(self.inner.get());
176        }
177    }
178}
179
180impl Builder {
181    /// Default stack size used when none is specified (8 KiB).
182    pub const DEFAULT_STACK_SIZE: usize = 0x2000;
183
184    /// Creates a builder with default settings: 8 KiB dynamic stack, priority 16, started immediately.
185    pub fn new() -> Self {
186        Self {
187            name: None,
188            halted: false,
189            stack: EitherOr::B(Self::DEFAULT_STACK_SIZE),
190            priority: 16,
191        }
192    }
193
194    /// Sets the thread name, truncated to 31 bytes.
195    pub fn name(mut self, name: &str) -> Self {
196        let mut buffer = [0 as c_char; 32];
197        let bytes = name.as_bytes();
198        let len = bytes.len().min(31);
199        for i in 0..len {
200            buffer[i] = bytes[i] as c_char;
201        }
202        self.name = Some(buffer);
203        self
204    }
205
206    /// Creates the thread in a halted state; call [`Thread::start`] to begin execution.
207    pub fn halted(mut self) -> Self {
208        self.halted = true;
209        self
210    }
211
212    /// Uses a caller-provided static buffer as the thread stack instead of allocating one.
213    pub fn stack(mut self, stack: &'static mut [u8]) -> Self {
214        self.stack = EitherOr::A(stack);
215        self
216    }
217
218    /// Sets the size in bytes for the dynamically allocated stack.
219    pub fn stack_size(mut self, size: usize) -> Self {
220        self.stack = EitherOr::B(size);
221        self
222    }
223
224    /// Sets the thread scheduling priority.
225    pub fn priority(mut self, priority: i32) -> Self {
226        self.priority = priority;
227        self
228    }
229
230    /// Consumes the builder and spawns a thread running `f`.
231    pub fn spawn<F: FnOnce() + Send + 'static>(self, f: F) -> Thread {
232        let thread = match self.stack {
233            EitherOr::A(stack) => spawn_halted_with(f, stack, self.priority),
234            EitherOr::B(size) => spawn_halted(f, size, self.priority),
235        };
236
237        if let Some(name) = self.name {
238            unsafe {
239                nnosSetThreadName(thread.inner.get(), name.as_ptr());
240            }
241        }
242
243        if !self.halted {
244            thread.start();
245        }
246
247        thread
248    }
249}
250
251impl AlignedStack {
252    fn new(size: usize) -> Self {
253        let layout = Layout::from_size_align(size, STACK_ALIGNMENT).expect("Invalid Stack Layout");
254        let ptr = unsafe { alloc(layout) };
255        NonNull::new(ptr)
256            .map(|ptr| Self { ptr, size })
257            .unwrap_or_else(|| handle_alloc_error(layout))
258    }
259
260    fn as_mut_ptr(&mut self) -> *mut u8 {
261        self.ptr.as_ptr()
262    }
263
264    fn len(&self) -> usize {
265        self.size
266    }
267}
268
269impl Drop for AlignedStack {
270    fn drop(&mut self) {
271        unsafe {
272            dealloc(
273                self.as_mut_ptr(),
274                Layout::from_size_align_unchecked(self.size, STACK_ALIGNMENT),
275            );
276        }
277    }
278}