Skip to main content

servo_allocator/
lib.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Selecting the default global allocator for Servo, and exposing common
6//! allocator introspection APIs for memory profiling.
7
8use std::os::raw::c_void;
9
10#[cfg(not(feature = "allocation-tracking"))]
11#[global_allocator]
12static ALLOC: Allocator = Allocator;
13
14#[cfg(feature = "allocation-tracking")]
15#[global_allocator]
16static ALLOC: crate::tracking::AccountingAlloc<Allocator> =
17    crate::tracking::AccountingAlloc::with_allocator(Allocator);
18
19#[cfg(feature = "allocation-tracking")]
20mod tracking;
21
22pub fn is_tracking_unmeasured() -> bool {
23    cfg!(feature = "allocation-tracking")
24}
25
26pub fn dump_unmeasured(_writer: impl std::io::Write) {
27    #[cfg(feature = "allocation-tracking")]
28    ALLOC.dump_unmeasured_allocations(_writer);
29}
30
31pub struct HeapReport {
32    pub path: &'static str,
33    pub size: Option<usize>,
34}
35
36pub use crate::platform::*;
37
38type EnclosingSizeFn = unsafe extern "C" fn(*const c_void) -> usize;
39
40/// # Safety
41/// No restrictions. The passed pointer is never dereferenced.
42/// This function is only marked unsafe because the MallocSizeOfOps APIs
43/// requires an unsafe function pointer.
44#[cfg(feature = "allocation-tracking")]
45unsafe extern "C" fn enclosing_size_impl(ptr: *const c_void) -> usize {
46    let (adjusted, size) = crate::ALLOC.enclosing_size(ptr);
47    if size != 0 {
48        crate::ALLOC.note_allocation(adjusted, size);
49    }
50    size
51}
52
53#[expect(non_upper_case_globals)]
54#[cfg(feature = "allocation-tracking")]
55pub static enclosing_size: Option<EnclosingSizeFn> = Some(crate::enclosing_size_impl);
56
57#[expect(non_upper_case_globals)]
58#[cfg(not(feature = "allocation-tracking"))]
59pub static enclosing_size: Option<EnclosingSizeFn> = None;
60
61#[cfg(not(any(windows, feature = "use-system-allocator", target_env = "ohos")))]
62mod platform {
63    use std::ffi::CStr;
64    use std::mem::size_of_val;
65    use std::os::raw::c_void;
66    use std::ptr;
67
68    use tikv_jemalloc_sys::mallctl;
69    pub use tikv_jemallocator::Jemalloc as Allocator;
70
71    pub fn heap_reports() -> Vec<crate::HeapReport> {
72        vec![
73            crate::HeapReport {
74                path: "jemalloc-heap-allocated",
75                size: jemalloc_stat(c"stats.allocated"),
76            },
77            crate::HeapReport {
78                path: "jemalloc-heap-active",
79                size: jemalloc_stat(c"stats.active"),
80            },
81            crate::HeapReport {
82                path: "jemalloc-heap-mapped",
83                size: jemalloc_stat(c"stats.mapped"),
84            },
85        ]
86    }
87
88    fn jemalloc_stat(value_name: &CStr) -> Option<usize> {
89        // Before we request the measurement of interest, we first send an "epoch"
90        // request. Without that jemalloc gives cached statistics(!) which can be
91        // highly inaccurate.
92        let epoch_c_name = c"epoch";
93        let mut epoch: u64 = 0;
94        let epoch_ptr = &raw mut epoch;
95        let mut epoch_len = size_of_val(&epoch);
96
97        let mut value: usize = 0;
98        let value_ptr = &raw mut value;
99        let mut value_len = size_of_val(&value);
100
101        // Using the same values for the `old` and `new` parameters is enough
102        // to get the statistics updated.
103        let rv = unsafe {
104            mallctl(
105                epoch_c_name.as_ptr(),
106                epoch_ptr.cast(),
107                &mut epoch_len,
108                epoch_ptr.cast(),
109                epoch_len,
110            )
111        };
112        if rv != 0 {
113            return None;
114        }
115
116        let rv = unsafe {
117            mallctl(
118                value_name.as_ptr(),
119                value_ptr.cast(),
120                &mut value_len,
121                ptr::null_mut(),
122                0,
123            )
124        };
125        if rv != 0 {
126            return None;
127        }
128
129        Some(value)
130    }
131
132    /// Get the size of a heap block.
133    ///
134    /// # Safety
135    ///
136    /// Passing a non-heap allocated pointer to this function results in undefined behavior.
137    pub unsafe extern "C" fn usable_size(ptr: *const c_void) -> usize {
138        let size = unsafe { tikv_jemallocator::usable_size(ptr) };
139        #[cfg(feature = "allocation-tracking")]
140        crate::ALLOC.note_allocation(ptr, size);
141        size
142    }
143
144    /// Memory allocation APIs compatible with libc
145    pub mod libc_compat {
146        pub use tikv_jemalloc_sys::{free, malloc, realloc};
147    }
148}
149
150#[cfg(all(
151    not(windows),
152    any(feature = "use-system-allocator", target_env = "ohos")
153))]
154mod platform {
155    pub use std::alloc::System as Allocator;
156    use std::os::raw::c_void;
157
158    /// Get the size of a heap block.
159    ///
160    /// # Safety
161    ///
162    /// Passing a non-heap allocated pointer to this function results in undefined behavior.
163    pub unsafe extern "C" fn usable_size(ptr: *const c_void) -> usize {
164        #[cfg(target_vendor = "apple")]
165        unsafe {
166            let size = libc::malloc_size(ptr);
167            #[cfg(feature = "allocation-tracking")]
168            crate::ALLOC.note_allocation(ptr, size);
169            size
170        }
171
172        #[cfg(not(target_vendor = "apple"))]
173        unsafe {
174            let size = libc::malloc_usable_size(ptr as *mut _);
175            #[cfg(feature = "allocation-tracking")]
176            crate::ALLOC.note_allocation(ptr, size);
177            size
178        }
179    }
180
181    pub mod libc_compat {
182        pub use libc::{free, malloc, realloc};
183    }
184
185    pub fn heap_reports() -> Vec<crate::HeapReport> {
186        Vec::new()
187    }
188}
189
190#[cfg(windows)]
191mod platform {
192    pub use std::alloc::System as Allocator;
193    use std::os::raw::c_void;
194
195    use windows_sys::Win32::Foundation::FALSE;
196    use windows_sys::Win32::System::Memory::{GetProcessHeap, HeapSize, HeapValidate};
197
198    /// Get the size of a heap block.
199    ///
200    /// # Safety
201    ///
202    /// Passing a non-heap allocated pointer to this function results in undefined behavior.
203    pub unsafe extern "C" fn usable_size(mut ptr: *const c_void) -> usize {
204        unsafe {
205            let heap = GetProcessHeap();
206
207            if HeapValidate(heap, 0, ptr) == FALSE {
208                ptr = *(ptr as *const *const c_void).offset(-1)
209            }
210
211            let size = HeapSize(heap, 0, ptr) as usize;
212            #[cfg(feature = "allocation-tracking")]
213            crate::ALLOC.note_allocation(ptr, size);
214            size
215        }
216    }
217
218    pub fn heap_reports() -> Vec<crate::HeapReport> {
219        Vec::new()
220    }
221}