| //! The virtual memory representation of the MIR interpreter. | |
| mod init_mask; | |
| mod provenance_map; | |
| use std::alloc::{self, Layout}; | |
| use std::borrow::Cow; | |
| use std::hash::Hash; | |
| use std::ops::{Deref, DerefMut, Range}; | |
| use std::{fmt, hash, ptr}; | |
| use either::{Left, Right}; | |
| use init_mask::*; | |
| pub use init_mask::{InitChunk, InitChunkIter}; | |
| use provenance_map::*; | |
| use rustc_abi::{Align, HasDataLayout, Size}; | |
| use rustc_ast::Mutability; | |
| use rustc_data_structures::intern::Interned; | |
| use rustc_macros::StableHash; | |
| use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; | |
| use super::{ | |
| AllocId, BadBytesAccess, CtfeProvenance, InterpErrorKind, InterpResult, Pointer, Provenance, | |
| ResourceExhaustionInfo, Scalar, ScalarSizeMismatch, UndefinedBehaviorInfo, UnsupportedOpInfo, | |
| interp_ok, read_target_uint, write_target_uint, | |
| }; | |
| use crate::ty; | |
| /// Functionality required for the bytes of an `Allocation`. | |
| pub trait AllocBytes: Clone + fmt::Debug + Deref<Target = [u8]> + DerefMut<Target = [u8]> { | |
| /// The type of extra parameters passed in when creating an allocation. | |
| /// Can be used by `interpret::Machine` instances to make runtime-configuration-dependent | |
| /// decisions about the allocation strategy. | |
| type AllocParams; | |
| /// Create an `AllocBytes` from a slice of `u8`. | |
| fn from_bytes<'a>( | |
| slice: impl Into<Cow<'a, [u8]>>, | |
| _align: Align, | |
| _params: Self::AllocParams, | |
| ) -> Self; | |
| /// Create a zeroed `AllocBytes` of the specified size and alignment. | |
| /// Returns `None` if we ran out of memory on the host. | |
| fn zeroed(size: Size, _align: Align, _params: Self::AllocParams) -> Option<Self>; | |
| /// Gives direct access to the raw underlying storage. | |
| /// | |
| /// Crucially this pointer is compatible with: | |
| /// - other pointers returned by this method, and | |
| /// - references returned from `deref()`, as long as there was no write. | |
| fn as_mut_ptr(&mut self) -> *mut u8; | |
| /// Gives direct access to the raw underlying storage. | |
| /// | |
| /// Crucially this pointer is compatible with: | |
| /// - other pointers returned by this method, and | |
| /// - references returned from `deref()`, as long as there was no write. | |
| fn as_ptr(&self) -> *const u8; | |
| } | |
| /// Default `bytes` for `Allocation` is a `Box<u8>`. | |
| impl AllocBytes for Box<[u8]> { | |
| type AllocParams = (); | |
| fn from_bytes<'a>(slice: impl Into<Cow<'a, [u8]>>, _align: Align, _params: ()) -> Self { | |
| Box::<[u8]>::from(slice.into()) | |
| } | |
| fn zeroed(size: Size, _align: Align, _params: ()) -> Option<Self> { | |
| let bytes = Box::<[u8]>::try_new_zeroed_slice(size.bytes().try_into().ok()?).ok()?; | |
| // SAFETY: the box was zero-allocated, which is a valid initial value for Box<[u8]> | |
| let bytes = unsafe { bytes.assume_init() }; | |
| Some(bytes) | |
| } | |
| fn as_mut_ptr(&mut self) -> *mut u8 { | |
| Box::as_mut_ptr(self).cast() | |
| } | |
| fn as_ptr(&self) -> *const u8 { | |
| Box::as_ptr(self).cast() | |
| } | |
| } | |
| /// This type represents an Allocation in the Miri/CTFE core engine. | |
| /// | |
| /// Its public API is rather low-level, working directly with allocation offsets and a custom error | |
| /// type to account for the lack of an AllocId on this level. The Miri/CTFE core engine `memory` | |
| /// module provides higher-level access. | |
| // Note: for performance reasons when interning, some of the `Allocation` fields can be partially | |
| // hashed. (see the `Hash` impl below for more details), so the impl is not derived. | |
| #[derive(Clone, Eq, PartialEq)] | |
| #[derive(StableHash)] | |
| pub struct Allocation<Prov: Provenance = CtfeProvenance, Extra = (), Bytes = Box<[u8]>> { | |
| /// The actual bytes of the allocation. | |
| /// Note that the bytes of a pointer represent the offset of the pointer. | |
| bytes: Bytes, | |
| /// Maps from byte addresses to extra provenance data for each pointer. | |
| /// Only the first byte of a pointer is inserted into the map; i.e., | |
| /// every entry in this map applies to `pointer_size` consecutive bytes starting | |
| /// at the given offset. | |
| provenance: ProvenanceMap<Prov>, | |
| /// Denotes which part of this allocation is initialized. | |
| /// | |
| /// Invariant: the uninitialized parts have no provenance. | |
| init_mask: InitMask, | |
| /// The alignment of the allocation to detect unaligned reads. | |
| /// (`Align` guarantees that this is a power of two.) | |
| pub align: Align, | |
| /// `true` if the allocation is mutable. | |
| /// Also used by codegen to determine if a static should be put into mutable memory, | |
| /// which happens for `static mut` and `static` with interior mutability. | |
| pub mutability: Mutability, | |
| /// Extra state for the machine. | |
| pub extra: Extra, | |
| } | |
| /// Helper struct that packs an alignment, mutability, and "all bytes are zero" flag together. | |
| /// | |
| /// Alignment values always have 2 free high bits, and we check for this in our [`Encodable`] impl. | |
| struct AllocFlags { | |
| align: Align, | |
| mutability: Mutability, | |
| all_zero: bool, | |
| } | |
| impl<E: Encoder> Encodable<E> for AllocFlags { | |
| fn encode(&self, encoder: &mut E) { | |
| // Make sure Align::MAX can be stored with the high 2 bits unset. | |
| const { | |
| let max_supported_align_repr = u8::MAX >> 2; | |
| let max_supported_align = 1 << max_supported_align_repr; | |
| assert!(Align::MAX.bytes() <= max_supported_align) | |
| } | |
| let mut flags = self.align.bytes().trailing_zeros() as u8; | |
| flags |= match self.mutability { | |
| Mutability::Not => 0, | |
| Mutability::Mut => 1 << 6, | |
| }; | |
| flags |= (self.all_zero as u8) << 7; | |
| flags.encode(encoder); | |
| } | |
| } | |
| impl<D: Decoder> Decodable<D> for AllocFlags { | |
| fn decode(decoder: &mut D) -> Self { | |
| let flags: u8 = Decodable::decode(decoder); | |
| let align = flags & 0b0011_1111; | |
| let mutability = flags & 0b0100_0000; | |
| let all_zero = flags & 0b1000_0000; | |
| let align = Align::from_bytes(1 << align).unwrap(); | |
| let mutability = match mutability { | |
| 0 => Mutability::Not, | |
| _ => Mutability::Mut, | |
| }; | |
| let all_zero = all_zero > 0; | |
| AllocFlags { align, mutability, all_zero } | |
| } | |
| } | |
| /// Efficiently detect whether a slice of `u8` is all zero. | |
| /// | |
| /// This is used in encoding of [`Allocation`] to special-case all-zero allocations. It is only | |
| /// optimized a little, because for many allocations the encoding of the actual bytes does not | |
| /// dominate runtime. | |
| #[inline] | |
| fn all_zero(buf: &[u8]) -> bool { | |
| // In the empty case we wouldn't encode any contents even without this system where we | |
| // special-case allocations whose contents are all 0. We can return anything in the empty case. | |
| if buf.is_empty() { | |
| return true; | |
| } | |
| // Just fast-rejecting based on the first element significantly reduces the amount that we end | |
| // up walking the whole array. | |
| if buf[0] != 0 { | |
| return false; | |
| } | |
| // This strategy of combining all slice elements with & or | is unbeatable for the large | |
| // all-zero case because it is so well-understood by autovectorization. | |
| buf.iter().fold(true, |acc, b| acc & (*b == 0)) | |
| } | |
| /// Custom encoder for [`Allocation`] to more efficiently represent the case where all bytes are 0. | |
| impl<Prov: Provenance, Extra, E: Encoder> Encodable<E> for Allocation<Prov, Extra, Box<[u8]>> | |
| where | |
| ProvenanceMap<Prov>: Encodable<E>, | |
| Extra: Encodable<E>, | |
| { | |
| fn encode(&self, encoder: &mut E) { | |
| let all_zero = all_zero(&self.bytes); | |
| AllocFlags { align: self.align, mutability: self.mutability, all_zero }.encode(encoder); | |
| encoder.emit_usize(self.bytes.len()); | |
| if !all_zero { | |
| encoder.emit_raw_bytes(&self.bytes); | |
| } | |
| self.provenance.encode(encoder); | |
| self.init_mask.encode(encoder); | |
| self.extra.encode(encoder); | |
| } | |
| } | |
| impl<Prov: Provenance, Extra, D: Decoder> Decodable<D> for Allocation<Prov, Extra, Box<[u8]>> | |
| where | |
| ProvenanceMap<Prov>: Decodable<D>, | |
| Extra: Decodable<D>, | |
| { | |
| fn decode(decoder: &mut D) -> Self { | |
| let AllocFlags { align, mutability, all_zero } = Decodable::decode(decoder); | |
| let len = decoder.read_usize(); | |
| let bytes = if all_zero { vec![0u8; len] } else { decoder.read_raw_bytes(len).to_vec() }; | |
| let bytes = <Box<[u8]> as AllocBytes>::from_bytes(bytes, align, ()); | |
| let provenance = Decodable::decode(decoder); | |
| let init_mask = Decodable::decode(decoder); | |
| let extra = Decodable::decode(decoder); | |
| Self { bytes, provenance, init_mask, align, mutability, extra } | |
| } | |
| } | |
| /// This is the maximum size we will hash at a time, when interning an `Allocation` and its | |
| /// `InitMask`. Note, we hash that amount of bytes twice: at the start, and at the end of a buffer. | |
| /// Used when these two structures are large: we only partially hash the larger fields in that | |
| /// situation. See the comment at the top of their respective `Hash` impl for more details. | |
| const MAX_BYTES_TO_HASH: usize = 64; | |
| /// This is the maximum size (in bytes) for which a buffer will be fully hashed, when interning. | |
| /// Otherwise, it will be partially hashed in 2 slices, requiring at least 2 `MAX_BYTES_TO_HASH` | |
| /// bytes. | |
| const MAX_HASHED_BUFFER_LEN: usize = 2 * MAX_BYTES_TO_HASH; | |
| // Const allocations are only hashed for interning. However, they can be large, making the hashing | |
| // expensive especially since it uses `FxHash`: it's better suited to short keys, not potentially | |
| // big buffers like the actual bytes of allocation. We can partially hash some fields when they're | |
| // large. | |
| impl hash::Hash for Allocation { | |
| fn hash<H: hash::Hasher>(&self, state: &mut H) { | |
| let Self { | |
| bytes, | |
| provenance, | |
| init_mask, | |
| align, | |
| mutability, | |
| extra: (), // don't bother hashing () | |
| } = self; | |
| // Partially hash the `bytes` buffer when it is large. To limit collisions with common | |
| // prefixes and suffixes, we hash the length and some slices of the buffer. | |
| let byte_count = bytes.len(); | |
| if byte_count > MAX_HASHED_BUFFER_LEN { | |
| // Hash the buffer's length. | |
| byte_count.hash(state); | |
| // And its head and tail. | |
| bytes[..MAX_BYTES_TO_HASH].hash(state); | |
| bytes[byte_count - MAX_BYTES_TO_HASH..].hash(state); | |
| } else { | |
| bytes.hash(state); | |
| } | |
| // Hash the other fields as usual. | |
| provenance.hash(state); | |
| init_mask.hash(state); | |
| align.hash(state); | |
| mutability.hash(state); | |
| } | |
| } | |
| /// Interned types generally have an `Outer` type and an `Inner` type, where | |
| /// `Outer` is a newtype around `Interned<Inner>`, and all the operations are | |
| /// done on `Outer`, because all occurrences are interned. E.g. `Ty` is an | |
| /// outer type and `TyKind` is its inner type. | |
| /// | |
| /// Here things are different because only const allocations are interned. This | |
| /// means that both the inner type (`Allocation`) and the outer type | |
| /// (`ConstAllocation`) are used quite a bit. | |
| #[derive(Copy, Clone, PartialEq, Eq, Hash, StableHash)] | |
| #[rustc_pass_by_value] | |
| pub struct ConstAllocation<'tcx>(pub Interned<'tcx, Allocation>); | |
| impl<'tcx> fmt::Debug for ConstAllocation<'tcx> { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| // The debug representation of this is very verbose and basically useless, | |
| // so don't print it. | |
| write!(f, "ConstAllocation {{ .. }}") | |
| } | |
| } | |
| impl<'tcx> ConstAllocation<'tcx> { | |
| pub fn inner(self) -> &'tcx Allocation { | |
| self.0.0 | |
| } | |
| } | |
| /// We have our own error type that does not know about the `AllocId`; that information | |
| /// is added when converting to `InterpError`. | |
| #[derive(Debug)] | |
| pub enum AllocError { | |
| /// A scalar had the wrong size. | |
| ScalarSizeMismatch(ScalarSizeMismatch), | |
| /// Encountered a pointer where we needed raw bytes. | |
| ReadPointerAsInt(Option<BadBytesAccess>), | |
| /// Partially copying a pointer. | |
| ReadPartialPointer(Size), | |
| /// Using uninitialized data where it is not allowed. | |
| InvalidUninitBytes(Option<BadBytesAccess>), | |
| } | |
| pub type AllocResult<T = ()> = Result<T, AllocError>; | |
| impl From<ScalarSizeMismatch> for AllocError { | |
| fn from(s: ScalarSizeMismatch) -> Self { | |
| AllocError::ScalarSizeMismatch(s) | |
| } | |
| } | |
| impl AllocError { | |
| pub fn to_interp_error<'tcx>(self, alloc_id: AllocId) -> InterpErrorKind<'tcx> { | |
| use AllocError::*; | |
| match self { | |
| ScalarSizeMismatch(s) => { | |
| InterpErrorKind::UndefinedBehavior(UndefinedBehaviorInfo::ScalarSizeMismatch(s)) | |
| } | |
| ReadPointerAsInt(info) => InterpErrorKind::Unsupported( | |
| UnsupportedOpInfo::ReadPointerAsInt(info.map(|b| (alloc_id, b))), | |
| ), | |
| ReadPartialPointer(offset) => InterpErrorKind::Unsupported( | |
| UnsupportedOpInfo::ReadPartialPointer(Pointer::new(alloc_id, offset)), | |
| ), | |
| InvalidUninitBytes(info) => InterpErrorKind::UndefinedBehavior( | |
| UndefinedBehaviorInfo::InvalidUninitBytes(info.map(|b| (alloc_id, b))), | |
| ), | |
| } | |
| } | |
| } | |
| /// The information that makes up a memory access: offset and size. | |
| #[derive(Debug, Copy, Clone)] | |
| pub struct AllocRange { | |
| pub start: Size, | |
| pub size: Size, | |
| } | |
| impl fmt::Display for AllocRange { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| write!(f, "[{:#x}..{:#x}]", self.start.bytes(), self.end().bytes()) | |
| } | |
| } | |
| /// Free-starting constructor for less syntactic overhead. | |
| #[inline(always)] | |
| pub fn alloc_range(start: Size, size: Size) -> AllocRange { | |
| AllocRange { start, size } | |
| } | |
| impl From<Range<Size>> for AllocRange { | |
| #[inline] | |
| fn from(r: Range<Size>) -> Self { | |
| alloc_range(r.start, r.end - r.start) // `Size` subtraction (overflow-checked) | |
| } | |
| } | |
| impl From<Range<usize>> for AllocRange { | |
| #[inline] | |
| fn from(r: Range<usize>) -> Self { | |
| AllocRange::from(Size::from_bytes(r.start)..Size::from_bytes(r.end)) | |
| } | |
| } | |
| impl AllocRange { | |
| #[inline(always)] | |
| pub fn end(self) -> Size { | |
| self.start + self.size // This does overflow checking. | |
| } | |
| /// Returns the `subrange` within this range; panics if it is not a subrange. | |
| #[inline] | |
| pub fn subrange(self, subrange: AllocRange) -> AllocRange { | |
| let sub_start = self.start + subrange.start; | |
| let range = alloc_range(sub_start, subrange.size); | |
| assert!(range.end() <= self.end(), "access outside the bounds for given AllocRange"); | |
| range | |
| } | |
| } | |
| /// Whether a new allocation should be initialized with zero-bytes. | |
| pub enum AllocInit { | |
| Uninit, | |
| Zero, | |
| } | |
| // The constructors are all without extra; the extra gets added by a machine hook later. | |
| impl<Prov: Provenance, Bytes: AllocBytes> Allocation<Prov, (), Bytes> { | |
| /// Creates an allocation initialized by the given bytes | |
| pub fn from_bytes<'a>( | |
| slice: impl Into<Cow<'a, [u8]>>, | |
| align: Align, | |
| mutability: Mutability, | |
| params: <Bytes as AllocBytes>::AllocParams, | |
| ) -> Self { | |
| let bytes = Bytes::from_bytes(slice, align, params); | |
| let size = Size::from_bytes(bytes.len()); | |
| Self { | |
| bytes, | |
| provenance: ProvenanceMap::new(), | |
| init_mask: InitMask::new(size, true), | |
| align, | |
| mutability, | |
| extra: (), | |
| } | |
| } | |
| pub fn from_bytes_byte_aligned_immutable<'a>( | |
| slice: impl Into<Cow<'a, [u8]>>, | |
| params: <Bytes as AllocBytes>::AllocParams, | |
| ) -> Self { | |
| Allocation::from_bytes(slice, Align::ONE, Mutability::Not, params) | |
| } | |
| fn new_inner<R>( | |
| size: Size, | |
| align: Align, | |
| init: AllocInit, | |
| params: <Bytes as AllocBytes>::AllocParams, | |
| fail: impl FnOnce() -> R, | |
| ) -> Result<Self, R> { | |
| // We raise an error if we cannot create the allocation on the host. | |
| // This results in an error that can happen non-deterministically, since the memory | |
| // available to the compiler can change between runs. Normally queries are always | |
| // deterministic. However, we can be non-deterministic here because all uses of const | |
| // evaluation (including ConstProp!) will make compilation fail (via hard error | |
| // or OOM) upon encountering a `MemoryExhausted` error. | |
| let bytes = Bytes::zeroed(size, align, params).ok_or_else(fail)?; | |
| Ok(Allocation { | |
| bytes, | |
| provenance: ProvenanceMap::new(), | |
| init_mask: InitMask::new( | |
| size, | |
| match init { | |
| AllocInit::Uninit => false, | |
| AllocInit::Zero => true, | |
| }, | |
| ), | |
| align, | |
| mutability: Mutability::Mut, | |
| extra: (), | |
| }) | |
| } | |
| /// Try to create an Allocation of `size` bytes, failing if there is not enough memory | |
| /// available to the compiler to do so. | |
| pub fn try_new<'tcx>( | |
| size: Size, | |
| align: Align, | |
| init: AllocInit, | |
| params: <Bytes as AllocBytes>::AllocParams, | |
| ) -> InterpResult<'tcx, Self> { | |
| Self::new_inner(size, align, init, params, || { | |
| ty::tls::with(|tcx| tcx.dcx().delayed_bug("exhausted memory during interpretation")); | |
| InterpErrorKind::ResourceExhaustion(ResourceExhaustionInfo::MemoryExhausted) | |
| }) | |
| .into() | |
| } | |
| /// Try to create an Allocation of `size` bytes. Aborts if there is not enough memory | |
| /// available to the compiler to do so. | |
| /// | |
| /// Example use case: To obtain an Allocation filled with specific data, | |
| /// first call this function and then call write_scalar to fill in the right data. | |
| pub fn new( | |
| size: Size, | |
| align: Align, | |
| init: AllocInit, | |
| params: <Bytes as AllocBytes>::AllocParams, | |
| ) -> Self { | |
| match Self::new_inner(size, align, init, params, || { | |
| // `size` may actually be bigger than isize::MAX since it is a *target* size. | |
| // Clamp it to isize::MAX to still give a somewhat reasonable error message. | |
| alloc::handle_alloc_error( | |
| Layout::from_size_align( | |
| size.bytes().min(isize::MAX as u64) as usize, | |
| align.bytes_usize(), | |
| ) | |
| .unwrap(), | |
| ) | |
| }) { | |
| Ok(x) => x, | |
| Err(x) => x, | |
| } | |
| } | |
| /// Add the extra. | |
| pub fn with_extra<Extra>(self, extra: Extra) -> Allocation<Prov, Extra, Bytes> { | |
| Allocation { | |
| bytes: self.bytes, | |
| provenance: self.provenance, | |
| init_mask: self.init_mask, | |
| align: self.align, | |
| mutability: self.mutability, | |
| extra, | |
| } | |
| } | |
| } | |
| impl Allocation { | |
| /// Adjust allocation from the ones in `tcx` to a custom Machine instance | |
| /// with a different `Provenance` and `Byte` type. | |
| pub fn adjust_from_tcx<'tcx, Prov: Provenance, Bytes: AllocBytes>( | |
| &self, | |
| cx: &impl HasDataLayout, | |
| alloc_bytes: impl FnOnce(&[u8], Align) -> InterpResult<'tcx, Bytes>, | |
| mut adjust_ptr: impl FnMut(Pointer<CtfeProvenance>) -> InterpResult<'tcx, Pointer<Prov>>, | |
| ) -> InterpResult<'tcx, Allocation<Prov, (), Bytes>> { | |
| // Copy the data. | |
| let mut bytes = alloc_bytes(&*self.bytes, self.align)?; | |
| // Adjust provenance of pointers stored in this allocation. | |
| let mut new_provenance = Vec::with_capacity(self.provenance.ptrs().len()); | |
| let ptr_size = cx.data_layout().pointer_size().bytes_usize(); | |
| let endian = cx.data_layout().endian; | |
| for &(offset, alloc_id) in self.provenance.ptrs().iter() { | |
| let idx = offset.bytes_usize(); | |
| let ptr_bytes = &mut bytes[idx..idx + ptr_size]; | |
| let bits = read_target_uint(endian, ptr_bytes).unwrap(); | |
| let (ptr_prov, ptr_offset) = | |
| adjust_ptr(Pointer::new(alloc_id, Size::from_bytes(bits)))?.into_raw_parts(); | |
| write_target_uint(endian, ptr_bytes, ptr_offset.bytes().into()).unwrap(); | |
| new_provenance.push((offset, ptr_prov)); | |
| } | |
| // Create allocation. | |
| interp_ok(Allocation { | |
| bytes, | |
| provenance: ProvenanceMap::from_presorted_ptrs(new_provenance), | |
| init_mask: self.init_mask.clone(), | |
| align: self.align, | |
| mutability: self.mutability, | |
| extra: self.extra, | |
| }) | |
| } | |
| } | |
| /// Raw accessors. Provide access to otherwise private bytes. | |
| impl<Prov: Provenance, Extra, Bytes: AllocBytes> Allocation<Prov, Extra, Bytes> { | |
| pub fn len(&self) -> usize { | |
| self.bytes.len() | |
| } | |
| pub fn size(&self) -> Size { | |
| Size::from_bytes(self.len()) | |
| } | |
| /// Looks at a slice which may contain uninitialized bytes or provenance. This differs | |
| /// from `get_bytes_with_uninit_and_ptr` in that it does no provenance checks (even on the | |
| /// edges) at all. | |
| /// This must not be used for reads affecting the interpreter execution. | |
| pub fn inspect_with_uninit_and_ptr_outside_interpreter(&self, range: Range<usize>) -> &[u8] { | |
| &self.bytes[range] | |
| } | |
| /// Returns the mask indicating which bytes are initialized. | |
| pub fn init_mask(&self) -> &InitMask { | |
| &self.init_mask | |
| } | |
| /// Returns the provenance map. | |
| pub fn provenance(&self) -> &ProvenanceMap<Prov> { | |
| &self.provenance | |
| } | |
| } | |
| /// Byte accessors. | |
| impl<Prov: Provenance, Extra, Bytes: AllocBytes> Allocation<Prov, Extra, Bytes> { | |
| /// This is the entirely abstraction-violating way to just grab the raw bytes without | |
| /// caring about provenance or initialization. | |
| /// | |
| /// This function also guarantees that the resulting pointer will remain stable | |
| /// even when new allocations are pushed to the `HashMap`. `mem_copy_repeatedly` relies | |
| /// on that. | |
| #[inline] | |
| pub fn get_bytes_unchecked(&self, range: AllocRange) -> &[u8] { | |
| &self.bytes[range.start.bytes_usize()..range.end().bytes_usize()] | |
| } | |
| /// Checks that these bytes are initialized, and then strip provenance (if possible) and return | |
| /// them. | |
| /// | |
| /// It is the caller's responsibility to check bounds and alignment beforehand. | |
| /// Most likely, you want to use the `PlaceTy` and `OperandTy`-based methods | |
| /// on `InterpCx` instead. | |
| #[inline] | |
| pub fn get_bytes_strip_provenance( | |
| &self, | |
| cx: &impl HasDataLayout, | |
| range: AllocRange, | |
| ) -> AllocResult<&[u8]> { | |
| self.init_mask.is_range_initialized(range).map_err(|uninit_range| { | |
| AllocError::InvalidUninitBytes(Some(BadBytesAccess { | |
| access: range, | |
| bad: uninit_range, | |
| })) | |
| })?; | |
| if !Prov::OFFSET_IS_ADDR && !self.provenance.range_empty(range, cx) { | |
| // Find the provenance. | |
| let (prov_range, _prov) = self | |
| .provenance | |
| .get_range(range, cx) | |
| .next() | |
| .expect("there must be provenance somewhere here"); | |
| let start = prov_range.start.max(range.start); // the pointer might begin before `range`! | |
| let end = prov_range.end().min(range.end()); // the pointer might end after `range`! | |
| return Err(AllocError::ReadPointerAsInt(Some(BadBytesAccess { | |
| access: range, | |
| bad: AllocRange::from(start..end), | |
| }))); | |
| } | |
| Ok(self.get_bytes_unchecked(range)) | |
| } | |
| /// This is the entirely abstraction-violating way to just get mutable access to the raw bytes. | |
| /// Just calling this already marks everything as defined and removes provenance, so be sure to | |
| /// actually overwrite all the data there! | |
| /// | |
| /// It is the caller's responsibility to check bounds and alignment beforehand. | |
| /// Most likely, you want to use the `PlaceTy` and `OperandTy`-based methods | |
| /// on `InterpCx` instead. | |
| pub fn get_bytes_unchecked_for_overwrite( | |
| &mut self, | |
| cx: &impl HasDataLayout, | |
| range: AllocRange, | |
| ) -> &mut [u8] { | |
| self.mark_init(range, true); | |
| self.provenance.clear(range, &self.bytes, cx); | |
| &mut self.bytes[range.start.bytes_usize()..range.end().bytes_usize()] | |
| } | |
| /// A raw pointer variant of `get_bytes_unchecked_for_overwrite` that avoids invalidating existing immutable aliases | |
| /// into this memory. | |
| pub fn get_bytes_unchecked_for_overwrite_ptr( | |
| &mut self, | |
| cx: &impl HasDataLayout, | |
| range: AllocRange, | |
| ) -> *mut [u8] { | |
| self.mark_init(range, true); | |
| self.provenance.clear(range, &self.bytes, cx); | |
| assert!(range.end().bytes_usize() <= self.bytes.len()); // need to do our own bounds-check | |
| // Crucially, we go via `AllocBytes::as_mut_ptr`, not `AllocBytes::deref_mut`. | |
| let begin_ptr = self.bytes.as_mut_ptr().wrapping_add(range.start.bytes_usize()); | |
| let len = range.end().bytes_usize() - range.start.bytes_usize(); | |
| ptr::slice_from_raw_parts_mut(begin_ptr, len) | |
| } | |
| /// This gives direct mutable access to the entire buffer, just exposing their internal state | |
| /// without resetting anything. Directly exposes `AllocBytes::as_mut_ptr`. Only works if | |
| /// `OFFSET_IS_ADDR` is true. | |
| pub fn get_bytes_unchecked_raw_mut(&mut self) -> *mut u8 { | |
| assert!(Prov::OFFSET_IS_ADDR); | |
| self.bytes.as_mut_ptr() | |
| } | |
| /// This gives direct immutable access to the entire buffer, just exposing their internal state | |
| /// without resetting anything. Directly exposes `AllocBytes::as_ptr`. Only works if | |
| /// `OFFSET_IS_ADDR` is true. | |
| pub fn get_bytes_unchecked_raw(&self) -> *const u8 { | |
| assert!(Prov::OFFSET_IS_ADDR); | |
| self.bytes.as_ptr() | |
| } | |
| } | |
| /// Reading and writing. | |
| impl<Prov: Provenance, Extra, Bytes: AllocBytes> Allocation<Prov, Extra, Bytes> { | |
| /// Sets the init bit for the given range. | |
| fn mark_init(&mut self, range: AllocRange, is_init: bool) { | |
| if range.size.bytes() == 0 { | |
| return; | |
| } | |
| assert!(self.mutability == Mutability::Mut); | |
| self.init_mask.set_range(range, is_init); | |
| } | |
| /// Reads a *non-ZST* scalar. | |
| /// | |
| /// If `read_provenance` is `true`, this will also read provenance; otherwise (if the machine | |
| /// supports that) provenance is entirely ignored. | |
| /// | |
| /// ZSTs can't be read because in order to obtain a `Pointer`, we need to check | |
| /// for ZSTness anyway due to integer pointers being valid for ZSTs. | |
| /// | |
| /// It is the caller's responsibility to check bounds and alignment beforehand. | |
| /// Most likely, you want to call `InterpCx::read_scalar` instead of this method. | |
| pub fn read_scalar( | |
| &self, | |
| cx: &impl HasDataLayout, | |
| range: AllocRange, | |
| read_provenance: bool, | |
| ) -> AllocResult<Scalar<Prov>> { | |
| // First and foremost, if anything is uninit, bail. | |
| if let Err(bad) = self.init_mask.is_range_initialized(range) { | |
| return Err(AllocError::InvalidUninitBytes(Some(BadBytesAccess { | |
| access: range, | |
| bad, | |
| }))); | |
| } | |
| // Get the integer part of the result. We HAVE TO check provenance before returning this! | |
| let bytes = self.get_bytes_unchecked(range); | |
| let bits = read_target_uint(cx.data_layout().endian, bytes).unwrap(); | |
| if read_provenance { | |
| assert_eq!(range.size, cx.data_layout().pointer_size()); | |
| if let Some(prov) = self.provenance.read_ptr(range.start, cx)? { | |
| // Assemble the bits with their provenance. | |
| let ptr = Pointer::new(prov, Size::from_bytes(bits)); | |
| Ok(Scalar::from_pointer(ptr, cx)) | |
| } else { | |
| // Return raw bits without provenance. | |
| Ok(Scalar::from_uint(bits, range.size)) | |
| } | |
| } else { | |
| // We are *not* reading a pointer. | |
| // If we can just ignore provenance or there is none, that's easy. | |
| if Prov::OFFSET_IS_ADDR || self.provenance.range_empty(range, cx) { | |
| // We just strip provenance. | |
| return Ok(Scalar::from_uint(bits, range.size)); | |
| } | |
| // There is some provenance and we don't have OFFSET_IS_ADDR. This doesn't work. | |
| return Err(AllocError::ReadPointerAsInt(None)); | |
| } | |
| } | |
| /// Writes a *non-ZST* scalar. | |
| /// | |
| /// ZSTs can't be read because in order to obtain a `Pointer`, we need to check | |
| /// for ZSTness anyway due to integer pointers being valid for ZSTs. | |
| /// | |
| /// It is the caller's responsibility to check bounds and alignment beforehand. | |
| /// Most likely, you want to call `InterpCx::write_scalar` instead of this method. | |
| pub fn write_scalar( | |
| &mut self, | |
| cx: &impl HasDataLayout, | |
| range: AllocRange, | |
| val: Scalar<Prov>, | |
| ) -> AllocResult { | |
| assert!(self.mutability == Mutability::Mut); | |
| // `to_bits_or_ptr_internal` is the right method because we just want to store this data | |
| // as-is into memory. This also double-checks that `val.size()` matches `range.size`. | |
| let (bytes, provenance) = match val.to_bits_or_ptr_internal(range.size)? { | |
| Right(ptr) => { | |
| let (provenance, offset) = ptr.into_raw_parts(); | |
| (u128::from(offset.bytes()), Some(provenance)) | |
| } | |
| Left(data) => (data, None), | |
| }; | |
| let endian = cx.data_layout().endian; | |
| // Yes we do overwrite all the bytes in `dst`. | |
| let dst = self.get_bytes_unchecked_for_overwrite(cx, range); | |
| write_target_uint(endian, dst, bytes).unwrap(); | |
| // See if we have to also store some provenance. | |
| if let Some(provenance) = provenance { | |
| assert_eq!(range.size, cx.data_layout().pointer_size()); | |
| self.provenance.insert_ptr(range.start, provenance, cx); | |
| } | |
| Ok(()) | |
| } | |
| /// Write "uninit" to the given memory range. | |
| pub fn write_uninit(&mut self, cx: &impl HasDataLayout, range: AllocRange) { | |
| self.mark_init(range, false); | |
| self.provenance.clear(range, &self.bytes, cx); | |
| } | |
| /// Mark all bytes in the given range as initialised and reset the provenance | |
| /// to wildcards. This entirely breaks the normal mechanisms for tracking | |
| /// initialisation and is only provided for Miri operating in native-lib | |
| /// mode. UB will be missed if the underlying bytes were not actually written to. | |
| /// | |
| /// If `range` is `None`, defaults to performing this on the whole allocation. | |
| pub fn process_native_write(&mut self, cx: &impl HasDataLayout, range: Option<AllocRange>) { | |
| let range = range.unwrap_or_else(|| AllocRange { | |
| start: Size::ZERO, | |
| size: Size::from_bytes(self.len()), | |
| }); | |
| self.mark_init(range, true); | |
| self.provenance.write_wildcards(cx, &self.bytes, range); | |
| } | |
| /// Remove all provenance in the given memory range. | |
| pub fn clear_provenance(&mut self, cx: &impl HasDataLayout, range: AllocRange) { | |
| self.provenance.clear(range, &self.bytes, cx); | |
| } | |
| pub fn provenance_merge_bytes(&mut self, cx: &impl HasDataLayout) -> bool { | |
| self.provenance.merge_bytes(cx) | |
| } | |
| pub fn provenance_prepare_copy( | |
| &self, | |
| range: AllocRange, | |
| cx: &impl HasDataLayout, | |
| ) -> ProvenanceCopy<Prov> { | |
| self.provenance.prepare_copy(range, &self.bytes, cx) | |
| } | |
| /// Applies a previously prepared provenance copy. | |
| /// The affected range is expected to be clear of provenance. | |
| /// | |
| /// This is dangerous to use as it can violate internal `Allocation` invariants! | |
| /// It only exists to support an efficient implementation of `mem_copy_repeatedly`. | |
| pub fn provenance_apply_copy( | |
| &mut self, | |
| copy: ProvenanceCopy<Prov>, | |
| range: AllocRange, | |
| repeat: u64, | |
| ) { | |
| self.provenance.apply_copy(copy, range, repeat) | |
| } | |
| /// Applies a previously prepared copy of the init mask. | |
| /// | |
| /// This is dangerous to use as it can violate internal `Allocation` invariants! | |
| /// It only exists to support an efficient implementation of `mem_copy_repeatedly`. | |
| pub fn init_mask_apply_copy(&mut self, copy: InitCopy, range: AllocRange, repeat: u64) { | |
| self.init_mask.apply_copy(copy, range, repeat) | |
| } | |
| } |