diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 0d584be4ad0b0..2f9489ac67ad4 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -851,6 +851,7 @@ fn test_unstable_options_tracking_hash() { tracked!(min_function_alignment, Some(Align::EIGHT)); tracked!(min_recursion_limit, Some(256)); tracked!(mir_enable_passes, vec![("DestProp".to_string(), false)]); + tracked!(mir_move_elimination, true); tracked!(mir_opt_level, Some(4)); tracked!(mir_preserve_ub, true); tracked!(move_size_limit, Some(4096)); diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index 95a708f125660..d0b9c686c3037 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -205,6 +205,7 @@ declare_passes! { mod sroa : ScalarReplacementOfAggregates; mod strip_debuginfo : StripDebugInfo; mod ssa_range_prop: SsaRangePropagation; + mod tail_copy_to_move : TailCopyToMove; mod unreachable_enum_branching : UnreachableEnumBranching; mod unreachable_prop : UnreachablePropagation; mod validate : Validator; @@ -761,6 +762,7 @@ pub(crate) fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<' ©_prop::CopyProp, &dead_store_elimination::DeadStoreElimination::Final, &dest_prop::DestinationPropagation, + &tail_copy_to_move::TailCopyToMove, &simplify::SimplifyLocals::Final, &multiple_return_terminators::MultipleReturnTerminators, // Some cleanup necessary at least for LLVM and potentially other codegen backends. diff --git a/compiler/rustc_mir_transform/src/tail_copy_to_move.rs b/compiler/rustc_mir_transform/src/tail_copy_to_move.rs new file mode 100644 index 0000000000000..e97844748cefb --- /dev/null +++ b/compiler/rustc_mir_transform/src/tail_copy_to_move.rs @@ -0,0 +1,258 @@ +//! Rewrite final-use copies before return into moves. +//! +//! # The problem +//! +//! MIR building represents reads of values whose type is `Copy` using +//! `Operand::Copy`, including when such a local is returned. If that local's +//! address has ever been observed, `MoveElimination` conservatively considers +//! it live until its `StorageDead` or function exit. This keeps the local live +//! across `_0 = copy local`, so its live range overlaps with the return place +//! and `MoveElimination` cannot unify the source local with `_0`. +//! +//! # The solution +//! +//! At function return, all local allocations are about to become invalid +//! anyway. After borrowck, this pass can therefore turn a final-use `Copy` into +//! a `Move`, as long as shortening the source local's live range has no +//! observable effect before the return happens. Concretely, between the +//! transformed copy (now a move) and the return, there may only be writes to +//! unborrowed locals, storage markers, nops, and gotos. +//! +//! # The algorithm +//! +//! Start from every `Return` terminator, with `_0` treated as used by the +//! return. Then scan predecessor blocks backward through `Goto` edges, forming +//! a return-tail tree. The scan maintains `used_after`, the set of locals +//! accessed later on that path. +//! +//! A `Copy` operand is rewritten to a `Move` when its base local is not in +//! `used_after`. Then any locals touched by that operand, including +//! index-projection locals, are added to `used_after` before the backward scan +//! continues. +//! +//! The scan stops when accessing an indirect place because that may access any +//! borrowed local, which would make the pass unable to prove any useful final +//! uses. It also stops at writes to borrowed locals, because those can create a +//! new address-observed allocation range whose overlap with an earlier borrowed +//! local must be preserved. + +use std::ops::ControlFlow; + +use rustc_index::bit_set::DenseBitSet; +use rustc_middle::mir::*; +use rustc_middle::ty::TyCtxt; +use rustc_mir_dataflow::impls::borrowed_locals; + +use crate::PassPolicy; + +pub(super) struct TailCopyToMove; + +impl<'tcx> crate::MirPass<'tcx> for TailCopyToMove { + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional( + ctx.mir_opt_level() >= 2 && ctx.opts.unstable_opts.mir_move_elimination, + ) + } + + #[tracing::instrument(level = "trace", skip(self, _tcx, body))] + fn run_pass(&self, _tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { + let borrowed = borrowed_locals(body); + let predecessors = body.basic_blocks.predecessors().clone(); + let mut stack = Vec::new(); + + // A return terminator implicitly uses the return place. Walking + // backward through assignments records the locals accessed later on + // this path. + for (bb, data) in body.basic_blocks.iter_enumerated() { + if matches!(data.terminator().kind, TerminatorKind::Return) { + let mut used_after = DenseBitSet::new_empty(body.local_decls.len()); + used_after.insert(RETURN_PLACE); + stack.push(TailState { block: bb, used_after }); + } + } + + while let Some(mut state) = stack.pop() { + // `scan_block` rewrites final-use copies in this block and updates + // `used_after` to the locals whose allocation is accessed after the + // block starts. If the block is not pure tail code, this path is + // done. + if scan_block(body, state.block, &mut state.used_after, &borrowed).is_break() { + continue; + } + + // Continue through predecessor blocks only when the predecessor's + // terminator is a plain `Goto` to this block. Other terminators are + // control-flow or effect boundaries. + let mut first = None; + for pred in predecessors[state.block].iter().copied() { + let terminator = body.basic_blocks[pred].terminator(); + if let TerminatorKind::Goto { target } = terminator.kind { + debug_assert_eq!(target, state.block); + if first.is_none() { + first = Some(pred); + } else { + stack.push(TailState { block: pred, used_after: state.used_after.clone() }); + } + } + } + + // Avoid cloning the bitset for the first predecessor. + if let Some(pred) = first { + stack.push(TailState { block: pred, used_after: state.used_after }); + } + } + } +} + +struct TailState { + block: BasicBlock, + used_after: DenseBitSet, +} + +/// Scan a block backward while the return-tail invariant still holds. +/// +/// The invariant is that a whole-local `Copy` can be changed to a `Move` only +/// if this path has no later access to that local's allocation before +/// returning, and no later operation whose observable behavior could depend on +/// ending an address-observed local's allocation early. `used_after` tracks +/// those later local-allocation accesses. +fn scan_block<'tcx>( + body: &mut Body<'tcx>, + block: BasicBlock, + used_after: &mut DenseBitSet, + borrowed: &DenseBitSet, +) -> ControlFlow<()> { + for statement in body.basic_blocks.as_mut_preserves_cfg()[block].statements.iter_mut().rev() { + match &mut statement.kind { + // Under the local lifetime semantics from RFC 3943, `StorageLive` + // does not allocate, and `StorageDead` has no effect if the local + // was already freed by a move. These markers therefore do not + // affect whether a copy can be treated as a final use. + StatementKind::StorageLive(_) | StatementKind::StorageDead(_) | StatementKind::Nop => {} + StatementKind::Assign((place, rhs)) => { + // Accessing an indirect place may touch any borrowed local, so + // continuing would require treating all borrowed locals as used + // after this point. + if place.is_indirect_first_projection() { + return ControlFlow::Break(()); + } + + // Writing to a borrowed local can start a new allocation range. + // Shortening an earlier borrowed local could remove an overlap + // with that new range. + if borrowed.contains(place.local) { + return ControlFlow::Break(()); + } + + // A destination write accesses the base local, and evaluating + // the destination may also access projection locals, such as an + // index. + record_place_locals(*place, used_after); + + // This pass only models `Use` and `Aggregate` rvalues whose + // operands are direct. Other rvalues are outside the + // conservative return-tail shape handled here. + process_rvalue(rhs, used_after)?; + } + StatementKind::SetDiscriminant { place, .. } => { + // Accessing an indirect place may touch any borrowed local, so + // continuing would require treating all borrowed locals as used + // after this point. + if place.is_indirect_first_projection() { + return ControlFlow::Break(()); + } + + // Writing to a borrowed local can start a new allocation range. + // Shortening an earlier borrowed local could remove an overlap + // with that new range. + if borrowed.contains(place.local) { + return ControlFlow::Break(()); + } + + // `SetDiscriminant` has a validity invariant on the rest of the + // place, so treat the base local as accessed along with any + // projection locals. + record_place_locals(**place, used_after); + } + _ => { + // Anything else may perform effects or evaluate places in ways + // this pass does not model, so it is not part of the pure + // return tail. + return ControlFlow::Break(()); + } + } + } + + ControlFlow::Continue(()) +} + +/// Records all locals used in a place, including `Index` projections in +/// `used_after`. +fn record_place_locals<'tcx>(place: Place<'tcx>, used_after: &mut DenseBitSet) { + for local in place.as_ref().accessed_locals() { + used_after.insert(local); + } +} + +/// Process the RHS of an assignment in a pure return tail. +fn process_rvalue<'tcx>( + rvalue: &mut Rvalue<'tcx>, + used_after: &mut DenseBitSet, +) -> ControlFlow<()> { + match rvalue { + Rvalue::Use(operand, _) => process_operand(operand, used_after), + Rvalue::Aggregate(_, operands) => { + // Operands are evaluated left-to-right. We scan them right-to-left + // so `used_after` includes uses later in the same statement. If an + // operand accesses an indirect place, only earlier operands and + // earlier statements are outside the pure tail. + for operand in operands.iter_mut().rev() { + process_operand(operand, used_after)?; + } + + ControlFlow::Continue(()) + } + _ => { + // This pass doesn't model other rvalues, so they are not part of + // the pure return tail. + ControlFlow::Break(()) + } + } +} + +/// Process one operand in an rvalue. +fn process_operand<'tcx>( + operand: &mut Operand<'tcx>, + used_after: &mut DenseBitSet, +) -> ControlFlow<()> { + let place = match operand { + Operand::Copy(place) | Operand::Move(place) if place.is_indirect_first_projection() => { + // Accessing an indirect place may touch any borrowed local. + // Continuing would require treating all borrowed locals as used + // after this point, which would prevent the useful copy-to-move + // rewrites this pass is looking for. + return ControlFlow::Break(()); + } + Operand::Copy(place) => { + let place = *place; + // No later operation in the scanned tail accesses this local's + // allocation, so this copy is a final use on the current return + // path and can be represented as a move. + if !used_after.contains(place.local) { + *operand = Operand::Move(place); + } + Some(place) + } + Operand::Move(place) => Some(*place), + Operand::Constant(_) | Operand::RuntimeChecks(_) => None, + }; + + if let Some(place) = place { + // Reading an operand place accesses its base local, and evaluating its + // projections may access additional locals, such as the index local in + // `place[index]`. + record_place_locals(place, used_after); + } + + ControlFlow::Continue(()) +} diff --git a/compiler/rustc_next_trait_solver/src/solve/mod.rs b/compiler/rustc_next_trait_solver/src/solve/mod.rs index dddc78373b1b5..924a705205e1c 100644 --- a/compiler/rustc_next_trait_solver/src/solve/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/mod.rs @@ -79,6 +79,34 @@ fn has_only_region_constraints(response: ty::Canonical( + a: &CanonicalResponse, + b: &CanonicalResponse, +) -> bool { + let CanonicalResponse { + max_universe: a_max_universe, + var_kinds: a_var_kinds, + value: + Response { + var_values: a_var_values, + certainty: a_certainty, + external_constraints: a_external_constraints, + }, + } = a; + + let ExternalConstraintsData { region_constraints: _, opaque_types, normalization_nested_goals } = + &**a_external_constraints; + + a_max_universe == &b.max_universe + && a_var_kinds == &b.var_kinds + && a_var_values == &b.value.var_values + && a_certainty == &b.value.certainty + && opaque_types == &b.value.external_constraints.opaque_types + && normalization_nested_goals == &b.value.external_constraints.normalization_nested_goals +} + impl<'a, D, I> EvalCtxt<'a, D> where D: SolverDelegate, @@ -318,13 +346,36 @@ where candidate.result.value.certainty == Certainty::Yes && has_no_inference_or_external_constraints(candidate.result) }); - if let Some((i, c)) = always_applicable { - return Some((c.result, MergeCandidateInfo::AlwaysApplicable(i))); + + if let Some((i, candidate)) = always_applicable { + return Some((candidate.result, MergeCandidateInfo::AlwaysApplicable(i))); } let one: CanonicalResponse = candidates[0].result; - if candidates[1..].iter().all(|candidate| candidate.result == one) { - return Some((one, MergeCandidateInfo::EqualResponse)); + + if candidates[1..] + .iter() + .all(|candidate| equal_response_modulo_region_constraints(&one, &candidate.result)) + { + let region_constraints = &one.value.external_constraints.region_constraints; + if candidates[1..].iter().all(|candidate| { + &candidate.result.value.external_constraints.region_constraints + == region_constraints + }) { + return Some((one, MergeCandidateInfo::EqualResponse)); + } + + // If candidates differ only in region constraints, their merged region + // constraints are an `Or` of their respective constraints. If one of them + // has no region constraints, the `Or` constraint evaluates to `true`. + // + // This is a special case of `-Zassumptions-on-binders` and should be + // replaced eventually. + if let Some(candidate) = candidates.iter().find(|candidate| { + candidate.result.value.external_constraints.region_constraints.is_empty() + }) { + return Some((candidate.result, MergeCandidateInfo::EqualResponse)); + } } None diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index d91c1eceb7db1..4c153cbb8c031 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2687,6 +2687,8 @@ 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_move_elimination: bool = (false, parse_bool, [TRACKED], + "enable the experimental MIR move elimination pass (default: no)"), mir_opt_bisect_limit: Option = (None, parse_opt_number, [TRACKED], "limit the number of MIR optimization pass executions (global across all bodies). \ Pass executions after this limit are skipped and reported. (default: no limit)"), diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 2760cd6b13f2a..29c1aad06e98c 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -3094,6 +3094,10 @@ fn test_dir_rename_file() { assert_eq!(b"bar", &buf); } +// FIXME: re-enable once QNX fixes TOCTOU bug for fs::remove_dir +// Note that it may get fixed in QNX 8 in a future libc release +// ... https://github.com/rust-lang/rust/issues/153781 +#[cfg_attr(any(target_os = "nto", target_os = "qnx"), ignore)] #[test] fn test_dir_remove_dir() { let tmpdir = tmpdir(); diff --git a/tests/mir-opt/tail_copy_to_move.aggregate.TailCopyToMove.diff b/tests/mir-opt/tail_copy_to_move.aggregate.TailCopyToMove.diff new file mode 100644 index 0000000000000..8c4045d4efcea --- /dev/null +++ b/tests/mir-opt/tail_copy_to_move.aggregate.TailCopyToMove.diff @@ -0,0 +1,24 @@ +- // MIR for `aggregate` before TailCopyToMove ++ // MIR for `aggregate` after TailCopyToMove + + fn aggregate(_1: u32, _2: u32) -> Pair { + debug x => _1; + debug y => _2; + let mut _0: Pair; + let mut _3: u32; + let mut _4: u32; + + bb0: { + StorageLive(_3); +- _3 = copy _1; ++ _3 = move _1; + StorageLive(_4); +- _4 = copy _2; ++ _4 = move _2; + _0 = Pair { a: move _3, b: move _4 }; + StorageDead(_4); + StorageDead(_3); + return; + } + } + diff --git a/tests/mir-opt/tail_copy_to_move.aggregate_operands.TailCopyToMove.diff b/tests/mir-opt/tail_copy_to_move.aggregate_operands.TailCopyToMove.diff new file mode 100644 index 0000000000000..cf0c2d62d3960 --- /dev/null +++ b/tests/mir-opt/tail_copy_to_move.aggregate_operands.TailCopyToMove.diff @@ -0,0 +1,13 @@ +- // MIR for `aggregate_operands` before TailCopyToMove ++ // MIR for `aggregate_operands` after TailCopyToMove + + fn aggregate_operands(_1: u32, _2: u32) -> (u32, u32) { + let mut _0: (u32, u32); + + bb0: { +- _0 = (copy _1, copy _2); ++ _0 = (move _1, move _2); + return; + } + } + diff --git a/tests/mir-opt/tail_copy_to_move.aggregate_with_deref.TailCopyToMove.diff b/tests/mir-opt/tail_copy_to_move.aggregate_with_deref.TailCopyToMove.diff new file mode 100644 index 0000000000000..f4a70b1d9fabb --- /dev/null +++ b/tests/mir-opt/tail_copy_to_move.aggregate_with_deref.TailCopyToMove.diff @@ -0,0 +1,16 @@ +- // MIR for `aggregate_with_deref` before TailCopyToMove ++ // MIR for `aggregate_with_deref` after TailCopyToMove + + fn aggregate_with_deref(_1: u32) -> (u32, u32) { + let mut _0: (u32, u32); + let mut _2: *const u32; + let mut _3: u32; + + bb0: { + _2 = &raw const _1; + _3 = copy _1; + _0 = (copy _3, copy (*_2)); + return; + } + } + diff --git a/tests/mir-opt/tail_copy_to_move.borrowed_dest_stops_tail.TailCopyToMove.diff b/tests/mir-opt/tail_copy_to_move.borrowed_dest_stops_tail.TailCopyToMove.diff new file mode 100644 index 0000000000000..ac0046e4624e3 --- /dev/null +++ b/tests/mir-opt/tail_copy_to_move.borrowed_dest_stops_tail.TailCopyToMove.diff @@ -0,0 +1,17 @@ +- // MIR for `borrowed_dest_stops_tail` before TailCopyToMove ++ // MIR for `borrowed_dest_stops_tail` after TailCopyToMove + + fn borrowed_dest_stops_tail(_1: u32, _2: u32) -> u32 { + debug y => _3; + let mut _0: u32; + let mut _3: u32; + let mut _4: *const u32; + + bb0: { + _4 = &raw const _3; + _0 = copy _1; + _3 = copy _2; + return; + } + } + diff --git a/tests/mir-opt/tail_copy_to_move.borrowed_source_tail.TailCopyToMove.diff b/tests/mir-opt/tail_copy_to_move.borrowed_source_tail.TailCopyToMove.diff new file mode 100644 index 0000000000000..fb24d156a7383 --- /dev/null +++ b/tests/mir-opt/tail_copy_to_move.borrowed_source_tail.TailCopyToMove.diff @@ -0,0 +1,15 @@ +- // MIR for `borrowed_source_tail` before TailCopyToMove ++ // MIR for `borrowed_source_tail` after TailCopyToMove + + fn borrowed_source_tail(_1: u32) -> u32 { + let mut _0: u32; + let mut _2: *const u32; + + bb0: { + _2 = &raw const _1; +- _0 = copy _1; ++ _0 = move _1; + return; + } + } + diff --git a/tests/mir-opt/tail_copy_to_move.chain.TailCopyToMove.diff b/tests/mir-opt/tail_copy_to_move.chain.TailCopyToMove.diff new file mode 100644 index 0000000000000..d354f9f23446f --- /dev/null +++ b/tests/mir-opt/tail_copy_to_move.chain.TailCopyToMove.diff @@ -0,0 +1,22 @@ +- // MIR for `chain` before TailCopyToMove ++ // MIR for `chain` after TailCopyToMove + + fn chain(_1: u32) -> u32 { + debug x => _1; + let mut _0: u32; + let _2: u32; + scope 1 { + debug t => _2; + } + + bb0: { + StorageLive(_2); +- _2 = copy _1; +- _0 = copy _2; ++ _2 = move _1; ++ _0 = move _2; + StorageDead(_2); + return; + } + } + diff --git a/tests/mir-opt/tail_copy_to_move.direct.TailCopyToMove.diff b/tests/mir-opt/tail_copy_to_move.direct.TailCopyToMove.diff new file mode 100644 index 0000000000000..84e7c4f9d42ca --- /dev/null +++ b/tests/mir-opt/tail_copy_to_move.direct.TailCopyToMove.diff @@ -0,0 +1,14 @@ +- // MIR for `direct` before TailCopyToMove ++ // MIR for `direct` after TailCopyToMove + + fn direct(_1: u32) -> u32 { + debug x => _1; + let mut _0: u32; + + bb0: { +- _0 = copy _1; ++ _0 = move _1; + return; + } + } + diff --git a/tests/mir-opt/tail_copy_to_move.index_dest.TailCopyToMove.diff b/tests/mir-opt/tail_copy_to_move.index_dest.TailCopyToMove.diff new file mode 100644 index 0000000000000..37ee7527b3492 --- /dev/null +++ b/tests/mir-opt/tail_copy_to_move.index_dest.TailCopyToMove.diff @@ -0,0 +1,18 @@ +- // MIR for `index_dest` before TailCopyToMove ++ // MIR for `index_dest` after TailCopyToMove + + fn index_dest(_1: [usize; 4], _2: usize) -> [usize; 4] { + debug a => _3; + let mut _0: [usize; 4]; + let mut _3: [usize; 4]; + + bb0: { +- _3 = copy _1; ++ _3 = move _1; + _3[_2] = copy _2; +- _0 = copy _3; ++ _0 = move _3; + return; + } + } + diff --git a/tests/mir-opt/tail_copy_to_move.index_operand.TailCopyToMove.diff b/tests/mir-opt/tail_copy_to_move.index_operand.TailCopyToMove.diff new file mode 100644 index 0000000000000..da6bbb117a846 --- /dev/null +++ b/tests/mir-opt/tail_copy_to_move.index_operand.TailCopyToMove.diff @@ -0,0 +1,13 @@ +- // MIR for `index_operand` before TailCopyToMove ++ // MIR for `index_operand` after TailCopyToMove + + fn index_operand(_1: [u32; 4], _2: usize) -> (usize, u32) { + let mut _0: (usize, u32); + + bb0: { +- _0 = (copy _2, copy _1[_2]); ++ _0 = (copy _2, move _1[_2]); + return; + } + } + diff --git a/tests/mir-opt/tail_copy_to_move.indirect_tail_read.TailCopyToMove.diff b/tests/mir-opt/tail_copy_to_move.indirect_tail_read.TailCopyToMove.diff new file mode 100644 index 0000000000000..16ac0dd1806ca --- /dev/null +++ b/tests/mir-opt/tail_copy_to_move.indirect_tail_read.TailCopyToMove.diff @@ -0,0 +1,19 @@ +- // MIR for `indirect_tail_read` before TailCopyToMove ++ // MIR for `indirect_tail_read` after TailCopyToMove + + fn indirect_tail_read(_1: u32) -> (u32, u32) { + let mut _0: (u32, u32); + let mut _2: *const u32; + let mut _3: u32; + let mut _4: u32; + + bb0: { + _2 = &raw const _1; + _3 = copy _1; + _4 = copy (*_2); +- _0 = (copy _3, copy _4); ++ _0 = (move _3, move _4); + return; + } + } + diff --git a/tests/mir-opt/tail_copy_to_move.indirect_tail_write.TailCopyToMove.diff b/tests/mir-opt/tail_copy_to_move.indirect_tail_write.TailCopyToMove.diff new file mode 100644 index 0000000000000..b98fb6458eec3 --- /dev/null +++ b/tests/mir-opt/tail_copy_to_move.indirect_tail_write.TailCopyToMove.diff @@ -0,0 +1,16 @@ +- // MIR for `indirect_tail_write` before TailCopyToMove ++ // MIR for `indirect_tail_write` after TailCopyToMove + + fn indirect_tail_write(_1: u32, _2: u32) -> u32 { + debug p => _3; + let mut _0: u32; + let mut _3: *mut u32; + + bb0: { + _3 = &raw mut _1; + _0 = copy _1; + (*_3) = copy _2; + return; + } + } + diff --git a/tests/mir-opt/tail_copy_to_move.projected.TailCopyToMove.diff b/tests/mir-opt/tail_copy_to_move.projected.TailCopyToMove.diff new file mode 100644 index 0000000000000..d19a551cfb737 --- /dev/null +++ b/tests/mir-opt/tail_copy_to_move.projected.TailCopyToMove.diff @@ -0,0 +1,14 @@ +- // MIR for `projected` before TailCopyToMove ++ // MIR for `projected` after TailCopyToMove + + fn projected(_1: Pair) -> u32 { + debug pair => _1; + let mut _0: u32; + + bb0: { +- _0 = copy (_1.0: u32); ++ _0 = move (_1.0: u32); + return; + } + } + diff --git a/tests/mir-opt/tail_copy_to_move.projected_dest.TailCopyToMove.diff b/tests/mir-opt/tail_copy_to_move.projected_dest.TailCopyToMove.diff new file mode 100644 index 0000000000000..14911f4313ebd --- /dev/null +++ b/tests/mir-opt/tail_copy_to_move.projected_dest.TailCopyToMove.diff @@ -0,0 +1,15 @@ +- // MIR for `projected_dest` before TailCopyToMove ++ // MIR for `projected_dest` after TailCopyToMove + + fn projected_dest(_1: u32, _2: u32) -> (u32, u32) { + let mut _0: (u32, u32); + + bb0: { +- (_0.0: u32) = copy _1; +- (_0.1: u32) = copy _2; ++ (_0.0: u32) = move _1; ++ (_0.1: u32) = move _2; + return; + } + } + diff --git a/tests/mir-opt/tail_copy_to_move.repeated_operand.TailCopyToMove.diff b/tests/mir-opt/tail_copy_to_move.repeated_operand.TailCopyToMove.diff new file mode 100644 index 0000000000000..59e775c20f898 --- /dev/null +++ b/tests/mir-opt/tail_copy_to_move.repeated_operand.TailCopyToMove.diff @@ -0,0 +1,13 @@ +- // MIR for `repeated_operand` before TailCopyToMove ++ // MIR for `repeated_operand` after TailCopyToMove + + fn repeated_operand(_1: u32) -> (u32, u32) { + let mut _0: (u32, u32); + + bb0: { +- _0 = (copy _1, copy _1); ++ _0 = (copy _1, move _1); + return; + } + } + diff --git a/tests/mir-opt/tail_copy_to_move.rs b/tests/mir-opt/tail_copy_to_move.rs new file mode 100644 index 0000000000000..c4c18db6570d3 --- /dev/null +++ b/tests/mir-opt/tail_copy_to_move.rs @@ -0,0 +1,347 @@ +//@ test-mir-pass: TailCopyToMove +//@ compile-flags: -Cpanic=abort + +#![feature(custom_mir, core_intrinsics)] +#![allow(internal_features)] + +use std::intrinsics::mir::*; + +#[derive(Copy, Clone)] +pub struct Pair { + a: u32, + b: u32, +} + +#[derive(Copy, Clone)] +pub enum Choice { + A(u32), + B, +} + +// EMIT_MIR tail_copy_to_move.direct.TailCopyToMove.diff +pub fn direct(x: u32) -> u32 { + // Checks the simplest returned `Copy` local. + // CHECK-LABEL: fn direct( + // CHECK: _0 = move _1; + x +} + +// EMIT_MIR tail_copy_to_move.chain.TailCopyToMove.diff +pub fn chain(x: u32) -> u32 { + // Checks that the scan propagates through a temporary local. + // CHECK-LABEL: fn chain( + // CHECK: debug t => [[TMP:_.*]]; + // CHECK: [[TMP]] = move _1; + // CHECK: _0 = move [[TMP]]; + let t = x; + t +} + +// EMIT_MIR tail_copy_to_move.aggregate.TailCopyToMove.diff +pub fn aggregate(x: u32, y: u32) -> Pair { + // Checks aggregate construction from returned `Copy` locals. + // CHECK-LABEL: fn aggregate( + // CHECK: [[A:_.*]] = move _1; + // CHECK: [[B:_.*]] = move _2; + // CHECK: _0 = Pair { a: move [[A]], b: move [[B]] }; + Pair { a: x, b: y } +} + +// EMIT_MIR tail_copy_to_move.aggregate_operands.TailCopyToMove.diff +#[custom_mir(dialect = "runtime", phase = "post-cleanup")] +pub fn aggregate_operands(x: u32, y: u32) -> (u32, u32) { + // Checks aggregate operands that are already in the final assignment. + // CHECK-LABEL: fn aggregate_operands( + // CHECK: _0 = (move _1, move _2); + mir!({ + RET = (x, y); + Return() + }) +} + +// EMIT_MIR tail_copy_to_move.projected_dest.TailCopyToMove.diff +#[custom_mir(dialect = "runtime", phase = "post-cleanup")] +pub fn projected_dest(x: u32, y: u32) -> (u32, u32) { + // Checks assignments to direct projections of the return place. + // CHECK-LABEL: fn projected_dest( + // CHECK: (_0.0: u32) = move _1; + // CHECK: (_0.1: u32) = move _2; + mir! { + type RET = (u32, u32); + { + RET.0 = x; + RET.1 = y; + Return() + } + } +} + +// EMIT_MIR tail_copy_to_move.projected.TailCopyToMove.diff +pub fn projected(pair: Pair) -> u32 { + // Checks that direct projected source copies are also rewritten. + // CHECK-LABEL: fn projected( + // CHECK: _0 = move (_1.0: u32); + pair.a +} + +// EMIT_MIR tail_copy_to_move.set_discriminant.TailCopyToMove.diff +#[custom_mir(dialect = "runtime", phase = "post-cleanup")] +pub fn set_discriminant(choice: Choice) -> Choice { + // Checks that `SetDiscriminant` is accepted in the return tail. + // CHECK-LABEL: fn set_discriminant( + // CHECK: _0 = move _1; + // CHECK: discriminant(_0) = 1; + mir!({ + RET = choice; + SetDiscriminant(RET, 1); + Return() + }) +} + +// EMIT_MIR tail_copy_to_move.set_discriminant_indirect.TailCopyToMove.diff +#[custom_mir(dialect = "runtime", phase = "post-cleanup")] +pub fn set_discriminant_indirect(choice: Choice) -> Choice { + // Checks that an indirect `SetDiscriminant` place stops the scan. + // CHECK-LABEL: fn set_discriminant_indirect( + // CHECK: debug p => [[P:_.*]]; + // CHECK: _0 = copy _1; + // CHECK: discriminant((*[[P]])) = 1; + mir! { + let p: *mut Choice; + debug p => p; + + { + p = &raw mut choice; + RET = choice; + SetDiscriminant(*p, 1); + Return() + } + } +} + +// EMIT_MIR tail_copy_to_move.set_discriminant_borrowed.TailCopyToMove.diff +#[custom_mir(dialect = "runtime", phase = "post-cleanup")] +pub fn set_discriminant_borrowed(input: Choice) -> Choice { + // Checks that writing a borrowed local's discriminant stops the scan. + // CHECK-LABEL: fn set_discriminant_borrowed( + // CHECK: debug local => [[LOCAL:_.*]]; + // CHECK: _0 = copy _1; + // CHECK: discriminant([[LOCAL]]) = 1; + mir! { + let local: Choice; + let p: *const Choice; + debug local => local; + + { + p = &raw const local; + RET = input; + SetDiscriminant(local, 1); + Return() + } + } +} + +// EMIT_MIR tail_copy_to_move.set_discriminant_index.TailCopyToMove.diff +#[custom_mir(dialect = "runtime", phase = "post-cleanup")] +pub fn set_discriminant_index(arr: [Choice; 4], idx: usize) -> usize { + // Checks that `SetDiscriminant` records projection locals such as indexes. + // CHECK-LABEL: fn set_discriminant_index( + // CHECK: debug local => [[ARR:_.*]]; + // CHECK: [[ARR]] = move _1; + // CHECK: _0 = copy _2; + // CHECK: discriminant([[ARR]][_2]) = 1; + mir! { + let local: [Choice; 4]; + debug local => local; + + { + local = arr; + RET = idx; + SetDiscriminant(local[idx], 1); + Return() + } + } +} + +// EMIT_MIR tail_copy_to_move.indirect_tail_read.TailCopyToMove.diff +#[custom_mir(dialect = "runtime", phase = "post-cleanup")] +pub fn indirect_tail_read(x: u32) -> (u32, u32) { + // Checks that an indirect read stops the scan before earlier assignments. + // CHECK-LABEL: fn indirect_tail_read( + // CHECK: [[P:_.*]] = &raw const _1; + // CHECK: [[Q:_.*]] = copy _1; + // CHECK: [[S:_.*]] = copy (*[[P]]); + // CHECK: _0 = (move [[Q]], move [[S]]); + mir! { + let p: *const u32; + let q: u32; + let s: u32; + + { + p = &raw const x; + q = x; + s = *p; + RET = (q, s); + Return() + } + } +} + +// EMIT_MIR tail_copy_to_move.indirect_tail_write.TailCopyToMove.diff +#[custom_mir(dialect = "runtime", phase = "post-cleanup")] +pub fn indirect_tail_write(x: u32, z: u32) -> u32 { + // Checks that an indirect assignment destination stops the scan. + // CHECK-LABEL: fn indirect_tail_write( + // CHECK: debug p => [[P:_.*]]; + // CHECK: _0 = copy _1; + // CHECK: (*[[P]]) = copy _2; + mir! { + let p: *mut u32; + debug p => p; + + { + p = &raw mut x; + RET = x; + *p = z; + Return() + } + } +} + +// EMIT_MIR tail_copy_to_move.aggregate_with_deref.TailCopyToMove.diff +#[custom_mir(dialect = "runtime", phase = "post-cleanup")] +pub fn aggregate_with_deref(x: u32) -> (u32, u32) { + // Checks that an indirect aggregate operand stops the aggregate scan. + // CHECK-LABEL: fn aggregate_with_deref( + // CHECK: [[P:_.*]] = &raw const _1; + // CHECK: [[Q:_.*]] = copy _1; + // CHECK: _0 = (copy [[Q]], copy (*[[P]])); + mir! { + let p: *const u32; + let q: u32; + + { + p = &raw const x; + q = x; + RET = (q, *p); + Return() + } + } +} + +// EMIT_MIR tail_copy_to_move.borrowed_dest_stops_tail.TailCopyToMove.diff +#[custom_mir(dialect = "runtime", phase = "post-cleanup")] +pub fn borrowed_dest_stops_tail(x: u32, z: u32) -> u32 { + // Checks that writing to a borrowed local stops the scan. + // CHECK-LABEL: fn borrowed_dest_stops_tail( + // CHECK: debug y => [[Y:_.*]]; + // CHECK: _0 = copy _1; + // CHECK: [[Y]] = copy _2; + mir! { + let y: u32; + let p: *const u32; + debug y => y; + + { + p = &raw const y; + RET = x; + y = z; + Return() + } + } +} + +// EMIT_MIR tail_copy_to_move.unrelated_tail_store.TailCopyToMove.diff +#[custom_mir(dialect = "runtime", phase = "post-cleanup")] +pub fn unrelated_tail_store(x: u32, z: u32) -> u32 { + // Checks that writing to an unborrowed local remains in the tail. + // CHECK-LABEL: fn unrelated_tail_store( + // CHECK: debug y => [[Y:_.*]]; + // CHECK: _0 = move _1; + // CHECK: [[Y]] = move _2; + mir! { + let y: u32; + debug y => y; + + { + RET = x; + y = z; + Return() + } + } +} + +// EMIT_MIR tail_copy_to_move.index_operand.TailCopyToMove.diff +#[custom_mir(dialect = "runtime", phase = "post-cleanup")] +pub fn index_operand(arr: [u32; 4], idx: usize) -> (usize, u32) { + // Checks that index projection locals count as later uses. + // CHECK-LABEL: fn index_operand( + // CHECK: _0 = (copy _2, move _1[_2]); + mir! { + { + RET = (idx, arr[idx]); + Return() + } + } +} + +// EMIT_MIR tail_copy_to_move.index_dest.TailCopyToMove.diff +#[custom_mir(dialect = "runtime", phase = "post-cleanup")] +pub fn index_dest(arr: [usize; 4], idx: usize) -> [usize; 4] { + // Checks that index locals in destination projections are recorded. + // CHECK-LABEL: fn index_dest( + // CHECK: debug a => [[ARR:_.*]]; + // CHECK: [[ARR]] = move _1; + // CHECK: [[ARR]][_2] = copy _2; + // CHECK: _0 = move [[ARR]]; + mir! { + let a: [usize; 4]; + debug a => a; + + { + a = arr; + a[idx] = idx; + RET = a; + Return() + } + } +} + +// EMIT_MIR tail_copy_to_move.repeated_operand.TailCopyToMove.diff +#[custom_mir(dialect = "runtime", phase = "post-cleanup")] +pub fn repeated_operand(x: u32) -> (u32, u32) { + // Checks right-to-left aggregate scanning for repeated operands. + // CHECK-LABEL: fn repeated_operand( + // CHECK: _0 = (copy _1, move _1); + mir!({ + RET = (x, x); + Return() + }) +} + +// EMIT_MIR tail_copy_to_move.borrowed_source_tail.TailCopyToMove.diff +#[custom_mir(dialect = "runtime", phase = "post-cleanup")] +pub fn borrowed_source_tail(x: u32) -> u32 { + // Checks that a borrowed source can still move at its final use. + // CHECK-LABEL: fn borrowed_source_tail( + // CHECK: [[P:_.*]] = &raw const _1; + // CHECK: _0 = move _1; + mir! { + let p: *const u32; + + { + p = &raw const x; + RET = x; + Return() + } + } +} + +// EMIT_MIR tail_copy_to_move.shared_return.TailCopyToMove.diff +pub fn shared_return(x: u32, y: u32, take_x: bool) -> u32 { + // Checks branch arms that share a return block. + // CHECK-LABEL: fn shared_return( + // CHECK: _0 = move _1; + // CHECK: _0 = move _2; + if take_x { x } else { y } +} diff --git a/tests/mir-opt/tail_copy_to_move.set_discriminant.TailCopyToMove.diff b/tests/mir-opt/tail_copy_to_move.set_discriminant.TailCopyToMove.diff new file mode 100644 index 0000000000000..76f23dae83a91 --- /dev/null +++ b/tests/mir-opt/tail_copy_to_move.set_discriminant.TailCopyToMove.diff @@ -0,0 +1,14 @@ +- // MIR for `set_discriminant` before TailCopyToMove ++ // MIR for `set_discriminant` after TailCopyToMove + + fn set_discriminant(_1: Choice) -> Choice { + let mut _0: Choice; + + bb0: { +- _0 = copy _1; ++ _0 = move _1; + discriminant(_0) = 1; + return; + } + } + diff --git a/tests/mir-opt/tail_copy_to_move.set_discriminant_borrowed.TailCopyToMove.diff b/tests/mir-opt/tail_copy_to_move.set_discriminant_borrowed.TailCopyToMove.diff new file mode 100644 index 0000000000000..945c0e6101b4b --- /dev/null +++ b/tests/mir-opt/tail_copy_to_move.set_discriminant_borrowed.TailCopyToMove.diff @@ -0,0 +1,17 @@ +- // MIR for `set_discriminant_borrowed` before TailCopyToMove ++ // MIR for `set_discriminant_borrowed` after TailCopyToMove + + fn set_discriminant_borrowed(_1: Choice) -> Choice { + debug local => _2; + let mut _0: Choice; + let mut _2: Choice; + let mut _3: *const Choice; + + bb0: { + _3 = &raw const _2; + _0 = copy _1; + discriminant(_2) = 1; + return; + } + } + diff --git a/tests/mir-opt/tail_copy_to_move.set_discriminant_index.TailCopyToMove.diff b/tests/mir-opt/tail_copy_to_move.set_discriminant_index.TailCopyToMove.diff new file mode 100644 index 0000000000000..cde22c5d7a9f9 --- /dev/null +++ b/tests/mir-opt/tail_copy_to_move.set_discriminant_index.TailCopyToMove.diff @@ -0,0 +1,17 @@ +- // MIR for `set_discriminant_index` before TailCopyToMove ++ // MIR for `set_discriminant_index` after TailCopyToMove + + fn set_discriminant_index(_1: [Choice; 4], _2: usize) -> usize { + debug local => _3; + let mut _0: usize; + let mut _3: [Choice; 4]; + + bb0: { +- _3 = copy _1; ++ _3 = move _1; + _0 = copy _2; + discriminant(_3[_2]) = 1; + return; + } + } + diff --git a/tests/mir-opt/tail_copy_to_move.set_discriminant_indirect.TailCopyToMove.diff b/tests/mir-opt/tail_copy_to_move.set_discriminant_indirect.TailCopyToMove.diff new file mode 100644 index 0000000000000..80df658ce9e25 --- /dev/null +++ b/tests/mir-opt/tail_copy_to_move.set_discriminant_indirect.TailCopyToMove.diff @@ -0,0 +1,16 @@ +- // MIR for `set_discriminant_indirect` before TailCopyToMove ++ // MIR for `set_discriminant_indirect` after TailCopyToMove + + fn set_discriminant_indirect(_1: Choice) -> Choice { + debug p => _2; + let mut _0: Choice; + let mut _2: *mut Choice; + + bb0: { + _2 = &raw mut _1; + _0 = copy _1; + discriminant((*_2)) = 1; + return; + } + } + diff --git a/tests/mir-opt/tail_copy_to_move.shared_return.TailCopyToMove.diff b/tests/mir-opt/tail_copy_to_move.shared_return.TailCopyToMove.diff new file mode 100644 index 0000000000000..26c0879695ae2 --- /dev/null +++ b/tests/mir-opt/tail_copy_to_move.shared_return.TailCopyToMove.diff @@ -0,0 +1,34 @@ +- // MIR for `shared_return` before TailCopyToMove ++ // MIR for `shared_return` after TailCopyToMove + + fn shared_return(_1: u32, _2: u32, _3: bool) -> u32 { + debug x => _1; + debug y => _2; + debug take_x => _3; + let mut _0: u32; + let mut _4: bool; + + bb0: { + StorageLive(_4); + _4 = copy _3; + switchInt(move _4) -> [0: bb2, otherwise: bb1]; + } + + bb1: { +- _0 = copy _1; ++ _0 = move _1; + goto -> bb3; + } + + bb2: { +- _0 = copy _2; ++ _0 = move _2; + goto -> bb3; + } + + bb3: { + StorageDead(_4); + return; + } + } + diff --git a/tests/mir-opt/tail_copy_to_move.unrelated_tail_store.TailCopyToMove.diff b/tests/mir-opt/tail_copy_to_move.unrelated_tail_store.TailCopyToMove.diff new file mode 100644 index 0000000000000..4af29766f48e9 --- /dev/null +++ b/tests/mir-opt/tail_copy_to_move.unrelated_tail_store.TailCopyToMove.diff @@ -0,0 +1,17 @@ +- // MIR for `unrelated_tail_store` before TailCopyToMove ++ // MIR for `unrelated_tail_store` after TailCopyToMove + + fn unrelated_tail_store(_1: u32, _2: u32) -> u32 { + debug y => _3; + let mut _0: u32; + let mut _3: u32; + + bb0: { +- _0 = copy _1; +- _3 = copy _2; ++ _0 = move _1; ++ _3 = move _2; + return; + } + } + diff --git a/tests/ui/traits/next-solver/projection-candidates-modulo-regions-issue-162323.rs b/tests/ui/traits/next-solver/projection-candidates-modulo-regions-issue-162323.rs new file mode 100644 index 0000000000000..779a097385622 --- /dev/null +++ b/tests/ui/traits/next-solver/projection-candidates-modulo-regions-issue-162323.rs @@ -0,0 +1,45 @@ +//@ check-pass +//@ compile-flags: -Znext-solver=globally + +// Two alias-bound candidates normalize `V` to `Input`, but differ in +// their region constraints. One candidate has no region constraints, +// allowing the responses to be merged. + +struct Input; + +struct MyRhs; + +trait Jac { + type V; + fn jacobian(&self, x: Self::V); +} + +impl Jac for MyRhs { + type V = Input; + + fn jacobian(&self, _: Self::V) {} +} + +struct MyEqns; + +trait EqnsRef<'a, ImplicitBounds = &'a ()> { + type Rhs: Jac; + + fn rhs(&'a self) -> Self::Rhs; +} + +impl<'a> EqnsRef<'a> for MyEqns { + type Rhs = MyRhs; + + fn rhs(&'a self) -> Self::Rhs { + MyRhs + } +} + +fn inner() -> impl for<'a> EqnsRef<'a, Rhs: Jac> { + MyEqns +} + +fn main() { + inner().rhs().jacobian(Input); +} diff --git a/tests/ui/type-inference/generalize-subtyped-variables-old.next.stderr b/tests/ui/type-inference/generalize-subtyped-variables-old.next.stderr new file mode 100644 index 0000000000000..ce703154fc7ac --- /dev/null +++ b/tests/ui/type-inference/generalize-subtyped-variables-old.next.stderr @@ -0,0 +1,12 @@ +error[E0308]: mismatched types + --> $DIR/generalize-subtyped-variables-old.rs:23:26 + | +LL | let mut w = (&mut x, z, z); + | ^ expected `Option fn(&'a ())>`, found `Option` + | + = note: expected enum `Option fn(&'a ())>` + found enum `Option` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/type-inference/generalize-subtyped-variables-old.rs b/tests/ui/type-inference/generalize-subtyped-variables-old.rs new file mode 100644 index 0000000000000..3eb58a62fdbe6 --- /dev/null +++ b/tests/ui/type-inference/generalize-subtyped-variables-old.rs @@ -0,0 +1,27 @@ +// A test for the way we handle higher-ranked subtyping and subtyping requirements. +// +// - `let y = x` creates a `Subtype` obligation that is deferred for later. +// - `w = a` sets the type of `x` to `Option fn(&'a ())>` and generalized +// `z` first to `Option<_>` and then to `Option`. +// - The various subtyping obligations are then processed. +// +// Whether the `?x <: ?y` obligation incorrectly constrains `?y` to +// `Option fn(&'a ())>` is order dependent and passed with the old +// solver while breaking with the new one. +// +// Found when considering fixes to #117151 + +//@ revisions: old next +//@[next] compile-flags: -Znext-solver +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[old] check-pass + +fn main() { + let mut x = None; + let y = x; + let z = Default::default(); + let mut w = (&mut x, z, z); + //[next]~^ ERROR: mismatched types + let a = (&mut None::, y, None::); + w = a; +} diff --git a/tests/ui/type-inference/generalize-subtyped-variables.rs b/tests/ui/type-inference/generalize-subtyped-variables.rs index b7cae32ee32ff..558e25cd39a97 100644 --- a/tests/ui/type-inference/generalize-subtyped-variables.rs +++ b/tests/ui/type-inference/generalize-subtyped-variables.rs @@ -1,25 +1,36 @@ -// Test for specific details of how we handle higher-ranked subtyping to make -// sure that any changes are made deliberately. +// A test checking whether we eagerly apply subtype requirements +// when relating types. This relies on incomplete inference with +// higher-ranked types. If we eagerly apply the `?x <: ?y` subtype +// requirements after constraining `?x` to `for<'a> fn(&'a ())` we +// would incorrectly constrain `?y` to also be `for<'a> fn(&'a ())`. // -// - `let y = x` creates a `Subtype` obligation that is deferred for later. -// - `w = a` sets the type of `x` to `Option fn(&'a ())>` and generalizes -// `z` first to `Option<_>` and then to `Option`. -// - The various subtyping obligations are then processed. +// This would then result in an error when relating `y` with `Inv`. // -// This requires that -// 1. the `Subtype` obligation from `y = x` isn't processed while the types of -// `w` and `a` are being unified. -// 2. the pending subtype obligation isn't considered when determining the type -// to generalize `z` to first (when related to the type of `y`). -// -// Found when considering fixes to #117151 +// It's fine for this behavior to change, we should do so intentionally however. + +//@ revisions: old next +//@[next] compile-flags: -Znext-solver +//@ ignore-compare-mode-next-solver (explicit revisions) //@ check-pass +fn to_inv(x: Option) -> Inv { + Inv(None) +} +#[derive(Copy, Clone)] +struct Inv(Option<*mut T>); + fn main() { - let mut x = None; + let x = None; let y = x; - let z = Default::default(); - let mut w = (&mut x, z, z); - let a = (&mut None::, y, None::); + let mut x = to_inv(x); + let y = to_inv(y); + // deferred ?x <: ?y + let z = Inv(None); + // type_of(w) = (Inv, Inv, Inv) + let mut w = (x, z, z); + // type_of(a) = (Inv fn(&'a ())>, Inv, Inv) + let a = (Inv::(None), y, Inv::(None)); + // ?x = for<'a> fn(&'a ()) + // ?y = fn(&'static ()) w = a; }