Skip to content

Commit 985da43

Browse files
committed
[mir-opt] Allow debuginfo to be generated for a constant or a Place
Prior to this commit, debuginfo was always generated by mapping a name to a Place. This has the side-effect that `SimplifyLocals` cannot remove locals that are only used for debuginfo because their other uses have been const-propagated. To allow these locals to be removed, we now allow debuginfo to point to a constant value. The `ConstProp` pass detects when debuginfo points to a local with a known constant value and replaces it with the value. This allows the later `SimplifyLocals` pass to remove the local.
1 parent c351775 commit 985da43

22 files changed

+411
-134
lines changed

compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1416,10 +1416,11 @@ fn generator_layout_and_saved_local_names(
14161416

14171417
let state_arg = mir::Local::new(1);
14181418
for var in &body.var_debug_info {
1419-
if var.place.local != state_arg {
1419+
let place = if let mir::VarDebugInfoContents::Place(p) = var.value { p } else { continue };
1420+
if place.local != state_arg {
14201421
continue;
14211422
}
1422-
match var.place.projection[..] {
1423+
match place.projection[..] {
14231424
[
14241425
// Deref of the `Pin<&mut Self>` state argument.
14251426
mir::ProjectionElem::Field(..),

compiler/rustc_codegen_ssa/src/mir/constant.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use super::FunctionCx;
1111

1212
impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
1313
pub fn eval_mir_constant_to_operand(
14-
&mut self,
14+
&self,
1515
bx: &mut Bx,
1616
constant: &mir::Constant<'tcx>,
1717
) -> Result<OperandRef<'tcx, Bx::Value>, ErrorHandled> {
@@ -21,7 +21,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
2121
}
2222

2323
pub fn eval_mir_constant(
24-
&mut self,
24+
&self,
2525
constant: &mir::Constant<'tcx>,
2626
) -> Result<ConstValue<'tcx>, ErrorHandled> {
2727
match self.monomorphize(&constant.literal).val {

compiler/rustc_codegen_ssa/src/mir/debuginfo.rs

Lines changed: 67 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use rustc_span::symbol::{kw, Symbol};
99
use rustc_span::{BytePos, Span};
1010
use rustc_target::abi::{LayoutOf, Size};
1111

12-
use super::operand::OperandValue;
12+
use super::operand::{OperandRef, OperandValue};
1313
use super::place::PlaceRef;
1414
use super::{FunctionCx, LocalRef};
1515

@@ -110,6 +110,24 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
110110
}
111111
}
112112

113+
fn spill_operand_to_stack(
114+
operand: &OperandRef<'tcx, Bx::Value>,
115+
name: Option<String>,
116+
bx: &mut Bx,
117+
) -> PlaceRef<'tcx, Bx::Value> {
118+
// "Spill" the value onto the stack, for debuginfo,
119+
// without forcing non-debuginfo uses of the local
120+
// to also load from the stack every single time.
121+
// FIXME(#68817) use `llvm.dbg.value` instead,
122+
// at least for the cases which LLVM handles correctly.
123+
let spill_slot = PlaceRef::alloca(bx, operand.layout);
124+
if let Some(name) = name {
125+
bx.set_var_name(spill_slot.llval, &(name + ".dbg.spill"));
126+
}
127+
operand.val.store(bx, spill_slot);
128+
spill_slot
129+
}
130+
113131
/// Apply debuginfo and/or name, after creating the `alloca` for a local,
114132
/// or initializing the local with an operand (whichever applies).
115133
pub fn debug_introduce_local(&self, bx: &mut Bx, local: mir::Local) {
@@ -224,17 +242,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
224242
return;
225243
}
226244

227-
// "Spill" the value onto the stack, for debuginfo,
228-
// without forcing non-debuginfo uses of the local
229-
// to also load from the stack every single time.
230-
// FIXME(#68817) use `llvm.dbg.value` instead,
231-
// at least for the cases which LLVM handles correctly.
232-
let spill_slot = PlaceRef::alloca(bx, operand.layout);
233-
if let Some(name) = name {
234-
bx.set_var_name(spill_slot.llval, &(name + ".dbg.spill"));
235-
}
236-
operand.val.store(bx, spill_slot);
237-
spill_slot
245+
Self::spill_operand_to_stack(operand, name, bx)
238246
}
239247

240248
LocalRef::Place(place) => *place,
@@ -309,6 +317,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
309317
/// Partition all `VarDebugInfo` in `self.mir`, by their base `Local`.
310318
pub fn compute_per_local_var_debug_info(
311319
&self,
320+
bx: &mut Bx,
312321
) -> Option<IndexVec<mir::Local, Vec<PerLocalVarDebugInfo<'tcx, Bx::DIVariable>>>> {
313322
let full_debug_info = self.cx.sess().opts.debuginfo == DebugInfo::Full;
314323

@@ -323,22 +332,30 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
323332
} else {
324333
(None, var.source_info.span)
325334
};
335+
let (var_ty, var_kind) = match var.value {
336+
mir::VarDebugInfoContents::Place(place) => {
337+
let var_ty = self.monomorphized_place_ty(place.as_ref());
338+
let var_kind = if self.mir.local_kind(place.local) == mir::LocalKind::Arg
339+
&& place.projection.is_empty()
340+
&& var.source_info.scope == mir::OUTERMOST_SOURCE_SCOPE
341+
{
342+
let arg_index = place.local.index() - 1;
343+
344+
// FIXME(eddyb) shouldn't `ArgumentVariable` indices be
345+
// offset in closures to account for the hidden environment?
346+
// Also, is this `+ 1` needed at all?
347+
VariableKind::ArgumentVariable(arg_index + 1)
348+
} else {
349+
VariableKind::LocalVariable
350+
};
351+
(var_ty, var_kind)
352+
}
353+
mir::VarDebugInfoContents::Const(c) => {
354+
let ty = self.monomorphize(&c.literal.ty);
355+
(ty, VariableKind::LocalVariable)
356+
}
357+
};
326358
let dbg_var = scope.map(|scope| {
327-
let place = var.place;
328-
let var_ty = self.monomorphized_place_ty(place.as_ref());
329-
let var_kind = if self.mir.local_kind(place.local) == mir::LocalKind::Arg
330-
&& place.projection.is_empty()
331-
&& var.source_info.scope == mir::OUTERMOST_SOURCE_SCOPE
332-
{
333-
let arg_index = place.local.index() - 1;
334-
335-
// FIXME(eddyb) shouldn't `ArgumentVariable` indices be
336-
// offset in closures to account for the hidden environment?
337-
// Also, is this `+ 1` needed at all?
338-
VariableKind::ArgumentVariable(arg_index + 1)
339-
} else {
340-
VariableKind::LocalVariable
341-
};
342359
self.cx.create_dbg_var(
343360
self.debug_context.as_ref().unwrap(),
344361
var.name,
@@ -349,12 +366,29 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
349366
)
350367
});
351368

352-
per_local[var.place.local].push(PerLocalVarDebugInfo {
353-
name: var.name,
354-
source_info: var.source_info,
355-
dbg_var,
356-
projection: var.place.projection,
357-
});
369+
match var.value {
370+
mir::VarDebugInfoContents::Place(place) => {
371+
per_local[place.local].push(PerLocalVarDebugInfo {
372+
name: var.name,
373+
source_info: var.source_info,
374+
dbg_var,
375+
projection: place.projection,
376+
});
377+
}
378+
mir::VarDebugInfoContents::Const(c) => {
379+
if let (Some(scope), Some(dbg_var)) = (scope, dbg_var) {
380+
if let Ok(operand) = self.eval_mir_constant_to_operand(bx, &c) {
381+
let base = Self::spill_operand_to_stack(
382+
&operand,
383+
Some(var.name.to_string()),
384+
bx,
385+
);
386+
387+
bx.dbg_var_addr(dbg_var, scope, base.llval, Size::ZERO, &[], span);
388+
}
389+
}
390+
}
391+
}
358392
}
359393
Some(per_local)
360394
}

compiler/rustc_codegen_ssa/src/mir/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
190190
caller_location: None,
191191
};
192192

193-
fx.per_local_var_debug_info = fx.compute_per_local_var_debug_info();
193+
fx.per_local_var_debug_info = fx.compute_per_local_var_debug_info(&mut bx);
194194

195195
for const_ in &mir.required_consts {
196196
if let Err(err) = fx.eval_mir_constant(const_) {

compiler/rustc_middle/src/mir/mod.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,23 @@ impl<'tcx> LocalDecl<'tcx> {
10571057
}
10581058
}
10591059

1060+
#[derive(Clone, TyEncodable, TyDecodable, HashStable, TypeFoldable)]
1061+
pub enum VarDebugInfoContents<'tcx> {
1062+
/// NOTE(eddyb) There's an unenforced invariant that this `Place` is
1063+
/// based on a `Local`, not a `Static`, and contains no indexing.
1064+
Place(Place<'tcx>),
1065+
Const(Constant<'tcx>),
1066+
}
1067+
1068+
impl<'tcx> Debug for VarDebugInfoContents<'tcx> {
1069+
fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
1070+
match self {
1071+
VarDebugInfoContents::Const(c) => write!(fmt, "{}", c),
1072+
VarDebugInfoContents::Place(p) => write!(fmt, "{:?}", p),
1073+
}
1074+
}
1075+
}
1076+
10601077
/// Debug information pertaining to a user variable.
10611078
#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable)]
10621079
pub struct VarDebugInfo<'tcx> {
@@ -1068,9 +1085,7 @@ pub struct VarDebugInfo<'tcx> {
10681085
pub source_info: SourceInfo,
10691086

10701087
/// Where the data for this user variable is to be found.
1071-
/// NOTE(eddyb) There's an unenforced invariant that this `Place` is
1072-
/// based on a `Local`, not a `Static`, and contains no indexing.
1073-
pub place: Place<'tcx>,
1088+
pub value: VarDebugInfoContents<'tcx>,
10741089
}
10751090

10761091
///////////////////////////////////////////////////////////////////////////

compiler/rustc_middle/src/mir/visit.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -797,16 +797,20 @@ macro_rules! make_mir_visitor {
797797
let VarDebugInfo {
798798
name: _,
799799
source_info,
800-
place,
800+
value,
801801
} = var_debug_info;
802802

803803
self.visit_source_info(source_info);
804804
let location = START_BLOCK.start_location();
805-
self.visit_place(
806-
place,
807-
PlaceContext::NonUse(NonUseContext::VarDebugInfo),
808-
location,
809-
);
805+
match value {
806+
VarDebugInfoContents::Const(c) => self.visit_constant(c, location),
807+
VarDebugInfoContents::Place(place) =>
808+
self.visit_place(
809+
place,
810+
PlaceContext::NonUse(NonUseContext::VarDebugInfo),
811+
location
812+
),
813+
}
810814
}
811815

812816
fn super_source_scope(&mut self,

compiler/rustc_mir/src/borrow_check/mod.rs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use rustc_index::vec::IndexVec;
1111
use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
1212
use rustc_middle::mir::{
1313
traversal, Body, ClearCrossCrate, Local, Location, Mutability, Operand, Place, PlaceElem,
14-
PlaceRef,
14+
PlaceRef, VarDebugInfoContents,
1515
};
1616
use rustc_middle::mir::{AggregateKind, BasicBlock, BorrowCheckResult, BorrowKind};
1717
use rustc_middle::mir::{Field, ProjectionElem, Promoted, Rvalue, Statement, StatementKind};
@@ -133,19 +133,21 @@ fn do_mir_borrowck<'a, 'tcx>(
133133

134134
let mut local_names = IndexVec::from_elem(None, &input_body.local_decls);
135135
for var_debug_info in &input_body.var_debug_info {
136-
if let Some(local) = var_debug_info.place.as_local() {
137-
if let Some(prev_name) = local_names[local] {
138-
if var_debug_info.name != prev_name {
139-
span_bug!(
140-
var_debug_info.source_info.span,
141-
"local {:?} has many names (`{}` vs `{}`)",
142-
local,
143-
prev_name,
144-
var_debug_info.name
145-
);
136+
if let VarDebugInfoContents::Place(place) = var_debug_info.value {
137+
if let Some(local) = place.as_local() {
138+
if let Some(prev_name) = local_names[local] {
139+
if var_debug_info.name != prev_name {
140+
span_bug!(
141+
var_debug_info.source_info.span,
142+
"local {:?} has many names (`{}` vs `{}`)",
143+
local,
144+
prev_name,
145+
var_debug_info.name
146+
);
147+
}
146148
}
149+
local_names[local] = Some(var_debug_info.name);
147150
}
148-
local_names[local] = Some(var_debug_info.name);
149151
}
150152
}
151153

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
//! Finds locals which are assigned once to a const and unused except for debuginfo and converts
2+
//! their debuginfo to use the const directly, allowing the local to be removed.
3+
4+
use rustc_middle::{
5+
mir::{
6+
visit::{PlaceContext, Visitor},
7+
Body, Constant, Local, Location, Operand, Rvalue, StatementKind, VarDebugInfoContents,
8+
},
9+
ty::TyCtxt,
10+
};
11+
12+
use super::MirSource;
13+
use crate::transform::MirPass;
14+
use rustc_index::{bit_set::BitSet, vec::IndexVec};
15+
16+
pub struct ConstDebugInfo;
17+
18+
impl<'tcx> MirPass<'tcx> for ConstDebugInfo {
19+
fn run_pass(&self, _tcx: TyCtxt<'tcx>, source: MirSource<'tcx>, body: &mut Body<'tcx>) {
20+
trace!("running ConstDebugInfo on {:?}", source);
21+
22+
for (local, constant) in find_optimization_oportunities(body) {
23+
for debuginfo in &mut body.var_debug_info {
24+
if let VarDebugInfoContents::Place(p) = debuginfo.value {
25+
if p.local == local && p.projection.is_empty() {
26+
trace!(
27+
"changing debug info for {:?} from place {:?} to constant {:?}",
28+
debuginfo.name,
29+
p,
30+
constant
31+
);
32+
debuginfo.value = VarDebugInfoContents::Const(constant);
33+
}
34+
}
35+
}
36+
}
37+
}
38+
}
39+
40+
struct LocalUseVisitor {
41+
local_mutating_uses: IndexVec<Local, u8>,
42+
local_assignment_locations: IndexVec<Local, Option<Location>>,
43+
}
44+
45+
fn find_optimization_oportunities<'tcx>(body: &Body<'tcx>) -> Vec<(Local, Constant<'tcx>)> {
46+
let mut visitor = LocalUseVisitor {
47+
local_mutating_uses: IndexVec::from_elem(0, &body.local_decls),
48+
local_assignment_locations: IndexVec::from_elem(None, &body.local_decls),
49+
};
50+
51+
visitor.visit_body(body);
52+
53+
let mut locals_to_debuginfo = BitSet::new_empty(body.local_decls.len());
54+
for debuginfo in &body.var_debug_info {
55+
if let VarDebugInfoContents::Place(p) = debuginfo.value {
56+
if let Some(l) = p.as_local() {
57+
locals_to_debuginfo.insert(l);
58+
}
59+
}
60+
}
61+
62+
let mut eligable_locals = Vec::new();
63+
for (local, mutating_uses) in visitor.local_mutating_uses.drain_enumerated(..) {
64+
if mutating_uses != 1 || !locals_to_debuginfo.contains(local) {
65+
continue;
66+
}
67+
68+
if let Some(location) = visitor.local_assignment_locations[local] {
69+
let bb = &body[location.block];
70+
71+
// The value is assigned as the result of a call, not a constant
72+
if bb.statements.len() == location.statement_index {
73+
continue;
74+
}
75+
76+
if let StatementKind::Assign(box (p, Rvalue::Use(Operand::Constant(box c)))) =
77+
&bb.statements[location.statement_index].kind
78+
{
79+
if let Some(local) = p.as_local() {
80+
eligable_locals.push((local, *c));
81+
}
82+
}
83+
}
84+
}
85+
86+
eligable_locals
87+
}
88+
89+
impl<'tcx> Visitor<'tcx> for LocalUseVisitor {
90+
fn visit_local(&mut self, local: &Local, context: PlaceContext, location: Location) {
91+
if context.is_mutating_use() {
92+
self.local_mutating_uses[*local] += 1;
93+
94+
if context.is_place_assignment() {
95+
self.local_assignment_locations[*local] = Some(location);
96+
}
97+
}
98+
}
99+
}

0 commit comments

Comments
 (0)