Float,
};
use rustc_macros::HashStable;
-use rustc_target::abi::{HasDataLayout, Size, TargetDataLayout};
+use rustc_target::abi::{HasDataLayout, Size};
use crate::ty::{Lift, ParamEnv, ScalarInt, Ty, TyCtxt};
-use super::{AllocId, AllocRange, Allocation, InterpResult, Pointer, PointerArithmetic};
+use super::{
+ AllocId, AllocRange, Allocation, InterpResult, Pointer, PointerArithmetic, Provenance,
+};
/// Represents the result of const evaluation via the `eval_to_allocation` query.
#[derive(Copy, Clone, HashStable, TyEncodable, TyDecodable, Debug, Hash, Eq, PartialEq)]
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
static_assert_size!(ConstValue<'_>, 32);
-impl From<Scalar> for ConstValue<'tcx> {
- fn from(s: Scalar) -> Self {
- Self::Scalar(s)
- }
-}
-
impl<'a, 'tcx> Lift<'tcx> for ConstValue<'a> {
type Lifted = ConstValue<'tcx>;
fn lift_to_tcx(self, tcx: TyCtxt<'tcx>) -> Option<ConstValue<'tcx>> {
impl<'tcx> ConstValue<'tcx> {
#[inline]
- pub fn try_to_scalar(&self) -> Option<Scalar> {
+ pub fn try_to_scalar(&self) -> Option<Scalar<AllocId>> {
match *self {
ConstValue::ByRef { .. } | ConstValue::Slice { .. } => None,
ConstValue::Scalar(val) => Some(val),
/// `memory::Allocation`. It is in many ways like a small chunk of a `Allocation`, up to 16 bytes in
/// size. Like a range of bytes in an `Allocation`, a `Scalar` can either represent the raw bytes
/// of a simple value or a pointer into another `Allocation`
+///
+/// These variants would be private if there was a convenient way to achieve that in Rust.
+/// Do *not* match on a `Scalar`! Use the various `to_*` methods instead.
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, TyEncodable, TyDecodable, Hash)]
#[derive(HashStable)]
-pub enum Scalar<Tag = ()> {
+pub enum Scalar<Tag = AllocId> {
/// The raw bytes of a simple value.
Int(ScalarInt),
/// A pointer into an `Allocation`. An `Allocation` in the `memory` module has a list of
/// relocations, but a `Scalar` is only large enough to contain one, so we just represent the
/// relocation and its associated offset together as a `Pointer` here.
- Ptr(Pointer<Tag>),
+ ///
+ /// We also store the size of the pointer, such that a `Scalar` always knows how big it is.
+ /// The size is always the pointer size of the current target, but this is not information
+ /// that we always have readily available.
+ Ptr(Pointer<Tag>, u8),
}
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
// We want the `Debug` output to be readable as it is used by `derive(Debug)` for
// all the Miri types.
-impl<Tag: fmt::Debug> fmt::Debug for Scalar<Tag> {
+impl<Tag: Provenance> fmt::Debug for Scalar<Tag> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
- Scalar::Ptr(ptr) => write!(f, "{:?}", ptr),
+ Scalar::Ptr(ptr, _size) => write!(f, "{:?}", ptr),
Scalar::Int(int) => write!(f, "{:?}", int),
}
}
}
-impl<Tag: fmt::Debug> fmt::Display for Scalar<Tag> {
+impl<Tag: Provenance> fmt::Display for Scalar<Tag> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
- Scalar::Ptr(ptr) => write!(f, "pointer to {}", ptr),
- Scalar::Int { .. } => fmt::Debug::fmt(self, f),
+ Scalar::Ptr(ptr, _size) => write!(f, "pointer to {:?}", ptr),
+ Scalar::Int(int) => write!(f, "{:?}", int),
}
}
}
}
}
-impl Scalar<()> {
- /// Tag this scalar with `new_tag` if it is a pointer, leave it unchanged otherwise.
- ///
- /// Used by `MemPlace::replace_tag`.
- #[inline]
- pub fn with_tag<Tag>(self, new_tag: Tag) -> Scalar<Tag> {
- match self {
- Scalar::Ptr(ptr) => Scalar::Ptr(ptr.with_tag(new_tag)),
- Scalar::Int(int) => Scalar::Int(int),
- }
+impl<Tag> From<ScalarInt> for Scalar<Tag> {
+ #[inline(always)]
+ fn from(ptr: ScalarInt) -> Self {
+ Scalar::Int(ptr)
}
}
-impl<'tcx, Tag> Scalar<Tag> {
+impl<Tag> Scalar<Tag> {
pub const ZST: Self = Scalar::Int(ScalarInt::ZST);
- /// Erase the tag from the scalar, if any.
- ///
- /// Used by error reporting code to avoid having the error type depend on `Tag`.
- #[inline]
- pub fn erase_tag(self) -> Scalar {
- match self {
- Scalar::Ptr(ptr) => Scalar::Ptr(ptr.erase_tag()),
- Scalar::Int(int) => Scalar::Int(int),
- }
- }
-
- #[inline]
- pub fn null_ptr(cx: &impl HasDataLayout) -> Self {
- Scalar::Int(ScalarInt::null(cx.data_layout().pointer_size))
- }
-
#[inline(always)]
- fn ptr_op(
- self,
- dl: &TargetDataLayout,
- f_int: impl FnOnce(u64) -> InterpResult<'tcx, u64>,
- f_ptr: impl FnOnce(Pointer<Tag>) -> InterpResult<'tcx, Pointer<Tag>>,
- ) -> InterpResult<'tcx, Self> {
- match self {
- Scalar::Int(int) => Ok(Scalar::Int(int.ptr_sized_op(dl, f_int)?)),
- Scalar::Ptr(ptr) => Ok(Scalar::Ptr(f_ptr(ptr)?)),
- }
- }
-
- #[inline]
- pub fn ptr_offset(self, i: Size, cx: &impl HasDataLayout) -> InterpResult<'tcx, Self> {
- let dl = cx.data_layout();
- self.ptr_op(dl, |int| dl.offset(int, i.bytes()), |ptr| ptr.offset(i, dl))
+ pub fn from_pointer(ptr: Pointer<Tag>, cx: &impl HasDataLayout) -> Self {
+ Scalar::Ptr(ptr, u8::try_from(cx.pointer_size().bytes()).unwrap())
}
- #[inline]
- pub fn ptr_wrapping_offset(self, i: Size, cx: &impl HasDataLayout) -> Self {
- let dl = cx.data_layout();
- self.ptr_op(
- dl,
- |int| Ok(dl.overflowing_offset(int, i.bytes()).0),
- |ptr| Ok(ptr.wrapping_offset(i, dl)),
- )
- .unwrap()
- }
-
- #[inline]
- pub fn ptr_signed_offset(self, i: i64, cx: &impl HasDataLayout) -> InterpResult<'tcx, Self> {
- let dl = cx.data_layout();
- self.ptr_op(dl, |int| dl.signed_offset(int, i), |ptr| ptr.signed_offset(i, dl))
+ /// Create a Scalar from a pointer with an `Option<_>` tag (where `None` represents a plain integer).
+ pub fn from_maybe_pointer(ptr: Pointer<Option<Tag>>, cx: &impl HasDataLayout) -> Self {
+ match ptr.into_parts() {
+ (Some(tag), offset) => Scalar::from_pointer(Pointer::new(tag, offset), cx),
+ (None, offset) => {
+ Scalar::Int(ScalarInt::try_from_uint(offset.bytes(), cx.pointer_size()).unwrap())
+ }
+ }
}
#[inline]
- pub fn ptr_wrapping_signed_offset(self, i: i64, cx: &impl HasDataLayout) -> Self {
- let dl = cx.data_layout();
- self.ptr_op(
- dl,
- |int| Ok(dl.overflowing_signed_offset(int, i).0),
- |ptr| Ok(ptr.wrapping_signed_offset(i, dl)),
- )
- .unwrap()
+ pub fn null_ptr(cx: &impl HasDataLayout) -> Self {
+ Scalar::Int(ScalarInt::null(cx.pointer_size()))
}
#[inline]
Scalar::Int(f.into())
}
- /// This is very rarely the method you want! You should dispatch on the type
- /// and use `force_bits`/`assert_bits`/`force_ptr`/`assert_ptr`.
- /// This method only exists for the benefit of low-level memory operations
- /// as well as the implementation of the `force_*` methods.
+ /// This is almost certainly not the method you want! You should dispatch on the type
+ /// and use `to_{u8,u16,...}`/`scalar_to_ptr` to perform ptr-to-int / int-to-ptr casts as needed.
+ ///
+ /// This method only exists for the benefit of low-level operations that truly need to treat the
+ /// scalar in whatever form it is.
#[inline]
- pub fn to_bits_or_ptr(
- self,
- target_size: Size,
- cx: &impl HasDataLayout,
- ) -> Result<u128, Pointer<Tag>> {
+ pub fn to_bits_or_ptr_internal(self, target_size: Size) -> Result<u128, Pointer<Tag>> {
assert_ne!(target_size.bytes(), 0, "you should never look at the bits of a ZST");
match self {
Scalar::Int(int) => Ok(int.assert_bits(target_size)),
- Scalar::Ptr(ptr) => {
- assert_eq!(target_size, cx.data_layout().pointer_size);
+ Scalar::Ptr(ptr, sz) => {
+ assert_eq!(target_size.bytes(), u64::from(sz));
Err(ptr)
}
}
}
+}
- /// This method is intentionally private!
- /// It is just a helper for other methods in this file.
+impl<'tcx, Tag: Provenance> Scalar<Tag> {
+ /// Fundamental scalar-to-int (cast) operation. Many convenience wrappers exist below, that you
+ /// likely want to use instead.
+ ///
+ /// Will perform ptr-to-int casts if needed and possible.
+ /// If that fails, we know the offset is relative, so we return an "erased" Scalar
+ /// (which is useful for error messages but not much else).
#[inline]
- fn to_bits(self, target_size: Size) -> InterpResult<'tcx, u128> {
- assert_ne!(target_size.bytes(), 0, "you should never look at the bits of a ZST");
+ pub fn try_to_int(self) -> Result<ScalarInt, Scalar<AllocId>> {
match self {
- Scalar::Int(int) => int.to_bits(target_size).map_err(|size| {
- err_ub!(ScalarSizeMismatch {
- target_size: target_size.bytes(),
- data_size: size.bytes(),
- })
- .into()
- }),
- Scalar::Ptr(_) => throw_unsup!(ReadPointerAsBytes),
+ Scalar::Int(int) => Ok(int),
+ Scalar::Ptr(ptr, sz) => {
+ if Tag::OFFSET_IS_ADDR {
+ Ok(ScalarInt::try_from_uint(ptr.offset.bytes(), Size::from_bytes(sz)).unwrap())
+ } else {
+ // We know `offset` is relative, since `OFFSET_IS_ADDR == false`.
+ let (tag, offset) = ptr.into_parts();
+ Err(Scalar::Ptr(Pointer::new(tag.get_alloc_id(), offset), sz))
+ }
+ }
}
}
#[inline(always)]
- pub fn assert_bits(self, target_size: Size) -> u128 {
- self.to_bits(target_size).expect("expected Raw bits but got a Pointer")
- }
-
- #[inline]
pub fn assert_int(self) -> ScalarInt {
- match self {
- Scalar::Ptr(_) => bug!("expected an int but got an abstract pointer"),
- Scalar::Int(int) => int,
- }
+ self.try_to_int().unwrap()
}
+ /// This throws UB (instead of ICEing) on a size mismatch since size mismatches can arise in
+ /// Miri when someone declares a function that we shim (such as `malloc`) with a wrong type.
#[inline]
- pub fn assert_ptr(self) -> Pointer<Tag> {
- match self {
- Scalar::Ptr(p) => p,
- Scalar::Int { .. } => bug!("expected a Pointer but got Raw bits"),
- }
- }
-
- /// Do not call this method! Dispatch based on the type instead.
- #[inline]
- pub fn is_bits(self) -> bool {
- matches!(self, Scalar::Int { .. })
+ pub fn to_bits(self, target_size: Size) -> InterpResult<'tcx, u128> {
+ assert_ne!(target_size.bytes(), 0, "you should never look at the bits of a ZST");
+ self.try_to_int().map_err(|_| err_unsup!(ReadPointerAsBytes))?.to_bits(target_size).map_err(
+ |size| {
+ err_ub!(ScalarSizeMismatch {
+ target_size: target_size.bytes(),
+ data_size: size.bytes(),
+ })
+ .into()
+ },
+ )
}
- /// Do not call this method! Dispatch based on the type instead.
- #[inline]
- pub fn is_ptr(self) -> bool {
- matches!(self, Scalar::Ptr(_))
+ #[inline(always)]
+ pub fn assert_bits(self, target_size: Size) -> u128 {
+ self.to_bits(target_size).unwrap()
}
pub fn to_bool(self) -> InterpResult<'tcx, bool> {
}
}
-impl<Tag> From<Pointer<Tag>> for Scalar<Tag> {
- #[inline(always)]
- fn from(ptr: Pointer<Tag>) -> Self {
- Scalar::Ptr(ptr)
- }
-}
-
-impl<Tag> From<ScalarInt> for Scalar<Tag> {
- #[inline(always)]
- fn from(ptr: ScalarInt) -> Self {
- Scalar::Int(ptr)
- }
-}
-
#[derive(Clone, Copy, Eq, PartialEq, TyEncodable, TyDecodable, HashStable, Hash)]
-pub enum ScalarMaybeUninit<Tag = ()> {
+pub enum ScalarMaybeUninit<Tag = AllocId> {
Scalar(Scalar<Tag>),
Uninit,
}
}
}
-impl<Tag> From<Pointer<Tag>> for ScalarMaybeUninit<Tag> {
- #[inline(always)]
- fn from(s: Pointer<Tag>) -> Self {
- ScalarMaybeUninit::Scalar(s.into())
- }
-}
-
// We want the `Debug` output to be readable as it is used by `derive(Debug)` for
// all the Miri types.
-impl<Tag: fmt::Debug> fmt::Debug for ScalarMaybeUninit<Tag> {
+impl<Tag: Provenance> fmt::Debug for ScalarMaybeUninit<Tag> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ScalarMaybeUninit::Uninit => write!(f, "<uninitialized>"),
}
}
-impl<Tag: fmt::Debug> fmt::Display for ScalarMaybeUninit<Tag> {
+impl<Tag: Provenance> fmt::Display for ScalarMaybeUninit<Tag> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ScalarMaybeUninit::Uninit => write!(f, "uninitialized bytes"),
}
}
-impl<'tcx, Tag> ScalarMaybeUninit<Tag> {
- /// Erase the tag from the scalar, if any.
- ///
- /// Used by error reporting code to avoid having the error type depend on `Tag`.
+impl<Tag> ScalarMaybeUninit<Tag> {
#[inline]
- pub fn erase_tag(self) -> ScalarMaybeUninit {
- match self {
- ScalarMaybeUninit::Scalar(s) => ScalarMaybeUninit::Scalar(s.erase_tag()),
- ScalarMaybeUninit::Uninit => ScalarMaybeUninit::Uninit,
- }
+ pub fn from_pointer(ptr: Pointer<Tag>, cx: &impl HasDataLayout) -> Self {
+ ScalarMaybeUninit::Scalar(Scalar::from_pointer(ptr, cx))
+ }
+
+ #[inline]
+ pub fn from_maybe_pointer(ptr: Pointer<Option<Tag>>, cx: &impl HasDataLayout) -> Self {
+ ScalarMaybeUninit::Scalar(Scalar::from_maybe_pointer(ptr, cx))
}
#[inline]
ScalarMaybeUninit::Uninit => throw_ub!(InvalidUninitBytes(None)),
}
}
+}
+impl<'tcx, Tag: Provenance> ScalarMaybeUninit<Tag> {
#[inline(always)]
pub fn to_bool(self) -> InterpResult<'tcx, bool> {
self.check_init()?.to_bool()