rb_sys/
tracking_allocator.rs1use std::{
4 fmt::Formatter,
5 sync::{
6 atomic::{AtomicIsize, Ordering},
7 Arc,
8 },
9};
10
11#[cfg(ruby_engine = "mri")]
12mod mri {
13 use crate::{rb_gc_adjust_memory_usage, utils::is_ruby_vm_started};
14 use std::alloc::{GlobalAlloc, Layout, System};
15
16 #[derive(Debug)]
20 pub struct TrackingAllocator;
21
22 impl TrackingAllocator {
23 #[allow(clippy::new_without_default)]
25 pub const fn new() -> Self {
26 Self
27 }
28
29 pub const fn default() -> Self {
31 Self::new()
32 }
33
34 #[inline]
49 pub fn adjust_memory_usage(delta: isize) -> isize {
50 if delta == 0 {
51 return 0;
52 }
53
54 #[cfg(target_pointer_width = "32")]
55 let delta = delta as i32;
56
57 #[cfg(target_pointer_width = "64")]
58 let delta = delta as i64;
59
60 unsafe {
61 if is_ruby_vm_started() {
62 rb_gc_adjust_memory_usage(delta);
63 delta as isize
64 } else {
65 0
66 }
67 }
68 }
69 }
70
71 unsafe impl GlobalAlloc for TrackingAllocator {
72 #[inline]
73 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
74 let ret = System.alloc(layout);
75 let delta = layout.size() as isize;
76
77 if !ret.is_null() && delta != 0 {
78 Self::adjust_memory_usage(delta);
79 }
80
81 ret
82 }
83
84 #[inline]
85 unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
86 let ret = System.alloc_zeroed(layout);
87 let delta = layout.size() as isize;
88
89 if !ret.is_null() && delta != 0 {
90 Self::adjust_memory_usage(delta);
91 }
92
93 ret
94 }
95
96 #[inline]
97 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
98 System.dealloc(ptr, layout);
99 let delta = -(layout.size() as isize);
100
101 if delta != 0 {
102 Self::adjust_memory_usage(delta);
103 }
104 }
105
106 #[inline]
107 unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
108 let ret = System.realloc(ptr, layout, new_size);
109 let delta = new_size as isize - layout.size() as isize;
110
111 if !ret.is_null() && delta != 0 {
112 Self::adjust_memory_usage(delta);
113 }
114
115 ret
116 }
117 }
118}
119
120#[cfg(not(ruby_engine = "mri"))]
121mod non_mri {
122 use std::alloc::{GlobalAlloc, Layout, System};
123
124 pub struct TrackingAllocator;
126
127 impl TrackingAllocator {
128 #[allow(clippy::new_without_default)]
129 pub const fn new() -> Self {
130 Self
131 }
132
133 pub const fn default() -> Self {
134 Self::new()
135 }
136
137 pub fn adjust_memory_usage(_delta: isize) -> isize {
138 0
139 }
140 }
141
142 unsafe impl GlobalAlloc for TrackingAllocator {
143 #[inline]
144 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
145 System.alloc(layout)
146 }
147
148 #[inline]
149 unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
150 System.alloc_zeroed(layout)
151 }
152
153 #[inline]
154 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
155 System.dealloc(ptr, layout)
156 }
157
158 #[inline]
159 unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
160 System.realloc(ptr, layout, new_size)
161 }
162 }
163}
164
165#[cfg(ruby_engine = "mri")]
166pub use mri::*;
167
168#[cfg(not(ruby_engine = "mri"))]
169pub use non_mri::*;
170
171#[macro_export]
181macro_rules! set_global_tracking_allocator {
182 () => {
183 #[global_allocator]
184 static RUBY_GLOBAL_TRACKING_ALLOCATOR: $crate::tracking_allocator::TrackingAllocator =
185 $crate::tracking_allocator::TrackingAllocator;
186 };
187}
188
189#[derive(Debug)]
190#[repr(transparent)]
191struct MemsizeDelta(Arc<AtomicIsize>);
192
193impl MemsizeDelta {
194 fn new(delta: isize) -> Self {
195 let delta = TrackingAllocator::adjust_memory_usage(delta);
196 Self(Arc::new(AtomicIsize::new(delta)))
197 }
198
199 fn add(&self, delta: usize) {
200 if delta == 0 {
201 return;
202 }
203
204 let delta = TrackingAllocator::adjust_memory_usage(delta as _);
205 self.0.fetch_add(delta as _, Ordering::SeqCst);
206 }
207
208 fn sub(&self, delta: usize) {
209 if delta == 0 {
210 return;
211 }
212
213 let delta = TrackingAllocator::adjust_memory_usage(-(delta as isize));
214 self.0.fetch_add(delta, Ordering::SeqCst);
215 }
216
217 fn get(&self) -> isize {
218 self.0.load(Ordering::SeqCst)
219 }
220}
221
222impl Clone for MemsizeDelta {
223 fn clone(&self) -> Self {
224 Self(Arc::clone(&self.0))
225 }
226}
227
228impl Drop for MemsizeDelta {
229 fn drop(&mut self) {
230 let memsize = self.0.swap(0, Ordering::SeqCst);
231 TrackingAllocator::adjust_memory_usage(0 - memsize);
232 }
233}
234
235pub struct ManuallyTracked<T> {
256 item: T,
257 memsize_delta: MemsizeDelta,
258}
259
260impl<T> ManuallyTracked<T> {
261 pub fn wrap(item: T, memsize: usize) -> Self {
264 Self {
265 item,
266 memsize_delta: MemsizeDelta::new(memsize as _),
267 }
268 }
269
270 pub fn increase_memory_usage(&self, memsize: usize) {
272 self.memsize_delta.add(memsize);
273 }
274
275 pub fn decrease_memory_usage(&self, memsize: usize) {
277 self.memsize_delta.sub(memsize);
278 }
279
280 pub fn memsize_delta(&self) -> isize {
282 self.memsize_delta.get()
283 }
284
285 pub fn get(&self) -> &T {
287 &self.item
288 }
289
290 pub fn get_mut(&mut self) -> &mut T {
292 &mut self.item
293 }
294}
295
296impl ManuallyTracked<()> {
297 pub fn new(memsize: usize) -> Self {
300 Self::wrap((), memsize)
301 }
302}
303
304impl Default for ManuallyTracked<()> {
305 fn default() -> Self {
306 Self::wrap((), 0)
307 }
308}
309
310impl<T: Clone> Clone for ManuallyTracked<T> {
311 fn clone(&self) -> Self {
312 Self {
313 item: self.item.clone(),
314 memsize_delta: self.memsize_delta.clone(),
315 }
316 }
317}
318
319impl<T: std::fmt::Debug> std::fmt::Debug for ManuallyTracked<T> {
320 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
321 f.debug_struct("ManuallyTracked")
322 .field("item", &self.item)
323 .field("memsize_delta", &self.memsize_delta)
324 .finish()
325 }
326}