Skip to main content

memscope_rs/analysis/
node_id.rs

1//! Node ID Module
2//!
3//! This module provides a unified node identity system for the memscope graph model.
4//!
5//! # Design Principles
6//!
7//! - Graph identity is independent of memory address
8//! - NodeID is globally unique and thread-safe
9//! - Container types do not require a pointer
10//!
11//! # Architecture
12//!
13//! ```text
14//! Allocation → NodeID (unique)
15//! Memory Address → Optional pointer
16//! Graph → NodeID-based edges
17//! ```
18
19use serde::{Deserialize, Serialize};
20use std::sync::atomic::{AtomicU64, Ordering};
21
22/// Unique node identifier
23///
24/// This type represents a unique identifier for each node in the ownership graph.
25/// It is globally unique and thread-safe.
26///
27/// # Examples
28///
29/// ```
30/// use memscope_rs::analysis::node_id::NodeId;
31///
32/// let id1 = NodeId::new();
33/// let id2 = NodeId::new();
34/// assert_ne!(id1, id2);
35/// ```
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
37pub struct NodeId(pub u64);
38
39impl NodeId {
40    /// Create a new unique node ID
41    ///
42    /// This uses an atomic counter to ensure global uniqueness and thread safety.
43    ///
44    /// # Returns
45    ///
46    /// A new unique `NodeId`.
47    ///
48    /// # Examples
49    ///
50    /// ```
51    /// use memscope_rs::analysis::node_id::NodeId;
52    ///
53    /// let id = NodeId::new();
54    /// assert!(id.0 > 0);
55    /// ```
56    #[inline]
57    pub fn new() -> Self {
58        Self(NODE_COUNTER.fetch_add(1, Ordering::Relaxed))
59    }
60
61    /// Create NodeID from a raw value
62    ///
63    /// # Safety
64    ///
65    /// This function should only be used when you are certain that the value
66    /// does not conflict with existing node IDs. Use with caution.
67    ///
68    /// # Arguments
69    ///
70    /// * `value` - The raw u64 value
71    ///
72    /// # Returns
73    ///
74    /// A `NodeId` with the specified value.
75    #[inline]
76    pub const fn from_raw(value: u64) -> Self {
77        Self(value)
78    }
79
80    /// Create NodeId from a pointer address
81    ///
82    /// This is useful when converting from memory addresses to node IDs.
83    ///
84    /// # Arguments
85    ///
86    /// * `ptr` - The pointer address as usize
87    ///
88    /// # Returns
89    ///
90    /// A `NodeId` with the pointer value.
91    #[inline]
92    pub fn from_ptr(ptr: usize) -> Self {
93        Self(ptr as u64)
94    }
95
96    /// Get the raw u64 value
97    ///
98    /// # Returns
99    ///
100    /// The raw u64 value of this NodeID.
101    #[inline]
102    pub const fn as_u64(&self) -> u64 {
103        self.0
104    }
105
106    /// Check if this NodeID is valid (non-zero)
107    ///
108    /// # Returns
109    ///
110    /// `true` if the NodeID is valid, `false` otherwise.
111    #[inline]
112    pub const fn is_valid(&self) -> bool {
113        self.0 > 0
114    }
115}
116
117impl Default for NodeId {
118    /// Default implementation for NodeId
119    ///
120    /// Creates a new unique NodeID.
121    ///
122    /// # Returns
123    ///
124    /// A new unique `NodeId`.
125    #[inline]
126    fn default() -> Self {
127        Self::new()
128    }
129}
130
131impl std::fmt::Display for NodeId {
132    /// Display implementation for NodeId
133    ///
134    /// # Returns
135    ///
136    /// Formatted string representation of the NodeId.
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        write!(f, "{}", self.0)
139    }
140}
141
142/// Virtual pointer base address for Container types.
143///
144/// Container types (like HashMap, Vec, etc.) don't have real heap pointers.
145/// We use virtual addresses in a reserved range to avoid collisions with
146/// real heap allocations.
147///
148/// The 128PB (0x8000_0000_0000_0000) threshold is chosen to be:
149/// - Above the highest possible user-space heap address on any platform
150///   (macOS can allocate up to ~0x7FFF_FFFF_FFFF_FFFF)
151/// - Below kernel space, fitting cleanly in 64-bit address space
152/// - Aligned to a large boundary for easy identification
153pub const VIRTUAL_PTR_BASE: usize = 0x8000_0000_0000_0000;
154
155/// Check if a pointer is a virtual pointer used for Container types.
156///
157/// Returns `true` if the pointer is >= VIRTUAL_PTR_BASE (indicating it's
158/// a virtual pointer for Container types rather than a real heap address).
159#[inline]
160pub const fn is_virtual_pointer(ptr: usize) -> bool {
161    ptr >= VIRTUAL_PTR_BASE
162}
163
164/// Global node counter
165///
166/// This atomic counter ensures that all NodeIDs are globally unique
167/// and thread-safe across the entire application.
168static NODE_COUNTER: AtomicU64 = AtomicU64::new(1);
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn test_node_id_uniqueness() {
176        let id1 = NodeId::new();
177        let id2 = NodeId::new();
178        assert_ne!(id1, id2);
179    }
180
181    #[test]
182    fn test_node_id_validity() {
183        let id = NodeId::new();
184        assert!(id.is_valid());
185
186        let invalid_id = NodeId::from_raw(0);
187        assert!(!invalid_id.is_valid());
188    }
189
190    #[test]
191    fn test_node_id_from_raw() {
192        let value = 42u64;
193        let id = NodeId::from_raw(value);
194        assert_eq!(id.as_u64(), value);
195    }
196
197    #[test]
198    fn test_node_id_ord() {
199        let id1 = NodeId::new();
200        let id2 = NodeId::new();
201        assert!(id1 < id2);
202    }
203}