Skip to content

Commit adc26a3

Browse files
committed
Auto merge of #1885 - DrMeepster:global_allocator, r=RalfJung
add support for `#[global_allocator]` This PR adds support for custom global allocators. Unfortunately, the code given in #1207 still causes errors when used with box. I believe this is because Box is special-cased in miri and stacked borrows.
2 parents 7decf8c + e6a27a6 commit adc26a3

File tree

8 files changed

+157
-40
lines changed

8 files changed

+157
-40
lines changed

src/shims/foreign_items.rs

+85-36
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::{
66
use log::trace;
77

88
use rustc_apfloat::Float;
9+
use rustc_ast::expand::allocator::AllocatorKind;
910
use rustc_hir::{
1011
def::DefKind,
1112
def_id::{CrateNum, DefId, LOCAL_CRATE},
@@ -27,11 +28,13 @@ use super::backtrace::EvalContextExt as _;
2728
use crate::*;
2829

2930
/// Returned by `emulate_foreign_item_by_name`.
30-
pub enum EmulateByNameResult {
31+
pub enum EmulateByNameResult<'mir, 'tcx> {
3132
/// The caller is expected to jump to the return block.
3233
NeedsJumping,
3334
/// Jumping has already been taken care of.
3435
AlreadyJumped,
36+
/// A MIR body has been found for the function
37+
MirBody(&'mir mir::Body<'tcx>),
3538
/// The item is not supported.
3639
NotSupported,
3740
}
@@ -281,6 +284,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
281284
this.go_to_block(ret);
282285
}
283286
EmulateByNameResult::AlreadyJumped => (),
287+
EmulateByNameResult::MirBody(mir) => return Ok(Some(mir)),
284288
EmulateByNameResult::NotSupported => {
285289
if let Some(body) = this.lookup_exported_symbol(link_name)? {
286290
return Ok(Some(body));
@@ -294,6 +298,36 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
294298
Ok(None)
295299
}
296300

301+
/// Emulates calling the internal __rust_* allocator functions
302+
fn emulate_allocator(
303+
&mut self,
304+
symbol: Symbol,
305+
default: impl FnOnce(&mut MiriEvalContext<'mir, 'tcx>) -> InterpResult<'tcx>,
306+
) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
307+
let this = self.eval_context_mut();
308+
309+
let allocator_kind = if let Some(allocator_kind) = this.tcx.allocator_kind(()) {
310+
allocator_kind
311+
} else {
312+
// in real code, this symbol does not exist without an allocator
313+
return Ok(EmulateByNameResult::NotSupported);
314+
};
315+
316+
match allocator_kind {
317+
AllocatorKind::Global => {
318+
let body = this
319+
.lookup_exported_symbol(symbol)?
320+
.expect("symbol should be present if there is a global allocator");
321+
322+
Ok(EmulateByNameResult::MirBody(body))
323+
}
324+
AllocatorKind::Default => {
325+
default(this)?;
326+
Ok(EmulateByNameResult::NeedsJumping)
327+
}
328+
}
329+
}
330+
297331
/// Emulates calling a foreign item using its name.
298332
fn emulate_foreign_item_by_name(
299333
&mut self,
@@ -302,7 +336,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
302336
args: &[OpTy<'tcx, Tag>],
303337
dest: &PlaceTy<'tcx, Tag>,
304338
ret: mir::BasicBlock,
305-
) -> InterpResult<'tcx, EmulateByNameResult> {
339+
) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
306340
let this = self.eval_context_mut();
307341

308342
// Here we dispatch all the shims for foreign functions. If you have a platform specific
@@ -362,63 +396,78 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
362396
}
363397

364398
// Rust allocation
365-
// (Usually these would be forwarded to to `#[global_allocator]`; we instead implement a generic
366-
// allocation that also checks that all conditions are met, such as not permitting zero-sized allocations.)
367399
"__rust_alloc" => {
368400
let &[ref size, ref align] = this.check_shim(abi, Abi::Rust, link_name, args)?;
369401
let size = this.read_scalar(size)?.to_machine_usize(this)?;
370402
let align = this.read_scalar(align)?.to_machine_usize(this)?;
371-
Self::check_alloc_request(size, align)?;
372-
let ptr = this.memory.allocate(
373-
Size::from_bytes(size),
374-
Align::from_bytes(align).unwrap(),
375-
MiriMemoryKind::Rust.into(),
376-
)?;
377-
this.write_pointer(ptr, dest)?;
403+
404+
return this.emulate_allocator(Symbol::intern("__rg_alloc"), |this| {
405+
Self::check_alloc_request(size, align)?;
406+
407+
let ptr = this.memory.allocate(
408+
Size::from_bytes(size),
409+
Align::from_bytes(align).unwrap(),
410+
MiriMemoryKind::Rust.into(),
411+
)?;
412+
413+
this.write_pointer(ptr, dest)
414+
});
378415
}
379416
"__rust_alloc_zeroed" => {
380417
let &[ref size, ref align] = this.check_shim(abi, Abi::Rust, link_name, args)?;
381418
let size = this.read_scalar(size)?.to_machine_usize(this)?;
382419
let align = this.read_scalar(align)?.to_machine_usize(this)?;
383-
Self::check_alloc_request(size, align)?;
384-
let ptr = this.memory.allocate(
385-
Size::from_bytes(size),
386-
Align::from_bytes(align).unwrap(),
387-
MiriMemoryKind::Rust.into(),
388-
)?;
389-
// We just allocated this, the access is definitely in-bounds.
390-
this.memory.write_bytes(ptr.into(), iter::repeat(0u8).take(usize::try_from(size).unwrap())).unwrap();
391-
this.write_pointer(ptr, dest)?;
420+
421+
return this.emulate_allocator(Symbol::intern("__rg_alloc_zeroed"), |this| {
422+
Self::check_alloc_request(size, align)?;
423+
424+
let ptr = this.memory.allocate(
425+
Size::from_bytes(size),
426+
Align::from_bytes(align).unwrap(),
427+
MiriMemoryKind::Rust.into(),
428+
)?;
429+
430+
// We just allocated this, the access is definitely in-bounds.
431+
this.memory.write_bytes(ptr.into(), iter::repeat(0u8).take(usize::try_from(size).unwrap())).unwrap();
432+
this.write_pointer(ptr, dest)
433+
});
392434
}
393435
"__rust_dealloc" => {
394436
let &[ref ptr, ref old_size, ref align] = this.check_shim(abi, Abi::Rust, link_name, args)?;
395437
let ptr = this.read_pointer(ptr)?;
396438
let old_size = this.read_scalar(old_size)?.to_machine_usize(this)?;
397439
let align = this.read_scalar(align)?.to_machine_usize(this)?;
398-
// No need to check old_size/align; we anyway check that they match the allocation.
399-
this.memory.deallocate(
400-
ptr,
401-
Some((Size::from_bytes(old_size), Align::from_bytes(align).unwrap())),
402-
MiriMemoryKind::Rust.into(),
403-
)?;
440+
441+
return this.emulate_allocator(Symbol::intern("__rg_dealloc"), |this| {
442+
// No need to check old_size/align; we anyway check that they match the allocation.
443+
this.memory.deallocate(
444+
ptr,
445+
Some((Size::from_bytes(old_size), Align::from_bytes(align).unwrap())),
446+
MiriMemoryKind::Rust.into(),
447+
)
448+
});
404449
}
405450
"__rust_realloc" => {
406451
let &[ref ptr, ref old_size, ref align, ref new_size] = this.check_shim(abi, Abi::Rust, link_name, args)?;
407452
let ptr = this.read_pointer(ptr)?;
408453
let old_size = this.read_scalar(old_size)?.to_machine_usize(this)?;
409454
let align = this.read_scalar(align)?.to_machine_usize(this)?;
410455
let new_size = this.read_scalar(new_size)?.to_machine_usize(this)?;
411-
Self::check_alloc_request(new_size, align)?;
412456
// No need to check old_size; we anyway check that they match the allocation.
413-
let align = Align::from_bytes(align).unwrap();
414-
let new_ptr = this.memory.reallocate(
415-
ptr,
416-
Some((Size::from_bytes(old_size), align)),
417-
Size::from_bytes(new_size),
418-
align,
419-
MiriMemoryKind::Rust.into(),
420-
)?;
421-
this.write_pointer(new_ptr, dest)?;
457+
458+
return this.emulate_allocator(Symbol::intern("__rg_realloc"), |this| {
459+
Self::check_alloc_request(new_size, align)?;
460+
461+
let align = Align::from_bytes(align).unwrap();
462+
let new_ptr = this.memory.reallocate(
463+
ptr,
464+
Some((Size::from_bytes(old_size), align)),
465+
Size::from_bytes(new_size),
466+
align,
467+
MiriMemoryKind::Rust.into(),
468+
)?;
469+
this.write_pointer(new_ptr, dest)
470+
});
422471
}
423472

424473
// C memory handling functions

src/shims/posix/foreign_items.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
2121
args: &[OpTy<'tcx, Tag>],
2222
dest: &PlaceTy<'tcx, Tag>,
2323
ret: mir::BasicBlock,
24-
) -> InterpResult<'tcx, EmulateByNameResult> {
24+
) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
2525
let this = self.eval_context_mut();
2626

2727
match &*link_name.as_str() {

src/shims/posix/linux/foreign_items.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
1818
args: &[OpTy<'tcx, Tag>],
1919
dest: &PlaceTy<'tcx, Tag>,
2020
_ret: mir::BasicBlock,
21-
) -> InterpResult<'tcx, EmulateByNameResult> {
21+
) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
2222
let this = self.eval_context_mut();
2323

2424
match &*link_name.as_str() {

src/shims/posix/macos/foreign_items.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
1616
args: &[OpTy<'tcx, Tag>],
1717
dest: &PlaceTy<'tcx, Tag>,
1818
_ret: mir::BasicBlock,
19-
) -> InterpResult<'tcx, EmulateByNameResult> {
19+
) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
2020
let this = self.eval_context_mut();
2121

2222
match &*link_name.as_str() {

src/shims/windows/foreign_items.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
1818
args: &[OpTy<'tcx, Tag>],
1919
dest: &PlaceTy<'tcx, Tag>,
2020
_ret: mir::BasicBlock,
21-
) -> InterpResult<'tcx, EmulateByNameResult> {
21+
) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
2222
let this = self.eval_context_mut();
2323

2424
// Windows API stubs.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Make sure we pretend the allocation symbols don't exist when there is no allocator
2+
3+
#![feature(lang_items, start)]
4+
#![no_std]
5+
6+
extern "Rust" {
7+
fn __rust_alloc(size: usize, align: usize) -> *mut u8;
8+
}
9+
10+
#[start]
11+
fn start(_: isize, _: *const *const u8) -> isize {
12+
unsafe {
13+
__rust_alloc(1, 1); //~ERROR: unsupported operation: can't call foreign function: __rust_alloc
14+
}
15+
16+
0
17+
}
18+
19+
#[panic_handler]
20+
fn panic_handler(_: &core::panic::PanicInfo) -> ! {
21+
loop {}
22+
}
23+
24+
#[lang = "eh_personality"]
25+
fn eh_personality() {}

tests/run-pass/global_allocator.rs

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#![feature(allocator_api, slice_ptr_get)]
2+
3+
use std::alloc::{Allocator as _, Global, GlobalAlloc, Layout, System};
4+
5+
#[global_allocator]
6+
static ALLOCATOR: Allocator = Allocator;
7+
8+
struct Allocator;
9+
10+
unsafe impl GlobalAlloc for Allocator {
11+
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
12+
// use specific size to avoid getting triggered by rt
13+
if layout.size() == 123 {
14+
println!("Allocated!")
15+
}
16+
17+
System.alloc(layout)
18+
}
19+
20+
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
21+
if layout.size() == 123 {
22+
println!("Dellocated!")
23+
}
24+
25+
System.dealloc(ptr, layout)
26+
}
27+
}
28+
29+
fn main() {
30+
// Only okay because we explicitly set a global allocator that uses the system allocator!
31+
let l = Layout::from_size_align(123, 1).unwrap();
32+
let ptr = Global.allocate(l).unwrap().as_non_null_ptr(); // allocating with Global...
33+
unsafe {
34+
System.deallocate(ptr, l);
35+
} // ... and deallocating with System.
36+
37+
let ptr = System.allocate(l).unwrap().as_non_null_ptr(); // allocating with System...
38+
unsafe {
39+
Global.deallocate(ptr, l);
40+
} // ... and deallocating with Global.
41+
}
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Allocated!
2+
Dellocated!

0 commit comments

Comments
 (0)