turbo_tasks_malloc/
lib.rs1mod counter;
2mod memory_pressure;
3
4use std::{
5 alloc::{GlobalAlloc, Layout},
6 marker::PhantomData,
7 ops::{Add, AddAssign},
8};
9
10use self::counter::{add, flush, remove, update};
11
12#[derive(Default, Clone, Debug)]
13pub struct AllocationInfo {
14 pub allocations: usize,
15 pub deallocations: usize,
16 pub allocation_count: usize,
17 pub deallocation_count: usize,
18}
19
20impl AllocationInfo {
21 pub const ZERO: Self = Self {
22 allocations: 0,
23 deallocations: 0,
24 allocation_count: 0,
25 deallocation_count: 0,
26 };
27
28 pub fn is_empty(&self) -> bool {
29 self.allocations == 0
30 && self.deallocations == 0
31 && self.allocation_count == 0
32 && self.deallocation_count == 0
33 }
34
35 pub fn memory_usage(&self) -> usize {
36 self.allocations.saturating_sub(self.deallocations)
37 }
38}
39
40impl Add<Self> for AllocationInfo {
41 type Output = Self;
42
43 fn add(self, other: Self) -> Self {
44 Self {
45 allocations: self.allocations + other.allocations,
46 deallocations: self.deallocations + other.deallocations,
47 allocation_count: self.allocation_count + other.allocation_count,
48 deallocation_count: self.deallocation_count + other.deallocation_count,
49 }
50 }
51}
52
53impl AddAssign<Self> for AllocationInfo {
54 fn add_assign(&mut self, other: Self) {
55 self.allocations += other.allocations;
56 self.deallocations += other.deallocations;
57 self.allocation_count += other.allocation_count;
58 self.deallocation_count += other.deallocation_count;
59 }
60}
61
62#[derive(Default, Clone, Debug)]
63pub struct AllocationCounters {
64 pub allocations: usize,
65 pub deallocations: usize,
66 pub allocation_count: usize,
67 pub deallocation_count: usize,
68 _not_send: PhantomData<*mut ()>,
69}
70
71impl AllocationCounters {
72 const fn new() -> Self {
73 Self {
74 allocation_count: 0,
75 deallocation_count: 0,
76 allocations: 0,
77 deallocations: 0,
78 _not_send: PhantomData {},
79 }
80 }
81}
82
83pub struct TurboMalloc;
86
87impl TurboMalloc {
88 pub fn memory_usage() -> usize {
99 #[cfg(all(feature = "custom_allocator", not(target_family = "wasm")))]
100 {
101 let mut current_commit = 0usize;
105 unsafe {
107 libmimalloc_sys::mi_process_info(
108 std::ptr::null_mut(),
109 std::ptr::null_mut(),
110 std::ptr::null_mut(),
111 std::ptr::null_mut(),
112 std::ptr::null_mut(),
113 &mut current_commit,
114 std::ptr::null_mut(),
115 std::ptr::null_mut(),
116 );
117 }
118 current_commit
119 }
120 #[cfg(not(all(feature = "custom_allocator", not(target_family = "wasm"))))]
121 {
122 self::counter::get()
123 }
124 }
125
126 pub fn thread_stop() {
129 flush();
130 }
131
132 pub fn thread_park() {
133 Self::collect(false);
134 }
135
136 pub fn collect(force: bool) {
142 #[cfg(all(feature = "custom_allocator", not(target_family = "wasm")))]
143 unsafe {
144 libmimalloc_sys::mi_collect(force);
145 }
146 #[cfg(not(all(feature = "custom_allocator", not(target_family = "wasm"))))]
147 {
148 let _ = force;
149 }
150 }
151
152 pub fn allocation_counters() -> AllocationCounters {
153 self::counter::allocation_counters()
154 }
155
156 pub fn reset_allocation_counters(start: AllocationCounters) {
157 self::counter::reset_allocation_counters(start);
158 }
159
160 pub fn memory_pressure() -> Option<u8> {
174 memory_pressure::memory_pressure()
175 }
176}
177
178#[inline]
180fn base_alloc() -> &'static impl GlobalAlloc {
181 #[cfg(all(feature = "custom_allocator", not(target_family = "wasm")))]
182 return &mimalloc::MiMalloc;
183 #[cfg(not(all(feature = "custom_allocator", not(target_family = "wasm"))))]
184 return &std::alloc::System;
185}
186
187#[allow(unused_variables)]
188unsafe fn base_alloc_size(ptr: *const u8, layout: Layout) -> usize {
189 #[cfg(all(feature = "custom_allocator", not(target_family = "wasm")))]
190 return unsafe { mimalloc::MiMalloc.usable_size(ptr) };
191 #[cfg(not(all(feature = "custom_allocator", not(target_family = "wasm"))))]
192 return layout.size();
193}
194
195unsafe impl GlobalAlloc for TurboMalloc {
196 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
197 let ret = unsafe { base_alloc().alloc(layout) };
198 if !ret.is_null() {
199 let size = unsafe { base_alloc_size(ret, layout) };
200 add(size);
201 }
202 ret
203 }
204
205 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
206 let size = unsafe { base_alloc_size(ptr, layout) };
207 unsafe { base_alloc().dealloc(ptr, layout) };
208 remove(size);
209 }
210
211 unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
212 let ret = unsafe { base_alloc().alloc_zeroed(layout) };
213 if !ret.is_null() {
214 let size = unsafe { base_alloc_size(ret, layout) };
215 add(size);
216 }
217 ret
218 }
219
220 unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
221 let old_size = unsafe { base_alloc_size(ptr, layout) };
222 let ret = unsafe { base_alloc().realloc(ptr, layout, new_size) };
223 if !ret.is_null() {
224 let new_layout = unsafe { Layout::from_size_align_unchecked(new_size, layout.align()) };
227 let new_size = unsafe { base_alloc_size(ret, new_layout) };
228 update(old_size, new_size);
229 }
230 ret
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::TurboMalloc;
237
238 #[global_allocator]
242 static ALLOC: TurboMalloc = TurboMalloc;
243
244 #[test]
246 fn memory_usage_is_reported_and_tracks_a_large_allocation() {
247 let before = TurboMalloc::memory_usage();
248 assert!(before > 0, "a running process has live memory");
249
250 const SIZE: usize = 256 * 1024 * 1024;
253 let mut buffer = vec![0u8; SIZE];
254 for chunk in buffer.chunks_mut(4096) {
255 chunk[0] = 1;
256 }
257 std::hint::black_box(&buffer);
258
259 let after = TurboMalloc::memory_usage();
260 assert!(
261 after >= before + SIZE / 2,
262 "expected a rise of at least {} bytes, got {before} -> {after}",
263 SIZE / 2
264 );
265 drop(buffer);
266 }
267
268 #[test]
269 fn memory_pressure_is_in_range() {
270 let value = TurboMalloc::memory_pressure();
271
272 #[cfg(any(
274 all(target_os = "linux", not(target_family = "wasm")),
275 target_os = "macos",
276 windows,
277 ))]
278 let value = value.expect("memory_pressure() should return Some on this platform");
279
280 #[cfg(not(any(
282 all(target_os = "linux", not(target_family = "wasm")),
283 target_os = "macos",
284 windows,
285 )))]
286 let Some(value) = value else {
287 return;
288 };
289
290 assert!(
291 value <= 100,
292 "memory_pressure() returned {value}, expected a value in 0..=100"
293 );
294 }
295}