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