-
Notifications
You must be signed in to change notification settings - Fork 13.6k
Introduce debuginfo to statements in MIR #142771
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
06f5a9d
29357bc
a0b4fe1
ec25192
458ad93
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -228,8 +228,10 @@ impl<'a> MaybeTransitiveLiveLocals<'a> { | |
// Compute the place that we are storing to, if any | ||
let destination = match stmt_kind { | ||
StatementKind::Assign(box (place, rvalue)) => (rvalue.is_safe_to_remove() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit, easier for me to understand:
and then proceed to reasoning according to |
||
// FIXME: We are not sure how we should represent this debugging information for some statements, | ||
// keep it for now. | ||
&& (!debuginfo_locals.contains(place.local) | ||
|| (place.as_local().is_some() && matches!(rvalue, mir::Rvalue::Ref(..))))) | ||
|| (place.as_local().is_some() && stmt_kind.as_debuginfo().is_some()))) | ||
.then_some(*place), | ||
StatementKind::SetDiscriminant { place, .. } | StatementKind::Deinit(place) => { | ||
(!debuginfo_locals.contains(place.local)).then_some(**place) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,13 +22,14 @@ use rustc_mir_dataflow::impls::{ | |
LivenessTransferFunction, MaybeTransitiveLiveLocals, borrowed_locals, | ||
}; | ||
|
||
use crate::simplify::UsedInStmtLocals; | ||
use crate::util::is_within_packed; | ||
|
||
/// Performs the optimization on the body | ||
/// | ||
/// The `borrowed` set must be a `DenseBitSet` of all the locals that are ever borrowed in this | ||
/// body. It can be generated via the [`borrowed_locals`] function. | ||
fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { | ||
fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> bool { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you doc-comment what this bool is? |
||
let borrowed_locals = borrowed_locals(body); | ||
|
||
// If the user requests complete debuginfo, mark the locals that appear in it as live, so | ||
|
@@ -97,8 +98,9 @@ fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { | |
} | ||
|
||
if patch.is_empty() && call_operands_to_move.is_empty() { | ||
return; | ||
return false; | ||
} | ||
let eliminated = !patch.is_empty(); | ||
|
||
let bbs = body.basic_blocks.as_mut_preserves_cfg(); | ||
for (Location { block, statement_index }, drop_debuginfo) in patch { | ||
|
@@ -112,6 +114,8 @@ fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { | |
let Operand::Copy(place) = *arg else { bug!() }; | ||
*arg = Operand::Move(place); | ||
} | ||
|
||
eliminated | ||
} | ||
|
||
pub(super) enum DeadStoreElimination { | ||
|
@@ -132,7 +136,12 @@ impl<'tcx> crate::MirPass<'tcx> for DeadStoreElimination { | |
} | ||
|
||
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { | ||
eliminate(tcx, body); | ||
if eliminate(tcx, body) { | ||
UsedInStmtLocals::new(body).remove_unused_storage_annotations(body); | ||
for data in body.basic_blocks.as_mut_preserves_cfg() { | ||
data.strip_nops(); | ||
} | ||
} | ||
} | ||
|
||
fn is_required(&self) -> bool { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,12 +34,12 @@ | |
//! The normal logic that a program with UB can be changed to do anything does not apply to | ||
//! pre-"runtime" MIR! | ||
|
||
use rustc_index::bit_set::DenseBitSet; | ||
use rustc_index::{Idx, IndexSlice, IndexVec}; | ||
use rustc_middle::mir::visit::{ | ||
MutVisitor, MutatingUseContext, NonUseContext, PlaceContext, Visitor, | ||
}; | ||
use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor}; | ||
use rustc_middle::mir::*; | ||
use rustc_middle::ty::TyCtxt; | ||
use rustc_mir_dataflow::debuginfo::debuginfo_locals; | ||
use rustc_span::DUMMY_SP; | ||
use smallvec::SmallVec; | ||
use tracing::{debug, trace}; | ||
|
@@ -335,7 +335,7 @@ impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> { | |
|
||
fn strip_nops(&mut self) { | ||
for blk in self.basic_blocks.iter_mut() { | ||
blk.retain_statements(|stmt| !matches!(stmt.kind, StatementKind::Nop)) | ||
blk.strip_nops(); | ||
} | ||
} | ||
} | ||
|
@@ -508,28 +508,39 @@ fn make_local_map<V>( | |
/// Keeps track of used & unused locals. | ||
struct UsedLocals { | ||
increment: bool, | ||
arg_count: u32, | ||
use_count: IndexVec<Local, u32>, | ||
always_used: DenseBitSet<Local>, | ||
} | ||
|
||
impl UsedLocals { | ||
/// Determines which locals are used & unused in the given body. | ||
fn new(body: &Body<'_>) -> Self { | ||
let mut always_used = debuginfo_locals(body); | ||
always_used.insert(RETURN_PLACE); | ||
for arg in body.args_iter() { | ||
always_used.insert(arg); | ||
} | ||
let mut this = Self { | ||
increment: true, | ||
arg_count: body.arg_count.try_into().unwrap(), | ||
use_count: IndexVec::from_elem(0, &body.local_decls), | ||
always_used, | ||
}; | ||
this.visit_body(body); | ||
this | ||
} | ||
|
||
/// Checks if local is used. | ||
/// | ||
/// Return place and arguments are always considered used. | ||
/// Return place, arguments, var debuginfo are always considered used. | ||
fn is_used(&self, local: Local) -> bool { | ||
trace!("is_used({:?}): use_count: {:?}", local, self.use_count[local]); | ||
local.as_u32() <= self.arg_count || self.use_count[local] != 0 | ||
trace!( | ||
"is_used({:?}): use_count: {:?}, always_used: {}", | ||
local, | ||
self.use_count[local], | ||
self.always_used.contains(local) | ||
); | ||
// To keep things simple, we don't handle debugging information here, these are in DSE. | ||
self.always_used.contains(local) || self.use_count[local] != 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rather keep handling debuginfo in this general There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps, but I don't want to make this PR any more complicated, and I'm concerned about the compile time. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps I could handle this in this PR. |
||
} | ||
|
||
/// Updates the use counts to reflect the removal of given statement. | ||
|
@@ -574,17 +585,9 @@ impl<'tcx> Visitor<'tcx> for UsedLocals { | |
StatementKind::ConstEvalCounter | ||
| StatementKind::Nop | ||
| StatementKind::StorageLive(..) | ||
| StatementKind::StorageDead(..) => { | ||
for debuginfo in statement.debuginfos.iter() { | ||
self.visit_statement_debuginfo(debuginfo, location); | ||
} | ||
} | ||
|
||
| StatementKind::StorageDead(..) => {} | ||
StatementKind::Assign(box (ref place, ref rvalue)) => { | ||
if rvalue.is_safe_to_remove() { | ||
for debuginfo in statement.debuginfos.iter() { | ||
self.visit_statement_debuginfo(debuginfo, location); | ||
} | ||
self.visit_lhs(place, location); | ||
self.visit_rvalue(rvalue, location); | ||
} else { | ||
|
@@ -595,18 +598,18 @@ impl<'tcx> Visitor<'tcx> for UsedLocals { | |
StatementKind::SetDiscriminant { ref place, variant_index: _ } | ||
| StatementKind::Deinit(ref place) | ||
| StatementKind::BackwardIncompatibleDropHint { ref place, reason: _ } => { | ||
for debuginfo in statement.debuginfos.iter() { | ||
self.visit_statement_debuginfo(debuginfo, location); | ||
} | ||
self.visit_lhs(place, location); | ||
} | ||
} | ||
} | ||
|
||
fn visit_local(&mut self, local: Local, ctx: PlaceContext, _location: Location) { | ||
if matches!(ctx, PlaceContext::NonUse(_)) { | ||
return; | ||
} | ||
if self.increment { | ||
self.use_count[local] += 1; | ||
} else if ctx != PlaceContext::NonUse(NonUseContext::VarDebugInfo) { | ||
} else { | ||
assert_ne!(self.use_count[local], 0); | ||
self.use_count[local] -= 1; | ||
} | ||
|
@@ -626,28 +629,26 @@ fn remove_unused_definitions_helper(used_locals: &mut UsedLocals, body: &mut Bod | |
|
||
for data in body.basic_blocks.as_mut_preserves_cfg() { | ||
// Remove unnecessary StorageLive and StorageDead annotations. | ||
data.retain_statements(|statement| { | ||
let keep = match &statement.kind { | ||
for statement in data.statements.iter_mut() { | ||
let keep_statement = match &statement.kind { | ||
StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => { | ||
used_locals.is_used(*local) | ||
} | ||
StatementKind::Assign(box (place, _)) => used_locals.is_used(place.local), | ||
|
||
StatementKind::SetDiscriminant { place, .. } | ||
| StatementKind::BackwardIncompatibleDropHint { place, reason: _ } | ||
| StatementKind::Deinit(place) => used_locals.is_used(place.local), | ||
StatementKind::Nop => false, | ||
_ => true, | ||
StatementKind::Assign(box (place, _)) | ||
| StatementKind::SetDiscriminant { box place, .. } | ||
| StatementKind::BackwardIncompatibleDropHint { box place, .. } | ||
| StatementKind::Deinit(box place) => used_locals.is_used(place.local), | ||
_ => continue, | ||
}; | ||
|
||
if !keep { | ||
trace!("removing statement {:?}", statement); | ||
modified = true; | ||
used_locals.statement_removed(statement); | ||
if keep_statement { | ||
continue; | ||
} | ||
|
||
keep | ||
}); | ||
trace!("removing statement {:?}", statement); | ||
modified = true; | ||
used_locals.statement_removed(statement); | ||
statement.make_nop(true); | ||
} | ||
data.strip_nops(); | ||
} | ||
} | ||
} | ||
|
@@ -666,3 +667,42 @@ impl<'tcx> MutVisitor<'tcx> for LocalUpdater<'tcx> { | |
*l = self.map[*l].unwrap(); | ||
} | ||
} | ||
|
||
pub(crate) struct UsedInStmtLocals { | ||
pub(crate) locals: DenseBitSet<Local>, | ||
} | ||
|
||
impl UsedInStmtLocals { | ||
pub(crate) fn new(body: &Body<'_>) -> Self { | ||
let mut this = Self { locals: DenseBitSet::new_empty(body.local_decls.len()) }; | ||
this.visit_body(body); | ||
this | ||
} | ||
|
||
pub(crate) fn remove_unused_storage_annotations<'tcx>(&self, body: &mut Body<'tcx>) { | ||
for data in body.basic_blocks.as_mut_preserves_cfg() { | ||
// Remove unnecessary StorageLive and StorageDead annotations. | ||
for statement in data.statements.iter_mut() { | ||
let keep_statement = match &statement.kind { | ||
StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => { | ||
self.locals.contains(*local) | ||
} | ||
_ => continue, | ||
}; | ||
if keep_statement { | ||
continue; | ||
} | ||
statement.make_nop(true); | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl<'tcx> Visitor<'tcx> for UsedInStmtLocals { | ||
fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) { | ||
if matches!(context, PlaceContext::NonUse(_)) { | ||
return; | ||
} | ||
self.locals.insert(local); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder: should we attempt to keep debuginfo in most cases, and only drop statements that we do not know how to convert? I mean, consider
drop_debuginfo
to be always false, and replace thebug!
by a no-op?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer to do this after we've reviewed most of the MIR statements.