fdu_core/counters/
alloc.rs1#![allow(unsafe_code)]
4
5use std::alloc::{GlobalAlloc, Layout, System};
6
7#[derive(Clone, Copy)]
12pub struct Sinks {
13 alloc: fn(u64),
14 realloc: fn(u64),
15 dealloc: fn(),
16}
17
18impl Sinks {
19 #[must_use]
21 pub const fn noop() -> Self {
22 Self { alloc: |_| {}, realloc: |_| {}, dealloc: || {} }
23 }
24
25 #[must_use]
47 pub const unsafe fn new(alloc: fn(u64), realloc: fn(u64), dealloc: fn()) -> Self {
48 Self { alloc, realloc, dealloc }
49 }
50}
51
52pub struct CountingAlloc<A> {
54 inner: A,
55 sinks: Sinks,
56}
57
58impl CountingAlloc<System> {
59 #[must_use]
61 pub const fn system(sinks: Sinks) -> Self {
62 Self { inner: System, sinks }
63 }
64}
65
66impl<A> CountingAlloc<A> {
67 #[must_use]
69 pub const fn wrapping(inner: A, sinks: Sinks) -> Self {
70 Self { inner, sinks }
71 }
72}
73
74pub(super) const fn fdu_sinks() -> Sinks {
76 unsafe { Sinks::new(super::record_alloc, super::record_realloc, super::record_dealloc) }
80}
81
82unsafe impl<A: GlobalAlloc> GlobalAlloc for CountingAlloc<A> {
86 #[inline]
87 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
88 (self.sinks.alloc)(u64::try_from(layout.size()).unwrap_or(u64::MAX));
89 unsafe { self.inner.alloc(layout) }
91 }
92
93 #[inline]
94 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
95 (self.sinks.dealloc)();
96 unsafe { self.inner.dealloc(ptr, layout) }
99 }
100
101 #[inline]
102 unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
103 let growth = u64::try_from(new_size.saturating_sub(layout.size())).unwrap_or(u64::MAX);
104 (self.sinks.realloc)(growth);
105 unsafe { self.inner.realloc(ptr, layout, new_size) }
107 }
108
109 #[inline]
110 unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
111 (self.sinks.alloc)(u64::try_from(layout.size()).unwrap_or(u64::MAX));
112 unsafe { self.inner.alloc_zeroed(layout) }
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use std::sync::atomic::{AtomicU64, Ordering};
120
121 use super::*;
122
123 static ALLOCS: AtomicU64 = AtomicU64::new(0);
124 static BYTES: AtomicU64 = AtomicU64::new(0);
125 static FREES: AtomicU64 = AtomicU64::new(0);
126
127 fn alloc(size: u64) {
128 ALLOCS.fetch_add(1, Ordering::Relaxed);
129 BYTES.fetch_add(size, Ordering::Relaxed);
130 }
131
132 fn realloc(growth: u64) {
133 BYTES.fetch_add(growth, Ordering::Relaxed);
134 }
135
136 fn dealloc() {
137 FREES.fetch_add(1, Ordering::Relaxed);
138 }
139
140 fn test_sinks() -> Sinks {
141 unsafe { Sinks::new(alloc, realloc, dealloc) }
144 }
145
146 #[test]
147 fn forwards_memory_intact_and_counts_it() {
148 let _serial = crate::counters::test_serial();
149 let allocator = CountingAlloc::system(test_sinks());
150 let layout = Layout::from_size_align(64, 8).expect("valid layout");
151 ALLOCS.store(0, Ordering::Relaxed);
152 FREES.store(0, Ordering::Relaxed);
153 BYTES.store(0, Ordering::Relaxed);
154
155 unsafe {
158 let pointer = allocator.alloc(layout);
159 assert!(!pointer.is_null());
160 pointer.write_bytes(0xAB, 64);
161 assert_eq!(pointer.read(), 0xAB);
162 allocator.dealloc(pointer, layout);
163 }
164
165 assert_eq!(ALLOCS.load(Ordering::Relaxed), 1);
166 assert_eq!(FREES.load(Ordering::Relaxed), 1);
167 assert_eq!(BYTES.load(Ordering::Relaxed), 64);
168 }
169
170 #[test]
171 fn noop_sinks_are_const_constructible() {
172 const ALLOCATOR: CountingAlloc<System> = CountingAlloc::system(Sinks::noop());
173 let layout = Layout::from_size_align(16, 8).expect("valid layout");
174 unsafe {
176 let pointer = ALLOCATOR.alloc(layout);
177 assert!(!pointer.is_null());
178 ALLOCATOR.dealloc(pointer, layout);
179 }
180 }
181}