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}