use std::{
fmt::Formatter,
sync::{
atomic::{AtomicIsize, Ordering},
Arc,
},
};
#[cfg(ruby_engine = "mri")]
mod mri {
use crate::{rb_gc_adjust_memory_usage, utils::is_ruby_vm_started};
use std::alloc::{GlobalAlloc, Layout, System};
#[derive(Debug)]
pub struct TrackingAllocator;
impl TrackingAllocator {
#[allow(clippy::new_without_default)]
pub const fn new() -> Self {
Self
}
pub const fn default() -> Self {
Self::new()
}
#[inline]
pub fn adjust_memory_usage(delta: isize) -> isize {
if delta == 0 {
return 0;
}
#[cfg(target_pointer_width = "32")]
let delta = delta as i32;
#[cfg(target_pointer_width = "64")]
let delta = delta as i64;
unsafe {
if is_ruby_vm_started() {
rb_gc_adjust_memory_usage(delta);
delta as isize
} else {
0
}
}
}
}
unsafe impl GlobalAlloc for TrackingAllocator {
#[inline]
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let ret = System.alloc(layout);
let delta = layout.size() as isize;
if !ret.is_null() && delta != 0 {
Self::adjust_memory_usage(delta);
}
ret
}
#[inline]
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
let ret = System.alloc_zeroed(layout);
let delta = layout.size() as isize;
if !ret.is_null() && delta != 0 {
Self::adjust_memory_usage(delta);
}
ret
}
#[inline]
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
System.dealloc(ptr, layout);
let delta = -(layout.size() as isize);
if delta != 0 {
Self::adjust_memory_usage(delta);
}
}
#[inline]
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
let ret = System.realloc(ptr, layout, new_size);
let delta = new_size as isize - layout.size() as isize;
if !ret.is_null() && delta != 0 {
Self::adjust_memory_usage(delta);
}
ret
}
}
}
#[cfg(not(ruby_engine = "mri"))]
mod non_mri {
use std::alloc::{GlobalAlloc, Layout, System};
pub struct TrackingAllocator;
impl TrackingAllocator {
#[allow(clippy::new_without_default)]
pub const fn new() -> Self {
Self
}
pub const fn default() -> Self {
Self::new()
}
pub fn adjust_memory_usage(_delta: isize) -> isize {
0
}
}
unsafe impl GlobalAlloc for TrackingAllocator {
#[inline]
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
System.alloc(layout)
}
#[inline]
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
System.alloc_zeroed(layout)
}
#[inline]
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
System.dealloc(ptr, layout)
}
#[inline]
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
System.realloc(ptr, layout, new_size)
}
}
}
#[cfg(ruby_engine = "mri")]
pub use mri::*;
#[cfg(not(ruby_engine = "mri"))]
pub use non_mri::*;
#[macro_export]
macro_rules! set_global_tracking_allocator {
() => {
#[global_allocator]
static RUBY_GLOBAL_TRACKING_ALLOCATOR: $crate::tracking_allocator::TrackingAllocator =
$crate::tracking_allocator::TrackingAllocator;
};
}
#[derive(Debug)]
#[repr(transparent)]
struct MemsizeDelta(Arc<AtomicIsize>);
impl MemsizeDelta {
fn new(delta: isize) -> Self {
let delta = TrackingAllocator::adjust_memory_usage(delta);
Self(Arc::new(AtomicIsize::new(delta)))
}
fn add(&self, delta: usize) {
if delta == 0 {
return;
}
let delta = TrackingAllocator::adjust_memory_usage(delta as _);
self.0.fetch_add(delta as _, Ordering::SeqCst);
}
fn sub(&self, delta: usize) {
if delta == 0 {
return;
}
let delta = TrackingAllocator::adjust_memory_usage(-(delta as isize));
self.0.fetch_add(delta, Ordering::SeqCst);
}
fn get(&self) -> isize {
self.0.load(Ordering::SeqCst)
}
}
impl Clone for MemsizeDelta {
fn clone(&self) -> Self {
Self(Arc::clone(&self.0))
}
}
impl Drop for MemsizeDelta {
fn drop(&mut self) {
let memsize = self.0.swap(0, Ordering::SeqCst);
TrackingAllocator::adjust_memory_usage(0 - memsize);
}
}
pub struct ManuallyTracked<T> {
item: T,
memsize_delta: MemsizeDelta,
}
impl<T> ManuallyTracked<T> {
pub fn wrap(item: T, memsize: usize) -> Self {
Self {
item,
memsize_delta: MemsizeDelta::new(memsize as _),
}
}
pub fn increase_memory_usage(&self, memsize: usize) {
self.memsize_delta.add(memsize);
}
pub fn decrease_memory_usage(&self, memsize: usize) {
self.memsize_delta.sub(memsize);
}
pub fn memsize_delta(&self) -> isize {
self.memsize_delta.get()
}
pub fn get(&self) -> &T {
&self.item
}
pub fn get_mut(&mut self) -> &mut T {
&mut self.item
}
}
impl ManuallyTracked<()> {
pub fn new(memsize: usize) -> Self {
Self::wrap((), memsize)
}
}
impl Default for ManuallyTracked<()> {
fn default() -> Self {
Self::wrap((), 0)
}
}
impl<T: Clone> Clone for ManuallyTracked<T> {
fn clone(&self) -> Self {
Self {
item: self.item.clone(),
memsize_delta: self.memsize_delta.clone(),
}
}
}
impl<T: std::fmt::Debug> std::fmt::Debug for ManuallyTracked<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ManuallyTracked")
.field("item", &self.item)
.field("memsize_delta", &self.memsize_delta)
.finish()
}
}