Skip to main content

rustc_mir_transform/
elaborate_box_derefs.rs

1//! This pass transforms derefs of Box into a deref of the pointer inside Box.
2//!
3//! Box is not actually a pointer so it is incorrect to dereference it directly.
4
5use rustc_abi::FieldIdx;
6use rustc_middle::mir::visit::MutVisitor;
7use rustc_middle::mir::*;
8use rustc_middle::span_bug;
9use rustc_middle::ty::{self, PatternKind, Ty, TyCtxt};
10
11use crate::PassPolicy;
12use crate::patch::MirPatch;
13
14/// Constructs the types used when accessing a Box's pointer
15fn build_ptr_tys<'tcx>(
16    tcx: TyCtxt<'tcx>,
17    pointee: Ty<'tcx>,
18    unique_def: ty::AdtDef<'tcx>,
19    nonnull_def: ty::AdtDef<'tcx>,
20) -> (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>) {
21    let args = tcx.mk_args(&[pointee.into()]);
22    let unique_ty = Ty::new_adt(tcx, unique_def, args);
23    let nonnull_ty = Ty::new_adt(tcx, nonnull_def, args);
24    let ptr_ty = Ty::new_imm_ptr(tcx, pointee);
25
26    (unique_ty, nonnull_ty, ptr_ty)
27}
28
29struct ElaborateBoxDerefVisitor<'a, 'tcx> {
30    tcx: TyCtxt<'tcx>,
31    local_decls: &'a mut LocalDecls<'tcx>,
32    patch: MirPatch<'tcx>,
33}
34
35impl<'a, 'tcx> MutVisitor<'tcx> for ElaborateBoxDerefVisitor<'a, 'tcx> {
36    fn tcx(&self) -> TyCtxt<'tcx> {
37        self.tcx
38    }
39
40    fn visit_place(
41        &mut self,
42        place: &mut Place<'tcx>,
43        context: visit::PlaceContext,
44        location: Location,
45    ) {
46        let tcx = self.tcx;
47
48        let base_ty = self.local_decls[place.local].ty;
49
50        // Derefer ensures that derefs are always the first projection
51        if let Some(PlaceElem::Deref) = place.projection.first()
52            && let Some(boxed_ty) = base_ty.boxed_ty()
53        {
54            let source_info = self.local_decls[place.local].source_info;
55
56            let ptr_ty = Ty::new_imm_ptr(tcx, boxed_ty);
57
58            let ptr_local = self.patch.new_temp(ptr_ty, source_info.span);
59
60            // Project to the first field (a `Unique`), then transmute that. We could project one
61            // further but in the end we'd hit a pattern type so we'd always have to transmute.
62            let field_place =
63                Place::from(place.local).project_to_field(FieldIdx::ZERO, &*self.local_decls, tcx);
64            self.patch.add_assign(
65                location,
66                Place::from(ptr_local),
67                Rvalue::Cast(CastKind::BoxDerefTransmute, Operand::Copy(field_place), ptr_ty),
68            );
69
70            place.local = ptr_local;
71        }
72
73        self.super_place(place, context, location);
74    }
75}
76
77pub(super) struct ElaborateBoxDerefs;
78
79impl<'tcx> crate::MirPass<'tcx> for ElaborateBoxDerefs {
80    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
81        // If box is not present, this pass doesn't need to do anything.
82        let Some(def_id) = tcx.lang_items().owned_box() else { return };
83
84        let unique_did = tcx.adt_def(def_id).non_enum_variant().fields[FieldIdx::ZERO].did;
85
86        let Some(unique_def) =
87            tcx.type_of(unique_did).instantiate_identity().skip_norm_wip().ty_adt_def()
88        else {
89            span_bug!(tcx.def_span(unique_did), "expected Box to contain Unique")
90        };
91
92        let nonnull_did = unique_def.non_enum_variant().fields[FieldIdx::ZERO].did;
93
94        let Some(nonnull_def) =
95            tcx.type_of(nonnull_did).instantiate_identity().skip_norm_wip().ty_adt_def()
96        else {
97            span_bug!(tcx.def_span(nonnull_did), "expected Unique to contain Nonnull")
98        };
99
100        let patch = MirPatch::new(body);
101
102        let local_decls = &mut body.local_decls;
103
104        let mut visitor = ElaborateBoxDerefVisitor { tcx, local_decls, patch };
105
106        for (block, data) in body.basic_blocks.as_mut_preserves_cfg().iter_enumerated_mut() {
107            visitor.visit_basic_block_data(block, data);
108        }
109
110        visitor.patch.apply(body);
111
112        for debug_info in body.var_debug_info.iter_mut() {
113            if let VarDebugInfoContents::Place(place) = &mut debug_info.value {
114                let mut new_projections: Option<Vec<_>> = None;
115
116                for (base, elem) in place.iter_projections() {
117                    let base_ty = base.ty(&body.local_decls, tcx).ty;
118
119                    if let PlaceElem::Deref = elem
120                        && let Some(boxed_ty) = base_ty.boxed_ty()
121                    {
122                        // Clone the projections before us, since now we need to mutate them.
123                        let new_projections =
124                            new_projections.get_or_insert_with(|| base.projection.to_vec());
125
126                        let (unique_ty, nonnull_ty, ptr_ty) =
127                            build_ptr_tys(tcx, boxed_ty, unique_def, nonnull_def);
128
129                        new_projections.extend_from_slice(&[
130                            PlaceElem::Field(FieldIdx::ZERO, unique_ty),
131                            PlaceElem::Field(FieldIdx::ZERO, nonnull_ty),
132                        ]);
133                        // While we can't project into a pattern type in a basic block,
134                        // this is debug info where it's fine.
135                        let pat_ty = Ty::new_pat(tcx, ptr_ty, tcx.mk_pat(PatternKind::NotNull));
136                        new_projections.push(PlaceElem::Field(FieldIdx::ZERO, pat_ty));
137                        new_projections.push(PlaceElem::Field(FieldIdx::ZERO, ptr_ty));
138                        new_projections.push(PlaceElem::Deref);
139                    } else if let Some(new_projections) = new_projections.as_mut() {
140                        // Keep building up our projections list once we've started it.
141                        new_projections.push(elem);
142                    }
143                }
144
145                // Store the mutated projections if we actually changed something.
146                if let Some(new_projections) = new_projections {
147                    place.projection = tcx.mk_place_elems(&new_projections);
148                }
149            }
150        }
151    }
152
153    fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy {
154        // Implements Box dereference semantics so backends and Miri do not have to handle them.
155        PassPolicy::Required
156    }
157}