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. Then, we traverse
6//! the [`RetagPlan`] to emit the calls.
7
8use rustc_abi::{FieldIdx, FieldsShape, Size, VariantIdx, Variants};
9use rustc_ast::Mutability;
10use rustc_data_structures::fx::FxIndexMap;
11use rustc_data_structures::range_set::RangeSet;
12use rustc_middle::mir::interpret::Allocation;
13use rustc_middle::mir::{Rvalue, WithRetag};
14use rustc_middle::ty::layout::{HasTypingEnv, TyAndLayout};
15use rustc_middle::ty::{self, Ty};
16
17use crate::common::IntPredicate;
18use crate::mir::operand::{OperandRef, OperandRefBuilder, OperandValue};
19use crate::mir::place::PlaceRef;
20use crate::mir::{FunctionCx, bug};
21use crate::traits::{
22    BaseTypeCodegenMethods, BuilderMethods, ConstCodegenMethods, StaticCodegenMethods,
23};
24use crate::{RetagFlags, RetagInfo};
25
26pub(crate) fn rvalue_needs_retag(rvalue: &Rvalue<'_>) -> bool {
27    // `Ref` has its own internal retagging
28    !#[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))
29}
30
31/// A description of the pointers within a type that need to be retagged.
32#[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 {
            Self::EmitRetag(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "EmitRetag", &__self_0),
            Self::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)]
33enum RetagPlan<V> {
34    /// Indicates that a pointer should be retagged.
35    EmitRetag(RetagInfo<V>),
36    /// Indicates that one or more fields or variants of this type
37    /// contain pointers that need to be retagged.
38    Recurse {
39        field_plans: Option<RetagFields<V>>,
40        variant_plans: FxIndexMap<VariantIdx, RetagPlan<V>>,
41    },
42}
43
44/// The layout of the fields that need to be retagged.
45///
46/// This mirrors [`FieldsShape`] but excludes unions,
47/// which are never retagged, and primitives, which fall
48/// into [`RetagPlan::EmitRetag`].
49#[derive(#[automatically_derived]
impl<V: ::core::fmt::Debug> ::core::fmt::Debug for RetagFields<V> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            Self::Arbitrary(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "Arbitrary", &__self_0),
            Self::Array(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Array",
                    &__self_0),
        }
    }
}Debug)]
50enum RetagFields<V> {
51    Arbitrary(FxIndexMap<FieldIdx, RetagPlan<V>>),
52    Array(Box<RetagPlan<V>>),
53}
54
55impl<V> RetagPlan<V> {
56    /// A helper function to move a [`RetagPlan`] into a particular field.
57    fn for_field(self, ix: FieldIdx) -> Self {
58        let mut field_plans = FxIndexMap::default();
59        field_plans.insert(ix, self);
60        RetagPlan::Recurse {
61            field_plans: Some(RetagFields::Arbitrary(field_plans)),
62            variant_plans: FxIndexMap::default(),
63        }
64    }
65}
66
67impl<'a, 'tcx, V> RetagPlan<V> {
68    /// Attempts to create a [`RetagPlan`] for a place or operand with the given layout.
69    fn build<Bx: BuilderMethods<'a, 'tcx>>(
70        bx: &mut Bx,
71        layout: TyAndLayout<'tcx>,
72        is_fn_entry: bool,
73    ) -> Option<RetagPlan<Bx::Value>> {
74        // If the value being retagged is smaller than a pointer, then it can't contain any
75        // pointers we need to retag, so we can stop recursion early. This optimization is
76        // crucial for ZSTs, because they can contain way more fields than we can ever visit.
77        if layout.is_sized() && layout.size < bx.tcx().data_layout.pointer_size() {
78            return None;
79        }
80        // Check the type of this value to see what to do with it (retag, or recurse).
81        match layout.ty.kind() {
82            &ty::Ref(_, pointee, mt) => {
83                let pointee_layout = bx.layout_of(pointee);
84                Self::emit_retag(bx, pointee_layout, Some(mt), is_fn_entry)
85            }
86            &ty::RawPtr(_, _) => None,
87            // `Box` needs special handling, since the innermost pointer is what gets retagged, but
88            //  the outermost `Box` is what determines the permission that gets created.
89            ty::Adt(adt, _) if adt.is_box() => Self::visit_box(bx, layout, is_fn_entry),
90            // Skip traversing for everything inside of `MaybeDangling`
91            _ if layout.ty.is_like_maybe_dangling() => None,
92            _ => Self::walk_value(bx, layout, is_fn_entry),
93        }
94    }
95
96    /// Recurses through the fields and variants of a value in memory order to create a [`RetagPlan`].
97    fn walk_value<Bx: BuilderMethods<'a, 'tcx>>(
98        bx: &mut Bx,
99        layout: TyAndLayout<'tcx>,
100        is_fn_entry: bool,
101    ) -> Option<RetagPlan<Bx::Value>> {
102        let field_plans = match &layout.fields {
103            FieldsShape::Union(_) | FieldsShape::Primitive => None,
104            FieldsShape::Arbitrary { .. } => {
105                let mut field_plans = FxIndexMap::default();
106                for ix in layout.fields.index_by_increasing_offset() {
107                    let field_layout = layout.field(bx, ix);
108                    if let Some(plan) = Self::build(bx, field_layout, is_fn_entry) {
109                        field_plans.insert(FieldIdx::from_usize(ix), plan);
110                    }
111                }
112                (!field_plans.is_empty()).then_some(RetagFields::Arbitrary(field_plans))
113            }
114            FieldsShape::Array { count, .. } if *count > 0 => {
115                let field_layout = layout.field(bx, 0);
116                Self::build(bx, field_layout, is_fn_entry)
117                    .map(|plan| RetagFields::Array(Box::new(plan)))
118            }
119            FieldsShape::Array { .. } => None,
120        };
121
122        let mut variant_plans = FxIndexMap::default();
123        match &layout.variants {
124            Variants::Single { .. } | Variants::Empty => {}
125            Variants::Multiple { variants, .. } => {
126                for ix in variants.indices() {
127                    let variant_layout = layout.for_variant(bx, ix);
128                    if let Some(plan) = Self::build(bx, variant_layout, is_fn_entry) {
129                        variant_plans.insert(ix, plan);
130                    }
131                }
132            }
133        }
134
135        (field_plans.is_some() || !variant_plans.is_empty())
136            .then(|| RetagPlan::Recurse { field_plans, variant_plans })
137    }
138
139    /// Emits a retag for a `Box`.
140    fn visit_box<Bx: BuilderMethods<'a, 'tcx>>(
141        bx: &mut Bx,
142        layout: TyAndLayout<'tcx>,
143        is_fn_entry: bool,
144    ) -> Option<RetagPlan<Bx::Value>> {
145        if !layout.ty.is_box() {
    ::core::panicking::panic("assertion failed: layout.ty.is_box()")
};assert!(layout.ty.is_box());
146        {
    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");
147        let mut field_plans = FxIndexMap::default();
148
149        // Only retag the inner pointer of a `Box` if it came from the global allocator.
150        if layout.ty.is_box_global(bx.tcx()) {
151            let boxed_ty = layout.ty.expect_boxed_ty();
152            let boxed_layout = bx.layout_of(boxed_ty);
153            if let Some(mut plan) = Self::emit_retag(bx, boxed_layout, None, is_fn_entry) {
154                // `BoxRaw<T>`
155                let unique = layout.field(bx, 0);
156                {
    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);
157                plan = plan.for_field(FieldIdx::ZERO);
158
159                // `NonNull<T>`
160                let nonnull = unique.field(bx, 0);
161                {
    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);
162                plan = plan.for_field(FieldIdx::ZERO);
163
164                // `*mut T is !null`
165                let pattern = nonnull.field(bx, 0);
166                let ty::Pat(base, _) = pattern.ty.kind() else {
167                    {
    ::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")
168                };
169                {
    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));
170
171                field_plans.insert(FieldIdx::ZERO, plan);
172            }
173        }
174
175        // We always try to retag the second field (the allocator)
176        let field_layout = layout.field(bx, 1);
177        if let Some(plan) = Self::build(bx, field_layout, is_fn_entry) {
178            field_plans.insert(FieldIdx::ONE, plan);
179        }
180
181        (!field_plans.is_empty()).then(|| RetagPlan::Recurse {
182            field_plans: Some(RetagFields::Arbitrary(field_plans)),
183            variant_plans: FxIndexMap::default(),
184        })
185    }
186
187    /// Determines if a pointer needs to be retagged, when it points to
188    /// a type with the given layout. Returns `None` for mutable pointers
189    /// to types that are entirely covered by `UnsafePinned`, for which retags
190    /// are a no-op.
191    fn emit_retag<Bx: BuilderMethods<'a, 'tcx>>(
192        bx: &mut Bx,
193        pointee_layout: TyAndLayout<'tcx>,
194        ptr_kind: Option<Mutability>,
195        is_fn_entry: bool,
196    ) -> Option<RetagPlan<Bx::Value>> {
197        let tcx = bx.tcx();
198        let retag_opts = tcx.sess.opts.unstable_opts.codegen_emit_retag.unwrap_or_default();
199
200        let pointee_ty = pointee_layout.ty;
201
202        let is_mutable = #[allow(non_exhaustive_omitted_patterns)] match ptr_kind {
    Some(Mutability::Mut) | None => true,
    _ => false,
}matches!(ptr_kind, Some(Mutability::Mut) | None);
203        let is_unpin = UnsafePinnedRanges::excludes(bx, pointee_ty);
204        let is_freeze = UnsafeCellRanges::excludes(bx, pointee_ty);
205        let is_box = ptr_kind.is_none();
206
207        // `&mut !Unpin` is not protected
208        let is_protected = is_fn_entry && (!is_mutable || is_unpin);
209
210        let pin_ranges = UnsafePinnedRanges::collect(bx, pointee_layout, retag_opts.no_precise_pin);
211
212        if is_mutable {
213            // Everything is `UnsafePinned` if the collected ranges
214            // cover the entire size of the layout.
215            let all_pinned = #[allow(non_exhaustive_omitted_patterns)] match pin_ranges.as_slice() {
    [(Size::ZERO, size)] if *size == pointee_layout.size => true,
    _ => false,
}matches!(
216                pin_ranges.as_slice(),
217                [(Size::ZERO, size)] if *size == pointee_layout.size,
218            );
219
220            // Otherwise, if we can't find any `UnsafePinned`,
221            // the type is still might be `!Unpin` or `!UnsafeUnpin`,
222            // so we should include the entire range.
223            let implicitly_pinned = pin_ranges.is_empty() && !is_unpin;
224
225            if all_pinned || implicitly_pinned {
226                return None;
227            }
228        }
229
230        if is_mutable && !is_unpin {
231            return None;
232        }
233
234        let im_ranges = UnsafeCellRanges::collect(bx, pointee_layout, retag_opts.no_precise_im);
235        let all_im = #[allow(non_exhaustive_omitted_patterns)] match im_ranges.as_slice() {
    [(Size::ZERO, size)] if *size == pointee_layout.size => true,
    _ => false,
}matches!(
236            im_ranges.as_slice(),
237            [(Size::ZERO, size)] if *size == pointee_layout.size,
238        );
239
240        let pin_layout = Self::alloc_ranges(bx, pin_ranges);
241
242        // If the entire type is covered by `UnsafeCell`, then we can
243        // defer to checking if the type is `Freeze` via `RetagFlags`,
244        // to avoid allocating a global array.
245        let im_layout =
246            if all_im { bx.const_null(bx.type_ptr()) } else { Self::alloc_ranges(bx, im_ranges) };
247
248        let mut flags = RetagFlags::empty();
249        flags.set(RetagFlags::IS_PROTECTED, is_protected);
250        flags.set(RetagFlags::IS_MUTABLE, is_mutable);
251        // Even though we have a list of interior mutable ranges,
252        // we still need a separate flag for `Freeze` types, for when
253        // we retag interior mutable ZSTs.
254        flags.set(RetagFlags::IS_FREEZE, is_freeze);
255        flags.set(RetagFlags::IS_BOX, is_box);
256
257        Some(RetagPlan::EmitRetag(RetagInfo {
258            size: pointee_layout.size,
259            im_layout,
260            pin_layout,
261            flags,
262        }))
263    }
264
265    /// Creates a pointer to a global static allocation containing adjacent pairs of `u64` bytes,
266    /// which indicate the offset and width of a range within the layout of a type. Returns a null
267    /// pointer if the list of ranges is empty.
268    fn alloc_ranges<Bx: BuilderMethods<'a, 'tcx>>(
269        bx: &mut Bx,
270        ranges: Vec<(Size, Size)>,
271    ) -> Bx::Value {
272        let tcx = bx.tcx();
273        let data_layout = &tcx.data_layout;
274
275        if ranges.is_empty() {
276            return bx.const_null(bx.type_ptr());
277        }
278
279        let mut bytes: Vec<u8> = ::alloc::vec::Vec::new()vec![];
280        for (start, end) in ranges.iter() {
281            bytes.extend_from_slice(&start.bytes().to_ne_bytes());
282            bytes.extend_from_slice(&end.bytes().to_ne_bytes());
283        }
284
285        let intptr_ty = data_layout.ptr_sized_integer();
286        let align = intptr_ty.align(data_layout).abi;
287
288        let alloc = Allocation::from_bytes(&bytes, align, Mutability::Not, ());
289        let const_alloc = tcx.mk_const_alloc(alloc);
290
291        // Different IDs are produced, but identical range lists
292        // will resolve to the same allocation.
293        let alloc_id = tcx.reserve_and_set_memory_alloc(const_alloc);
294        let global_alloc = tcx.global_alloc(alloc_id);
295        let global_mem = global_alloc.unwrap_memory();
296
297        bx.cx().static_addr_of(global_mem, None)
298    }
299}
300
301/// A visitor trait for collecting the ranges within a layout that satisfy a given predicate.
302trait PerByteTracking<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> {
303    /// Indicates that we can exclude the range of bytes that contains this type.
304    /// This tells us that [`PerByteTracking::contains`] is false for every
305    /// field or variant without having to recurse any further into the layout of the type.
306    fn excludes(bx: &mut Bx, ty: Ty<'tcx>) -> bool;
307
308    /// Indicates that we should include the range containing this type.
309    fn contains(bx: &mut Bx, ty: Ty<'tcx>) -> bool;
310
311    /// Traverses through the layout of a type to find each range satisfying
312    /// the predicate.
313    ///
314    /// If `imprecise` is true, then the entire size of the type will be included,
315    /// even if only one of its fields satisfies the predicate.
316    fn visit_layout(
317        bx: &mut Bx,
318        offset: Size,
319        ranges: &mut RangeSet<Size>,
320        layout: TyAndLayout<'tcx>,
321        imprecise: bool,
322    ) {
323        if layout.is_zst() {
324            return;
325        }
326
327        if Self::excludes(bx, layout.ty) {
328            return;
329        }
330
331        if imprecise {
332            return ranges.add_range(offset, layout.size);
333        }
334
335        let union_or_primitive =
336            #[allow(non_exhaustive_omitted_patterns)] match layout.fields {
    FieldsShape::Union(..) | FieldsShape::Primitive => true,
    _ => false,
}matches!(layout.fields, FieldsShape::Union(..) | FieldsShape::Primitive);
337        let has_multiple_variants = #[allow(non_exhaustive_omitted_patterns)] match layout.variants {
    Variants::Multiple { .. } => true,
    _ => false,
}matches!(layout.variants, Variants::Multiple { .. });
338
339        if Self::contains(bx, layout.ty) || union_or_primitive || has_multiple_variants {
340            ranges.add_range(offset, layout.size);
341        } else {
342            // We know at this point that we have an array or an arbitrary layout.
343            for ix in layout.fields.index_by_increasing_offset() {
344                // We need to find the offset for this field relative
345                // to the entire type, not just the current aggregate
346                // that we are visiting here.
347                let field_offset = layout.fields.offset(ix);
348                let layout_offset = field_offset + offset;
349
350                let field = layout.field(bx, ix);
351                Self::visit_layout(bx, layout_offset, ranges, field, imprecise);
352            }
353        }
354    }
355    /// Collects the ranges within a type that satisfy the given predicate.
356    fn collect(bx: &mut Bx, layout: TyAndLayout<'tcx>, imprecise: bool) -> Vec<(Size, Size)> {
357        let mut ranges = RangeSet::<Size>::new();
358        Self::visit_layout(bx, Size::ZERO, &mut ranges, layout, imprecise);
359        ranges.0
360    }
361}
362
363/// Collects the ranges within a type that are covered by `UnsafeCell`.
364struct UnsafeCellRanges;
365
366impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> PerByteTracking<'a, 'tcx, Bx> for UnsafeCellRanges {
367    fn excludes(bx: &mut Bx, ty: Ty<'tcx>) -> bool {
368        ty.is_freeze(bx.tcx(), bx.cx().typing_env())
369    }
370
371    fn contains(bx: &mut Bx, ty: Ty<'tcx>) -> bool {
372        let tcx = bx.tcx();
373        match ty.kind() {
374            ty::Adt(adt, _) => Some(adt.did()) == tcx.lang_items().unsafe_cell_type(),
375            _ => false,
376        }
377    }
378}
379
380/// Collects the ranges within a type that are covered by `UnsafePinned`.
381struct UnsafePinnedRanges;
382
383impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> PerByteTracking<'a, 'tcx, Bx> for UnsafePinnedRanges {
384    fn excludes(bx: &mut Bx, ty: Ty<'tcx>) -> bool {
385        ty.is_unpin(bx.tcx(), bx.typing_env()) && ty.is_unsafe_unpin(bx.tcx(), bx.typing_env())
386    }
387
388    fn contains(bx: &mut Bx, ty: Ty<'tcx>) -> bool {
389        let tcx = bx.tcx();
390        match ty.kind() {
391            ty::Adt(adt, _) => Some(adt.did()) == tcx.lang_items().unsafe_pinned_type(),
392            _ => false,
393        }
394    }
395}
396
397impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
398    /// Retags the pointers within an [`OperandRef`].
399    pub(crate) fn codegen_retag_operand(
400        &mut self,
401        bx: &mut Bx,
402        operand: OperandRef<'tcx, Bx::Value>,
403        is_fn_entry: bool,
404    ) -> OperandRef<'tcx, Bx::Value> {
405        if let OperandValue::Ref(place_ref) = operand.val {
406            let place_ref = place_ref.with_type(operand.layout);
407            self.codegen_retag_place(bx, place_ref, is_fn_entry);
408        } else if let Some(plan) = RetagPlan::<Bx::Value>::build(bx, operand.layout, is_fn_entry) {
409            let mut builder = OperandRefBuilder::from_existing(operand);
410            self.retag_operand(bx, &plan, operand, &mut builder, Size::ZERO);
411            return builder.build(bx.cx());
412        }
413        operand
414    }
415
416    /// Retags the pointers within a [`PlaceRef`].
417    pub(crate) fn codegen_retag_place(
418        &mut self,
419        bx: &mut Bx,
420        place_ref: PlaceRef<'tcx, Bx::Value>,
421        is_fn_entry: bool,
422    ) {
423        if let Some(plan) = RetagPlan::<Bx::Value>::build(bx, place_ref.layout, is_fn_entry) {
424            self.retag_place(bx, &plan, place_ref);
425        }
426    }
427
428    fn retag_operand(
429        &mut self,
430        bx: &mut Bx,
431        plan: &RetagPlan<Bx::Value>,
432        curr_operand: OperandRef<'tcx, Bx::Value>,
433        builder: &mut OperandRefBuilder<'tcx, Bx::Value>,
434        offset: Size,
435    ) {
436        match plan {
437            RetagPlan::EmitRetag(info) => {
438                let (pointer, _) = curr_operand.val.pointer_parts();
439                let retagged_pointer = bx.retag_reg(pointer, info);
440                builder.update_imm(offset, retagged_pointer);
441            }
442            RetagPlan::Recurse { field_plans, variant_plans } => {
443                let layout = curr_operand.layout;
444                if let Some(plans) = field_plans {
445                    let mut retag_field = |ix: FieldIdx, plan: &RetagPlan<Bx::Value>| {
446                        let inner_offset = layout.fields.offset(ix.as_usize());
447                        let field_offset = offset + inner_offset;
448                        let field_layout = curr_operand.layout.field(bx, ix.index());
449                        if curr_operand.layout.is_ssa_standalone()
450                            && !field_layout.is_ssa_standalone()
451                        {
452                            // FIXME: Nothing should be looking at the *array* inside a `repr(simd)` type,
453                            // as that array doesn't really exist. Perhaps this should be a `bug!`,
454                            // with simd types handled before getting here?
455                        } else {
456                            let field_operand = curr_operand.extract_field(self, bx, ix.as_usize());
457                            self.retag_operand(bx, plan, field_operand, builder, field_offset);
458                        }
459                    };
460
461                    match plans {
462                        RetagFields::Arbitrary(field_plans) => {
463                            for (ix, plan) in field_plans {
464                                retag_field(*ix, plan);
465                            }
466                        }
467                        RetagFields::Array(plan) => {
468                            let FieldsShape::Array { count, .. } = layout.fields else {
469                                ::rustc_span::macros::bug_impl(None,
    format_args!("Expected `FieldsShape::Array` but found {0:?}",
        layout.fields), Location::caller());bug!("Expected `FieldsShape::Array` but found {:?}", layout.fields);
470                            };
471                            for idx in 0..count {
472                                let idx = FieldIdx::from_usize(idx as usize);
473                                retag_field(idx, plan)
474                            }
475                        }
476                    }
477                }
478
479                if !variant_plans.is_empty() {
480                    let discr_ty = layout.ty.discriminant_ty(bx.tcx());
481                    let discr_val = curr_operand.codegen_get_discr(self, bx, discr_ty);
482
483                    if let Some(val) = bx.const_to_opt_u128(discr_val, false) {
484                        let ix = VariantIdx::from_usize(val as usize);
485                        if let Some(plan) = variant_plans.get(&ix) {
486                            let mut variant_op = curr_operand;
487                            variant_op.layout = curr_operand.layout.for_variant(bx, ix);
488
489                            self.retag_operand(bx, plan, variant_op, builder, offset);
490                        }
491                    } else {
492                        // We create a temporary place to store the operand, because its
493                        // value will differ depending on the variant that we have.
494                        let scratch = PlaceRef::alloca(bx, curr_operand.layout);
495                        scratch.storage_live(bx);
496                        curr_operand.store_with_annotation(bx, scratch);
497
498                        // We retag the contents of the place
499                        self.retag_variants(bx, scratch, discr_val, variant_plans);
500
501                        // Afterward, we load the now-updated operand and end the lifetime of the place.
502                        let updated_op = bx.load_operand(scratch);
503                        scratch.storage_dead(bx);
504
505                        match updated_op.val {
506                            OperandValue::ZeroSized | OperandValue::Ref(_) => {}
507                            OperandValue::Immediate(imm) => builder.update_imm(offset, imm),
508                            OperandValue::Pair(fst, snd) => {
509                                builder.update_imm(offset, fst);
510                                builder.update_imm(offset + Size::from_bytes(1), snd)
511                            }
512                        }
513                    }
514                }
515            }
516        }
517    }
518
519    fn retag_place(
520        &mut self,
521        bx: &mut Bx,
522        plan: &RetagPlan<Bx::Value>,
523        place: PlaceRef<'tcx, Bx::Value>,
524    ) {
525        match plan {
526            RetagPlan::EmitRetag(info) => {
527                bx.retag_mem(place.val.llval, info);
528            }
529            RetagPlan::Recurse { field_plans, variant_plans } => {
530                if let Some(field_plans) = field_plans {
531                    let mut retag_field = |ix: FieldIdx, plan: &RetagPlan<Bx::Value>| {
532                        let field_place = place.project_field(bx, ix.as_usize());
533                        self.retag_place(bx, plan, field_place);
534                    };
535                    match field_plans {
536                        RetagFields::Arbitrary(fields) => {
537                            for (ix, plan) in fields {
538                                retag_field(*ix, plan);
539                            }
540                        }
541                        RetagFields::Array(plan) => {
542                            let FieldsShape::Array { count, .. } = place.layout.fields else {
543                                ::rustc_span::macros::bug_impl(None,
    format_args!("Expected `FieldsShape::Array` but found {0:?}",
        place.layout.fields), Location::caller());bug!(
544                                    "Expected `FieldsShape::Array` but found {:?}",
545                                    place.layout.fields
546                                );
547                            };
548                            // When an array of N references needs to be retagged (e.g. `[&T; N]`),
549                            // it is more efficient to emit a loop for large values of N than
550                            // to emit N retags in succession.
551                            within_loop(bx, count, |bx: &mut Bx, ix: Bx::Value| {
552                                let field_place = place.project_index(bx, ix);
553                                self.retag_place(bx, plan, field_place);
554                            });
555                        }
556                    }
557                }
558                if !variant_plans.is_empty() {
559                    let operand = bx.load_operand(place);
560                    let discr_ty = place.layout.ty.discriminant_ty(bx.tcx());
561                    let discr_val = operand.codegen_get_discr(self, bx, discr_ty);
562                    self.retag_variants(bx, place, discr_val, variant_plans);
563                }
564            }
565        }
566    }
567
568    /// Retags each variant of a [`PlaceRef`] with the given discriminant.
569    fn retag_variants(
570        &mut self,
571        bx: &mut Bx,
572        place: PlaceRef<'tcx, Bx::Value>,
573        discr: Bx::Value,
574        variant_plans: &FxIndexMap<VariantIdx, RetagPlan<Bx::Value>>,
575    ) {
576        let layout = place.layout;
577
578        let root_block = bx.llbb();
579        let mut variant_blocks = Vec::with_capacity(variant_plans.len());
580        let join_block = bx.append_sibling_block("retag_join");
581
582        for (ix, plan) in variant_plans {
583            let variant_discr = layout.ty.discriminant_for_variant(bx.tcx(), *ix);
584            let variant_discr_val = variant_discr.expect("Invalid variant index.").val;
585
586            let variant_block = bx.append_sibling_block("retag_variant");
587            bx.switch_to_block(variant_block);
588
589            let variant_place = place.project_downcast(bx, *ix);
590            self.retag_place(bx, plan, variant_place);
591
592            variant_blocks.push((variant_discr_val, variant_block));
593            bx.br(join_block);
594        }
595
596        bx.switch_to_block(root_block);
597        bx.switch(discr, join_block, variant_blocks.into_iter());
598        bx.switch_to_block(join_block);
599    }
600}
601
602/// Creates a simple loop, iterating the specified number of times.
603/// The dynamic value of the loop counter is passed as an argument
604/// to the closure.
605fn within_loop<'a, 'tcx, F, Bx: BuilderMethods<'a, 'tcx>>(bx: &mut Bx, max_iter: u64, mut op: F)
606where
607    F: FnMut(&mut Bx, Bx::Value),
608{
609    // Avoid emitting a no-op loop
610    if max_iter == 0 {
611        return;
612    }
613
614    // Initialize the loop counter to zero.
615    let counter_layout = bx.layout_of(bx.tcx().types.usize);
616    let counter_alloca = PlaceRef::alloca(bx, counter_layout);
617    counter_alloca.storage_live(bx);
618    bx.store_to_place(bx.const_usize(0), counter_alloca.val);
619
620    // Jump to a new block for the body of the loop.
621    let loop_body = bx.append_sibling_block("retag_loop");
622    bx.br(loop_body);
623
624    bx.switch_to_block(loop_body);
625
626    // Load the value of the counter, and pass it to the closure.
627    let curr_count = bx.load_from_place(bx.type_isize(), counter_alloca.val);
628    op(bx, curr_count);
629
630    let next_count = bx.unchecked_uadd(curr_count, bx.const_usize(1));
631    bx.store_to_place(next_count, counter_alloca.val);
632
633    // If the next count is equal to the maximum number of
634    // iterations, then exit the loop.
635    let max_count = bx.const_usize(max_iter);
636    let loop_exit = bx.append_sibling_block("retag_loop_exit");
637    let should_exit_loop = bx.icmp(IntPredicate::IntEQ, next_count, max_count);
638    bx.cond_br(should_exit_loop, loop_exit, loop_body);
639
640    bx.switch_to_block(loop_exit);
641    counter_alloca.storage_dead(bx);
642}