Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
9 changes: 6 additions & 3 deletions compiler/rustc_borrowck/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2516,10 +2516,13 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
}
}

/// If the local is in the `EverInits` set, returns the first init in gather order.
/// FIXME(nnethercote): this choice is sub-optimal for certain error messages.
fn is_local_ever_initialized(&self, local: Local, state: &BorrowckDomain) -> Option<InitIndex> {
let mpi = self.move_data.rev_lookup.find_local(local)?;
let ii = &self.move_data.init_path_map[mpi];
ii.into_iter().find(|&&index| state.ever_inits.contains(index)).copied()
state.ever_inits.contains(local).then(|| {
let mpi = self.move_data.rev_lookup.find_local(local).unwrap();
self.move_data.init_path_map[mpi][0]
})
}

/// Adds the place into the used mutable variables set
Expand Down
99 changes: 49 additions & 50 deletions compiler/rustc_mir_dataflow/src/impls/initialized.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
use std::assert_matches;

use rustc_abi::VariantIdx;
use rustc_index::Idx;
use rustc_index::bit_set::{DenseBitSet, MixedBitSet};
use rustc_middle::bug;
use rustc_middle::mir::{self, Body, CallReturnPlaces, Location, TerminatorEdges};
use rustc_middle::mir::{self, Body, CallReturnPlaces, Local, Location, TerminatorEdges};
use rustc_middle::ty::{self, TyCtxt};
use smallvec::SmallVec;
use tracing::{debug, instrument};
use tracing::instrument;

use crate::drop_flag_effects::{DropFlagState, InactiveVariants};
use crate::move_paths::{HasMoveData, InitIndex, InitKind, LookupResult, MoveData, MovePathIndex};
use crate::move_paths::{HasMoveData, InitKind, LookupResult, MoveData, MovePathIndex};
use crate::{
Analysis, GenKill, MaybeReachable, SwitchTargetIndex, drop_flag_effects,
drop_flag_effects_for_function_entry, drop_flag_effects_for_location, on_all_children_bits,
Expand Down Expand Up @@ -267,36 +266,34 @@ impl<'tcx> HasMoveData<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
}
}

/// `EverInitializedPlaces` tracks all initializations that may have occurred
/// upon reaching a particular point in the control flow for a function,
/// without an intervening `StorageDead`.
/// `EverInitializedPlaces` tracks all initializations of locals that may have
/// occurred upon reaching a particular point in the control flow for a
/// function, without an intervening `StorageDead`.
///
/// This dataflow is used to determine if an immutable local variable may
/// be assigned to.
///
/// For example, in code like the following, we have corresponding
/// dataflow information shown in the right-hand comments. Underscored indices
/// are used to distinguish between multiple initializations of the same local
/// variable, e.g. `b_0` and `b_1`.
/// dataflow information shown in the right-hand comments.
///
/// ```rust
/// struct S;
/// #[rustfmt::skip]
/// fn foo(p: bool) { // ever-init:
/// // {p, }
/// let a = S; let mut b = S; let c; let d; // {p, a, b_0, }
/// // {p, }
/// let a = S; let mut b = S; let c; let d; // {p, a, b, }
///
/// if p {
/// drop(a); // {p, a, b_0, }
/// b = S; // {p, a, b_0, b_1, }
/// drop(a); // {p, a, b, }
/// b = S; // {p, a, b, }
///
/// } else {
/// drop(b); // {p, a, b_0, b_1, }
/// d = S; // {p, a, b_0, b_1, d}
/// drop(b); // {p, a, b, }
/// d = S; // {p, a, b, d}
///
/// } // {p, a, b_0, b_1, d}
/// } // {p, a, b, d}
///
/// c = S; // {p, a, b_0, b_1, c, d}
/// c = S; // {p, a, b, c, d}
/// }
/// ```
pub struct EverInitializedPlaces<'a, 'tcx> {
Expand Down Expand Up @@ -590,23 +587,21 @@ impl<'tcx> Analysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
}
}

/// There can be many more `InitIndex` than there are locals in a MIR body.
/// We use a mixed bitset to avoid paying too high a memory footprint.
pub type EverInitializedPlacesDomain = MixedBitSet<InitIndex>;
pub type EverInitializedPlacesDomain = DenseBitSet<Local>;

impl<'tcx> Analysis<'tcx> for EverInitializedPlaces<'_, 'tcx> {
type Domain = EverInitializedPlacesDomain;

const NAME: &'static str = "ever_init";

fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
// bottom = no initialized variables by default
MixedBitSet::new_empty(self.move_data().inits.len())
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
// bottom = no initialized locals by default
DenseBitSet::new_empty(body.local_decls.len())
}

fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) {
for arg_init in 0..body.arg_count {
state.insert(InitIndex::new(arg_init));
for arg in body.args_iter() {
state.insert(arg);
}
}

Expand All @@ -618,20 +613,18 @@ impl<'tcx> Analysis<'tcx> for EverInitializedPlaces<'_, 'tcx> {
location: Location,
) {
let move_data = self.move_data();
let init_path_map = &move_data.init_path_map;
let init_loc_map = &move_data.init_loc_map;
let rev_lookup = &move_data.rev_lookup;

debug!("initializes move_indexes {:?}", init_loc_map[location]);
state.gen_all(init_loc_map[location].iter().copied());
// Record inits of locals. Projections can be ignored.
state.gen_all(init_loc_map[location].iter().copied().filter_map(|ii| {
let init_mpi = move_data.inits[ii].path;
move_data.move_paths[init_mpi].place.as_local()
}));

if let mir::StatementKind::StorageDead(local) = stmt.kind
// End inits for StorageDead, so that an immutable variable can
// be reinitialized on the next iteration of the loop.
&& let Some(move_path_index) = rev_lookup.find_local(local)
{
debug!("clears the ever initialized status of {:?}", init_path_map[move_path_index]);
state.kill_all(init_path_map[move_path_index].iter().copied());
// Kill on StorageDead, so that an immutable variable can
// be reinitialized on the next iteration of the loop.
if let mir::StatementKind::StorageDead(local) = stmt.kind {
state.kill(local);
}
}

Expand All @@ -644,16 +637,16 @@ impl<'tcx> Analysis<'tcx> for EverInitializedPlaces<'_, 'tcx> {
) -> TerminatorEdges<'mir, 'tcx> {
let move_data = self.move_data();
let init_loc_map = &move_data.init_loc_map;
debug!(?terminator);
debug!("initializes move_indexes {:?}", init_loc_map[location]);
state.gen_all(
init_loc_map[location]
.iter()
.filter(|init_index| {
move_data.inits[**init_index].kind != InitKind::NonPanicPathOnly
})
.copied(),
);

// Record inits of locals. Projections can be ignored.
state.gen_all(init_loc_map[location].iter().copied().filter_map(|ii| {
let init = &move_data.inits[ii];
if init.kind != InitKind::NonPanicPathOnly {
move_data.move_paths[init.path].place.as_local()
} else {
None
}
}));
terminator.edges()
}

Expand All @@ -666,9 +659,15 @@ impl<'tcx> Analysis<'tcx> for EverInitializedPlaces<'_, 'tcx> {
let move_data = self.move_data();
let init_loc_map = &move_data.init_loc_map;

// Record inits of locals. Projections can be ignored.
let call_loc = self.body.terminator_loc(block);
for init_index in &init_loc_map[call_loc] {
state.gen_(*init_index);
}
state.gen_all(init_loc_map[call_loc].iter().copied().filter_map(|ii| {
let init = &move_data.inits[ii];
if init.kind == InitKind::NonPanicPathOnly {

@cjgillot cjgillot Aug 3, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why do we check NonPanicPathOnly now?

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Look at Forward::apply_effects_in_block:

  • It calls apply_primary_terminator_effect unconditionally on the terminator.
  • It calls apply_call_return_effect for an AssignOnReturn terminator with at least one return edge (Call, Yield, InlineAsm).
  • So there is double handling of some inits, specifically Deep inits for Yield/InlineAsm.
  • apply_primary_terminator_effect already filters out the NonPanicPathOnly cases, so apply_call_return_effect can apply the inverse filter to avoid the double handling.

Double handling doesn't really matter, because gen is an idempotent operation. However, removing the double handling makes the if/else if/else in init_reaches_location cleanly mirror apply_primary_statement_effect / apply_call_return_effect / apply_primary_terminator_effect, as per the comments in init_reaches_location.

move_data.move_paths[init.path].place.as_local()
} else {
None
}
}));
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
error[E0384]: cannot assign twice to immutable variable `x`
--> $DIR/liveness-assign-imm-local-notes.rs:10:9
|
LL | x = 2;
LL | x = 1;
| ----- first assignment to `x`
...
LL | x = 3;
| ^^^^^ cannot assign twice to immutable variable
|
Expand All @@ -14,8 +15,9 @@ LL | let mut x;
error[E0384]: cannot assign twice to immutable variable `x`
--> $DIR/liveness-assign-imm-local-notes.rs:21:13
|
LL | x = 2;
LL | x = 1;
| ----- first assignment to `x`
...
LL | x = 3;
| ^^^^^ cannot assign twice to immutable variable
|
Expand Down