Skip to content

Commit c2562d0

Browse files
committed
move allocator shim logic into its own file
1 parent a22e355 commit c2562d0

File tree

6 files changed

+178
-157
lines changed

6 files changed

+178
-157
lines changed

src/shims/alloc.rs

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
use std::iter;
2+
3+
use rustc_ast::expand::allocator::AllocatorKind;
4+
use rustc_target::abi::{Align, Size};
5+
6+
use crate::*;
7+
use shims::foreign_items::EmulateForeignItemResult;
8+
9+
/// Check some basic requirements for this allocation request:
10+
/// non-zero size, power-of-two alignment.
11+
pub(super) fn check_alloc_request<'tcx>(size: u64, align: u64) -> InterpResult<'tcx> {
12+
if size == 0 {
13+
throw_ub_format!("creating allocation with size 0");
14+
}
15+
if !align.is_power_of_two() {
16+
throw_ub_format!("creating allocation with non-power-of-two alignment {}", align);
17+
}
18+
Ok(())
19+
}
20+
21+
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
22+
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
23+
/// Returns the minimum alignment for the target architecture for allocations of the given size.
24+
fn min_align(&self, size: u64, kind: MiriMemoryKind) -> Align {
25+
let this = self.eval_context_ref();
26+
// List taken from `library/std/src/sys/pal/common/alloc.rs`.
27+
// This list should be kept in sync with the one from libstd.
28+
let min_align = match this.tcx.sess.target.arch.as_ref() {
29+
"x86" | "arm" | "mips" | "mips32r6" | "powerpc" | "powerpc64" | "wasm32" => 8,
30+
"x86_64" | "aarch64" | "mips64" | "mips64r6" | "s390x" | "sparc64" | "loongarch64" =>
31+
16,
32+
arch => bug!("unsupported target architecture for malloc: `{}`", arch),
33+
};
34+
// Windows always aligns, even small allocations.
35+
// Source: <https://support.microsoft.com/en-us/help/286470/how-to-use-pageheap-exe-in-windows-xp-windows-2000-and-windows-server>
36+
// But jemalloc does not, so for the C heap we only align if the allocation is sufficiently big.
37+
if kind == MiriMemoryKind::WinHeap || size >= min_align {
38+
return Align::from_bytes(min_align).unwrap();
39+
}
40+
// We have `size < min_align`. Round `size` *down* to the next power of two and use that.
41+
fn prev_power_of_two(x: u64) -> u64 {
42+
let next_pow2 = x.next_power_of_two();
43+
if next_pow2 == x {
44+
// x *is* a power of two, just use that.
45+
x
46+
} else {
47+
// x is between two powers, so next = 2*prev.
48+
next_pow2 / 2
49+
}
50+
}
51+
Align::from_bytes(prev_power_of_two(size)).unwrap()
52+
}
53+
54+
/// Emulates calling the internal __rust_* allocator functions
55+
fn emulate_allocator(
56+
&mut self,
57+
default: impl FnOnce(&mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx>,
58+
) -> InterpResult<'tcx, EmulateForeignItemResult> {
59+
let this = self.eval_context_mut();
60+
61+
let Some(allocator_kind) = this.tcx.allocator_kind(()) else {
62+
// in real code, this symbol does not exist without an allocator
63+
return Ok(EmulateForeignItemResult::NotSupported);
64+
};
65+
66+
match allocator_kind {
67+
AllocatorKind::Global => {
68+
// When `#[global_allocator]` is used, `__rust_*` is defined by the macro expansion
69+
// of this attribute. As such we have to call an exported Rust function,
70+
// and not execute any Miri shim. Somewhat unintuitively doing so is done
71+
// by returning `NotSupported`, which triggers the `lookup_exported_symbol`
72+
// fallback case in `emulate_foreign_item`.
73+
return Ok(EmulateForeignItemResult::NotSupported);
74+
}
75+
AllocatorKind::Default => {
76+
default(this)?;
77+
Ok(EmulateForeignItemResult::NeedsJumping)
78+
}
79+
}
80+
}
81+
82+
fn malloc(
83+
&mut self,
84+
size: u64,
85+
zero_init: bool,
86+
kind: MiriMemoryKind,
87+
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
88+
let this = self.eval_context_mut();
89+
if size == 0 {
90+
Ok(Pointer::null())
91+
} else {
92+
let align = this.min_align(size, kind);
93+
let ptr = this.allocate_ptr(Size::from_bytes(size), align, kind.into())?;
94+
if zero_init {
95+
// We just allocated this, the access is definitely in-bounds and fits into our address space.
96+
this.write_bytes_ptr(
97+
ptr.into(),
98+
iter::repeat(0u8).take(usize::try_from(size).unwrap()),
99+
)
100+
.unwrap();
101+
}
102+
Ok(ptr.into())
103+
}
104+
}
105+
106+
fn free(
107+
&mut self,
108+
ptr: Pointer<Option<Provenance>>,
109+
kind: MiriMemoryKind,
110+
) -> InterpResult<'tcx> {
111+
let this = self.eval_context_mut();
112+
if !this.ptr_is_null(ptr)? {
113+
this.deallocate_ptr(ptr, None, kind.into())?;
114+
}
115+
Ok(())
116+
}
117+
118+
fn realloc(
119+
&mut self,
120+
old_ptr: Pointer<Option<Provenance>>,
121+
new_size: u64,
122+
kind: MiriMemoryKind,
123+
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
124+
let this = self.eval_context_mut();
125+
let new_align = this.min_align(new_size, kind);
126+
if this.ptr_is_null(old_ptr)? {
127+
// Here we must behave like `malloc`.
128+
if new_size == 0 {
129+
Ok(Pointer::null())
130+
} else {
131+
let new_ptr =
132+
this.allocate_ptr(Size::from_bytes(new_size), new_align, kind.into())?;
133+
Ok(new_ptr.into())
134+
}
135+
} else {
136+
if new_size == 0 {
137+
// C, in their infinite wisdom, made this UB.
138+
// <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2464.pdf>
139+
throw_ub_format!("`realloc` with a size of zero");
140+
} else {
141+
let new_ptr = this.reallocate_ptr(
142+
old_ptr,
143+
None,
144+
Size::from_bytes(new_size),
145+
new_align,
146+
kind.into(),
147+
)?;
148+
Ok(new_ptr.into())
149+
}
150+
}
151+
}
152+
}

src/shims/foreign_items.rs

+5-146
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{collections::hash_map::Entry, io::Write, iter, path::Path};
22

33
use rustc_apfloat::Float;
4-
use rustc_ast::expand::allocator::{alloc_error_handler_name, AllocatorKind};
4+
use rustc_ast::expand::allocator::alloc_error_handler_name;
55
use rustc_hir::{def::DefKind, def_id::CrateNum};
66
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
77
use rustc_middle::mir;
@@ -12,6 +12,7 @@ use rustc_target::{
1212
spec::abi::Abi,
1313
};
1414

15+
use super::alloc::{check_alloc_request, EvalContextExt as _};
1516
use super::backtrace::EvalContextExt as _;
1617
use crate::*;
1718
use helpers::{ToHost, ToSoft};
@@ -232,140 +233,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
232233
Some(instance) => Ok(Some((this.load_mir(instance.def, None)?, instance))),
233234
}
234235
}
235-
236-
fn malloc(
237-
&mut self,
238-
size: u64,
239-
zero_init: bool,
240-
kind: MiriMemoryKind,
241-
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
242-
let this = self.eval_context_mut();
243-
if size == 0 {
244-
Ok(Pointer::null())
245-
} else {
246-
let align = this.min_align(size, kind);
247-
let ptr = this.allocate_ptr(Size::from_bytes(size), align, kind.into())?;
248-
if zero_init {
249-
// We just allocated this, the access is definitely in-bounds and fits into our address space.
250-
this.write_bytes_ptr(
251-
ptr.into(),
252-
iter::repeat(0u8).take(usize::try_from(size).unwrap()),
253-
)
254-
.unwrap();
255-
}
256-
Ok(ptr.into())
257-
}
258-
}
259-
260-
fn free(
261-
&mut self,
262-
ptr: Pointer<Option<Provenance>>,
263-
kind: MiriMemoryKind,
264-
) -> InterpResult<'tcx> {
265-
let this = self.eval_context_mut();
266-
if !this.ptr_is_null(ptr)? {
267-
this.deallocate_ptr(ptr, None, kind.into())?;
268-
}
269-
Ok(())
270-
}
271-
272-
fn realloc(
273-
&mut self,
274-
old_ptr: Pointer<Option<Provenance>>,
275-
new_size: u64,
276-
kind: MiriMemoryKind,
277-
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
278-
let this = self.eval_context_mut();
279-
let new_align = this.min_align(new_size, kind);
280-
if this.ptr_is_null(old_ptr)? {
281-
// Here we must behave like `malloc`.
282-
if new_size == 0 {
283-
Ok(Pointer::null())
284-
} else {
285-
let new_ptr =
286-
this.allocate_ptr(Size::from_bytes(new_size), new_align, kind.into())?;
287-
Ok(new_ptr.into())
288-
}
289-
} else {
290-
if new_size == 0 {
291-
// C, in their infinite wisdom, made this UB.
292-
// <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2464.pdf>
293-
throw_ub_format!("`realloc` with a size of zero");
294-
} else {
295-
let new_ptr = this.reallocate_ptr(
296-
old_ptr,
297-
None,
298-
Size::from_bytes(new_size),
299-
new_align,
300-
kind.into(),
301-
)?;
302-
Ok(new_ptr.into())
303-
}
304-
}
305-
}
306236
}
307237

308238
impl<'mir, 'tcx: 'mir> EvalContextExtPriv<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
309239
trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
310-
/// Returns the minimum alignment for the target architecture for allocations of the given size.
311-
fn min_align(&self, size: u64, kind: MiriMemoryKind) -> Align {
312-
let this = self.eval_context_ref();
313-
// List taken from `library/std/src/sys/pal/common/alloc.rs`.
314-
// This list should be kept in sync with the one from libstd.
315-
let min_align = match this.tcx.sess.target.arch.as_ref() {
316-
"x86" | "arm" | "mips" | "mips32r6" | "powerpc" | "powerpc64" | "wasm32" => 8,
317-
"x86_64" | "aarch64" | "mips64" | "mips64r6" | "s390x" | "sparc64" | "loongarch64" =>
318-
16,
319-
arch => bug!("unsupported target architecture for malloc: `{}`", arch),
320-
};
321-
// Windows always aligns, even small allocations.
322-
// Source: <https://support.microsoft.com/en-us/help/286470/how-to-use-pageheap-exe-in-windows-xp-windows-2000-and-windows-server>
323-
// But jemalloc does not, so for the C heap we only align if the allocation is sufficiently big.
324-
if kind == MiriMemoryKind::WinHeap || size >= min_align {
325-
return Align::from_bytes(min_align).unwrap();
326-
}
327-
// We have `size < min_align`. Round `size` *down* to the next power of two and use that.
328-
fn prev_power_of_two(x: u64) -> u64 {
329-
let next_pow2 = x.next_power_of_two();
330-
if next_pow2 == x {
331-
// x *is* a power of two, just use that.
332-
x
333-
} else {
334-
// x is between two powers, so next = 2*prev.
335-
next_pow2 / 2
336-
}
337-
}
338-
Align::from_bytes(prev_power_of_two(size)).unwrap()
339-
}
340-
341-
/// Emulates calling the internal __rust_* allocator functions
342-
fn emulate_allocator(
343-
&mut self,
344-
default: impl FnOnce(&mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx>,
345-
) -> InterpResult<'tcx, EmulateForeignItemResult> {
346-
let this = self.eval_context_mut();
347-
348-
let Some(allocator_kind) = this.tcx.allocator_kind(()) else {
349-
// in real code, this symbol does not exist without an allocator
350-
return Ok(EmulateForeignItemResult::NotSupported);
351-
};
352-
353-
match allocator_kind {
354-
AllocatorKind::Global => {
355-
// When `#[global_allocator]` is used, `__rust_*` is defined by the macro expansion
356-
// of this attribute. As such we have to call an exported Rust function,
357-
// and not execute any Miri shim. Somewhat unintuitively doing so is done
358-
// by returning `NotSupported`, which triggers the `lookup_exported_symbol`
359-
// fallback case in `emulate_foreign_item`.
360-
return Ok(EmulateForeignItemResult::NotSupported);
361-
}
362-
AllocatorKind::Default => {
363-
default(this)?;
364-
Ok(EmulateForeignItemResult::NeedsJumping)
365-
}
366-
}
367-
}
368-
369240
fn emulate_foreign_item_inner(
370241
&mut self,
371242
link_name: Symbol,
@@ -612,7 +483,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
612483
let size = this.read_target_usize(size)?;
613484
let align = this.read_target_usize(align)?;
614485

615-
Self::check_alloc_request(size, align)?;
486+
check_alloc_request(size, align)?;
616487

617488
let memory_kind = match link_name.as_str() {
618489
"__rust_alloc" => MiriMemoryKind::Rust,
@@ -646,7 +517,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
646517
let size = this.read_target_usize(size)?;
647518
let align = this.read_target_usize(align)?;
648519

649-
Self::check_alloc_request(size, align)?;
520+
check_alloc_request(size, align)?;
650521

651522
let ptr = this.allocate_ptr(
652523
Size::from_bytes(size),
@@ -710,7 +581,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
710581
let new_size = this.read_target_usize(new_size)?;
711582
// No need to check old_size; we anyway check that they match the allocation.
712583

713-
Self::check_alloc_request(new_size, align)?;
584+
check_alloc_request(new_size, align)?;
714585

715586
let align = Align::from_bytes(align).unwrap();
716587
let new_ptr = this.reallocate_ptr(
@@ -1102,16 +973,4 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
1102973
// i.e., if we actually emulated the function with one of the shims.
1103974
Ok(EmulateForeignItemResult::NeedsJumping)
1104975
}
1105-
1106-
/// Check some basic requirements for this allocation request:
1107-
/// non-zero size, power-of-two alignment.
1108-
fn check_alloc_request(size: u64, align: u64) -> InterpResult<'tcx> {
1109-
if size == 0 {
1110-
throw_ub_format!("creating allocation with size 0");
1111-
}
1112-
if !align.is_power_of_two() {
1113-
throw_ub_format!("creating allocation with non-power-of-two alignment {}", align);
1114-
}
1115-
Ok(())
1116-
}
1117976
}

src/shims/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![warn(clippy::arithmetic_side_effects)]
22

3+
mod alloc;
34
mod backtrace;
45
#[cfg(target_os = "linux")]
56
pub mod ffi_support;

src/shims/unix/foreign_items.rs

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use rustc_span::Symbol;
66
use rustc_target::abi::{Align, Size};
77
use rustc_target::spec::abi::Abi;
88

9+
use crate::shims::alloc::EvalContextExt as _;
910
use crate::shims::unix::*;
1011
use crate::*;
1112
use shims::foreign_items::EmulateForeignItemResult;

0 commit comments

Comments
 (0)