Auto merge of #140053 - ChrisDenton:rollup-tt00skl, r=ChrisDenton
Rollup of 7 pull requests Successful merges: - #139042 (Do not remove trivial `SwitchInt` in analysis MIR) - #139533 (add next_index to Enumerate) - #139843 (Setup editor file associations for non-rs extensions) - #140000 (skip llvm-config in autodiff check builds, when its unavailable) - #140008 (Improve `clean_maybe_renamed_item` function code a bit) - #140024 (Remove early exits from JumpThreading.) - #140039 (Add option for stable backport poll) r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
90fd16eb5b
29 changed files with 363 additions and 149 deletions
|
@ -818,8 +818,8 @@ fn test_unstable_options_tracking_hash() {
|
|||
tracked!(min_function_alignment, Some(Align::EIGHT));
|
||||
tracked!(mir_emit_retag, true);
|
||||
tracked!(mir_enable_passes, vec![("DestProp".to_string(), false)]);
|
||||
tracked!(mir_keep_place_mention, true);
|
||||
tracked!(mir_opt_level, Some(4));
|
||||
tracked!(mir_preserve_ub, true);
|
||||
tracked!(move_size_limit, Some(4096));
|
||||
tracked!(mutable_noalias, false);
|
||||
tracked!(next_solver, NextSolverConfig { coherence: true, globally: true });
|
||||
|
|
|
@ -223,7 +223,7 @@ impl<'tcx> crate::MirPass<'tcx> for EarlyOtherwiseBranch {
|
|||
// Since this optimization adds new basic blocks and invalidates others,
|
||||
// clean up the cfg to make it nicer for other passes
|
||||
if should_cleanup {
|
||||
simplify_cfg(body);
|
||||
simplify_cfg(tcx, body);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ impl<'tcx> crate::MirPass<'tcx> for Inline {
|
|||
let _guard = span.enter();
|
||||
if inline::<NormalInliner<'tcx>>(tcx, body) {
|
||||
debug!("running simplify cfg on {:?}", body.source);
|
||||
simplify_cfg(body);
|
||||
simplify_cfg(tcx, body);
|
||||
deref_finder(tcx, body);
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ impl<'tcx> crate::MirPass<'tcx> for ForceInline {
|
|||
let _guard = span.enter();
|
||||
if inline::<ForceInliner<'tcx>>(tcx, body) {
|
||||
debug!("running simplify cfg on {:?}", body.source);
|
||||
simplify_cfg(body);
|
||||
simplify_cfg(tcx, body);
|
||||
deref_finder(tcx, body);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,11 +90,7 @@ impl<'tcx> crate::MirPass<'tcx> for JumpThreading {
|
|||
};
|
||||
|
||||
for bb in body.basic_blocks.indices() {
|
||||
let old_len = finder.opportunities.len();
|
||||
// If we have any const-eval errors discard any opportunities found
|
||||
if finder.start_from_switch(bb).is_none() {
|
||||
finder.opportunities.truncate(old_len);
|
||||
}
|
||||
finder.start_from_switch(bb);
|
||||
}
|
||||
|
||||
let opportunities = finder.opportunities;
|
||||
|
@ -201,28 +197,26 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> {
|
|||
|
||||
/// Recursion entry point to find threading opportunities.
|
||||
#[instrument(level = "trace", skip(self))]
|
||||
fn start_from_switch(&mut self, bb: BasicBlock) -> Option<()> {
|
||||
fn start_from_switch(&mut self, bb: BasicBlock) {
|
||||
let bbdata = &self.body[bb];
|
||||
if bbdata.is_cleanup || self.loop_headers.contains(bb) {
|
||||
return Some(());
|
||||
return;
|
||||
}
|
||||
let Some((discr, targets)) = bbdata.terminator().kind.as_switch() else { return Some(()) };
|
||||
let Some(discr) = discr.place() else { return Some(()) };
|
||||
let Some((discr, targets)) = bbdata.terminator().kind.as_switch() else { return };
|
||||
let Some(discr) = discr.place() else { return };
|
||||
debug!(?discr, ?bb);
|
||||
|
||||
let discr_ty = discr.ty(self.body, self.tcx).ty;
|
||||
let Ok(discr_layout) = self.ecx.layout_of(discr_ty) else {
|
||||
return Some(());
|
||||
};
|
||||
let Ok(discr_layout) = self.ecx.layout_of(discr_ty) else { return };
|
||||
|
||||
let Some(discr) = self.map.find(discr.as_ref()) else { return Some(()) };
|
||||
let Some(discr) = self.map.find(discr.as_ref()) else { return };
|
||||
debug!(?discr);
|
||||
|
||||
let cost = CostChecker::new(self.tcx, self.typing_env, None, self.body);
|
||||
let mut state = State::new_reachable();
|
||||
|
||||
let conds = if let Some((value, then, else_)) = targets.as_static_if() {
|
||||
let value = ScalarInt::try_from_uint(value, discr_layout.size)?;
|
||||
let Some(value) = ScalarInt::try_from_uint(value, discr_layout.size) else { return };
|
||||
self.arena.alloc_from_iter([
|
||||
Condition { value, polarity: Polarity::Eq, target: then },
|
||||
Condition { value, polarity: Polarity::Ne, target: else_ },
|
||||
|
@ -248,10 +242,10 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> {
|
|||
mut state: State<ConditionSet<'a>>,
|
||||
mut cost: CostChecker<'_, 'tcx>,
|
||||
depth: usize,
|
||||
) -> Option<()> {
|
||||
) {
|
||||
// Do not thread through loop headers.
|
||||
if self.loop_headers.contains(bb) {
|
||||
return Some(());
|
||||
return;
|
||||
}
|
||||
|
||||
debug!(cost = ?cost.cost());
|
||||
|
@ -259,16 +253,16 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> {
|
|||
self.body.basic_blocks[bb].statements.iter().enumerate().rev()
|
||||
{
|
||||
if self.is_empty(&state) {
|
||||
return Some(());
|
||||
return;
|
||||
}
|
||||
|
||||
cost.visit_statement(stmt, Location { block: bb, statement_index });
|
||||
if cost.cost() > MAX_COST {
|
||||
return Some(());
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt to turn the `current_condition` on `lhs` into a condition on another place.
|
||||
self.process_statement(bb, stmt, &mut state)?;
|
||||
self.process_statement(bb, stmt, &mut state);
|
||||
|
||||
// When a statement mutates a place, assignments to that place that happen
|
||||
// above the mutation cannot fulfill a condition.
|
||||
|
@ -280,7 +274,7 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> {
|
|||
}
|
||||
|
||||
if self.is_empty(&state) || depth >= MAX_BACKTRACK {
|
||||
return Some(());
|
||||
return;
|
||||
}
|
||||
|
||||
let last_non_rec = self.opportunities.len();
|
||||
|
@ -293,9 +287,9 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> {
|
|||
match term.kind {
|
||||
TerminatorKind::SwitchInt { ref discr, ref targets } => {
|
||||
self.process_switch_int(discr, targets, bb, &mut state);
|
||||
self.find_opportunity(pred, state, cost, depth + 1)?;
|
||||
self.find_opportunity(pred, state, cost, depth + 1);
|
||||
}
|
||||
_ => self.recurse_through_terminator(pred, || state, &cost, depth)?,
|
||||
_ => self.recurse_through_terminator(pred, || state, &cost, depth),
|
||||
}
|
||||
} else if let &[ref predecessors @ .., last_pred] = &predecessors[..] {
|
||||
for &pred in predecessors {
|
||||
|
@ -320,13 +314,12 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> {
|
|||
let first = &mut new_tos[0];
|
||||
*first = ThreadingOpportunity { chain: vec![bb], target: first.target };
|
||||
self.opportunities.truncate(last_non_rec + 1);
|
||||
return Some(());
|
||||
return;
|
||||
}
|
||||
|
||||
for op in self.opportunities[last_non_rec..].iter_mut() {
|
||||
op.chain.push(bb);
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Extract the mutated place from a statement.
|
||||
|
@ -440,23 +433,23 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> {
|
|||
lhs: PlaceIndex,
|
||||
rhs: &Operand<'tcx>,
|
||||
state: &mut State<ConditionSet<'a>>,
|
||||
) -> Option<()> {
|
||||
) {
|
||||
match rhs {
|
||||
// If we expect `lhs ?= A`, we have an opportunity if we assume `constant == A`.
|
||||
Operand::Constant(constant) => {
|
||||
let constant = self
|
||||
.ecx
|
||||
.eval_mir_constant(&constant.const_, constant.span, None)
|
||||
.discard_err()?;
|
||||
let Some(constant) =
|
||||
self.ecx.eval_mir_constant(&constant.const_, constant.span, None).discard_err()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
self.process_constant(bb, lhs, constant, state);
|
||||
}
|
||||
// Transfer the conditions on the copied rhs.
|
||||
Operand::Move(rhs) | Operand::Copy(rhs) => {
|
||||
let Some(rhs) = self.map.find(rhs.as_ref()) else { return Some(()) };
|
||||
let Some(rhs) = self.map.find(rhs.as_ref()) else { return };
|
||||
state.insert_place_idx(rhs, lhs, &self.map);
|
||||
}
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self))]
|
||||
|
@ -466,18 +459,14 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> {
|
|||
lhs_place: &Place<'tcx>,
|
||||
rhs: &Rvalue<'tcx>,
|
||||
state: &mut State<ConditionSet<'a>>,
|
||||
) -> Option<()> {
|
||||
let Some(lhs) = self.map.find(lhs_place.as_ref()) else {
|
||||
return Some(());
|
||||
};
|
||||
) {
|
||||
let Some(lhs) = self.map.find(lhs_place.as_ref()) else { return };
|
||||
match rhs {
|
||||
Rvalue::Use(operand) => self.process_operand(bb, lhs, operand, state)?,
|
||||
Rvalue::Use(operand) => self.process_operand(bb, lhs, operand, state),
|
||||
// Transfer the conditions on the copy rhs.
|
||||
Rvalue::CopyForDeref(rhs) => {
|
||||
self.process_operand(bb, lhs, &Operand::Copy(*rhs), state)?
|
||||
}
|
||||
Rvalue::CopyForDeref(rhs) => self.process_operand(bb, lhs, &Operand::Copy(*rhs), state),
|
||||
Rvalue::Discriminant(rhs) => {
|
||||
let Some(rhs) = self.map.find_discr(rhs.as_ref()) else { return Some(()) };
|
||||
let Some(rhs) = self.map.find_discr(rhs.as_ref()) else { return };
|
||||
state.insert_place_idx(rhs, lhs, &self.map);
|
||||
}
|
||||
// If we expect `lhs ?= A`, we have an opportunity if we assume `constant == A`.
|
||||
|
@ -485,7 +474,7 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> {
|
|||
let agg_ty = lhs_place.ty(self.body, self.tcx).ty;
|
||||
let lhs = match kind {
|
||||
// Do not support unions.
|
||||
AggregateKind::Adt(.., Some(_)) => return Some(()),
|
||||
AggregateKind::Adt(.., Some(_)) => return,
|
||||
AggregateKind::Adt(_, variant_index, ..) if agg_ty.is_enum() => {
|
||||
if let Some(discr_target) = self.map.apply(lhs, TrackElem::Discriminant)
|
||||
&& let Some(discr_value) = self
|
||||
|
@ -498,23 +487,23 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> {
|
|||
if let Some(idx) = self.map.apply(lhs, TrackElem::Variant(*variant_index)) {
|
||||
idx
|
||||
} else {
|
||||
return Some(());
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => lhs,
|
||||
};
|
||||
for (field_index, operand) in operands.iter_enumerated() {
|
||||
if let Some(field) = self.map.apply(lhs, TrackElem::Field(field_index)) {
|
||||
self.process_operand(bb, field, operand, state)?;
|
||||
self.process_operand(bb, field, operand, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Transfer the conditions on the copy rhs, after inverting the value of the condition.
|
||||
Rvalue::UnaryOp(UnOp::Not, Operand::Move(place) | Operand::Copy(place)) => {
|
||||
let layout = self.ecx.layout_of(place.ty(self.body, self.tcx).ty).unwrap();
|
||||
let Some(conditions) = state.try_get_idx(lhs, &self.map) else { return Some(()) };
|
||||
let Some(place) = self.map.find(place.as_ref()) else { return Some(()) };
|
||||
let conds = conditions.map(self.arena, |mut cond| {
|
||||
let Some(conditions) = state.try_get_idx(lhs, &self.map) else { return };
|
||||
let Some(place) = self.map.find(place.as_ref()) else { return };
|
||||
let Some(conds) = conditions.map(self.arena, |mut cond| {
|
||||
cond.value = self
|
||||
.ecx
|
||||
.unary_op(UnOp::Not, &ImmTy::from_scalar_int(cond.value, layout))
|
||||
|
@ -522,7 +511,9 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> {
|
|||
.to_scalar_int()
|
||||
.discard_err()?;
|
||||
Some(cond)
|
||||
})?;
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
state.insert_value_idx(place, conds, &self.map);
|
||||
}
|
||||
// We expect `lhs ?= A`. We found `lhs = Eq(rhs, B)`.
|
||||
|
@ -532,34 +523,38 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> {
|
|||
box (Operand::Move(place) | Operand::Copy(place), Operand::Constant(value))
|
||||
| box (Operand::Constant(value), Operand::Move(place) | Operand::Copy(place)),
|
||||
) => {
|
||||
let Some(conditions) = state.try_get_idx(lhs, &self.map) else { return Some(()) };
|
||||
let Some(place) = self.map.find(place.as_ref()) else { return Some(()) };
|
||||
let Some(conditions) = state.try_get_idx(lhs, &self.map) else { return };
|
||||
let Some(place) = self.map.find(place.as_ref()) else { return };
|
||||
let equals = match op {
|
||||
BinOp::Eq => ScalarInt::TRUE,
|
||||
BinOp::Ne => ScalarInt::FALSE,
|
||||
_ => return Some(()),
|
||||
_ => return,
|
||||
};
|
||||
if value.const_.ty().is_floating_point() {
|
||||
// Floating point equality does not follow bit-patterns.
|
||||
// -0.0 and NaN both have special rules for equality,
|
||||
// and therefore we cannot use integer comparisons for them.
|
||||
// Avoid handling them, though this could be extended in the future.
|
||||
return Some(());
|
||||
return;
|
||||
}
|
||||
let value = value.const_.try_eval_scalar_int(self.tcx, self.typing_env)?;
|
||||
let conds = conditions.map(self.arena, |c| {
|
||||
let Some(value) = value.const_.try_eval_scalar_int(self.tcx, self.typing_env)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(conds) = conditions.map(self.arena, |c| {
|
||||
Some(Condition {
|
||||
value,
|
||||
polarity: if c.matches(equals) { Polarity::Eq } else { Polarity::Ne },
|
||||
..c
|
||||
})
|
||||
})?;
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
state.insert_value_idx(place, conds, &self.map);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self))]
|
||||
|
@ -568,7 +563,7 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> {
|
|||
bb: BasicBlock,
|
||||
stmt: &Statement<'tcx>,
|
||||
state: &mut State<ConditionSet<'a>>,
|
||||
) -> Option<()> {
|
||||
) {
|
||||
let register_opportunity = |c: Condition| {
|
||||
debug!(?bb, ?c.target, "register");
|
||||
self.opportunities.push(ThreadingOpportunity { chain: vec![bb], target: c.target })
|
||||
|
@ -581,32 +576,30 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> {
|
|||
// If we expect `discriminant(place) ?= A`,
|
||||
// we have an opportunity if `variant_index ?= A`.
|
||||
StatementKind::SetDiscriminant { box place, variant_index } => {
|
||||
let Some(discr_target) = self.map.find_discr(place.as_ref()) else {
|
||||
return Some(());
|
||||
};
|
||||
let Some(discr_target) = self.map.find_discr(place.as_ref()) else { return };
|
||||
let enum_ty = place.ty(self.body, self.tcx).ty;
|
||||
// `SetDiscriminant` guarantees that the discriminant is now `variant_index`.
|
||||
// Even if the discriminant write does nothing due to niches, it is UB to set the
|
||||
// discriminant when the data does not encode the desired discriminant.
|
||||
let discr =
|
||||
self.ecx.discriminant_for_variant(enum_ty, *variant_index).discard_err()?;
|
||||
self.process_immediate(bb, discr_target, discr, state);
|
||||
let Some(discr) =
|
||||
self.ecx.discriminant_for_variant(enum_ty, *variant_index).discard_err()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
self.process_immediate(bb, discr_target, discr, state)
|
||||
}
|
||||
// If we expect `lhs ?= true`, we have an opportunity if we assume `lhs == true`.
|
||||
StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(
|
||||
Operand::Copy(place) | Operand::Move(place),
|
||||
)) => {
|
||||
let Some(conditions) = state.try_get(place.as_ref(), &self.map) else {
|
||||
return Some(());
|
||||
};
|
||||
conditions.iter_matches(ScalarInt::TRUE).for_each(register_opportunity);
|
||||
let Some(conditions) = state.try_get(place.as_ref(), &self.map) else { return };
|
||||
conditions.iter_matches(ScalarInt::TRUE).for_each(register_opportunity)
|
||||
}
|
||||
StatementKind::Assign(box (lhs_place, rhs)) => {
|
||||
self.process_assign(bb, lhs_place, rhs, state)?;
|
||||
self.process_assign(bb, lhs_place, rhs, state)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self, state, cost))]
|
||||
|
@ -617,7 +610,7 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> {
|
|||
state: impl FnOnce() -> State<ConditionSet<'a>>,
|
||||
cost: &CostChecker<'_, 'tcx>,
|
||||
depth: usize,
|
||||
) -> Option<()> {
|
||||
) {
|
||||
let term = self.body.basic_blocks[bb].terminator();
|
||||
let place_to_flood = match term.kind {
|
||||
// We come from a target, so those are not possible.
|
||||
|
@ -632,9 +625,9 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> {
|
|||
| TerminatorKind::FalseUnwind { .. }
|
||||
| TerminatorKind::Yield { .. } => bug!("{term:?} invalid"),
|
||||
// Cannot reason about inline asm.
|
||||
TerminatorKind::InlineAsm { .. } => return Some(()),
|
||||
TerminatorKind::InlineAsm { .. } => return,
|
||||
// `SwitchInt` is handled specially.
|
||||
TerminatorKind::SwitchInt { .. } => return Some(()),
|
||||
TerminatorKind::SwitchInt { .. } => return,
|
||||
// We can recurse, no thing particular to do.
|
||||
TerminatorKind::Goto { .. } => None,
|
||||
// Flood the overwritten place, and progress through.
|
||||
|
|
|
@ -43,7 +43,7 @@ impl<'tcx> crate::MirPass<'tcx> for MatchBranchSimplification {
|
|||
}
|
||||
|
||||
if should_cleanup {
|
||||
simplify_cfg(body);
|
||||
simplify_cfg(tcx, body);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ pub(super) struct RemovePlaceMention;
|
|||
|
||||
impl<'tcx> crate::MirPass<'tcx> for RemovePlaceMention {
|
||||
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
|
||||
!sess.opts.unstable_opts.mir_keep_place_mention
|
||||
!sess.opts.unstable_opts.mir_preserve_ub
|
||||
}
|
||||
|
||||
fn run_pass(&self, _: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||
|
|
|
@ -35,7 +35,7 @@ impl<'tcx> crate::MirPass<'tcx> for RemoveUnneededDrops {
|
|||
// if we applied optimizations, we potentially have some cfg to cleanup to
|
||||
// make it easier for further passes
|
||||
if should_simplify {
|
||||
simplify_cfg(body);
|
||||
simplify_cfg(tcx, body);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,13 @@
|
|||
//! Here the block (`{ return; }`) has the return type `char`, rather than `()`, but the MIR we
|
||||
//! naively generate still contains the `_a = ()` write in the unreachable block "after" the
|
||||
//! return.
|
||||
//!
|
||||
//! **WARNING**: This is one of the few optimizations that runs on built and analysis MIR, and
|
||||
//! so its effects may affect the type-checking, borrow-checking, and other analysis of MIR.
|
||||
//! We must be extremely careful to only apply optimizations that preserve UB and all
|
||||
//! non-determinism, since changes here can affect which programs compile in an insta-stable way.
|
||||
//! The normal logic that a program with UB can be changed to do anything does not apply to
|
||||
//! pre-"runtime" MIR!
|
||||
|
||||
use rustc_index::{Idx, IndexSlice, IndexVec};
|
||||
use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor};
|
||||
|
@ -66,8 +73,8 @@ impl SimplifyCfg {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn simplify_cfg(body: &mut Body<'_>) {
|
||||
CfgSimplifier::new(body).simplify();
|
||||
pub(super) fn simplify_cfg<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||
CfgSimplifier::new(tcx, body).simplify();
|
||||
remove_dead_blocks(body);
|
||||
|
||||
// FIXME: Should probably be moved into some kind of pass manager
|
||||
|
@ -79,9 +86,9 @@ impl<'tcx> crate::MirPass<'tcx> for SimplifyCfg {
|
|||
self.name()
|
||||
}
|
||||
|
||||
fn run_pass(&self, _: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||
debug!("SimplifyCfg({:?}) - simplifying {:?}", self.name(), body.source);
|
||||
simplify_cfg(body);
|
||||
simplify_cfg(tcx, body);
|
||||
}
|
||||
|
||||
fn is_required(&self) -> bool {
|
||||
|
@ -90,12 +97,13 @@ impl<'tcx> crate::MirPass<'tcx> for SimplifyCfg {
|
|||
}
|
||||
|
||||
struct CfgSimplifier<'a, 'tcx> {
|
||||
preserve_switch_reads: bool,
|
||||
basic_blocks: &'a mut IndexSlice<BasicBlock, BasicBlockData<'tcx>>,
|
||||
pred_count: IndexVec<BasicBlock, u32>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> {
|
||||
fn new(body: &'a mut Body<'tcx>) -> Self {
|
||||
fn new(tcx: TyCtxt<'tcx>, body: &'a mut Body<'tcx>) -> Self {
|
||||
let mut pred_count = IndexVec::from_elem(0u32, &body.basic_blocks);
|
||||
|
||||
// we can't use mir.predecessors() here because that counts
|
||||
|
@ -110,9 +118,12 @@ impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
// Preserve `SwitchInt` reads on built and analysis MIR, or if `-Zmir-preserve-ub`.
|
||||
let preserve_switch_reads = matches!(body.phase, MirPhase::Built | MirPhase::Analysis(_))
|
||||
|| tcx.sess.opts.unstable_opts.mir_preserve_ub;
|
||||
let basic_blocks = body.basic_blocks_mut();
|
||||
|
||||
CfgSimplifier { basic_blocks, pred_count }
|
||||
CfgSimplifier { preserve_switch_reads, basic_blocks, pred_count }
|
||||
}
|
||||
|
||||
fn simplify(mut self) {
|
||||
|
@ -253,9 +264,15 @@ impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> {
|
|||
|
||||
// turn a branch with all successors identical to a goto
|
||||
fn simplify_branch(&mut self, terminator: &mut Terminator<'tcx>) -> bool {
|
||||
match terminator.kind {
|
||||
TerminatorKind::SwitchInt { .. } => {}
|
||||
_ => return false,
|
||||
// Removing a `SwitchInt` terminator may remove reads that result in UB,
|
||||
// so we must not apply this optimization before borrowck or when
|
||||
// `-Zmir-preserve-ub` is set.
|
||||
if self.preserve_switch_reads {
|
||||
return false;
|
||||
}
|
||||
|
||||
let TerminatorKind::SwitchInt { .. } = terminator.kind else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let first_succ = {
|
||||
|
|
|
@ -2322,12 +2322,12 @@ options! {
|
|||
mir_include_spans: MirIncludeSpans = (MirIncludeSpans::default(), parse_mir_include_spans, [UNTRACKED],
|
||||
"include extra comments in mir pretty printing, like line numbers and statement indices, \
|
||||
details about types, etc. (boolean for all passes, 'nll' to enable in NLL MIR only, default: 'nll')"),
|
||||
mir_keep_place_mention: bool = (false, parse_bool, [TRACKED],
|
||||
"keep place mention MIR statements, interpreted e.g., by miri; implies -Zmir-opt-level=0 \
|
||||
(default: no)"),
|
||||
#[rustc_lint_opt_deny_field_access("use `Session::mir_opt_level` instead of this field")]
|
||||
mir_opt_level: Option<usize> = (None, parse_opt_number, [TRACKED],
|
||||
"MIR optimization level (0-4; default: 1 in non optimized builds and 2 in optimized builds)"),
|
||||
mir_preserve_ub: bool = (false, parse_bool, [TRACKED],
|
||||
"keep place mention statements and reads in trivial SwitchInt terminators, which are interpreted \
|
||||
e.g., by miri; implies -Zmir-opt-level=0 (default: no)"),
|
||||
mir_strip_debuginfo: MirStripDebugInfo = (MirStripDebugInfo::None, parse_mir_strip_debuginfo, [TRACKED],
|
||||
"Whether to remove some of the MIR debug info from methods. Default: None"),
|
||||
move_size_limit: Option<usize> = (None, parse_opt_number, [TRACKED],
|
||||
|
|
|
@ -23,6 +23,39 @@ impl<I> Enumerate<I> {
|
|||
pub(in crate::iter) fn new(iter: I) -> Enumerate<I> {
|
||||
Enumerate { iter, count: 0 }
|
||||
}
|
||||
|
||||
/// Retrieve the current position of the iterator.
|
||||
///
|
||||
/// If the iterator has not advanced, the position returned will be 0.
|
||||
///
|
||||
/// The position may also exceed the bounds of the iterator to allow for calculating
|
||||
/// the displacement of the iterator from following calls to [`Iterator::next`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(next_index)]
|
||||
///
|
||||
/// let arr = ['a', 'b'];
|
||||
///
|
||||
/// let mut iter = arr.iter().enumerate();
|
||||
///
|
||||
/// assert_eq!(iter.next_index(), 0);
|
||||
/// assert_eq!(iter.next(), Some((0, &'a')));
|
||||
///
|
||||
/// assert_eq!(iter.next_index(), 1);
|
||||
/// assert_eq!(iter.next_index(), 1);
|
||||
/// assert_eq!(iter.next(), Some((1, &'b')));
|
||||
///
|
||||
/// assert_eq!(iter.next_index(), 2);
|
||||
/// assert_eq!(iter.next(), None);
|
||||
/// assert_eq!(iter.next_index(), 2);
|
||||
/// ```
|
||||
#[inline]
|
||||
#[unstable(feature = "next_index", issue = "130711")]
|
||||
pub fn next_index(&self) -> usize {
|
||||
self.count
|
||||
}
|
||||
}
|
||||
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
|
|
|
@ -120,3 +120,13 @@ fn test_double_ended_enumerate() {
|
|||
assert_eq!(it.next_back(), Some((2, 3)));
|
||||
assert_eq!(it.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_iterator_enumerate_next_index() {
|
||||
let mut it = empty::<i32>().enumerate();
|
||||
assert_eq!(it.next_index(), 0);
|
||||
assert_eq!(it.next_index(), 0);
|
||||
assert_eq!(it.next(), None);
|
||||
assert_eq!(it.next_index(), 0);
|
||||
assert_eq!(it.next_index(), 0);
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
#![feature(maybe_uninit_write_slice)]
|
||||
#![feature(min_specialization)]
|
||||
#![feature(never_type)]
|
||||
#![feature(next_index)]
|
||||
#![feature(numfmt)]
|
||||
#![feature(pattern)]
|
||||
#![feature(pointer_is_aligned_to)]
|
||||
|
|
|
@ -1194,8 +1194,7 @@ pub fn rustc_cargo(
|
|||
let enzyme_dir = builder.build.out.join(arch).join("enzyme").join("lib");
|
||||
cargo.rustflag("-L").rustflag(enzyme_dir.to_str().expect("Invalid path"));
|
||||
|
||||
if !builder.config.dry_run() {
|
||||
let llvm_config = builder.llvm_config(builder.config.build).unwrap();
|
||||
if let Some(llvm_config) = builder.llvm_config(builder.config.build) {
|
||||
let llvm_version_major = llvm::get_llvm_version_major(builder, &llvm_config);
|
||||
cargo.rustflag("-l").rustflag(&format!("Enzyme-{llvm_version_major}"));
|
||||
}
|
||||
|
|
|
@ -584,6 +584,7 @@ Select which editor you would like to set up [default: None]: ";
|
|||
"51068d4747a13732440d1a8b8f432603badb1864fa431d83d0fd4f8fa57039e0",
|
||||
"d29af4d949bbe2371eac928a3c31cf9496b1701aa1c45f11cd6c759865ad5c45",
|
||||
"b5dd299b93dca3ceeb9b335f929293cb3d4bf4977866fbe7ceeac2a8a9f99088",
|
||||
"631c837b0e98ae35fd48b0e5f743b1ca60adadf2d0a2b23566ba25df372cf1a9",
|
||||
],
|
||||
EditorKind::Helix => &[
|
||||
"2d3069b8cf1b977e5d4023965eb6199597755e6c96c185ed5f2854f98b83d233",
|
||||
|
@ -602,10 +603,12 @@ Select which editor you would like to set up [default: None]: ";
|
|||
"4eecb58a2168b252077369da446c30ed0e658301efe69691979d1ef0443928f4",
|
||||
"c394386e6133bbf29ffd32c8af0bb3d4aac354cba9ee051f29612aa9350f8f8d",
|
||||
"e53e9129ca5ee5dcbd6ec8b68c2d87376474eb154992deba3c6d9ab1703e0717",
|
||||
"f954316090936c7e590c253ca9d524008375882fa13c5b41d7e2547a896ff893",
|
||||
],
|
||||
EditorKind::Zed => &[
|
||||
"bbce727c269d1bd0c98afef4d612eb4ce27aea3c3a8968c5f10b31affbc40b6c",
|
||||
"a5380cf5dd9328731aecc5dfb240d16dac46ed272126b9728006151ef42f5909",
|
||||
"2e96bf0d443852b12f016c8fc9840ab3d0a2b4fe0b0fb3a157e8d74d5e7e0e26",
|
||||
],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,10 +8,11 @@
|
|||
"check"
|
||||
"--json-output"])
|
||||
:linkedProjects ["Cargo.toml"
|
||||
"src/bootstrap/Cargo.toml"
|
||||
"src/tools/rust-analyzer/Cargo.toml"
|
||||
"compiler/rustc_codegen_cranelift/Cargo.toml"
|
||||
"compiler/rustc_codegen_gcc/Cargo.toml"]
|
||||
"compiler/rustc_codegen_gcc/Cargo.toml"
|
||||
"library/Cargo.toml"
|
||||
"src/bootstrap/Cargo.toml"
|
||||
"src/tools/rust-analyzer/Cargo.toml"]
|
||||
:rustfmt ( :overrideCommand ["build/host/rustfmt/bin/rustfmt"
|
||||
"--edition=2021"])
|
||||
:procMacro ( :server "build/host/stage0/libexec/rust-analyzer-proc-macro-srv"
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
],
|
||||
"rust-analyzer.linkedProjects": [
|
||||
"Cargo.toml",
|
||||
"compiler/rustc_codegen_cranelift/Cargo.toml",
|
||||
"compiler/rustc_codegen_gcc/Cargo.toml",
|
||||
"library/Cargo.toml",
|
||||
"src/bootstrap/Cargo.toml",
|
||||
"src/tools/rust-analyzer/Cargo.toml",
|
||||
"compiler/rustc_codegen_cranelift/Cargo.toml",
|
||||
"compiler/rustc_codegen_gcc/Cargo.toml"
|
||||
"src/tools/rust-analyzer/Cargo.toml"
|
||||
],
|
||||
"rust-analyzer.rustfmt.overrideCommand": [
|
||||
"${workspaceFolder}/build/host/rustfmt/bin/rustfmt",
|
||||
|
@ -36,5 +36,10 @@
|
|||
},
|
||||
"rust-analyzer.server.extraEnv": {
|
||||
"RUSTUP_TOOLCHAIN": "nightly"
|
||||
},
|
||||
"files.associations": {
|
||||
"*.fixed": "rust",
|
||||
"*.pp": "rust",
|
||||
"*.mir": "rust"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,15 +21,15 @@
|
|||
},
|
||||
"linkedProjects": [
|
||||
"Cargo.toml",
|
||||
"compiler/rustc_codegen_cranelift/Cargo.toml",
|
||||
"compiler/rustc_codegen_gcc/Cargo.toml",
|
||||
"library/Cargo.toml",
|
||||
"src/bootstrap/Cargo.toml",
|
||||
"src/tools/rust-analyzer/Cargo.toml",
|
||||
"compiler/rustc_codegen_cranelift/Cargo.toml",
|
||||
"compiler/rustc_codegen_gcc/Cargo.toml"
|
||||
"src/tools/rust-analyzer/Cargo.toml"
|
||||
],
|
||||
"procMacro": {
|
||||
"enable": true,
|
||||
"server": "${workspaceFolder}/build/host/stage0/libexec/rust-analyzer-proc-macro-srv"
|
||||
"enable": true,
|
||||
"server": "${workspaceFolder}/build/host/stage0/libexec/rust-analyzer-proc-macro-srv"
|
||||
},
|
||||
"rustc": {
|
||||
"source": "./Cargo.toml"
|
||||
|
@ -47,5 +47,8 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"file_types": {
|
||||
"Rust": ["fixed", "pp", "mir"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2773,10 +2773,35 @@ fn clean_maybe_renamed_item<'tcx>(
|
|||
) -> Vec<Item> {
|
||||
use hir::ItemKind;
|
||||
|
||||
let def_id = item.owner_id.to_def_id();
|
||||
let mut name = if renamed.is_some() { renamed } else { cx.tcx.hir_opt_name(item.hir_id()) };
|
||||
fn get_name(
|
||||
cx: &DocContext<'_>,
|
||||
item: &hir::Item<'_>,
|
||||
renamed: Option<Symbol>,
|
||||
) -> Option<Symbol> {
|
||||
renamed.or_else(|| cx.tcx.hir_opt_name(item.hir_id()))
|
||||
}
|
||||
|
||||
let def_id = item.owner_id.to_def_id();
|
||||
cx.with_param_env(def_id, |cx| {
|
||||
// These kinds of item either don't need a `name` or accept a `None` one so we handle them
|
||||
// before.
|
||||
match item.kind {
|
||||
ItemKind::Impl(impl_) => return clean_impl(impl_, item.owner_id.def_id, cx),
|
||||
ItemKind::Use(path, kind) => {
|
||||
return clean_use_statement(
|
||||
item,
|
||||
get_name(cx, item, renamed),
|
||||
path,
|
||||
kind,
|
||||
cx,
|
||||
&mut FxHashSet::default(),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let mut name = get_name(cx, item, renamed).unwrap();
|
||||
|
||||
let kind = match item.kind {
|
||||
ItemKind::Static(_, ty, mutability, body_id) => StaticItem(Static {
|
||||
type_: Box::new(clean_ty(ty, cx)),
|
||||
|
@ -2815,7 +2840,7 @@ fn clean_maybe_renamed_item<'tcx>(
|
|||
item_type: Some(type_),
|
||||
})),
|
||||
item.owner_id.def_id.to_def_id(),
|
||||
name.unwrap(),
|
||||
name,
|
||||
import_id,
|
||||
renamed,
|
||||
));
|
||||
|
@ -2838,17 +2863,14 @@ fn clean_maybe_renamed_item<'tcx>(
|
|||
generics: clean_generics(generics, cx),
|
||||
fields: variant_data.fields().iter().map(|x| clean_field(x, cx)).collect(),
|
||||
}),
|
||||
ItemKind::Impl(impl_) => return clean_impl(impl_, item.owner_id.def_id, cx),
|
||||
ItemKind::Macro(_, macro_def, MacroKind::Bang) => MacroItem(Macro {
|
||||
source: display_macro_source(cx, name.unwrap(), macro_def),
|
||||
source: display_macro_source(cx, name, macro_def),
|
||||
macro_rules: macro_def.macro_rules,
|
||||
}),
|
||||
ItemKind::Macro(_, _, macro_kind) => {
|
||||
clean_proc_macro(item, name.as_mut().unwrap(), macro_kind, cx)
|
||||
}
|
||||
ItemKind::Macro(_, _, macro_kind) => clean_proc_macro(item, &mut name, macro_kind, cx),
|
||||
// proc macros can have a name set by attributes
|
||||
ItemKind::Fn { ref sig, generics, body: body_id, .. } => {
|
||||
clean_fn_or_proc_macro(item, sig, generics, body_id, name.as_mut().unwrap(), cx)
|
||||
clean_fn_or_proc_macro(item, sig, generics, body_id, &mut name, cx)
|
||||
}
|
||||
ItemKind::Trait(_, _, _, generics, bounds, item_ids) => {
|
||||
let items = item_ids
|
||||
|
@ -2864,10 +2886,7 @@ fn clean_maybe_renamed_item<'tcx>(
|
|||
}))
|
||||
}
|
||||
ItemKind::ExternCrate(orig_name, _) => {
|
||||
return clean_extern_crate(item, name.unwrap(), orig_name, cx);
|
||||
}
|
||||
ItemKind::Use(path, kind) => {
|
||||
return clean_use_statement(item, name, path, kind, cx, &mut FxHashSet::default());
|
||||
return clean_extern_crate(item, name, orig_name, cx);
|
||||
}
|
||||
_ => span_bug!(item.span, "not yet converted"),
|
||||
};
|
||||
|
@ -2876,7 +2895,7 @@ fn clean_maybe_renamed_item<'tcx>(
|
|||
cx,
|
||||
kind,
|
||||
item.owner_id.def_id.to_def_id(),
|
||||
name.unwrap(),
|
||||
name,
|
||||
import_id,
|
||||
renamed,
|
||||
)]
|
||||
|
|
|
@ -169,7 +169,7 @@ pub const MIRI_DEFAULT_ARGS: &[&str] = &[
|
|||
"-Zalways-encode-mir",
|
||||
"-Zextra-const-ub-checks",
|
||||
"-Zmir-emit-retag",
|
||||
"-Zmir-keep-place-mention",
|
||||
"-Zmir-preserve-ub",
|
||||
"-Zmir-opt-level=0",
|
||||
"-Zmir-enable-passes=-CheckAlignment,-CheckNull",
|
||||
// Deduplicating diagnostics means we miss events when tracking what happens during an
|
||||
|
|
14
src/tools/miri/tests/fail/read_from_trivial_switch.rs
Normal file
14
src/tools/miri/tests/fail/read_from_trivial_switch.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
// Ensure that we don't optimize out `SwitchInt` reads even if that terminator
|
||||
// branches to the same basic block on every target, since the operand may have
|
||||
// side-effects that affect analysis of the MIR.
|
||||
//
|
||||
// See <https://github.com/rust-lang/miri/issues/4237>.
|
||||
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
fn main() {
|
||||
let uninit: MaybeUninit<i32> = MaybeUninit::uninit();
|
||||
let bad_ref: &i32 = unsafe { uninit.assume_init_ref() };
|
||||
let &(0 | _) = bad_ref;
|
||||
//~^ ERROR: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
|
||||
}
|
15
src/tools/miri/tests/fail/read_from_trivial_switch.stderr
Normal file
15
src/tools/miri/tests/fail/read_from_trivial_switch.stderr
Normal file
|
@ -0,0 +1,15 @@
|
|||
error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
|
||||
--> tests/fail/read_from_trivial_switch.rs:LL:CC
|
||||
|
|
||||
LL | let &(0 | _) = bad_ref;
|
||||
| ^^^^^^^^ using uninitialized data, but this operation requires initialized memory
|
||||
|
|
||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
= note: BACKTRACE:
|
||||
= note: inside `main` at tests/fail/read_from_trivial_switch.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
|
@ -24,43 +24,47 @@ fn match_tuple(_1: (u32, bool, Option<i32>, u32)) -> u32 {
|
|||
|
||||
bb1: {
|
||||
_0 = const 0_u32;
|
||||
goto -> bb10;
|
||||
goto -> bb11;
|
||||
}
|
||||
|
||||
bb2: {
|
||||
_2 = discriminant((_1.2: std::option::Option<i32>));
|
||||
switchInt(move _2) -> [0: bb4, 1: bb3, otherwise: bb1];
|
||||
switchInt(copy (_1.1: bool)) -> [0: bb3, otherwise: bb3];
|
||||
}
|
||||
|
||||
bb3: {
|
||||
switchInt(copy (((_1.2: std::option::Option<i32>) as Some).0: i32)) -> [1: bb4, 8: bb4, otherwise: bb1];
|
||||
_2 = discriminant((_1.2: std::option::Option<i32>));
|
||||
switchInt(move _2) -> [0: bb5, 1: bb4, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb4: {
|
||||
_5 = Le(const 6_u32, copy (_1.3: u32));
|
||||
switchInt(move _5) -> [0: bb5, otherwise: bb7];
|
||||
switchInt(copy (((_1.2: std::option::Option<i32>) as Some).0: i32)) -> [1: bb5, 8: bb5, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb5: {
|
||||
_3 = Le(const 13_u32, copy (_1.3: u32));
|
||||
switchInt(move _3) -> [0: bb1, otherwise: bb6];
|
||||
_5 = Le(const 6_u32, copy (_1.3: u32));
|
||||
switchInt(move _5) -> [0: bb6, otherwise: bb8];
|
||||
}
|
||||
|
||||
bb6: {
|
||||
_4 = Le(copy (_1.3: u32), const 16_u32);
|
||||
switchInt(move _4) -> [0: bb1, otherwise: bb8];
|
||||
_3 = Le(const 13_u32, copy (_1.3: u32));
|
||||
switchInt(move _3) -> [0: bb1, otherwise: bb7];
|
||||
}
|
||||
|
||||
bb7: {
|
||||
_6 = Le(copy (_1.3: u32), const 9_u32);
|
||||
switchInt(move _6) -> [0: bb5, otherwise: bb8];
|
||||
_4 = Le(copy (_1.3: u32), const 16_u32);
|
||||
switchInt(move _4) -> [0: bb1, otherwise: bb9];
|
||||
}
|
||||
|
||||
bb8: {
|
||||
falseEdge -> [real: bb9, imaginary: bb1];
|
||||
_6 = Le(copy (_1.3: u32), const 9_u32);
|
||||
switchInt(move _6) -> [0: bb6, otherwise: bb9];
|
||||
}
|
||||
|
||||
bb9: {
|
||||
falseEdge -> [real: bb10, imaginary: bb1];
|
||||
}
|
||||
|
||||
bb10: {
|
||||
StorageLive(_7);
|
||||
_7 = copy (_1.0: u32);
|
||||
StorageLive(_8);
|
||||
|
@ -74,10 +78,10 @@ fn match_tuple(_1: (u32, bool, Option<i32>, u32)) -> u32 {
|
|||
StorageDead(_9);
|
||||
StorageDead(_8);
|
||||
StorageDead(_7);
|
||||
goto -> bb10;
|
||||
goto -> bb11;
|
||||
}
|
||||
|
||||
bb10: {
|
||||
bb11: {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// and don't remove it as a dead store.
|
||||
//
|
||||
//@ test-mir-pass: DeadStoreElimination-initial
|
||||
//@ compile-flags: -Zmir-keep-place-mention
|
||||
//@ compile-flags: -Zmir-preserve-ub
|
||||
|
||||
// EMIT_MIR place_mention.main.DeadStoreElimination-initial.diff
|
||||
fn main() {
|
||||
|
|
|
@ -14,7 +14,7 @@ fn single_switchint() -> () {
|
|||
}
|
||||
|
||||
bb1: {
|
||||
switchInt(copy (_2.0: i32)) -> [3: bb8, 4: bb8, otherwise: bb7];
|
||||
switchInt(copy (_2.0: i32)) -> [3: bb9, 4: bb9, otherwise: bb8];
|
||||
}
|
||||
|
||||
bb2: {
|
||||
|
@ -22,7 +22,7 @@ fn single_switchint() -> () {
|
|||
}
|
||||
|
||||
bb3: {
|
||||
falseEdge -> [real: bb12, imaginary: bb4];
|
||||
falseEdge -> [real: bb14, imaginary: bb4];
|
||||
}
|
||||
|
||||
bb4: {
|
||||
|
@ -30,43 +30,51 @@ fn single_switchint() -> () {
|
|||
}
|
||||
|
||||
bb5: {
|
||||
falseEdge -> [real: bb11, imaginary: bb6];
|
||||
falseEdge -> [real: bb13, imaginary: bb6];
|
||||
}
|
||||
|
||||
bb6: {
|
||||
falseEdge -> [real: bb10, imaginary: bb1];
|
||||
switchInt(copy (_2.1: bool)) -> [0: bb7, otherwise: bb7];
|
||||
}
|
||||
|
||||
bb7: {
|
||||
_1 = const 5_i32;
|
||||
goto -> bb13;
|
||||
falseEdge -> [real: bb12, imaginary: bb1];
|
||||
}
|
||||
|
||||
bb8: {
|
||||
falseEdge -> [real: bb9, imaginary: bb7];
|
||||
_1 = const 5_i32;
|
||||
goto -> bb15;
|
||||
}
|
||||
|
||||
bb9: {
|
||||
_1 = const 4_i32;
|
||||
goto -> bb13;
|
||||
switchInt(copy (_2.1: bool)) -> [0: bb10, otherwise: bb10];
|
||||
}
|
||||
|
||||
bb10: {
|
||||
_1 = const 3_i32;
|
||||
goto -> bb13;
|
||||
falseEdge -> [real: bb11, imaginary: bb8];
|
||||
}
|
||||
|
||||
bb11: {
|
||||
_1 = const 2_i32;
|
||||
goto -> bb13;
|
||||
_1 = const 4_i32;
|
||||
goto -> bb15;
|
||||
}
|
||||
|
||||
bb12: {
|
||||
_1 = const 1_i32;
|
||||
goto -> bb13;
|
||||
_1 = const 3_i32;
|
||||
goto -> bb15;
|
||||
}
|
||||
|
||||
bb13: {
|
||||
_1 = const 2_i32;
|
||||
goto -> bb15;
|
||||
}
|
||||
|
||||
bb14: {
|
||||
_1 = const 1_i32;
|
||||
goto -> bb15;
|
||||
}
|
||||
|
||||
bb15: {
|
||||
StorageDead(_2);
|
||||
StorageDead(_1);
|
||||
_0 = const ();
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
- // MIR for `main` before SimplifyCfg-initial
|
||||
+ // MIR for `main` after SimplifyCfg-initial
|
||||
|
||||
fn main() -> () {
|
||||
let mut _0: ();
|
||||
let _1: &i32;
|
||||
let _2: i32;
|
||||
scope 1 {
|
||||
debug ref_ => _1;
|
||||
scope 2 {
|
||||
}
|
||||
}
|
||||
|
||||
bb0: {
|
||||
StorageLive(_1);
|
||||
StorageLive(_2);
|
||||
_2 = const 1_i32;
|
||||
_1 = &_2;
|
||||
FakeRead(ForLet(None), _1);
|
||||
PlaceMention(_1);
|
||||
- switchInt(copy (*_1)) -> [0: bb2, otherwise: bb1];
|
||||
+ switchInt(copy (*_1)) -> [0: bb1, otherwise: bb1];
|
||||
}
|
||||
|
||||
bb1: {
|
||||
- goto -> bb5;
|
||||
- }
|
||||
-
|
||||
- bb2: {
|
||||
- goto -> bb5;
|
||||
- }
|
||||
-
|
||||
- bb3: {
|
||||
- goto -> bb1;
|
||||
- }
|
||||
-
|
||||
- bb4: {
|
||||
- FakeRead(ForMatchedPlace(None), _1);
|
||||
- unreachable;
|
||||
- }
|
||||
-
|
||||
- bb5: {
|
||||
_0 = const ();
|
||||
StorageDead(_2);
|
||||
StorageDead(_1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
15
tests/mir-opt/read_from_trivial_switch.rs
Normal file
15
tests/mir-opt/read_from_trivial_switch.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
// Ensure that we don't optimize out `SwitchInt` reads even if that terminator
|
||||
// branches to the same basic block on every target, since the operand may have
|
||||
// side-effects that affect analysis of the MIR.
|
||||
//
|
||||
// See <https://github.com/rust-lang/miri/issues/4237>.
|
||||
|
||||
//@ test-mir-pass: SimplifyCfg-initial
|
||||
//@ compile-flags: -Zmir-preserve-ub
|
||||
|
||||
// EMIT_MIR read_from_trivial_switch.main.SimplifyCfg-initial.diff
|
||||
fn main() {
|
||||
let ref_ = &1i32;
|
||||
// CHECK: switchInt
|
||||
let &(0 | _) = ref_;
|
||||
}
|
8
tests/ui/pattern/uninit-trivial.rs
Normal file
8
tests/ui/pattern/uninit-trivial.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
// Regression test for the semantic changes in
|
||||
// <https://github.com/rust-lang/rust/pull/139042>.
|
||||
|
||||
fn main() {
|
||||
let x;
|
||||
let (0 | _) = x;
|
||||
//~^ ERROR used binding `x` isn't initialized
|
||||
}
|
16
tests/ui/pattern/uninit-trivial.stderr
Normal file
16
tests/ui/pattern/uninit-trivial.stderr
Normal file
|
@ -0,0 +1,16 @@
|
|||
error[E0381]: used binding `x` isn't initialized
|
||||
--> $DIR/uninit-trivial.rs:6:10
|
||||
|
|
||||
LL | let x;
|
||||
| - binding declared here but left uninitialized
|
||||
LL | let (0 | _) = x;
|
||||
| ^^^^^ `x` used here but it isn't initialized
|
||||
|
|
||||
help: consider assigning a value
|
||||
|
|
||||
LL | let x = 42;
|
||||
| ++++
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0381`.
|
|
@ -659,6 +659,7 @@ message_on_add = [
|
|||
"""\
|
||||
/poll Approve stable backport of #{number}?
|
||||
approve
|
||||
approve (but does not justify new dot release on its own)
|
||||
decline
|
||||
don't know
|
||||
""",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue