| use std::ops::ControlFlow; | |
| use rustc_abi::FieldIdx; | |
| use rustc_data_structures::graph::dominators::Dominators; | |
| use rustc_middle::mir::{BasicBlock, Body, Location, Place, PlaceRef, ProjectionElem}; | |
| use rustc_middle::ty::TyCtxt; | |
| use tracing::debug; | |
| use crate::borrow_set::{BorrowData, BorrowSet, TwoPhaseActivation}; | |
| use crate::{AccessDepth, BorrowIndex, places_conflict}; | |
| /// Encapsulates the idea of iterating over every borrow that involves a particular path | |
| pub(super) fn each_borrow_involving_path<'tcx, F, I, S>( | |
| s: &mut S, | |
| tcx: TyCtxt<'tcx>, | |
| body: &Body<'tcx>, | |
| access_place: (AccessDepth, Place<'tcx>), | |
| borrow_set: &BorrowSet<'tcx>, | |
| is_candidate: I, | |
| mut op: F, | |
| ) where | |
| F: FnMut(&mut S, BorrowIndex, &BorrowData<'tcx>) -> ControlFlow<()>, | |
| I: Fn(BorrowIndex) -> bool, | |
| { | |
| let (access, place) = access_place; | |
| // The number of candidates can be large, but borrows for different locals cannot conflict with | |
| // each other, so we restrict the working set a priori. | |
| let Some(borrows_for_place_base) = borrow_set.local_map.get(&place.local) else { return }; | |
| // check for loan restricting path P being used. Accounts for | |
| // borrows of P, P.a.b, etc. | |
| for &i in borrows_for_place_base { | |
| if !is_candidate(i) { | |
| continue; | |
| } | |
| let borrowed = &borrow_set[i]; | |
| if places_conflict::borrow_conflicts_with_place( | |
| tcx, | |
| body, | |
| borrowed.borrowed_place, | |
| borrowed.kind, | |
| place.as_ref(), | |
| access, | |
| places_conflict::PlaceConflictBias::Overlap, | |
| ) { | |
| debug!( | |
| "each_borrow_involving_path: {:?} @ {:?} vs. {:?}/{:?}", | |
| i, borrowed, place, access | |
| ); | |
| let ctrl = op(s, i, borrowed); | |
| if matches!(ctrl, ControlFlow::Break(_)) { | |
| return; | |
| } | |
| } | |
| } | |
| } | |
| pub(super) fn is_active<'tcx>( | |
| dominators: &Dominators<BasicBlock>, | |
| borrow_data: &BorrowData<'tcx>, | |
| location: Location, | |
| ) -> bool { | |
| debug!("is_active(borrow_data={:?}, location={:?})", borrow_data, location); | |
| let activation_location = match borrow_data.activation_location { | |
| // If this is not a 2-phase borrow, it is always active. | |
| TwoPhaseActivation::NotTwoPhase => return true, | |
| // And if the unique 2-phase use is not an activation, then it is *never* active. | |
| TwoPhaseActivation::NotActivated => return false, | |
| // Otherwise, we derive info from the activation point `loc`: | |
| TwoPhaseActivation::ActivatedAt(loc) => loc, | |
| }; | |
| // Otherwise, it is active for every location *except* in between | |
| // the reservation and the activation: | |
| // | |
| // X | |
| // / | |
| // R <--+ Except for this | |
| // / \ | diamond | |
| // \ / | | |
| // A <------+ | |
| // | | |
| // Z | |
| // | |
| // Note that we assume that: | |
| // - the reservation R dominates the activation A | |
| // - the activation A post-dominates the reservation R (ignoring unwinding edges). | |
| // | |
| // This means that there can't be an edge that leaves A and | |
| // comes back into that diamond unless it passes through R. | |
| // | |
| // Suboptimal: In some cases, this code walks the dominator | |
| // tree twice when it only has to be walked once. I am | |
| // lazy. -nmatsakis | |
| // If dominated by the activation A, then it is active. The | |
| // activation occurs upon entering the point A, so this is | |
| // also true if location == activation_location. | |
| if activation_location.dominates(location, dominators) { | |
| return true; | |
| } | |
| // The reservation starts *on exiting* the reservation block, | |
| // so check if the location is dominated by R.successor. If so, | |
| // this point falls in between the reservation and location. | |
| let reserve_location = borrow_data.reserve_location.successor_within_block(); | |
| if reserve_location.dominates(location, dominators) { | |
| false | |
| } else { | |
| // Otherwise, this point is outside the diamond, so | |
| // consider the borrow active. This could happen for | |
| // example if the borrow remains active around a loop (in | |
| // which case it would be active also for the point R, | |
| // which would generate an error). | |
| true | |
| } | |
| } | |
| /// Determines if a given borrow is borrowing local data | |
| /// This is called for all Yield expressions on movable coroutines | |
| pub(super) fn borrow_of_local_data(place: Place<'_>) -> bool { | |
| // Reborrow of already borrowed data is ignored | |
| // Any errors will be caught on the initial borrow | |
| !place.is_indirect() | |
| } | |
| /// If `place` is a field projection, and the field is being projected from a closure type, | |
| /// then returns the index of the field being projected. Note that this closure will always | |
| /// be `self` in the current MIR, because that is the only time we directly access the fields | |
| /// of a closure type. | |
| pub(crate) fn is_upvar_field_projection<'tcx>( | |
| tcx: TyCtxt<'tcx>, | |
| upvars: &[&rustc_middle::ty::CapturedPlace<'tcx>], | |
| place_ref: PlaceRef<'tcx>, | |
| body: &Body<'tcx>, | |
| ) -> Option<FieldIdx> { | |
| let mut place_ref = place_ref; | |
| let mut by_ref = false; | |
| if let Some((place_base, ProjectionElem::Deref)) = place_ref.last_projection() { | |
| place_ref = place_base; | |
| by_ref = true; | |
| } | |
| match place_ref.last_projection() { | |
| Some((place_base, ProjectionElem::Field(field, _ty))) => { | |
| let base_ty = place_base.ty(body, tcx).ty; | |
| if (base_ty.is_closure() || base_ty.is_coroutine() || base_ty.is_coroutine_closure()) | |
| && (!by_ref || upvars[field.index()].is_by_ref()) | |
| { | |
| Some(field) | |
| } else { | |
| None | |
| } | |
| } | |
| _ => None, | |
| } | |
| } |