Skip to main content

cpu_local/
thread.rs

1use core::{
2    mem::{offset_of, size_of},
3    pin::Pin,
4    ptr::NonNull,
5    sync::atomic::{AtomicUsize, Ordering},
6};
7
8use crate::{CpuAreaRef, ThreadSwitchError};
9
10/// Stable opaque identity of one runtime-owned execution context.
11#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
12#[repr(transparent)]
13pub struct CurrentContext(usize);
14
15impl CurrentContext {
16    /// Converts a non-null opaque execution-context handle.
17    pub const fn from_raw(raw: usize) -> Option<Self> {
18        if raw == 0 { None } else { Some(Self(raw)) }
19    }
20
21    /// Returns the opaque scalar representation.
22    pub const fn as_usize(self) -> usize {
23        self.0
24    }
25}
26
27#[derive(Clone, Copy, Debug, Eq, PartialEq)]
28pub(crate) struct CpuBindingEpoch(usize);
29
30#[derive(Clone, Copy, Debug)]
31pub(crate) struct CurrentCpuBinding {
32    pub(crate) area: CpuAreaRef,
33    pub(crate) epoch: CpuBindingEpoch,
34}
35
36const CPU_PHASE_MASK: usize = 0b11;
37const CPU_UNBOUND: usize = 0b00;
38const CPU_BINDING: usize = 0b01;
39const CPU_BOUND: usize = 0b10;
40const CPU_UNBINDING: usize = 0b11;
41
42const fn current_thread_reserved_size() -> usize {
43    64 - 5 * size_of::<usize>()
44}
45
46/// Pinned scheduler/architecture header for one execution context.
47///
48/// CPU binding uses a four-phase publication word:
49/// `Unbound -> Binding -> Bound -> Unbinding -> next Unbound`. The epoch is
50/// retained solely to reject a stale incoming switch tail.
51#[repr(C, align(64))]
52pub struct CurrentThreadHeader {
53    context: usize,
54    cpu_area: AtomicUsize,
55    binding_epoch: AtomicUsize,
56    architecture_state: [AtomicUsize; 2],
57    reserved: [u8; current_thread_reserved_size()],
58}
59
60impl CurrentThreadHeader {
61    /// Creates an unbound header before placing it in stable pinned storage.
62    pub const fn new(context: CurrentContext) -> Self {
63        Self {
64            context: context.0,
65            cpu_area: AtomicUsize::new(0),
66            binding_epoch: AtomicUsize::new(CPU_UNBOUND),
67            architecture_state: [const { AtomicUsize::new(0) }; 2],
68            reserved: [0; current_thread_reserved_size()],
69        }
70    }
71
72    pub(crate) const fn boot(area_base: usize) -> Self {
73        Self {
74            context: 0,
75            cpu_area: AtomicUsize::new(area_base),
76            binding_epoch: AtomicUsize::new(CPU_BOUND),
77            architecture_state: [const { AtomicUsize::new(0) }; 2],
78            reserved: [0; current_thread_reserved_size()],
79        }
80    }
81
82    /// Returns the immutable runtime context identity, if this is a task.
83    pub const fn current_context(&self) -> Option<CurrentContext> {
84        CurrentContext::from_raw(self.context)
85    }
86
87    /// Returns the stable CPU area while this header is fully bound.
88    pub fn cpu_area(&self) -> Option<CpuAreaRef> {
89        self.cpu_binding().map(|binding| binding.area)
90    }
91
92    /// Returns the raw bound area base used by architecture trap entry.
93    pub fn cpu_area_base(&self) -> Option<usize> {
94        self.cpu_binding().map(|binding| binding.area.base())
95    }
96
97    pub(crate) unsafe fn bind_cpu(
98        self: Pin<&Self>,
99        area: CpuAreaRef,
100    ) -> Result<CpuBindingEpoch, ThreadSwitchError> {
101        let this = self.get_ref();
102        let unbound = this.binding_epoch.load(Ordering::Acquire);
103        if unbound & CPU_PHASE_MASK != CPU_UNBOUND {
104            return Err(ThreadSwitchError::NextThreadAlreadyBound);
105        }
106        this.binding_epoch
107            .compare_exchange(
108                unbound,
109                unbound | CPU_BINDING,
110                Ordering::AcqRel,
111                Ordering::Acquire,
112            )
113            .map_err(|_| ThreadSwitchError::NextThreadAlreadyBound)?;
114        this.cpu_area.store(area.base(), Ordering::Relaxed);
115        let bound = (unbound & !CPU_PHASE_MASK) | CPU_BOUND;
116        this.binding_epoch.store(bound, Ordering::Release);
117        Ok(CpuBindingEpoch(bound))
118    }
119
120    pub(crate) unsafe fn unbind_cpu(
121        self: Pin<&Self>,
122        expected: CpuBindingEpoch,
123    ) -> Result<(), ThreadSwitchError> {
124        if expected.0 & CPU_PHASE_MASK != CPU_BOUND {
125            return Err(ThreadSwitchError::StalePreviousBinding);
126        }
127        let this = self.get_ref();
128        let unbinding = (expected.0 & !CPU_PHASE_MASK) | CPU_UNBINDING;
129        this.binding_epoch
130            .compare_exchange(expected.0, unbinding, Ordering::AcqRel, Ordering::Acquire)
131            .map_err(|_| ThreadSwitchError::StalePreviousBinding)?;
132        this.cpu_area.store(0, Ordering::Relaxed);
133        let next_unbound = (expected.0 & !CPU_PHASE_MASK).wrapping_add(4);
134        this.binding_epoch.store(next_unbound, Ordering::Release);
135        Ok(())
136    }
137
138    pub(crate) fn cpu_binding(&self) -> Option<CurrentCpuBinding> {
139        let (area_base, epoch) = self.raw_cpu_binding()?;
140        // SAFETY: only bind_cpu can publish this field, and it accepts an
141        // already validated shutdown-lifetime CpuAreaRef.
142        let area = unsafe { CpuAreaRef::from_initialized_base(area_base) }.ok()?;
143        Some(CurrentCpuBinding { area, epoch })
144    }
145
146    pub(crate) fn raw_cpu_binding(&self) -> Option<(usize, CpuBindingEpoch)> {
147        loop {
148            let before = self.binding_epoch.load(Ordering::Acquire);
149            if before & CPU_PHASE_MASK != CPU_BOUND {
150                return None;
151            }
152            let area_base = self.cpu_area.load(Ordering::Relaxed);
153            let after = self.binding_epoch.load(Ordering::Acquire);
154            if before == after {
155                return Some((area_base, CpuBindingEpoch(after)));
156            }
157            core::hint::spin_loop();
158        }
159    }
160
161    /// Returns the stable pointer installed in the current-thread register.
162    pub fn as_non_null(self: Pin<&Self>) -> NonNull<Self> {
163        NonNull::from(self.get_ref())
164    }
165}
166
167/// Byte offset of the current header's bound CPU-area base.
168pub const CURRENT_THREAD_CPU_BASE_OFFSET: usize = offset_of!(CurrentThreadHeader, cpu_area);
169/// Byte offset of architecture-owned task trap state.
170pub const CURRENT_THREAD_ARCH_STATE_OFFSET: usize =
171    offset_of!(CurrentThreadHeader, architecture_state);
172/// Reserved bytes available to architecture-owned task trap state.
173pub const CURRENT_THREAD_ARCH_STATE_SIZE: usize = 2 * size_of::<usize>();
174
175const _: () = {
176    assert!(size_of::<CurrentThreadHeader>() == 64);
177    assert!(core::mem::align_of::<CurrentThreadHeader>() == 64);
178};