]> git.lizzy.rs Git - rust.git/blobdiff - compiler/rustc_middle/src/mir/interpret/pointer.rs
CTFE/Miri engine Pointer type overhaul: make Scalar-to-Pointer conversion infallible
[rust.git] / compiler / rustc_middle / src / mir / interpret / pointer.rs
index 8774b48fb3e403665dae7b75ce9d3b96d2ff6d55..c9dc96fc88e0290cccf2215e571401fdcaf913e5 100644 (file)
@@ -83,55 +83,74 @@ fn signed_offset<'tcx>(&self, val: u64, i: i64) -> InterpResult<'tcx, u64> {
 
 impl<T: HasDataLayout> PointerArithmetic for T {}
 
-/// Represents a pointer in the Miri engine.
-///
-/// `Pointer` is generic over the `Tag` associated with each pointer,
-/// which is used to do provenance tracking during execution.
-#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, TyEncodable, TyDecodable, Hash)]
-#[derive(HashStable)]
-pub struct Pointer<Tag = ()> {
-    pub alloc_id: AllocId,
-    pub offset: Size,
-    pub tag: Tag,
+/// This trait abstracts over the kind of provenance that is associated with a `Pointer`. It is
+/// mostly opaque; the `Machine` trait extends it with some more operations that also have access to
+/// some global state.
+pub trait Provenance: Copy {
+    /// Says whether the `offset` field of `Pointer` is the actual physical address.
+    /// If `true, ptr-to-int casts work by simply discarding the provenance.
+    /// If `false`, ptr-to-int casts are not supported.
+    const OFFSET_IS_ADDR: bool;
+
+    /// Determines how a pointer should be printed.
+    fn fmt(ptr: &Pointer<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result
+    where
+        Self: Sized;
+
+    /// "Erasing" a tag converts it to the default tag type if possible. Used only for formatting purposes!
+    fn erase_for_fmt(self) -> AllocId;
 }
 
-static_assert_size!(Pointer, 16);
+impl Provenance for AllocId {
+    // With the `AllocId` as provenance, the `offset` is interpreted *relative to the allocation*,
+    // so ptr-to-int casts are not possible (since we do not know the global physical offset).
+    const OFFSET_IS_ADDR: bool = false;
 
-/// Print the address of a pointer (without the tag)
-fn print_ptr_addr<Tag>(ptr: &Pointer<Tag>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-    // Forward `alternate` flag to `alloc_id` printing.
-    if f.alternate() {
-        write!(f, "{:#?}", ptr.alloc_id)?;
-    } else {
-        write!(f, "{:?}", ptr.alloc_id)?;
+    fn fmt(ptr: &Pointer<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        // Forward `alternate` flag to `alloc_id` printing.
+        if f.alternate() {
+            write!(f, "{:#?}", ptr.provenance)?;
+        } else {
+            write!(f, "{:?}", ptr.provenance)?;
+        }
+        // Print offset only if it is non-zero.
+        if ptr.offset.bytes() > 0 {
+            write!(f, "+0x{:x}", ptr.offset.bytes())?;
+        }
+        Ok(())
     }
-    // Print offset only if it is non-zero.
-    if ptr.offset.bytes() > 0 {
-        write!(f, "+0x{:x}", ptr.offset.bytes())?;
+
+    fn erase_for_fmt(self) -> AllocId {
+        self
     }
-    Ok(())
 }
 
+/// Represents a pointer in the Miri engine.
+///
+/// Pointers are "tagged" with provenance information; typically the `AllocId` they belong to.
+#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, TyEncodable, TyDecodable, Hash)]
+#[derive(HashStable)]
+pub struct Pointer<Tag = AllocId> {
+    pub(super) offset: Size, // kept private to avoid accidental misinterpretation (meaning depends on `Tag` type)
+    pub provenance: Tag,
+}
+
+//FIXME static_assert_size!(Pointer, 16);
+
 // We want the `Debug` output to be readable as it is used by `derive(Debug)` for
 // all the Miri types.
-// We have to use `Debug` output for the tag, because `()` does not implement
-// `Display` so we cannot specialize that.
-impl<Tag: fmt::Debug> fmt::Debug for Pointer<Tag> {
-    default fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        print_ptr_addr(self, f)?;
-        write!(f, "[{:?}]", self.tag)
-    }
-}
-// Specialization for no tag
-impl fmt::Debug for Pointer<()> {
+impl<Tag: Provenance> fmt::Debug for Pointer<Tag> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        print_ptr_addr(self, f)
+        Tag::fmt(self, f)
     }
 }
 
-impl<Tag: fmt::Debug> fmt::Display for Pointer<Tag> {
+impl<Tag: Provenance> fmt::Debug for Pointer<Option<Tag>> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        fmt::Debug::fmt(self, f)
+        match self.provenance {
+            Some(tag) => Tag::fmt(&Pointer::new(tag, self.offset), f),
+            None => write!(f, "0x{:x}", self.offset.bytes()),
+        }
     }
 }
 
@@ -143,37 +162,66 @@ fn from(alloc_id: AllocId) -> Self {
     }
 }
 
-impl Pointer<()> {
+impl<Tag> From<Pointer<Tag>> for Pointer<Option<Tag>> {
     #[inline(always)]
-    pub fn new(alloc_id: AllocId, offset: Size) -> Self {
-        Pointer { alloc_id, offset, tag: () }
+    fn from(ptr: Pointer<Tag>) -> Self {
+        let (tag, offset) = ptr.into_parts();
+        Pointer::new(Some(tag), offset)
+    }
+}
+
+impl<Tag> Pointer<Option<Tag>> {
+    pub fn into_pointer_or_offset(self) -> Result<Pointer<Tag>, Size> {
+        match self.provenance {
+            Some(tag) => Ok(Pointer::new(tag, self.offset)),
+            None => Err(self.offset),
+        }
     }
 
     #[inline(always)]
-    pub fn with_tag<Tag>(self, tag: Tag) -> Pointer<Tag> {
-        Pointer::new_with_tag(self.alloc_id, self.offset, tag)
+    pub fn map_erase_for_fmt(self) -> Pointer<Option<AllocId>>
+    where
+        Tag: Provenance,
+    {
+        Pointer { offset: self.offset, provenance: self.provenance.map(Provenance::erase_for_fmt) }
     }
 }
 
 impl<'tcx, Tag> Pointer<Tag> {
     #[inline(always)]
-    pub fn new_with_tag(alloc_id: AllocId, offset: Size, tag: Tag) -> Self {
-        Pointer { alloc_id, offset, tag }
+    pub fn new(provenance: Tag, offset: Size) -> Self {
+        Pointer { provenance, offset }
+    }
+
+    /// Obtain the constituents of this pointer. Not that the meaning of the offset depends on the type `Tag`!
+    /// This function must only be used in the implementation of `Machine::ptr_get_alloc`,
+    /// and when a `Pointer` is taken apart to be stored efficiently in an `Allocation`.
+    #[inline(always)]
+    pub fn into_parts(self) -> (Tag, Size) {
+        (self.provenance, self.offset)
+    }
+
+    #[inline(always)]
+    pub fn erase_for_fmt(self) -> Pointer
+    where
+        Tag: Provenance,
+    {
+        Pointer { offset: self.offset, provenance: self.provenance.erase_for_fmt() }
     }
 
     #[inline]
     pub fn offset(self, i: Size, cx: &impl HasDataLayout) -> InterpResult<'tcx, Self> {
-        Ok(Pointer::new_with_tag(
-            self.alloc_id,
-            Size::from_bytes(cx.data_layout().offset(self.offset.bytes(), i.bytes())?),
-            self.tag,
-        ))
+        Ok(Pointer {
+            offset: Size::from_bytes(cx.data_layout().offset(self.offset.bytes(), i.bytes())?),
+            ..self
+        })
     }
 
     #[inline]
     pub fn overflowing_offset(self, i: Size, cx: &impl HasDataLayout) -> (Self, bool) {
         let (res, over) = cx.data_layout().overflowing_offset(self.offset.bytes(), i.bytes());
-        (Pointer::new_with_tag(self.alloc_id, Size::from_bytes(res), self.tag), over)
+        let ptr = Pointer { offset: Size::from_bytes(res), ..self };
+        (ptr, over)
     }
 
     #[inline(always)]
@@ -183,26 +231,21 @@ pub fn wrapping_offset(self, i: Size, cx: &impl HasDataLayout) -> Self {
 
     #[inline]
     pub fn signed_offset(self, i: i64, cx: &impl HasDataLayout) -> InterpResult<'tcx, Self> {
-        Ok(Pointer::new_with_tag(
-            self.alloc_id,
-            Size::from_bytes(cx.data_layout().signed_offset(self.offset.bytes(), i)?),
-            self.tag,
-        ))
+        Ok(Pointer {
+            offset: Size::from_bytes(cx.data_layout().signed_offset(self.offset.bytes(), i)?),
+            ..self
+        })
     }
 
     #[inline]
     pub fn overflowing_signed_offset(self, i: i64, cx: &impl HasDataLayout) -> (Self, bool) {
         let (res, over) = cx.data_layout().overflowing_signed_offset(self.offset.bytes(), i);
-        (Pointer::new_with_tag(self.alloc_id, Size::from_bytes(res), self.tag), over)
+        let ptr = Pointer { offset: Size::from_bytes(res), ..self };
+        (ptr, over)
     }
 
     #[inline(always)]
     pub fn wrapping_signed_offset(self, i: i64, cx: &impl HasDataLayout) -> Self {
         self.overflowing_signed_offset(i, cx).0
     }
-
-    #[inline(always)]
-    pub fn erase_tag(self) -> Pointer {
-        Pointer { alloc_id: self.alloc_id, offset: self.offset, tag: () }
-    }
 }