miri/shims/alloc.rs
1use rustc_abi::{Align, Size};
2use rustc_ast::expand::allocator::AllocatorKind;
3
4use crate::*;
5
6impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
7pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
8 /// Returns the alignment that `malloc` would guarantee for requests of the given size.
9 fn malloc_align(&self, size: u64) -> Align {
10 let this = self.eval_context_ref();
11 // The C standard says: "The pointer returned if the allocation succeeds is suitably aligned
12 // so that it may be assigned to a pointer to any type of object with a fundamental
13 // alignment requirement and size less than or equal to the size requested."
14 // So first we need to figure out what the limits are for "fundamental alignment".
15 // This is given by `alignof(max_align_t)`. The following list is taken from
16 // `library/std/src/sys/alloc/mod.rs` (where this is called `MIN_ALIGN`) and should
17 // be kept in sync.
18 let os = this.tcx.sess.target.os.as_ref();
19 let max_fundamental_align = match this.tcx.sess.target.arch.as_ref() {
20 "riscv32" if matches!(os, "espidf" | "zkvm") => 4,
21 "xtensa" if matches!(os, "espidf") => 4,
22 "x86" | "arm" | "m68k" | "csky" | "loongarch32" | "mips" | "mips32r6" | "powerpc"
23 | "powerpc64" | "sparc" | "wasm32" | "hexagon" | "riscv32" | "xtensa" => 8,
24 "x86_64" | "aarch64" | "arm64ec" | "loongarch64" | "mips64" | "mips64r6" | "s390x"
25 | "sparc64" | "riscv64" | "wasm64" => 16,
26 arch => bug!("unsupported target architecture for malloc: `{}`", arch),
27 };
28 // The C standard only requires sufficient alignment for any *type* with size less than or
29 // equal to the size requested. Types one can define in standard C seem to never have an alignment
30 // bigger than their size. So if the size is 2, then only alignment 2 is guaranteed, even if
31 // `max_fundamental_align` is bigger.
32 // This matches what some real-world implementations do, see e.g.
33 // - https://github.com/jemalloc/jemalloc/issues/1533
34 // - https://github.com/llvm/llvm-project/issues/53540
35 // - https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2293.htm
36 if size >= max_fundamental_align {
37 return Align::from_bytes(max_fundamental_align).unwrap();
38 }
39 // C doesn't have zero-sized types, so presumably nothing is guaranteed here.
40 if size == 0 {
41 return Align::ONE;
42 }
43 // We have `size < min_align`. Round `size` *down* to the next power of two and use that.
44 fn prev_power_of_two(x: u64) -> u64 {
45 let next_pow2 = x.next_power_of_two();
46 if next_pow2 == x {
47 // x *is* a power of two, just use that.
48 x
49 } else {
50 // x is between two powers, so next = 2*prev.
51 next_pow2 / 2
52 }
53 }
54 Align::from_bytes(prev_power_of_two(size)).unwrap()
55 }
56
57 /// Emulates calling the internal __rust_* allocator functions
58 fn emulate_allocator(
59 &mut self,
60 default: impl FnOnce(&mut MiriInterpCx<'tcx>) -> InterpResult<'tcx>,
61 ) -> InterpResult<'tcx, EmulateItemResult> {
62 let this = self.eval_context_mut();
63
64 let Some(allocator_kind) = this.tcx.allocator_kind(()) else {
65 // in real code, this symbol does not exist without an allocator
66 return interp_ok(EmulateItemResult::NotSupported);
67 };
68
69 match allocator_kind {
70 AllocatorKind::Global => {
71 // When `#[global_allocator]` is used, `__rust_*` is defined by the macro expansion
72 // of this attribute. As such we have to call an exported Rust function,
73 // and not execute any Miri shim. Somewhat unintuitively doing so is done
74 // by returning `NotSupported`, which triggers the `lookup_exported_symbol`
75 // fallback case in `emulate_foreign_item`.
76 interp_ok(EmulateItemResult::NotSupported)
77 }
78 AllocatorKind::Default => {
79 default(this)?;
80 interp_ok(EmulateItemResult::NeedsReturn)
81 }
82 }
83 }
84
85 fn malloc(&mut self, size: u64, init: AllocInit) -> InterpResult<'tcx, Pointer> {
86 let this = self.eval_context_mut();
87 let align = this.malloc_align(size);
88 let ptr =
89 this.allocate_ptr(Size::from_bytes(size), align, MiriMemoryKind::C.into(), init)?;
90 interp_ok(ptr.into())
91 }
92
93 fn posix_memalign(
94 &mut self,
95 memptr: &OpTy<'tcx>,
96 align: &OpTy<'tcx>,
97 size: &OpTy<'tcx>,
98 ) -> InterpResult<'tcx, Scalar> {
99 let this = self.eval_context_mut();
100 let memptr = this.deref_pointer_as(memptr, this.machine.layouts.mut_raw_ptr)?;
101 let align = this.read_target_usize(align)?;
102 let size = this.read_target_usize(size)?;
103
104 // Align must be power of 2, and also at least ptr-sized (POSIX rules).
105 // But failure to adhere to this is not UB, it's an error condition.
106 if !align.is_power_of_two() || align < this.pointer_size().bytes() {
107 interp_ok(this.eval_libc("EINVAL"))
108 } else {
109 let ptr = this.allocate_ptr(
110 Size::from_bytes(size),
111 Align::from_bytes(align).unwrap(),
112 MiriMemoryKind::C.into(),
113 AllocInit::Uninit,
114 )?;
115 this.write_pointer(ptr, &memptr)?;
116 interp_ok(Scalar::from_i32(0))
117 }
118 }
119
120 fn free(&mut self, ptr: Pointer) -> InterpResult<'tcx> {
121 let this = self.eval_context_mut();
122 if !this.ptr_is_null(ptr)? {
123 this.deallocate_ptr(ptr, None, MiriMemoryKind::C.into())?;
124 }
125 interp_ok(())
126 }
127
128 fn realloc(&mut self, old_ptr: Pointer, new_size: u64) -> InterpResult<'tcx, Pointer> {
129 let this = self.eval_context_mut();
130 let new_align = this.malloc_align(new_size);
131 if this.ptr_is_null(old_ptr)? {
132 // Here we must behave like `malloc`.
133 self.malloc(new_size, AllocInit::Uninit)
134 } else {
135 if new_size == 0 {
136 // C, in their infinite wisdom, made this UB.
137 // <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2464.pdf>
138 throw_ub_format!("`realloc` with a size of zero");
139 } else {
140 let new_ptr = this.reallocate_ptr(
141 old_ptr,
142 None,
143 Size::from_bytes(new_size),
144 new_align,
145 MiriMemoryKind::C.into(),
146 AllocInit::Uninit,
147 )?;
148 interp_ok(new_ptr.into())
149 }
150 }
151 }
152
153 fn aligned_alloc(
154 &mut self,
155 align: &OpTy<'tcx>,
156 size: &OpTy<'tcx>,
157 ) -> InterpResult<'tcx, Pointer> {
158 let this = self.eval_context_mut();
159 let align = this.read_target_usize(align)?;
160 let size = this.read_target_usize(size)?;
161
162 // Alignment must be a power of 2, and "supported by the implementation".
163 // We decide that "supported by the implementation" means that the
164 // size must be a multiple of the alignment. (This restriction seems common
165 // enough that it is stated on <https://en.cppreference.com/w/c/memory/aligned_alloc>
166 // as a general rule, but the actual standard has no such rule.)
167 // If any of these are violated, we have to return NULL.
168 // All fundamental alignments must be supported.
169 //
170 // macOS and Illumos are buggy in that they require the alignment
171 // to be at least the size of a pointer, so they do not support all fundamental
172 // alignments. We do not emulate those platform bugs.
173 //
174 // Linux also sets errno to EINVAL, but that's non-standard behavior that we do not
175 // emulate.
176 // FreeBSD says some of these cases are UB but that's violating the C standard.
177 // http://en.cppreference.com/w/cpp/memory/c/aligned_alloc
178 // Linux: https://linux.die.net/man/3/aligned_alloc
179 // FreeBSD: https://man.freebsd.org/cgi/man.cgi?query=aligned_alloc&apropos=0&sektion=3&manpath=FreeBSD+9-current&format=html
180 match size.checked_rem(align) {
181 Some(0) if align.is_power_of_two() => {
182 let align = align.max(this.malloc_align(size).bytes());
183 let ptr = this.allocate_ptr(
184 Size::from_bytes(size),
185 Align::from_bytes(align).unwrap(),
186 MiriMemoryKind::C.into(),
187 AllocInit::Uninit,
188 )?;
189 interp_ok(ptr.into())
190 }
191 _ => interp_ok(Pointer::null()),
192 }
193 }
194}