rustc_codegen_llvm/debuginfo/
create_scope_map.rs

1use std::collections::hash_map::Entry;
2
3use rustc_codegen_ssa::mir::debuginfo::{DebugScope, FunctionDebugContext};
4use rustc_codegen_ssa::traits::*;
5use rustc_data_structures::fx::FxHashMap;
6use rustc_index::bit_set::DenseBitSet;
7use rustc_middle::mir::{Body, SourceScope};
8use rustc_middle::ty::layout::{FnAbiOf, HasTypingEnv};
9use rustc_middle::ty::{self, Instance};
10use rustc_session::config::DebugInfo;
11use rustc_span::{BytePos, DUMMY_SP, hygiene};
12
13use super::metadata::file_metadata;
14use super::utils::DIB;
15use crate::common::CodegenCx;
16use crate::llvm;
17use crate::llvm::debuginfo::{DILocation, DIScope};
18
19/// Produces DIScope DIEs for each MIR Scope which has variables defined in it.
20// FIXME(eddyb) almost all of this should be in `rustc_codegen_ssa::mir::debuginfo`.
21pub(crate) fn compute_mir_scopes<'ll, 'tcx>(
22    cx: &CodegenCx<'ll, 'tcx>,
23    instance: Instance<'tcx>,
24    mir: &Body<'tcx>,
25    debug_context: &mut FunctionDebugContext<'tcx, &'ll DIScope, &'ll DILocation>,
26) {
27    // Find all scopes with variables defined in them.
28    let variables = if cx.sess().opts.debuginfo == DebugInfo::Full {
29        let mut vars = DenseBitSet::new_empty(mir.source_scopes.len());
30        // FIXME(eddyb) take into account that arguments always have debuginfo,
31        // irrespective of their name (assuming full debuginfo is enabled).
32        // NOTE(eddyb) actually, on second thought, those are always in the
33        // function scope, which always exists.
34        for var_debug_info in &mir.var_debug_info {
35            vars.insert(var_debug_info.source_info.scope);
36        }
37        Some(vars)
38    } else {
39        // Nothing to emit, of course.
40        None
41    };
42    let mut instantiated = DenseBitSet::new_empty(mir.source_scopes.len());
43    let mut discriminators = FxHashMap::default();
44    // Instantiate all scopes.
45    for scope in mir.source_scopes.indices() {
46        make_mir_scope(
47            cx,
48            instance,
49            mir,
50            &variables,
51            debug_context,
52            &mut instantiated,
53            &mut discriminators,
54            scope,
55        );
56    }
57    assert!(instantiated.count() == mir.source_scopes.len());
58}
59
60fn make_mir_scope<'ll, 'tcx>(
61    cx: &CodegenCx<'ll, 'tcx>,
62    instance: Instance<'tcx>,
63    mir: &Body<'tcx>,
64    variables: &Option<DenseBitSet<SourceScope>>,
65    debug_context: &mut FunctionDebugContext<'tcx, &'ll DIScope, &'ll DILocation>,
66    instantiated: &mut DenseBitSet<SourceScope>,
67    discriminators: &mut FxHashMap<BytePos, u32>,
68    scope: SourceScope,
69) {
70    if instantiated.contains(scope) {
71        return;
72    }
73
74    let scope_data = &mir.source_scopes[scope];
75    let parent_scope = if let Some(parent) = scope_data.parent_scope {
76        make_mir_scope(
77            cx,
78            instance,
79            mir,
80            variables,
81            debug_context,
82            instantiated,
83            discriminators,
84            parent,
85        );
86        debug_context.scopes[parent]
87    } else {
88        // The root is the function itself.
89        let file = cx.sess().source_map().lookup_source_file(mir.span.lo());
90        debug_context.scopes[scope] = DebugScope {
91            file_start_pos: file.start_pos,
92            file_end_pos: file.end_position(),
93            ..debug_context.scopes[scope]
94        };
95        instantiated.insert(scope);
96        return;
97    };
98
99    if let Some(vars) = variables
100        && !vars.contains(scope)
101        && scope_data.inlined.is_none()
102    {
103        // Do not create a DIScope if there are no variables defined in this
104        // MIR `SourceScope`, and it's not `inlined`, to avoid debuginfo bloat.
105        debug_context.scopes[scope] = parent_scope;
106        instantiated.insert(scope);
107        return;
108    }
109
110    let loc = cx.lookup_debug_loc(scope_data.span.lo());
111    let file_metadata = file_metadata(cx, &loc.file);
112
113    let dbg_scope = match scope_data.inlined {
114        Some((callee, _)) => {
115            // FIXME(eddyb) this would be `self.monomorphize(&callee)`
116            // if this is moved to `rustc_codegen_ssa::mir::debuginfo`.
117            let callee = cx.tcx.instantiate_and_normalize_erasing_regions(
118                instance.args,
119                cx.typing_env(),
120                ty::EarlyBinder::bind(callee),
121            );
122            debug_context.inlined_function_scopes.entry(callee).or_insert_with(|| {
123                let callee_fn_abi = cx.fn_abi_of_instance(callee, ty::List::empty());
124                cx.dbg_scope_fn(callee, callee_fn_abi, None)
125            })
126        }
127        None => unsafe {
128            llvm::LLVMDIBuilderCreateLexicalBlock(
129                DIB(cx),
130                parent_scope.dbg_scope,
131                file_metadata,
132                loc.line,
133                loc.col,
134            )
135        },
136    };
137
138    let inlined_at = scope_data.inlined.map(|(_, callsite_span)| {
139        let callsite_span = hygiene::walk_chain_collapsed(callsite_span, mir.span);
140        let callsite_scope = parent_scope.adjust_dbg_scope_for_span(cx, callsite_span);
141        let loc = cx.dbg_loc(callsite_scope, parent_scope.inlined_at, callsite_span);
142
143        // NB: In order to produce proper debug info for variables (particularly
144        // arguments) in multiply-inlined functions, LLVM expects to see a single
145        // DILocalVariable with multiple different DILocations in the IR. While
146        // the source information for each DILocation would be identical, their
147        // inlinedAt attributes will be unique to the particular callsite.
148        //
149        // We generate DILocations here based on the callsite's location in the
150        // source code. A single location in the source code usually can't
151        // produce multiple distinct calls so this mostly works, until
152        // macros get involved. A macro can generate multiple calls
153        // at the same span, which breaks the assumption that we're going to
154        // produce a unique DILocation for every scope we process here. We
155        // have to explicitly add discriminators if we see inlines into the
156        // same source code location.
157        //
158        // Note further that we can't key this hashtable on the span itself,
159        // because these spans could have distinct SyntaxContexts. We have
160        // to key on exactly what we're giving to LLVM.
161        match discriminators.entry(callsite_span.lo()) {
162            Entry::Occupied(mut o) => {
163                *o.get_mut() += 1;
164                // NB: We have to emit *something* here or we'll fail LLVM IR verification
165                // in at least some circumstances (see issue #135322) so if the required
166                // discriminant cannot be encoded fall back to the dummy location.
167                unsafe { llvm::LLVMRustDILocationCloneWithBaseDiscriminator(loc, *o.get()) }
168                    .unwrap_or_else(|| {
169                        cx.dbg_loc(callsite_scope, parent_scope.inlined_at, DUMMY_SP)
170                    })
171            }
172            Entry::Vacant(v) => {
173                v.insert(0);
174                loc
175            }
176        }
177    });
178
179    debug_context.scopes[scope] = DebugScope {
180        dbg_scope,
181        inlined_at: inlined_at.or(parent_scope.inlined_at),
182        file_start_pos: loc.file.start_pos,
183        file_end_pos: loc.file.end_position(),
184    };
185    instantiated.insert(scope);
186}