Skip to content

Commit b53ed38

Browse files
committed
add separate allocator for MiriMachine
1 parent af0c541 commit b53ed38

File tree

6 files changed

+325
-3
lines changed

6 files changed

+325
-3
lines changed

Cargo.lock

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ features = ['unprefixed_malloc_on_supported_platforms']
3939
libc = "0.2"
4040
libffi = "4.0.0"
4141
libloading = "0.8"
42+
nix = { version = "0.30.1", features = ["mman", "ptrace", "signal"] }
4243

4344
[target.'cfg(target_family = "windows")'.dependencies]
4445
windows-sys = { version = "0.59", features = [

src/alloc_bytes.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use std::{alloc, slice};
55
use rustc_abi::{Align, Size};
66
use rustc_middle::mir::interpret::AllocBytes;
77

8+
#[cfg(all(unix, any(target_arch = "x86", target_arch = "x86_64")))]
9+
use crate::discrete_alloc::MachineAlloc;
810
use crate::helpers::ToU64 as _;
911

1012
/// Allocation bytes that explicitly handle the layout of the data they're storing.
@@ -37,8 +39,14 @@ impl Drop for MiriAllocBytes {
3739
} else {
3840
self.layout
3941
};
42+
4043
// SAFETY: Invariant, `self.ptr` points to memory allocated with `self.layout`.
41-
unsafe { alloc::dealloc(self.ptr, alloc_layout) }
44+
unsafe {
45+
#[cfg(all(unix, any(target_arch = "x86", target_arch = "x86_64")))]
46+
MachineAlloc::dealloc(self.ptr, alloc_layout);
47+
#[cfg(not(all(unix, any(target_arch = "x86", target_arch = "x86_64"))))]
48+
alloc::dealloc(self.ptr, alloc_layout);
49+
}
4250
}
4351
}
4452

@@ -91,7 +99,16 @@ impl AllocBytes for MiriAllocBytes {
9199
let size = slice.len();
92100
let align = align.bytes();
93101
// SAFETY: `alloc_fn` will only be used with `size != 0`.
94-
let alloc_fn = |layout| unsafe { alloc::alloc(layout) };
102+
let alloc_fn = |layout| unsafe {
103+
#[cfg(all(unix, any(target_arch = "x86", target_arch = "x86_64")))]
104+
{
105+
MachineAlloc::alloc(layout)
106+
}
107+
#[cfg(not(all(unix, any(target_arch = "x86", target_arch = "x86_64"))))]
108+
{
109+
alloc::alloc(layout)
110+
}
111+
};
95112
let alloc_bytes = MiriAllocBytes::alloc_with(size.to_u64(), align, alloc_fn)
96113
.unwrap_or_else(|()| {
97114
panic!("Miri ran out of memory: cannot create allocation of {size} bytes")
@@ -106,7 +123,16 @@ impl AllocBytes for MiriAllocBytes {
106123
let size = size.bytes();
107124
let align = align.bytes();
108125
// SAFETY: `alloc_fn` will only be used with `size != 0`.
109-
let alloc_fn = |layout| unsafe { alloc::alloc_zeroed(layout) };
126+
let alloc_fn = |layout| unsafe {
127+
#[cfg(all(unix, any(target_arch = "x86", target_arch = "x86_64")))]
128+
{
129+
MachineAlloc::alloc_zeroed(layout)
130+
}
131+
#[cfg(not(all(unix, any(target_arch = "x86", target_arch = "x86_64"))))]
132+
{
133+
alloc::alloc_zeroed(layout)
134+
}
135+
};
110136
MiriAllocBytes::alloc_with(size, align, alloc_fn).ok()
111137
}
112138

src/discrete_alloc.rs

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
use std::alloc::{self, Layout};
2+
use std::sync;
3+
4+
use nix::sys::mman::ProtFlags;
5+
6+
use crate::helpers::ToU64;
7+
8+
static ALLOCATOR: sync::Mutex<MachineAlloc> = sync::Mutex::new(MachineAlloc::empty());
9+
10+
/// A distinct allocator for the `MiriMachine`, allowing us to manage its
11+
/// memory separately from that of Miri itself.
12+
#[derive(Debug)]
13+
pub struct MachineAlloc {
14+
pages: Vec<*mut u8>,
15+
huge_allocs: Vec<(*mut u8, usize)>,
16+
allocated: Vec<Box<[u8]>>,
17+
page_size: usize,
18+
enabled: bool,
19+
}
20+
21+
// SAFETY: We only point to heap-allocated data
22+
unsafe impl Send for MachineAlloc {}
23+
24+
impl MachineAlloc {
25+
// Allocation-related methods
26+
27+
/// Initializes the allocator with placeholder 4k pages.
28+
const fn empty() -> Self {
29+
Self {
30+
pages: Vec::new(),
31+
huge_allocs: Vec::new(),
32+
allocated: Vec::new(),
33+
page_size: 4096,
34+
enabled: false,
35+
}
36+
}
37+
38+
/// SAFETY: There must be no existing `MiriAllocBytes`
39+
pub unsafe fn enable() {
40+
let mut alloc = ALLOCATOR.lock().unwrap();
41+
alloc.enabled = true;
42+
// This needs to specifically be the system pagesize!
43+
alloc.page_size = unsafe {
44+
let ret = libc::sysconf(libc::_SC_PAGE_SIZE);
45+
if ret > 0 {
46+
ret.try_into().unwrap()
47+
} else {
48+
4096 // fallback
49+
}
50+
}
51+
}
52+
53+
/// Returns a vector of page addresses managed by the allocator.
54+
#[expect(dead_code)]
55+
pub fn pages() -> Vec<u64> {
56+
let alloc = ALLOCATOR.lock().unwrap();
57+
alloc.pages.clone().into_iter().map(|p| p.addr().to_u64()).collect()
58+
}
59+
60+
fn add_page(&mut self) {
61+
let page_layout =
62+
unsafe { Layout::from_size_align_unchecked(self.page_size, self.page_size) };
63+
let page_ptr = unsafe { alloc::alloc(page_layout) };
64+
if page_ptr.is_null() {
65+
panic!("aligned_alloc failed!!!")
66+
}
67+
self.allocated.push(vec![0u8; self.page_size / 8].into_boxed_slice());
68+
self.pages.push(page_ptr);
69+
}
70+
71+
#[inline]
72+
fn normalized_layout(layout: Layout) -> (usize, usize) {
73+
let align = if layout.align() < 8 { 8 } else { layout.align() };
74+
let size = layout.size().next_multiple_of(8);
75+
(size, align)
76+
}
77+
78+
#[inline]
79+
fn huge_normalized_layout(&self, layout: Layout) -> (usize, usize) {
80+
let size = layout.size().next_multiple_of(self.page_size);
81+
let align = std::cmp::max(layout.align(), self.page_size);
82+
(size, align)
83+
}
84+
85+
/// SAFETY: See alloc::alloc()
86+
#[inline]
87+
pub unsafe fn alloc(layout: Layout) -> *mut u8 {
88+
let mut alloc = ALLOCATOR.lock().unwrap();
89+
unsafe { if alloc.enabled { alloc.alloc_inner(layout) } else { alloc::alloc(layout) } }
90+
}
91+
92+
/// SAFETY: See alloc::alloc_zeroed()
93+
pub unsafe fn alloc_zeroed(layout: Layout) -> *mut u8 {
94+
let mut alloc = ALLOCATOR.lock().unwrap();
95+
if alloc.enabled {
96+
let ptr = unsafe { alloc.alloc_inner(layout) };
97+
if !ptr.is_null() {
98+
unsafe {
99+
ptr.write_bytes(0, layout.size());
100+
}
101+
}
102+
ptr
103+
} else {
104+
unsafe { alloc::alloc_zeroed(layout) }
105+
}
106+
}
107+
108+
/// SAFETY: See alloc::alloc()
109+
unsafe fn alloc_inner(&mut self, layout: Layout) -> *mut u8 {
110+
let (size, align) = MachineAlloc::normalized_layout(layout);
111+
112+
if align > self.page_size || size > self.page_size {
113+
unsafe { self.alloc_multi_page(layout) }
114+
} else {
115+
for (page, pinfo) in std::iter::zip(&mut self.pages, &mut self.allocated) {
116+
for idx in (0..self.page_size).step_by(align) {
117+
let idx_pinfo = idx / 8;
118+
let size_pinfo = size / 8;
119+
if pinfo.len() < idx_pinfo + size_pinfo {
120+
break;
121+
}
122+
if pinfo[idx_pinfo..idx_pinfo + size_pinfo].iter().all(|v| *v == 0) {
123+
pinfo[idx_pinfo..idx_pinfo + size_pinfo].fill(255);
124+
unsafe {
125+
let ret = page.offset(idx.try_into().unwrap());
126+
if ret.addr() >= page.addr() + self.page_size {
127+
panic!("Returing {} from page {}", ret.addr(), page.addr());
128+
}
129+
return page.offset(idx.try_into().unwrap());
130+
}
131+
}
132+
}
133+
}
134+
135+
// We get here only if there's no space in our existing pages
136+
self.add_page();
137+
unsafe { self.alloc_inner(layout) }
138+
}
139+
}
140+
141+
/// SAFETY: See alloc::alloc()
142+
unsafe fn alloc_multi_page(&mut self, layout: Layout) -> *mut u8 {
143+
let (size, align) = self.huge_normalized_layout(layout);
144+
145+
let layout = unsafe { Layout::from_size_align_unchecked(size, align) };
146+
let ret = unsafe { alloc::alloc(layout) };
147+
self.huge_allocs.push((ret, size));
148+
ret
149+
}
150+
151+
/// Safety: see alloc::dealloc()
152+
pub unsafe fn dealloc(ptr: *mut u8, layout: Layout) {
153+
let mut alloc = ALLOCATOR.lock().unwrap();
154+
unsafe {
155+
if alloc.enabled {
156+
alloc.dealloc_inner(ptr, layout);
157+
} else {
158+
alloc::dealloc(ptr, layout);
159+
}
160+
}
161+
}
162+
163+
/// SAFETY: See alloc::dealloc()
164+
unsafe fn dealloc_inner(&mut self, ptr: *mut u8, layout: Layout) {
165+
let (size, align) = MachineAlloc::normalized_layout(layout);
166+
167+
if size == 0 || ptr.is_null() {
168+
return;
169+
}
170+
171+
let ptr_idx = ptr.addr() % self.page_size;
172+
let page_addr = ptr.addr() - ptr_idx;
173+
174+
if align > self.page_size || size > self.page_size {
175+
unsafe {
176+
self.dealloc_multi_page(ptr, layout);
177+
}
178+
} else {
179+
let pinfo = std::iter::zip(&mut self.pages, &mut self.allocated)
180+
.find(|(page, _)| page.addr() == page_addr);
181+
let Some((_, pinfo)) = pinfo else {
182+
panic!("Freeing in an unallocated page: {ptr:?}\nHolding pages {:?}", self.pages)
183+
};
184+
let ptr_idx_pinfo = ptr_idx / 8;
185+
let size_pinfo = size / 8;
186+
// Everything is always aligned to at least 8 bytes so this is ok
187+
pinfo[ptr_idx_pinfo..ptr_idx_pinfo + size_pinfo].fill(0);
188+
}
189+
190+
let mut free = vec![];
191+
let page_layout =
192+
unsafe { Layout::from_size_align_unchecked(self.page_size, self.page_size) };
193+
for (idx, pinfo) in self.allocated.iter().enumerate() {
194+
if pinfo.iter().all(|p| *p == 0) {
195+
free.push(idx);
196+
}
197+
}
198+
free.reverse();
199+
for idx in free {
200+
let _ = self.allocated.remove(idx);
201+
unsafe {
202+
alloc::dealloc(self.pages.remove(idx), page_layout);
203+
}
204+
}
205+
}
206+
207+
/// SAFETY: See alloc::dealloc()
208+
unsafe fn dealloc_multi_page(&mut self, ptr: *mut u8, layout: Layout) {
209+
let (idx, _) = self
210+
.huge_allocs
211+
.iter()
212+
.enumerate()
213+
.find(|pg| ptr.addr() == pg.1.0.addr())
214+
.expect("Freeing unallocated pages");
215+
let ptr = self.huge_allocs.remove(idx).0;
216+
let (size, align) = self.huge_normalized_layout(layout);
217+
unsafe {
218+
let layout = Layout::from_size_align_unchecked(size, align);
219+
alloc::dealloc(ptr, layout);
220+
}
221+
}
222+
223+
// Protection-related methods
224+
225+
/// Protects all owned memory, preventing accesses.
226+
///
227+
/// SAFETY: Accessing memory after this point will result in a segfault
228+
/// unless it is first unprotected.
229+
#[expect(dead_code)]
230+
pub unsafe fn prepare_ffi() -> Result<(), nix::errno::Errno> {
231+
let mut alloc = ALLOCATOR.lock().unwrap();
232+
unsafe {
233+
alloc.mprotect(ProtFlags::PROT_NONE)?;
234+
}
235+
Ok(())
236+
}
237+
238+
/// Deprotects all owned memory by setting it to RW. Erroring here is very
239+
/// likely unrecoverable, so it may panic if applying those permissions
240+
/// fails.
241+
#[expect(dead_code)]
242+
pub fn unprep_ffi() {
243+
let mut alloc = ALLOCATOR.lock().unwrap();
244+
let default_flags = ProtFlags::PROT_READ | ProtFlags::PROT_WRITE;
245+
unsafe {
246+
alloc.mprotect(default_flags).unwrap();
247+
}
248+
}
249+
250+
/// Applies `prot` to every page managed by the allocator.
251+
///
252+
/// SAFETY: Accessing memory in violation of the protection flags will
253+
/// trigger a segfault.
254+
unsafe fn mprotect(&mut self, prot: ProtFlags) -> Result<(), nix::errno::Errno> {
255+
for &pg in &self.pages {
256+
unsafe {
257+
// We already know only non-null ptrs are pushed to self.pages
258+
let addr: std::ptr::NonNull<std::ffi::c_void> =
259+
std::ptr::NonNull::new_unchecked(pg.cast());
260+
nix::sys::mman::mprotect(addr, self.page_size, prot)?;
261+
}
262+
}
263+
for &(hpg, size) in &self.huge_allocs {
264+
unsafe {
265+
let addr = std::ptr::NonNull::new_unchecked(hpg.cast());
266+
nix::sys::mman::mprotect(addr, size, prot)?;
267+
}
268+
}
269+
Ok(())
270+
}
271+
}

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#![feature(nonzero_ops)]
1212
#![feature(strict_overflow_ops)]
1313
#![feature(pointer_is_aligned_to)]
14+
#![feature(ptr_metadata)]
1415
#![feature(unqualified_local_imports)]
1516
#![feature(derive_coerce_pointee)]
1617
#![feature(arbitrary_self_types)]
@@ -75,6 +76,8 @@ mod borrow_tracker;
7576
mod clock;
7677
mod concurrency;
7778
mod diagnostics;
79+
#[cfg(all(unix, any(target_arch = "x86", target_arch = "x86_64")))]
80+
mod discrete_alloc;
7881
mod eval;
7982
mod helpers;
8083
mod intrinsics;

0 commit comments

Comments
 (0)