]> git.lizzy.rs Git - rust.git/blobdiff - src/stacked_borrows.rs
Tidy up comments and function layout, should fix most of the review notes.
[rust.git] / src / stacked_borrows.rs
index 152171aed066aff3814625186d486c237ac10fd8..616950eb0a0a44a5ca923ae78a7a68c3c2c6e9e3 100644 (file)
@@ -1,33 +1,36 @@
+//! Implements "Stacked Borrows".  See <https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md>
+//! for further information.
+
 use std::cell::RefCell;
-use std::collections::HashSet;
-use std::rc::Rc;
 use std::fmt;
 use std::num::NonZeroU64;
+use std::rc::Rc;
 
-use rustc::ty::{self, layout::Size};
-use rustc::hir::{Mutability, MutMutable, MutImmutable};
-use rustc::mir::RetagKind;
+use log::trace;
 
-use crate::{
-    EvalResult, InterpError, MiriEvalContext, HelpersEvalContextExt, Evaluator, MutValueVisitor,
-    MemoryKind, MiriMemoryKind, RangeMap, Allocation, AllocationExtra,
-    Pointer, Immediate, ImmTy, PlaceTy, MPlaceTy,
-};
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_middle::mir::RetagKind;
+use rustc_middle::ty;
+use rustc_target::abi::{Align, LayoutOf, Size};
+use rustc_hir::Mutability;
+
+use crate::*;
 
 pub type PtrId = NonZeroU64;
-pub type CallId = u64;
+pub type CallId = NonZeroU64;
+pub type AllocExtra = Stacks;
 
 /// Tracking pointer provenance
-#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
+#[derive(Copy, Clone, Hash, PartialEq, Eq)]
 pub enum Tag {
     Tagged(PtrId),
     Untagged,
 }
 
-impl fmt::Display for Tag {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+impl fmt::Debug for Tag {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
-            Tag::Tagged(id) => write!(f, "{}", id),
+            Tag::Tagged(id) => write!(f, "<{}>", id),
             Tag::Untagged => write!(f, "<untagged>"),
         }
     }
@@ -40,25 +43,32 @@ pub enum Permission {
     Unique,
     /// Grants shared mutable access.
     SharedReadWrite,
-    /// Greants shared read-only access.
+    /// Grants shared read-only access.
     SharedReadOnly,
+    /// Grants no access, but separates two groups of SharedReadWrite so they are not
+    /// all considered mutually compatible.
+    Disabled,
 }
 
 /// An item in the per-location borrow stack.
-#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
-pub enum Item {
-    /// Grants the given permission for pointers with this tag.
-    Permission(Permission, Tag),
-    /// A barrier, tracking the function it belongs to by its index on the call stack.
-    FnBarrier(CallId),
+#[derive(Copy, Clone, Hash, PartialEq, Eq)]
+pub struct Item {
+    /// The permission this item grants.
+    perm: Permission,
+    /// The pointers the permission is granted to.
+    tag: Tag,
+    /// An optional protector, ensuring the item cannot get popped until `CallId` is over.
+    protector: Option<CallId>,
 }
 
-impl fmt::Display for Item {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Item::Permission(perm, tag) => write!(f, "[{:?} for {}]", perm, tag),
-            Item::FnBarrier(call) => write!(f, "[barrier {}]", call),
+impl fmt::Debug for Item {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "[{:?} for {:?}", self.perm, self.tag)?;
+        if let Some(call) = self.protector {
+            write!(f, " (call {})", call)?;
         }
+        write!(f, "]")?;
+        Ok(())
     }
 }
 
@@ -66,98 +76,104 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct Stack {
     /// Used *mostly* as a stack; never empty.
-    /// We sometimes push into the middle but never remove from the middle.
-    /// The same tag may occur multiple times, e.g. from a two-phase borrow.
     /// Invariants:
-    /// * Above a `SharedReadOnly` there can only be barriers and more `SharedReadOnly`.
+    /// * Above a `SharedReadOnly` there can only be more `SharedReadOnly`.
+    /// * Except for `Untagged`, no tag occurs in the stack more than once.
     borrows: Vec<Item>,
 }
 
-
 /// Extra per-allocation state.
 #[derive(Clone, Debug)]
 pub struct Stacks {
     // Even reading memory can have effects on the stack, so we need a `RefCell` here.
     stacks: RefCell<RangeMap<Stack>>,
     // Pointer to global state
-    global: MemoryState,
+    global: MemoryExtra,
 }
 
 /// Extra global state, available to the memory access hooks.
 #[derive(Debug)]
 pub struct GlobalState {
+    /// Next unused pointer ID (tag).
     next_ptr_id: PtrId,
+    /// Table storing the "base" tag for each allocation.
+    /// The base tag is the one used for the initial pointer.
+    /// We need this in a separate table to handle cyclic statics.
+    base_ptr_ids: FxHashMap<AllocId, Tag>,
+    /// Next unused call ID (for protectors).
     next_call_id: CallId,
-    active_calls: HashSet<CallId>,
+    /// Those call IDs corresponding to functions that are still running.
+    active_calls: FxHashSet<CallId>,
+    /// The pointer id to trace
+    tracked_pointer_tag: Option<PtrId>,
+    /// The call id to trace
+    tracked_call_id: Option<CallId>,
+    /// Whether to track raw pointers.
+    track_raw: bool,
 }
-pub type MemoryState = Rc<RefCell<GlobalState>>;
+/// Memory extra state gives us interior mutable access to the global state.
+pub type MemoryExtra = Rc<RefCell<GlobalState>>;
 
 /// Indicates which kind of access is being performed.
-#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
+#[derive(Copy, Clone, Hash, PartialEq, Eq)]
 pub enum AccessKind {
     Read,
-    Write { dealloc: bool },
-}
-
-// "Fake" constructors
-impl AccessKind {
-    fn write() -> AccessKind {
-        AccessKind::Write { dealloc: false }
-    }
-
-    fn dealloc() -> AccessKind {
-        AccessKind::Write { dealloc: true }
-    }
+    Write,
 }
 
 impl fmt::Display for AccessKind {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
-            AccessKind::Read => write!(f, "read"),
-            AccessKind::Write { dealloc: false } => write!(f, "write"),
-            AccessKind::Write { dealloc: true } => write!(f, "deallocation"),
+            AccessKind::Read => write!(f, "read access"),
+            AccessKind::Write => write!(f, "write access"),
         }
     }
 }
 
 /// Indicates which kind of reference is being created.
-/// Used by `reborrow` to compute which permissions to grant to the
+/// Used by high-level `reborrow` to compute which permissions to grant to the
 /// new pointer.
-#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
+#[derive(Copy, Clone, Hash, PartialEq, Eq)]
 pub enum RefKind {
-    /// `&mut`.
-    Mutable,
+    /// `&mut` and `Box`.
+    Unique { two_phase: bool },
     /// `&` with or without interior mutability.
-    Shared { frozen: bool },
-    /// `*` (raw pointer).
-    Raw,
+    Shared,
+    /// `*mut`/`*const` (raw pointers).
+    Raw { mutable: bool },
 }
 
 impl fmt::Display for RefKind {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
-            RefKind::Mutable => write!(f, "mutable"),
-            RefKind::Shared { frozen: true } => write!(f, "shared (frozen)"),
-            RefKind::Shared { frozen: false } => write!(f, "shared (mutable)"),
-            RefKind::Raw => write!(f, "raw"),
+            RefKind::Unique { two_phase: false } => write!(f, "unique"),
+            RefKind::Unique { two_phase: true } => write!(f, "unique (two-phase)"),
+            RefKind::Shared => write!(f, "shared"),
+            RefKind::Raw { mutable: true } => write!(f, "raw (mutable)"),
+            RefKind::Raw { mutable: false } => write!(f, "raw (constant)"),
         }
     }
 }
 
 /// Utilities for initialization and ID generation
-impl Default for GlobalState {
-    fn default() -> Self {
+impl GlobalState {
+    pub fn new(tracked_pointer_tag: Option<PtrId>, tracked_call_id: Option<CallId>, track_raw: bool) -> Self {
         GlobalState {
             next_ptr_id: NonZeroU64::new(1).unwrap(),
-            next_call_id: 0,
-            active_calls: HashSet::default(),
+            base_ptr_ids: FxHashMap::default(),
+            next_call_id: NonZeroU64::new(1).unwrap(),
+            active_calls: FxHashSet::default(),
+            tracked_pointer_tag,
+            tracked_call_id,
+            track_raw,
         }
     }
-}
 
-impl GlobalState {
-    pub fn new_ptr(&mut self) -> PtrId {
+    fn new_ptr(&mut self) -> PtrId {
         let id = self.next_ptr_id;
+        if Some(id) == self.tracked_pointer_tag {
+            register_diagnostic(NonHaltingDiagnostic::CreatedPointerTag(id));
+        }
         self.next_ptr_id = NonZeroU64::new(id.get() + 1).unwrap();
         id
     }
@@ -165,8 +181,11 @@ pub fn new_ptr(&mut self) -> PtrId {
     pub fn new_call(&mut self) -> CallId {
         let id = self.next_call_id;
         trace!("new_call: Assigning ID {}", id);
-        self.active_calls.insert(id);
-        self.next_call_id = id+1;
+        if Some(id) == self.tracked_call_id {
+            register_diagnostic(NonHaltingDiagnostic::CreatedCallId(id));
+        }
+        assert!(self.active_calls.insert(id));
+        self.next_call_id = NonZeroU64::new(id.get() + 1).unwrap();
         id
     }
 
@@ -177,6 +196,23 @@ pub fn end_call(&mut self, id: CallId) {
     fn is_active(&self, id: CallId) -> bool {
         self.active_calls.contains(&id)
     }
+
+    pub fn global_base_ptr(&mut self, id: AllocId) -> Tag {
+        self.base_ptr_ids.get(&id).copied().unwrap_or_else(|| {
+            let tag = Tag::Tagged(self.new_ptr());
+            trace!("New allocation {:?} has base tag {:?}", id, tag);
+            self.base_ptr_ids.insert(id, tag).unwrap_none();
+            tag
+        })
+    }
+}
+
+/// Error reporting
+fn err_sb_ub(msg: String) -> InterpError<'static> {
+    err_machine_stop!(TerminationInfo::ExperimentalUb {
+        msg,
+        url: format!("https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md"),
+    })
 }
 
 // # Stacked Borrows Core Begin
@@ -195,494 +231,346 @@ fn is_active(&self, id: CallId) -> bool {
 /// F3: If an access happens with an `&` outside `UnsafeCell`,
 ///     it requires the `SharedReadOnly` to still be in the stack.
 
-impl Default for Tag {
-    #[inline(always)]
-    fn default() -> Tag {
-        Tag::Untagged
-    }
-}
-
-/// Core relations on `Permission` define which accesses are allowed:
-/// On every access, we try to find a *granting* item, and then we remove all
-/// *incompatible* items above it.
+/// Core relation on `Permission` to define which accesses are allowed
 impl Permission {
     /// This defines for a given permission, whether it permits the given kind of access.
     fn grants(self, access: AccessKind) -> bool {
-        match (self, access) {
-            // Unique and SharedReadWrite allow any kind of access.
-            (Permission::Unique, _) |
-            (Permission::SharedReadWrite, _) =>
-                true,
-            // SharedReadOnly only permits read access.
-            (Permission::SharedReadOnly, AccessKind::Read) =>
-                true,
-            (Permission::SharedReadOnly, AccessKind::Write { .. }) =>
-                false,
-        }
-    }
-
-    /// This defines for a given permission, which other permissions it can tolerate "above" itself
-    /// for which kinds of accesses.
-    /// If true, then `other` is allowed to remain on top of `self` when `access` happens.
-    fn compatible_with(self, access: AccessKind, other: Permission) -> bool {
-        use self::Permission::*;
-
-        match (self, access, other) {
-            // Some cases are impossible.
-            (SharedReadOnly, _, SharedReadWrite) |
-            (SharedReadOnly, _, Unique) =>
-                bug!("There can never be a SharedReadWrite or a Unique on top of a SharedReadOnly"),
-            // When `other` is `SharedReadOnly`, that is NEVER compatible with
-            // write accesses.
-            // This makes sure read-only pointers become invalid on write accesses (ensures F2a).
-            (_, AccessKind::Write { .. }, SharedReadOnly) =>
-                false,
-            // When `other` is `Unique`, that is compatible with nothing.
-            // This makes sure unique pointers become invalid on incompatible accesses (ensures U2).
-            (_, _, Unique) =>
-                false,
-            // When we are unique and this is a write/dealloc, we tolerate nothing.
-            // This makes sure we re-assert uniqueness ("being on top") on write accesses.
-            // (This is particularily important such that when a new mutable ref gets created, it gets
-            // pushed into the right item -- this behaves like a write and we assert uniqueness of the
-            // pointer from which this comes, *if* it was a unique pointer.)
-            (Unique, AccessKind::Write { .. }, _) =>
-                false,
-            // `SharedReadWrite` items can tolerate any other akin items for any kind of access.
-            (SharedReadWrite, _, SharedReadWrite) =>
-                true,
-            // Any item can tolerate read accesses for shared items.
-            // This includes unique items!  Reads from unique pointers do not invalidate
-            // other pointers.
-            (_, AccessKind::Read, SharedReadWrite) |
-            (_, AccessKind::Read, SharedReadOnly) =>
-                true,
-            // That's it.
-        }
+        // Disabled grants nothing. Otherwise, all items grant read access, and except for SharedReadOnly they grant write access.
+        self != Permission::Disabled
+            && (access == AccessKind::Read || self != Permission::SharedReadOnly)
     }
 }
 
-impl<'tcx> RefKind {
-    /// Defines which kind of access the "parent" must grant to create this reference.
-    fn access(self) -> AccessKind {
-        match self {
-            RefKind::Mutable | RefKind::Shared { frozen: false } => AccessKind::write(),
-            RefKind::Raw | RefKind::Shared { frozen: true } => AccessKind::Read,
-            // FIXME: Just requiring read-only access for raw means that a raw ptr might not be writeable
-            // even when we think it should be!  Think about this some more.
-        }
-    }
-
-    /// This defines the new permission used when a pointer gets created: For raw pointers, whether these are read-only
-    /// or read-write depends on the permission from which they derive.
-    fn new_perm(self, derived_from: Permission) -> EvalResult<'tcx, Permission> {
-        Ok(match (self, derived_from) {
-            // Do not derive writable safe pointer from read-only pointer!
-            (RefKind::Mutable, Permission::SharedReadOnly) =>
-                return err!(MachineError(format!(
-                    "deriving mutable reference from read-only pointer"
-                ))),
-            (RefKind::Shared { frozen: false }, Permission::SharedReadOnly) =>
-                return err!(MachineError(format!(
-                    "deriving shared reference with interior mutability from read-only pointer"
-                ))),
-            // Safe pointer cases.
-            (RefKind::Mutable, _) => Permission::Unique,
-            (RefKind::Shared { frozen: true }, _) => Permission::SharedReadOnly,
-            (RefKind::Shared { frozen: false }, _) => Permission::SharedReadWrite,
-            // Raw pointer cases.
-            (RefKind::Raw, Permission::SharedReadOnly) => Permission::SharedReadOnly,
-            (RefKind::Raw, _) => Permission::SharedReadWrite,
-        })
-    }
-}
-
-/// Core per-location operations: access, create.
+/// Core per-location operations: access, dealloc, reborrow.
 impl<'tcx> Stack {
-    /// Find the item granting the given kind of access to the given tag, and where that item is in the stack.
-    fn find_granting(&self, access: AccessKind, tag: Tag) -> Option<(usize, Permission)> {
-        self.borrows.iter()
+    /// Find the item granting the given kind of access to the given tag, and return where
+    /// it is on the stack.
+    fn find_granting(&self, access: AccessKind, tag: Tag) -> Option<usize> {
+        self.borrows
+            .iter()
             .enumerate() // we also need to know *where* in the stack
             .rev() // search top-to-bottom
             // Return permission of first item that grants access.
             // We require a permission with the right tag, ensuring U3 and F3.
-            .filter_map(|(idx, item)| match item {
-                &Item::Permission(perm, item_tag) if perm.grants(access) && tag == item_tag =>
-                    Some((idx, perm)),
-                _ => None,
-            })
-            .next()
-    }
-
-    /// Test if a memory `access` using pointer tagged `tag` is granted.
-    /// If yes, return the index of the item that granted it.
-    fn access(
-        &mut self,
-        access: AccessKind,
-        tag: Tag,
-        global: &GlobalState,
-    ) -> EvalResult<'tcx, usize> {
-        // Two main steps: Find granting item, remove all incompatible items above.
-        // The second step is where barriers get implemented: they "protect" the items
-        // below them, meaning that if we remove an item and then further up encounter a barrier,
-        // we raise an error.
-        // Afterwards we just do some post-processing for deallocation accesses.
-
-        // Step 1: Find granting item.
-        let (granting_idx, granting_perm) = self.find_granting(access, tag)
-            .ok_or_else(|| InterpError::MachineError(format!(
-                    "no item granting {} access to tag {} found in borrow stack",
-                    access, tag,
-            )))?;
-
-        // Step 2: Remove everything incompatible above them.
-        // Items below an active barrier however may not be removed, so we check that as well.
-        // We do *not* maintain a stack discipline here.  We could, in principle, decide to only
-        // keep the items immediately above `granting_idx` that are compatible, and then pop the rest.
-        // However, that kills off entire "branches" of pointer derivation too easily:
-        // in `let raw = &mut *x as *mut _; let _val = *x;`, the second statement would pop the `Unique`
-        // from the reborrow of the first statement, and subequently also pop the `SharedReadWrite` for `raw`.
-        {
-            // Implemented with indices because there does not seem to be a nice iterator and range-based
-            // API for this.
-            let mut cur = granting_idx + 1;
-            let mut removed_item = None;
-            while let Some(item) = self.borrows.get(cur) {
-                match *item {
-                    Item::Permission(perm, _) => {
-                        if granting_perm.compatible_with(access, perm) {
-                            // Keep this, check next.
-                            cur += 1;
-                        } else {
-                            // Aha! This is a bad one, remove it.
-                            let item = self.borrows.remove(cur);
-                            trace!("access: popping item {}", item);
-                            removed_item = Some(item);
-                        }
-                    }
-                    Item::FnBarrier(call) if !global.is_active(call) => {
-                        // An inactive barrier, just get rid of it. (Housekeeping.)
-                        self.borrows.remove(cur);
-                    }
-                    Item::FnBarrier(call) => {
-                        // We hit an active barrier!  If we have already removed an item,
-                        // we got a problem!  The barrier was supposed to protect this item.
-                        if let Some(removed_item) = removed_item {
-                            return err!(MachineError(format!(
-                                    "not granting {} access to tag {} because barrier ({}) protects incompatible item {}",
-                                    access, tag, call, removed_item
-                                )));
-                        }
-                        // Keep this, check next.
-                        cur += 1;
+            .find_map(
+                |(idx, item)| {
+                    if tag == item.tag && item.perm.grants(access) { Some(idx) } else { None }
+                },
+            )
+    }
+
+    /// Find the first write-incompatible item above the given one --
+    /// i.e, find the height to which the stack will be truncated when writing to `granting`.
+    fn find_first_write_incompatible(&self, granting: usize) -> usize {
+        let perm = self.borrows[granting].perm;
+        match perm {
+            Permission::SharedReadOnly => bug!("Cannot use SharedReadOnly for writing"),
+            Permission::Disabled => bug!("Cannot use Disabled for anything"),
+            // On a write, everything above us is incompatible.
+            Permission::Unique => granting + 1,
+            Permission::SharedReadWrite => {
+                // The SharedReadWrite *just* above us are compatible, to skip those.
+                let mut idx = granting + 1;
+                while let Some(item) = self.borrows.get(idx) {
+                    if item.perm == Permission::SharedReadWrite {
+                        // Go on.
+                        idx += 1;
+                    } else {
+                        // Found first incompatible!
+                        break;
                     }
                 }
+                idx
             }
         }
+    }
 
-        // Post-processing.
-        // If we got here, we found a matching item. Congratulations!
-        // However, we are not done yet: If this access is deallocating, we must make sure
-        // there are no active barriers remaining on the stack.
-        if access == AccessKind::dealloc() {
-            for &itm in self.borrows.iter().rev() {
-                match itm {
-                    Item::FnBarrier(call) if global.is_active(call) => {
-                        return err!(MachineError(format!(
-                            "deallocating with active barrier ({})", call
-                        )))
-                    }
-                    _ => {},
+    /// Check if the given item is protected.
+    fn check_protector(item: &Item, tag: Option<Tag>, global: &GlobalState) -> InterpResult<'tcx> {
+        if let Tag::Tagged(id) = item.tag {
+            if Some(id) == global.tracked_pointer_tag {
+                register_diagnostic(NonHaltingDiagnostic::PoppedPointerTag(item.clone()));
+            }
+        }
+        if let Some(call) = item.protector {
+            if global.is_active(call) {
+                if let Some(tag) = tag {
+                    Err(err_sb_ub(format!(
+                        "not granting access to tag {:?} because incompatible item is protected: {:?}",
+                        tag, item
+                    )))?
+                } else {
+                    Err(err_sb_ub(format!(
+                        "deallocating while item is protected: {:?}",
+                        item
+                    )))?
                 }
             }
         }
-
-        // Done.
-        return Ok(granting_idx);
+        Ok(())
     }
 
-    /// `reborrow` helper function.
-    /// Grant `permisson` to new pointer tagged `tag`, added at `position` in the stack.
-    fn grant(&mut self, perm: Permission, tag: Tag, position: usize) {
-        // Simply add it to the "stack" -- this might add in the middle.
-        // As an optimization, do nothing if the new item is identical to one of its neighbors.
-        let item = Item::Permission(perm, tag);
-        if self.borrows[position-1] == item || self.borrows.get(position) == Some(&item) {
-            // Optimization applies, done.
-            trace!("reborrow: avoiding redundant item {}", item);
-            return;
-        }
-        trace!("reborrow: pushing item {}", item);
-        self.borrows.insert(position, item);
-    }
+    /// Test if a memory `access` using pointer tagged `tag` is granted.
+    /// If yes, return the index of the item that granted it.
+    fn access(&mut self, access: AccessKind, ptr: Pointer<Tag>, global: &GlobalState) -> InterpResult<'tcx> {
+        // Two main steps: Find granting item, remove incompatible items above.
 
-    /// `reborrow` helper function.
-    /// Adds a barrier.
-    fn barrier(&mut self, call: CallId) {
-        let itm = Item::FnBarrier(call);
-        if *self.borrows.last().unwrap() == itm {
-            // This is just an optimization, no functional change: Avoid stacking
-            // multiple identical barriers on top of each other.
-            // This can happen when a function receives several shared references
-            // that overlap.
-            trace!("reborrow: avoiding redundant extra barrier");
+        // Step 1: Find granting item.
+        let granting_idx = self.find_granting(access, ptr.tag).ok_or_else(|| {
+            err_sb_ub(format!(
+                "no item granting {} to tag {:?} at {} found in borrow stack.",
+                access, ptr.tag, ptr.erase_tag(),
+            ))
+        })?;
+
+        // Step 2: Remove incompatible items above them.  Make sure we do not remove protected
+        // items.  Behavior differs for reads and writes.
+        if access == AccessKind::Write {
+            // Remove everything above the write-compatible items, like a proper stack. This makes sure read-only and unique
+            // pointers become invalid on write accesses (ensures F2a, and ensures U2 for write accesses).
+            let first_incompatible_idx = self.find_first_write_incompatible(granting_idx);
+            for item in self.borrows.drain(first_incompatible_idx..).rev() {
+                trace!("access: popping item {:?}", item);
+                Stack::check_protector(&item, Some(ptr.tag), global)?;
+            }
         } else {
-            trace!("reborrow: pushing barrier for call {}", call);
-            self.borrows.push(itm);
+            // On a read, *disable* all `Unique` above the granting item.  This ensures U2 for read accesses.
+            // The reason this is not following the stack discipline (by removing the first Unique and
+            // everything on top of it) is that in `let raw = &mut *x as *mut _; let _val = *x;`, the second statement
+            // would pop the `Unique` from the reborrow of the first statement, and subsequently also pop the
+            // `SharedReadWrite` for `raw`.
+            // This pattern occurs a lot in the standard library: create a raw pointer, then also create a shared
+            // reference and use that.
+            // We *disable* instead of removing `Unique` to avoid "connecting" two neighbouring blocks of SRWs.
+            for idx in ((granting_idx + 1)..self.borrows.len()).rev() {
+                let item = &mut self.borrows[idx];
+                if item.perm == Permission::Unique {
+                    trace!("access: disabling item {:?}", item);
+                    Stack::check_protector(item, Some(ptr.tag), global)?;
+                    item.perm = Permission::Disabled;
+                }
+            }
         }
+
+        // Done.
+        Ok(())
     }
 
-    /// `reborrow` helper function: test that the stack invariants are still maintained.
-    fn test_invariants(&self) {
-        let mut saw_shared_read_only = false;
-        for item in self.borrows.iter() {
-            match item {
-                Item::Permission(Permission::SharedReadOnly, _) => {
-                    saw_shared_read_only = true;
-                }
-                Item::Permission(perm, _) if saw_shared_read_only => {
-                    panic!("Found {:?} on top of a SharedReadOnly!", perm);
-                }
-                _ => {}
-            }
+    /// Deallocate a location: Like a write access, but also there must be no
+    /// active protectors at all because we will remove all items.
+    fn dealloc(&mut self, ptr: Pointer<Tag>, global: &GlobalState) -> InterpResult<'tcx> {
+        // Step 1: Find granting item.
+        self.find_granting(AccessKind::Write, ptr.tag).ok_or_else(|| {
+            err_sb_ub(format!(
+                "no item granting write access for deallocation to tag {:?} at {} found in borrow stack",
+                ptr.tag, ptr.erase_tag(),
+            ))
+        })?;
+
+        // Step 2: Remove all items.  Also checks for protectors.
+        for item in self.borrows.drain(..).rev() {
+            Stack::check_protector(&item, None, global)?;
         }
+
+        Ok(())
     }
 
-    /// Derived a new pointer from one with the given tag.
-    fn reborrow(
-        &mut self,
-        derived_from: Tag,
-        barrier: Option<CallId>,
-        new_kind: RefKind,
-        new_tag: Tag,
-        global: &GlobalState,
-    ) -> EvalResult<'tcx> {
-        // Find the permission "from which we derive".  To this end we first have to decide
-        // if we derive from a permission that grants writes or just reads.
-        let access = new_kind.access();
-        // Now we figure out which item grants our parent (`derived_from`) permission.
-        // We use that to determine (a) where to put the new item, and for raw pointers
-        // (b) whether to given read-only or read-write access.
-        // FIXME: This handling of raw pointers is fragile, very fragile.  What if we do
-        // not get "the right one", like when there are multiple items granting `derived_from`
-        // and we accidentally create a read-only pointer?  This can happen for two-phase borrows
-        // (then there's a `Unique` and a `SharedReadOnly` for the same tag), and for raw pointers
-        // (which currently all are `Untagged`).
-        let (derived_from_idx, derived_from_perm) = self.find_granting(access, derived_from)
-            .ok_or_else(|| InterpError::MachineError(format!(
-                    "no item to reborrow as {} from tag {} found in borrow stack", new_kind, derived_from,
+    /// Derive a new pointer from one with the given tag.
+    /// `weak` controls whether this operation is weak or strong: weak granting does not act as
+    /// an access, and they add the new item directly on top of the one it is derived
+    /// from instead of all the way at the top of the stack.
+    fn grant(&mut self, derived_from: Pointer<Tag>, new: Item, global: &GlobalState) -> InterpResult<'tcx> {
+        // Figure out which access `perm` corresponds to.
+        let access =
+            if new.perm.grants(AccessKind::Write) { AccessKind::Write } else { AccessKind::Read };
+        // Now we figure out which item grants our parent (`derived_from`) this kind of access.
+        // We use that to determine where to put the new item.
+        let granting_idx = self.find_granting(access, derived_from.tag)
+            .ok_or_else(|| err_sb_ub(format!(
+                "trying to reborrow for {:?} at {}, but parent tag {:?} does not have an appropriate item in the borrow stack",
+                new.perm, derived_from.erase_tag(), derived_from.tag,
             )))?;
-        // With this we can compute the permission for the new pointer.
-        let new_perm = new_kind.new_perm(derived_from_perm).expect("this should never fail");
-
-        // We behave very differently for the "unsafe" case of a shared-read-write pointer
-        // ("unsafe" because this also applies to shared references with interior mutability).
-        // This is because such pointers may be reborrowed to unique pointers that actually
-        // remain valid when their "parents" get further reborrows!
-        // However, either way, we ensure that we insert the new item in a way that between
+
+        // Compute where to put the new item.
+        // Either way, we ensure that we insert the new item in a way such that between
         // `derived_from` and the new one, there are only items *compatible with* `derived_from`.
-        if new_perm == Permission::SharedReadWrite {
-            // A very liberal reborrow because the new pointer does not expect any kind of aliasing guarantee.
-            // Just insert new permission as child of old permission, and maintain everything else.
-            // This inserts "as far down as possible", which is good because it makes this pointer as
-            // long-lived as possible *and* we want all the items that are incompatible with this
-            // to actually get removed from the stack.  If we pushed a `SharedReadWrite` on top of
-            // a `SharedReadOnly`, we'd violate the invariant that `SaredReadOnly` are at the top
-            // and we'd allow write access without invalidating frozen shared references!
-            // This ensures F2b for `SharedReadWrite` by adding the new item below any
-            // potentially existing `SharedReadOnly`.
-            self.grant(new_perm, new_tag, derived_from_idx+1);
-
-            // No barrier. They can rightfully alias with `&mut`.
-            // FIXME: This means that the `dereferencable` attribute on non-frozen shared references
-            // is incorrect! They are dereferencable when the function is called, but might become
-            // non-dereferencable during the course of execution.
-            // Also see [1], [2].
-            //
-            // [1]: <https://internals.rust-lang.org/t/
-            //       is-it-possible-to-be-memory-safe-with-deallocated-self/8457/8>,
-            // [2]: <https://lists.llvm.org/pipermail/llvm-dev/2018-July/124555.html>
+        let new_idx = if new.perm == Permission::SharedReadWrite {
+            assert!(
+                access == AccessKind::Write,
+                "this case only makes sense for stack-like accesses"
+            );
+            // SharedReadWrite can coexist with "existing loans", meaning they don't act like a write
+            // access.  Instead of popping the stack, we insert the item at the place the stack would
+            // be popped to (i.e., we insert it above all the write-compatible items).
+            // This ensures F2b by adding the new item below any potentially existing `SharedReadOnly`.
+            self.find_first_write_incompatible(granting_idx)
         } else {
             // A "safe" reborrow for a pointer that actually expects some aliasing guarantees.
-            // Here, creating a reference actually counts as an access, and pops incompatible
-            // stuff off the stack.
+            // Here, creating a reference actually counts as an access.
             // This ensures F2b for `Unique`, by removing offending `SharedReadOnly`.
-            let check_idx = self.access(access, derived_from, global)?;
-            assert_eq!(check_idx, derived_from_idx, "somehow we saw different items??");
+            self.access(access, derived_from, global)?;
 
             // We insert "as far up as possible": We know only compatible items are remaining
             // on top of `derived_from`, and we want the new item at the top so that we
             // get the strongest possible guarantees.
             // This ensures U1 and F1.
-            self.grant(new_perm, new_tag, self.borrows.len());
-
-            // Now is a good time to add the barrier, protecting the item we just added.
-            if let Some(call) = barrier {
-                self.barrier(call);
-            }
-        }
+            self.borrows.len()
+        };
 
-        // Make sure that after all this, the stack's invariant is still maintained.
-        if cfg!(debug_assertions) {
-            self.test_invariants();
+        // Put the new item there. As an optimization, deduplicate if it is equal to one of its new neighbors.
+        if self.borrows[new_idx - 1] == new || self.borrows.get(new_idx) == Some(&new) {
+            // Optimization applies, done.
+            trace!("reborrow: avoiding adding redundant item {:?}", new);
+        } else {
+            trace!("reborrow: adding item {:?}", new);
+            self.borrows.insert(new_idx, new);
         }
 
         Ok(())
     }
 }
+// # Stacked Borrows Core End
 
-/// Higher-level per-location operations: deref, access, reborrow.
+/// Map per-stack operations to higher-level per-location-range operations.
 impl<'tcx> Stacks {
     /// Creates new stack with initial tag.
-    pub(crate) fn new(
-        size: Size,
-        tag: Tag,
-        extra: MemoryState,
-    ) -> Self {
-        let item = Item::Permission(Permission::Unique, tag);
-        let stack = Stack {
-            borrows: vec![item],
-        };
-        Stacks {
-            stacks: RefCell::new(RangeMap::new(size, stack)),
-            global: extra,
-        }
-    }
+    fn new(size: Size, perm: Permission, tag: Tag, extra: MemoryExtra) -> Self {
+        let item = Item { perm, tag, protector: None };
+        let stack = Stack { borrows: vec![item] };
 
-    /// `ptr` got used, reflect that in the stack.
-    fn access(
-        &self,
-        ptr: Pointer<Tag>,
-        size: Size,
-        kind: AccessKind,
-    ) -> EvalResult<'tcx> {
-        trace!("{} access of tag {}: {:?}, size {}", kind, ptr.tag, ptr, size.bytes());
-        // Even reads can have a side-effect, by invalidating other references.
-        // This is fundamentally necessary since `&mut` asserts that there
-        // are no accesses through other references, not even reads.
-        let global = self.global.borrow();
-        let mut stacks = self.stacks.borrow_mut();
-        for stack in stacks.iter_mut(ptr.offset, size) {
-            stack.access(kind, ptr.tag, &*global)?;
-        }
-        Ok(())
+        Stacks { stacks: RefCell::new(RangeMap::new(size, stack)), global: extra }
     }
 
-    /// Reborrow the given pointer to the new tag for the given kind of reference.
-    /// This works on `&self` because we might encounter references to constant memory.
-    fn reborrow(
+    /// Call `f` on every stack in the range.
+    fn for_each(
         &self,
         ptr: Pointer<Tag>,
         size: Size,
-        barrier: Option<CallId>,
-        new_kind: RefKind,
-        new_tag: Tag,
-    ) -> EvalResult<'tcx> {
-        trace!(
-            "{} reborrow for tag {} to {}: {:?}, size {}",
-            new_kind, ptr.tag, new_tag, ptr, size.bytes(),
-        );
+        f: impl Fn(Pointer<Tag>, &mut Stack, &GlobalState) -> InterpResult<'tcx>,
+    ) -> InterpResult<'tcx> {
         let global = self.global.borrow();
         let mut stacks = self.stacks.borrow_mut();
-        for stack in stacks.iter_mut(ptr.offset, size) {
-            stack.reborrow(ptr.tag, barrier, new_kind, new_tag, &*global)?;
+        for (offset, stack) in stacks.iter_mut(ptr.offset, size) {
+            let mut cur_ptr = ptr;
+            cur_ptr.offset = offset;
+            f(cur_ptr, stack, &*global)?;
         }
         Ok(())
     }
 }
 
-// # Stacked Borrows Core End
-
-// Glue code to connect with Miri Machine Hooks
-
+/// Glue code to connect with Miri Machine Hooks
 impl Stacks {
     pub fn new_allocation(
+        id: AllocId,
         size: Size,
-        extra: &MemoryState,
+        extra: MemoryExtra,
         kind: MemoryKind<MiriMemoryKind>,
     ) -> (Self, Tag) {
-        let tag = match kind {
-            MemoryKind::Stack => {
-                // New unique borrow. This `Uniq` is not accessible by the program,
-                // so it will only ever be used when using the local directly (i.e.,
-                // not through a pointer). That is, whenever we directly use a local, this will pop
-                // everything else off the stack, invalidating all previous pointers,
-                // and in particular, *all* raw pointers. This subsumes the explicit
-                // `reset` which the blog post [1] says to perform when accessing a local.
-                //
-                // [1]: <https://www.ralfj.de/blog/2018/08/07/stacked-borrows.html>
-                Tag::Tagged(extra.borrow_mut().new_ptr())
-            }
+        let (tag, perm) = match kind {
+            // New unique borrow. This tag is not accessible by the program,
+            // so it will only ever be used when using the local directly (i.e.,
+            // not through a pointer). That is, whenever we directly write to a local, this will pop
+            // everything else off the stack, invalidating all previous pointers,
+            // and in particular, *all* raw pointers.
+            MemoryKind::Stack => (Tag::Tagged(extra.borrow_mut().new_ptr()), Permission::Unique),
+            // `Global` memory can be referenced by global pointers from `tcx`.
+            // Thus we call `global_base_ptr` such that the global pointers get the same tag
+            // as what we use here.
+            // `ExternStatic` is used for extern statics, and thus must also be listed here.
+            // `Env` we list because we can get away with precise tracking there.
+            // The base pointer is not unique, so the base permission is `SharedReadWrite`.
+            MemoryKind::Machine(MiriMemoryKind::Global | MiriMemoryKind::ExternStatic | MiriMemoryKind::Tls | MiriMemoryKind::Env) =>
+                (extra.borrow_mut().global_base_ptr(id), Permission::SharedReadWrite),
+            // Everything else we handle like raw pointers for now.
             _ => {
-                Tag::Untagged
+                let mut extra = extra.borrow_mut();
+                let tag = if extra.track_raw { Tag::Tagged(extra.new_ptr()) } else { Tag::Untagged };
+                (tag, Permission::SharedReadWrite)
             }
         };
-        let stack = Stacks::new(size, tag, Rc::clone(extra));
-        (stack, tag)
+        (Stacks::new(size, perm, tag, extra), tag)
     }
-}
 
-impl AllocationExtra<Tag> for Stacks {
     #[inline(always)]
-    fn memory_read<'tcx>(
-        alloc: &Allocation<Tag, Stacks>,
-        ptr: Pointer<Tag>,
-        size: Size,
-    ) -> EvalResult<'tcx> {
-        alloc.extra.access(ptr, size, AccessKind::Read)
+    pub fn memory_read<'tcx>(&self, ptr: Pointer<Tag>, size: Size) -> InterpResult<'tcx> {
+        trace!("read access with tag {:?}: {:?}, size {}", ptr.tag, ptr.erase_tag(), size.bytes());
+        self.for_each(ptr, size, |ptr, stack, global| stack.access(AccessKind::Read, ptr, global))
     }
 
     #[inline(always)]
-    fn memory_written<'tcx>(
-        alloc: &mut Allocation<Tag, Stacks>,
-        ptr: Pointer<Tag>,
-        size: Size,
-    ) -> EvalResult<'tcx> {
-        alloc.extra.access(ptr, size, AccessKind::write())
+    pub fn memory_written<'tcx>(&mut self, ptr: Pointer<Tag>, size: Size) -> InterpResult<'tcx> {
+        trace!("write access with tag {:?}: {:?}, size {}", ptr.tag, ptr.erase_tag(), size.bytes());
+        self.for_each(ptr, size, |ptr, stack, global| stack.access(AccessKind::Write, ptr, global))
     }
 
     #[inline(always)]
-    fn memory_deallocated<'tcx>(
-        alloc: &mut Allocation<Tag, Stacks>,
+    pub fn memory_deallocated<'tcx>(
+        &mut self,
         ptr: Pointer<Tag>,
         size: Size,
-    ) -> EvalResult<'tcx> {
-        alloc.extra.access(ptr, size, AccessKind::dealloc())
+    ) -> InterpResult<'tcx> {
+        trace!("deallocation with tag {:?}: {:?}, size {}", ptr.tag, ptr.erase_tag(), size.bytes());
+        self.for_each(ptr, size, |ptr, stack, global| stack.dealloc(ptr, global))
     }
 }
 
-impl<'a, 'mir, 'tcx> EvalContextPrivExt<'a, 'mir, 'tcx> for crate::MiriEvalContext<'a, 'mir, 'tcx> {}
-trait EvalContextPrivExt<'a, 'mir, 'tcx: 'a+'mir>: crate::MiriEvalContextExt<'a, 'mir, 'tcx> {
+/// Retagging/reborrowing.  There is some policy in here, such as which permissions
+/// to grant for which references, and when to add protectors.
+impl<'mir, 'tcx: 'mir> EvalContextPrivExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
+trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
     fn reborrow(
         &mut self,
         place: MPlaceTy<'tcx, Tag>,
         size: Size,
-        mutbl: Option<Mutability>,
+        kind: RefKind,
         new_tag: Tag,
-        fn_barrier: bool,
-    ) -> EvalResult<'tcx> {
+        protect: bool,
+    ) -> InterpResult<'tcx> {
         let this = self.eval_context_mut();
-        let barrier = if fn_barrier { Some(this.frame().extra) } else { None };
-        let ptr = place.ptr.to_ptr()?;
-        trace!("reborrow: creating new reference for {:?} (pointee {}): {:?}",
-            ptr, place.layout.ty, new_tag);
+        let protector = if protect { Some(this.frame().extra.call_id) } else { None };
+        let ptr = place.ptr.assert_ptr();
+        trace!(
+            "reborrow: {} reference {:?} derived from {:?} (pointee {}): {:?}, size {}",
+            kind,
+            new_tag,
+            ptr.tag,
+            place.layout.ty,
+            ptr.erase_tag(),
+            size.bytes()
+        );
 
         // Get the allocation. It might not be mutable, so we cannot use `get_mut`.
-        let alloc = this.memory().get(ptr.alloc_id)?;
-        alloc.check_bounds(this, ptr, size)?;
+        let extra = &this.memory.get_raw(ptr.alloc_id)?.extra;
+        let stacked_borrows =
+            extra.stacked_borrows.as_ref().expect("we should have Stacked Borrows data");
         // Update the stacks.
-        if mutbl == Some(MutImmutable) {
-            // Reference that cares about freezing. We need a frozen-sensitive reborrow.
-            this.visit_freeze_sensitive(place, size, |cur_ptr, size, frozen| {
-                let new_kind = RefKind::Shared { frozen };
-                alloc.extra.reborrow(cur_ptr, size, barrier, new_kind, new_tag)
-            })?;
-        } else {
-            // Just treat this as one big chunk.
-            let new_kind = if mutbl == Some(MutMutable) { RefKind::Mutable } else { RefKind::Raw };
-            alloc.extra.reborrow(ptr, size, barrier, new_kind, new_tag)?;
-        }
-        Ok(())
+        // Make sure that raw pointers and mutable shared references are reborrowed "weak":
+        // There could be existing unique pointers reborrowed from them that should remain valid!
+        let perm = match kind {
+            RefKind::Unique { two_phase: false } => Permission::Unique,
+            RefKind::Unique { two_phase: true } => Permission::SharedReadWrite,
+            RefKind::Raw { mutable: true } => Permission::SharedReadWrite,
+            RefKind::Shared | RefKind::Raw { mutable: false } => {
+                // Shared references and *const are a whole different kind of game, the
+                // permission is not uniform across the entire range!
+                // We need a frozen-sensitive reborrow.
+                return this.visit_freeze_sensitive(place, size, |cur_ptr, size, frozen| {
+                    // We are only ever `SharedReadOnly` inside the frozen bits.
+                    let perm = if frozen {
+                        Permission::SharedReadOnly
+                    } else {
+                        Permission::SharedReadWrite
+                    };
+                    let item = Item { perm, tag: new_tag, protector };
+                    stacked_borrows.for_each(cur_ptr, size, |cur_ptr, stack, global| {
+                        stack.grant(cur_ptr, item, global)
+                    })
+                });
+            }
+        };
+        let item = Item { perm, tag: new_tag, protector };
+        stacked_borrows.for_each(ptr, size, |ptr, stack, global| stack.grant(ptr, item, global))
     }
 
     /// Retags an indidual pointer, returning the retagged version.
@@ -690,121 +578,114 @@ fn reborrow(
     fn retag_reference(
         &mut self,
         val: ImmTy<'tcx, Tag>,
-        mutbl: Option<Mutability>,
-        fn_barrier: bool,
-        two_phase: bool,
-    ) -> EvalResult<'tcx, Immediate<Tag>> {
+        kind: RefKind,
+        protect: bool,
+    ) -> InterpResult<'tcx, ImmTy<'tcx, Tag>> {
         let this = self.eval_context_mut();
         // We want a place for where the ptr *points to*, so we get one.
         let place = this.ref_to_mplace(val)?;
-        let size = this.size_and_align_of_mplace(place)?
+        let size = this
+            .size_and_align_of_mplace(place)?
             .map(|(size, _)| size)
             .unwrap_or_else(|| place.layout.size);
+        // `reborrow` relies on getting a `Pointer` and everything being in-bounds,
+        // so let's ensure that. However, we do not care about alignment.
+        // We can see dangling ptrs in here e.g. after a Box's `Unique` was
+        // updated using "self.0 = ..." (can happen in Box::from_raw) so we cannot ICE; see miri#1050.
+        let place = this.mplace_access_checked(place, Some(Align::from_bytes(1).unwrap()))?;
+        // Nothing to do for ZSTs.
         if size == Size::ZERO {
-            // Nothing to do for ZSTs.
-            return Ok(*val);
+            return Ok(val);
         }
 
         // Compute new borrow.
-        let new_tag = match mutbl {
-            Some(_) => Tag::Tagged(this.memory().extra.borrow_mut().new_ptr()),
-            None => Tag::Untagged,
+        let new_tag = {
+            let mut mem_extra = this.memory.extra.stacked_borrows.as_ref().unwrap().borrow_mut();
+            match kind {
+                // Give up tracking for raw pointers.
+                RefKind::Raw { .. } if !mem_extra.track_raw => Tag::Untagged,
+                // All other pointers are properly tracked.
+                _ => Tag::Tagged(mem_extra.new_ptr()),
+            }
         };
 
         // Reborrow.
-        this.reborrow(place, size, mutbl, new_tag, fn_barrier)?;
+        this.reborrow(place, size, kind, new_tag, protect)?;
         let new_place = place.replace_tag(new_tag);
-        // Handle two-phase borrows.
-        if two_phase {
-            assert!(mutbl == Some(MutMutable), "two-phase shared borrows make no sense");
-            // Grant read access *to the parent pointer* with the old tag.  This means the same pointer
-            // has multiple items in the stack now!
-            // FIXME: Think about this some more, in particular about the interaction with cast-to-raw.
-            // Maybe find a better way to express 2-phase, now that we have a "more expressive language"
-            // in the stack.
-            let old_tag = place.ptr.to_ptr().unwrap().tag;
-            this.reborrow(new_place, size, Some(MutImmutable), old_tag, /* fn_barrier: */ false)?;
-        }
 
         // Return new pointer.
-        Ok(new_place.to_ref())
+        Ok(ImmTy::from_immediate(new_place.to_ref(), val.layout))
     }
 }
 
-impl<'a, 'mir, 'tcx> EvalContextExt<'a, 'mir, 'tcx> for crate::MiriEvalContext<'a, 'mir, 'tcx> {}
-pub trait EvalContextExt<'a, 'mir, 'tcx: 'a+'mir>: crate::MiriEvalContextExt<'a, 'mir, 'tcx> {
-    fn retag(
-        &mut self,
-        kind: RetagKind,
-        place: PlaceTy<'tcx, Tag>
-    ) -> EvalResult<'tcx> {
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
+    fn retag(&mut self, kind: RetagKind, place: PlaceTy<'tcx, Tag>) -> InterpResult<'tcx> {
         let this = self.eval_context_mut();
-        // Determine mutability and whether to add a barrier.
+        // Determine mutability and whether to add a protector.
         // Cannot use `builtin_deref` because that reports *immutable* for `Box`,
         // making it useless.
-        fn qualify(ty: ty::Ty<'_>, kind: RetagKind) -> Option<(Option<Mutability>, bool)> {
-            match ty.sty {
+        fn qualify(ty: ty::Ty<'_>, kind: RetagKind) -> Option<(RefKind, bool)> {
+            match ty.kind() {
                 // References are simple.
-                ty::Ref(_, _, mutbl) => Some((Some(mutbl), kind == RetagKind::FnEntry)),
+                ty::Ref(_, _, Mutability::Mut) => Some((
+                    RefKind::Unique { two_phase: kind == RetagKind::TwoPhase },
+                    kind == RetagKind::FnEntry,
+                )),
+                ty::Ref(_, _, Mutability::Not) =>
+                    Some((RefKind::Shared, kind == RetagKind::FnEntry)),
                 // Raw pointers need to be enabled.
-                ty::RawPtr(..) if kind == RetagKind::Raw => Some((None, false)),
-                // Boxes do not get a barrier: barriers reflect that references outlive the call
+                ty::RawPtr(tym) if kind == RetagKind::Raw =>
+                    Some((RefKind::Raw { mutable: tym.mutbl == Mutability::Mut }, false)),
+                // Boxes do not get a protector: protectors reflect that references outlive the call
                 // they were passed in to; that's just not the case for boxes.
-                ty::Adt(..) if ty.is_box() => Some((Some(MutMutable), false)),
+                ty::Adt(..) if ty.is_box() => Some((RefKind::Unique { two_phase: false }, false)),
                 _ => None,
             }
         }
 
-        // We need a visitor to visit all references. However, that requires
-        // a `MemPlace`, so we have a fast path for reference types that
-        // avoids allocating.
-        if let Some((mutbl, barrier)) = qualify(place.layout.ty, kind) {
+        // We only reborrow "bare" references/boxes.
+        // Not traversing into fields helps with <https://github.com/rust-lang/unsafe-code-guidelines/issues/125>,
+        // but might also cost us optimization and analyses. We will have to experiment more with this.
+        if let Some((mutbl, protector)) = qualify(place.layout.ty, kind) {
             // Fast path.
             let val = this.read_immediate(this.place_to_op(place)?)?;
-            let val = this.retag_reference(val, mutbl, barrier, kind == RetagKind::TwoPhase)?;
-            this.write_immediate(val, place)?;
-            return Ok(());
+            let val = this.retag_reference(val, mutbl, protector)?;
+            this.write_immediate(*val, place)?;
         }
-        let place = this.force_allocation(place)?;
 
-        let mut visitor = RetagVisitor { ecx: this, kind };
-        visitor.visit_value(place)?;
-
-        // The actual visitor.
-        struct RetagVisitor<'ecx, 'a, 'mir, 'tcx> {
-            ecx: &'ecx mut MiriEvalContext<'a, 'mir, 'tcx>,
-            kind: RetagKind,
-        }
-        impl<'ecx, 'a, 'mir, 'tcx>
-            MutValueVisitor<'a, 'mir, 'tcx, Evaluator<'tcx>>
-        for
-            RetagVisitor<'ecx, 'a, 'mir, 'tcx>
-        {
-            type V = MPlaceTy<'tcx, Tag>;
-
-            #[inline(always)]
-            fn ecx(&mut self) -> &mut MiriEvalContext<'a, 'mir, 'tcx> {
-                &mut self.ecx
-            }
+        Ok(())
+    }
 
-            // Primitives of reference type, that is the one thing we are interested in.
-            fn visit_primitive(&mut self, place: MPlaceTy<'tcx, Tag>) -> EvalResult<'tcx>
-            {
-                // Cannot use `builtin_deref` because that reports *immutable* for `Box`,
-                // making it useless.
-                if let Some((mutbl, barrier)) = qualify(place.layout.ty, self.kind) {
-                    let val = self.ecx.read_immediate(place.into())?;
-                    let val = self.ecx.retag_reference(
-                        val,
-                        mutbl,
-                        barrier,
-                        self.kind == RetagKind::TwoPhase
-                    )?;
-                    self.ecx.write_immediate(val, place.into())?;
-                }
-                Ok(())
-            }
+    /// After a stack frame got pushed, retag the return place so that we are sure
+    /// it does not alias with anything.
+    /// 
+    /// This is a HACK because there is nothing in MIR that would make the retag
+    /// explicit. Also see https://github.com/rust-lang/rust/issues/71117.
+    fn retag_return_place(&mut self) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let return_place = if let Some(return_place) = this.frame_mut().return_place {
+            return_place
+        } else {
+            // No return place, nothing to do.
+            return Ok(());
+        };
+        if return_place.layout.is_zst() {
+            // There may not be any memory here, nothing to do.
+            return Ok(());
         }
+        // We need this to be in-memory to use tagged pointers.
+        let return_place = this.force_allocation(return_place)?;
+
+        // We have to turn the place into a pointer to use the existing code.
+        // (The pointer type does not matter, so we use a raw pointer.)
+        let ptr_layout = this.layout_of(this.tcx.mk_mut_ptr(return_place.layout.ty))?;
+        let val = ImmTy::from_immediate(return_place.to_ref(), ptr_layout);
+        // Reborrow it.
+        let val = this.retag_reference(val, RefKind::Unique { two_phase: false }, /*protector*/ true)?;
+        // And use reborrowed pointer for return place.
+        let return_place = this.ref_to_mplace(val)?;
+        this.frame_mut().return_place = Some(return_place.into());
 
         Ok(())
     }