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