rustc_codegen_ssa/mir/
debuginfo.rs

1use std::collections::hash_map::Entry;
2use std::marker::PhantomData;
3use std::ops::Range;
4
5use rustc_abi::{BackendRepr, FieldIdx, FieldsShape, Size, VariantIdx};
6use rustc_data_structures::fx::FxHashMap;
7use rustc_index::IndexVec;
8use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
9use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
10use rustc_middle::ty::{Instance, Ty};
11use rustc_middle::{bug, mir, ty};
12use rustc_session::config::DebugInfo;
13use rustc_span::{BytePos, Span, Symbol, hygiene, kw};
14
15use super::operand::{OperandRef, OperandValue};
16use super::place::{PlaceRef, PlaceValue};
17use super::{FunctionCx, LocalRef, PerLocalVarDebugInfoIndexVec};
18use crate::traits::*;
19
20pub struct FunctionDebugContext<'tcx, S, L> {
21    /// Maps from source code to the corresponding debug info scope.
22    pub scopes: IndexVec<mir::SourceScope, DebugScope<S, L>>,
23
24    /// Maps from an inlined function to its debug info declaration.
25    pub inlined_function_scopes: FxHashMap<Instance<'tcx>, S>,
26}
27
28#[derive(Copy, Clone)]
29pub enum VariableKind {
30    ArgumentVariable(usize /*index*/),
31    LocalVariable,
32}
33
34/// Like `mir::VarDebugInfo`, but within a `mir::Local`.
35#[derive(Clone)]
36pub struct PerLocalVarDebugInfo<'tcx, D> {
37    pub name: Symbol,
38    pub source_info: mir::SourceInfo,
39
40    /// `DIVariable` returned by `create_dbg_var`.
41    pub dbg_var: Option<D>,
42
43    /// Byte range in the `dbg_var` covered by this fragment,
44    /// if this is a fragment of a composite `VarDebugInfo`.
45    pub fragment: Option<Range<Size>>,
46
47    /// `.place.projection` from `mir::VarDebugInfo`.
48    pub projection: &'tcx ty::List<mir::PlaceElem<'tcx>>,
49}
50
51/// Information needed to emit a constant.
52pub struct ConstDebugInfo<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> {
53    pub name: String,
54    pub source_info: mir::SourceInfo,
55    pub operand: OperandRef<'tcx, Bx::Value>,
56    pub dbg_var: Bx::DIVariable,
57    pub dbg_loc: Bx::DILocation,
58    pub fragment: Option<Range<Size>>,
59    pub _phantom: PhantomData<&'a ()>,
60}
61
62#[derive(Clone, Copy, Debug)]
63pub struct DebugScope<S, L> {
64    pub dbg_scope: S,
65
66    /// Call site location, if this scope was inlined from another function.
67    pub inlined_at: Option<L>,
68
69    // Start and end offsets of the file to which this DIScope belongs.
70    // These are used to quickly determine whether some span refers to the same file.
71    pub file_start_pos: BytePos,
72    pub file_end_pos: BytePos,
73}
74
75impl<'tcx, S: Copy, L: Copy> DebugScope<S, L> {
76    /// DILocations inherit source file name from the parent DIScope. Due to macro expansions
77    /// it may so happen that the current span belongs to a different file than the DIScope
78    /// corresponding to span's containing source scope. If so, we need to create a DIScope
79    /// "extension" into that file.
80    pub fn adjust_dbg_scope_for_span<Cx: CodegenMethods<'tcx, DIScope = S, DILocation = L>>(
81        &self,
82        cx: &Cx,
83        span: Span,
84    ) -> S {
85        let pos = span.lo();
86        if pos < self.file_start_pos || pos >= self.file_end_pos {
87            let sm = cx.sess().source_map();
88            cx.extend_scope_to_file(self.dbg_scope, &sm.lookup_char_pos(pos).file)
89        } else {
90            self.dbg_scope
91        }
92    }
93}
94
95trait DebugInfoOffsetLocation<'tcx, Bx> {
96    fn deref(&self, bx: &mut Bx) -> Self;
97    fn layout(&self) -> TyAndLayout<'tcx>;
98    fn project_field(&self, bx: &mut Bx, field: FieldIdx) -> Self;
99    fn project_constant_index(&self, bx: &mut Bx, offset: u64) -> Self;
100    fn downcast(&self, bx: &mut Bx, variant: VariantIdx) -> Self;
101}
102
103impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> DebugInfoOffsetLocation<'tcx, Bx>
104    for PlaceRef<'tcx, Bx::Value>
105{
106    fn deref(&self, bx: &mut Bx) -> Self {
107        bx.load_operand(*self).deref(bx.cx())
108    }
109
110    fn layout(&self) -> TyAndLayout<'tcx> {
111        self.layout
112    }
113
114    fn project_field(&self, bx: &mut Bx, field: FieldIdx) -> Self {
115        PlaceRef::project_field(*self, bx, field.index())
116    }
117
118    fn project_constant_index(&self, bx: &mut Bx, offset: u64) -> Self {
119        let lloffset = bx.cx().const_usize(offset);
120        self.project_index(bx, lloffset)
121    }
122
123    fn downcast(&self, bx: &mut Bx, variant: VariantIdx) -> Self {
124        self.project_downcast(bx, variant)
125    }
126}
127
128impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> DebugInfoOffsetLocation<'tcx, Bx>
129    for TyAndLayout<'tcx>
130{
131    fn deref(&self, bx: &mut Bx) -> Self {
132        bx.cx().layout_of(
133            self.ty.builtin_deref(true).unwrap_or_else(|| bug!("cannot deref `{}`", self.ty)),
134        )
135    }
136
137    fn layout(&self) -> TyAndLayout<'tcx> {
138        *self
139    }
140
141    fn project_field(&self, bx: &mut Bx, field: FieldIdx) -> Self {
142        self.field(bx.cx(), field.index())
143    }
144
145    fn project_constant_index(&self, bx: &mut Bx, index: u64) -> Self {
146        self.field(bx.cx(), index as usize)
147    }
148
149    fn downcast(&self, bx: &mut Bx, variant: VariantIdx) -> Self {
150        self.for_variant(bx.cx(), variant)
151    }
152}
153
154struct DebugInfoOffset<T> {
155    /// Offset from the `base` used to calculate the debuginfo offset.
156    direct_offset: Size,
157    /// Each offset in this vector indicates one level of indirection from the base or previous
158    /// indirect offset plus a dereference.
159    indirect_offsets: Vec<Size>,
160    /// The final location debuginfo should point to.
161    result: T,
162}
163
164fn calculate_debuginfo_offset<
165    'a,
166    'tcx,
167    Bx: BuilderMethods<'a, 'tcx>,
168    L: DebugInfoOffsetLocation<'tcx, Bx>,
169>(
170    bx: &mut Bx,
171    projection: &[mir::PlaceElem<'tcx>],
172    base: L,
173) -> DebugInfoOffset<L> {
174    let mut direct_offset = Size::ZERO;
175    // FIXME(eddyb) use smallvec here.
176    let mut indirect_offsets = vec![];
177    let mut place = base;
178
179    for elem in projection {
180        match *elem {
181            mir::ProjectionElem::Deref => {
182                indirect_offsets.push(Size::ZERO);
183                place = place.deref(bx);
184            }
185            mir::ProjectionElem::Field(field, _) => {
186                let offset = indirect_offsets.last_mut().unwrap_or(&mut direct_offset);
187                *offset += place.layout().fields.offset(field.index());
188                place = place.project_field(bx, field);
189            }
190            mir::ProjectionElem::Downcast(_, variant) => {
191                place = place.downcast(bx, variant);
192            }
193            mir::ProjectionElem::ConstantIndex {
194                offset: index,
195                min_length: _,
196                from_end: false,
197            } => {
198                let offset = indirect_offsets.last_mut().unwrap_or(&mut direct_offset);
199                let FieldsShape::Array { stride, count: _ } = place.layout().fields else {
200                    bug!("ConstantIndex on non-array type {:?}", place.layout())
201                };
202                *offset += stride * index;
203                place = place.project_constant_index(bx, index);
204            }
205            _ => {
206                // Sanity check for `can_use_in_debuginfo`.
207                assert!(!elem.can_use_in_debuginfo());
208                bug!("unsupported var debuginfo projection `{:?}`", projection)
209            }
210        }
211    }
212
213    DebugInfoOffset { direct_offset, indirect_offsets, result: place }
214}
215
216impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
217    pub fn set_debug_loc(&self, bx: &mut Bx, source_info: mir::SourceInfo) {
218        bx.set_span(source_info.span);
219        if let Some(dbg_loc) = self.dbg_loc(source_info) {
220            bx.set_dbg_loc(dbg_loc);
221        }
222    }
223
224    fn dbg_loc(&self, source_info: mir::SourceInfo) -> Option<Bx::DILocation> {
225        let (dbg_scope, inlined_at, span) = self.adjusted_span_and_dbg_scope(source_info)?;
226        Some(self.cx.dbg_loc(dbg_scope, inlined_at, span))
227    }
228
229    fn adjusted_span_and_dbg_scope(
230        &self,
231        source_info: mir::SourceInfo,
232    ) -> Option<(Bx::DIScope, Option<Bx::DILocation>, Span)> {
233        let scope = &self.debug_context.as_ref()?.scopes[source_info.scope];
234        let span = hygiene::walk_chain_collapsed(source_info.span, self.mir.span);
235        Some((scope.adjust_dbg_scope_for_span(self.cx, span), scope.inlined_at, span))
236    }
237
238    fn spill_operand_to_stack(
239        operand: OperandRef<'tcx, Bx::Value>,
240        name: Option<String>,
241        bx: &mut Bx,
242    ) -> PlaceRef<'tcx, Bx::Value> {
243        // "Spill" the value onto the stack, for debuginfo,
244        // without forcing non-debuginfo uses of the local
245        // to also load from the stack every single time.
246        // FIXME(#68817) use `llvm.dbg.value` instead,
247        // at least for the cases which LLVM handles correctly.
248        let spill_slot = PlaceRef::alloca(bx, operand.layout);
249        if let Some(name) = name {
250            bx.set_var_name(spill_slot.val.llval, &(name + ".dbg.spill"));
251        }
252        operand.val.store(bx, spill_slot);
253        spill_slot
254    }
255
256    /// Apply debuginfo and/or name, after creating the `alloca` for a local,
257    /// or initializing the local with an operand (whichever applies).
258    pub(crate) fn debug_introduce_local(&self, bx: &mut Bx, local: mir::Local) {
259        let full_debug_info = bx.sess().opts.debuginfo == DebugInfo::Full;
260
261        let vars = match &self.per_local_var_debug_info {
262            Some(per_local) => &per_local[local],
263            None => return,
264        };
265        let whole_local_var = vars.iter().find(|var| var.projection.is_empty()).cloned();
266        let has_proj = || vars.iter().any(|var| !var.projection.is_empty());
267
268        let fallback_var = if self.mir.local_kind(local) == mir::LocalKind::Arg {
269            let arg_index = local.index() - 1;
270
271            // Add debuginfo even to unnamed arguments.
272            // FIXME(eddyb) is this really needed?
273            if arg_index == 0 && has_proj() {
274                // Hide closure environments from debuginfo.
275                // FIXME(eddyb) shouldn't `ArgumentVariable` indices
276                // be offset to account for the hidden environment?
277                None
278            } else if whole_local_var.is_some() {
279                // No need to make up anything, there is a `mir::VarDebugInfo`
280                // covering the whole local.
281                // FIXME(eddyb) take `whole_local_var.source_info.scope` into
282                // account, just in case it doesn't use `ArgumentVariable`
283                // (after #67586 gets fixed).
284                None
285            } else {
286                let name = kw::Empty;
287                let decl = &self.mir.local_decls[local];
288                let dbg_var = if full_debug_info {
289                    self.adjusted_span_and_dbg_scope(decl.source_info).map(
290                        |(dbg_scope, _, span)| {
291                            // FIXME(eddyb) is this `+ 1` needed at all?
292                            let kind = VariableKind::ArgumentVariable(arg_index + 1);
293
294                            let arg_ty = self.monomorphize(decl.ty);
295
296                            self.cx.create_dbg_var(name, arg_ty, dbg_scope, kind, span)
297                        },
298                    )
299                } else {
300                    None
301                };
302
303                Some(PerLocalVarDebugInfo {
304                    name,
305                    source_info: decl.source_info,
306                    dbg_var,
307                    fragment: None,
308                    projection: ty::List::empty(),
309                })
310            }
311        } else {
312            None
313        };
314
315        let local_ref = &self.locals[local];
316
317        let name = if bx.sess().fewer_names() {
318            None
319        } else {
320            Some(match whole_local_var.or(fallback_var.clone()) {
321                Some(var) if var.name != kw::Empty => var.name.to_string(),
322                _ => format!("{local:?}"),
323            })
324        };
325
326        if let Some(name) = &name {
327            match local_ref {
328                LocalRef::Place(place) | LocalRef::UnsizedPlace(place) => {
329                    bx.set_var_name(place.val.llval, name);
330                }
331                LocalRef::Operand(operand) => match operand.val {
332                    OperandValue::Ref(PlaceValue { llval: x, .. }) | OperandValue::Immediate(x) => {
333                        bx.set_var_name(x, name);
334                    }
335                    OperandValue::Pair(a, b) => {
336                        // FIXME(eddyb) these are scalar components,
337                        // maybe extract the high-level fields?
338                        bx.set_var_name(a, &(name.clone() + ".0"));
339                        bx.set_var_name(b, &(name.clone() + ".1"));
340                    }
341                    OperandValue::ZeroSized => {
342                        // These never have a value to talk about
343                    }
344                },
345                LocalRef::PendingOperand => {}
346            }
347        }
348
349        if !full_debug_info || vars.is_empty() && fallback_var.is_none() {
350            return;
351        }
352
353        let base = match local_ref {
354            LocalRef::PendingOperand => return,
355
356            LocalRef::Operand(operand) => {
357                // Don't spill operands onto the stack in naked functions.
358                // See: https://github.com/rust-lang/rust/issues/42779
359                let attrs = bx.tcx().codegen_fn_attrs(self.instance.def_id());
360                if attrs.flags.contains(CodegenFnAttrFlags::NAKED) {
361                    return;
362                }
363
364                Self::spill_operand_to_stack(*operand, name, bx)
365            }
366
367            LocalRef::Place(place) => *place,
368
369            // FIXME(eddyb) add debuginfo for unsized places too.
370            LocalRef::UnsizedPlace(_) => return,
371        };
372
373        let vars = vars.iter().cloned().chain(fallback_var);
374
375        for var in vars {
376            self.debug_introduce_local_as_var(bx, local, base, var);
377        }
378    }
379
380    fn debug_introduce_local_as_var(
381        &self,
382        bx: &mut Bx,
383        local: mir::Local,
384        base: PlaceRef<'tcx, Bx::Value>,
385        var: PerLocalVarDebugInfo<'tcx, Bx::DIVariable>,
386    ) {
387        let Some(dbg_var) = var.dbg_var else { return };
388        let Some(dbg_loc) = self.dbg_loc(var.source_info) else { return };
389
390        let DebugInfoOffset { direct_offset, indirect_offsets, result: _ } =
391            calculate_debuginfo_offset(bx, var.projection, base.layout);
392
393        // When targeting MSVC, create extra allocas for arguments instead of pointing multiple
394        // dbg_var_addr() calls into the same alloca with offsets. MSVC uses CodeView records
395        // not DWARF and LLVM doesn't support translating the resulting
396        // [DW_OP_deref, DW_OP_plus_uconst, offset, DW_OP_deref] debug info to CodeView.
397        // Creating extra allocas on the stack makes the resulting debug info simple enough
398        // that LLVM can generate correct CodeView records and thus the values appear in the
399        // debugger. (#83709)
400        let should_create_individual_allocas = bx.cx().sess().target.is_like_msvc
401            && self.mir.local_kind(local) == mir::LocalKind::Arg
402            // LLVM can handle simple things but anything more complex than just a direct
403            // offset or one indirect offset of 0 is too complex for it to generate CV records
404            // correctly.
405            && (direct_offset != Size::ZERO || !matches!(&indirect_offsets[..], [Size::ZERO] | []));
406
407        if should_create_individual_allocas {
408            let DebugInfoOffset { direct_offset: _, indirect_offsets: _, result: place } =
409                calculate_debuginfo_offset(bx, var.projection, base);
410
411            // Create a variable which will be a pointer to the actual value
412            let ptr_ty = Ty::new_mut_ptr(bx.tcx(), place.layout.ty);
413            let ptr_layout = bx.layout_of(ptr_ty);
414            let alloca = PlaceRef::alloca(bx, ptr_layout);
415            bx.set_var_name(alloca.val.llval, &(var.name.to_string() + ".dbg.spill"));
416
417            // Write the pointer to the variable
418            bx.store_to_place(place.val.llval, alloca.val);
419
420            // Point the debug info to `*alloca` for the current variable
421            bx.dbg_var_addr(
422                dbg_var,
423                dbg_loc,
424                alloca.val.llval,
425                Size::ZERO,
426                &[Size::ZERO],
427                var.fragment,
428            );
429        } else {
430            bx.dbg_var_addr(
431                dbg_var,
432                dbg_loc,
433                base.val.llval,
434                direct_offset,
435                &indirect_offsets,
436                var.fragment,
437            );
438        }
439    }
440
441    pub(crate) fn debug_introduce_locals(
442        &self,
443        bx: &mut Bx,
444        consts: Vec<ConstDebugInfo<'a, 'tcx, Bx>>,
445    ) {
446        if bx.sess().opts.debuginfo == DebugInfo::Full || !bx.sess().fewer_names() {
447            for local in self.locals.indices() {
448                self.debug_introduce_local(bx, local);
449            }
450
451            for ConstDebugInfo { name, source_info, operand, dbg_var, dbg_loc, fragment, .. } in
452                consts.into_iter()
453            {
454                self.set_debug_loc(bx, source_info);
455                let base = FunctionCx::spill_operand_to_stack(operand, Some(name), bx);
456                bx.clear_dbg_loc();
457
458                bx.dbg_var_addr(dbg_var, dbg_loc, base.val.llval, Size::ZERO, &[], fragment);
459            }
460        }
461    }
462
463    /// Partition all `VarDebugInfo` in `self.mir`, by their base `Local`.
464    pub(crate) fn compute_per_local_var_debug_info(
465        &self,
466        bx: &mut Bx,
467    ) -> Option<(
468        PerLocalVarDebugInfoIndexVec<'tcx, Bx::DIVariable>,
469        Vec<ConstDebugInfo<'a, 'tcx, Bx>>,
470    )> {
471        let full_debug_info = self.cx.sess().opts.debuginfo == DebugInfo::Full;
472
473        let target_is_msvc = self.cx.sess().target.is_like_msvc;
474
475        if !full_debug_info && self.cx.sess().fewer_names() {
476            return None;
477        }
478
479        let mut per_local = IndexVec::from_elem(vec![], &self.mir.local_decls);
480        let mut constants = vec![];
481        let mut params_seen: FxHashMap<_, Bx::DIVariable> = Default::default();
482        for var in &self.mir.var_debug_info {
483            let dbg_scope_and_span = if full_debug_info {
484                self.adjusted_span_and_dbg_scope(var.source_info)
485            } else {
486                None
487            };
488
489            let var_ty = if let Some(ref fragment) = var.composite {
490                self.monomorphize(fragment.ty)
491            } else {
492                match var.value {
493                    mir::VarDebugInfoContents::Place(place) => {
494                        self.monomorphized_place_ty(place.as_ref())
495                    }
496                    mir::VarDebugInfoContents::Const(c) => self.monomorphize(c.ty()),
497                }
498            };
499
500            let dbg_var = dbg_scope_and_span.map(|(dbg_scope, _, span)| {
501                let var_kind = if let Some(arg_index) = var.argument_index
502                    && var.composite.is_none()
503                    && let mir::VarDebugInfoContents::Place(place) = var.value
504                    && place.projection.is_empty()
505                {
506                    let arg_index = arg_index as usize;
507                    if target_is_msvc {
508                        // ScalarPair parameters are spilled to the stack so they need to
509                        // be marked as a `LocalVariable` for MSVC debuggers to visualize
510                        // their data correctly. (See #81894 & #88625)
511                        let var_ty_layout = self.cx.layout_of(var_ty);
512                        if let BackendRepr::ScalarPair(_, _) = var_ty_layout.backend_repr {
513                            VariableKind::LocalVariable
514                        } else {
515                            VariableKind::ArgumentVariable(arg_index)
516                        }
517                    } else {
518                        // FIXME(eddyb) shouldn't `ArgumentVariable` indices be
519                        // offset in closures to account for the hidden environment?
520                        VariableKind::ArgumentVariable(arg_index)
521                    }
522                } else {
523                    VariableKind::LocalVariable
524                };
525
526                if let VariableKind::ArgumentVariable(arg_index) = var_kind {
527                    match params_seen.entry((dbg_scope, arg_index)) {
528                        Entry::Occupied(o) => o.get().clone(),
529                        Entry::Vacant(v) => v
530                            .insert(
531                                self.cx.create_dbg_var(var.name, var_ty, dbg_scope, var_kind, span),
532                            )
533                            .clone(),
534                    }
535                } else {
536                    self.cx.create_dbg_var(var.name, var_ty, dbg_scope, var_kind, span)
537                }
538            });
539
540            let fragment = if let Some(ref fragment) = var.composite {
541                let var_layout = self.cx.layout_of(var_ty);
542
543                let DebugInfoOffset { direct_offset, indirect_offsets, result: fragment_layout } =
544                    calculate_debuginfo_offset(bx, &fragment.projection, var_layout);
545                assert!(indirect_offsets.is_empty());
546
547                if fragment_layout.size == Size::ZERO {
548                    // Fragment is a ZST, so does not represent anything. Avoid generating anything
549                    // as this may conflict with a fragment that covers the entire variable.
550                    continue;
551                } else if fragment_layout.size == var_layout.size {
552                    // Fragment covers entire variable, so as far as
553                    // DWARF is concerned, it's not really a fragment.
554                    None
555                } else {
556                    Some(direct_offset..direct_offset + fragment_layout.size)
557                }
558            } else {
559                None
560            };
561
562            match var.value {
563                mir::VarDebugInfoContents::Place(place) => {
564                    per_local[place.local].push(PerLocalVarDebugInfo {
565                        name: var.name,
566                        source_info: var.source_info,
567                        dbg_var,
568                        fragment,
569                        projection: place.projection,
570                    });
571                }
572                mir::VarDebugInfoContents::Const(c) => {
573                    if let Some(dbg_var) = dbg_var {
574                        let Some(dbg_loc) = self.dbg_loc(var.source_info) else { continue };
575
576                        let operand = self.eval_mir_constant_to_operand(bx, &c);
577                        constants.push(ConstDebugInfo {
578                            name: var.name.to_string(),
579                            source_info: var.source_info,
580                            operand,
581                            dbg_var,
582                            dbg_loc,
583                            fragment,
584                            _phantom: PhantomData,
585                        });
586                    }
587                }
588            }
589        }
590        Some((per_local, constants))
591    }
592}