From dcf3e1c85976f3be1c88ad91eb25c317fb7e2030 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 22:14:40 +0000 Subject: [PATCH] Remove writes to a part of a dead local before analysis Liveness counts a write such as `s.f = v` as neither a use nor a definition of `s`, so when `s` is never read afterwards it is already dropped where the write happens. In a later basic block the local is unbound and the assignment aborts the analyzer; in the same block a `&mut` stored this way is never dropped, so its lender is havoc'd. Such a write is unobservable, so remove it before computing mutabilities and drop points; its operands are then dropped where they die. Removing a write can make another one dead, so repeat until none is left. Fixes #346. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UTeLTYcpPtuCS816SZ1NJP --- src/analyze/local_def.rs | 43 +++++++++++++++++++++++++++++++ tests/ui/fail/dead_field_write.rs | 16 ++++++++++++ tests/ui/pass/dead_field_write.rs | 16 ++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 tests/ui/fail/dead_field_write.rs create mode 100644 tests/ui/pass/dead_field_write.rs diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs index 6a9cdf1b..953f1f68 100644 --- a/src/analyze/local_def.rs +++ b/src/analyze/local_def.rs @@ -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() { + 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>, @@ -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); diff --git a/tests/ui/fail/dead_field_write.rs b/tests/ui/fail/dead_field_write.rs new file mode 100644 index 00000000..7321ba18 --- /dev/null +++ b/tests/ui/fail/dead_field_write.rs @@ -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() {} diff --git a/tests/ui/pass/dead_field_write.rs b/tests/ui/pass/dead_field_write.rs new file mode 100644 index 00000000..b28e6d0b --- /dev/null +++ b/tests/ui/pass/dead_field_write.rs @@ -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() {}