Skip to main content

ax_runtime/thread/
address_space.rs

1//! Runtime-owned address-space tokens and per-CPU active-mm state.
2
3use alloc::{boxed::Box, sync::Arc};
4#[cfg(feature = "uspace")]
5use core::sync::atomic::AtomicBool;
6#[cfg(feature = "qperf-metrics")]
7use core::sync::atomic::AtomicU64;
8use core::{
9    marker::PhantomData,
10    mem::align_of,
11    ptr,
12    sync::atomic::{AtomicU32, AtomicUsize, Ordering},
13};
14
15use ax_hal::percpu::CpuPin;
16use ax_memory_addr::PhysAddr;
17use ax_task::{
18    runtime::{
19        RuntimeStatus,
20        resource::{
21            AddressSpaceDestroyOutcome, AddressSpaceHandle, AddressSpaceMembarrierId,
22            AddressSpaceMembarrierState, AddressSpaceReclaimArmOutcome, AddressSpaceToken,
23            MembarrierRegistration, MembarrierRegistrationPhase,
24        },
25    },
26    thread::TaskError,
27};
28
29use super::mm_activation::UserAddressSpaceOwner;
30#[cfg(feature = "uspace")]
31use super::mm_activation::{AddressSpaceSwitchProof, SchedulerAddressSpaceActivation};
32#[cfg(feature = "uspace")]
33use super::with_current_cpu_pin;
34
35/// OS-owned lifetime anchor retained by a scheduler address-space token.
36trait TaskAddressSpaceOwner: Send + Sync {
37    /// Releases ownership that follows the attached task while retaining any
38    /// storage needed by CPUs that still carry the address space as lazy mm.
39    fn detach_from_task(&self);
40
41    #[cfg(feature = "uspace")]
42    fn prepare_activation(
43        &self,
44        _cpu: usize,
45    ) -> Result<Option<SchedulerAddressSpaceActivation>, RuntimeStatus> {
46        Ok(None)
47    }
48}
49
50struct ManagedTaskAddressSpaceOwner<T>(T);
51
52impl<T: UserAddressSpaceOwner> TaskAddressSpaceOwner for ManagedTaskAddressSpaceOwner<T> {
53    fn detach_from_task(&self) {
54        self.0.detach_from_task();
55    }
56
57    #[cfg(feature = "uspace")]
58    fn prepare_activation(
59        &self,
60        cpu: usize,
61    ) -> Result<Option<SchedulerAddressSpaceActivation>, RuntimeStatus> {
62        self.0
63            .prepare_activation(cpu)
64            .map(Some)
65            .map_err(|_| RuntimeStatus::InvalidHandle)
66    }
67}
68
69struct RetainedTaskAddressSpaceOwner<T>(T);
70
71impl<T: Send + Sync> TaskAddressSpaceOwner for RetainedTaskAddressSpaceOwner<T> {
72    fn detach_from_task(&self) {}
73}
74
75struct DetachableTaskAddressSpaceOwner<T> {
76    owner: T,
77    detached: core::sync::atomic::AtomicBool,
78    detach: fn(&T),
79}
80
81impl<T: Send + Sync> TaskAddressSpaceOwner for DetachableTaskAddressSpaceOwner<T> {
82    fn detach_from_task(&self) {
83        if !self.detached.swap(true, Ordering::AcqRel) {
84            (self.detach)(&self.owner);
85        }
86    }
87}
88
89struct RuntimeAddressSpace {
90    /// Number of runtime tokens currently borrowing this address-space owner.
91    ///
92    /// The CPU footprint itself belongs to `cpu_state`; same-mm switches keep
93    /// the existing lease even when the selected task token changes.
94    active_leases: AtomicUsize,
95    reclaim_waiting: AtomicUsize,
96    cpu_state: Arc<AddressSpaceCpuState>,
97    _owner: Box<dyn TaskAddressSpaceOwner>,
98}
99
100#[cfg(feature = "uspace")]
101impl RuntimeAddressSpace {
102    fn root(&self) -> usize {
103        self.cpu_state.root()
104    }
105}
106
107const _: () = assert!(crate::CPU_CAPACITY <= usize::BITS as usize);
108
109#[cfg(feature = "qperf-metrics")]
110static ACTIVE_MM_SAME_ACTIVATIONS: AtomicU64 = AtomicU64::new(0);
111#[cfg(feature = "qperf-metrics")]
112static ACTIVE_MM_DIFFERENT_ACTIVATIONS: AtomicU64 = AtomicU64::new(0);
113#[cfg(feature = "qperf-metrics")]
114static ACTIVE_MM_KERNEL_LAZY_ACTIVATIONS: AtomicU64 = AtomicU64::new(0);
115#[cfg(feature = "qperf-metrics")]
116static ACTIVE_MM_HARDWARE_ROOT_WRITES: AtomicU64 = AtomicU64::new(0);
117#[cfg(feature = "qperf-metrics")]
118static ACTIVE_MM_LEASE_ACTIVATIONS: AtomicU64 = AtomicU64::new(0);
119#[cfg(feature = "qperf-metrics")]
120static ACTIVE_MM_LEASE_DEACTIVATIONS: AtomicU64 = AtomicU64::new(0);
121#[cfg(feature = "qperf-metrics")]
122static ACTIVE_MM_RECLAIM_READY: AtomicU64 = AtomicU64::new(0);
123#[cfg(feature = "qperf-metrics")]
124static ACTIVE_MM_RECLAIM_DESTROYED: AtomicU64 = AtomicU64::new(0);
125
126#[cfg(feature = "qperf-metrics")]
127#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
128pub(super) struct QperfAddressSpaceMetricsSnapshot {
129    pub(super) same_activations: u64,
130    pub(super) different_activations: u64,
131    pub(super) kernel_lazy_activations: u64,
132    pub(super) hardware_root_writes: u64,
133    pub(super) lease_activations: u64,
134    pub(super) lease_deactivations: u64,
135    pub(super) reclaim_ready: u64,
136    pub(super) reclaim_destroyed: u64,
137}
138
139#[cfg(feature = "qperf-metrics")]
140pub(super) fn qperf_address_space_metrics_snapshot() -> QperfAddressSpaceMetricsSnapshot {
141    QperfAddressSpaceMetricsSnapshot {
142        same_activations: ACTIVE_MM_SAME_ACTIVATIONS.load(Ordering::Relaxed),
143        different_activations: ACTIVE_MM_DIFFERENT_ACTIVATIONS.load(Ordering::Relaxed),
144        kernel_lazy_activations: ACTIVE_MM_KERNEL_LAZY_ACTIVATIONS.load(Ordering::Relaxed),
145        hardware_root_writes: ACTIVE_MM_HARDWARE_ROOT_WRITES.load(Ordering::Relaxed),
146        lease_activations: ACTIVE_MM_LEASE_ACTIVATIONS.load(Ordering::Relaxed),
147        lease_deactivations: ACTIVE_MM_LEASE_DEACTIVATIONS.load(Ordering::Relaxed),
148        reclaim_ready: ACTIVE_MM_RECLAIM_READY.load(Ordering::Relaxed),
149        reclaim_destroyed: ACTIVE_MM_RECLAIM_DESTROYED.load(Ordering::Relaxed),
150    }
151}
152
153/// Shared CPU-footprint state for one hardware page-table root.
154///
155/// Every scheduler token for threads sharing one OS address space must carry
156/// the same tracker. The runtime publishes a CPU bit before installing the
157/// root and clears it only after replacing the hardware root, so page-table
158/// mutation can target every CPU that may retain a translation.
159pub struct AddressSpaceCpuState {
160    root: usize,
161    active_mask: ActiveCpuMask,
162    membarrier_bits: AtomicU32,
163}
164
165enum ActiveCpuMask {
166    Runtime(AtomicUsize),
167    Mm(Arc<AtomicUsize>),
168}
169
170impl AddressSpaceCpuState {
171    /// Creates inactive runtime state permanently bound to one `mm` root.
172    pub fn new(root: PhysAddr) -> Self {
173        Self {
174            root: root.as_usize(),
175            active_mask: ActiveCpuMask::Runtime(AtomicUsize::new(0)),
176            membarrier_bits: AtomicU32::new(0),
177        }
178    }
179
180    /// Shares the MM's authoritative footprint; only its activation leases may
181    /// publish or clear bits. Runtime tokens retain separate reclamation counts.
182    pub fn with_mm_active_mask(root: PhysAddr, active_mask: Arc<AtomicUsize>) -> Self {
183        Self {
184            root: root.as_usize(),
185            active_mask: ActiveCpuMask::Mm(active_mask),
186            membarrier_bits: AtomicU32::new(0),
187        }
188    }
189
190    fn matches_root(&self, root: PhysAddr) -> bool {
191        self.root() == root.as_usize()
192    }
193
194    fn root(&self) -> usize {
195        self.root
196    }
197
198    /// Returns the CPUs that may currently retain translations for this root.
199    pub fn active_mask(&self) -> usize {
200        match &self.active_mask {
201            ActiveCpuMask::Runtime(mask) => mask.load(Ordering::Acquire),
202            ActiveCpuMask::Mm(mask) => mask.load(Ordering::Acquire),
203        }
204    }
205
206    fn membarrier_state(this: &Arc<Self>) -> AddressSpaceMembarrierState {
207        let raw = Arc::as_ptr(this).expose_provenance();
208        // SAFETY: every scheduler token and `AddrSpace` owner retains this Arc,
209        // so its allocation cannot be reused while the identity is rq-visible.
210        let identity = unsafe { AddressSpaceMembarrierId::from_raw(raw) };
211        let bits = this.membarrier_bits.load(Ordering::SeqCst);
212        // SAFETY: `membarrier_bits` is changed only through the typed phase
213        // update below and therefore contains only declared registration bits.
214        unsafe { AddressSpaceMembarrierState::new(identity, bits) }
215    }
216
217    fn update_membarrier_state(
218        this: &Arc<Self>,
219        registration: MembarrierRegistration,
220        phase: MembarrierRegistrationPhase,
221    ) -> AddressSpaceMembarrierState {
222        let bit = match phase {
223            MembarrierRegistrationPhase::Begin => registration.requested_bit(),
224            MembarrierRegistrationPhase::Complete => {
225                assert!(
226                    this.membarrier_bits.load(Ordering::SeqCst) & registration.requested_bit() != 0,
227                    "membarrier registration completed before its requested phase"
228                );
229                registration.ready_bit()
230            }
231        };
232        this.membarrier_bits.fetch_or(bit, Ordering::SeqCst);
233        Self::membarrier_state(this)
234    }
235
236    #[cfg(any(feature = "uspace", test))]
237    fn cpu_bit(cpu_id: usize) -> usize {
238        1usize.checked_shl(cpu_id as u32).unwrap_or_else(|| {
239            panic!("CPU {cpu_id} cannot be represented in an address-space mask")
240        })
241    }
242
243    #[cfg(any(feature = "uspace", test))]
244    fn activate(&self, cpu_id: usize) {
245        if let ActiveCpuMask::Runtime(mask) = &self.active_mask {
246            mask.fetch_or(Self::cpu_bit(cpu_id), Ordering::Release);
247        }
248    }
249
250    #[cfg(any(feature = "uspace", test))]
251    fn deactivate(&self, cpu_id: usize) {
252        if let ActiveCpuMask::Runtime(mask) = &self.active_mask {
253            mask.fetch_and(!Self::cpu_bit(cpu_id), Ordering::Release);
254        }
255    }
256}
257
258/// Move-only runtime token for one user address space.
259pub struct TaskAddressSpace(Option<AddressSpaceToken>);
260
261impl TaskAddressSpace {
262    /// Creates a scheduler token that owns `owner` until address-space reap.
263    pub fn new(root: PhysAddr, owner: impl Send + Sync + 'static) -> Result<Self, TaskError> {
264        Self::new_with_owner(
265            root,
266            super::allocation::try_arc(AddressSpaceCpuState::new(root))
267                .map_err(|status| TaskError::RuntimeFailure(status as u32))?,
268            super::allocation::try_box(RetainedTaskAddressSpaceOwner(owner))
269                .map_err(|status| TaskError::RuntimeFailure(status as u32))?,
270        )
271    }
272
273    /// Creates a scheduler token with task-detach ownership semantics.
274    ///
275    /// `detach` runs once in ordinary task context after the attached thread
276    /// has entered lazy kernel-mm state. It may release task-scoped accounting,
277    /// but `owner` must keep the hardware page-table root valid until its final
278    /// drop after all active-CPU leases have drained.
279    pub fn new_with_task_detach<T: Send + Sync + 'static>(
280        root: PhysAddr,
281        cpu_state: Arc<AddressSpaceCpuState>,
282        owner: T,
283        detach: fn(&T),
284    ) -> Result<Self, TaskError> {
285        Self::new_with_owner(
286            root,
287            cpu_state,
288            super::allocation::try_box(DetachableTaskAddressSpaceOwner {
289                owner,
290                detached: core::sync::atomic::AtomicBool::new(false),
291                detach,
292            })
293            .map_err(|status| TaskError::RuntimeFailure(status as u32))?,
294        )
295    }
296
297    /// Creates a task token backed by an OS-owned MM lifecycle.
298    /// Allocation occurs here in task context, before scheduler publication.
299    pub fn new_managed<T: UserAddressSpaceOwner + 'static>(
300        root: PhysAddr,
301        cpu_state: Arc<AddressSpaceCpuState>,
302        owner: T,
303    ) -> Result<Self, TaskError> {
304        Self::new_with_owner(
305            root,
306            cpu_state,
307            super::allocation::try_box(ManagedTaskAddressSpaceOwner(owner))
308                .map_err(|status| TaskError::RuntimeFailure(status as u32))?,
309        )
310    }
311
312    fn new_with_owner(
313        root: PhysAddr,
314        cpu_state: Arc<AddressSpaceCpuState>,
315        owner: Box<dyn TaskAddressSpaceOwner>,
316    ) -> Result<Self, TaskError> {
317        #[cfg(feature = "fault-injection")]
318        if super::creation_probe::record(super::creation_probe::CreationEvent::Mm) {
319            return Err(TaskError::RuntimeFailure(RuntimeStatus::NoMemory as u32));
320        }
321        if root.as_usize() == 0 || !cpu_state.matches_root(root) {
322            return Err(TaskError::InvalidRuntimeHandle);
323        }
324        let address_space = super::allocation::try_box(RuntimeAddressSpace {
325            active_leases: AtomicUsize::new(0),
326            reclaim_waiting: AtomicUsize::new(0),
327            cpu_state,
328            _owner: owner,
329        })
330        .map_err(|status| TaskError::RuntimeFailure(status as u32))?;
331        let raw = Box::into_raw(address_space).expose_provenance();
332        // SAFETY: the fresh allocation transfers its unique destruction right
333        // into this move-only token.
334        Ok(Self(Some(unsafe { AddressSpaceToken::from_raw(raw) })))
335    }
336
337    #[cfg(any(feature = "uspace", test))]
338    pub(super) fn handle(&self) -> AddressSpaceHandle {
339        self.0
340            .as_ref()
341            .unwrap_or_else(|| unreachable!("address-space token already transferred"))
342            .handle()
343    }
344
345    #[cfg(feature = "uspace")]
346    pub(super) fn token_mut(&mut self) -> &mut AddressSpaceToken {
347        self.0
348            .as_mut()
349            .unwrap_or_else(|| unreachable!("address-space token already transferred"))
350    }
351
352    pub(super) fn take_token(&mut self) -> AddressSpaceToken {
353        self.0
354            .take()
355            .unwrap_or_else(|| unreachable!("address-space token already transferred"))
356    }
357}
358
359fn detach_runtime_address_space_owner(address_space: AddressSpaceHandle) {
360    runtime_address_space(address_space)
361        .unwrap_or_else(|_| panic!("address-space detach received an invalid owning handle"))
362        ._owner
363        .detach_from_task();
364}
365
366#[cfg(any(feature = "uspace", test))]
367fn detach_replaced_address_space_owner(address_space: AddressSpaceHandle) {
368    detach_runtime_address_space_owner(address_space);
369}
370
371impl Drop for TaskAddressSpace {
372    fn drop(&mut self) {
373        let Some(address_space) = self.0.take() else {
374            return;
375        };
376        detach_runtime_address_space_owner(address_space.handle());
377        let outcome = destroy_runtime_address_space(address_space.handle());
378        assert_eq!(
379            outcome,
380            AddressSpaceDestroyOutcome::Released,
381            "unpublished address space retained an active CPU lease"
382        );
383    }
384}
385
386#[ax_percpu::def_percpu]
387static ACTIVE_ADDRESS_SPACE: usize = 0;
388
389#[ax_percpu::def_percpu]
390#[cfg(feature = "uspace")]
391static ACTIVE_MM_ACTIVATION: Option<SchedulerAddressSpaceActivation> = None;
392
393#[cfg(feature = "uspace")]
394fn replace_active_activation(
395    pin: &CpuPin<'_>,
396    next: Option<SchedulerAddressSpaceActivation>,
397) -> Option<SchedulerAddressSpaceActivation> {
398    debug_assert!(!ax_cpu::interrupt::irqs_enabled());
399    // SAFETY: the root-switch transaction holds local IRQ exclusion. This
400    // CPU-only slot has no remote readers, and the mutable borrow cannot escape.
401    unsafe {
402        ax_percpu::with_exclusive_cpu(pin, |exclusive| {
403            ACTIVE_MM_ACTIVATION
404                .with_current_mut(exclusive, |active| core::mem::replace(active, next))
405        })
406    }
407}
408
409#[cfg(feature = "uspace")]
410fn install_mm_identity(installed: ax_hal::context::InstalledAddressSpace) {
411    // SAFETY: the prepared/active lease owns the root, and the caller keeps IRQs
412    // disabled from CPU-footprint publication through active-lease publication.
413    unsafe { ax_cpu::mmu::install_user_address_space(installed.hardware()) };
414    #[cfg(feature = "qperf-metrics")]
415    ACTIVE_MM_HARDWARE_ROOT_WRITES.fetch_add(1, Ordering::Relaxed);
416}
417
418/// Last-active-mm notification claimed before the raw context switch.
419///
420/// The incoming switch tail consumes this bit and returns it to ax-task. The
421/// scheduler publishes task work only after releasing the outgoing task's
422/// `on_cpu` claim, matching Linux `finish_task_switch()` ordering.
423#[ax_percpu::def_percpu]
424#[cfg(feature = "uspace")]
425static CONTEXT_SWITCH_RECLAIM_READY: AtomicBool = AtomicBool::new(false);
426
427fn runtime_address_space(
428    address_space: AddressSpaceHandle,
429) -> Result<&'static RuntimeAddressSpace, RuntimeStatus> {
430    let raw = address_space.into_raw();
431    if raw == 0 || !raw.is_multiple_of(align_of::<RuntimeAddressSpace>()) {
432        return Err(RuntimeStatus::InvalidHandle);
433    }
434    let address_space = ptr::with_exposed_provenance::<RuntimeAddressSpace>(raw);
435    // SAFETY: a borrowed handle is reachable only while its owning token or a
436    // per-CPU active lease keeps this allocation live.
437    Ok(unsafe { &*address_space })
438}
439
440#[cfg(feature = "uspace")]
441fn offline_kernel_root() -> usize {
442    if cfg!(any(target_arch = "x86_64", target_arch = "riscv64")) {
443        // SAFETY: CPU offline holds IRQ exclusion, and bring-up published the
444        // immutable root before the CPU became scheduler-visible.
445        unsafe { with_current_cpu_pin(super::bootstrap::offline_kernel_root) }
446    } else {
447        // AArch64 and LoongArch keep kernel mappings in their separate upper
448        // root. Zero leaves no lower/user translation active while offline.
449        0
450    }
451}
452
453#[cfg(feature = "uspace")]
454pub(super) fn current_hardware_root() -> usize {
455    ax_cpu::mmu::read_user_page_table().as_usize()
456}
457
458#[cfg(feature = "uspace")]
459pub(super) fn validate_current_user_address_space(
460    pin: &CpuPin<'_>,
461    selected: AddressSpaceHandle,
462) -> Result<(), RuntimeStatus> {
463    if selected.is_none() {
464        return Err(RuntimeStatus::InvalidHandle);
465    }
466    let active_raw = ACTIVE_ADDRESS_SPACE.read_current(pin);
467    if active_raw == 0 {
468        return Err(RuntimeStatus::InvalidHandle);
469    }
470    // SAFETY: a non-zero CPU-local publication originates from a live active
471    // lease and remains pinned by the IRQ-off caller.
472    let active = runtime_address_space(unsafe { AddressSpaceHandle::from_raw(active_raw) })?;
473    let selected = runtime_address_space(selected)?;
474    let cpu_bit = AddressSpaceCpuState::cpu_bit(pin.area().cpu_index().as_usize());
475    if !same_logical_address_space(active, selected)
476        || active.active_leases.load(Ordering::Acquire) == 0
477        || active.cpu_state.active_mask() & cpu_bit == 0
478        || current_hardware_root() != active.root()
479    {
480        return Err(RuntimeStatus::InvalidHandle);
481    }
482    Ok(())
483}
484
485#[cfg(any(feature = "uspace", test))]
486#[derive(Clone, Copy, Debug, Eq, PartialEq)]
487enum HardwareAddressSpaceTransition {
488    SameAddressSpace,
489    DifferentAddressSpace,
490}
491
492#[cfg(any(feature = "uspace", test))]
493fn hardware_root_install_required(
494    current_root: usize,
495    next_root: usize,
496    transition: HardwareAddressSpaceTransition,
497) -> bool {
498    current_root != next_root || transition == HardwareAddressSpaceTransition::DifferentAddressSpace
499}
500
501#[cfg(feature = "uspace")]
502fn install_hardware_root(root: usize, transition: HardwareAddressSpaceTransition) {
503    if hardware_root_install_required(current_hardware_root(), root, transition) {
504        let root = ax_memory_addr::PhysAddr::from(root);
505        // SAFETY: callers retain local IRQ exclusion for the complete active-mm
506        // transaction.
507        unsafe { ax_cpu::mmu::write_user_page_table(root) };
508        #[cfg(feature = "qperf-metrics")]
509        ACTIVE_MM_HARDWARE_ROOT_WRITES.fetch_add(1, Ordering::Relaxed);
510        // Linux reloads CR3 when the logical mm changes even if a reclaimed
511        // page-table frame gives the new mm the same root address. Otherwise
512        // non-PCID x86 can retain translations from the former mm. The other
513        // architecture backends only update their root register and require an
514        // explicit invalidation for the same identity transition.
515        #[cfg(not(target_arch = "x86_64"))]
516        ax_cpu::mmu::flush_tlb(None);
517    }
518}
519
520#[cfg(feature = "uspace")]
521fn enter_lazy_kernel_address_space() {
522    // Linux's current x86, RISC-V and LoongArch enter_lazy_tlb paths retain the
523    // loaded user root and only change scheduler/ASID bookkeeping. AArch64
524    // installs its reserved lower root so a kernel thread cannot use the
525    // previous task's user mappings.
526    #[cfg(target_arch = "aarch64")]
527    install_hardware_root(0, HardwareAddressSpaceTransition::DifferentAddressSpace);
528}
529
530#[cfg(feature = "uspace")]
531fn commit_user_address_space_activation(
532    cpu_id: usize,
533    previous_raw: usize,
534    previous: Option<&RuntimeAddressSpace>,
535    next_raw: usize,
536    next: &RuntimeAddressSpace,
537    install_root: impl FnOnce(usize, HardwareAddressSpaceTransition),
538    publish_active: impl FnOnce(usize),
539) -> bool {
540    let same_address_space = next_raw == previous_raw
541        || previous.is_some_and(|previous| same_logical_address_space(previous, next));
542    if same_address_space {
543        #[cfg(feature = "qperf-metrics")]
544        ACTIVE_MM_SAME_ACTIVATIONS.fetch_add(1, Ordering::Relaxed);
545        debug_assert_eq!(
546            previous.map(RuntimeAddressSpace::root),
547            Some(next.root()),
548            "one address-space CPU tracker cannot describe different roots"
549        );
550        // Match Linux arm64's `enter_lazy_tlb()`/`switch_mm_irqs_off()` pair:
551        // retain the active-mm lease, but restore the user root if a kernel
552        // thread temporarily installed the reserved lower root. The runtime
553        // backend suppresses the write when the hardware root is already
554        // correct, so user-to-user switches in the same mm remain a no-op.
555        install_root(
556            next.root(),
557            HardwareAddressSpaceTransition::SameAddressSpace,
558        );
559        return false;
560    }
561    #[cfg(feature = "qperf-metrics")]
562    ACTIVE_MM_DIFFERENT_ACTIVATIONS.fetch_add(1, Ordering::Relaxed);
563    next.active_leases.fetch_add(1, Ordering::AcqRel);
564    next.cpu_state.activate(cpu_id);
565    #[cfg(feature = "qperf-metrics")]
566    ACTIVE_MM_LEASE_ACTIVATIONS.fetch_add(1, Ordering::Relaxed);
567    install_root(
568        next.root(),
569        HardwareAddressSpaceTransition::DifferentAddressSpace,
570    );
571    publish_active(next_raw);
572    if let Some(previous) = previous {
573        previous.cpu_state.deactivate(cpu_id);
574        #[cfg(feature = "qperf-metrics")]
575        ACTIVE_MM_LEASE_DEACTIVATIONS.fetch_add(1, Ordering::Relaxed);
576        release_active_cpu(previous)
577    } else {
578        false
579    }
580}
581
582#[cfg(feature = "uspace")]
583fn same_logical_address_space(first: &RuntimeAddressSpace, second: &RuntimeAddressSpace) -> bool {
584    Arc::ptr_eq(&first.cpu_state, &second.cpu_state)
585}
586
587enum PreparedAddressSpaceAction {
588    KernelLazy,
589    #[cfg(all(feature = "uspace", not(target_arch = "aarch64")))]
590    SameUser,
591    #[cfg(feature = "uspace")]
592    User {
593        next_raw: usize,
594        next: &'static RuntimeAddressSpace,
595        activation: Option<SchedulerAddressSpaceActivation>,
596    },
597}
598
599#[derive(Clone, Copy, Debug, Eq, PartialEq)]
600pub(super) enum AddressSpaceTransitionPhase {
601    #[cfg(feature = "uspace")]
602    CurrentTask,
603    ContextSwitch,
604}
605
606/// CPU-bound address-space half of one scheduler switch transaction.
607///
608/// Preparation validates every handle, the scheduler-selected logical `mm`,
609/// the current active-mm lease and the membarrier identity without changing
610/// hardware or ownership. Commit is therefore infallible and may be placed
611/// immediately before the naked architecture switch.
612#[must_use = "a prepared address-space switch must be committed with its context switch"]
613pub(super) struct PreparedAddressSpaceSwitch<'pin, 'cpu> {
614    #[cfg(feature = "uspace")]
615    pin: &'pin CpuPin<'cpu>,
616    phase: AddressSpaceTransitionPhase,
617    #[cfg(feature = "uspace")]
618    previous_raw: usize,
619    #[cfg(feature = "uspace")]
620    previous: Option<&'static RuntimeAddressSpace>,
621    action: PreparedAddressSpaceAction,
622    _not_send_or_sync: PhantomData<(&'pin CpuPin<'cpu>, *mut ())>,
623}
624
625impl PreparedAddressSpaceSwitch<'_, '_> {
626    /// Commits the active-mm transition without running fallible logic.
627    #[inline(always)]
628    pub(super) fn commit(self) {
629        #[cfg(feature = "uspace")]
630        let pin = self.pin;
631        match self.phase {
632            #[cfg(feature = "uspace")]
633            AddressSpaceTransitionPhase::CurrentTask => assert!(
634                !ax_cpu::interrupt::irqs_enabled(),
635                "current-task address-space commit requires local IRQ exclusion"
636            ),
637            AddressSpaceTransitionPhase::ContextSwitch => {}
638        }
639        #[cfg(not(feature = "uspace"))]
640        debug_assert_eq!(
641            self.phase,
642            AddressSpaceTransitionPhase::ContextSwitch,
643            "kernel-only builds prepare address spaces only for scheduler switches"
644        );
645        #[cfg(feature = "uspace")]
646        assert_eq!(
647            ACTIVE_ADDRESS_SPACE.read_current(pin),
648            self.previous_raw,
649            "active address space changed after switch preparation"
650        );
651
652        match self.action {
653            PreparedAddressSpaceAction::KernelLazy => {
654                #[cfg(feature = "uspace")]
655                {
656                    #[cfg(feature = "qperf-metrics")]
657                    ACTIVE_MM_KERNEL_LAZY_ACTIVATIONS.fetch_add(1, Ordering::Relaxed);
658                    enter_lazy_kernel_address_space();
659                }
660            }
661            #[cfg(all(feature = "uspace", not(target_arch = "aarch64")))]
662            PreparedAddressSpaceAction::SameUser => {}
663            #[cfg(feature = "uspace")]
664            PreparedAddressSpaceAction::User {
665                next_raw,
666                next,
667                mut activation,
668            } => {
669                let cpu_id = pin.area().cpu_index().as_usize();
670                let installed = activation
671                    .as_ref()
672                    .map(SchedulerAddressSpaceActivation::installed)
673                    .or_else(|| {
674                        if !self
675                            .previous
676                            .is_some_and(|previous| same_logical_address_space(previous, next))
677                        {
678                            return None;
679                        }
680                        ACTIVE_MM_ACTIVATION.with_current(pin, |active| {
681                            active
682                                .as_ref()
683                                .map(SchedulerAddressSpaceActivation::installed)
684                        })
685                    });
686                let reclaim_ready = commit_user_address_space_activation(
687                    pin.area().cpu_index().as_usize(),
688                    self.previous_raw,
689                    self.previous,
690                    next_raw,
691                    next,
692                    |root, transition| {
693                        if let Some(installed) = installed {
694                            if hardware_root_install_required(
695                                current_hardware_root(),
696                                root,
697                                transition,
698                            ) {
699                                install_mm_identity(installed);
700                            }
701                        } else {
702                            install_hardware_root(root, transition);
703                        }
704                    },
705                    |active| {
706                        if let Some(next) = activation.as_mut() {
707                            next.commit(cpu_id);
708                        }
709                        let previous = replace_active_activation(pin, activation.take());
710                        ACTIVE_ADDRESS_SPACE.write_current(pin, active);
711                        if let Some(previous) = previous {
712                            previous.release(AddressSpaceSwitchProof::new(cpu_id));
713                        }
714                    },
715                );
716                if reclaim_ready {
717                    route_reclaim_notification(
718                        self.phase,
719                        || {
720                            CONTEXT_SWITCH_RECLAIM_READY.with_current(pin, |pending| {
721                                assert!(
722                                    !pending.swap(true, Ordering::AcqRel),
723                                    "context switch retained an unconsumed active-mm reclaim edge"
724                                );
725                            });
726                        },
727                        ax_task::runtime::resource::notify_address_space_reclaim,
728                    );
729                }
730            }
731        }
732    }
733}
734
735/// Validates and prepares the address-space half of a scheduler switch.
736pub(super) fn prepare_runtime_address_space_switch<'pin, 'cpu>(
737    _pin: &'pin CpuPin<'cpu>,
738    previous_selected: AddressSpaceHandle,
739    next_selected: AddressSpaceHandle,
740    same_address_space: bool,
741    phase: AddressSpaceTransitionPhase,
742) -> Result<PreparedAddressSpaceSwitch<'pin, 'cpu>, RuntimeStatus> {
743    #[cfg(feature = "fault-injection")]
744    super::creation_probe::record_mm_switch(!previous_selected.is_none(), !next_selected.is_none());
745    #[cfg(feature = "uspace")]
746    let pin = _pin;
747    #[cfg(feature = "uspace")]
748    let cpu_id = pin.area().cpu_index().as_usize();
749    #[cfg(any(not(feature = "uspace"), target_arch = "aarch64"))]
750    let _ = same_address_space;
751
752    #[cfg(feature = "uspace")]
753    {
754        let previous_raw = ACTIVE_ADDRESS_SPACE.read_current(pin);
755        #[cfg(not(target_arch = "aarch64"))]
756        if same_address_space {
757            debug_assert!(!previous_selected.is_none());
758            debug_assert!(!next_selected.is_none());
759            debug_assert_ne!(previous_raw, 0);
760            return Ok(PreparedAddressSpaceSwitch {
761                pin,
762                phase,
763                previous_raw,
764                previous: None,
765                action: PreparedAddressSpaceAction::SameUser,
766                _not_send_or_sync: PhantomData,
767            });
768        }
769        let previous = if previous_raw == 0 {
770            None
771        } else {
772            // SAFETY: a non-zero CPU-local publication is created only from a
773            // live runtime address-space handle and retains its active lease.
774            let previous = unsafe { AddressSpaceHandle::from_raw(previous_raw) };
775            Some(runtime_address_space(previous)?)
776        };
777
778        #[cfg(not(target_arch = "aarch64"))]
779        if let Some((previous, next)) = previous.zip(
780            (!next_selected.is_none())
781                .then(|| runtime_address_space(next_selected))
782                .transpose()?,
783        ) && same_logical_address_space(previous, next)
784        {
785            // Linux's ordinary same-mm path compares the CPU's loaded mm with
786            // next->mm and returns without inspecting the old task token,
787            // active mask, lease count or hardware root. x86, RISC-V and
788            // LoongArch retain the loaded root across lazy kernel threads, so
789            // their user-to-user same-mm switch is likewise a pure no-op.
790            // AArch64 is excluded because its lazy path installs the reserved
791            // lower root and must restore TTBR0 on the following user switch.
792            debug_assert!(!previous_selected.is_none());
793            debug_assert!(
794                runtime_address_space(previous_selected)
795                    .is_ok_and(|selected| same_logical_address_space(previous, selected))
796            );
797            debug_assert_ne!(previous.active_leases.load(Ordering::Acquire), 0);
798            debug_assert!(
799                previous.cpu_state.active_mask() & AddressSpaceCpuState::cpu_bit(cpu_id) != 0
800            );
801            return Ok(PreparedAddressSpaceSwitch {
802                pin,
803                phase,
804                previous_raw,
805                previous: Some(previous),
806                action: PreparedAddressSpaceAction::SameUser,
807                _not_send_or_sync: PhantomData,
808            });
809        }
810
811        if let Some(previous) = previous {
812            let bit = AddressSpaceCpuState::cpu_bit(cpu_id);
813            if previous.active_leases.load(Ordering::Acquire) == 0
814                || previous.cpu_state.active_mask() & bit == 0
815            {
816                return Err(RuntimeStatus::InvalidHandle);
817            }
818        }
819
820        let previous_state = if previous_selected.is_none() {
821            None
822        } else {
823            let selected = runtime_address_space(previous_selected)?;
824            let Some(active) = previous else {
825                return Err(RuntimeStatus::InvalidArgument);
826            };
827            if !same_logical_address_space(active, selected) {
828                return Err(RuntimeStatus::InvalidArgument);
829            }
830            Some(selected)
831        };
832
833        let (next_state, action) = if next_selected.is_none() {
834            (None, PreparedAddressSpaceAction::KernelLazy)
835        } else {
836            let next = runtime_address_space(next_selected)?;
837            (
838                Some(next),
839                PreparedAddressSpaceAction::User {
840                    next_raw: next_selected.into_raw(),
841                    next,
842                    activation: if previous
843                        .is_some_and(|previous| same_logical_address_space(previous, next))
844                    {
845                        None
846                    } else {
847                        next._owner.prepare_activation(cpu_id)?
848                    },
849                },
850            )
851        };
852
853        // The switch barrier is keyed only by the logical mm identity. The
854        // membarrier registration bits are consumed by their own syscall
855        // paths; loading them on every same-mm thread switch is not part of
856        // Linux switch_mm() semantics and adds a SeqCst read to the hot path.
857        let changed_address_space = match (previous_state, next_state) {
858            (Some(previous), Some(next)) => !same_logical_address_space(previous, next),
859            (Some(_), None) | (None, Some(_)) => true,
860            (None, None) => false,
861        };
862        if changed_address_space {
863            // Common four-architecture counterpart of Linux switch_mm() and
864            // mmdrop's ordering after rq->curr publication and before user
865            // execution.
866            core::sync::atomic::fence(Ordering::SeqCst);
867        }
868
869        Ok(PreparedAddressSpaceSwitch {
870            pin,
871            phase,
872            previous_raw,
873            previous,
874            action,
875            _not_send_or_sync: PhantomData,
876        })
877    }
878
879    #[cfg(not(feature = "uspace"))]
880    {
881        if !previous_selected.is_none() || !next_selected.is_none() {
882            return Err(RuntimeStatus::Unsupported);
883        }
884        Ok(PreparedAddressSpaceSwitch {
885            phase,
886            action: PreparedAddressSpaceAction::KernelLazy,
887            _not_send_or_sync: PhantomData,
888        })
889    }
890}
891
892pub(super) fn release_current_active_address_space() {
893    #[cfg(feature = "uspace")]
894    let reclaim_ready = unsafe {
895        with_current_cpu_pin(|pin| {
896            let previous_raw = ACTIVE_ADDRESS_SPACE.read_current(pin);
897            if previous_raw == 0 {
898                return false;
899            }
900            install_hardware_root(
901                offline_kernel_root(),
902                HardwareAddressSpaceTransition::DifferentAddressSpace,
903            );
904            // CPU offline invalidates every local tag before retiring its MM.
905            ax_cpu::mmu::flush_tlb(None);
906            if let Some(activation) = replace_active_activation(pin, None) {
907                activation.release(AddressSpaceSwitchProof::new(
908                    pin.area().cpu_index().as_usize(),
909                ));
910            }
911            ACTIVE_ADDRESS_SPACE.write_current(pin, 0);
912            let previous = AddressSpaceHandle::from_raw(previous_raw);
913            let previous = runtime_address_space(previous)
914                .unwrap_or_else(|_| panic!("offline CPU retained a stale active address space"));
915            previous
916                .cpu_state
917                .deactivate(pin.area().cpu_index().as_usize());
918            #[cfg(feature = "qperf-metrics")]
919            ACTIVE_MM_LEASE_DEACTIVATIONS.fetch_add(1, Ordering::Relaxed);
920            release_active_cpu(previous)
921        })
922    };
923    #[cfg(feature = "uspace")]
924    if reclaim_ready {
925        ax_task::runtime::resource::notify_address_space_reclaim();
926    }
927}
928
929pub(super) fn destroy_runtime_address_space(
930    address_space: AddressSpaceHandle,
931) -> AddressSpaceDestroyOutcome {
932    let address_space = runtime_address_space(address_space)
933        .unwrap_or_else(|_| panic!("address-space destruction received an invalid owning handle"));
934    if address_space.active_leases.load(Ordering::Acquire) != 0 {
935        return AddressSpaceDestroyOutcome::Active;
936    }
937    let raw = address_space as *const RuntimeAddressSpace as *mut RuntimeAddressSpace;
938    // SAFETY: the caller owns the unique AddressSpaceToken destruction right,
939    // and the zero active count proves no CPU retains a borrowed pointer.
940    drop(unsafe { Box::from_raw(raw) });
941    #[cfg(feature = "qperf-metrics")]
942    ACTIVE_MM_RECLAIM_DESTROYED.fetch_add(1, Ordering::Relaxed);
943    AddressSpaceDestroyOutcome::Released
944}
945
946pub(super) fn arm_runtime_address_space_reclaim(
947    address_space: AddressSpaceHandle,
948) -> AddressSpaceReclaimArmOutcome {
949    let address_space = runtime_address_space(address_space)
950        .unwrap_or_else(|_| panic!("address-space reclaim arm received an invalid owning handle"));
951    address_space.reclaim_waiting.store(1, Ordering::Release);
952    if address_space.active_leases.load(Ordering::Acquire) == 0 {
953        address_space.reclaim_waiting.store(0, Ordering::Release);
954        AddressSpaceReclaimArmOutcome::Ready
955    } else {
956        AddressSpaceReclaimArmOutcome::Armed
957    }
958}
959
960pub(super) fn runtime_address_space_membarrier_state(
961    address_space: AddressSpaceHandle,
962) -> AddressSpaceMembarrierState {
963    let address_space = runtime_address_space(address_space)
964        .unwrap_or_else(|_| panic!("membarrier received an invalid address-space handle"));
965    AddressSpaceCpuState::membarrier_state(&address_space.cpu_state)
966}
967
968pub(super) fn update_runtime_address_space_membarrier_state(
969    address_space: AddressSpaceHandle,
970    registration: MembarrierRegistration,
971    phase: MembarrierRegistrationPhase,
972) -> AddressSpaceMembarrierState {
973    let address_space = runtime_address_space(address_space).unwrap_or_else(|_| {
974        panic!("membarrier registration received an invalid address-space handle")
975    });
976    AddressSpaceCpuState::update_membarrier_state(&address_space.cpu_state, registration, phase)
977}
978
979#[cfg(any(feature = "uspace", test))]
980fn release_active_cpu(address_space: &RuntimeAddressSpace) -> bool {
981    let active = address_space.active_leases.fetch_sub(1, Ordering::AcqRel);
982    assert!(active >= 1, "active address-space lease count underflow");
983    let reclaim_ready = active == 1 && address_space.reclaim_waiting.swap(0, Ordering::AcqRel) != 0;
984    #[cfg(feature = "qperf-metrics")]
985    if reclaim_ready {
986        ACTIVE_MM_RECLAIM_READY.fetch_add(1, Ordering::Relaxed);
987    }
988    reclaim_ready
989}
990
991#[cfg(any(feature = "uspace", test))]
992fn route_reclaim_notification(
993    phase: AddressSpaceTransitionPhase,
994    defer: impl FnOnce(),
995    publish: impl FnOnce(),
996) {
997    #[cfg(not(feature = "uspace"))]
998    let _ = publish;
999    match phase {
1000        #[cfg(feature = "uspace")]
1001        AddressSpaceTransitionPhase::CurrentTask => publish(),
1002        AddressSpaceTransitionPhase::ContextSwitch => defer(),
1003    }
1004}
1005
1006pub(super) fn take_context_switch_reclaim_ready() -> bool {
1007    #[cfg(feature = "uspace")]
1008    {
1009        // SAFETY: the incoming runtime switch tail retains the scheduler baton
1010        // and therefore cannot migrate while consuming the CPU-local edge.
1011        unsafe {
1012            with_current_cpu_pin(|pin| {
1013                CONTEXT_SWITCH_RECLAIM_READY
1014                    .with_current(pin, |pending| pending.swap(false, Ordering::AcqRel))
1015            })
1016        }
1017    }
1018    #[cfg(not(feature = "uspace"))]
1019    {
1020        false
1021    }
1022}
1023
1024#[cfg(feature = "uspace")]
1025fn commit_current_task_address_space_transition<T>(
1026    next_selected: AddressSpaceHandle,
1027    action: impl FnOnce() -> Result<T, TaskError>,
1028) -> Result<T, TaskError> {
1029    let _irq = crate::task::sync::IrqSaveGuard::new();
1030    // SAFETY: the IRQ guard pins the current CPU through preparation, the
1031    // scheduler-token operation, and the infallible active-mm commit.
1032    unsafe {
1033        with_current_cpu_pin(|pin| {
1034            let previous_selected = ax_task::runtime::resource::current_address_space_handle()?;
1035            let prepared = prepare_runtime_address_space_switch(
1036                pin,
1037                previous_selected,
1038                next_selected,
1039                false,
1040                AddressSpaceTransitionPhase::CurrentTask,
1041            )
1042            .map_err(super::runtime_status_error)?;
1043            let result = action()?;
1044            // No fallible operation may follow the ownership transition above.
1045            prepared.commit();
1046            Ok(result)
1047        })
1048    }
1049}
1050
1051/// Replaces the running user task's owning address-space token.
1052pub fn switch_current_address_space(address_space: TaskAddressSpace) -> Result<(), TaskError> {
1053    #[cfg(feature = "uspace")]
1054    {
1055        let mut address_space = address_space;
1056        let next_handle = address_space.handle();
1057        let previous = commit_current_task_address_space_transition(next_handle, || {
1058            ax_task::runtime::resource::replace_current_address_space(address_space.token_mut())
1059        })?;
1060        let transferred = address_space.take_token();
1061        debug_assert!(transferred.is_none());
1062
1063        // Reclaim may allocate or drop an OS ownership anchor. It therefore
1064        // runs only after the exec transaction has restored normal IRQ state.
1065        detach_replaced_address_space_owner(previous.handle());
1066        ax_task::runtime::resource::release_address_space_token(previous)
1067    }
1068    #[cfg(not(feature = "uspace"))]
1069    {
1070        let _ = address_space;
1071        Err(TaskError::RuntimeFailure(RuntimeStatus::Unsupported as u32))
1072    }
1073}
1074
1075/// Detaches the running user task from its address space before exit
1076/// publication, matching Linux `exit_mm` ordering.
1077pub fn detach_current_address_space() -> Result<(), TaskError> {
1078    #[cfg(feature = "uspace")]
1079    {
1080        let previous = commit_current_task_address_space_transition(
1081            AddressSpaceHandle::NONE,
1082            ax_task::runtime::resource::detach_current_address_space,
1083        )?;
1084
1085        // The task-scoped owner may acquire sleepable OS locks. Run it only
1086        // after restoring IRQs, while the runtime wrapper still pins the root
1087        // for any CPU retaining it as a lazy active mm.
1088        detach_runtime_address_space_owner(previous.handle());
1089        ax_task::runtime::resource::release_address_space_token(previous)
1090    }
1091    #[cfg(not(feature = "uspace"))]
1092    {
1093        Err(TaskError::RuntimeFailure(RuntimeStatus::Unsupported as u32))
1094    }
1095}
1096
1097#[cfg(test)]
1098mod tests {
1099    use alloc::sync::Arc;
1100    use core::sync::atomic::{AtomicUsize, Ordering};
1101
1102    use super::*;
1103
1104    struct CountDrop(Arc<AtomicUsize>);
1105
1106    impl Drop for CountDrop {
1107        fn drop(&mut self) {
1108            self.0.fetch_add(1, Ordering::Release);
1109        }
1110    }
1111
1112    #[test]
1113    fn switch_activation_defers_reclaim_notification_until_switch_tail() {
1114        let deferred = AtomicUsize::new(0);
1115        let published = AtomicUsize::new(0);
1116
1117        route_reclaim_notification(
1118            AddressSpaceTransitionPhase::ContextSwitch,
1119            || {
1120                deferred.fetch_add(1, Ordering::Relaxed);
1121            },
1122            || {
1123                published.fetch_add(1, Ordering::Relaxed);
1124            },
1125        );
1126
1127        assert_eq!(deferred.load(Ordering::Relaxed), 1);
1128        assert_eq!(published.load(Ordering::Relaxed), 0);
1129    }
1130
1131    #[test]
1132    fn active_cpu_lease_blocks_owner_destruction_until_release() {
1133        let drops = Arc::new(AtomicUsize::new(0));
1134        let mut token = TaskAddressSpace::new(
1135            ax_memory_addr::PhysAddr::from(0x4000),
1136            CountDrop(Arc::clone(&drops)),
1137        )
1138        .unwrap();
1139        let handle = token.handle();
1140        let runtime = runtime_address_space(handle).unwrap();
1141        runtime.active_leases.fetch_add(1, Ordering::AcqRel);
1142        let owned = token.take_token();
1143
1144        assert_eq!(
1145            destroy_runtime_address_space(handle),
1146            AddressSpaceDestroyOutcome::Active
1147        );
1148        assert_eq!(
1149            arm_runtime_address_space_reclaim(handle),
1150            AddressSpaceReclaimArmOutcome::Armed
1151        );
1152        assert_eq!(drops.load(Ordering::Acquire), 0);
1153
1154        assert!(release_active_cpu(runtime));
1155        assert_eq!(
1156            destroy_runtime_address_space(handle),
1157            AddressSpaceDestroyOutcome::Released
1158        );
1159        assert_eq!(drops.load(Ordering::Acquire), 1);
1160        assert!(!owned.is_none());
1161    }
1162
1163    #[test]
1164    fn task_detach_releases_task_owner_before_lazy_cpu_lease() {
1165        struct CountDetach {
1166            detaches: Arc<AtomicUsize>,
1167            drops: Arc<AtomicUsize>,
1168        }
1169
1170        impl Drop for CountDetach {
1171            fn drop(&mut self) {
1172                self.drops.fetch_add(1, Ordering::Release);
1173            }
1174        }
1175
1176        fn detach(owner: &CountDetach) {
1177            owner.detaches.fetch_add(1, Ordering::Release);
1178        }
1179
1180        let detaches = Arc::new(AtomicUsize::new(0));
1181        let drops = Arc::new(AtomicUsize::new(0));
1182        let mut token = TaskAddressSpace::new_with_task_detach(
1183            ax_memory_addr::PhysAddr::from(0x4000),
1184            Arc::new(AddressSpaceCpuState::new(PhysAddr::from(0x4000))),
1185            CountDetach {
1186                detaches: Arc::clone(&detaches),
1187                drops: Arc::clone(&drops),
1188            },
1189            detach,
1190        )
1191        .unwrap();
1192        let handle = token.handle();
1193        let runtime = runtime_address_space(handle).unwrap();
1194        runtime.active_leases.fetch_add(1, Ordering::AcqRel);
1195        let owned = token.take_token();
1196
1197        detach_runtime_address_space_owner(handle);
1198        detach_runtime_address_space_owner(handle);
1199        assert_eq!(detaches.load(Ordering::Acquire), 1);
1200        assert_eq!(drops.load(Ordering::Acquire), 0);
1201        assert_eq!(
1202            destroy_runtime_address_space(handle),
1203            AddressSpaceDestroyOutcome::Active
1204        );
1205
1206        assert!(!release_active_cpu(runtime));
1207        assert_eq!(
1208            destroy_runtime_address_space(handle),
1209            AddressSpaceDestroyOutcome::Released
1210        );
1211        assert_eq!(drops.load(Ordering::Acquire), 1);
1212        assert!(!owned.is_none());
1213    }
1214
1215    #[test]
1216    fn replaced_task_owner_is_detached_before_runtime_release() {
1217        struct CountDetach(Arc<AtomicUsize>);
1218
1219        fn detach(owner: &CountDetach) {
1220            owner.0.fetch_add(1, Ordering::Release);
1221        }
1222
1223        let detaches = Arc::new(AtomicUsize::new(0));
1224        let mut token = TaskAddressSpace::new_with_task_detach(
1225            PhysAddr::from(0x4000),
1226            Arc::new(AddressSpaceCpuState::new(PhysAddr::from(0x4000))),
1227            CountDetach(Arc::clone(&detaches)),
1228            detach,
1229        )
1230        .unwrap();
1231        let handle = token.handle();
1232        let owned = token.take_token();
1233
1234        detach_replaced_address_space_owner(handle);
1235        assert_eq!(detaches.load(Ordering::Acquire), 1);
1236        assert_eq!(
1237            destroy_runtime_address_space(handle),
1238            AddressSpaceDestroyOutcome::Released
1239        );
1240        assert!(!owned.is_none());
1241    }
1242
1243    #[test]
1244    fn shared_cpu_state_publishes_and_withdraws_cpu_footprints() {
1245        let tracker = AddressSpaceCpuState::new(PhysAddr::from(0x4000));
1246
1247        tracker.activate(1);
1248        tracker.activate(3);
1249        assert_eq!(tracker.active_mask(), (1usize << 1) | (1usize << 3));
1250
1251        tracker.deactivate(1);
1252        assert_eq!(tracker.active_mask(), 1usize << 3);
1253    }
1254
1255    #[test]
1256    fn address_space_cpu_state_rejects_mismatched_root() {
1257        let tracker = Arc::new(AddressSpaceCpuState::new(PhysAddr::from(0x4000)));
1258        let token =
1259            TaskAddressSpace::new_with_task_detach(PhysAddr::from(0x8000), tracker, (), |_| {});
1260
1261        assert!(matches!(token, Err(TaskError::InvalidRuntimeHandle)));
1262    }
1263
1264    #[test]
1265    fn shared_mm_tokens_share_membarrier_identity_and_registration() {
1266        let cpu_state = Arc::new(AddressSpaceCpuState::new(PhysAddr::from(0x4000)));
1267        let first = TaskAddressSpace::new_with_task_detach(
1268            PhysAddr::from(0x4000),
1269            Arc::clone(&cpu_state),
1270            (),
1271            |_| {},
1272        )
1273        .unwrap();
1274        let second = TaskAddressSpace::new_with_task_detach(
1275            PhysAddr::from(0x4000),
1276            Arc::clone(&cpu_state),
1277            (),
1278            |_| {},
1279        )
1280        .unwrap();
1281
1282        let requested = update_runtime_address_space_membarrier_state(
1283            first.handle(),
1284            MembarrierRegistration::PrivateExpedited,
1285            MembarrierRegistrationPhase::Begin,
1286        );
1287        let observed = runtime_address_space_membarrier_state(second.handle());
1288        assert_eq!(requested, observed);
1289        assert!(observed.requested(MembarrierRegistration::PrivateExpedited));
1290        assert!(!observed.ready(MembarrierRegistration::PrivateExpedited));
1291
1292        let ready = update_runtime_address_space_membarrier_state(
1293            second.handle(),
1294            MembarrierRegistration::PrivateExpedited,
1295            MembarrierRegistrationPhase::Complete,
1296        );
1297        assert_eq!(
1298            runtime_address_space_membarrier_state(first.handle()),
1299            ready
1300        );
1301        assert!(ready.ready(MembarrierRegistration::PrivateExpedited));
1302    }
1303
1304    #[cfg(feature = "uspace")]
1305    #[test]
1306    fn same_mm_activation_retains_the_existing_cpu_lease_without_hardware_work() {
1307        let tracker = Arc::new(AddressSpaceCpuState::new(PhysAddr::from(0x4000)));
1308        let previous = TaskAddressSpace::new_with_task_detach(
1309            PhysAddr::from(0x4000),
1310            Arc::clone(&tracker),
1311            (),
1312            |_| {},
1313        )
1314        .unwrap();
1315        let next = TaskAddressSpace::new_with_task_detach(
1316            PhysAddr::from(0x4000),
1317            Arc::clone(&tracker),
1318            (),
1319            |_| {},
1320        )
1321        .unwrap();
1322        let previous_runtime = runtime_address_space(previous.handle()).unwrap();
1323        let next_runtime = runtime_address_space(next.handle()).unwrap();
1324        previous_runtime.active_leases.store(1, Ordering::Release);
1325        tracker.activate(0);
1326        let hardware_root = AtomicUsize::new(0x4000);
1327        let hardware_installs = AtomicUsize::new(0);
1328        let active_publications = AtomicUsize::new(0);
1329
1330        let reclaim_ready = commit_user_address_space_activation(
1331            0,
1332            previous.handle().into_raw(),
1333            Some(previous_runtime),
1334            next.handle().into_raw(),
1335            next_runtime,
1336            |root, _transition| {
1337                if hardware_root.swap(root, Ordering::AcqRel) != root {
1338                    hardware_installs.fetch_add(1, Ordering::Relaxed);
1339                }
1340            },
1341            |_| {
1342                active_publications.fetch_add(1, Ordering::Relaxed);
1343            },
1344        );
1345
1346        let previous_leases = previous_runtime.active_leases.load(Ordering::Acquire);
1347        let next_leases = next_runtime.active_leases.load(Ordering::Acquire);
1348        previous_runtime.active_leases.store(0, Ordering::Release);
1349        next_runtime.active_leases.store(0, Ordering::Release);
1350        tracker.deactivate(0);
1351
1352        assert_eq!(previous_leases, 1);
1353        assert_eq!(next_leases, 0);
1354        assert_eq!(hardware_installs.load(Ordering::Relaxed), 0);
1355        assert_eq!(active_publications.load(Ordering::Relaxed), 0);
1356        assert!(!reclaim_ready);
1357    }
1358
1359    #[cfg(feature = "uspace")]
1360    #[test]
1361    fn lazy_kernel_root_is_restored_before_same_mm_user_execution() {
1362        let tracker = Arc::new(AddressSpaceCpuState::new(PhysAddr::from(0x4000)));
1363        let previous = TaskAddressSpace::new_with_task_detach(
1364            PhysAddr::from(0x4000),
1365            Arc::clone(&tracker),
1366            (),
1367            |_| {},
1368        )
1369        .unwrap();
1370        let next = TaskAddressSpace::new_with_task_detach(
1371            PhysAddr::from(0x4000),
1372            Arc::clone(&tracker),
1373            (),
1374            |_| {},
1375        )
1376        .unwrap();
1377        let previous_runtime = runtime_address_space(previous.handle()).unwrap();
1378        let next_runtime = runtime_address_space(next.handle()).unwrap();
1379        previous_runtime.active_leases.store(1, Ordering::Release);
1380        tracker.activate(0);
1381        let hardware_root = AtomicUsize::new(0);
1382        let hardware_installs = AtomicUsize::new(0);
1383        let active_publications = AtomicUsize::new(0);
1384
1385        let reclaim_ready = commit_user_address_space_activation(
1386            0,
1387            previous.handle().into_raw(),
1388            Some(previous_runtime),
1389            next.handle().into_raw(),
1390            next_runtime,
1391            |root, _transition| {
1392                if hardware_root.swap(root, Ordering::AcqRel) != root {
1393                    hardware_installs.fetch_add(1, Ordering::Relaxed);
1394                }
1395            },
1396            |_| {
1397                active_publications.fetch_add(1, Ordering::Relaxed);
1398            },
1399        );
1400
1401        let previous_leases = previous_runtime.active_leases.load(Ordering::Acquire);
1402        let next_leases = next_runtime.active_leases.load(Ordering::Acquire);
1403        previous_runtime.active_leases.store(0, Ordering::Release);
1404        next_runtime.active_leases.store(0, Ordering::Release);
1405        tracker.deactivate(0);
1406
1407        assert_eq!(hardware_root.load(Ordering::Acquire), 0x4000);
1408        assert_eq!(hardware_installs.load(Ordering::Relaxed), 1);
1409        assert_eq!(previous_leases, 1);
1410        assert_eq!(next_leases, 0);
1411        assert_eq!(active_publications.load(Ordering::Relaxed), 0);
1412        assert!(!reclaim_ready);
1413    }
1414
1415    #[cfg(feature = "uspace")]
1416    #[test]
1417    fn same_mm_chain_releases_the_retained_active_handle_on_other_mm_switch() {
1418        let shared = Arc::new(AddressSpaceCpuState::new(PhysAddr::from(0x4000)));
1419        let other = Arc::new(AddressSpaceCpuState::new(PhysAddr::from(0x8000)));
1420        let first = TaskAddressSpace::new_with_task_detach(
1421            PhysAddr::from(0x4000),
1422            Arc::clone(&shared),
1423            (),
1424            |_| {},
1425        )
1426        .unwrap();
1427        let second = TaskAddressSpace::new_with_task_detach(
1428            PhysAddr::from(0x4000),
1429            Arc::clone(&shared),
1430            (),
1431            |_| {},
1432        )
1433        .unwrap();
1434        let third = TaskAddressSpace::new_with_task_detach(
1435            PhysAddr::from(0x8000),
1436            Arc::clone(&other),
1437            (),
1438            |_| {},
1439        )
1440        .unwrap();
1441        let first_runtime = runtime_address_space(first.handle()).unwrap();
1442        let second_runtime = runtime_address_space(second.handle()).unwrap();
1443        let third_runtime = runtime_address_space(third.handle()).unwrap();
1444        first_runtime.active_leases.store(1, Ordering::Release);
1445        shared.activate(0);
1446        let active = AtomicUsize::new(first.handle().into_raw());
1447
1448        assert!(same_logical_address_space(first_runtime, second_runtime));
1449        assert!(!commit_user_address_space_activation(
1450            0,
1451            active.load(Ordering::Acquire),
1452            Some(first_runtime),
1453            second.handle().into_raw(),
1454            second_runtime,
1455            |_, _| {},
1456            |next| active.store(next, Ordering::Release),
1457        ));
1458        assert_eq!(active.load(Ordering::Acquire), first.handle().into_raw());
1459        assert_eq!(first_runtime.active_leases.load(Ordering::Acquire), 1);
1460        assert_eq!(second_runtime.active_leases.load(Ordering::Acquire), 0);
1461
1462        assert!(!commit_user_address_space_activation(
1463            0,
1464            active.load(Ordering::Acquire),
1465            Some(first_runtime),
1466            third.handle().into_raw(),
1467            third_runtime,
1468            |_, transition| {
1469                assert_eq!(
1470                    transition,
1471                    HardwareAddressSpaceTransition::DifferentAddressSpace
1472                );
1473            },
1474            |next| active.store(next, Ordering::Release),
1475        ));
1476        assert_eq!(active.load(Ordering::Acquire), third.handle().into_raw());
1477        assert_eq!(first_runtime.active_leases.load(Ordering::Acquire), 0);
1478        assert_eq!(second_runtime.active_leases.load(Ordering::Acquire), 0);
1479        assert_eq!(third_runtime.active_leases.load(Ordering::Acquire), 1);
1480        assert_eq!(shared.active_mask(), 0);
1481        assert_eq!(other.active_mask(), 1);
1482
1483        third_runtime.active_leases.store(0, Ordering::Release);
1484        other.deactivate(0);
1485    }
1486
1487    #[test]
1488    fn different_address_space_reusing_same_root_requires_hardware_install() {
1489        assert!(hardware_root_install_required(
1490            0x4000,
1491            0x4000,
1492            HardwareAddressSpaceTransition::DifferentAddressSpace,
1493        ));
1494        assert!(!hardware_root_install_required(
1495            0x4000,
1496            0x4000,
1497            HardwareAddressSpaceTransition::SameAddressSpace,
1498        ));
1499    }
1500}