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