Skip to main content

shape_gc/
platform.rs

1//! Hardware pointer masking platform detection and operations.
2//!
3//! Supports:
4//! - **ARM64 TBI** (Top Byte Ignore): Always available on ARM64. Hardware ignores
5//!   the top byte of pointers, giving us 8 bits for GC metadata.
6//! - **x86-64 LAM** (Linear Address Masking): Available on recent Intel/AMD.
7//!   Masks bits 62:57 (LAM57) or 62:48 (LAM48).
8//! - **Software fallback**: Manually AND-mask before dereferencing.
9//!
10//! ## Pointer format
11//!
12//! ```text
13//! ARM TBI:     [COLOR:2][GEN:1][unused:5][ADDRESS:56]
14//! x86-64 LAM:  [0][COLOR:2][GEN:1][unused:3][ADDRESS:57]
15//! Fallback:    Same format, software AND mask before deref
16//! ```
17//!
18//! ## Integration with NaN-boxing
19//!
20//! NaN-boxing uses a 48-bit PAYLOAD_MASK which already strips upper bits.
21//! GC metadata lives in bits 48-55 which are zeroed by PAYLOAD_MASK. So
22//! existing ValueWord pointer extraction automatically strips GC tags.
23//! Tags are only relevant inside the GC itself.
24
25use crate::header::{GcColor, Generation};
26use std::sync::atomic::{AtomicU8, Ordering};
27
28/// Detected pointer masking mode.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum MaskingMode {
31    /// ARM64 Top Byte Ignore — always available on ARM64.
32    ArmTbi,
33    /// x86-64 Linear Address Masking (57-bit).
34    X86Lam57,
35    /// Software fallback — manual AND before dereferencing.
36    Software,
37}
38
39/// Cached masking mode for fast repeated queries.
40///
41/// 0 = not initialized, 1 = Software, 2 = ArmTbi, 3 = X86Lam57
42static CACHED_MASKING_MODE: AtomicU8 = AtomicU8::new(0);
43
44/// Get the cached masking mode, detecting on first call.
45pub fn cached_masking_mode() -> MaskingMode {
46    let cached = CACHED_MASKING_MODE.load(Ordering::Relaxed);
47    match cached {
48        1 => MaskingMode::Software,
49        2 => MaskingMode::ArmTbi,
50        3 => MaskingMode::X86Lam57,
51        _ => {
52            let mode = detect_masking_mode();
53            let val = match mode {
54                MaskingMode::Software => 1,
55                MaskingMode::ArmTbi => 2,
56                MaskingMode::X86Lam57 => 3,
57            };
58            CACHED_MASKING_MODE.store(val, Ordering::Relaxed);
59            mode
60        }
61    }
62}
63
64/// Check if x86-64 LAM is available (runtime-detected).
65pub fn has_x86_lam() -> bool {
66    cached_masking_mode() == MaskingMode::X86Lam57
67}
68
69/// Address mask: zero out metadata bits to get the raw address.
70const ADDRESS_MASK_48: u64 = 0x0000_FFFF_FFFF_FFFF; // 48-bit (NaN-box compatible)
71const ADDRESS_MASK_56: u64 = 0x00FF_FFFF_FFFF_FFFF; // 56-bit (ARM TBI)
72const ADDRESS_MASK_57: u64 = 0x01FF_FFFF_FFFF_FFFF; // 57-bit (x86 LAM57)
73
74/// Bit positions for metadata in the upper pointer bits.
75const COLOR_SHIFT: u32 = 62;
76const GEN_SHIFT: u32 = 61;
77
78/// Detect the best available pointer masking mode for this platform.
79pub fn detect_masking_mode() -> MaskingMode {
80    #[cfg(target_arch = "aarch64")]
81    {
82        // ARM64 TBI is always available — the hardware ignores the top byte
83        return MaskingMode::ArmTbi;
84    }
85
86    #[cfg(target_arch = "x86_64")]
87    {
88        // Try to enable LAM57 via prctl
89        if try_enable_lam57() {
90            return MaskingMode::X86Lam57;
91        }
92    }
93
94    MaskingMode::Software
95}
96
97/// Try to enable x86-64 LAM57 via prctl.
98#[cfg(target_arch = "x86_64")]
99fn try_enable_lam57() -> bool {
100    // PR_SET_TAGGED_ADDR_CTRL = 54, PR_TAGGED_ADDR_ENABLE = 1
101    // LAM57: PR_MTE_TAG_MASK with appropriate flags
102    // For now, conservatively return false until kernel support is confirmed
103    false
104}
105
106/// Strip metadata bits from a tagged pointer, returning the raw address.
107///
108/// Safe to call on any pointer — if no metadata is present, this is a no-op
109/// (the upper bits will already be zero for valid user-space pointers).
110#[inline(always)]
111pub fn mask_ptr(tagged: *mut u8, mode: MaskingMode) -> *mut u8 {
112    let addr = tagged as u64;
113    let masked = match mode {
114        MaskingMode::ArmTbi => addr & ADDRESS_MASK_56,
115        MaskingMode::X86Lam57 => addr & ADDRESS_MASK_57,
116        MaskingMode::Software => addr & ADDRESS_MASK_48,
117    };
118    masked as *mut u8
119}
120
121/// Tag a raw pointer with GC metadata (color and generation).
122#[inline(always)]
123pub fn tag_ptr(ptr: *mut u8, color: GcColor, generation: Generation) -> *mut u8 {
124    let addr = ptr as u64;
125    let color_bits = (color as u64) << COLOR_SHIFT;
126    let gen_bit = (generation as u64) << GEN_SHIFT;
127    (addr | color_bits | gen_bit) as *mut u8
128}
129
130/// Read the color from a tagged pointer.
131#[inline(always)]
132pub fn read_color(tagged: *mut u8) -> GcColor {
133    let bits = tagged as u64;
134    match (bits >> COLOR_SHIFT) & 0b11 {
135        0 => GcColor::White,
136        1 => GcColor::Gray,
137        2 => GcColor::Black,
138        _ => GcColor::White,
139    }
140}
141
142/// Read the generation from a tagged pointer.
143#[inline(always)]
144pub fn read_generation(tagged: *mut u8) -> Generation {
145    let bits = tagged as u64;
146    if (bits >> GEN_SHIFT) & 1 == 0 {
147        Generation::Young
148    } else {
149        Generation::Old
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn test_software_mask_strips_upper_bits() {
159        let raw = 0xDEAD_0000_1234_5678_u64 as *mut u8;
160        let masked = mask_ptr(raw, MaskingMode::Software);
161        assert_eq!(masked as u64, 0x0000_0000_1234_5678);
162    }
163
164    #[test]
165    fn test_tag_and_read_color() {
166        let raw = 0x0000_0000_1234_5678_u64 as *mut u8;
167
168        let tagged = tag_ptr(raw, GcColor::Gray, Generation::Young);
169        assert_eq!(read_color(tagged), GcColor::Gray);
170        assert_eq!(read_generation(tagged), Generation::Young);
171
172        // Masking should recover the original address
173        let unmasked = mask_ptr(tagged, MaskingMode::Software);
174        // The 48-bit mask may strip more than needed, but the lower 48 bits are preserved
175        assert_eq!(
176            unmasked as u64 & 0x0000_FFFF_FFFF_FFFF,
177            0x0000_0000_1234_5678
178        );
179    }
180
181    #[test]
182    fn test_tag_and_read_generation() {
183        let raw = 0x0000_0000_ABCD_EF00_u64 as *mut u8;
184        let tagged = tag_ptr(raw, GcColor::Black, Generation::Old);
185        assert_eq!(read_color(tagged), GcColor::Black);
186        assert_eq!(read_generation(tagged), Generation::Old);
187    }
188}