rustc_mir_transform/
elaborate_box_derefs.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
//! This pass transforms derefs of Box into a deref of the pointer inside Box.
//!
//! Box is not actually a pointer so it is incorrect to dereference it directly.

use rustc_abi::FieldIdx;
use rustc_hir::def_id::DefId;
use rustc_middle::mir::patch::MirPatch;
use rustc_middle::mir::visit::MutVisitor;
use rustc_middle::mir::*;
use rustc_middle::span_bug;
use rustc_middle::ty::{Ty, TyCtxt};

/// Constructs the types used when accessing a Box's pointer
fn build_ptr_tys<'tcx>(
    tcx: TyCtxt<'tcx>,
    pointee: Ty<'tcx>,
    unique_did: DefId,
    nonnull_did: DefId,
) -> (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>) {
    let args = tcx.mk_args(&[pointee.into()]);
    let unique_ty = tcx.type_of(unique_did).instantiate(tcx, args);
    let nonnull_ty = tcx.type_of(nonnull_did).instantiate(tcx, args);
    let ptr_ty = Ty::new_imm_ptr(tcx, pointee);

    (unique_ty, nonnull_ty, ptr_ty)
}

/// Constructs the projection needed to access a Box's pointer
pub(super) fn build_projection<'tcx>(
    unique_ty: Ty<'tcx>,
    nonnull_ty: Ty<'tcx>,
    ptr_ty: Ty<'tcx>,
) -> [PlaceElem<'tcx>; 3] {
    [
        PlaceElem::Field(FieldIdx::ZERO, unique_ty),
        PlaceElem::Field(FieldIdx::ZERO, nonnull_ty),
        PlaceElem::Field(FieldIdx::ZERO, ptr_ty),
    ]
}

struct ElaborateBoxDerefVisitor<'a, 'tcx> {
    tcx: TyCtxt<'tcx>,
    unique_did: DefId,
    nonnull_did: DefId,
    local_decls: &'a mut LocalDecls<'tcx>,
    patch: MirPatch<'tcx>,
}

impl<'a, 'tcx> MutVisitor<'tcx> for ElaborateBoxDerefVisitor<'a, 'tcx> {
    fn tcx(&self) -> TyCtxt<'tcx> {
        self.tcx
    }

    fn visit_place(
        &mut self,
        place: &mut Place<'tcx>,
        context: visit::PlaceContext,
        location: Location,
    ) {
        let tcx = self.tcx;

        let base_ty = self.local_decls[place.local].ty;

        // Derefer ensures that derefs are always the first projection
        if let Some(PlaceElem::Deref) = place.projection.first()
            && let Some(boxed_ty) = base_ty.boxed_ty()
        {
            let source_info = self.local_decls[place.local].source_info;

            let (unique_ty, nonnull_ty, ptr_ty) =
                build_ptr_tys(tcx, boxed_ty, self.unique_did, self.nonnull_did);

            let ptr_local = self.patch.new_temp(ptr_ty, source_info.span);

            self.patch.add_assign(
                location,
                Place::from(ptr_local),
                Rvalue::Use(Operand::Copy(
                    Place::from(place.local)
                        .project_deeper(&build_projection(unique_ty, nonnull_ty, ptr_ty), tcx),
                )),
            );

            place.local = ptr_local;
        }

        self.super_place(place, context, location);
    }
}

pub(super) struct ElaborateBoxDerefs;

impl<'tcx> crate::MirPass<'tcx> for ElaborateBoxDerefs {
    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
        // If box is not present, this pass doesn't need to do anything.
        let Some(def_id) = tcx.lang_items().owned_box() else { return };

        let unique_did = tcx.adt_def(def_id).non_enum_variant().fields[FieldIdx::ZERO].did;

        let Some(nonnull_def) = tcx.type_of(unique_did).instantiate_identity().ty_adt_def() else {
            span_bug!(tcx.def_span(unique_did), "expected Box to contain Unique")
        };

        let nonnull_did = nonnull_def.non_enum_variant().fields[FieldIdx::ZERO].did;

        let patch = MirPatch::new(body);

        let local_decls = &mut body.local_decls;

        let mut visitor =
            ElaborateBoxDerefVisitor { tcx, unique_did, nonnull_did, local_decls, patch };

        for (block, data) in body.basic_blocks.as_mut_preserves_cfg().iter_enumerated_mut() {
            visitor.visit_basic_block_data(block, data);
        }

        visitor.patch.apply(body);

        for debug_info in body.var_debug_info.iter_mut() {
            if let VarDebugInfoContents::Place(place) = &mut debug_info.value {
                let mut new_projections: Option<Vec<_>> = None;

                for (base, elem) in place.iter_projections() {
                    let base_ty = base.ty(&body.local_decls, tcx).ty;

                    if let PlaceElem::Deref = elem
                        && let Some(boxed_ty) = base_ty.boxed_ty()
                    {
                        // Clone the projections before us, since now we need to mutate them.
                        let new_projections =
                            new_projections.get_or_insert_with(|| base.projection.to_vec());

                        let (unique_ty, nonnull_ty, ptr_ty) =
                            build_ptr_tys(tcx, boxed_ty, unique_did, nonnull_did);

                        new_projections
                            .extend_from_slice(&build_projection(unique_ty, nonnull_ty, ptr_ty));
                        new_projections.push(PlaceElem::Deref);
                    } else if let Some(new_projections) = new_projections.as_mut() {
                        // Keep building up our projections list once we've started it.
                        new_projections.push(elem);
                    }
                }

                // Store the mutated projections if we actually changed something.
                if let Some(new_projections) = new_projections {
                    place.projection = tcx.mk_place_elems(&new_projections);
                }
            }
        }
    }
}