Skip to main content

ax_runtime/thread/
spawn.rs

1use alloc::sync::Arc;
2
3use super::*;
4
5/// Creates a scheduler-owned kernel thread and enqueues it on the current CPU.
6pub fn spawn_raw<F>(entry: F, name: String, stack_size: usize) -> Result<ThreadHandle, TaskError>
7where
8    F: FnOnce() + Send + 'static,
9{
10    // SAFETY: `None` carries no external callback ownership.
11    unsafe { spawn_raw_with_extension(entry, name, stack_size, None) }
12}
13
14/// Creates a scheduler-owned kernel thread without making it runnable.
15///
16/// An OS publication transaction must call [`PreparedThread::stage`] before
17/// exposing external identity and [`StagedThread::activate`] after commit.
18pub fn prepare_raw<F>(
19    entry: F,
20    name: String,
21    stack_size: usize,
22) -> Result<PreparedThread, TaskError>
23where
24    F: FnOnce() + Send + 'static,
25{
26    unsafe {
27        // SAFETY: `None` carries no external callback ownership.
28        prepare_raw_with_options(
29            entry,
30            name,
31            stack_size,
32            None,
33            None,
34            SchedulePolicy::default(),
35            InitialContextState::kernel(),
36        )
37    }
38}
39
40/// Creates a scheduler-owned kernel thread with pre-publication affinity.
41pub fn spawn_raw_with_affinity<F>(
42    entry: F,
43    name: String,
44    stack_size: usize,
45    affinity: CpuSet,
46) -> Result<ThreadHandle, TaskError>
47where
48    F: FnOnce() + Send + 'static,
49{
50    // SAFETY: `None` carries no external callback ownership, while the affinity
51    // is installed before the new thread is published to a run queue.
52    unsafe { spawn_raw_with_extension_and_affinity(entry, name, stack_size, None, Some(affinity)) }
53}
54
55/// Creates a scheduler-owned kernel service thread with policy and affinity
56/// installed before run-queue publication.
57pub fn spawn_raw_with_policy_and_affinity<F>(
58    entry: F,
59    name: String,
60    stack_size: usize,
61    policy: SchedulePolicy,
62    affinity: CpuSet,
63) -> Result<ThreadHandle, TaskError>
64where
65    F: FnOnce() + Send + 'static,
66{
67    // SAFETY: `None` carries no external callback ownership. Both scheduler
68    // attributes are committed before the thread can execute.
69    unsafe {
70        spawn_raw_with_options(
71            entry,
72            name,
73            stack_size,
74            None,
75            Some(affinity),
76            policy,
77            InitialContextState::kernel(),
78        )
79    }
80}
81
82/// Creates a kernel thread while retaining one OS-specific extension.
83///
84/// The runtime owns an outer extension for the closure and join metadata. It
85/// forwards switch, exit, Deadline-overrun and final-drop callbacks to
86/// `os_extension`, preserving the inner callback-table address as its type
87/// identity for StarryOS or another consuming OS.
88///
89/// # Safety
90///
91/// When present, `os_extension` transfers its unique callback-data ownership
92/// to this function. The caller must not install another copy or invoke its
93/// drop callback, regardless of whether thread creation succeeds.
94pub unsafe fn spawn_raw_with_extension<F>(
95    entry: F,
96    name: String,
97    stack_size: usize,
98    os_extension: Option<ThreadExtension>,
99) -> Result<ThreadHandle, TaskError>
100where
101    F: FnOnce() + Send + 'static,
102{
103    // SAFETY: this function forwards the extension's unique ownership without
104    // creating another copy or invoking its callback table.
105    unsafe { spawn_raw_with_extension_and_affinity(entry, name, stack_size, os_extension, None) }
106}
107
108/// Creates a kernel thread with an OS extension and pre-publication affinity.
109///
110/// Unlike setting affinity on the returned handle, `affinity` is installed in
111/// [`ThreadSpec`] before the thread becomes Ready or enters a run queue. This is
112/// required by pinned vCPU and per-CPU service threads whose entry point must
113/// never execute on a disallowed CPU.
114///
115/// # Safety
116///
117/// When present, `os_extension` transfers its unique callback-data ownership
118/// to this function. The caller must not install another copy or invoke its
119/// drop callback, regardless of whether thread creation succeeds.
120pub unsafe fn spawn_raw_with_extension_and_affinity<F>(
121    entry: F,
122    name: String,
123    stack_size: usize,
124    os_extension: Option<ThreadExtension>,
125    affinity: Option<CpuSet>,
126) -> Result<ThreadHandle, TaskError>
127where
128    F: FnOnce() + Send + 'static,
129{
130    unsafe {
131        // SAFETY: this wrapper forwards unique extension ownership once.
132        spawn_raw_with_options(
133            entry,
134            name,
135            stack_size,
136            os_extension,
137            affinity,
138            SchedulePolicy::default(),
139            InitialContextState::kernel(),
140        )
141    }
142}
143
144/// Creates a scheduler thread whose architecture context retains a user page table.
145///
146/// # Safety
147///
148/// `os_extension` transfers unique callback-data ownership. `address_space`
149/// must describe the address space retained by the OS extension for the entire
150/// thread lifetime.
151pub unsafe fn spawn_raw_with_extension_in_address_space<F>(
152    entry: F,
153    name: String,
154    stack_size: usize,
155    os_extension: Option<ThreadExtension>,
156    address_space: TaskAddressSpace,
157) -> Result<ThreadHandle, TaskError>
158where
159    F: FnOnce() + Send + 'static,
160{
161    unsafe {
162        // SAFETY: this wrapper forwards both capabilities without copying the
163        // extension or exposing its architecture context.
164        spawn_raw_with_options(
165            entry,
166            name,
167            stack_size,
168            os_extension,
169            None,
170            SchedulePolicy::default(),
171            InitialContextState::user(address_space),
172        )
173    }
174}
175
176/// Prepares a user thread with its scheduler state installed before publication.
177///
178/// # Safety
179///
180/// The extension and address-space ownership rules are identical to
181/// [`spawn_raw_with_extension_in_address_space`].
182pub unsafe fn spawn_raw_with_extension_in_address_space_and_policy<F>(
183    entry: F,
184    name: String,
185    stack_size: usize,
186    os_extension: Option<ThreadExtension>,
187    address_space: TaskAddressSpace,
188    policy: SchedulePolicy,
189) -> Result<ThreadHandle, TaskError>
190where
191    F: FnOnce() + Send + 'static,
192{
193    unsafe {
194        // SAFETY: ownership is forwarded once and the validated policy is
195        // embedded in ThreadSpec before scheduler publication.
196        spawn_raw_with_options(
197            entry,
198            name,
199            stack_size,
200            os_extension,
201            None,
202            policy,
203            InitialContextState::user(address_space),
204        )
205    }
206}
207
208/// Prepares a user thread without making it runnable.
209///
210/// This is the transactional form of
211/// [`spawn_raw_with_extension_in_address_space_and_policy`]. The caller may
212/// inspect private identity through [`PreparedThread::thread_handle`], then
213/// call [`PreparedThread::stage`] before publishing OS registries and
214/// [`StagedThread::activate`] after that publication commits. Dropping either
215/// transaction token rolls back or aborts the unstarted entry.
216///
217/// # Safety
218///
219/// The extension and address-space ownership rules are identical to
220/// [`spawn_raw_with_extension_in_address_space_and_policy`].
221pub unsafe fn prepare_raw_with_extension_in_address_space_and_scheduler_state<F>(
222    entry: F,
223    name: String,
224    stack_size: usize,
225    os_extension: Option<ThreadExtension>,
226    address_space: TaskAddressSpace,
227    policy: SchedulePolicy,
228    affinity: CpuSet,
229) -> Result<PreparedThread, TaskError>
230where
231    F: FnOnce() + Send + 'static,
232{
233    unsafe {
234        prepare_raw_with_options(
235            entry,
236            name,
237            stack_size,
238            os_extension,
239            Some(affinity),
240            policy,
241            InitialContextState::user(address_space),
242        )
243    }
244}
245
246/// Creates a RISC-V user thread while preserving the inherited FP context.
247///
248/// # Safety
249///
250/// The extension and address-space contracts are identical to
251/// [`spawn_raw_with_extension_in_address_space`].
252#[cfg(all(target_arch = "riscv64", feature = "fp-simd"))]
253pub unsafe fn spawn_raw_with_extension_in_address_space_and_fp_state<F>(
254    entry: F,
255    name: String,
256    stack_size: usize,
257    os_extension: Option<ThreadExtension>,
258    address_space: TaskAddressSpace,
259    fp_state: ax_hal::cpu::FpState,
260) -> Result<ThreadHandle, TaskError>
261where
262    F: FnOnce() + Send + 'static,
263{
264    unsafe {
265        // SAFETY: the newly owned FP snapshot is installed before publication;
266        // extension ownership is forwarded exactly once.
267        spawn_raw_with_options(
268            entry,
269            name,
270            stack_size,
271            os_extension,
272            None,
273            SchedulePolicy::default(),
274            InitialContextState::user_with_fp_state(address_space, fp_state),
275        )
276    }
277}
278
279/// Creates a RISC-V user thread with inherited FP state and scheduling policy.
280///
281/// # Safety
282///
283/// The ownership rules are identical to
284/// [`spawn_raw_with_extension_in_address_space_and_fp_state`].
285#[cfg(all(target_arch = "riscv64", feature = "fp-simd"))]
286pub unsafe fn spawn_raw_with_extension_in_address_space_and_fp_state_and_policy<F>(
287    entry: F,
288    name: String,
289    stack_size: usize,
290    os_extension: Option<ThreadExtension>,
291    address_space: TaskAddressSpace,
292    fp_state: ax_hal::cpu::FpState,
293    policy: SchedulePolicy,
294) -> Result<ThreadHandle, TaskError>
295where
296    F: FnOnce() + Send + 'static,
297{
298    unsafe {
299        // SAFETY: all owned capabilities are installed before publication and
300        // each is transferred exactly once.
301        spawn_raw_with_options(
302            entry,
303            name,
304            stack_size,
305            os_extension,
306            None,
307            policy,
308            InitialContextState::user_with_fp_state(address_space, fp_state),
309        )
310    }
311}
312
313/// Prepares a RISC-V user thread with FP and scheduler state before publication.
314///
315/// # Safety
316///
317/// The ownership rules are identical to
318/// [`spawn_raw_with_extension_in_address_space_and_fp_state_and_policy`].
319#[cfg(all(target_arch = "riscv64", feature = "fp-simd"))]
320pub unsafe fn prepare_raw_with_extension_in_address_space_and_fp_scheduler_state<F>(
321    entry: F,
322    name: String,
323    stack_size: usize,
324    os_extension: Option<ThreadExtension>,
325    address_space: TaskAddressSpace,
326    fp_state: ax_hal::cpu::FpState,
327    policy: SchedulePolicy,
328    affinity: CpuSet,
329) -> Result<PreparedThread, TaskError>
330where
331    F: FnOnce() + Send + 'static,
332{
333    unsafe {
334        prepare_raw_with_options(
335            entry,
336            name,
337            stack_size,
338            os_extension,
339            Some(affinity),
340            policy,
341            InitialContextState::user_with_fp_state(address_space, fp_state),
342        )
343    }
344}
345
346/// Prepares an x86 user thread inheriting the current task's FP image.
347///
348/// The runtime saves the current hardware image directly into the new context
349/// before the scheduler can bind or publish it.
350///
351/// # Safety
352///
353/// The extension and address-space ownership rules are identical to
354/// [`spawn_raw_with_extension_in_address_space`]. The caller must be the
355/// ordinary task context whose userspace FP image the child inherits.
356#[cfg(all(target_arch = "x86_64", feature = "fp-simd", feature = "uspace"))]
357pub unsafe fn prepare_raw_with_extension_in_address_space_and_inherited_fp_scheduler_state<F>(
358    entry: F,
359    name: String,
360    stack_size: usize,
361    os_extension: Option<ThreadExtension>,
362    address_space: TaskAddressSpace,
363    policy: SchedulePolicy,
364    affinity: CpuSet,
365) -> Result<PreparedThread, TaskError>
366where
367    F: FnOnce() + Send + 'static,
368{
369    if let Err(error) = context::validate_current_user_fp_clone_context() {
370        // SAFETY: validation failed before any runtime object observed the
371        // uniquely transferred extension.
372        unsafe { release_transferred_extension(os_extension) };
373        return Err(error);
374    }
375    unsafe {
376        prepare_raw_with_options(
377            entry,
378            name,
379            stack_size,
380            os_extension,
381            Some(affinity),
382            policy,
383            InitialContextState::user_inheriting_current_fp_state(address_space),
384        )
385    }
386}
387
388unsafe fn spawn_raw_with_options<F>(
389    entry: F,
390    name: String,
391    stack_size: usize,
392    os_extension: Option<ThreadExtension>,
393    affinity: Option<CpuSet>,
394    policy: SchedulePolicy,
395    context_state: InitialContextState,
396) -> Result<ThreadHandle, TaskError>
397where
398    F: FnOnce() + Send + 'static,
399{
400    unsafe {
401        prepare_raw_with_options(
402            entry,
403            name,
404            stack_size,
405            os_extension,
406            affinity,
407            policy,
408            context_state,
409        )
410    }?
411    .publish()
412}
413
414unsafe fn prepare_raw_with_options<F>(
415    entry: F,
416    name: String,
417    stack_size: usize,
418    os_extension: Option<ThreadExtension>,
419    affinity: Option<CpuSet>,
420    policy: SchedulePolicy,
421    context_state: InitialContextState,
422) -> Result<PreparedThread, TaskError>
423where
424    F: FnOnce() + Send + 'static,
425{
426    if stack_size == 0 {
427        // SAFETY: this function accepted the extension's unique ownership on entry.
428        unsafe { release_transferred_extension(os_extension) };
429        return Err(TaskError::InvalidConfiguration);
430    }
431    let Some(system) = task_system() else {
432        // SAFETY: no runtime object observed or retained the extension.
433        unsafe { release_transferred_extension(os_extension) };
434        return Err(TaskError::NotInitialized);
435    };
436    let resources = match create_thread_resources(stack_size, runtime_thread_entry, context_state) {
437        Ok(resources) => resources,
438        Err(error) => {
439            // SAFETY: resource construction failed before publishing extension data.
440            unsafe { release_transferred_extension(os_extension) };
441            return Err(error);
442        }
443    };
444    let start = Arc::new(RuntimeThreadStart::new());
445    let data = Box::into_raw(Box::new(RuntimeThreadData::new(
446        Box::new(entry),
447        name,
448        os_extension,
449        Arc::clone(&start),
450    )))
451    .expose_provenance();
452    // SAFETY: the boxed data remains live until the scheduler reaper invokes
453    // `runtime_thread_drop_hook` through this exact ops table.
454    let extension = unsafe {
455        // SAFETY: `data` is the unique live runtime allocation created above.
456        runtime_thread_extension(data)
457    };
458    let mut spec = unsafe {
459        // SAFETY: create_thread_resources returned one live bundle created by
460        // this runtime, and this specification is its unique installation.
461        ThreadSpec::new(policy)
462            .with_extension(extension)
463            .with_resources(resources)
464    };
465    if let Some(affinity) = affinity {
466        spec = spec.with_affinity(affinity);
467    }
468    let handle = system.create_thread(spec)?;
469    Ok(PreparedThread::new(system, handle, start))
470}