hermes_simd_core/numa/
locality.rs1pub fn current_numa_node() -> Option<u32> {
6 themis::try_current_numa_node().map(|node| node.get())
7}
8
9pub fn refresh_numa_node() -> Option<u32> {
11 current_numa_node()
12}
13
14#[cfg(feature = "std")]
15#[derive(Copy, Clone)]
16struct CacheEntry {
17 ptr_start: usize,
18 ptr_end: usize,
19 node: u32,
20 generation: u64,
21 local: bool,
22}
23
24#[cfg(feature = "std")]
25struct LocalityCache {
26 entries: [Option<CacheEntry>; 16],
27 next_idx: usize,
28}
29
30#[cfg(feature = "std")]
31impl LocalityCache {
32 const fn new() -> Self {
33 Self {
34 entries: [None; 16],
35 next_idx: 0,
36 }
37 }
38}
39
40#[cfg(feature = "std")]
41thread_local! {
42 static LOCALITY_CACHE: core::cell::RefCell<LocalityCache> = const {
43 core::cell::RefCell::new(LocalityCache::new())
44 };
45}
46
47#[cfg(feature = "std")]
48#[repr(align(64))]
49struct CacheAlignedAtomicU64(core::sync::atomic::AtomicU64);
50
51#[cfg(feature = "std")]
52static ALLOC_GENERATION: CacheAlignedAtomicU64 =
53 CacheAlignedAtomicU64(core::sync::atomic::AtomicU64::new(0));
54
55#[inline]
64pub fn bump_alloc_generation() {
65 #[cfg(feature = "std")]
66 ALLOC_GENERATION
67 .0
68 .fetch_add(1, core::sync::atomic::Ordering::Release);
69}
70
71#[inline]
73pub fn get_alloc_generation() -> u64 {
74 #[cfg(feature = "std")]
75 {
76 ALLOC_GENERATION
77 .0
78 .load(core::sync::atomic::Ordering::Acquire)
79 }
80 #[cfg(not(feature = "std"))]
81 {
82 0
83 }
84}
85
86pub fn verify_numa_locality(ptr: *const u8, size: usize, expected_node: u32) -> bool {
88 #[cfg(feature = "std")]
89 {
90 let ptr_val = ptr as usize;
91 let gen = get_alloc_generation();
92
93 let cached = LOCALITY_CACHE.with(|cache| {
94 let cache_ref = cache.borrow();
95 let end_val = ptr_val.saturating_add(size);
96 for entry in cache_ref.entries.iter().flatten() {
97 if entry.node == expected_node
98 && entry.generation == gen
99 && ptr_val >= entry.ptr_start
100 && end_val <= entry.ptr_end
101 {
102 return Some(entry.local);
103 }
104 }
105 None
106 });
107
108 if let Some(local) = cached {
109 return local;
110 }
111
112 let local = verify_numa_locality_os(ptr, size, expected_node);
113
114 LOCALITY_CACHE.with(|cache| {
121 let mut cache_mut = cache.borrow_mut();
122 let idx = cache_mut.next_idx;
123 let end_val = ptr_val.saturating_add(size);
124 cache_mut.entries[idx] = Some(CacheEntry {
125 ptr_start: ptr_val,
126 ptr_end: end_val,
127 node: expected_node,
128 generation: gen,
129 local,
130 });
131 cache_mut.next_idx = (idx + 1) % 16;
132 });
133
134 local
135 }
136
137 #[cfg(not(feature = "std"))]
138 {
139 verify_numa_locality_os(ptr, size, expected_node)
140 }
141}
142
143fn verify_numa_locality_os(ptr: *const u8, size: usize, expected_node: u32) -> bool {
144 #[cfg(target_os = "windows")]
145 unsafe {
146 use core::ffi::c_void;
147 #[repr(C)]
148 #[derive(Copy, Clone)]
149 struct PsapiWorkingSetExInformation {
150 virtual_address: *mut c_void,
151 virtual_attributes: usize,
152 }
153 extern "system" {
154 fn GetCurrentProcess() -> *mut c_void;
155 fn K32QueryWorkingSetEx(hProcess: *mut c_void, pv: *mut c_void, cb: u32) -> i32;
156 }
157 let page_size = 4096;
158 let start_page = (ptr as usize) & !(page_size - 1);
159 let end_page = ((ptr as usize) + size + page_size - 1) & !(page_size - 1);
160 let pages_count = (end_page - start_page) / page_size;
161 if pages_count == 0 {
162 return true;
163 }
164
165 const CHUNK_SIZE: usize = 64;
166 let mut info_arr = [PsapiWorkingSetExInformation {
167 virtual_address: core::ptr::null_mut(),
168 virtual_attributes: 0,
169 }; CHUNK_SIZE];
170
171 let mut checked = 0;
172 while checked < pages_count {
173 let chunk_len = core::cmp::min(pages_count - checked, CHUNK_SIZE);
174 for i in 0..chunk_len {
175 info_arr[i].virtual_address =
176 (start_page + (checked + i) * page_size) as *mut c_void;
177 info_arr[i].virtual_attributes = 0;
178 }
179 let cb = (chunk_len * core::mem::size_of::<PsapiWorkingSetExInformation>()) as u32;
180 let res = K32QueryWorkingSetEx(
181 GetCurrentProcess(),
182 info_arr.as_mut_ptr() as *mut c_void,
183 cb,
184 );
185 if res != 0 {
186 for i in 0..chunk_len {
187 let flags = info_arr[i].virtual_attributes;
188 let valid = (flags & 1) != 0;
189 if valid {
190 let node = (flags >> 16) & 0x3F;
191 if node as u32 != expected_node {
192 return false;
193 }
194 }
195 }
196 } else {
197 return false;
198 }
199 checked += chunk_len;
200 }
201 true
202 }
203
204 #[cfg(all(target_os = "linux", feature = "libnuma"))]
205 unsafe {
206 #[link(name = "numa")]
207 extern "C" {
208 fn move_pages(
209 pid: i32,
210 count: usize,
211 pages: *const *mut core::ffi::c_void,
212 nodes: *const i32,
213 status: *mut i32,
214 flags: i32,
215 ) -> i32;
216 }
217 let page_size = 4096;
218 let start_page = (ptr as usize) & !(page_size - 1);
219 let end_page = ((ptr as usize) + size + page_size - 1) & !(page_size - 1);
220 let pages_count = (end_page - start_page) / page_size;
221 if pages_count == 0 {
222 return true;
223 }
224
225 const CHUNK_SIZE: usize = 64;
226 let mut pages_arr = [core::ptr::null_mut(); CHUNK_SIZE];
227 let mut status_arr = [0i32; CHUNK_SIZE];
228
229 let mut checked = 0;
230 while checked < pages_count {
231 let chunk_len = core::cmp::min(pages_count - checked, CHUNK_SIZE);
232 for i in 0..chunk_len {
233 pages_arr[i] = (start_page + (checked + i) * page_size) as *mut core::ffi::c_void;
234 }
235 let res = move_pages(
236 0,
237 chunk_len,
238 pages_arr.as_ptr(),
239 core::ptr::null(),
240 status_arr.as_mut_ptr(),
241 0,
242 );
243 if res >= 0 {
244 for i in 0..chunk_len {
245 let node = status_arr[i];
246 if node >= 0 && node as u32 != expected_node {
247 return false;
248 }
249 }
250 } else {
251 return false;
252 }
253 checked += chunk_len;
254 }
255 return true;
256 }
257
258 #[cfg(all(target_os = "linux", not(feature = "libnuma")))]
259 {
260 let _ = ptr;
261 let _ = size;
262 let _ = expected_node;
263 true
264 }
265
266 #[cfg(not(any(target_os = "windows", target_os = "linux")))]
267 {
268 let _ = ptr;
269 let _ = size;
270 let _ = expected_node;
271 true
272 }
273}