Skip to main content

rustc_codegen_ssa/mir/
retag.rs

1//! Experimental support for emitting retags as function calls in generated code.
2//!
3//! We attempt to retag every argument and return value of a function, and every rvalue
4//! of an assignment. The first step to retagging is to generate a [`RetagPlan`], which
5//! describes which pointers within the place or operand can be retagged.
6
7use rustc_abi::{FieldIdx, FieldsShape, Size, VariantIdx, Variants};
8use rustc_ast::Mutability;
9use rustc_data_structures::fx::FxIndexMap;
10use rustc_middle::mir::{Rvalue, WithRetag};
11use rustc_middle::ty;
12use rustc_middle::ty::layout::TyAndLayout;
13
14use crate::mir::FunctionCx;
15use crate::mir::operand::{OperandRef, OperandRefBuilder, OperandValue};
16use crate::mir::place::PlaceRef;
17use crate::traits::{BaseTypeCodegenMethods, BuilderMethods, ConstCodegenMethods};
18use crate::{RetagFlags, RetagInfo};
19
20pub(crate) fn rvalue_needs_retag(rvalue: &Rvalue<'_>) -> bool {
21    // `Ref` has its own internal retagging
22    !#[allow(non_exhaustive_omitted_patterns)] match rvalue {
    Rvalue::Ref(..) => true,
    _ => false,
}matches!(rvalue, Rvalue::Ref(..)) && !#[allow(non_exhaustive_omitted_patterns)] match rvalue {
    Rvalue::Use(.., WithRetag::No) => true,
    _ => false,
}matches!(rvalue, Rvalue::Use(.., WithRetag::No))
23}
24
25/// A description of the pointers within a type that need to be retagged.
26#[derive(#[automatically_derived]
impl<V: ::core::fmt::Debug> ::core::fmt::Debug for RetagPlan<V> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            RetagPlan::EmitRetag(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "EmitRetag", &__self_0),
            RetagPlan::Recurse {
                field_plans: __self_0, variant_plans: __self_1 } =>
                ::core::fmt::Formatter::debug_struct_field2_finish(f,
                    "Recurse", "field_plans", __self_0, "variant_plans",
                    &__self_1),
        }
    }
}Debug)]
27enum RetagPlan<V> {
28    /// Indicates that a pointer should be retagged.
29    EmitRetag(RetagInfo<V>),
30    /// Indicates that one or more fields or variants of this type
31    /// contain pointers that need to be retagged.
32    Recurse {
33        field_plans: FxIndexMap<FieldIdx, RetagPlan<V>>,
34        variant_plans: FxIndexMap<VariantIdx, RetagPlan<V>>,
35    },
36}
37
38impl<V> RetagPlan<V> {
39    /// A helper function to move a [`RetagPlan`] into a particular field.
40    fn for_field(self, ix: FieldIdx) -> Self {
41        let mut field_plans = FxIndexMap::default();
42        field_plans.insert(ix, self);
43        RetagPlan::Recurse { field_plans, variant_plans: FxIndexMap::default() }
44    }
45}
46
47impl<'a, 'tcx, V> RetagPlan<V> {
48    /// Attempts to create a [`RetagPlan`] for a place or operand with the given layout.
49    fn build<Bx: BuilderMethods<'a, 'tcx>>(
50        bx: &mut Bx,
51        layout: TyAndLayout<'tcx>,
52        is_fn_entry: bool,
53    ) -> Option<RetagPlan<Bx::Value>> {
54        // If the value being retagged is smaller than a pointer, then it can't contain any
55        // pointers we need to retag, so we can stop recursion early. This optimization is
56        // crucial for ZSTs, because they can contain way more fields than we can ever visit.
57        if layout.is_sized() && layout.size < bx.tcx().data_layout.pointer_size() {
58            return None;
59        }
60        // Check the type of this value to see what to do with it (retag, or recurse).
61        match layout.ty.kind() {
62            &ty::Ref(_, pointee, mt) => {
63                let pointee_layout = bx.layout_of(pointee);
64                Self::emit_retag(bx, pointee_layout, Some(mt), is_fn_entry)
65            }
66            &ty::RawPtr(_, _) => None,
67            // `Box` needs special handling, since the innermost pointer is what gets retagged, but
68            //  the outermost `Box` is what determines the permission that gets created.
69            ty::Adt(adt, _) if adt.is_box() => Self::visit_box(bx, layout, is_fn_entry),
70            // Skip traversing for everything inside of `MaybeDangling`
71            ty::Adt(adt, _) if adt.is_maybe_dangling() => None,
72            _ => Self::walk_value(bx, layout, is_fn_entry),
73        }
74    }
75
76    /// Recurses through the fields and variants of a value in memory order to create a [`RetagPlan`].
77    fn walk_value<Bx: BuilderMethods<'a, 'tcx>>(
78        bx: &mut Bx,
79        layout: TyAndLayout<'tcx>,
80        is_fn_entry: bool,
81    ) -> Option<RetagPlan<Bx::Value>> {
82        let mut field_plans = FxIndexMap::default();
83        let mut variant_plans = FxIndexMap::default();
84
85        match &layout.fields {
86            FieldsShape::Union(_) | FieldsShape::Primitive => {}
87            _ => {
88                for ix in layout.fields.index_by_increasing_offset() {
89                    let field_layout = layout.field(bx, ix);
90                    if let Some(plan) = Self::build(bx, field_layout, is_fn_entry) {
91                        field_plans.insert(FieldIdx::from_usize(ix), plan);
92                    }
93                }
94            }
95        }
96
97        match &layout.variants {
98            Variants::Single { .. } | Variants::Empty => {}
99            Variants::Multiple { variants, .. } => {
100                for ix in variants.indices() {
101                    let variant_layout = layout.for_variant(bx, ix);
102                    if let Some(plan) = Self::build(bx, variant_layout, is_fn_entry) {
103                        variant_plans.insert(ix, plan);
104                    }
105                }
106            }
107        }
108
109        (!field_plans.is_empty() || !variant_plans.is_empty())
110            .then(|| RetagPlan::Recurse { field_plans, variant_plans })
111    }
112
113    /// Emits a retag for a `Box`.
114    fn visit_box<Bx: BuilderMethods<'a, 'tcx>>(
115        bx: &mut Bx,
116        layout: TyAndLayout<'tcx>,
117        is_fn_entry: bool,
118    ) -> Option<RetagPlan<Bx::Value>> {
119        if !layout.ty.is_box() {
    ::core::panicking::panic("assertion failed: layout.ty.is_box()")
};assert!(layout.ty.is_box());
120        {
    match (&layout.fields.count(), &2) {
        (left_val, right_val) => {
            if !(*left_val == *right_val) {
                let kind = ::core::panicking::AssertKind::Eq;
                ::core::panicking::assert_failed(kind, &*left_val,
                    &*right_val,
                    ::core::option::Option::Some(format_args!("`Box` must have exactly 2 fields")));
            }
        }
    }
};assert_eq!(layout.fields.count(), 2, "`Box` must have exactly 2 fields");
121        let mut field_plans = FxIndexMap::default();
122
123        // Only retag the inner pointer of a `Box` if it came from the global allocator.
124        if layout.ty.is_box_global(bx.tcx()) {
125            let boxed_ty = layout.ty.expect_boxed_ty();
126            let boxed_layout = bx.layout_of(boxed_ty);
127            if let Some(mut plan) = Self::emit_retag(bx, boxed_layout, None, is_fn_entry) {
128                // `Unique<T>`
129                let unique = layout.field(bx, 0);
130                {
    match (&unique.fields.count(), &2) {
        (left_val, right_val) => {
            if !(*left_val == *right_val) {
                let kind = ::core::panicking::AssertKind::Eq;
                ::core::panicking::assert_failed(kind, &*left_val,
                    &*right_val, ::core::option::Option::None);
            }
        }
    }
};assert_eq!(unique.fields.count(), 2);
131                plan = plan.for_field(FieldIdx::ZERO);
132
133                // `NonNull<T>`
134                let nonnull = unique.field(bx, 0);
135                {
    match (&nonnull.fields.count(), &1) {
        (left_val, right_val) => {
            if !(*left_val == *right_val) {
                let kind = ::core::panicking::AssertKind::Eq;
                ::core::panicking::assert_failed(kind, &*left_val,
                    &*right_val, ::core::option::Option::None);
            }
        }
    }
};assert_eq!(nonnull.fields.count(), 1);
136                plan = plan.for_field(FieldIdx::ZERO);
137
138                // `*mut T is !null`
139                let pattern = nonnull.field(bx, 0);
140                let ty::Pat(base, _) = pattern.ty.kind() else {
141                    {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("`NonNull` should contain a pattern type")));
}unreachable!("`NonNull` should contain a pattern type")
142                };
143                {
    match (&base.builtin_deref(true), &Some(boxed_ty)) {
        (left_val, right_val) => {
            if !(*left_val == *right_val) {
                let kind = ::core::panicking::AssertKind::Eq;
                ::core::panicking::assert_failed(kind, &*left_val,
                    &*right_val, ::core::option::Option::None);
            }
        }
    }
};assert_eq!(base.builtin_deref(true), Some(boxed_ty));
144
145                field_plans.insert(FieldIdx::ZERO, plan);
146            }
147        }
148
149        // We always try to retag the second field (the allocator)
150        let field_layout = layout.field(bx, 1);
151        if let Some(plan) = Self::build(bx, field_layout, is_fn_entry) {
152            field_plans.insert(FieldIdx::ONE, plan);
153        }
154
155        (!field_plans.is_empty())
156            .then(|| RetagPlan::Recurse { field_plans, variant_plans: FxIndexMap::default() })
157    }
158
159    /// Determines if a pointer needs to be retagged, when it points to
160    /// a type with the given layout. Returns `None` for mutable pointers
161    /// to types that are entirely covered by `UnsafePinned`, for which retags
162    /// are a no-op.
163    fn emit_retag<Bx: BuilderMethods<'a, 'tcx>>(
164        bx: &mut Bx,
165        pointee_layout: TyAndLayout<'tcx>,
166        ptr_kind: Option<Mutability>,
167        is_fn_entry: bool,
168    ) -> Option<RetagPlan<Bx::Value>> {
169        let tcx = bx.tcx();
170
171        let pointee_ty = pointee_layout.ty;
172
173        let is_mutable = #[allow(non_exhaustive_omitted_patterns)] match ptr_kind {
    Some(Mutability::Mut) | None => true,
    _ => false,
}matches!(ptr_kind, Some(Mutability::Mut) | None);
174        let is_unpin = pointee_ty.is_unpin(tcx, bx.typing_env());
175        let is_freeze = pointee_ty.is_freeze(tcx, bx.typing_env());
176        let is_box = ptr_kind.is_none();
177
178        // `&mut !Unpin` is not protected
179        let is_protected = is_fn_entry && (!is_mutable || is_unpin);
180
181        if is_mutable && !is_unpin {
182            return None;
183        }
184
185        let im_layout = bx.const_null(bx.type_ptr());
186        let pin_layout = bx.const_null(bx.type_ptr());
187
188        let mut flags = RetagFlags::empty();
189        flags.set(RetagFlags::IS_PROTECTED, is_protected);
190        flags.set(RetagFlags::IS_MUTABLE, is_mutable);
191        flags.set(RetagFlags::IS_FREEZE, is_freeze);
192        flags.set(RetagFlags::IS_BOX, is_box);
193
194        Some(RetagPlan::EmitRetag(RetagInfo {
195            size: pointee_layout.size,
196            im_layout,
197            pin_layout,
198            flags,
199        }))
200    }
201}
202
203impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
204    /// Retags the pointers within an [`OperandRef`].
205    pub(crate) fn codegen_retag_operand(
206        &mut self,
207        bx: &mut Bx,
208        operand: OperandRef<'tcx, Bx::Value>,
209        is_fn_entry: bool,
210    ) -> OperandRef<'tcx, Bx::Value> {
211        if let OperandValue::Ref(place_ref) = operand.val {
212            let place_ref = place_ref.with_type(operand.layout);
213            self.codegen_retag_place(bx, place_ref, is_fn_entry);
214        } else if let Some(plan) = RetagPlan::<Bx::Value>::build(bx, operand.layout, is_fn_entry) {
215            let mut builder = OperandRefBuilder::from_existing(operand);
216            self.retag_operand(bx, &plan, operand, &mut builder, Size::ZERO);
217            return builder.build(bx.cx());
218        }
219        operand
220    }
221
222    /// Retags the pointers within a [`PlaceRef`].
223    pub(crate) fn codegen_retag_place(
224        &mut self,
225        bx: &mut Bx,
226        place_ref: PlaceRef<'tcx, Bx::Value>,
227        is_fn_entry: bool,
228    ) {
229        if let Some(plan) = RetagPlan::<Bx::Value>::build(bx, place_ref.layout, is_fn_entry) {
230            self.retag_place(bx, &plan, place_ref);
231        }
232    }
233
234    fn retag_operand(
235        &mut self,
236        bx: &mut Bx,
237        plan: &RetagPlan<Bx::Value>,
238        curr_operand: OperandRef<'tcx, Bx::Value>,
239        builder: &mut OperandRefBuilder<'tcx, Bx::Value>,
240        offset: Size,
241    ) {
242        match plan {
243            RetagPlan::EmitRetag(info) => {
244                let (pointer, _) = curr_operand.val.pointer_parts();
245                let retagged_pointer = bx.retag_reg(pointer, info);
246                builder.update_imm(offset, retagged_pointer);
247            }
248            RetagPlan::Recurse { field_plans, variant_plans } => {
249                let layout = curr_operand.layout;
250                for (ix, plan) in field_plans {
251                    let inner_offset = layout.fields.offset(ix.as_usize());
252                    let field_offset = offset + inner_offset;
253
254                    let field_layout = curr_operand.layout.field(bx, ix.index());
255                    // Part of https://github.com/rust-lang/compiler-team/issues/838
256                    if curr_operand.layout.is_ssa_standalone() && !field_layout.is_ssa_standalone()
257                    {
258                        // FIXME: support vector types, requires insert_element as part of cg-ssa
259                        // FIXME: Nothing should be looking at the *array* inside a `repr(simd)` type,
260                        // as that array doesn't really exist. Perhaps this should be a `bug!`,
261                        // with simd types handled before getting here?
262                    } else {
263                        let field_operand = curr_operand.extract_field(self, bx, ix.as_usize());
264                        self.retag_operand(bx, &plan, field_operand, builder, field_offset);
265                    }
266                }
267
268                if !variant_plans.is_empty() {
269                    let discr_ty = layout.ty.discriminant_ty(bx.tcx());
270                    let discr_val = curr_operand.codegen_get_discr(self, bx, discr_ty);
271
272                    if let Some(val) = bx.const_to_opt_u128(discr_val, false) {
273                        let ix = VariantIdx::from_usize(val as usize);
274                        if let Some(plan) = variant_plans.get(&ix) {
275                            let mut variant_op = curr_operand;
276                            variant_op.layout = curr_operand.layout.for_variant(bx, ix);
277
278                            self.retag_operand(bx, plan, variant_op, builder, offset);
279                        }
280                    } else {
281                        // We create a temporary place to store the operand, because its value will differ
282                        // depending on the variant that we have.
283                        let scratch = PlaceRef::alloca(bx, curr_operand.layout);
284                        scratch.storage_live(bx);
285                        curr_operand.store_with_annotation(bx, scratch);
286
287                        // We retag the contents of the place
288                        self.retag_variants(bx, scratch, discr_val, variant_plans);
289
290                        // Afterward, we load the now-updated operand and end the lifetime of the place.
291                        let updated_op = bx.load_operand(scratch);
292                        scratch.storage_dead(bx);
293
294                        match updated_op.val {
295                            OperandValue::ZeroSized | OperandValue::Ref(_) => {}
296                            OperandValue::Immediate(imm) => builder.update_imm(offset, imm),
297                            OperandValue::Pair(fst, snd) => {
298                                builder.update_imm(offset, fst);
299                                builder.update_imm(offset + Size::from_bytes(1), snd)
300                            }
301                        }
302                    }
303                }
304            }
305        }
306    }
307
308    fn retag_place(
309        &mut self,
310        bx: &mut Bx,
311        plan: &RetagPlan<Bx::Value>,
312        place: PlaceRef<'tcx, Bx::Value>,
313    ) {
314        match plan {
315            RetagPlan::EmitRetag(info) => {
316                bx.retag_mem(place.val.llval, info);
317            }
318            RetagPlan::Recurse { field_plans, variant_plans } => {
319                for (ix, plan) in field_plans {
320                    let field_place = place.project_field(bx, ix.as_usize());
321                    self.retag_place(bx, &plan, field_place);
322                }
323                if !variant_plans.is_empty() {
324                    let operand = bx.load_operand(place);
325                    let discr_ty = place.layout.ty.discriminant_ty(bx.tcx());
326                    let discr_val = operand.codegen_get_discr(self, bx, discr_ty);
327                    self.retag_variants(bx, place, discr_val, variant_plans);
328                }
329            }
330        }
331    }
332
333    /// Retags each variant of a [`PlaceRef`] with the given discriminant.
334    fn retag_variants(
335        &mut self,
336        bx: &mut Bx,
337        place: PlaceRef<'tcx, Bx::Value>,
338        discr: Bx::Value,
339        variant_plans: &FxIndexMap<VariantIdx, RetagPlan<Bx::Value>>,
340    ) {
341        let layout = place.layout;
342
343        let root_block = bx.llbb();
344        let mut variant_blocks = Vec::with_capacity(variant_plans.len());
345        let join_block = bx.append_sibling_block("retag_join");
346
347        for (ix, plan) in variant_plans {
348            let variant_discr = layout.ty.discriminant_for_variant(bx.tcx(), *ix);
349            let variant_discr_val = variant_discr.expect("Invalid variant index.").val;
350
351            let variant_block = bx.append_sibling_block("retag_variant");
352            bx.switch_to_block(variant_block);
353
354            let variant_place = place.project_downcast(bx, *ix);
355            self.retag_place(bx, plan, variant_place);
356
357            variant_blocks.push((variant_discr_val, variant_block));
358            bx.br(join_block);
359        }
360
361        bx.switch_to_block(root_block);
362        bx.switch(discr, join_block, variant_blocks.into_iter());
363        bx.switch_to_block(join_block);
364    }
365}