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//!
5//! `ShallowInitBox` being a device for drop elaboration to understand deferred assignment to box
6//! contents, we do not need this any more on runtime MIR.
7
8use rustc_abi::{FieldIdx, VariantIdx};
9use rustc_index::{IndexVec, indexvec};
10use rustc_middle::mir::visit::MutVisitor;
11use rustc_middle::mir::*;
12use rustc_middle::span_bug;
13use rustc_middle::ty::{self, Ty, TyCtxt};
14
15use crate::patch::MirPatch;
16
17/// Constructs the types used when accessing a Box's pointer
18fn build_ptr_tys<'tcx>(
19    tcx: TyCtxt<'tcx>,
20    pointee: Ty<'tcx>,
21    unique_def: ty::AdtDef<'tcx>,
22    nonnull_def: ty::AdtDef<'tcx>,
23) -> (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>) {
24    let args = tcx.mk_args(&[pointee.into()]);
25    let unique_ty = Ty::new_adt(tcx, unique_def, args);
26    let nonnull_ty = Ty::new_adt(tcx, nonnull_def, args);
27    let ptr_ty = Ty::new_imm_ptr(tcx, pointee);
28
29    (unique_ty, nonnull_ty, ptr_ty)
30}
31
32/// Constructs the projection needed to access a Box's pointer
33pub(super) fn build_projection<'tcx>(
34    unique_ty: Ty<'tcx>,
35    nonnull_ty: Ty<'tcx>,
36) -> [PlaceElem<'tcx>; 2] {
37    [PlaceElem::Field(FieldIdx::ZERO, unique_ty), PlaceElem::Field(FieldIdx::ZERO, nonnull_ty)]
38}
39
40struct ElaborateBoxDerefVisitor<'a, 'tcx> {
41    tcx: TyCtxt<'tcx>,
42    unique_def: ty::AdtDef<'tcx>,
43    nonnull_def: ty::AdtDef<'tcx>,
44    local_decls: &'a mut LocalDecls<'tcx>,
45    patch: MirPatch<'tcx>,
46}
47
48impl<'a, 'tcx> MutVisitor<'tcx> for ElaborateBoxDerefVisitor<'a, 'tcx> {
49    fn tcx(&self) -> TyCtxt<'tcx> {
50        self.tcx
51    }
52
53    fn visit_place(
54        &mut self,
55        place: &mut Place<'tcx>,
56        context: visit::PlaceContext,
57        location: Location,
58    ) {
59        let tcx = self.tcx;
60
61        let base_ty = self.local_decls[place.local].ty;
62
63        // Derefer ensures that derefs are always the first projection
64        if let Some(PlaceElem::Deref) = place.projection.first()
65            && let Some(boxed_ty) = base_ty.boxed_ty()
66        {
67            let source_info = self.local_decls[place.local].source_info;
68
69            let (unique_ty, nonnull_ty, ptr_ty) =
70                build_ptr_tys(tcx, boxed_ty, self.unique_def, self.nonnull_def);
71
72            let ptr_local = self.patch.new_temp(ptr_ty, source_info.span);
73
74            self.patch.add_assign(
75                location,
76                Place::from(ptr_local),
77                Rvalue::Cast(
78                    CastKind::Transmute,
79                    Operand::Copy(
80                        Place::from(place.local)
81                            .project_deeper(&build_projection(unique_ty, nonnull_ty), tcx),
82                    ),
83                    ptr_ty,
84                ),
85            );
86
87            place.local = ptr_local;
88        }
89
90        self.super_place(place, context, location);
91    }
92
93    fn visit_statement(&mut self, stmt: &mut Statement<'tcx>, location: Location) {
94        self.super_statement(stmt, location);
95
96        let tcx = self.tcx;
97        let source_info = stmt.source_info;
98
99        if let StatementKind::Assign(box (_, ref mut rvalue)) = stmt.kind
100            && let Rvalue::ShallowInitBox(ref mut mutptr_to_u8, pointee) = *rvalue
101            && let ty::Adt(box_adt, box_args) = Ty::new_box(tcx, pointee).kind()
102        {
103            let args = tcx.mk_args(&[pointee.into()]);
104            let (unique_ty, nonnull_ty, ptr_ty) =
105                build_ptr_tys(tcx, pointee, self.unique_def, self.nonnull_def);
106            let adt_kind = |def: ty::AdtDef<'tcx>, args| {
107                Box::new(AggregateKind::Adt(def.did(), VariantIdx::ZERO, args, None, None))
108            };
109            let zst = |ty| {
110                Operand::Constant(Box::new(ConstOperand {
111                    span: source_info.span,
112                    user_ty: None,
113                    const_: Const::zero_sized(ty),
114                }))
115            };
116
117            let constptr = self.patch.new_temp(ptr_ty, source_info.span);
118            self.patch.add_assign(
119                location,
120                constptr.into(),
121                Rvalue::Cast(CastKind::Transmute, mutptr_to_u8.clone(), ptr_ty),
122            );
123
124            let nonnull = self.patch.new_temp(nonnull_ty, source_info.span);
125            self.patch.add_assign(
126                location,
127                nonnull.into(),
128                Rvalue::Aggregate(
129                    adt_kind(self.nonnull_def, args),
130                    indexvec![Operand::Move(constptr.into())],
131                ),
132            );
133
134            let unique = self.patch.new_temp(unique_ty, source_info.span);
135            let phantomdata_ty =
136                self.unique_def.non_enum_variant().fields[FieldIdx::ONE].ty(tcx, args);
137            self.patch.add_assign(
138                location,
139                unique.into(),
140                Rvalue::Aggregate(
141                    adt_kind(self.unique_def, args),
142                    indexvec![Operand::Move(nonnull.into()), zst(phantomdata_ty)],
143                ),
144            );
145
146            let global_alloc_ty =
147                box_adt.non_enum_variant().fields[FieldIdx::ONE].ty(tcx, box_args);
148            *rvalue = Rvalue::Aggregate(
149                adt_kind(*box_adt, box_args),
150                indexvec![Operand::Move(unique.into()), zst(global_alloc_ty)],
151            );
152        }
153    }
154}
155
156pub(super) struct ElaborateBoxDerefs;
157
158impl<'tcx> crate::MirPass<'tcx> for ElaborateBoxDerefs {
159    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
160        // If box is not present, this pass doesn't need to do anything.
161        let Some(def_id) = tcx.lang_items().owned_box() else { return };
162
163        let unique_did = tcx.adt_def(def_id).non_enum_variant().fields[FieldIdx::ZERO].did;
164
165        let Some(unique_def) = tcx.type_of(unique_did).instantiate_identity().ty_adt_def() else {
166            span_bug!(tcx.def_span(unique_did), "expected Box to contain Unique")
167        };
168
169        let nonnull_did = unique_def.non_enum_variant().fields[FieldIdx::ZERO].did;
170
171        let Some(nonnull_def) = tcx.type_of(nonnull_did).instantiate_identity().ty_adt_def() else {
172            span_bug!(tcx.def_span(nonnull_did), "expected Unique to contain Nonnull")
173        };
174
175        let patch = MirPatch::new(body);
176
177        let local_decls = &mut body.local_decls;
178
179        let mut visitor =
180            ElaborateBoxDerefVisitor { tcx, unique_def, nonnull_def, local_decls, patch };
181
182        for (block, data) in body.basic_blocks.as_mut_preserves_cfg().iter_enumerated_mut() {
183            visitor.visit_basic_block_data(block, data);
184        }
185
186        visitor.patch.apply(body);
187
188        for debug_info in body.var_debug_info.iter_mut() {
189            if let VarDebugInfoContents::Place(place) = &mut debug_info.value {
190                let mut new_projections: Option<Vec<_>> = None;
191
192                for (base, elem) in place.iter_projections() {
193                    let base_ty = base.ty(&body.local_decls, tcx).ty;
194
195                    if let PlaceElem::Deref = elem
196                        && let Some(boxed_ty) = base_ty.boxed_ty()
197                    {
198                        // Clone the projections before us, since now we need to mutate them.
199                        let new_projections =
200                            new_projections.get_or_insert_with(|| base.projection.to_vec());
201
202                        let (unique_ty, nonnull_ty, ptr_ty) =
203                            build_ptr_tys(tcx, boxed_ty, unique_def, nonnull_def);
204
205                        new_projections.extend_from_slice(&build_projection(unique_ty, nonnull_ty));
206                        // While we can't project into `NonNull<_>` in a basic block
207                        // due to MCP#807, this is debug info where it's fine.
208                        new_projections.push(PlaceElem::Field(FieldIdx::ZERO, ptr_ty));
209                        new_projections.push(PlaceElem::Deref);
210                    } else if let Some(new_projections) = new_projections.as_mut() {
211                        // Keep building up our projections list once we've started it.
212                        new_projections.push(elem);
213                    }
214                }
215
216                // Store the mutated projections if we actually changed something.
217                if let Some(new_projections) = new_projections {
218                    place.projection = tcx.mk_place_elems(&new_projections);
219                }
220            }
221        }
222    }
223
224    fn is_required(&self) -> bool {
225        true
226    }
227}