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 1TB (0x10000000000) threshold is chosen to be:
149/// - High enough to avoid conflicts with real heap addresses on any platform
150/// - Low enough to fit in 64-bit address space comfortably
151/// - Aligned to a large boundary for easy identification
152pub const VIRTUAL_PTR_BASE: usize = 0x10000000000;
153
154/// Check if a pointer is a virtual pointer used for Container types.
155///
156/// Returns `true` if the pointer is >= VIRTUAL_PTR_BASE (indicating it's
157/// a virtual pointer for Container types rather than a real heap address).
158#[inline]
159pub const fn is_virtual_pointer(ptr: usize) -> bool {
160    ptr >= VIRTUAL_PTR_BASE
161}
162
163/// Global node counter
164///
165/// This atomic counter ensures that all NodeIDs are globally unique
166/// and thread-safe across the entire application.
167static NODE_COUNTER: AtomicU64 = AtomicU64::new(1);
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    #[test]
174    fn test_node_id_uniqueness() {
175        let id1 = NodeId::new();
176        let id2 = NodeId::new();
177        assert_ne!(id1, id2);
178    }
179
180    #[test]
181    fn test_node_id_validity() {
182        let id = NodeId::new();
183        assert!(id.is_valid());
184
185        let invalid_id = NodeId::from_raw(0);
186        assert!(!invalid_id.is_valid());
187    }
188
189    #[test]
190    fn test_node_id_from_raw() {
191        let value = 42u64;
192        let id = NodeId::from_raw(value);
193        assert_eq!(id.as_u64(), value);
194    }
195
196    #[test]
197    fn test_node_id_ord() {
198        let id1 = NodeId::new();
199        let id2 = NodeId::new();
200        assert!(id1 < id2);
201    }
202}