]> git.lizzy.rs Git - rust.git/commitdiff
rust-lang/rust#27282: emit `ReadForMatch` on each match arm.
authorFelix S. Klock II <pnkfelix@pnkfx.org>
Fri, 4 May 2018 10:05:10 +0000 (12:05 +0200)
committerFelix S. Klock II <pnkfelix@pnkfx.org>
Tue, 29 May 2018 21:02:39 +0000 (23:02 +0200)
Also, turn on ReadForMatch emission by default (when using NLL).

src/librustc/session/config.rs
src/librustc/ty/context.rs
src/librustc_mir/build/matches/mod.rs

index 45af66815fc2dd4bcd4c2afd73384e5bbe2e560f..35538e5d02a23b9cc390da69e33983cf32051933 100644 (file)
@@ -1298,10 +1298,14 @@ fn parse_cross_lang_lto(slot: &mut CrossLangLto, v: Option<&str>) -> bool {
                        "dump facts from NLL analysis into side files"),
     disable_nll_user_type_assert: bool = (false, parse_bool, [UNTRACKED],
         "disable user provided type assertion in NLL"),
+    nll_dont_emit_read_for_match: bool = (false, parse_bool, [UNTRACKED],
+        "in match codegen, do not include ReadForMatch statements (used by mir-borrowck)"),
     polonius: bool = (false, parse_bool, [UNTRACKED],
         "enable polonius-based borrow-checker"),
     codegen_time_graph: bool = (false, parse_bool, [UNTRACKED],
         "generate a graphical HTML report of time spent in codegen and LLVM"),
+    trans_time_graph: bool = (false, parse_bool, [UNTRACKED],
+        "generate a graphical HTML report of time spent in trans and LLVM"),
     thinlto: Option<bool> = (None, parse_opt_bool, [TRACKED],
         "enable ThinLTO when possible"),
     inline_in_all_cgus: Option<bool> = (None, parse_opt_bool, [TRACKED],
index 1ee28a65424d505c6bc74a98cb70d523a5d2ec2e..68f55b4993301fed0391b2246730d29878916c76 100644 (file)
@@ -1356,6 +1356,19 @@ pub fn use_mir_borrowck(self) -> bool {
         self.borrowck_mode().use_mir()
     }
 
+    /// If true, make MIR codegen for `match` emit a temp that holds a
+    /// borrow of the input to the match expression.
+    pub fn generate_borrow_of_any_match_input(&self) -> bool {
+        self.emit_read_for_match()
+    }
+
+    /// If true, make MIR codegen for `match` emit ReadForMatch
+    /// statements (which simulate the maximal effect of executing the
+    /// patterns in a match arm).
+    pub fn emit_read_for_match(&self) -> bool {
+        self.use_mir_borrowck() && !self.sess.opts.debugging_opts.nll_dont_emit_read_for_match
+    }
+
     /// If true, pattern variables for use in guards on match arms
     /// will be bound as references to the data, and occurrences of
     /// those variables in the guard expression will implicitly
index f3953d0877c357d47fd473001024e002bbdcd4f4..67c9db1bf9ee2d6c550854e9cad76baa171581ed 100644 (file)
 mod test;
 mod util;
 
+/// Injects a borrow of `place`. The region is unknown at this point; we rely on NLL
+/// inference to find an appropriate one. Therefore you can only call this when NLL
+/// is turned on.
+fn inject_borrow<'a, 'gcx, 'tcx>(tcx: ty::TyCtxt<'a, 'gcx, 'tcx>,
+                                 place: Place<'tcx>)
+                                 -> Rvalue<'tcx> {
+    assert!(tcx.use_mir_borrowck());
+    Rvalue::Ref(tcx.types.re_empty, BorrowKind::Shared, place)
+}
+
 /// ArmHasGuard is isomorphic to a boolean flag. It indicates whether
 /// a match arm has a guard expression attached to it.
 #[derive(Copy, Clone, Debug)]
@@ -43,6 +53,7 @@ pub fn match_expr(&mut self,
                       discriminant: ExprRef<'tcx>,
                       arms: Vec<Arm<'tcx>>)
                       -> BlockAnd<()> {
+        let tcx = self.hir.tcx();
         let discriminant_place = unpack!(block = self.as_place(block, discriminant));
 
         // Matching on a `discriminant_place` with an uninhabited type doesn't
@@ -55,16 +66,34 @@ pub fn match_expr(&mut self,
         // HACK(eddyb) Work around the above issue by adding a dummy inspection
         // of `discriminant_place`, specifically by applying `Rvalue::Discriminant`
         // (which will work regardless of type) and storing the result in a temp.
+        //
+        // FIXME: would just the borrow into `borrowed_input_temp`
+        // also achieve the desired effect here? TBD.
         let dummy_source_info = self.source_info(span);
         let dummy_access = Rvalue::Discriminant(discriminant_place.clone());
-        let dummy_ty = dummy_access.ty(&self.local_decls, self.hir.tcx());
+        let dummy_ty = dummy_access.ty(&self.local_decls, tcx);
         let dummy_temp = self.temp(dummy_ty, dummy_source_info.span);
         self.cfg.push_assign(block, dummy_source_info, &dummy_temp, dummy_access);
 
+        let source_info = self.source_info(span);
+        let borrowed_input_temp = if tcx.generate_borrow_of_any_match_input() {
+            let borrowed_input = inject_borrow(tcx, discriminant_place.clone());
+            let borrowed_input_ty = borrowed_input.ty(&self.local_decls, tcx);
+            let borrowed_input_temp = self.temp(borrowed_input_ty, span);
+            self.cfg.push_assign(block, source_info, &borrowed_input_temp, borrowed_input);
+            Some(borrowed_input_temp)
+        } else {
+            None
+        };
+
         let mut arm_blocks = ArmBlocks {
             blocks: arms.iter()
-                        .map(|_| self.cfg.start_new_block())
-                        .collect(),
+                .map(|_| {
+                    let arm_block = self.cfg.start_new_block();
+                    arm_block
+                })
+                .collect(),
+
         };
 
         // Get the arm bodies and their scopes, while declaring bindings.
@@ -81,7 +110,9 @@ pub fn match_expr(&mut self,
         // create binding start block for link them by false edges
         let candidate_count = arms.iter().fold(0, |ac, c| ac + c.patterns.len());
         let pre_binding_blocks: Vec<_> = (0..candidate_count + 1)
-            .map(|_| self.cfg.start_new_block()).collect();
+            .map(|_| self.cfg.start_new_block())
+
+            .collect();
 
         // assemble a list of candidates: there is one candidate per
         // pattern, which means there may be more than one candidate
@@ -99,6 +130,61 @@ pub fn match_expr(&mut self,
                 .zip(pre_binding_blocks.iter().zip(pre_binding_blocks.iter().skip(1)))
                 .map(|((arm_index, pattern, guard),
                        (pre_binding_block, next_candidate_pre_binding_block))| {
+
+                    if let (true, Some(borrow_temp)) = (tcx.emit_read_for_match(),
+                                                        borrowed_input_temp.clone()) {
+                        // inject a fake read of the borrowed input at
+                        // the start of each arm's pattern testing
+                        // code.
+                        //
+                        // This should ensure that you cannot change
+                        // the variant for an enum while you are in
+                        // the midst of matching on it.
+                        //
+                        // FIXME: I originally had put this at the
+                        // start of each elem of arm_blocks (see
+                        // ArmBlocks construction above). But this was
+                        // broken; for example, when you had a trivial
+                        // match like `match "foo".to_string() { _s =>
+                        // {} }`, it would inject a ReadForMatch
+                        // *after* the move of the input into `_s`,
+                        // and that then does not pass the borrow
+                        // check.
+                        //
+                        // * So: I need to fine tune exactly *where*
+                        //   the ReadForMatch belongs. Should it come
+                        //   at the beginning of each pattern match,
+                        //   or the end? And, should there be one
+                        //   ReadForMatch per arm, or one per
+                        //   candidate (aka pattern)?
+
+                        self.cfg.push(*pre_binding_block, Statement {
+                            source_info,
+                            kind: StatementKind::ReadForMatch(borrow_temp.clone()),
+                        });
+                    }
+
+                    // One might ask: why not build up the match pair such that it
+                    // matches via `borrowed_input_temp.deref()` instead of
+                    // using the `discriminant_place` directly, as it is doing here?
+                    //
+                    // The basic answer is that if you do that, then you end up with
+                    // accceses to a shared borrow of the input and that conflicts with
+                    // any arms that look like e.g.
+                    //
+                    // match Some(&4) {
+                    //     ref mut foo => {
+                    //         ... /* mutate `foo` in arm body */ ...
+                    //     }
+                    // }
+                    //
+                    // (Perhaps we could further revise the MIR
+                    //  construction here so that it only does a
+                    //  shared borrow at the outset and delays doing
+                    //  the mutable borrow until after the pattern is
+                    //  matched *and* the guard (if any) for the arm
+                    //  has been run.)
+
                     Candidate {
                         span: pattern.span,
                         match_pairs: vec![MatchPair::new(discriminant_place.clone(), pattern)],