Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/analyze/local_def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,48 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {
}
}

/// Removes writes to a part of a local (`s.f = v`) that is never read afterwards.
///
/// Liveness counts such a write as neither a use nor a definition of the local, so the local
/// is already dropped where the write happens, and nothing would drop the written value. With
/// the write gone, its operands are dropped where they die instead.
fn remove_dead_partial_writes(&mut self) {
use rustc_mir_dataflow::Analysis as _;

loop {
let mut results = rustc_mir_dataflow::impls::MaybeLiveLocals
.iterate_to_fixpoint(self.tcx, &self.body, None)
.into_results_cursor(&self.body);
let mut dead_writes = Vec::new();
for (block, data) in self.body.basic_blocks.iter_enumerated() {
for (statement_index, statement) in data.statements.iter().enumerate() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Traverse statements in backward analysis order

For functions containing large basic blocks, this forward enumeration drives the ResultsCursor opposite MaybeLiveLocals' backward direction; the cursor only has linear performance when statements are visited in the analysis direction and can otherwise repeatedly replay the block (rustc documentation). That makes each scan quadratic, and the surrounding fixpoint loop can require one scan per link in a chain of partial writes, producing cubic behavior and potentially making verification of generated or otherwise large functions impractically slow. Iterate the statements in reverse while retaining their original indices, or use a results visitor.

Useful? React with 👍 / 👎.

let Some((place, _)) = statement.kind.as_assign() else {
continue;
};
if place.projection.is_empty() || place.is_indirect() {
continue;
}
// liveness is a backward analysis: this is the state after the statement
results.seek_before_primary_effect(mir::Location {
block,
statement_index,
});
if !results.get().contains(place.local) {
dead_writes.push((block, statement_index));
}
}
}
if dead_writes.is_empty() {
return;
}
for (block, statement_index) in dead_writes {
tracing::debug!(?block, ?statement_index, "removing dead partial write");
self.body.basic_blocks.as_mut()[block].statements[statement_index].kind =
mir::StatementKind::Nop;
}
}
}

fn mut_locals(
&self,
data: &mir::BasicBlockData<'tcx>,
Expand Down Expand Up @@ -1160,6 +1202,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {

self.unelaborate_derefs();
analyze::reconstruct_slice_indexing::reconstruct(self.tcx, &mut self.body);
self.remove_dead_partial_writes();
self.reassign_local_mutabilities();
self.refine_basic_blocks();
self.analyze_basic_blocks(expected);
Expand Down
16 changes: 16 additions & 0 deletions tests/ui/fail/dead_field_write.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//@error-in-other-file: Unsat
//@compile-flags: -C debug-assertions=off

#[thrust::callable]
fn retarget(c: bool) {
let mut a = 1_i64;
let mut b = 2_i64;
let mut s = (&mut a, 0_i64);
*s.0 = 5;
if c {
s.0 = &mut b;
}
assert!(a == 5 && b == 3);
}

fn main() {}
16 changes: 16 additions & 0 deletions tests/ui/pass/dead_field_write.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//@check-pass
//@compile-flags: -C debug-assertions=off

#[thrust::callable]
fn retarget(c: bool) {
let mut a = 1_i64;
let mut b = 2_i64;
let mut s = (&mut a, 0_i64);
*s.0 = 5;
if c {
s.0 = &mut b;
}
assert!(a == 5 && b == 2);
}

fn main() {}
Loading