Skip to content

Commit 8372e7b

Browse files
committed
Auto merge of #50783 - pnkfelix:issue-27282-match-borrows-its-input-take-three, r=nikomatsakis
every match arm reads the match's borrowed input This PR changes the `match` codegen under NLL (and just NLL, at least for now) to make the following adjustments: * It adds a `-Z disable-ast-check-for-mutation-in-guard` which, as described, turns off the naive (conservative but also not 100% sound) check for mutation in guards of match arms. * We now borrow the match input at the outset and emit a special `ReadForMatch` statement (that, according to the *static* semantics, reads that borrowed match input) at the start of each match arm. The intent here is to catch cases where the match guard mutates the match input, either via an independent borrow or via `ref mut` borrows in that arm's pattern. * In order to ensure that `ref mut` borrows do not actually conflict with the emitted `ReadForMatch` statements, I expanded the two-phase-borrow system slightly, and also changed the MIR code gen so that under NLL, when there is a guard on a match arm, then each pattern variable ends up having *three* temporaries associated with it: 1. The first temporary will hold the substructure being matched; this is what we will move the (substructural) value into *if* the guard succeeds. 2. The second temporary also corresponds to the same value as the first, but we are just constructing this temporarily for use during the scope of the guard; it is unaliased and its sole referrer is the third temporary. 3. The third temporary is a reference to the second temporary. * (This sounds complicated, I know, but its actually *simpler* than what I was doing before and had checked into the repo, which was to sometimes construct the final value and then take a reference to it before evaluating the guard. See also PR #49870.) Fix #27282 This also provides a path towards resolving #24535 aka rust-lang/rfcs#1006, at least once the `-Z disable-ast-check-for-mutation-in-guard` is just turned on by default (under NLL, that is. It is not sound under AST-borrowck). * But I did not want to make `#![feature(nll)]` imply that as part of this PR; that seemed like too drastic a change to me.
2 parents ec99b22 + 9d5cdc9 commit 8372e7b

37 files changed

+790
-235
lines changed

src/librustc/ich/impls_mir.rs

+3
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,9 @@ for mir::StatementKind<'gcx> {
241241
place.hash_stable(hcx, hasher);
242242
rvalue.hash_stable(hcx, hasher);
243243
}
244+
mir::StatementKind::ReadForMatch(ref place) => {
245+
place.hash_stable(hcx, hasher);
246+
}
244247
mir::StatementKind::SetDiscriminant { ref place, variant_index } => {
245248
place.hash_stable(hcx, hasher);
246249
variant_index.hash_stable(hcx, hasher);

src/librustc/mir/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,10 @@ pub enum StatementKind<'tcx> {
12251225
/// Write the RHS Rvalue to the LHS Place.
12261226
Assign(Place<'tcx>, Rvalue<'tcx>),
12271227

1228+
/// This represents all the reading that a pattern match may do
1229+
/// (e.g. inspecting constants and discriminant values).
1230+
ReadForMatch(Place<'tcx>),
1231+
12281232
/// Write the discriminant for a variant to the enum Place.
12291233
SetDiscriminant { place: Place<'tcx>, variant_index: usize },
12301234

@@ -1327,6 +1331,7 @@ impl<'tcx> Debug for Statement<'tcx> {
13271331
use self::StatementKind::*;
13281332
match self.kind {
13291333
Assign(ref place, ref rv) => write!(fmt, "{:?} = {:?}", place, rv),
1334+
ReadForMatch(ref place) => write!(fmt, "ReadForMatch({:?})", place),
13301335
// (reuse lifetime rendering policy from ppaux.)
13311336
EndRegion(ref ce) => write!(fmt, "EndRegion({})", ty::ReScope(*ce)),
13321337
Validate(ref op, ref places) => write!(fmt, "Validate({:?}, {:?})", op, places),
@@ -2212,6 +2217,7 @@ BraceStructTypeFoldableImpl! {
22122217
EnumTypeFoldableImpl! {
22132218
impl<'tcx> TypeFoldable<'tcx> for StatementKind<'tcx> {
22142219
(StatementKind::Assign)(a, b),
2220+
(StatementKind::ReadForMatch)(place),
22152221
(StatementKind::SetDiscriminant) { place, variant_index },
22162222
(StatementKind::StorageLive)(a),
22172223
(StatementKind::StorageDead)(a),

src/librustc/mir/visit.rs

+5
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,11 @@ macro_rules! make_mir_visitor {
355355
ref $($mutability)* rvalue) => {
356356
self.visit_assign(block, place, rvalue, location);
357357
}
358+
StatementKind::ReadForMatch(ref $($mutability)* place) => {
359+
self.visit_place(place,
360+
PlaceContext::Inspect,
361+
location);
362+
}
358363
StatementKind::EndRegion(_) => {}
359364
StatementKind::Validate(_, ref $($mutability)* places) => {
360365
for operand in places {

src/librustc/session/config.rs

+6
Original file line numberDiff line numberDiff line change
@@ -1290,16 +1290,22 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
12901290
useful for profiling / PGO."),
12911291
relro_level: Option<RelroLevel> = (None, parse_relro_level, [TRACKED],
12921292
"choose which RELRO level to use"),
1293+
disable_ast_check_for_mutation_in_guard: bool = (false, parse_bool, [UNTRACKED],
1294+
"skip AST-based mutation-in-guard check (mir-borrowck provides more precise check)"),
12931295
nll_subminimal_causes: bool = (false, parse_bool, [UNTRACKED],
12941296
"when tracking region error causes, accept subminimal results for faster execution."),
12951297
nll_facts: bool = (false, parse_bool, [UNTRACKED],
12961298
"dump facts from NLL analysis into side files"),
12971299
disable_nll_user_type_assert: bool = (false, parse_bool, [UNTRACKED],
12981300
"disable user provided type assertion in NLL"),
1301+
nll_dont_emit_read_for_match: bool = (false, parse_bool, [UNTRACKED],
1302+
"in match codegen, do not include ReadForMatch statements (used by mir-borrowck)"),
12991303
polonius: bool = (false, parse_bool, [UNTRACKED],
13001304
"enable polonius-based borrow-checker"),
13011305
codegen_time_graph: bool = (false, parse_bool, [UNTRACKED],
13021306
"generate a graphical HTML report of time spent in codegen and LLVM"),
1307+
trans_time_graph: bool = (false, parse_bool, [UNTRACKED],
1308+
"generate a graphical HTML report of time spent in trans and LLVM"),
13031309
thinlto: Option<bool> = (None, parse_opt_bool, [TRACKED],
13041310
"enable ThinLTO when possible"),
13051311
inline_in_all_cgus: Option<bool> = (None, parse_opt_bool, [TRACKED],

src/librustc/ty/context.rs

+19
Original file line numberDiff line numberDiff line change
@@ -1344,12 +1344,31 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
13441344
self.on_disk_query_result_cache.serialize(self.global_tcx(), encoder)
13451345
}
13461346

1347+
/// If true, we should use a naive AST walk to determine if match
1348+
/// guard could perform bad mutations (or mutable-borrows).
1349+
pub fn check_for_mutation_in_guard_via_ast_walk(self) -> bool {
1350+
!self.sess.opts.debugging_opts.disable_ast_check_for_mutation_in_guard
1351+
}
1352+
13471353
/// If true, we should use the MIR-based borrowck (we may *also* use
13481354
/// the AST-based borrowck).
13491355
pub fn use_mir_borrowck(self) -> bool {
13501356
self.borrowck_mode().use_mir()
13511357
}
13521358

1359+
/// If true, make MIR codegen for `match` emit a temp that holds a
1360+
/// borrow of the input to the match expression.
1361+
pub fn generate_borrow_of_any_match_input(&self) -> bool {
1362+
self.emit_read_for_match()
1363+
}
1364+
1365+
/// If true, make MIR codegen for `match` emit ReadForMatch
1366+
/// statements (which simulate the maximal effect of executing the
1367+
/// patterns in a match arm).
1368+
pub fn emit_read_for_match(&self) -> bool {
1369+
self.use_mir_borrowck() && !self.sess.opts.debugging_opts.nll_dont_emit_read_for_match
1370+
}
1371+
13531372
/// If true, pattern variables for use in guards on match arms
13541373
/// will be bound as references to the data, and occurrences of
13551374
/// those variables in the guard expression will implicitly

src/librustc_codegen_llvm/mir/statement.rs

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ impl<'a, 'tcx> FunctionCx<'a, 'tcx> {
8282
asm::codegen_inline_asm(&bx, asm, outputs, input_vals);
8383
bx
8484
}
85+
mir::StatementKind::ReadForMatch(_) |
8586
mir::StatementKind::EndRegion(_) |
8687
mir::StatementKind::Validate(..) |
8788
mir::StatementKind::UserAssertTy(..) |

src/librustc_mir/borrow_check/borrow_set.rs

+34-10
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,25 @@ impl<'tcx> Index<BorrowIndex> for BorrowSet<'tcx> {
5353
}
5454
}
5555

56+
/// Every two-phase borrow has *exactly one* use (or else it is not a
57+
/// proper two-phase borrow under our current definition). However, not
58+
/// all uses are actually ones that activate the reservation.. In
59+
/// particular, a shared borrow of a `&mut` does not activate the
60+
/// reservation.
61+
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
62+
crate enum TwoPhaseUse {
63+
MutActivate,
64+
SharedUse,
65+
}
66+
5667
#[derive(Debug)]
5768
crate struct BorrowData<'tcx> {
5869
/// Location where the borrow reservation starts.
5970
/// In many cases, this will be equal to the activation location but not always.
6071
crate reserve_location: Location,
6172
/// Location where the borrow is activated. None if this is not a
6273
/// 2-phase borrow.
63-
crate activation_location: Option<Location>,
74+
crate activation_location: Option<(TwoPhaseUse, Location)>,
6475
/// What kind of borrow this is
6576
crate kind: mir::BorrowKind,
6677
/// The region for which this borrow is live
@@ -215,17 +226,16 @@ impl<'a, 'gcx, 'tcx> Visitor<'tcx> for GatherBorrows<'a, 'gcx, 'tcx> {
215226
Some(&borrow_index) => {
216227
let borrow_data = &mut self.idx_vec[borrow_index];
217228

218-
// Watch out: the use of TMP in the borrow
219-
// itself doesn't count as an
220-
// activation. =)
229+
// Watch out: the use of TMP in the borrow itself
230+
// doesn't count as an activation. =)
221231
if borrow_data.reserve_location == location && context == PlaceContext::Store {
222232
return;
223233
}
224234

225235
if let Some(other_activation) = borrow_data.activation_location {
226236
span_bug!(
227237
self.mir.source_info(location).span,
228-
"found two activations for 2-phase borrow temporary {:?}: \
238+
"found two uses for 2-phase borrow temporary {:?}: \
229239
{:?} and {:?}",
230240
temp,
231241
location,
@@ -235,11 +245,25 @@ impl<'a, 'gcx, 'tcx> Visitor<'tcx> for GatherBorrows<'a, 'gcx, 'tcx> {
235245

236246
// Otherwise, this is the unique later use
237247
// that we expect.
238-
borrow_data.activation_location = Some(location);
239-
self.activation_map
240-
.entry(location)
241-
.or_insert(Vec::new())
242-
.push(borrow_index);
248+
249+
let two_phase_use;
250+
251+
match context {
252+
// The use of TMP in a shared borrow does not
253+
// count as an actual activation.
254+
PlaceContext::Borrow { kind: mir::BorrowKind::Shared, .. } => {
255+
two_phase_use = TwoPhaseUse::SharedUse;
256+
}
257+
_ => {
258+
two_phase_use = TwoPhaseUse::MutActivate;
259+
self.activation_map
260+
.entry(location)
261+
.or_insert(Vec::new())
262+
.push(borrow_index);
263+
}
264+
}
265+
266+
borrow_data.activation_location = Some((two_phase_use, location));
243267
}
244268

245269
None => {}

src/librustc_mir/borrow_check/mod.rs

+19-8
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,14 @@ impl<'cx, 'gcx, 'tcx> DataflowResultsConsumer<'cx, 'tcx> for MirBorrowckCtxt<'cx
423423
flow_state,
424424
);
425425
}
426+
StatementKind::ReadForMatch(ref place) => {
427+
self.access_place(ContextKind::ReadForMatch.new(location),
428+
(place, span),
429+
(Deep, Read(ReadKind::Borrow(BorrowKind::Shared))),
430+
LocalMutationIsAllowed::No,
431+
flow_state,
432+
);
433+
}
426434
StatementKind::SetDiscriminant {
427435
ref place,
428436
variant_index: _,
@@ -1689,14 +1697,16 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
16891697
);
16901698
let mut error_reported = false;
16911699
match kind {
1692-
Reservation(WriteKind::MutableBorrow(BorrowKind::Unique))
1693-
| Write(WriteKind::MutableBorrow(BorrowKind::Unique)) => {
1694-
if let Err(_place_err) = self.is_mutable(place, LocalMutationIsAllowed::Yes) {
1695-
span_bug!(span, "&unique borrow for {:?} should not fail", place);
1696-
}
1697-
}
1698-
Reservation(WriteKind::MutableBorrow(BorrowKind::Mut { .. }))
1699-
| Write(WriteKind::MutableBorrow(BorrowKind::Mut { .. })) => {
1700+
Reservation(WriteKind::MutableBorrow(borrow_kind @ BorrowKind::Unique))
1701+
| Reservation(WriteKind::MutableBorrow(borrow_kind @ BorrowKind::Mut { .. }))
1702+
| Write(WriteKind::MutableBorrow(borrow_kind @ BorrowKind::Unique))
1703+
| Write(WriteKind::MutableBorrow(borrow_kind @ BorrowKind::Mut { .. })) =>
1704+
{
1705+
let is_local_mutation_allowed = match borrow_kind {
1706+
BorrowKind::Unique => LocalMutationIsAllowed::Yes,
1707+
BorrowKind::Mut { .. } => is_local_mutation_allowed,
1708+
BorrowKind::Shared => unreachable!(),
1709+
};
17001710
match self.is_mutable(place, is_local_mutation_allowed) {
17011711
Ok(root_place) => self.add_used_mut(root_place, flow_state),
17021712
Err(place_err) => {
@@ -2090,6 +2100,7 @@ enum ContextKind {
20902100
CallDest,
20912101
Assert,
20922102
Yield,
2103+
ReadForMatch,
20932104
StorageDead,
20942105
}
20952106

src/librustc_mir/borrow_check/nll/invalidation.rs

+8
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,14 @@ impl<'cg, 'cx, 'tcx, 'gcx> Visitor<'tcx> for InvalidationGenerator<'cg, 'cx, 'tc
9393
JustWrite
9494
);
9595
}
96+
StatementKind::ReadForMatch(ref place) => {
97+
self.access_place(
98+
ContextKind::ReadForMatch.new(location),
99+
place,
100+
(Deep, Read(ReadKind::Borrow(BorrowKind::Shared))),
101+
LocalMutationIsAllowed::No,
102+
);
103+
}
96104
StatementKind::SetDiscriminant {
97105
ref place,
98106
variant_index: _,

src/librustc_mir/borrow_check/nll/type_check/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -836,7 +836,8 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
836836
);
837837
}
838838
}
839-
StatementKind::StorageLive(_)
839+
StatementKind::ReadForMatch(_)
840+
| StatementKind::StorageLive(_)
840841
| StatementKind::StorageDead(_)
841842
| StatementKind::InlineAsm { .. }
842843
| StatementKind::EndRegion(_)

src/librustc_mir/borrow_check/path_utils.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
/// allowed to be split into separate Reservation and
1313
/// Activation phases.
1414
use borrow_check::ArtificialField;
15-
use borrow_check::borrow_set::{BorrowSet, BorrowData};
15+
use borrow_check::borrow_set::{BorrowSet, BorrowData, TwoPhaseUse};
1616
use borrow_check::{Context, Overlap};
1717
use borrow_check::{ShallowOrDeep, Deep, Shallow};
1818
use dataflow::indexes::BorrowIndex;
@@ -431,10 +431,13 @@ pub(super) fn is_active<'tcx>(
431431
) -> bool {
432432
debug!("is_active(borrow_data={:?}, location={:?})", borrow_data, location);
433433

434-
// If this is not a 2-phase borrow, it is always active.
435434
let activation_location = match borrow_data.activation_location {
436-
Some(v) => v,
435+
// If this is not a 2-phase borrow, it is always active.
437436
None => return true,
437+
// And if the unique 2-phase use is not an activation, then it is *never* active.
438+
Some((TwoPhaseUse::SharedUse, _)) => return false,
439+
// Otherwise, we derive info from the activation point `v`:
440+
Some((TwoPhaseUse::MutActivate, v)) => v,
438441
};
439442

440443
// Otherwise, it is active for every location *except* in between

src/librustc_mir/build/expr/as_place.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
//! See docs in build/expr/mod.rs
1212
1313
use build::{BlockAnd, BlockAndExtension, Builder};
14-
use build::ForGuard::{OutsideGuard, WithinGuard};
14+
use build::ForGuard::{OutsideGuard, RefWithinGuard, ValWithinGuard};
1515
use build::expr::category::Category;
1616
use hair::*;
1717
use rustc::mir::*;
@@ -88,10 +88,11 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
8888
}
8989
ExprKind::VarRef { id } => {
9090
let place = if this.is_bound_var_in_guard(id) {
91-
let index = this.var_local_id(id, WithinGuard);
9291
if this.hir.tcx().all_pat_vars_are_implicit_refs_within_guards() {
92+
let index = this.var_local_id(id, RefWithinGuard);
9393
Place::Local(index).deref()
9494
} else {
95+
let index = this.var_local_id(id, ValWithinGuard);
9596
Place::Local(index)
9697
}
9798
} else {

0 commit comments

Comments
 (0)