Skip to content

Commit ad34318

Browse files
committed
WIP: implement borrowck of pinned borrows
Step 1: implement `PinSet`
1 parent b49ecc9 commit ad34318

9 files changed

Lines changed: 455 additions & 270 deletions

File tree

compiler/rustc_borrowck/src/borrow_set.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::fmt;
22
use std::ops::Index;
33

4+
use rustc_abi::FieldIdx;
45
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
56
use rustc_index::bit_set::DenseBitSet;
67
use rustc_middle::mir::visit::{MutatingUseContext, NonUseContext, PlaceContext, Visitor};
@@ -69,6 +70,13 @@ pub enum TwoPhaseActivation {
6970
ActivatedAt(Location),
7071
}
7172

73+
/// Location where a place is pinned, if a borrowed place is pinned.
74+
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
75+
pub(crate) enum Pinnedness<'tcx> {
76+
Not,
77+
Pinned { to: mir::Place<'tcx>, at: Location },
78+
}
79+
7280
#[derive(Debug, Clone)]
7381
pub struct BorrowData<'tcx> {
7482
/// Location where the borrow reservation starts.
@@ -84,6 +92,8 @@ pub struct BorrowData<'tcx> {
8492
pub(crate) borrowed_place: mir::Place<'tcx>,
8593
/// Place to which the borrow was stored
8694
pub(crate) assigned_place: mir::Place<'tcx>,
95+
/// Place and location to the pinning statement, if the borrowed place is pinned.
96+
pub(crate) pinnedness: Pinnedness<'tcx>,
8797
}
8898

8999
// These methods are public to support borrowck consumers.
@@ -260,6 +270,7 @@ impl<'a, 'tcx> Visitor<'tcx> for GatherBorrows<'a, 'tcx> {
260270
activation_location,
261271
borrowed_place,
262272
assigned_place: *assigned_place,
273+
pinnedness: Pinnedness::Not,
263274
};
264275

265276
let idx = if !kind.is_two_phase_borrow() {
@@ -300,9 +311,40 @@ impl<'a, 'tcx> Visitor<'tcx> for GatherBorrows<'a, 'tcx> {
300311
idx
301312
};
302313

314+
if let Some((pinned_place, successor)) =
315+
succeeded_by_pin(self.tcx, self.body, location, *assigned_place)
316+
{
317+
self.location_map[&location].pinnedness =
318+
Pinnedness::Pinned { to: pinned_place, at: successor };
319+
}
320+
303321
self.local_map.entry(borrowed_place.local).or_default().insert(idx);
304322
}
305323

324+
// Whether the current statement is followed by a
325+
// `pinned_place = Pin::<&[mut] T> { pointer: move assigned_place }` statement.
326+
fn succeeded_by_pin<'tcx>(
327+
tcx: TyCtxt<'tcx>,
328+
body: &mir::Body<'tcx>,
329+
location: mir::Location,
330+
assigned_place: mir::Place<'tcx>,
331+
) -> Option<(mir::Place<'tcx>, mir::Location)> {
332+
let successor = location.successor_within_block();
333+
let stmt =
334+
body.basic_blocks[successor.block].statements.get(successor.statement_index)?;
335+
if let mir::StatementKind::Assign(box (pinned_place, ref rvalue)) = stmt.kind
336+
&& let mir::Rvalue::Aggregate(box agg_kind, operands) = rvalue
337+
&& let mir::AggregateKind::Adt(adt_did, _, args, _, _) = agg_kind
338+
&& tcx.adt_def(adt_did).is_pin()
339+
&& args.type_at(0).is_ref()
340+
&& let mir::Operand::Move(place) = operands[FieldIdx::ZERO]
341+
&& place == assigned_place
342+
{
343+
return Some((pinned_place, successor));
344+
}
345+
None
346+
}
347+
306348
self.super_assign(assigned_place, rvalue, location)
307349
}
308350

compiler/rustc_borrowck/src/dataflow.rs

Lines changed: 191 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@ use rustc_mir_dataflow::impls::{
1414
use rustc_mir_dataflow::{Analysis, GenKill, JoinSemiLattice};
1515
use tracing::debug;
1616

17-
use crate::{BorrowSet, PlaceConflictBias, PlaceExt, RegionInferenceContext, places_conflict};
17+
use crate::{
18+
BorrowSet, PinSet, PlaceConflictBias, PlaceExt, RegionInferenceContext, places_conflict,
19+
};
1820

1921
// This analysis is different to most others. Its results aren't computed with
2022
// `iterate_to_fixpoint`, but are instead composed from the results of three sub-analyses that are
2123
// computed individually with `iterate_to_fixpoint`.
2224
pub(crate) struct Borrowck<'a, 'tcx> {
2325
pub(crate) borrows: Borrows<'a, 'tcx>,
26+
pub(crate) pins: Pins<'a, 'tcx>,
2427
pub(crate) uninits: MaybeUninitializedPlaces<'a, 'tcx>,
2528
pub(crate) ever_inits: EverInitializedPlaces<'a, 'tcx>,
2629
}
@@ -33,6 +36,7 @@ impl<'a, 'tcx> Analysis<'tcx> for Borrowck<'a, 'tcx> {
3336
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
3437
BorrowckDomain {
3538
borrows: self.borrows.bottom_value(body),
39+
pins: self.pins.bottom_value(body),
3640
uninits: self.uninits.bottom_value(body),
3741
ever_inits: self.ever_inits.bottom_value(body),
3842
}
@@ -50,6 +54,7 @@ impl<'a, 'tcx> Analysis<'tcx> for Borrowck<'a, 'tcx> {
5054
loc: Location,
5155
) {
5256
self.borrows.apply_early_statement_effect(&mut state.borrows, stmt, loc);
57+
self.pins.apply_early_statement_effect(&mut state.pins, stmt, loc);
5358
self.uninits.apply_early_statement_effect(&mut state.uninits, stmt, loc);
5459
self.ever_inits.apply_early_statement_effect(&mut state.ever_inits, stmt, loc);
5560
}
@@ -61,6 +66,7 @@ impl<'a, 'tcx> Analysis<'tcx> for Borrowck<'a, 'tcx> {
6166
loc: Location,
6267
) {
6368
self.borrows.apply_primary_statement_effect(&mut state.borrows, stmt, loc);
69+
self.pins.apply_primary_statement_effect(&mut state.pins, stmt, loc);
6470
self.uninits.apply_primary_statement_effect(&mut state.uninits, stmt, loc);
6571
self.ever_inits.apply_primary_statement_effect(&mut state.ever_inits, stmt, loc);
6672
}
@@ -72,6 +78,7 @@ impl<'a, 'tcx> Analysis<'tcx> for Borrowck<'a, 'tcx> {
7278
loc: Location,
7379
) {
7480
self.borrows.apply_early_terminator_effect(&mut state.borrows, term, loc);
81+
self.pins.apply_early_terminator_effect(&mut state.pins, term, loc);
7582
self.uninits.apply_early_terminator_effect(&mut state.uninits, term, loc);
7683
self.ever_inits.apply_early_terminator_effect(&mut state.ever_inits, term, loc);
7784
}
@@ -83,6 +90,7 @@ impl<'a, 'tcx> Analysis<'tcx> for Borrowck<'a, 'tcx> {
8390
loc: Location,
8491
) -> TerminatorEdges<'mir, 'tcx> {
8592
self.borrows.apply_primary_terminator_effect(&mut state.borrows, term, loc);
93+
self.pins.apply_primary_terminator_effect(&mut state.pins, term, loc);
8694
self.uninits.apply_primary_terminator_effect(&mut state.uninits, term, loc);
8795
self.ever_inits.apply_primary_terminator_effect(&mut state.ever_inits, term, loc);
8896

@@ -116,6 +124,8 @@ where
116124
fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117125
f.write_str("borrows: ")?;
118126
self.borrows.fmt_with(ctxt, f)?;
127+
f.write_str(" pins: ")?;
128+
self.pins.fmt_with(ctxt, f)?;
119129
f.write_str(" uninits: ")?;
120130
self.uninits.fmt_with(ctxt, f)?;
121131
f.write_str(" ever_inits: ")?;
@@ -134,6 +144,12 @@ where
134144
f.write_str("\n")?;
135145
}
136146

147+
if self.pins != old.pins {
148+
f.write_str("pins: ")?;
149+
self.pins.fmt_diff_with(&old.pins, ctxt, f)?;
150+
f.write_str("\n")?;
151+
}
152+
137153
if self.uninits != old.uninits {
138154
f.write_str("uninits: ")?;
139155
self.uninits.fmt_diff_with(&old.uninits, ctxt, f)?;
@@ -154,6 +170,7 @@ where
154170
#[derive(Clone, Debug, PartialEq, Eq)]
155171
pub(crate) struct BorrowckDomain {
156172
pub(crate) borrows: BorrowsDomain,
173+
pub(crate) pins: PinsDomain,
157174
pub(crate) uninits: MaybeUninitializedPlacesDomain,
158175
pub(crate) ever_inits: EverInitializedPlacesDomain,
159176
}
@@ -164,6 +181,16 @@ rustc_index::newtype_index! {
164181
pub struct BorrowIndex {}
165182
}
166183

184+
impl<C> DebugWithContext<C> for BorrowIndex {}
185+
186+
rustc_index::newtype_index! {
187+
#[orderable]
188+
#[debug_format = "pi{}"]
189+
pub struct PinIndex {}
190+
}
191+
192+
impl<C> DebugWithContext<C> for PinIndex {}
193+
167194
/// `Borrows` stores the data used in the analyses that track the flow
168195
/// of borrows.
169196
///
@@ -178,6 +205,19 @@ pub struct Borrows<'a, 'tcx> {
178205
borrows_out_of_scope_at_location: FxIndexMap<Location, Vec<BorrowIndex>>,
179206
}
180207

208+
/// `Pins` stores the data used in the analyses that track the flow
209+
/// of pins.
210+
///
211+
/// It uniquely identifies every pinned borrow by a
212+
/// `PinIndex`, and maps each such index to a `PinData`
213+
/// describing the pin. These indexes are used for representing the
214+
/// pins in compact bitvectors.
215+
pub(crate) struct Pins<'a, 'tcx> {
216+
tcx: TyCtxt<'tcx>,
217+
body: &'a Body<'tcx>,
218+
pin_set: &'a PinSet<'tcx>,
219+
}
220+
181221
struct OutOfScopePrecomputer<'a, 'tcx> {
182222
visited: DenseBitSet<mir::BasicBlock>,
183223
visit_stack: Vec<mir::BasicBlock>,
@@ -617,4 +657,153 @@ impl<'tcx> rustc_mir_dataflow::Analysis<'tcx> for Borrows<'_, 'tcx> {
617657
}
618658
}
619659

620-
impl<C> DebugWithContext<C> for BorrowIndex {}
660+
impl<'a, 'tcx> Pins<'a, 'tcx> {
661+
pub(crate) fn new(
662+
tcx: TyCtxt<'tcx>,
663+
body: &'a Body<'tcx>,
664+
pin_set: &'a PinSet<'tcx>,
665+
) -> Self {
666+
Pins { tcx, body, pin_set }
667+
}
668+
669+
/// Kill any pins on `place`.
670+
fn kill_pins_on_place(
671+
&self,
672+
state: &mut <Self as Analysis<'tcx>>::Domain,
673+
place: Place<'tcx>,
674+
) {
675+
debug!("kill_pins_on_place: place={:?}", place);
676+
677+
let other_pins_of_local = self
678+
.pin_set
679+
.local_map
680+
.get(&place.local)
681+
.into_iter()
682+
.flat_map(|ps| ps.iter())
683+
.copied();
684+
685+
// If the place is a local with no projections, all pins of this
686+
// local must be killed. This is purely an optimization so we don't have to call
687+
// `places_conflict` for every pin.
688+
if place.projection.is_empty() {
689+
state.kill_all(other_pins_of_local);
690+
return;
691+
}
692+
693+
// By passing `PlaceConflictBias::NoOverlap`, we conservatively assume that any given
694+
// pair of array indices are not equal, so that when `places_conflict` returns true, we
695+
// will be assured that two places being compared definitely denotes the same sets of
696+
// locations.
697+
let definitely_conflicting_pins = other_pins_of_local.filter(|&i| {
698+
places_conflict(
699+
self.tcx,
700+
self.body,
701+
self.pin_set[i].pinned_place,
702+
place,
703+
PlaceConflictBias::NoOverlap,
704+
)
705+
});
706+
707+
state.kill_all(definitely_conflicting_pins);
708+
}
709+
}
710+
711+
type PinsDomain = MixedBitSet<PinIndex>;
712+
713+
/// Forward dataflow computation of the set of pins that are in scope at a particular location.
714+
/// - we gen the introduced pins
715+
/// - we kill pins on locals going out of scope
716+
/// - we kill pins when the pinned place is moved or overwritten
717+
impl<'tcx> rustc_mir_dataflow::Analysis<'tcx> for Pins<'_, 'tcx> {
718+
type Domain = PinsDomain;
719+
720+
const NAME: &'static str = "pins";
721+
722+
fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
723+
// bottom = nothing is pinned yet
724+
MixedBitSet::new_empty(self.pin_set.len())
725+
}
726+
727+
fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) {
728+
// no pins have been created prior to function execution
729+
}
730+
731+
fn apply_early_statement_effect(
732+
&self,
733+
_state: &mut Self::Domain,
734+
_statement: &mir::Statement<'tcx>,
735+
_location: Location,
736+
) {
737+
// Pins don't go out of scope based on regions like borrows do
738+
}
739+
740+
fn apply_primary_statement_effect(
741+
&self,
742+
state: &mut Self::Domain,
743+
stmt: &mir::Statement<'tcx>,
744+
location: Location,
745+
) {
746+
match &stmt.kind {
747+
mir::StatementKind::Assign(box (lhs, rhs)) => {
748+
// Check if this is a Pin aggregate creation
749+
if let mir::Rvalue::Aggregate(box agg_kind, _) = rhs
750+
&& let mir::AggregateKind::Adt(adt_did, _, args, _, _) = agg_kind
751+
&& self.tcx.adt_def(adt_did).is_pin()
752+
&& args.type_at(0).is_ref()
753+
{
754+
// Generate the pin
755+
if let Some(index) = self.pin_set.get_index_of(&location) {
756+
state.gen_(index);
757+
}
758+
}
759+
760+
// Kill any pins on the place being assigned to
761+
self.kill_pins_on_place(state, *lhs);
762+
}
763+
764+
mir::StatementKind::StorageDead(local) => {
765+
// Kill all pins on locals that are going out of scope
766+
self.kill_pins_on_place(state, Place::from(*local));
767+
}
768+
769+
mir::StatementKind::FakeRead(..)
770+
| mir::StatementKind::SetDiscriminant { .. }
771+
| mir::StatementKind::StorageLive(..)
772+
| mir::StatementKind::Retag { .. }
773+
| mir::StatementKind::PlaceMention(..)
774+
| mir::StatementKind::AscribeUserType(..)
775+
| mir::StatementKind::Coverage(..)
776+
| mir::StatementKind::Intrinsic(..)
777+
| mir::StatementKind::ConstEvalCounter
778+
| mir::StatementKind::BackwardIncompatibleDropHint { .. }
779+
| mir::StatementKind::Nop => {}
780+
}
781+
}
782+
783+
fn apply_early_terminator_effect(
784+
&self,
785+
_state: &mut Self::Domain,
786+
_terminator: &mir::Terminator<'tcx>,
787+
_location: Location,
788+
) {
789+
// No early terminator effects for pins
790+
}
791+
792+
fn apply_primary_terminator_effect<'mir>(
793+
&self,
794+
state: &mut Self::Domain,
795+
terminator: &'mir mir::Terminator<'tcx>,
796+
_location: Location,
797+
) -> TerminatorEdges<'mir, 'tcx> {
798+
if let mir::TerminatorKind::InlineAsm { operands, .. } = &terminator.kind {
799+
for op in operands {
800+
if let mir::InlineAsmOperand::Out { place: Some(place), .. }
801+
| mir::InlineAsmOperand::InOut { out_place: Some(place), .. } = *op
802+
{
803+
self.kill_pins_on_place(state, place);
804+
}
805+
}
806+
}
807+
terminator.edges()
808+
}
809+
}

0 commit comments

Comments
 (0)