Skip to main content

starry_kernel/mm/aspace/
vma.rs

1//! Immutable VMA descriptions and persistent snapshots.
2
3use alloc::{sync::Arc, vec::Vec};
4use core::{
5    fmt,
6    sync::atomic::{AtomicU64, Ordering},
7};
8
9use ax_memory_addr::{MemoryAddr, PAGE_SIZE_4K, VirtAddr, VirtAddrRange};
10use ax_runtime::hal::paging::MappingFlags;
11
12use super::backend::{
13    MappingFileInfo, MappingOperation, SharedFileMappingLease, SharedMemoryObject,
14};
15use crate::{StarryError, StarryResult};
16
17/// Stable name for the permissions carried by a VMA.  The architecture
18/// mapping implementation still supplies the bit layout, while callers no
19/// longer need to depend on the page-table module's concrete type.
20pub type MappingRights = MappingFlags;
21
22/// Linux `si_code` values for SIGBUS faults produced by a memory mapping.
23#[repr(i32)]
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum BusCode {
26    Adraln = 1,
27    AdrErr = 2,
28    ObjErr = 3,
29}
30
31/// Result of resolving one user page fault.  Keeping this separate from the
32/// boolean page-table handler preserves the distinction between an unmapped
33/// address, a permissions fault, and a file mapping that extends past EOF.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum FaultResult {
36    Handled,
37    Unmapped,
38    PermissionDenied,
39    /// Resolution was blocked by an in-flight eviction or shootdown.  The
40    /// instruction may be retried after the owner releases its lease.
41    Retry,
42    /// Allocation failed after the backend's reclaim or base-page fallback.
43    /// Kernel user copies must terminate instead of waiting on a transaction
44    /// owner that does not exist.
45    NoMemory,
46    Sigbus(BusCode),
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
50pub struct VmaId(u64);
51
52impl VmaId {
53    pub const fn new(value: u64) -> Self {
54        Self(value)
55    }
56
57    pub const fn get(self) -> u64 {
58        self.0
59    }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
63pub struct MappingId(u64);
64
65impl MappingId {
66    pub const fn new(value: u64) -> Self {
67        Self(value)
68    }
69
70    pub const fn get(self) -> u64 {
71        self.0
72    }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
76pub struct PageOrder(u8);
77
78impl PageOrder {
79    pub const BASE: Self = Self(0);
80
81    pub const fn new(order: u8) -> Self {
82        Self(order)
83    }
84
85    pub const fn get(self) -> u8 {
86        self.0
87    }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
91pub struct PageOffset(usize);
92
93impl PageOffset {
94    pub const ZERO: Self = Self(0);
95
96    pub const fn new(value: usize) -> Self {
97        Self(value)
98    }
99
100    pub const fn get(self) -> usize {
101        self.0
102    }
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
106pub enum PageSizePolicy {
107    #[default]
108    Base,
109    /// A normal anonymous mapping that may materialize a PMD-sized leaf after
110    /// `MADV_HUGEPAGE`. Allocation failure may fall back to the faulting base
111    /// page, matching Linux's `VM_FAULT_FALLBACK` contract.
112    Transparent { order: PageOrder },
113    /// An explicit `MAP_HUGETLB` mapping. Failure is reported to userspace;
114    /// silently changing its page size would violate the requested ABI.
115    ExplicitHuge { order: PageOrder },
116}
117
118impl PageSizePolicy {
119    pub const TRANSPARENT_2M: Self = Self::Transparent {
120        order: PageOrder::new(9),
121    };
122
123    /// Derive an explicit materialization policy from a validated backend page
124    /// size. Unsupported or non-power-of-two sizes conservatively use base
125    /// pages; the syscall/backend boundary remains responsible for rejecting
126    /// an invalid mapping request.
127    pub const fn for_size(size: usize) -> Self {
128        if size <= ax_memory_addr::PAGE_SIZE_4K || !size.is_power_of_two() {
129            Self::Base
130        } else {
131            Self::ExplicitHuge {
132                order: PageOrder::new((size.trailing_zeros() - 12) as u8),
133            }
134        }
135    }
136
137    /// Selects the preferred leaf for one fault after applying Linux's VMA and
138    /// process-wide THP controls. Starry runs anonymous THP in `madvise` mode:
139    /// default and `MADV_NOHUGEPAGE` use base pages, while `MADV_HUGEPAGE` may
140    /// use the group's PMD order unless the MM is completely disabled.
141    pub fn fault_leaf_size(
142        self,
143        advice: HugePageAdvice,
144        mode: TransparentHugePageMode,
145    ) -> Option<usize> {
146        let order = match self {
147            Self::Base => return Some(ax_memory_addr::PAGE_SIZE_4K),
148            Self::Transparent { .. }
149                if advice != HugePageAdvice::Prefer
150                    || mode == TransparentHugePageMode::Disabled =>
151            {
152                return Some(ax_memory_addr::PAGE_SIZE_4K);
153            }
154            Self::Transparent { order } | Self::ExplicitHuge { order } => order,
155        };
156        ax_memory_addr::PAGE_SIZE_4K.checked_shl(u32::from(order.get()))
157    }
158
159    pub const fn permits_fault_fallback(self) -> bool {
160        matches!(self, Self::Transparent { .. })
161    }
162}
163
164/// Per-VMA transparent-huge-page advice.
165///
166/// This mirrors Linux's mutually exclusive `VM_HUGEPAGE` and
167/// `VM_NOHUGEPAGE` flags.  It is intentionally separate from the mapping
168/// group's materialized page-size policy: two fragments of one logical
169/// mapping may receive different `madvise()` settings.
170#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
171pub enum HugePageAdvice {
172    #[default]
173    Default,
174    Prefer,
175    Avoid,
176}
177
178/// Process-wide transparent-huge-page policy stored with the MM identity.
179///
180/// The discriminants are the values returned by Linux
181/// `prctl(PR_GET_THP_DISABLE)`: enabled (0), fully disabled (1), or disabled
182/// except for explicitly advised VMAs (3).
183#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
184#[repr(u8)]
185pub enum TransparentHugePageMode {
186    #[default]
187    Enabled       = 0,
188    Disabled      = 1,
189    ExceptAdvised = 3,
190}
191
192impl TransparentHugePageMode {
193    pub const fn prctl_value(self) -> u32 {
194        self as u32
195    }
196
197    pub(crate) const fn from_storage(value: u8) -> Self {
198        match value {
199            0 => Self::Enabled,
200            3 => Self::ExceptAdvised,
201            // An unknown/corrupt value must not make a huge allocation more
202            // permissive than the stored state intended.
203            _ => Self::Disabled,
204        }
205    }
206}
207
208#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
209pub struct AnonymousSource;
210
211#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
212pub struct FileSource {
213    pub file_id: u64,
214    pub epoch: u64,
215    pub shared: bool,
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
219pub struct ExternalSource;
220
221#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
222pub struct LinearSource;
223
224/// Origin of a mapping.  The payload types keep source-specific metadata out
225/// of the VMA interval tree while still making a source transition explicit.
226#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
227pub enum MappingSource {
228    Anonymous(AnonymousSource),
229    File(FileSource),
230    External(ExternalSource),
231    Linear(LinearSource),
232}
233
234impl MappingSource {
235    /// Stable source key used to preserve a mapping group across VMA splits.
236    /// The key is metadata-only; it never encodes a pointer or an allocator
237    /// address, so it remains valid when a page-cache object is relocated.
238    pub const fn key(self) -> u64 {
239        match self {
240            Self::Anonymous(_) => 1,
241            Self::File(source) => {
242                source.file_id
243                    ^ source.epoch.rotate_left(17)
244                    ^ (source.shared as u64).rotate_left(41)
245            }
246            Self::External(_) => 2,
247            Self::Linear(_) => 3,
248        }
249    }
250}
251
252/// Mapping-source metadata needed to publish a VMA without borrowing the mutable
253/// backend across a lock or I/O boundary.
254#[derive(Debug, Clone, Copy, PartialEq, Eq)]
255pub struct VmaDescriptor {
256    pub mapping: MappingId,
257    pub source: MappingSource,
258    pub page_policy: PageSizePolicy,
259    pub source_offset: PageOffset,
260}
261
262#[derive(Debug, Clone)]
263pub struct MappingGroup {
264    pub id: MappingId,
265    pub source: Arc<MappingSource>,
266    pub page_policy: PageSizePolicy,
267}
268
269impl MappingGroup {
270    pub fn new(id: MappingId, source: MappingSource, page_policy: PageSizePolicy) -> Arc<Self> {
271        Arc::new(Self {
272            id,
273            source: Arc::new(source),
274            page_policy,
275        })
276    }
277}
278
279/// Stable metadata copied out of the mutable legacy backend.  A snapshot may
280/// safely cross a lock boundary or a sleeping page-cache operation.
281#[derive(Clone)]
282pub struct VmaSnapshot {
283    pub id: VmaId,
284    pub range: VirtAddrRange,
285    pub rights: MappingFlags,
286    pub reported_rights: MappingFlags,
287    pub max_rights: MappingFlags,
288    pub group: Arc<MappingGroup>,
289    pub source_offset: PageOffset,
290    pub huge_page_advice: HugePageAdvice,
291    pub lock_mode: VmaLockMode,
292    pub(crate) advice_policy: VmaAdvicePolicy,
293}
294
295/// Linux `VM_LOCKED`/`VM_LOCKONFAULT` policy carried by one immutable VMA.
296///
297/// This is metadata rather than a promise that every page is resident:
298/// `Locked` requests eager population while `LockOnFault` pins pages only as
299/// they fault in. Both modes reject `MS_INVALIDATE` until userspace applies
300/// `munlock` to the corresponding range.
301#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
302pub enum VmaLockMode {
303    #[default]
304    Unlocked,
305    Locked,
306    LockOnFault,
307}
308
309impl VmaLockMode {
310    pub const fn is_locked(self) -> bool {
311        !matches!(self, Self::Unlocked)
312    }
313}
314
315/// Linux VMA-local access and inheritance policy changed by `madvise`.
316///
317/// The policy belongs to the immutable VMA root so split, merge, fork and
318/// mremap all observe one published fact. Access-pattern advice is retained
319/// even before a readahead consumer exists; it must not be represented as a
320/// successful no-op that disappears at the next VMA mutation.
321#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
322pub(crate) struct VmaAdvicePolicy {
323    access_pattern: VmaAccessPattern,
324    dont_fork: bool,
325    dont_dump: bool,
326}
327
328impl VmaAdvicePolicy {
329    pub const DEFAULT: Self = Self {
330        access_pattern: VmaAccessPattern::Normal,
331        dont_fork: false,
332        dont_dump: false,
333    };
334
335    pub const fn dont_fork(self) -> bool {
336        self.dont_fork
337    }
338
339    pub const fn apply(self, update: VmaAdviceUpdate) -> Self {
340        match update {
341            VmaAdviceUpdate::AccessPattern(access_pattern) => Self {
342                access_pattern,
343                ..self
344            },
345            VmaAdviceUpdate::DontFork(dont_fork) => Self { dont_fork, ..self },
346            VmaAdviceUpdate::DontDump(dont_dump) => Self { dont_dump, ..self },
347        }
348    }
349}
350
351#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
352pub(crate) enum VmaAccessPattern {
353    #[default]
354    Normal,
355    Random,
356    Sequential,
357}
358
359#[derive(Debug, Clone, Copy, PartialEq, Eq)]
360pub(crate) enum VmaAdviceUpdate {
361    AccessPattern(VmaAccessPattern),
362    DontFork(bool),
363    DontDump(bool),
364}
365
366/// Public name used by callers that do not need to know that snapshots are
367/// copied out of the publication root.
368pub type Vma = VmaSnapshot;
369
370#[derive(Debug, Clone)]
371pub(crate) struct VmaInspectionRecord {
372    pub range: VirtAddrRange,
373    pub rights: MappingRights,
374    pub reported_rights: MappingRights,
375    pub file: MappingFileInfo,
376    lock_mode: VmaLockMode,
377}
378
379impl VmaInspectionRecord {
380    pub fn start(&self) -> VirtAddr {
381        self.range.start
382    }
383
384    pub fn end(&self) -> VirtAddr {
385        self.range.end
386    }
387
388    pub fn size(&self) -> usize {
389        self.range.size()
390    }
391
392    pub fn flags(&self) -> MappingRights {
393        self.rights
394    }
395
396    pub fn reported_flags(&self) -> MappingRights {
397        self.reported_rights
398    }
399
400    pub fn file_info(&self) -> &MappingFileInfo {
401        &self.file
402    }
403
404    pub fn is_locked(&self) -> bool {
405        self.lock_mode.is_locked()
406    }
407}
408
409#[derive(Clone, Copy)]
410enum AdviceMappingKind {
411    SharedFile,
412    ReclaimUnsupported,
413    Invalid,
414}
415
416/// Owned capability snapshot for one `madvise`/`msync` fragment. It can cross
417/// the VMA lock for filesystem work without retaining a general mapping
418/// executor or a tree node.
419#[derive(Clone)]
420pub(crate) struct VmaAdviceFragment {
421    pub gap_before: bool,
422    pub range: VirtAddrRange,
423    file: Option<SharedFileMappingLease>,
424    kind: AdviceMappingKind,
425    private_anonymous: bool,
426    lock_mode: VmaLockMode,
427}
428
429#[derive(Clone)]
430pub(crate) struct SharedFileVmaRecord {
431    pub range: VirtAddrRange,
432    pub rights: MappingRights,
433    pub file: SharedFileMappingLease,
434}
435
436/// Narrow, owned capability for lock-external mincore queries. It cannot
437/// mutate a mapping or expose the backing executor.
438#[derive(Clone)]
439pub(crate) struct VmaResidencyProbe {
440    operation: MappingOperation,
441}
442
443/// Immutable source capability for `mremap`. The syscall layer may inspect
444/// Linux-visible range and policy, but relocation creates the executable
445/// target only inside `AddrSpace`'s transaction entry.
446#[derive(Clone)]
447pub(crate) struct VmaMremapSource {
448    snapshot: Arc<VmaSnapshot>,
449    operation: MappingOperation,
450}
451
452impl VmaMremapSource {
453    pub fn start(&self) -> VirtAddr {
454        self.snapshot.start()
455    }
456
457    pub fn end(&self) -> VirtAddr {
458        self.snapshot.end()
459    }
460
461    pub fn rights(&self) -> MappingRights {
462        self.snapshot.rights
463    }
464
465    pub fn reported_rights(&self) -> MappingRights {
466        self.snapshot.reported_rights
467    }
468
469    pub fn max_rights(&self) -> MappingRights {
470        self.snapshot.max_rights
471    }
472
473    pub fn huge_page_advice(&self) -> HugePageAdvice {
474        self.snapshot.huge_page_advice
475    }
476
477    pub fn lock_mode(&self) -> VmaLockMode {
478        self.snapshot.lock_mode
479    }
480
481    pub(crate) fn advice_policy(&self) -> VmaAdvicePolicy {
482        self.snapshot.advice_policy
483    }
484
485    pub fn alignment(&self) -> usize {
486        self.operation.mremap_alignment()
487    }
488
489    pub fn supports_dontunmap(&self) -> bool {
490        self.operation.supports_mremap_dontunmap()
491    }
492
493    pub fn is_linear(&self) -> bool {
494        self.operation.is_linear()
495    }
496
497    pub fn shared_object(&self) -> Option<Arc<SharedMemoryObject>> {
498        self.operation.shared_memory_object()
499    }
500
501    pub(super) fn relocated_operation(
502        &self,
503        target: VirtAddr,
504        source_offset: usize,
505        target_size: usize,
506    ) -> StarryResult<MappingOperation> {
507        self.operation
508            .relocated(target, source_offset)?
509            .resized(target_size)
510    }
511}
512
513impl VmaResidencyProbe {
514    pub fn mincore_resident(&self, address: VirtAddr, cred: &crate::task::Cred) -> bool {
515        self.operation.mincore_resident(address, cred)
516    }
517}
518
519impl VmaAdviceFragment {
520    pub fn shared_file(&self) -> Option<&SharedFileMappingLease> {
521        self.file.as_ref()
522    }
523
524    pub fn is_private_anonymous(&self) -> bool {
525        self.private_anonymous
526    }
527
528    pub fn is_locked(&self) -> bool {
529        self.lock_mode.is_locked()
530    }
531
532    pub fn is_special(&self) -> bool {
533        matches!(self.kind, AdviceMappingKind::Invalid)
534    }
535
536    pub fn pageout(&self) -> StarryResult {
537        match (&self.file, self.kind) {
538            (Some(file), AdviceMappingKind::SharedFile) => {
539                let outcome = file.pageout_range(self.range.start, self.range.end)?;
540                if let Some(reason) = outcome.deferred_reason() {
541                    debug!(
542                        "file pageout deferred after reclaiming {} pages: {:?}",
543                        outcome.reclaimed(),
544                        reason
545                    );
546                }
547                Ok(())
548            }
549            (None, AdviceMappingKind::ReclaimUnsupported) => {
550                Err(StarryError::OperationNotSupported)
551            }
552            _ => Err(StarryError::InvalidInput),
553        }
554    }
555}
556
557impl VmaSnapshot {
558    pub const fn start(&self) -> VirtAddr {
559        self.range.start
560    }
561
562    pub const fn end(&self) -> VirtAddr {
563        self.range.end
564    }
565
566    pub fn size(&self) -> usize {
567        self.range.size()
568    }
569
570    pub const fn flags(&self) -> MappingFlags {
571        self.rights
572    }
573
574    pub const fn reported_flags(&self) -> MappingFlags {
575        self.reported_rights
576    }
577
578    pub const fn max_flags(&self) -> MappingFlags {
579        self.max_rights
580    }
581
582    pub fn contains(&self, address: VirtAddr) -> bool {
583        self.range.contains(address)
584    }
585
586    fn can_merge_with(&self, next: &Self) -> bool {
587        self.range.end == next.range.start
588            && self.rights == next.rights
589            && self.reported_rights == next.reported_rights
590            && self.max_rights == next.max_rights
591            && Arc::ptr_eq(&self.group, &next.group)
592            && self.huge_page_advice == next.huge_page_advice
593            && self.lock_mode == next.lock_mode
594            && self.advice_policy == next.advice_policy
595            && self.source_offset.get().checked_add(self.range.size())
596                == Some(next.source_offset.get())
597    }
598
599    fn merge_through(&self, last: &Self) -> Option<Self> {
600        if self.range.end > last.range.end {
601            return None;
602        }
603        Some(Self {
604            id: self.id,
605            range: VirtAddrRange::new(self.range.start, last.range.end),
606            rights: self.rights,
607            reported_rights: self.reported_rights,
608            max_rights: self.max_rights,
609            group: self.group.clone(),
610            source_offset: self.source_offset,
611            huge_page_advice: self.huge_page_advice,
612            lock_mode: self.lock_mode,
613            advice_policy: self.advice_policy,
614        })
615    }
616
617    pub(crate) fn fragment(&self, start: VirtAddr, end: VirtAddr, id: VmaId) -> Option<Self> {
618        if start >= end || start < self.range.start || end > self.range.end {
619            return None;
620        }
621        let range = VirtAddrRange::new(start, end);
622        let offset = start.checked_sub_addr(self.range.start)?;
623        Some(Self {
624            id,
625            range,
626            rights: self.rights,
627            reported_rights: self.reported_rights,
628            max_rights: self.max_rights,
629            group: self.group.clone(),
630            source_offset: PageOffset::new(self.source_offset.get().checked_add(offset)?),
631            huge_page_advice: self.huge_page_advice,
632            lock_mode: self.lock_mode,
633            advice_policy: self.advice_policy,
634        })
635    }
636}
637
638/// Private executable half of one immutable VMA node.
639///
640/// Readers receive only [`VmaSnapshot`]. Retaining a procfs/fault metadata
641/// snapshot therefore cannot accidentally pin a page-cache domain or shared
642/// backing object after the VMA has been retired.
643#[derive(Clone)]
644pub(super) struct VmaEntry {
645    snapshot: Arc<VmaSnapshot>,
646    operation: MappingOperation,
647}
648
649impl VmaEntry {
650    pub(super) fn new(snapshot: VmaSnapshot, operation: MappingOperation) -> Arc<Self> {
651        Arc::new(Self {
652            snapshot: Arc::new(snapshot),
653            operation,
654        })
655    }
656
657    pub(super) fn snapshot(&self) -> &Arc<VmaSnapshot> {
658        &self.snapshot
659    }
660
661    pub(super) fn operation(&self) -> &MappingOperation {
662        &self.operation
663    }
664
665    pub(super) fn operation_clone(&self) -> MappingOperation {
666        self.operation.clone()
667    }
668
669    pub(super) fn range(&self) -> VirtAddrRange {
670        self.snapshot.range
671    }
672
673    pub(super) fn start(&self) -> VirtAddr {
674        self.snapshot.range.start
675    }
676
677    pub(super) fn end(&self) -> VirtAddr {
678        self.snapshot.range.end
679    }
680
681    pub(super) fn size(&self) -> usize {
682        self.snapshot.range.size()
683    }
684
685    pub(super) fn rights(&self) -> MappingRights {
686        self.snapshot.rights
687    }
688
689    pub(super) fn reported_rights(&self) -> MappingRights {
690        self.snapshot.reported_rights
691    }
692
693    pub(super) fn max_rights(&self) -> MappingRights {
694        self.snapshot.max_rights
695    }
696
697    pub(super) fn inspection_record(&self) -> StarryResult<VmaInspectionRecord> {
698        Ok(VmaInspectionRecord {
699            range: self.range(),
700            rights: self.rights(),
701            reported_rights: self.reported_rights(),
702            file: self.operation.file_info()?,
703            lock_mode: self.snapshot.lock_mode,
704        })
705    }
706
707    pub(super) fn advice_fragment(
708        &self,
709        cursor: VirtAddr,
710        end: VirtAddr,
711    ) -> Option<VmaAdviceFragment> {
712        let fragment_start = self.start().max(cursor);
713        let fragment_end = self.end().min(end);
714        let range = VirtAddrRange::try_new(fragment_start, fragment_end)?;
715        let file = self.operation.shared_file_lease();
716        let kind = if file.is_some() {
717            AdviceMappingKind::SharedFile
718        } else if self.operation.is_linear() {
719            AdviceMappingKind::Invalid
720        } else {
721            AdviceMappingKind::ReclaimUnsupported
722        };
723        Some(VmaAdviceFragment {
724            gap_before: self.start() > cursor,
725            range,
726            file,
727            kind,
728            private_anonymous: self.operation.is_private_anonymous(),
729            lock_mode: self.snapshot.lock_mode,
730        })
731    }
732
733    pub(super) fn residency_probe(&self) -> VmaResidencyProbe {
734        VmaResidencyProbe {
735            operation: self.operation.clone(),
736        }
737    }
738
739    pub(super) fn mremap_source(&self) -> VmaMremapSource {
740        VmaMremapSource {
741            snapshot: self.snapshot.clone(),
742            operation: self.operation.clone(),
743        }
744    }
745
746    pub(super) fn shared_file_record(&self) -> Option<SharedFileVmaRecord> {
747        Some(SharedFileVmaRecord {
748            range: self.range(),
749            rights: self.rights(),
750            file: self.operation.shared_file_lease()?,
751        })
752    }
753
754    fn fragment(&self, start: VirtAddr, end: VirtAddr, id: VmaId) -> Option<Arc<Self>> {
755        let range = VirtAddrRange::new(start, end);
756        let snapshot = self.snapshot.fragment(start, end, id)?;
757        let operation = self.operation.fragment(self.snapshot.range, range).ok()?;
758        Some(Self::new(snapshot, operation))
759    }
760
761    fn extended_right(&self, additional_size: usize) -> Option<Arc<Self>> {
762        if additional_size == 0 {
763            return Some(Arc::new(self.clone()));
764        }
765        let new_end = self.snapshot.range.end.checked_add(additional_size)?;
766        let mut snapshot = (*self.snapshot).clone();
767        snapshot.range = VirtAddrRange::new(snapshot.range.start, new_end);
768        Some(Self::new(snapshot, self.operation.clone()))
769    }
770}
771
772impl fmt::Debug for VmaEntry {
773    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
774        f.debug_struct("VmaEntry")
775            .field("snapshot", &self.snapshot)
776            .finish_non_exhaustive()
777    }
778}
779
780impl fmt::Debug for VmaSnapshot {
781    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
782        f.debug_struct("VmaSnapshot")
783            .field("id", &self.id)
784            .field("range", &self.range)
785            .field("rights", &self.rights)
786            .field("reported_rights", &self.reported_rights)
787            .field("max_rights", &self.max_rights)
788            .field("group", &self.group)
789            .field("source_offset", &self.source_offset)
790            .field("huge_page_advice", &self.huge_page_advice)
791            .field("lock_mode", &self.lock_mode)
792            .field("advice_policy", &self.advice_policy)
793            .finish_non_exhaustive()
794    }
795}
796
797/// A persistent, path-copy interval tree.
798///
799/// Each update only allocates the nodes on the search path.  Readers retain an
800/// `Arc<VmaMap>` (or an `Arc<VmaSnapshot>`) and therefore continue to observe a
801/// coherent tree while another mutation publishes a successor root.
802#[derive(Clone, Default, Debug)]
803pub struct VmaMap {
804    root: Option<Arc<VmaNode>>,
805}
806
807#[derive(Debug)]
808struct VmaNode {
809    entry: Arc<VmaEntry>,
810    left: Option<Arc<VmaNode>>,
811    right: Option<Arc<VmaNode>>,
812    height: u8,
813}
814
815impl VmaNode {
816    fn new(entry: Arc<VmaEntry>) -> Arc<Self> {
817        Arc::new(Self {
818            entry,
819            left: None,
820            right: None,
821            height: 1,
822        })
823    }
824
825    fn with_children(
826        entry: Arc<VmaEntry>,
827        left: Option<Arc<VmaNode>>,
828        right: Option<Arc<VmaNode>>,
829    ) -> Arc<Self> {
830        Arc::new(Self {
831            entry,
832            height: 1 + node_height(&left).max(node_height(&right)),
833            left,
834            right,
835        })
836    }
837}
838
839fn node_height(node: &Option<Arc<VmaNode>>) -> u8 {
840    node.as_ref().map_or(0, |node| node.height)
841}
842
843fn balance_factor(node: &VmaNode) -> i16 {
844    i16::from(node_height(&node.left)) - i16::from(node_height(&node.right))
845}
846
847fn rotate_right(node: Arc<VmaNode>) -> Arc<VmaNode> {
848    let Some(pivot) = node.left.clone() else {
849        return node;
850    };
851    let new_right =
852        VmaNode::with_children(node.entry.clone(), pivot.right.clone(), node.right.clone());
853    VmaNode::with_children(pivot.entry.clone(), pivot.left.clone(), Some(new_right))
854}
855
856fn rotate_left(node: Arc<VmaNode>) -> Arc<VmaNode> {
857    let Some(pivot) = node.right.clone() else {
858        return node;
859    };
860    let new_left =
861        VmaNode::with_children(node.entry.clone(), node.left.clone(), pivot.left.clone());
862    VmaNode::with_children(pivot.entry.clone(), Some(new_left), pivot.right.clone())
863}
864
865fn rebalance(node: Arc<VmaNode>) -> Arc<VmaNode> {
866    let balance = balance_factor(&node);
867    if balance > 1 {
868        if node
869            .left
870            .as_ref()
871            .is_some_and(|left| balance_factor(left) < 0)
872        {
873            let left = node.left.as_ref().map(|left| rotate_left(left.clone()));
874            return rotate_right(VmaNode::with_children(
875                node.entry.clone(),
876                left,
877                node.right.clone(),
878            ));
879        }
880        return rotate_right(node);
881    }
882    if balance < -1 {
883        if node
884            .right
885            .as_ref()
886            .is_some_and(|right| balance_factor(right) > 0)
887        {
888            let right = node.right.as_ref().map(|right| rotate_right(right.clone()));
889            return rotate_left(VmaNode::with_children(
890                node.entry.clone(),
891                node.left.clone(),
892                right,
893            ));
894        }
895        return rotate_left(node);
896    }
897    node
898}
899
900fn insert_node(node: Option<Arc<VmaNode>>, entry: Arc<VmaEntry>) -> Result<Arc<VmaNode>, ()> {
901    let Some(current) = node else {
902        return Ok(VmaNode::new(entry));
903    };
904    if entry.snapshot.range.end <= current.entry.snapshot.range.start {
905        let left = insert_node(current.left.clone(), entry)?;
906        return Ok(rebalance(VmaNode::with_children(
907            current.entry.clone(),
908            Some(left),
909            current.right.clone(),
910        )));
911    }
912    if entry.snapshot.range.start >= current.entry.snapshot.range.end {
913        let right = insert_node(current.right.clone(), entry)?;
914        return Ok(rebalance(VmaNode::with_children(
915            current.entry.clone(),
916            current.left.clone(),
917            Some(right),
918        )));
919    }
920    Err(())
921}
922
923fn remove_min(node: Arc<VmaNode>) -> (Option<Arc<VmaNode>>, Arc<VmaNode>) {
924    let Some(left) = node.left.clone() else {
925        return (node.right.clone(), node);
926    };
927    let (new_left, minimum) = remove_min(left);
928    (
929        Some(rebalance(VmaNode::with_children(
930            node.entry.clone(),
931            new_left,
932            node.right.clone(),
933        ))),
934        minimum,
935    )
936}
937
938fn remove_node(
939    node: Option<Arc<VmaNode>>,
940    start: VirtAddr,
941) -> (Option<Arc<VmaNode>>, Option<Arc<VmaEntry>>) {
942    let Some(current) = node else {
943        return (None, None);
944    };
945    if start < current.entry.snapshot.range.start {
946        let (left, removed) = remove_node(current.left.clone(), start);
947        return (
948            Some(rebalance(VmaNode::with_children(
949                current.entry.clone(),
950                left,
951                current.right.clone(),
952            ))),
953            removed,
954        );
955    }
956    if start > current.entry.snapshot.range.start {
957        let (right, removed) = remove_node(current.right.clone(), start);
958        return (
959            Some(rebalance(VmaNode::with_children(
960                current.entry.clone(),
961                current.left.clone(),
962                right,
963            ))),
964            removed,
965        );
966    }
967    let removed = Some(current.entry.clone());
968    match (current.left.clone(), current.right.clone()) {
969        (None, right) => (right, removed),
970        (Some(left), None) => (Some(left), removed),
971        (Some(left), Some(right)) => {
972            let (new_right, successor) = remove_min(right);
973            (
974                Some(rebalance(VmaNode::with_children(
975                    successor.entry.clone(),
976                    Some(left),
977                    new_right,
978                ))),
979                removed,
980            )
981        }
982    }
983}
984
985impl VmaMap {
986    pub fn len(&self) -> usize {
987        self.iter().count()
988    }
989
990    pub fn is_empty(&self) -> bool {
991        self.root.is_none()
992    }
993
994    /// Derives Linux `mm->locked_vm` from the immutable VMA root.
995    ///
996    /// Keeping this as a read-side reduction avoids a second mutable charge
997    /// map that could diverge after split, merge, unmap, fork or mremap.
998    pub(crate) fn locked_pages(&self) -> Option<u64> {
999        self.iter().try_fold(0u64, |pages, vma| {
1000            if !vma.lock_mode.is_locked() {
1001                return Some(pages);
1002            }
1003            let vma_pages = u64::try_from(vma.range.size() / PAGE_SIZE_4K).ok()?;
1004            pages.checked_add(vma_pages)
1005        })
1006    }
1007
1008    pub fn lookup(&self, address: VirtAddr) -> Option<Arc<VmaSnapshot>> {
1009        self.lookup_entry(address)
1010            .map(|entry| entry.snapshot.clone())
1011    }
1012
1013    pub(super) fn lookup_entry(&self, address: VirtAddr) -> Option<Arc<VmaEntry>> {
1014        let mut node = self.root.clone();
1015        let mut candidate = None;
1016        while let Some(current) = node {
1017            if address < current.entry.snapshot.range.start {
1018                node = current.left.clone();
1019            } else {
1020                candidate = Some(current.entry.clone());
1021                node = current.right.clone();
1022            }
1023        }
1024        candidate.filter(|entry| entry.snapshot.contains(address))
1025    }
1026
1027    /// Visits every VMA intersecting `range` in ascending order, stopping as
1028    /// soon as `visit` returns `false`.
1029    ///
1030    /// VMAs never overlap, so each subtree covers one contiguous stretch of
1031    /// address space and two facts prune the descent: a left subtree ends at
1032    /// or before its parent's start, so it can only reach `range` when the
1033    /// parent starts after `range.start`; a right subtree starts at or after
1034    /// its parent's end, so it holds nothing once `range` ends there. What is
1035    /// left is the search path plus the matches, without materialising the
1036    /// tree or touching a single reference count for a VMA that does not
1037    /// intersect.
1038    pub fn for_each_overlapping(
1039        &self,
1040        range: VirtAddrRange,
1041        mut visit: impl FnMut(&Arc<VmaSnapshot>) -> bool,
1042    ) {
1043        self.for_each_overlapping_entry(range, |entry| visit(&entry.snapshot));
1044    }
1045
1046    /// [`Self::for_each_overlapping`] over the entries, for the callers that
1047    /// need the mapping operation and not just the snapshot.
1048    pub(super) fn for_each_overlapping_entry(
1049        &self,
1050        range: VirtAddrRange,
1051        mut visit: impl FnMut(&Arc<VmaEntry>) -> bool,
1052    ) {
1053        let mut visited = 0;
1054        Self::visit_overlapping(&self.root, range, &mut visited, &mut visit);
1055    }
1056
1057    /// `for_each_overlapping` with the node count the descent paid, which is
1058    /// what the regression test asserts on: the result of a range lookup is
1059    /// the same whether or not the tree was walked in full, so only the cost
1060    /// distinguishes them.
1061    fn visit_overlapping(
1062        node: &Option<Arc<VmaNode>>,
1063        range: VirtAddrRange,
1064        visited: &mut usize,
1065        visit: &mut impl FnMut(&Arc<VmaEntry>) -> bool,
1066    ) -> bool {
1067        let Some(current) = node else {
1068            return true;
1069        };
1070        *visited += 1;
1071        let vma = current.entry.snapshot.range;
1072        if range.start < vma.start
1073            && !Self::visit_overlapping(&current.left, range, visited, visit)
1074        {
1075            return false;
1076        }
1077        if vma.overlaps(range) && !visit(&current.entry) {
1078            return false;
1079        }
1080        if range.end > vma.end
1081            && !Self::visit_overlapping(&current.right, range, visited, visit)
1082        {
1083            return false;
1084        }
1085        true
1086    }
1087
1088    /// Looks up every VMA intersecting a checked range.  Returned snapshots
1089    /// own their metadata and can safely be used after the publication lock is
1090    /// released or while a backend performs I/O.
1091    pub fn lookup_range(&self, range: VirtAddrRange) -> Vec<Arc<VmaSnapshot>> {
1092        let mut found = Vec::new();
1093        self.for_each_overlapping(range, |vma| {
1094            found.push(vma.clone());
1095            true
1096        });
1097        found
1098    }
1099
1100    pub fn contains_range(&self, start: VirtAddr, size: usize) -> bool {
1101        let Some(request) = VirtAddrRange::try_from_start_size(start, size) else {
1102            return false;
1103        };
1104        if request.is_empty() {
1105            return false;
1106        }
1107        let mut cursor = request.start;
1108        let mut covered = false;
1109        self.for_each_overlapping(request, |vma| {
1110            if vma.range.end <= cursor {
1111                return true;
1112            }
1113            if vma.range.start > cursor {
1114                return false;
1115            }
1116            // A VMA may extend beyond the requested range.  Clamp the
1117            // progress marker instead of constructing an invalid
1118            // `AddrRange` whose start is greater than its end.
1119            cursor = vma.range.end.min(request.end);
1120            if cursor >= request.end {
1121                covered = true;
1122                return false;
1123            }
1124            true
1125        });
1126        covered
1127    }
1128
1129    fn fragment_with_huge_page_advice(
1130        source: &VmaEntry,
1131        start: VirtAddr,
1132        end: VirtAddr,
1133        id: VmaId,
1134        advice: HugePageAdvice,
1135    ) -> Option<Arc<VmaEntry>> {
1136        let fragment = source.fragment(start, end, id)?;
1137        let mut snapshot = (*fragment.snapshot).clone();
1138        snapshot.huge_page_advice = advice;
1139        Some(VmaEntry::new(snapshot, fragment.operation.clone()))
1140    }
1141
1142    fn update_one_huge_page_advice(
1143        &self,
1144        source: &Arc<VmaEntry>,
1145        range: VirtAddrRange,
1146        advice: HugePageAdvice,
1147    ) -> Option<Self> {
1148        if range.start < source.snapshot.range.start || range.end > source.snapshot.range.end {
1149            return None;
1150        }
1151        if source.snapshot.huge_page_advice == advice {
1152            return Some(self.clone());
1153        }
1154
1155        let (mut updated, removed) = self.remove_entry(source.snapshot.range.start)?;
1156        if removed.snapshot.id != source.snapshot.id {
1157            return None;
1158        }
1159        let mut retained_id = Some(source.snapshot.id);
1160        if source.snapshot.range.start < range.start {
1161            let head = Self::fragment_with_huge_page_advice(
1162                source,
1163                source.snapshot.range.start,
1164                range.start,
1165                retained_id.take().unwrap_or_else(allocate_vma_id),
1166                source.snapshot.huge_page_advice,
1167            )?;
1168            updated = updated.insert_entry(head)?;
1169        }
1170        let body = Self::fragment_with_huge_page_advice(
1171            source,
1172            range.start,
1173            range.end,
1174            retained_id.take().unwrap_or_else(allocate_vma_id),
1175            advice,
1176        )?;
1177        updated = updated.insert_entry(body)?;
1178        if range.end < source.snapshot.range.end {
1179            let tail = Self::fragment_with_huge_page_advice(
1180                source,
1181                range.end,
1182                source.snapshot.range.end,
1183                retained_id.take().unwrap_or_else(allocate_vma_id),
1184                source.snapshot.huge_page_advice,
1185            )?;
1186            updated = updated.insert_entry(tail)?;
1187        }
1188        Some(updated)
1189    }
1190
1191    /// Returns a successor root with every VMA fragment intersecting `range`
1192    /// removed.  Executable operations are split together with their public
1193    /// metadata, so source coordinates and backing-object ownership cannot
1194    /// diverge during a partial `munmap`.
1195    pub(super) fn without_range(&self, range: VirtAddrRange) -> Option<Self> {
1196        if range.is_empty() {
1197            return Some(self.clone());
1198        }
1199
1200        let affected: Vec<_> = self
1201            .iter_entries()
1202            .filter(|entry| entry.snapshot.range.overlaps(range))
1203            .collect();
1204        let mut updated = self.clone();
1205        for source in affected {
1206            let (next, removed) = updated.remove_entry(source.snapshot.range.start)?;
1207            if removed.snapshot.id != source.snapshot.id {
1208                return None;
1209            }
1210            updated = next;
1211
1212            let mut retained_id = Some(source.snapshot.id);
1213            if source.snapshot.range.start < range.start {
1214                let head = source.fragment(
1215                    source.snapshot.range.start,
1216                    source.snapshot.range.end.min(range.start),
1217                    retained_id.take().unwrap_or_else(allocate_vma_id),
1218                )?;
1219                updated = updated.insert_entry(head)?;
1220            }
1221            if source.snapshot.range.end > range.end {
1222                let tail = source.fragment(
1223                    source.snapshot.range.start.max(range.end),
1224                    source.snapshot.range.end,
1225                    retained_id.take().unwrap_or_else(allocate_vma_id),
1226                )?;
1227                updated = updated.insert_entry(tail)?;
1228            }
1229        }
1230        Some(updated)
1231    }
1232
1233    /// Returns a successor root with `range` assigned new current and
1234    /// userspace-reported permissions.  The maximum Linux `VM_MAY*` envelope
1235    /// remains unchanged and every executable operation is carved at the same
1236    /// boundaries as its immutable snapshot.
1237    pub(super) fn with_permissions(
1238        &self,
1239        range: VirtAddrRange,
1240        rights: MappingRights,
1241        reported_rights: MappingRights,
1242    ) -> Option<Self> {
1243        if range.is_empty() || !self.contains_range(range.start, range.size()) {
1244            return None;
1245        }
1246
1247        let affected: Vec<_> = self
1248            .iter_entries()
1249            .filter(|entry| entry.snapshot.range.overlaps(range))
1250            .collect();
1251        let mut updated = self.clone();
1252        for source in affected {
1253            let (next, removed) = updated.remove_entry(source.snapshot.range.start)?;
1254            if removed.snapshot.id != source.snapshot.id {
1255                return None;
1256            }
1257            updated = next;
1258
1259            let intersection = VirtAddrRange::new(
1260                source.snapshot.range.start.max(range.start),
1261                source.snapshot.range.end.min(range.end),
1262            );
1263            let mut retained_id = Some(source.snapshot.id);
1264            if source.snapshot.range.start < intersection.start {
1265                let head = source.fragment(
1266                    source.snapshot.range.start,
1267                    intersection.start,
1268                    retained_id.take().unwrap_or_else(allocate_vma_id),
1269                )?;
1270                updated = updated.insert_entry(head)?;
1271            }
1272
1273            let body = source.fragment(
1274                intersection.start,
1275                intersection.end,
1276                retained_id.take().unwrap_or_else(allocate_vma_id),
1277            )?;
1278            let mut body_snapshot = (*body.snapshot).clone();
1279            body_snapshot.rights = rights;
1280            body_snapshot.reported_rights = reported_rights;
1281            updated = updated.insert_entry(VmaEntry::new(body_snapshot, body.operation.clone()))?;
1282
1283            if intersection.end < source.snapshot.range.end {
1284                let tail = source.fragment(
1285                    intersection.end,
1286                    source.snapshot.range.end,
1287                    retained_id.take().unwrap_or_else(allocate_vma_id),
1288                )?;
1289                updated = updated.insert_entry(tail)?;
1290            }
1291        }
1292        updated.coalesce_compatible()
1293    }
1294
1295    /// Returns a successor root with Linux VMA locking policy applied to a
1296    /// fully mapped range. Partial updates path-copy the affected VMA into
1297    /// head/body/tail fragments; only fragments with identical lock policy may
1298    /// coalesce again.
1299    pub(super) fn with_lock_mode(
1300        &self,
1301        range: VirtAddrRange,
1302        lock_mode: VmaLockMode,
1303    ) -> Option<Self> {
1304        if range.is_empty() || !self.contains_range(range.start, range.size()) {
1305            return None;
1306        }
1307
1308        let affected: Vec<_> = self
1309            .iter_entries()
1310            .filter(|entry| entry.snapshot.range.overlaps(range))
1311            .collect();
1312        let mut updated = self.clone();
1313        for source in affected {
1314            let (next, removed) = updated.remove_entry(source.snapshot.range.start)?;
1315            if removed.snapshot.id != source.snapshot.id {
1316                return None;
1317            }
1318            updated = next;
1319
1320            let intersection = VirtAddrRange::new(
1321                source.snapshot.range.start.max(range.start),
1322                source.snapshot.range.end.min(range.end),
1323            );
1324            let mut retained_id = Some(source.snapshot.id);
1325            if source.snapshot.range.start < intersection.start {
1326                let head = source.fragment(
1327                    source.snapshot.range.start,
1328                    intersection.start,
1329                    retained_id.take().unwrap_or_else(allocate_vma_id),
1330                )?;
1331                updated = updated.insert_entry(head)?;
1332            }
1333
1334            let body = source.fragment(
1335                intersection.start,
1336                intersection.end,
1337                retained_id.take().unwrap_or_else(allocate_vma_id),
1338            )?;
1339            let mut body_snapshot = (*body.snapshot).clone();
1340            body_snapshot.lock_mode = lock_mode;
1341            updated = updated.insert_entry(VmaEntry::new(body_snapshot, body.operation.clone()))?;
1342
1343            if intersection.end < source.snapshot.range.end {
1344                let tail = source.fragment(
1345                    intersection.end,
1346                    source.snapshot.range.end,
1347                    retained_id.take().unwrap_or_else(allocate_vma_id),
1348                )?;
1349                updated = updated.insert_entry(tail)?;
1350            }
1351        }
1352        updated.coalesce_compatible()
1353    }
1354
1355    /// Returns a successor root with one Linux VMA advice policy update
1356    /// applied to every VMA fragment in `range`.
1357    pub(super) fn with_advice_update(
1358        &self,
1359        range: VirtAddrRange,
1360        update: VmaAdviceUpdate,
1361    ) -> Option<Self> {
1362        if range.is_empty() || !self.contains_range(range.start, range.size()) {
1363            return None;
1364        }
1365
1366        let affected: Vec<_> = self
1367            .iter_entries()
1368            .filter(|entry| entry.snapshot.range.overlaps(range))
1369            .collect();
1370        let mut updated = self.clone();
1371        for source in affected {
1372            let (next, removed) = updated.remove_entry(source.snapshot.range.start)?;
1373            if removed.snapshot.id != source.snapshot.id {
1374                return None;
1375            }
1376            updated = next;
1377
1378            let intersection = VirtAddrRange::new(
1379                source.snapshot.range.start.max(range.start),
1380                source.snapshot.range.end.min(range.end),
1381            );
1382            let mut retained_id = Some(source.snapshot.id);
1383            if source.snapshot.range.start < intersection.start {
1384                let head = source.fragment(
1385                    source.snapshot.range.start,
1386                    intersection.start,
1387                    retained_id.take().unwrap_or_else(allocate_vma_id),
1388                )?;
1389                updated = updated.insert_entry(head)?;
1390            }
1391
1392            let body = source.fragment(
1393                intersection.start,
1394                intersection.end,
1395                retained_id.take().unwrap_or_else(allocate_vma_id),
1396            )?;
1397            let mut body_snapshot = (*body.snapshot).clone();
1398            body_snapshot.advice_policy = body_snapshot.advice_policy.apply(update);
1399            updated = updated.insert_entry(VmaEntry::new(body_snapshot, body.operation.clone()))?;
1400
1401            if intersection.end < source.snapshot.range.end {
1402                let tail = source.fragment(
1403                    intersection.end,
1404                    source.snapshot.range.end,
1405                    retained_id.take().unwrap_or_else(allocate_vma_id),
1406                )?;
1407                updated = updated.insert_entry(tail)?;
1408            }
1409        }
1410        updated.coalesce_compatible()
1411    }
1412
1413    /// Merges adjacent fragments only when the public mapping identity,
1414    /// permissions, policy, advice and source coordinates all agree.  The
1415    /// first fragment's operation starts at the merged range and therefore
1416    /// remains the executable owner for the combined VMA.
1417    fn coalesce_compatible(&self) -> Option<Self> {
1418        let ordered: Vec<_> = self.iter_entries().collect();
1419        let mut updated = self.clone();
1420        let mut index = 0;
1421        while index < ordered.len() {
1422            let first = index;
1423            while index + 1 < ordered.len()
1424                && ordered[index]
1425                    .snapshot
1426                    .can_merge_with(ordered[index + 1].snapshot.as_ref())
1427            {
1428                index += 1;
1429            }
1430            if index > first {
1431                for entry in &ordered[first..=index] {
1432                    let (next, removed) = updated.remove_entry(entry.snapshot.range.start)?;
1433                    if removed.snapshot.id != entry.snapshot.id {
1434                        return None;
1435                    }
1436                    updated = next;
1437                }
1438                let merged = ordered[first]
1439                    .snapshot
1440                    .merge_through(ordered[index].snapshot.as_ref())?;
1441                updated =
1442                    updated.insert_with_operation(merged, ordered[first].operation.clone())?;
1443            }
1444            index += 1;
1445        }
1446        Some(updated)
1447    }
1448
1449    /// Coalesces only compatible runs that touch the updated interval.
1450    ///
1451    /// The ordered scan is read-only.  Actual changes still remove and insert
1452    /// through path-copy operations, so unrelated subtrees remain shared with
1453    /// the rollback root.  Requiring one `MappingGroup` and continuous source
1454    /// offsets prevents an advice update from erasing a logical mapping
1455    /// boundary.
1456    fn coalesce_huge_page_advice_near(&self, changed: VirtAddrRange) -> Option<Self> {
1457        let ordered: Vec<_> = self.iter_entries().collect();
1458        let mut replacements = Vec::new();
1459        let mut index = 0;
1460        while index < ordered.len() {
1461            let first = index;
1462            while index + 1 < ordered.len()
1463                && ordered[index]
1464                    .snapshot
1465                    .can_merge_with(ordered[index + 1].snapshot.as_ref())
1466            {
1467                index += 1;
1468            }
1469            if index > first
1470                && ordered[index].snapshot.range.end >= changed.start
1471                && ordered[first].snapshot.range.start <= changed.end
1472            {
1473                let mut starts = Vec::new();
1474                starts.try_reserve(index - first + 1).ok()?;
1475                starts.extend(
1476                    ordered[first..=index]
1477                        .iter()
1478                        .map(|entry| entry.snapshot.range.start),
1479                );
1480                let merged = ordered[first]
1481                    .snapshot
1482                    .merge_through(ordered[index].snapshot.as_ref())?;
1483                replacements.push((starts, merged, ordered[first].operation.clone()));
1484            }
1485            index += 1;
1486        }
1487
1488        let mut updated = self.clone();
1489        for (starts, merged, operation) in replacements {
1490            for start in starts {
1491                let (next, _) = updated.remove_entry(start)?;
1492                updated = next;
1493            }
1494            updated = updated.insert_with_operation(merged, operation)?;
1495        }
1496        Some(updated)
1497    }
1498
1499    /// Returns a successor root with `advice` applied to a fully mapped range.
1500    ///
1501    /// Every affected interval is replaced through path-copy updates.  The
1502    /// original root remains a complete rollback preimage until the caller
1503    /// publishes the successor through its address-space mutation gate.
1504    pub fn with_huge_page_advice(
1505        &self,
1506        range: VirtAddrRange,
1507        advice: HugePageAdvice,
1508    ) -> Option<Self> {
1509        if range.is_empty() || !self.contains_range(range.start, range.size()) {
1510            return None;
1511        }
1512        let affected: Vec<_> = self
1513            .iter_entries()
1514            .filter(|entry| entry.snapshot.range.overlaps(range))
1515            .collect();
1516        let mut updated = self.clone();
1517        for source in affected {
1518            let fragment = VirtAddrRange::new(
1519                source.snapshot.range.start.max(range.start),
1520                source.snapshot.range.end.min(range.end),
1521            );
1522            updated = updated.update_one_huge_page_advice(&source, fragment, advice)?;
1523        }
1524        updated.coalesce_huge_page_advice_near(range)
1525    }
1526
1527    /// Finds a free, aligned interval without exposing mutable tree nodes.
1528    pub fn find_free_area(
1529        &self,
1530        hint: VirtAddr,
1531        size: usize,
1532        limit: VirtAddrRange,
1533        align: usize,
1534    ) -> Option<VirtAddr> {
1535        // An empty/invalid search interval must never be treated as an
1536        // unbounded one.  In particular `start == end` used to let the final
1537        // candidate check succeed after arithmetic was rounded, returning an
1538        // address outside the caller's limit.
1539        if limit.start >= limit.end
1540            || size == 0
1541            || align == 0
1542            || !align.is_power_of_two()
1543            || !size.is_multiple_of(align)
1544        {
1545            return None;
1546        }
1547        let align_up = |address: VirtAddr| {
1548            address
1549                .as_usize()
1550                .checked_add(align - 1)
1551                .map(|value| VirtAddr::from_usize(value & !(align - 1)))
1552        };
1553        let mut candidate = align_up(hint.max(limit.start))?;
1554        if candidate < limit.start || candidate >= limit.end {
1555            return None;
1556        }
1557        for vma in self.iter() {
1558            if vma.range.end <= candidate {
1559                continue;
1560            }
1561            if vma.range.start > candidate
1562                && candidate >= limit.start
1563                && candidate
1564                    .checked_add(size)
1565                    .is_some_and(|end| end <= vma.range.start)
1566                && candidate
1567                    .checked_add(size)
1568                    .is_some_and(|end| end <= limit.end)
1569            {
1570                return Some(candidate);
1571            }
1572            candidate = align_up(vma.range.end.max(limit.start))?;
1573            if candidate >= limit.end {
1574                return None;
1575            }
1576        }
1577        candidate
1578            .checked_add(size)
1579            .is_some_and(|end| end <= limit.end)
1580            .then_some(candidate)
1581    }
1582
1583    pub(super) fn insert_entry(&self, entry: Arc<VmaEntry>) -> Option<Self> {
1584        insert_node(self.root.clone(), entry)
1585            .ok()
1586            .map(|root| Self { root: Some(root) })
1587    }
1588
1589    fn group_for_descriptor(&self, descriptor: VmaDescriptor) -> Arc<MappingGroup> {
1590        self.iter()
1591            .find(|candidate| same_mapping_group(candidate, descriptor))
1592            .map_or_else(
1593                || {
1594                    MappingGroup::new(
1595                        descriptor.mapping,
1596                        descriptor.source,
1597                        descriptor.page_policy,
1598                    )
1599                },
1600                |candidate| candidate.group.clone(),
1601            )
1602    }
1603
1604    #[allow(clippy::too_many_arguments)]
1605    pub(super) fn prepare_mapping_entry(
1606        &self,
1607        range: VirtAddrRange,
1608        rights: MappingRights,
1609        reported_rights: MappingRights,
1610        max_rights: MappingRights,
1611        huge_page_advice: HugePageAdvice,
1612        lock_mode: VmaLockMode,
1613        advice_policy: VmaAdvicePolicy,
1614        operation: MappingOperation,
1615    ) -> Option<Arc<VmaEntry>> {
1616        if range.is_empty() {
1617            return None;
1618        }
1619        let descriptor = operation.vma_descriptor(range.start);
1620        Some(VmaEntry::new(
1621            VmaSnapshot {
1622                id: allocate_vma_id(),
1623                range,
1624                rights,
1625                reported_rights,
1626                max_rights,
1627                group: self.group_for_descriptor(descriptor),
1628                source_offset: descriptor.source_offset,
1629                huge_page_advice,
1630                lock_mode,
1631                advice_policy,
1632            },
1633            operation,
1634        ))
1635    }
1636
1637    /// Prepares the complete metadata successor for a fresh mapping or a
1638    /// `MAP_FIXED` replacement. No PTE or externally visible root is changed.
1639    pub(super) fn with_mapping_entry(&self, entry: Arc<VmaEntry>, replace: bool) -> Option<Self> {
1640        let range = entry.range();
1641        let base = if self.overlaps(range) {
1642            if !replace {
1643                return None;
1644            }
1645            self.without_range(range)?
1646        } else {
1647            self.clone()
1648        };
1649        base.insert_entry(entry)
1650    }
1651
1652    /// Prepares a successor in which the VMA containing `address` has been
1653    /// extended to the right. The interval-tree insertion is the overlap
1654    /// check, while the caller separately validates/maps the new suffix.
1655    pub(super) fn with_extended_right(
1656        &self,
1657        address: VirtAddr,
1658        additional_size: usize,
1659    ) -> Option<Self> {
1660        let source = self.lookup_entry(address)?;
1661        let (without_source, removed) = self.remove_entry(source.start())?;
1662        if removed.snapshot.id != source.snapshot.id {
1663            return None;
1664        }
1665        without_source.insert_entry(source.extended_right(additional_size)?)
1666    }
1667
1668    pub(super) fn insert_with_operation(
1669        &self,
1670        vma: VmaSnapshot,
1671        operation: MappingOperation,
1672    ) -> Option<Self> {
1673        self.insert_entry(VmaEntry::new(vma, operation))
1674    }
1675
1676    fn remove_entry(&self, start: VirtAddr) -> Option<(Self, Arc<VmaEntry>)> {
1677        let (root, removed) = remove_node(self.root.clone(), start);
1678        removed.map(|removed| (Self { root }, removed))
1679    }
1680
1681    pub fn remove(&self, start: VirtAddr) -> Option<(Self, Arc<VmaSnapshot>)> {
1682        self.remove_entry(start)
1683            .map(|(map, entry)| (map, entry.snapshot.clone()))
1684    }
1685
1686    pub fn iter(&self) -> impl Iterator<Item = Arc<VmaSnapshot>> {
1687        let mut values = Vec::new();
1688        let mut stack = Vec::new();
1689        let mut node = self.root.clone();
1690        while node.is_some() || !stack.is_empty() {
1691            while let Some(current) = node {
1692                node = current.left.clone();
1693                stack.push(current);
1694            }
1695            let Some(current) = stack.pop() else {
1696                break;
1697            };
1698            values.push(current.entry.snapshot.clone());
1699            node = current.right.clone();
1700        }
1701        values.into_iter()
1702    }
1703
1704    pub(super) fn iter_entries(&self) -> impl Iterator<Item = Arc<VmaEntry>> {
1705        let mut values = Vec::new();
1706        let mut stack = Vec::new();
1707        let mut node = self.root.clone();
1708        while node.is_some() || !stack.is_empty() {
1709            while let Some(current) = node {
1710                node = current.left.clone();
1711                stack.push(current);
1712            }
1713            let Some(current) = stack.pop() else {
1714                break;
1715            };
1716            values.push(current.entry.clone());
1717            node = current.right.clone();
1718        }
1719        values.into_iter()
1720    }
1721
1722    pub(super) fn overlaps(&self, range: VirtAddrRange) -> bool {
1723        self.lookup_entry(range.start).is_some()
1724            || self
1725                .iter_entries()
1726                .any(|entry| entry.start() >= range.start && entry.start() < range.end)
1727    }
1728}
1729
1730fn same_mapping_group(candidate: &VmaSnapshot, descriptor: VmaDescriptor) -> bool {
1731    candidate.group.id == descriptor.mapping
1732        && candidate.group.source.as_ref() == &descriptor.source
1733        && candidate.group.page_policy == descriptor.page_policy
1734}
1735
1736/// Creates a process-wide unique VMA identifier.
1737pub fn allocate_vma_id() -> VmaId {
1738    static NEXT_ID: AtomicU64 = AtomicU64::new(1);
1739    VmaId::new(NEXT_ID.fetch_add(1, Ordering::Relaxed))
1740}
1741
1742/// Creates a process-wide unique logical mapping-group identifier.
1743pub fn allocate_mapping_id() -> MappingId {
1744    static NEXT_ID: AtomicU64 = AtomicU64::new(1);
1745    MappingId::new(NEXT_ID.fetch_add(1, Ordering::Relaxed))
1746}
1747
1748#[cfg(test)]
1749mod tests {
1750    use super::*;
1751
1752    fn snapshot(start: usize, size: usize) -> (VmaSnapshot, MappingOperation) {
1753        let start = VirtAddr::from_usize(start);
1754        let operation =
1755            MappingOperation::new_alloc(start, ax_memory_addr::PAGE_SIZE_4K, "vma-test");
1756        (
1757            VmaSnapshot {
1758                id: allocate_vma_id(),
1759                range: VirtAddrRange::from_start_size(start, size),
1760                rights: MappingFlags::READ,
1761                reported_rights: MappingFlags::READ,
1762                max_rights: MappingFlags::READ,
1763                group: MappingGroup::new(
1764                    MappingId::new(1),
1765                    MappingSource::Anonymous(AnonymousSource),
1766                    PageSizePolicy::Base,
1767                ),
1768                source_offset: PageOffset::ZERO,
1769                huge_page_advice: HugePageAdvice::Default,
1770                lock_mode: VmaLockMode::Unlocked,
1771                advice_policy: VmaAdvicePolicy::default(),
1772            },
1773            operation,
1774        )
1775    }
1776
1777    fn insert(map: &VmaMap, start: usize, size: usize) -> Option<VmaMap> {
1778        let (snapshot, operation) = snapshot(start, size);
1779        map.insert_with_operation(snapshot, operation)
1780    }
1781
1782    /// One VMA per 0x2000, so VMA `i` covers `[0x1000 + i * 0x2000, +0x1000)`.
1783    #[cfg(all(test, not(axtest)))]
1784    fn map_of(count: usize) -> VmaMap {
1785        let mut map = VmaMap::default();
1786        for i in 0..count {
1787            map = insert(&map, 0x1000 + i * 0x2000, 0x1000).unwrap();
1788        }
1789        map
1790    }
1791
1792    #[cfg(all(test, not(axtest)))]
1793    #[test]
1794    fn a_range_lookup_walks_the_search_path_not_the_whole_tree() {
1795        let map = map_of(256);
1796        let target = 0x1000 + 128 * 0x2000;
1797        let range = VirtAddrRange::from_start_size(VirtAddr::from_usize(target), 0x1000);
1798
1799        let mut visited = 0;
1800        let mut found = 0;
1801        let mut first = None;
1802        VmaMap::visit_overlapping(&map.root, range, &mut visited, &mut |entry| {
1803            found += 1;
1804            first.get_or_insert(entry.snapshot.range.start);
1805            true
1806        });
1807
1808        assert_eq!(found, 1);
1809        assert_eq!(first, Some(VirtAddr::from_usize(target)));
1810        assert!(
1811            visited <= 24,
1812            "a one-VMA lookup walked {visited} nodes of a 256-VMA tree",
1813        );
1814    }
1815
1816    #[cfg(all(test, not(axtest)))]
1817    #[test]
1818    fn a_spanning_lookup_returns_every_intersecting_vma_in_order() {
1819        let map = map_of(64);
1820        let start = 0x1000 + 10 * 0x2000;
1821        let end = 0x1000 + 20 * 0x2000;
1822        let range = VirtAddrRange::from_start_size(VirtAddr::from_usize(start), end - start);
1823
1824        let found = map.lookup_range(range);
1825
1826        assert_eq!(found.len(), 10);
1827        for (offset, vma) in found.iter().enumerate() {
1828            assert_eq!(
1829                vma.range.start,
1830                VirtAddr::from_usize(0x1000 + (10 + offset) * 0x2000),
1831            );
1832        }
1833    }
1834
1835    #[cfg(all(test, not(axtest)))]
1836    #[test]
1837    fn a_lookup_starting_inside_a_vma_still_reaches_its_predecessor() {
1838        let map = map_of(32);
1839        // Start halfway through VMA 7 so the match lies to the left of the
1840        // node the descent lands on.
1841        let start = 0x1000 + 7 * 0x2000 + 0x800;
1842        let range = VirtAddrRange::from_start_size(VirtAddr::from_usize(start), 0x400);
1843
1844        let found = map.lookup_range(range);
1845
1846        assert_eq!(found.len(), 1);
1847        assert_eq!(
1848            found[0].range.start,
1849            VirtAddr::from_usize(0x1000 + 7 * 0x2000),
1850        );
1851    }
1852
1853    #[test]
1854    fn snapshots_are_not_mutated_by_path_copy() {
1855        let first = insert(&VmaMap::default(), 0x1000, 0x1000).unwrap();
1856        let second = insert(&first, 0x3000, 0x1000).unwrap();
1857        assert!(first.lookup(VirtAddr::from_usize(0x3000)).is_none());
1858        assert!(second.lookup(VirtAddr::from_usize(0x3000)).is_some());
1859    }
1860
1861    #[test]
1862    fn remove_is_path_copy_and_rejects_overlap() {
1863        let first = insert(&VmaMap::default(), 0x1000, 0x2000).unwrap();
1864        assert!(insert(&first, 0x2000, 0x1000).is_none());
1865        let second = insert(&first, 0x5000, 0x1000).unwrap();
1866        let (third, removed) = second.remove(VirtAddr::from_usize(0x1000)).unwrap();
1867        assert_eq!(removed.range.start, VirtAddr::from_usize(0x1000));
1868        assert!(second.lookup(VirtAddr::from_usize(0x1800)).is_some());
1869        assert!(third.lookup(VirtAddr::from_usize(0x1800)).is_none());
1870        assert!(third.lookup(VirtAddr::from_usize(0x5000)).is_some());
1871    }
1872
1873    #[test]
1874    fn partial_lock_is_path_copied_and_unlock_coalesces_fragments() {
1875        let original = insert(&VmaMap::default(), 0x1000, 0x3000).unwrap();
1876        let middle = VirtAddrRange::from_start_size(VirtAddr::from_usize(0x2000), 0x1000);
1877        let locked = original
1878            .with_lock_mode(middle, VmaLockMode::LockOnFault)
1879            .unwrap();
1880
1881        assert_eq!(original.len(), 1);
1882        assert_eq!(locked.len(), 3);
1883        assert_eq!(
1884            original
1885                .lookup(VirtAddr::from_usize(0x2000))
1886                .unwrap()
1887                .lock_mode,
1888            VmaLockMode::Unlocked
1889        );
1890        assert_eq!(
1891            locked
1892                .lookup(VirtAddr::from_usize(0x1000))
1893                .unwrap()
1894                .lock_mode,
1895            VmaLockMode::Unlocked
1896        );
1897        assert_eq!(
1898            locked
1899                .lookup(VirtAddr::from_usize(0x2000))
1900                .unwrap()
1901                .lock_mode,
1902            VmaLockMode::LockOnFault
1903        );
1904        assert_eq!(
1905            locked
1906                .lookup(VirtAddr::from_usize(0x3000))
1907                .unwrap()
1908                .lock_mode,
1909            VmaLockMode::Unlocked
1910        );
1911
1912        let unlocked = locked
1913            .with_lock_mode(middle, VmaLockMode::Unlocked)
1914            .unwrap();
1915        assert_eq!(unlocked.len(), 1);
1916        assert_eq!(
1917            unlocked.lookup(VirtAddr::from_usize(0x2000)).unwrap().range,
1918            VirtAddrRange::from_start_size(VirtAddr::from_usize(0x1000), 0x3000)
1919        );
1920    }
1921
1922    #[test]
1923    fn unmap_successor_carves_middle_and_preserves_source_coordinates() {
1924        let original = insert(&VmaMap::default(), 0x1000, 0x4000).unwrap();
1925        let successor = original
1926            .without_range(VirtAddrRange::from_start_size(
1927                VirtAddr::from_usize(0x2000),
1928                0x2000,
1929            ))
1930            .unwrap();
1931
1932        assert_eq!(original.len(), 1);
1933        assert_eq!(successor.len(), 2);
1934        let head = successor.lookup(VirtAddr::from_usize(0x1000)).unwrap();
1935        let tail = successor.lookup(VirtAddr::from_usize(0x4000)).unwrap();
1936        assert_eq!(
1937            head.range,
1938            VirtAddrRange::from_start_size(VirtAddr::from_usize(0x1000), 0x1000)
1939        );
1940        assert_eq!(head.source_offset, PageOffset::ZERO);
1941        assert_eq!(
1942            tail.range,
1943            VirtAddrRange::from_start_size(VirtAddr::from_usize(0x4000), 0x1000)
1944        );
1945        assert_eq!(tail.source_offset, PageOffset::new(0x3000));
1946        assert!(successor.lookup(VirtAddr::from_usize(0x2000)).is_none());
1947    }
1948
1949    #[test]
1950    fn protection_successor_changes_only_the_intersection() {
1951        let original = insert(&VmaMap::default(), 0x1000, 0x4000).unwrap();
1952        let successor = original
1953            .with_permissions(
1954                VirtAddrRange::from_start_size(VirtAddr::from_usize(0x2000), 0x1000),
1955                MappingFlags::READ | MappingFlags::WRITE,
1956                MappingFlags::READ,
1957            )
1958            .unwrap();
1959
1960        assert_eq!(original.len(), 1);
1961        assert_eq!(successor.len(), 3);
1962        assert_eq!(
1963            successor
1964                .lookup(VirtAddr::from_usize(0x1000))
1965                .unwrap()
1966                .rights,
1967            MappingFlags::READ
1968        );
1969        let body = successor.lookup(VirtAddr::from_usize(0x2000)).unwrap();
1970        assert_eq!(body.rights, MappingFlags::READ | MappingFlags::WRITE);
1971        assert_eq!(body.reported_rights, MappingFlags::READ);
1972        assert_eq!(
1973            successor
1974                .lookup(VirtAddr::from_usize(0x3000))
1975                .unwrap()
1976                .rights,
1977            MappingFlags::READ
1978        );
1979        assert_eq!(original.len(), 1);
1980    }
1981
1982    fn huge_page_advice_is_vma_local_and_path_copied_for_test() {
1983        let original = insert(&VmaMap::default(), 0x1000, 0x4000).unwrap();
1984        let advised = original
1985            .with_huge_page_advice(
1986                VirtAddrRange::from_start_size(VirtAddr::from_usize(0x2000), 0x1000),
1987                HugePageAdvice::Avoid,
1988            )
1989            .unwrap();
1990
1991        assert_eq!(original.len(), 1);
1992        assert_eq!(
1993            original
1994                .lookup(VirtAddr::from_usize(0x2000))
1995                .unwrap()
1996                .huge_page_advice,
1997            HugePageAdvice::Default
1998        );
1999        assert_eq!(advised.len(), 3);
2000        assert_eq!(
2001            advised
2002                .lookup(VirtAddr::from_usize(0x1000))
2003                .unwrap()
2004                .huge_page_advice,
2005            HugePageAdvice::Default
2006        );
2007        assert_eq!(
2008            advised
2009                .lookup(VirtAddr::from_usize(0x2000))
2010                .unwrap()
2011                .huge_page_advice,
2012            HugePageAdvice::Avoid
2013        );
2014        assert_eq!(
2015            advised
2016                .lookup(VirtAddr::from_usize(0x3000))
2017                .unwrap()
2018                .huge_page_advice,
2019            HugePageAdvice::Default
2020        );
2021
2022        let restored = advised
2023            .with_huge_page_advice(
2024                VirtAddrRange::from_start_size(VirtAddr::from_usize(0x2000), 0x1000),
2025                HugePageAdvice::Default,
2026            )
2027            .unwrap();
2028        assert_eq!(restored.len(), 1);
2029        let restored_vma = restored.lookup(VirtAddr::from_usize(0x3000)).unwrap();
2030        assert_eq!(restored_vma.range, original.iter().next().unwrap().range);
2031        assert_eq!(restored_vma.huge_page_advice, HugePageAdvice::Default);
2032        assert_eq!(advised.len(), 3);
2033    }
2034
2035    fn process_thp_disable_overrides_vma_preference_for_test() {
2036        assert_eq!(
2037            PageSizePolicy::TRANSPARENT_2M
2038                .fault_leaf_size(HugePageAdvice::Prefer, TransparentHugePageMode::Disabled,),
2039            Some(ax_memory_addr::PAGE_SIZE_4K)
2040        );
2041        assert_eq!(
2042            PageSizePolicy::TRANSPARENT_2M.fault_leaf_size(
2043                HugePageAdvice::Prefer,
2044                TransparentHugePageMode::ExceptAdvised,
2045            ),
2046            Some(ax_memory_addr::PAGE_SIZE_2M)
2047        );
2048    }
2049
2050    #[cfg(all(test, not(axtest)))]
2051    #[test]
2052    fn huge_page_advice_is_vma_local_and_path_copied() {
2053        huge_page_advice_is_vma_local_and_path_copied_for_test();
2054    }
2055
2056    #[cfg(all(test, not(axtest)))]
2057    #[test]
2058    fn process_thp_disable_overrides_vma_preference() {
2059        process_thp_disable_overrides_vma_preference_for_test();
2060    }
2061
2062    #[cfg(all(test, axtest))]
2063    #[axtest::axtest]
2064    fn huge_page_advice_is_vma_local_and_path_copied() {
2065        huge_page_advice_is_vma_local_and_path_copied_for_test();
2066    }
2067
2068    #[cfg(all(test, axtest))]
2069    #[axtest::axtest]
2070    fn process_thp_disable_overrides_vma_preference() {
2071        process_thp_disable_overrides_vma_preference_for_test();
2072    }
2073}