Skip to main content

ax_task/runtime/
switch.rs

1//! Context-switch plans, entry contracts and completion.
2
3use alloc::sync::Arc;
4use core::{marker::PhantomData, ptr::NonNull};
5
6pub use crate::runtime::switch::dispatch::{
7    schedule_current_cpu, schedule_current_cpu_from_irq_guard_exit,
8    schedule_current_cpu_from_preempt_exit,
9};
10use crate::{
11    runtime::{
12        RuntimeStatus, TaskSystemHandle,
13        context::{RuntimeIrqGuard, validate_task_context},
14        cpu::{CurrentCpuOwnerHandles, RuntimeCpuId},
15        resource::{AddressSpaceHandle, AddressSpaceMembarrierId, ExecutionContextHandle},
16        switch::dispatch::complete_current_context_switch_tail,
17        task_runtime,
18    },
19    thread::TaskError,
20};
21
22/// Completes switch tail and consumes the inherited IRQ guard on first entry.
23///
24/// Fresh context trampolines must invoke this before accessing thread-local
25/// state, enabling interrupts, polling futures, or calling user/OS code.
26/// Resumed contexts must not call it because their suspended scheduler guard
27/// consumes the same baton when the architecture switch returns.
28///
29/// # Safety
30///
31/// The caller must be the first instruction sequence of a freshly switched-in
32/// context. Exactly one scheduler IRQ guard must be inherited on this CPU, and
33/// this function must be called exactly once for that context's first entry.
34pub unsafe fn finish_initial_context_switch() -> Result<(), TaskError> {
35    validate_task_context()?;
36    let mut irq = RuntimeIrqGuard::enter();
37    // SAFETY: this trampoline inherits the transferred scheduler baton and
38    // the runtime IRQ guard retains its raw IRQ-off state through completion.
39    unsafe { complete_current_context_switch_tail(&mut irq)? };
40    drop(irq);
41    task_runtime::finish_initial_context_switch();
42    Ok(())
43}
44pub(crate) mod dispatch;
45
46use crate::runtime::handle::opaque_handle;
47
48opaque_handle!(
49    /// Opaque pointer to the Arc-backed scheduler core of the current thread.
50    ///
51    /// This value is useful only as part of a runtime-provided
52    /// [`CurrentThreadPublication`]. The scheduler may acquire a strong handle
53    /// from it only while a preemption pin proves that the published thread is
54    /// still current and therefore retains its owner-side strong reference.
55    CurrentThreadOwnerHandle,
56    "runtime::switch"
57);
58
59/// Move-only runtime transaction for one committed scheduler switch.
60///
61/// ax-task constructs this value only after the scheduler has committed two
62/// distinct live endpoints and released its internal locks. The execution
63/// contexts and logical address spaces travel through one runtime call, so a
64/// provider cannot activate an `mm` and then fail before preparing the matching
65/// architecture context. Consuming the transaction prevents replay.
66#[derive(Debug, Eq, PartialEq)]
67#[repr(C)]
68pub struct RuntimeSwitchPlan {
69    previous_context: ExecutionContextHandle,
70    previous_address_space: AddressSpaceHandle,
71    next_context: ExecutionContextHandle,
72    next_address_space: AddressSpaceHandle,
73    #[cfg(feature = "qperf-metrics")]
74    qperf_prepare_started_ns: u64,
75}
76
77impl RuntimeSwitchPlan {
78    pub(crate) fn new(
79        previous_context: ExecutionContextHandle,
80        previous_address_space: AddressSpaceHandle,
81        previous_address_space_identity: AddressSpaceMembarrierId,
82        next_context: ExecutionContextHandle,
83        next_address_space: AddressSpaceHandle,
84        next_address_space_identity: AddressSpaceMembarrierId,
85    ) -> Option<Self> {
86        debug_assert_eq!(
87            previous_address_space.is_none(),
88            previous_address_space_identity.is_none(),
89        );
90        debug_assert_eq!(
91            next_address_space.is_none(),
92            next_address_space_identity.is_none(),
93        );
94        debug_assert!(
95            previous_address_space != next_address_space
96                || previous_address_space_identity == next_address_space_identity,
97        );
98        if previous_context.is_none() || next_context.is_none() || previous_context == next_context
99        {
100            None
101        } else {
102            let same_address_space = !previous_address_space_identity.is_none()
103                && previous_address_space_identity == next_address_space_identity;
104            Some(Self {
105                previous_context,
106                previous_address_space: if same_address_space {
107                    next_address_space
108                } else {
109                    previous_address_space
110                },
111                next_context,
112                next_address_space,
113                #[cfg(feature = "qperf-metrics")]
114                qperf_prepare_started_ns: 0,
115            })
116        }
117    }
118
119    /// Returns the outgoing runtime context.
120    pub const fn previous_context(&self) -> ExecutionContextHandle {
121        self.previous_context
122    }
123
124    /// Returns the outgoing logical address space, canonicalized to the
125    /// incoming live token when both endpoints select the same `mm`.
126    pub const fn previous_address_space(&self) -> AddressSpaceHandle {
127        self.previous_address_space
128    }
129
130    /// Returns the incoming runtime context.
131    pub const fn next_context(&self) -> ExecutionContextHandle {
132        self.next_context
133    }
134
135    /// Returns the incoming scheduler-selected logical address space.
136    pub const fn next_address_space(&self) -> AddressSpaceHandle {
137        self.next_address_space
138    }
139
140    /// Returns whether both scheduler endpoints select the same logical `mm`.
141    pub const fn same_address_space(&self) -> bool {
142        !self.next_address_space.is_none()
143            && self.previous_address_space.into_raw() == self.next_address_space.into_raw()
144    }
145
146    #[cfg(feature = "qperf-metrics")]
147    pub(crate) fn set_qperf_prepare_started_ns(&mut self, started_ns: u64) {
148        self.qperf_prepare_started_ns = started_ns;
149    }
150
151    #[doc(hidden)]
152    #[cfg(feature = "qperf-metrics")]
153    pub const fn qperf_prepare_started_ns(&self) -> u64 {
154        self.qperf_prepare_started_ns
155    }
156}
157
158/// Immutable scheduler snapshot of one thread's runtime switch bindings.
159///
160/// Linux keeps the architecture context and `mm` selected by the rq transition
161/// reachable without taking a second task lock after `pick_next_task()`.  The
162/// ax-task owner rq follows the same rule: task-control code republishes this
163/// value whenever the binding changes, and the switch plan consumes only the
164/// rq-owned snapshot.
165#[derive(Clone, Copy, Debug, Eq, PartialEq)]
166pub(crate) struct ThreadRuntimeBinding {
167    context: ExecutionContextHandle,
168    address_space: AddressSpaceHandle,
169}
170
171impl ThreadRuntimeBinding {
172    pub(crate) const fn new(
173        context: ExecutionContextHandle,
174        address_space: AddressSpaceHandle,
175    ) -> Self {
176        Self {
177            context,
178            address_space,
179        }
180    }
181
182    pub(crate) const fn context(self) -> ExecutionContextHandle {
183        self.context
184    }
185
186    pub(crate) const fn address_space(self) -> AddressSpaceHandle {
187        self.address_space
188    }
189}
190
191/// Scheduler entry whose context constraints the runtime must validate.
192#[derive(Clone, Copy, Debug, Eq, PartialEq)]
193#[repr(u32)]
194pub enum RuntimeScheduleOrigin {
195    /// A thread is about to publish or commit a blocking state.
196    Block   = 0,
197    /// A thread voluntarily yields its remaining service.
198    Yield   = 1,
199    /// A thread permanently exits.
200    Exit    = 2,
201    /// A sticky preemption request is serviced from task context.
202    Preempt = 3,
203}
204
205/// Typed source of one scheduler-frame baton.
206///
207/// The runtime uses this value to validate and atomically transform its
208/// CPU-local preemption state. In particular, preemption-guard exits retain
209/// their final lock depth until the scheduler frame owns the baton, closing the
210/// interrupt window between enabling preemption and entering the scheduler.
211#[derive(Clone, Copy, Debug, Eq, PartialEq)]
212#[repr(u32)]
213pub enum RuntimeSchedulerEntry {
214    /// Ordinary task context with IRQs enabled and no preemption guard.
215    Task         = 0,
216    /// Final task-context preemption guard exit with IRQs disabled.
217    ///
218    /// The runtime retains the final preemption depth while it disables raw
219    /// IRQs, then atomically converts that depth into the scheduler baton.
220    PreemptExit  = 1,
221    /// Final IRQ-return preemption guard exit with IRQs still disabled.
222    IrqReturn    = 2,
223    /// Final task-context IRQ publication guard exit with IRQs disabled.
224    ///
225    /// The runtime retains the final IRQ-guard depth after publishing local
226    /// scheduler work, then atomically converts that depth into the scheduler
227    /// baton. This is the local counterpart of a remote scheduler IPI.
228    IrqGuardExit = 3,
229    /// A repeated IRQ-return pass after the previous scheduler frame fully
230    /// released its switch baton.
231    ///
232    /// The caller enters with hardware IRQs disabled and preemption depth zero.
233    /// Before claiming the fresh scheduler baton, the runtime establishes one
234    /// ordinary preemption depth, opens the Linux-style IRQ window, disables
235    /// IRQs again, and atomically converts that depth into the scheduler baton.
236    IrqReturnContinuation = 4,
237}
238
239/// Raw IRQ state expected by the suspended scheduler continuation.
240///
241/// This is continuation-local rather than CPU-local: a context resumed by an
242/// IRQ-return schedule may itself have been suspended in an ordinary task
243/// schedule, and vice versa.
244#[derive(Clone, Copy, Debug, Eq, PartialEq)]
245#[repr(u32)]
246pub enum RuntimeSchedulerReturn {
247    /// Resume ordinary task context with local IRQs enabled.
248    Task      = 0,
249    /// Resume the architecture trap epilogue with local IRQs disabled.
250    IrqReturn = 1,
251}
252
253/// Versioned generation-bearing thread identity for runtime context binding.
254///
255/// The explicit fields keep the scheduler's private integer encoding out of OS
256/// runtime implementations while remaining a value-only trait-FFI type.
257#[derive(Clone, Copy, Debug, Eq, PartialEq)]
258#[repr(C)]
259pub struct ThreadIdentityV1 {
260    /// Task-system registry slot.
261    pub slot: u32,
262    /// Non-zero reuse generation for `slot`.
263    pub generation: u32,
264}
265
266/// Immutable scheduler publication owned by one runtime execution context.
267///
268/// This is the Rust equivalent of Linux's architecture-selected `current`
269/// pointer: the identity and its Arc-backed owner address are installed once
270/// before the context can run, then remain immutable across preemption and
271/// migration. The owner address is never a standalone weak or strong handle.
272#[derive(Clone, Copy, Debug, Eq, PartialEq)]
273#[repr(C)]
274pub struct CurrentThreadPublication {
275    identity: ThreadIdentityV1,
276    owner: CurrentThreadOwnerHandle,
277}
278
279/// Atomic runtime result of claiming one scheduler frame.
280///
281/// A successful result carries every immutable capability selected under the
282/// same IRQ-off CPU pin: task system and owner CPU endpoints. Current-thread
283/// identity is read only by operations that need it, while this frame keeps
284/// the execution context pinned; scheduler selection itself uses `rq->curr`.
285#[derive(Clone, Copy, Debug, Eq, PartialEq)]
286#[repr(C)]
287pub struct RuntimeSchedulerFrameEnterResult {
288    system: TaskSystemHandle,
289    cpu: CurrentCpuOwnerHandles,
290}
291
292impl RuntimeSchedulerFrameEnterResult {
293    /// Creates a successful scheduler-frame capability snapshot.
294    ///
295    /// # Safety
296    ///
297    /// `system` must be non-empty. All handles must describe the CPU pinned by
298    /// the scheduler baton that was claimed in the same runtime transaction.
299    pub const unsafe fn success(system: TaskSystemHandle, cpu: CurrentCpuOwnerHandles) -> Self {
300        Self { system, cpu }
301    }
302
303    /// Creates an unsafe-context rejection without live capabilities.
304    pub const fn failure() -> Self {
305        Self {
306            system: TaskSystemHandle::NONE,
307            cpu: CurrentCpuOwnerHandles::NONE,
308        }
309    }
310
311    /// Returns the runtime entry status.
312    pub const fn status(self) -> RuntimeStatus {
313        if self.system.is_none() {
314            RuntimeStatus::UnsafeContext
315        } else {
316            RuntimeStatus::Success
317        }
318    }
319
320    /// Returns the pinned task-system capability.
321    pub const fn system(self) -> TaskSystemHandle {
322        self.system
323    }
324
325    /// Returns the pinned owner-CPU capability.
326    pub const fn cpu(self) -> CurrentCpuOwnerHandles {
327        self.cpu
328    }
329}
330
331/// Borrowed view of the scheduler-owned current-thread reference.
332///
333/// Unlike [`crate::thread::ThreadHandle`], this capability does not acquire an
334/// external lifetime lease. It is confined to the current execution context;
335/// the architecture publication and scheduler-owned `rq->curr` reference keep
336/// the pointed-to core alive until the synchronous operation returns.
337pub(crate) struct CurrentThreadRef {
338    identity: crate::thread::ThreadId,
339    core: NonNull<crate::thread::ThreadCore>,
340    _not_send: PhantomData<*mut ()>,
341}
342
343impl CurrentThreadRef {
344    pub(crate) const fn id(&self) -> crate::thread::ThreadId {
345        self.identity
346    }
347
348    pub(crate) fn runtime_core(&self) -> &crate::thread::ThreadCore {
349        // SAFETY: construction validates the current publication while the
350        // scheduler retains its owner-side reference. The borrow cannot
351        // outlive this non-Send capability.
352        unsafe { self.core.as_ref() }
353    }
354}
355
356impl CurrentThreadPublication {
357    /// Sentinel returned by an unbound bootstrap execution context.
358    pub const NONE: Self = Self {
359        identity: ThreadIdentityV1::NONE,
360        owner: CurrentThreadOwnerHandle::NONE,
361    };
362
363    /// Returns the generation-bearing scheduler identity.
364    pub const fn identity(self) -> ThreadIdentityV1 {
365        self.identity
366    }
367
368    /// Returns the opaque current-owner address.
369    pub const fn owner(self) -> CurrentThreadOwnerHandle {
370        self.owner
371    }
372
373    pub(crate) fn from_core(
374        identity: crate::thread::ThreadId,
375        core: &Arc<crate::thread::ThreadCore>,
376    ) -> Self {
377        let owner = Arc::as_ptr(core).expose_provenance();
378        // SAFETY: `core` supplies the live Arc allocation. Consumers may use
379        // this address only through the checked current-publication accessors
380        // while the matching runtime context remains the executing task.
381        let owner = unsafe { CurrentThreadOwnerHandle::from_raw(owner) };
382        Self {
383            identity: ThreadIdentityV1::new(identity.slot(), identity.generation()),
384            owner,
385        }
386    }
387
388    /// Borrows the scheduler-owned current reference without creating an
389    /// external handle or changing any Arc count.
390    ///
391    /// # Safety
392    ///
393    /// The runtime must have copied this publication from the architecture-
394    /// selected current context. The caller must use the returned capability
395    /// only in the synchronous operation of that context and must not exit the
396    /// thread while it remains live.
397    pub(crate) unsafe fn borrow_current(
398        self,
399    ) -> Result<CurrentThreadRef, crate::thread::TaskError> {
400        if !self.identity.is_bound() {
401            return Err(crate::thread::TaskError::NoRunnableThread);
402        }
403        let core = NonNull::new(core::ptr::with_exposed_provenance_mut::<
404            crate::thread::ThreadCore,
405        >(self.owner.into_raw()))
406        .ok_or(crate::thread::TaskError::InvalidRuntimeHandle)?;
407        let identity =
408            crate::thread::ThreadId::from_parts(self.identity.slot, self.identity.generation);
409        let current = CurrentThreadRef {
410            identity,
411            core,
412            _not_send: PhantomData,
413        };
414        if current.runtime_core().id() != identity {
415            return Err(crate::thread::TaskError::InvalidRuntimeHandle);
416        }
417        Ok(current)
418    }
419
420    /// Acquires an ordinary external scheduler handle from the current
421    /// context's owner publication.
422    ///
423    /// # Safety
424    ///
425    /// The runtime must have copied the publication from the architecture-
426    /// selected current task context. The scheduler must retain that thread's
427    /// owner-side `Arc` while the caller can execute or resume this operation.
428    pub(crate) unsafe fn acquire_handle(
429        self,
430    ) -> Result<crate::thread::ThreadHandle, crate::thread::TaskError> {
431        let core = unsafe {
432            // SAFETY: this method has the same current-context ownership
433            // contract as `acquire_scheduler_core`.
434            self.acquire_scheduler_core()?
435        };
436        Ok(crate::thread::ThreadHandle::from_core(core))
437    }
438
439    /// Acquires a scheduler-internal strong reference without publishing an
440    /// external management lifetime lease.
441    ///
442    /// # Safety
443    ///
444    /// The runtime must have copied the publication from the architecture-
445    /// selected current task context. The scheduler must retain that thread's
446    /// owner-side `Arc` while the caller can execute or resume this operation.
447    pub(crate) unsafe fn acquire_scheduler_core(
448        self,
449    ) -> Result<Arc<crate::thread::ThreadCore>, crate::thread::TaskError> {
450        if !self.identity.is_bound() {
451            return Err(crate::thread::TaskError::NoRunnableThread);
452        }
453        if self.owner.is_none() {
454            return Err(crate::thread::TaskError::InvalidRuntimeHandle);
455        }
456        let core =
457            core::ptr::with_exposed_provenance::<crate::thread::ThreadCore>(self.owner.into_raw());
458        // SAFETY: the current-task publication contract proves that an owner-
459        // side strong reference remains live across preemption and migration.
460        unsafe { Arc::increment_strong_count(core) };
461        // SAFETY: the increment above created exactly one strong reference for
462        // this reconstruction.
463        let core = unsafe { Arc::from_raw(core) };
464        let expected =
465            crate::thread::ThreadId::from_parts(self.identity.slot, self.identity.generation);
466        if core.id() != expected {
467            return Err(crate::thread::TaskError::InvalidRuntimeHandle);
468        }
469        Ok(core)
470    }
471}
472
473impl ThreadIdentityV1 {
474    /// Sentinel returned before a runtime context is bound to a scheduler thread.
475    pub const NONE: Self = Self {
476        slot: 0,
477        generation: 0,
478    };
479
480    /// Creates a runtime identity from its explicit generation-bearing parts.
481    pub const fn new(slot: u32, generation: u32) -> Self {
482        Self { slot, generation }
483    }
484
485    /// Returns whether this value names a published scheduler generation.
486    pub const fn is_bound(self) -> bool {
487        self.generation != 0
488    }
489}
490
491/// Immutable association between one runtime context and scheduler ownership.
492///
493/// Contexts are created before the scheduler allocates a generation-bearing
494/// thread ID. The scheduler submits this value exactly once after ID allocation
495/// and before the thread can become runnable. The publication keeps only a
496/// pointer-sized owner address; it does not transfer an Arc or external reaper
497/// lease across the trait-FFI boundary.
498#[derive(Clone, Copy, Debug, Eq, PartialEq)]
499#[repr(C)]
500pub struct ContextThreadBinding {
501    /// Live runtime-owned execution context to bind.
502    pub context: ExecutionContextHandle,
503    /// Immutable current-thread publication for this execution context.
504    pub publication: CurrentThreadPublication,
505}
506
507/// Allocation-free scheduler switch diagnostic record.
508#[derive(Clone, Copy, Debug, Eq, PartialEq)]
509#[repr(C)]
510pub struct SchedSwitchRecord {
511    /// Logical CPU performing the switch.
512    pub cpu: RuntimeCpuId,
513    /// Previous generation-based thread identifier encoded as a scalar.
514    pub previous_thread: u64,
515    /// Next generation-based thread identifier encoded as a scalar.
516    pub next_thread: u64,
517    /// Monotonic switch timestamp.
518    pub timestamp_ns: u64,
519    /// Policy-specific reason code defined by ax-task.
520    pub reason: u32,
521}
522
523pub use crate::sched::system::{
524    ScheduleDecision, SchedulerOutcome, SwitchInCompletion, YieldOutcome,
525};
526
527#[cfg(test)]
528mod switch_plan_tests {
529    use super::*;
530
531    #[test]
532    fn runtime_switch_plan_keeps_context_and_logical_mm_in_one_transaction() {
533        // SAFETY: opaque values are never dereferenced by this value-only
534        // contract test.
535        let previous_context = unsafe { ExecutionContextHandle::from_raw(0x1000) };
536        // SAFETY: see above.
537        let next_context = unsafe { ExecutionContextHandle::from_raw(0x2000) };
538        // SAFETY: see above.
539        let previous_mm = unsafe { AddressSpaceHandle::from_raw(0x3000) };
540        // SAFETY: see above.
541        let next_mm = unsafe { AddressSpaceHandle::from_raw(0x4000) };
542        // SAFETY: opaque values are compared only and never dereferenced.
543        let previous_mm_identity = unsafe { AddressSpaceMembarrierId::from_raw(0x5000) };
544        // SAFETY: see above.
545        let next_mm_identity = unsafe { AddressSpaceMembarrierId::from_raw(0x6000) };
546        let plan = RuntimeSwitchPlan::new(
547            previous_context,
548            previous_mm,
549            previous_mm_identity,
550            next_context,
551            next_mm,
552            next_mm_identity,
553        )
554        .expect("two distinct live contexts must form one runtime switch plan");
555
556        assert_eq!(plan.previous_context(), previous_context);
557        assert_eq!(plan.previous_address_space(), previous_mm);
558        assert_eq!(plan.next_context(), next_context);
559        assert_eq!(plan.next_address_space(), next_mm);
560    }
561}