Skip to content

Commit c683873

Browse files
committed
add associated type for allocbytes
1 parent 3de4f1c commit c683873

File tree

10 files changed

+438
-19
lines changed

10 files changed

+438
-19
lines changed

compiler/rustc_middle/src/mir/interpret/allocation.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,20 @@ use crate::ty;
2727

2828
/// Functionality required for the bytes of an `Allocation`.
2929
pub trait AllocBytes: Clone + fmt::Debug + Deref<Target = [u8]> + DerefMut<Target = [u8]> {
30+
/// Used for giving extra metadata on creation. Miri uses this to allow
31+
/// machine memory to be allocated separately from other allocations.
32+
type ByteMetadata: Default + Clone + fmt::Debug;
33+
3034
/// Create an `AllocBytes` from a slice of `u8`.
31-
fn from_bytes<'a>(slice: impl Into<Cow<'a, [u8]>>, _align: Align) -> Self;
35+
fn from_bytes<'a>(
36+
slice: impl Into<Cow<'a, [u8]>>,
37+
_align: Align,
38+
_dsc: Self::ByteMetadata,
39+
) -> Self;
3240

3341
/// Create a zeroed `AllocBytes` of the specified size and alignment.
3442
/// Returns `None` if we ran out of memory on the host.
35-
fn zeroed(size: Size, _align: Align) -> Option<Self>;
43+
fn zeroed(size: Size, _align: Align, _dsc: Self::ByteMetadata) -> Option<Self>;
3644

3745
/// Gives direct access to the raw underlying storage.
3846
///
@@ -51,11 +59,13 @@ pub trait AllocBytes: Clone + fmt::Debug + Deref<Target = [u8]> + DerefMut<Targe
5159

5260
/// Default `bytes` for `Allocation` is a `Box<u8>`.
5361
impl AllocBytes for Box<[u8]> {
54-
fn from_bytes<'a>(slice: impl Into<Cow<'a, [u8]>>, _align: Align) -> Self {
62+
type ByteMetadata = ();
63+
64+
fn from_bytes<'a>(slice: impl Into<Cow<'a, [u8]>>, _align: Align, _dsc: ()) -> Self {
5565
Box::<[u8]>::from(slice.into())
5666
}
5767

58-
fn zeroed(size: Size, _align: Align) -> Option<Self> {
68+
fn zeroed(size: Size, _align: Align, _dsc: ()) -> Option<Self> {
5969
let bytes = Box::<[u8]>::try_new_zeroed_slice(size.bytes().try_into().ok()?).ok()?;
6070
// SAFETY: the box was zero-allocated, which is a valid initial value for Box<[u8]>
6171
let bytes = unsafe { bytes.assume_init() };
@@ -203,7 +213,7 @@ where
203213

204214
let len = decoder.read_usize();
205215
let bytes = if all_zero { vec![0u8; len] } else { decoder.read_raw_bytes(len).to_vec() };
206-
let bytes = Bytes::from_bytes(bytes, align);
216+
let bytes = Bytes::from_bytes(bytes, align, Bytes::ByteMetadata::default());
207217

208218
let provenance = Decodable::decode(decoder);
209219
let init_mask = Decodable::decode(decoder);
@@ -396,7 +406,7 @@ impl<Prov: Provenance, Bytes: AllocBytes> Allocation<Prov, (), Bytes> {
396406
align: Align,
397407
mutability: Mutability,
398408
) -> Self {
399-
let bytes = Bytes::from_bytes(slice, align);
409+
let bytes = Bytes::from_bytes(slice, align, Bytes::ByteMetadata::default());
400410
let size = Size::from_bytes(bytes.len());
401411
Self {
402412
bytes,
@@ -424,7 +434,7 @@ impl<Prov: Provenance, Bytes: AllocBytes> Allocation<Prov, (), Bytes> {
424434
// deterministic. However, we can be non-deterministic here because all uses of const
425435
// evaluation (including ConstProp!) will make compilation fail (via hard error
426436
// or ICE) upon encountering a `MemoryExhausted` error.
427-
let bytes = Bytes::zeroed(size, align).ok_or_else(fail)?;
437+
let bytes = Bytes::zeroed(size, align, Bytes::ByteMetadata::default()).ok_or_else(fail)?;
428438

429439
Ok(Allocation {
430440
bytes,
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
use std::alloc::Layout;
2+
use std::borrow::Cow;
3+
use std::{alloc, slice};
4+
5+
use rustc_abi::{Align, Size};
6+
use rustc_middle::mir::interpret::AllocBytes;
7+
8+
#[cfg(target_os = "linux")]
9+
use crate::alloc::discrete_alloc::MachineAlloc;
10+
use crate::helpers::ToU64 as _;
11+
12+
#[derive(Clone, Copy, Debug, Default)]
13+
pub enum MiriByteMdata {
14+
#[default]
15+
Global,
16+
Isolated,
17+
}
18+
19+
/// Allocation bytes that explicitly handle the layout of the data they're storing.
20+
/// This is necessary to interface with native code that accesses the program store in Miri.
21+
#[derive(Debug)]
22+
pub struct MiriAllocBytes {
23+
/// Stored layout information about the allocation.
24+
layout: alloc::Layout,
25+
/// Pointer to the allocation contents.
26+
/// Invariant:
27+
/// * If `self.layout.size() == 0`, then `self.ptr` was allocated with the equivalent layout with size 1.
28+
/// * Otherwise, `self.ptr` points to memory allocated with `self.layout`.
29+
ptr: *mut u8,
30+
/// Whether this instance of `MiriAllocBytes` had its allocation created by calling `alloc::alloc()`
31+
/// (`Global`) or the discrete allocator (`Isolated`)
32+
dsc: MiriByteMdata,
33+
}
34+
35+
impl Clone for MiriAllocBytes {
36+
fn clone(&self) -> Self {
37+
let bytes: Cow<'_, [u8]> = Cow::Borrowed(self);
38+
let align = Align::from_bytes(self.layout.align().to_u64()).unwrap();
39+
MiriAllocBytes::from_bytes(bytes, align, self.dsc)
40+
}
41+
}
42+
43+
impl Drop for MiriAllocBytes {
44+
fn drop(&mut self) {
45+
// We have to reconstruct the actual layout used for allocation.
46+
// (`Deref` relies on `size` so we can't just always set it to at least 1.)
47+
let alloc_layout = if self.layout.size() == 0 {
48+
Layout::from_size_align(1, self.layout.align()).unwrap()
49+
} else {
50+
self.layout
51+
};
52+
53+
// SAFETY: Invariant, `self.ptr` points to memory allocated with `self.layout`.
54+
unsafe {
55+
match self.dsc {
56+
MiriByteMdata::Global => alloc::dealloc(self.ptr, alloc_layout),
57+
MiriByteMdata::Isolated => {
58+
#[cfg(target_os = "linux")]
59+
{MachineAlloc::dealloc(self.ptr, alloc_layout)}
60+
#[cfg(not(target_os = "linux"))]
61+
{unreachable!()}
62+
},
63+
}
64+
}
65+
}
66+
}
67+
68+
impl std::ops::Deref for MiriAllocBytes {
69+
type Target = [u8];
70+
71+
fn deref(&self) -> &Self::Target {
72+
// SAFETY: `ptr` is non-null, properly aligned, and valid for reading out `self.layout.size()`-many bytes.
73+
// Note that due to the invariant this is true even if `self.layout.size() == 0`.
74+
unsafe { slice::from_raw_parts(self.ptr, self.layout.size()) }
75+
}
76+
}
77+
78+
impl std::ops::DerefMut for MiriAllocBytes {
79+
fn deref_mut(&mut self) -> &mut Self::Target {
80+
// SAFETY: `ptr` is non-null, properly aligned, and valid for reading out `self.layout.size()`-many bytes.
81+
// Note that due to the invariant this is true even if `self.layout.size() == 0`.
82+
unsafe { slice::from_raw_parts_mut(self.ptr, self.layout.size()) }
83+
}
84+
}
85+
86+
impl MiriAllocBytes {
87+
/// This method factors out how a `MiriAllocBytes` object is allocated, given a specific allocation function.
88+
/// If `size == 0` we allocate using a different `alloc_layout` with `size = 1`, to ensure each allocation has a unique address.
89+
/// Returns `Err(alloc_layout)` if the allocation function returns a `ptr` where `ptr.is_null()`.
90+
fn alloc_with(
91+
size: u64,
92+
align: u64,
93+
dsc: MiriByteMdata,
94+
alloc_fn: impl FnOnce(Layout) -> *mut u8,
95+
) -> Result<MiriAllocBytes, ()> {
96+
let size = usize::try_from(size).map_err(|_| ())?;
97+
let align = usize::try_from(align).map_err(|_| ())?;
98+
let layout = Layout::from_size_align(size, align).map_err(|_| ())?;
99+
// When size is 0 we allocate 1 byte anyway, to ensure each allocation has a unique address.
100+
let alloc_layout =
101+
if size == 0 { Layout::from_size_align(1, align).unwrap() } else { layout };
102+
let ptr = alloc_fn(alloc_layout);
103+
if ptr.is_null() {
104+
Err(())
105+
} else {
106+
// SAFETY: All `MiriAllocBytes` invariants are fulfilled.
107+
Ok(Self { ptr, layout, dsc })
108+
}
109+
}
110+
}
111+
112+
impl AllocBytes for MiriAllocBytes {
113+
type ByteMetadata = MiriByteMdata;
114+
115+
fn from_bytes<'a>(slice: impl Into<Cow<'a, [u8]>>, align: Align, dsc: MiriByteMdata) -> Self {
116+
let slice = slice.into();
117+
let size = slice.len();
118+
let align = align.bytes();
119+
// SAFETY: `alloc_fn` will only be used with `size != 0`.
120+
let alloc_fn = |layout| unsafe {
121+
match dsc {
122+
MiriByteMdata::Global => alloc::alloc(layout),
123+
MiriByteMdata::Isolated => {
124+
#[cfg(target_os = "linux")]
125+
{MachineAlloc::alloc(layout)}
126+
#[cfg(not(target_os = "linux"))]
127+
{unreachable!()}
128+
},
129+
}
130+
};
131+
let alloc_bytes = MiriAllocBytes::alloc_with(size.to_u64(), align, dsc, alloc_fn)
132+
.unwrap_or_else(|()| {
133+
panic!("Miri ran out of memory: cannot create allocation of {size} bytes")
134+
});
135+
// SAFETY: `alloc_bytes.ptr` and `slice.as_ptr()` are non-null, properly aligned
136+
// and valid for the `size`-many bytes to be copied.
137+
unsafe { alloc_bytes.ptr.copy_from(slice.as_ptr(), size) };
138+
alloc_bytes
139+
}
140+
141+
fn zeroed(size: Size, align: Align, dsc: MiriByteMdata) -> Option<Self> {
142+
let size = size.bytes();
143+
let align = align.bytes();
144+
// SAFETY: `alloc_fn` will only be used with `size != 0`.
145+
let alloc_fn = |layout| unsafe {
146+
match dsc {
147+
MiriByteMdata::Global => alloc::alloc_zeroed(layout),
148+
MiriByteMdata::Isolated => {
149+
#[cfg(target_os = "linux")]
150+
{MachineAlloc::alloc_zeroed(layout)}
151+
#[cfg(not(target_os = "linux"))]
152+
{unreachable!()}
153+
},
154+
}
155+
};
156+
MiriAllocBytes::alloc_with(size, align, dsc, alloc_fn).ok()
157+
}
158+
159+
fn as_mut_ptr(&mut self) -> *mut u8 {
160+
self.ptr
161+
}
162+
163+
fn as_ptr(&self) -> *const u8 {
164+
self.ptr
165+
}
166+
}

0 commit comments

Comments
 (0)