Skip to content

Commit b026d85

Browse files
committed
Auto merge of #132613 - khuey:master, r=jieyouxu
Add discriminators to DILocations when multiple functions are inlined into a single point. LLVM does not expect to ever see multiple dbg_declares for the same variable at the same location with different values. proc-macros make it possible for arbitrary code, including multiple calls that get inlined, to happen at any given location in the source code. Add discriminators when that happens so these locations are different to LLVM. This may interfere with the AddDiscriminators pass in LLVM, which is added by the unstable flag -Zdebug-info-for-profiling.
2 parents b73478b + 1dc1061 commit b026d85

File tree

5 files changed

+133
-3
lines changed

5 files changed

+133
-3
lines changed

Diff for: compiler/rustc_codegen_llvm/src/debuginfo/create_scope_map.rs

+57-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1+
use std::collections::hash_map::Entry;
2+
13
use rustc_codegen_ssa::mir::debuginfo::{DebugScope, FunctionDebugContext};
24
use rustc_codegen_ssa::traits::*;
5+
use rustc_data_structures::fx::FxHashMap;
36
use rustc_index::Idx;
47
use rustc_index::bit_set::BitSet;
58
use rustc_middle::mir::{Body, SourceScope};
69
use rustc_middle::ty::layout::FnAbiOf;
710
use rustc_middle::ty::{self, Instance};
811
use rustc_session::config::DebugInfo;
12+
use rustc_span::BytePos;
913

1014
use super::metadata::file_metadata;
1115
use super::utils::DIB;
@@ -37,10 +41,20 @@ pub(crate) fn compute_mir_scopes<'ll, 'tcx>(
3741
None
3842
};
3943
let mut instantiated = BitSet::new_empty(mir.source_scopes.len());
44+
let mut discriminators = FxHashMap::default();
4045
// Instantiate all scopes.
4146
for idx in 0..mir.source_scopes.len() {
4247
let scope = SourceScope::new(idx);
43-
make_mir_scope(cx, instance, mir, &variables, debug_context, &mut instantiated, scope);
48+
make_mir_scope(
49+
cx,
50+
instance,
51+
mir,
52+
&variables,
53+
debug_context,
54+
&mut instantiated,
55+
&mut discriminators,
56+
scope,
57+
);
4458
}
4559
assert!(instantiated.count() == mir.source_scopes.len());
4660
}
@@ -52,6 +66,7 @@ fn make_mir_scope<'ll, 'tcx>(
5266
variables: &Option<BitSet<SourceScope>>,
5367
debug_context: &mut FunctionDebugContext<'tcx, &'ll DIScope, &'ll DILocation>,
5468
instantiated: &mut BitSet<SourceScope>,
69+
discriminators: &mut FxHashMap<BytePos, u32>,
5570
scope: SourceScope,
5671
) {
5772
if instantiated.contains(scope) {
@@ -60,7 +75,16 @@ fn make_mir_scope<'ll, 'tcx>(
6075

6176
let scope_data = &mir.source_scopes[scope];
6277
let parent_scope = if let Some(parent) = scope_data.parent_scope {
63-
make_mir_scope(cx, instance, mir, variables, debug_context, instantiated, parent);
78+
make_mir_scope(
79+
cx,
80+
instance,
81+
mir,
82+
variables,
83+
debug_context,
84+
instantiated,
85+
discriminators,
86+
parent,
87+
);
6488
debug_context.scopes[parent]
6589
} else {
6690
// The root is the function itself.
@@ -117,7 +141,37 @@ fn make_mir_scope<'ll, 'tcx>(
117141
// FIXME(eddyb) this doesn't account for the macro-related
118142
// `Span` fixups that `rustc_codegen_ssa::mir::debuginfo` does.
119143
let callsite_scope = parent_scope.adjust_dbg_scope_for_span(cx, callsite_span);
120-
cx.dbg_loc(callsite_scope, parent_scope.inlined_at, callsite_span)
144+
let loc = cx.dbg_loc(callsite_scope, parent_scope.inlined_at, callsite_span);
145+
146+
// NB: In order to produce proper debug info for variables (particularly
147+
// arguments) in multiply-inline functions, LLVM expects to see a single
148+
// DILocalVariable with multiple different DILocations in the IR. While
149+
// the source information for each DILocation would be identical, their
150+
// inlinedAt attributes will be unique to the particular callsite.
151+
//
152+
// We generate DILocations here based on the callsite's location in the
153+
// source code. A single location in the source code usually can't
154+
// produce multiple distinct calls so this mostly works, until
155+
// proc-macros get involved. A proc-macro can generate multiple calls
156+
// at the same span, which breaks the assumption that we're going to
157+
// produce a unique DILocation for every scope we process here. We
158+
// have to explicitly add discriminators if we see inlines into the
159+
// same source code location.
160+
//
161+
// Note further that we can't key this hashtable on the span itself,
162+
// because these spans could have distinct SyntaxContexts. We have
163+
// to key on exactly what we're giving to LLVM.
164+
match discriminators.entry(callsite_span.lo()) {
165+
Entry::Occupied(mut o) => {
166+
*o.get_mut() += 1;
167+
unsafe { llvm::LLVMRustDILocationCloneWithBaseDiscriminator(loc, *o.get()) }
168+
.expect("Failed to encode discriminator in DILocation")
169+
}
170+
Entry::Vacant(v) => {
171+
v.insert(0);
172+
loc
173+
}
174+
}
121175
});
122176

123177
debug_context.scopes[scope] = DebugScope {

Diff for: compiler/rustc_codegen_llvm/src/llvm/ffi.rs

+4
Original file line numberDiff line numberDiff line change
@@ -2174,6 +2174,10 @@ unsafe extern "C" {
21742174
Scope: &'a DIScope,
21752175
InlinedAt: Option<&'a DILocation>,
21762176
) -> &'a DILocation;
2177+
pub fn LLVMRustDILocationCloneWithBaseDiscriminator<'a>(
2178+
Location: &'a DILocation,
2179+
BD: c_uint,
2180+
) -> Option<&'a DILocation>;
21772181
pub fn LLVMRustDIBuilderCreateOpDeref() -> u64;
21782182
pub fn LLVMRustDIBuilderCreateOpPlusUconst() -> u64;
21792183
pub fn LLVMRustDIBuilderCreateOpLLVMFragment() -> u64;

Diff for: compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp

+8
Original file line numberDiff line numberDiff line change
@@ -1305,6 +1305,14 @@ LLVMRustDIBuilderCreateDebugLocation(unsigned Line, unsigned Column,
13051305
return wrap(Loc);
13061306
}
13071307

1308+
extern "C" LLVMMetadataRef
1309+
LLVMRustDILocationCloneWithBaseDiscriminator(LLVMMetadataRef Location,
1310+
unsigned BD) {
1311+
DILocation *Loc = unwrapDIPtr<DILocation>(Location);
1312+
auto NewLoc = Loc->cloneWithBaseDiscriminator(BD);
1313+
return wrap(NewLoc.has_value() ? NewLoc.value() : nullptr);
1314+
}
1315+
13081316
extern "C" uint64_t LLVMRustDIBuilderCreateOpDeref() {
13091317
return dwarf::DW_OP_deref;
13101318
}
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//@ force-host
2+
//@ no-prefer-dynamic
3+
#![crate_type = "proc-macro"]
4+
5+
extern crate proc_macro;
6+
use proc_macro::*;
7+
8+
#[proc_macro]
9+
pub fn square_twice(_item: TokenStream) -> TokenStream {
10+
"(square(env::vars().count() as i32), square(env::vars().count() as i32))".parse().unwrap()
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//@ min-llvm-version: 19
2+
//@ compile-flags: -Cdebuginfo=2 -Copt-level=0 -Zmir-enable-passes=+Inline
3+
// MSVC is different because of the individual allocas.
4+
//@ ignore-msvc
5+
6+
//@ aux-build:macro_def.rs
7+
8+
// Find the variable.
9+
// CHECK-DAG: ![[#var_dbg:]] = !DILocalVariable(name: "n",{{( arg: 1,)?}} scope: ![[#var_scope:]]
10+
11+
// Find both dbg_declares. These will proceed the variable metadata, of course, so we're looking
12+
// backwards.
13+
// CHECK-DAG: dbg_declare(ptr %n.dbg.spill{{[0-9]}}, ![[#var_dbg]], !DIExpression(), ![[#var_loc2:]])
14+
// CHECK-DAG: dbg_declare(ptr %n.dbg.spill, ![[#var_dbg]], !DIExpression(), ![[#var_loc1:]])
15+
16+
// Find the first location definition, looking forwards again.
17+
// CHECK: ![[#var_loc1]] = !DILocation
18+
// CHECK-SAME: scope: ![[#var_scope:]], inlinedAt: ![[#var_inlinedAt1:]]
19+
20+
// Find the first location's inlinedAt
21+
// NB: If we fail here it's *probably* because we failed to produce two
22+
// different locations and ended up reusing an earlier one.
23+
// CHECK: ![[#var_inlinedAt1]] = !DILocation
24+
// CHECK-SAME: scope: ![[var_inlinedAt1_scope:]]
25+
26+
// Find the second location definition, still looking forwards.
27+
// NB: If we failed to produce two different locations, the test will
28+
// definitely fail by this point (if it hasn't already) because we won't
29+
// be able to find the same line again.
30+
// CHECK: ![[#var_loc2]] = !DILocation
31+
// CHECK-SAME: scope: ![[#var_scope]], inlinedAt: ![[#var_inlinedAt2:]]
32+
33+
// Find the second location's inlinedAt.
34+
// CHECK: ![[#var_inlinedAt2]] = !DILocation
35+
// CHECK-SAME: scope: ![[#var_inlinedAt2_scope:]]
36+
37+
// Finally, check that a discriminator was emitted for the second scope.
38+
// FIXMEkhuey ideally we would check that *either* scope has a discriminator
39+
// but I don't know that it's possible to check that with FileCheck.
40+
// CHECK: ![[#var_inlinedAt2_scope]] = !DILexicalBlockFile
41+
// CHECK-SAME: discriminator: [[#]]
42+
extern crate macro_def;
43+
44+
use std::env;
45+
46+
fn square(n: i32) -> i32 {
47+
n * n
48+
}
49+
50+
fn main() {
51+
let (z1, z2) = macro_def::square_twice!();
52+
println!("{z1} == {z2}");
53+
}

0 commit comments

Comments
 (0)