Skip to content

Commit 67b7412

Browse files
committed
WIP: implelment borrowck
1 parent d007e81 commit 67b7412

13 files changed

Lines changed: 364 additions & 214 deletions

File tree

compiler/rustc_borrowck/src/borrow_set.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,18 @@ impl<'tcx> BorrowData<'tcx> {
121121
pub fn assigned_place(&self) -> mir::Place<'tcx> {
122122
self.assigned_place
123123
}
124+
125+
pub(crate) fn is_pinned(&self) -> bool {
126+
matches!(self.pinnedness, Pinnedness::Pinned { .. })
127+
}
128+
129+
/// Returns the Pin result local if this borrow is pinned.
130+
pub(crate) fn pin_target_local(&self) -> Option<mir::Local> {
131+
match self.pinnedness {
132+
Pinnedness::Pinned { to, .. } => Some(to.local),
133+
Pinnedness::Not => None,
134+
}
135+
}
124136
}
125137

126138
impl<'tcx> fmt::Display for BorrowData<'tcx> {
@@ -335,7 +347,7 @@ impl<'a, 'tcx> Visitor<'tcx> for GatherBorrows<'a, 'tcx> {
335347
if let mir::StatementKind::Assign(box (pinned_place, ref rvalue)) = stmt.kind
336348
&& let mir::Rvalue::Aggregate(box agg_kind, operands) = rvalue
337349
&& let mir::AggregateKind::Adt(adt_did, _, args, _, _) = agg_kind
338-
&& tcx.adt_def(adt_did).is_pin()
350+
&& tcx.adt_def(*adt_did).is_pin()
339351
&& args.type_at(0).is_ref()
340352
&& let mir::Operand::Move(place) = operands[FieldIdx::ZERO]
341353
&& place == assigned_place

compiler/rustc_borrowck/src/dataflow.rs

Lines changed: 103 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,12 @@ impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> {
240240
borrows_out_of_scope_at_location: FxIndexMap::default(),
241241
};
242242
for (borrow_index, borrow_data) in borrow_set.iter_enumerated() {
243+
// Skip pinned borrows: their lifetime is extended beyond NLL scope.
244+
// They are killed by StorageDead of the Pin result local or by
245+
// reassignment of the borrowed place.
246+
if borrow_data.is_pinned() {
247+
continue;
248+
}
243249
let borrow_region = borrow_data.region;
244250
let location = borrow_data.reserve_location;
245251
prec.precompute_borrows_out_of_scope(borrow_index, borrow_region, location);
@@ -356,6 +362,10 @@ impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> {
356362
loans_out_of_scope_at_location: FxIndexMap::default(),
357363
};
358364
for (loan_idx, loan_data) in borrow_set.iter_enumerated() {
365+
// Skip pinned borrows: their lifetime is extended beyond NLL scope.
366+
if loan_data.is_pinned() {
367+
continue;
368+
}
359369
let loan_issued_at = loan_data.reserve_location;
360370
prec.precompute_loans_out_of_scope(loan_idx, loan_issued_at);
361371
}
@@ -546,6 +556,65 @@ impl<'a, 'tcx> Borrows<'a, 'tcx> {
546556

547557
state.kill_all(definitely_conflicting_borrows);
548558
}
559+
560+
/// Kill pinned borrows whose Pin result local matches `local`.
561+
/// This is called when the Pin value goes out of scope (StorageDead).
562+
fn kill_pinned_borrows_on_local(
563+
&self,
564+
state: &mut <Self as Analysis<'tcx>>::Domain,
565+
local: mir::Local,
566+
) {
567+
debug!("kill_pinned_borrows_on_local: local={:?}", local);
568+
569+
let to_kill: Vec<_> = self
570+
.borrow_set
571+
.iter_enumerated()
572+
.filter_map(|(idx, data)| {
573+
if data.pin_target_local() == Some(local) { Some(idx) } else { None }
574+
})
575+
.collect();
576+
577+
state.kill_all(to_kill.into_iter());
578+
}
579+
580+
/// Kill pinned borrows whose borrowed place conflicts with `place`.
581+
/// Called in the early phase so reassignment of a pinned place is allowed.
582+
fn kill_pinned_borrows_on_reassignment(
583+
&self,
584+
state: &mut <Self as Analysis<'tcx>>::Domain,
585+
place: Place<'tcx>,
586+
) {
587+
debug!("kill_pinned_borrows_on_reassignment: place={:?}", place);
588+
589+
let other_borrows_of_local = self
590+
.borrow_set
591+
.local_map
592+
.get(&place.local)
593+
.into_iter()
594+
.flat_map(|bs| bs.iter())
595+
.copied();
596+
597+
if place.projection.is_empty() {
598+
// Only kill pinned borrows, not regular ones
599+
let pinned_borrows = other_borrows_of_local
600+
.filter(|&i| self.borrow_set[i].is_pinned());
601+
state.kill_all(pinned_borrows);
602+
return;
603+
}
604+
605+
let conflicting_pinned_borrows = other_borrows_of_local.filter(|&i| {
606+
self.borrow_set[i].is_pinned()
607+
&& places_conflict(
608+
self.tcx,
609+
self.body,
610+
self.borrow_set[i].borrowed_place,
611+
place,
612+
PlaceConflictBias::NoOverlap,
613+
)
614+
});
615+
616+
state.kill_all(conflicting_pinned_borrows);
617+
}
549618
}
550619

551620
type BorrowsDomain = MixedBitSet<BorrowIndex>;
@@ -575,10 +644,17 @@ impl<'tcx> rustc_mir_dataflow::Analysis<'tcx> for Borrows<'_, 'tcx> {
575644
fn apply_early_statement_effect(
576645
&self,
577646
state: &mut Self::Domain,
578-
_statement: &mir::Statement<'tcx>,
647+
statement: &mir::Statement<'tcx>,
579648
location: Location,
580649
) {
581650
self.kill_loans_out_of_scope_at_location(state, location);
651+
652+
// For pinned borrows whose lifetime is extended beyond NLL scope,
653+
// kill them early when the pinned place is reassigned. This allows
654+
// the reassignment to proceed without conflicting with the borrow.
655+
if let mir::StatementKind::Assign(box (lhs, _)) = &statement.kind {
656+
self.kill_pinned_borrows_on_reassignment(state, *lhs);
657+
}
582658
}
583659

584660
fn apply_primary_statement_effect(
@@ -613,6 +689,8 @@ impl<'tcx> rustc_mir_dataflow::Analysis<'tcx> for Borrows<'_, 'tcx> {
613689
// Make sure there are no remaining borrows for locals that
614690
// are gone out of scope.
615691
self.kill_borrows_on_place(state, Place::from(*local));
692+
// Kill pinned borrows whose Pin result local is going out of scope.
693+
self.kill_pinned_borrows_on_local(state, *local);
616694
}
617695

618696
mir::StatementKind::FakeRead(..)
@@ -666,7 +744,7 @@ impl<'a, 'tcx> Pins<'a, 'tcx> {
666744
Pins { tcx, body, pin_set }
667745
}
668746

669-
/// Kill any pins on `place`.
747+
/// Kill any pins whose original pinned place conflicts with `place`.
670748
fn kill_pins_on_place(
671749
&self,
672750
state: &mut <Self as Analysis<'tcx>>::Domain,
@@ -706,6 +784,26 @@ impl<'a, 'tcx> Pins<'a, 'tcx> {
706784

707785
state.kill_all(definitely_conflicting_pins);
708786
}
787+
788+
/// Kill any pins whose Pin result local is `local`.
789+
/// This is used when the Pin value goes out of scope (StorageDead).
790+
fn kill_pins_by_pin_local(
791+
&self,
792+
state: &mut <Self as Analysis<'tcx>>::Domain,
793+
local: mir::Local,
794+
) {
795+
debug!("kill_pins_by_pin_local: local={:?}", local);
796+
797+
let pins_of_local = self
798+
.pin_set
799+
.pin_local_map
800+
.get(&local)
801+
.into_iter()
802+
.flat_map(|ps| ps.iter())
803+
.copied();
804+
805+
state.kill_all(pins_of_local);
806+
}
709807
}
710808

711809
type PinsDomain = MixedBitSet<PinIndex>;
@@ -748,7 +846,7 @@ impl<'tcx> rustc_mir_dataflow::Analysis<'tcx> for Pins<'_, 'tcx> {
748846
// Check if this is a Pin aggregate creation
749847
if let mir::Rvalue::Aggregate(box agg_kind, _) = rhs
750848
&& let mir::AggregateKind::Adt(adt_did, _, args, _, _) = agg_kind
751-
&& self.tcx.adt_def(adt_did).is_pin()
849+
&& self.tcx.adt_def(*adt_did).is_pin()
752850
&& args.type_at(0).is_ref()
753851
{
754852
// Generate the pin
@@ -764,6 +862,8 @@ impl<'tcx> rustc_mir_dataflow::Analysis<'tcx> for Pins<'_, 'tcx> {
764862
mir::StatementKind::StorageDead(local) => {
765863
// Kill all pins on locals that are going out of scope
766864
self.kill_pins_on_place(state, Place::from(*local));
865+
// Also kill pins whose Pin result local is going out of scope
866+
self.kill_pins_by_pin_local(state, *local);
767867
}
768868

769869
mir::StatementKind::FakeRead(..)

compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,8 @@ use super::{DescribePlaceOpt, RegionName, RegionNameSource, UseSpans};
4444
use crate::borrow_set::{BorrowData, TwoPhaseActivation};
4545
use crate::diagnostics::conflict_errors::StorageDeadOrDrop::LocalStorageDead;
4646
use crate::diagnostics::{CapturedMessageOpt, call_kind, find_all_local_uses};
47-
use crate::pin_set::PinData;
4847
use crate::prefixes::IsPrefixOf;
49-
use crate::{InitializationRequiringAction, MirBorrowckCtxt, ReadOrWrite, WriteKind, borrowck_errors};
48+
use crate::{InitializationRequiringAction, MirBorrowckCtxt, WriteKind, borrowck_errors};
5049

5150
#[derive(Debug)]
5251
struct MoveSite {
@@ -1684,45 +1683,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
16841683
err
16851684
}
16861685

1687-
pub(crate) fn report_pin_violation(
1688-
&mut self,
1689-
_location: Location,
1690-
place_span: (Place<'tcx>, Span),
1691-
pin_data: &PinData<'tcx>,
1692-
access_kind: ReadOrWrite,
1693-
) {
1694-
use ReadOrWrite::Write;
1695-
use WriteKind::{Move, MutableBorrow};
1696-
1697-
let (place, span) = place_span;
1698-
1699-
let violation_kind = match access_kind {
1700-
Write(Move) => "move",
1701-
Write(MutableBorrow(_)) => "mutably borrow",
1702-
_ => unreachable!(),
1703-
};
1704-
1705-
let place_desc = self
1706-
.describe_place(place.as_ref())
1707-
.unwrap_or_else(|| "value".to_string());
1708-
1709-
let mut err = self.root_cx.tcx.dcx().struct_span_err(
1710-
span,
1711-
format!("cannot {} `{}` because it is pinned", violation_kind, place_desc),
1712-
);
1713-
1714-
// Show where the pin was created
1715-
let pin_span = self.body.source_info(pin_data.location).span;
1716-
err.span_note(pin_span, "pin created here");
1717-
1718-
err.help(
1719-
"pinned values cannot be moved or mutably borrowed, \
1720-
as this would invalidate the pin guarantee",
1721-
);
1722-
1723-
self.buffer_error(err);
1724-
}
1725-
17261686
pub(crate) fn report_conflicting_borrow(
17271687
&self,
17281688
location: Location,

compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,13 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
612612
borrow: &BorrowData<'tcx>,
613613
kind_place: Option<(WriteKind, Place<'tcx>)>,
614614
) -> BorrowExplanation<'tcx> {
615+
// For pinned borrows, the borrow lifetime is extended beyond the NLL region.
616+
// The region inference can't explain why the borrow is still active,
617+
// so return Unexplained and let the caller provide context.
618+
if borrow.is_pinned() {
619+
return BorrowExplanation::Unexplained;
620+
}
621+
615622
let regioncx = &self.regioncx;
616623
let body: &Body<'_> = self.body;
617624
let tcx = self.infcx.tcx;

compiler/rustc_borrowck/src/lib.rs

Lines changed: 12 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,12 @@ use rustc_mir_dataflow::points::DenseLocationMap;
4848
use rustc_mir_dataflow::{Analysis, EntryStates, Results, ResultsVisitor, visit_results};
4949
use rustc_session::lint::builtin::{TAIL_EXPR_DROP_ORDER, UNUSED_MUT};
5050
use rustc_span::{ErrorGuaranteed, Span, Symbol};
51-
use rustc_trait_selection::infer::InferCtxtExt;
5251
use smallvec::SmallVec;
5352
use tracing::{debug, instrument};
5453

5554
use crate::borrow_set::{BorrowData, BorrowSet};
5655
use crate::consumers::{BodyWithBorrowckFacts, RustcFacts};
57-
use crate::dataflow::{BorrowIndex, Borrowck, BorrowckDomain, Borrows, PinIndex, Pins};
56+
use crate::dataflow::{BorrowIndex, Borrowck, BorrowckDomain, Borrows, Pins};
5857
use crate::diagnostics::{
5958
AccessKind, BorrowckDiagnosticsBuffer, IllegalMoveOriginKind, MoveError, RegionName,
6059
};
@@ -775,6 +774,7 @@ pub(crate) struct MirBorrowckCtxt<'a, 'infcx, 'tcx> {
775774
borrow_set: &'a BorrowSet<'tcx>,
776775

777776
/// The set of pins extracted from the MIR
777+
#[allow(dead_code)]
778778
pin_set: &'a PinSet<'tcx>,
779779

780780
/// Information about upvars not necessarily preserved in types or MIR
@@ -1032,6 +1032,15 @@ impl<'a, 'tcx> ResultsVisitor<'tcx, Borrowck<'a, 'tcx>> for MirBorrowckCtxt<'a,
10321032
// so this "extra check" serves as a kind of backup.
10331033
for i in state.borrows.iter() {
10341034
let borrow = &self.borrow_set[i];
1035+
// Skip pinned borrows: their lifetime is managed by
1036+
// StorageDead of the Pin result local and reassignment,
1037+
// not by NLL regions. On unwind paths, StorageDead may
1038+
// not have been emitted, so these borrows can appear
1039+
// spuriously alive. The normal dataflow-based conflict
1040+
// detection already catches pin violations.
1041+
if borrow.is_pinned() {
1042+
continue;
1043+
}
10351044
self.check_for_invalidation_at_exit(loc, borrow, span);
10361045
}
10371046
}
@@ -1249,10 +1258,8 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
12491258
location,
12501259
);
12511260
let conflict_error = self.check_access_for_conflict(location, place_span, sd, rw, state);
1252-
let pin_conflict_error =
1253-
self.check_access_for_pin_conflict(location, place_span, sd, rw, state);
12541261

1255-
if conflict_error || mutability_error || pin_conflict_error {
1262+
if conflict_error || mutability_error {
12561263
debug!("access_place: logging error place_span=`{:?}` kind=`{:?}`", place_span, kind);
12571264
self.access_place_error_reported.insert((place_span.0, place_span.1));
12581265
}
@@ -1276,77 +1283,6 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
12761283
}
12771284
}
12781285

1279-
fn pins_in_scope<'s>(
1280-
&self,
1281-
_location: Location,
1282-
state: &'s BorrowckDomain,
1283-
) -> &'s MixedBitSet<PinIndex> {
1284-
&state.pins
1285-
}
1286-
1287-
#[instrument(level = "debug", skip(self, state))]
1288-
fn check_access_for_pin_conflict(
1289-
&mut self,
1290-
location: Location,
1291-
place_span: (Place<'tcx>, Span),
1292-
sd: AccessDepth,
1293-
rw: ReadOrWrite,
1294-
state: &BorrowckDomain,
1295-
) -> bool {
1296-
use ReadOrWrite::Write;
1297-
use WriteKind::{Move, MutableBorrow};
1298-
1299-
// Early return: only check Move and MutableBorrow operations
1300-
let is_violating_access = matches!(rw, Write(Move) | Write(MutableBorrow(_)));
1301-
if !is_violating_access {
1302-
return false;
1303-
}
1304-
1305-
let mut error_reported = false;
1306-
let pins_in_scope = self.pins_in_scope(location, state);
1307-
1308-
// Check each active pin for conflicts
1309-
for pin_index in pins_in_scope.iter() {
1310-
let pin_data = &self.pin_set[pin_index];
1311-
1312-
// Check if accessed place conflicts with pinned place
1313-
if places_conflict(
1314-
self.infcx.tcx,
1315-
self.body,
1316-
pin_data.pinned_place,
1317-
place_span.0,
1318-
PlaceConflictBias::Overlap,
1319-
) {
1320-
// Get the type of the pinned place (Pin<Ptr>)
1321-
let pinned_ty = pin_data.pinned_place.ty(self.body, self.infcx.tcx).ty;
1322-
1323-
// Extract the pointee type from Pin<&[mut] T> to get T
1324-
if let Some(pointee_ty) = pinned_ty.builtin_deref(true) {
1325-
// Check if the pointee type implements Unpin
1326-
let unpin_trait = self.infcx.tcx.lang_items().unpin_trait();
1327-
if let Some(unpin_trait) = unpin_trait {
1328-
let param_env = self.infcx.tcx.param_env(
1329-
self.body.source.def_id()
1330-
);
1331-
let ty_is_unpin = self.infcx.type_implements_trait(
1332-
unpin_trait,
1333-
[pointee_ty],
1334-
param_env,
1335-
).must_apply_modulo_regions();
1336-
1337-
// Only report violation if the type does NOT implement Unpin
1338-
if !ty_is_unpin {
1339-
error_reported = true;
1340-
self.report_pin_violation(location, place_span, pin_data, rw);
1341-
}
1342-
}
1343-
}
1344-
}
1345-
}
1346-
1347-
error_reported
1348-
}
1349-
13501286
#[instrument(level = "debug", skip(self, state))]
13511287
fn check_access_for_conflict(
13521288
&mut self,

0 commit comments

Comments
 (0)