|
1 | 1 | #![deny(rustc::untranslatable_diagnostic)]
|
2 | 2 | #![deny(rustc::diagnostic_outside_of_impl)]
|
3 | 3 | use rustc_data_structures::fx::FxIndexMap;
|
| 4 | +use rustc_data_structures::graph::WithSuccessors; |
4 | 5 | use rustc_index::bit_set::BitSet;
|
5 | 6 | use rustc_middle::mir::{
|
6 | 7 | self, BasicBlock, Body, CallReturnPlaces, Location, Place, TerminatorEdges,
|
@@ -239,15 +240,196 @@ pub fn calculate_borrows_out_of_scope_at_location<'tcx>(
|
239 | 240 | prec.borrows_out_of_scope_at_location
|
240 | 241 | }
|
241 | 242 |
|
| 243 | +struct PoloniusOutOfScopePrecomputer<'a, 'tcx> { |
| 244 | + visited: BitSet<mir::BasicBlock>, |
| 245 | + visit_stack: Vec<mir::BasicBlock>, |
| 246 | + body: &'a Body<'tcx>, |
| 247 | + regioncx: &'a RegionInferenceContext<'tcx>, |
| 248 | + |
| 249 | + loans_out_of_scope_at_location: FxIndexMap<Location, Vec<BorrowIndex>>, |
| 250 | +} |
| 251 | + |
| 252 | +impl<'a, 'tcx> PoloniusOutOfScopePrecomputer<'a, 'tcx> { |
| 253 | + fn new(body: &'a Body<'tcx>, regioncx: &'a RegionInferenceContext<'tcx>) -> Self { |
| 254 | + Self { |
| 255 | + visited: BitSet::new_empty(body.basic_blocks.len()), |
| 256 | + visit_stack: vec![], |
| 257 | + body, |
| 258 | + regioncx, |
| 259 | + loans_out_of_scope_at_location: FxIndexMap::default(), |
| 260 | + } |
| 261 | + } |
| 262 | +} |
| 263 | + |
| 264 | +impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> { |
| 265 | + /// Loans are in scope while they are live: whether they are contained within any live region. |
| 266 | + /// In the location-insensitive analysis, a loan will be contained in a region if the issuing |
| 267 | + /// region can reach it in the subset graph. So this is a reachability problem. |
| 268 | + fn precompute_loans_out_of_scope( |
| 269 | + &mut self, |
| 270 | + loan_idx: BorrowIndex, |
| 271 | + issuing_region: RegionVid, |
| 272 | + loan_issued_at: Location, |
| 273 | + ) { |
| 274 | + let sccs = self.regioncx.constraint_sccs(); |
| 275 | + let issuing_region_scc = sccs.scc(issuing_region); |
| 276 | + |
| 277 | + // We first handle the cases where the loan doesn't go out of scope, depending on the issuing |
| 278 | + // region's successors. |
| 279 | + for scc in sccs.depth_first_search(issuing_region_scc) { |
| 280 | + // 1. Via member constraints |
| 281 | + // |
| 282 | + // The issuing region can flow into the choice regions, and they are either: |
| 283 | + // - placeholders or free regions themselves, |
| 284 | + // - or also transitively outlive a free region. |
| 285 | + // |
| 286 | + // That is to say, if there are member constraints here, the loan escapes the function |
| 287 | + // and cannot go out of scope. We can early return. |
| 288 | + if self.regioncx.scc_has_member_constraints(scc) { |
| 289 | + return; |
| 290 | + } |
| 291 | + |
| 292 | + // 2. Via regions that are live at all points: placeholders and free regions. |
| 293 | + // |
| 294 | + // If the issuing region outlives such a region, its loan escapes the function and |
| 295 | + // cannot go out of scope. We can early return. |
| 296 | + if self.regioncx.scc_is_live_at_all_points(scc) { |
| 297 | + return; |
| 298 | + } |
| 299 | + } |
| 300 | + |
| 301 | + let first_block = loan_issued_at.block; |
| 302 | + let first_bb_data = &self.body.basic_blocks[first_block]; |
| 303 | + |
| 304 | + // The first block we visit is the one where the loan is issued, starting from the statement |
| 305 | + // where the loan is issued: at `loan_issued_at`. |
| 306 | + let first_lo = loan_issued_at.statement_index; |
| 307 | + let first_hi = first_bb_data.statements.len(); |
| 308 | + |
| 309 | + if let Some(kill_location) = |
| 310 | + self.loan_kill_location(loan_idx, loan_issued_at, first_block, first_lo, first_hi) |
| 311 | + { |
| 312 | + debug!("loan {:?} gets killed at {:?}", loan_idx, kill_location); |
| 313 | + self.loans_out_of_scope_at_location.entry(kill_location).or_default().push(loan_idx); |
| 314 | + |
| 315 | + // The loan dies within the first block, we're done and can early return. |
| 316 | + return; |
| 317 | + } |
| 318 | + |
| 319 | + // The loan is not dead. Add successor BBs to the work list, if necessary. |
| 320 | + for succ_bb in first_bb_data.terminator().successors() { |
| 321 | + if self.visited.insert(succ_bb) { |
| 322 | + self.visit_stack.push(succ_bb); |
| 323 | + } |
| 324 | + } |
| 325 | + |
| 326 | + // We may end up visiting `first_block` again. This is not an issue: we know at this point |
| 327 | + // that the loan is not killed in the `first_lo..=first_hi` range, so checking the |
| 328 | + // `0..first_lo` range and the `0..first_hi` range gives the same result. |
| 329 | + while let Some(block) = self.visit_stack.pop() { |
| 330 | + let bb_data = &self.body[block]; |
| 331 | + let num_stmts = bb_data.statements.len(); |
| 332 | + if let Some(kill_location) = |
| 333 | + self.loan_kill_location(loan_idx, loan_issued_at, block, 0, num_stmts) |
| 334 | + { |
| 335 | + debug!("loan {:?} gets killed at {:?}", loan_idx, kill_location); |
| 336 | + self.loans_out_of_scope_at_location |
| 337 | + .entry(kill_location) |
| 338 | + .or_default() |
| 339 | + .push(loan_idx); |
| 340 | + |
| 341 | + // The loan dies within this block, so we don't need to visit its successors. |
| 342 | + continue; |
| 343 | + } |
| 344 | + |
| 345 | + // Add successor BBs to the work list, if necessary. |
| 346 | + for succ_bb in bb_data.terminator().successors() { |
| 347 | + if self.visited.insert(succ_bb) { |
| 348 | + self.visit_stack.push(succ_bb); |
| 349 | + } |
| 350 | + } |
| 351 | + } |
| 352 | + |
| 353 | + self.visited.clear(); |
| 354 | + assert!(self.visit_stack.is_empty(), "visit stack should be empty"); |
| 355 | + } |
| 356 | + |
| 357 | + /// Returns the lowest statement in `start..=end`, where the loan goes out of scope, if any. |
| 358 | + /// This is the statement where the issuing region can't reach any of the regions that are live |
| 359 | + /// at this point. |
| 360 | + fn loan_kill_location( |
| 361 | + &self, |
| 362 | + loan_idx: BorrowIndex, |
| 363 | + loan_issued_at: Location, |
| 364 | + block: BasicBlock, |
| 365 | + start: usize, |
| 366 | + end: usize, |
| 367 | + ) -> Option<Location> { |
| 368 | + for statement_index in start..=end { |
| 369 | + let location = Location { block, statement_index }; |
| 370 | + |
| 371 | + // Check whether the issuing region can reach local regions that are live at this point: |
| 372 | + // - a loan is always live at its issuing location because it can reach the issuing |
| 373 | + // region, which is always live at this location. |
| 374 | + if location == loan_issued_at { |
| 375 | + continue; |
| 376 | + } |
| 377 | + |
| 378 | + // - the loan goes out of scope at `location` if it's not contained within any regions |
| 379 | + // live at this point. |
| 380 | + // |
| 381 | + // FIXME: if the issuing region `i` can reach a live region `r` at point `p`, and `r` is |
| 382 | + // live at point `q`, then it's guaranteed that `i` would reach `r` at point `q`. |
| 383 | + // Reachability is location-insensitive, and we could take advantage of that, by jumping |
| 384 | + // to a further point than just the next statement: we can jump to the furthest point |
| 385 | + // within the block where `r` is live. |
| 386 | + if self.regioncx.is_loan_live_at(loan_idx, location) { |
| 387 | + continue; |
| 388 | + } |
| 389 | + |
| 390 | + // No live region is reachable from the issuing region: the loan is killed at this |
| 391 | + // point. |
| 392 | + return Some(location); |
| 393 | + } |
| 394 | + |
| 395 | + None |
| 396 | + } |
| 397 | +} |
| 398 | + |
242 | 399 | impl<'a, 'tcx> Borrows<'a, 'tcx> {
|
243 | 400 | pub fn new(
|
244 | 401 | tcx: TyCtxt<'tcx>,
|
245 | 402 | body: &'a Body<'tcx>,
|
246 | 403 | regioncx: &'a RegionInferenceContext<'tcx>,
|
247 | 404 | borrow_set: &'a BorrowSet<'tcx>,
|
248 | 405 | ) -> Self {
|
249 |
| - let borrows_out_of_scope_at_location = |
| 406 | + let mut borrows_out_of_scope_at_location = |
250 | 407 | calculate_borrows_out_of_scope_at_location(body, regioncx, borrow_set);
|
| 408 | + |
| 409 | + // The in-tree polonius analysis computes loans going out of scope using the set-of-loans |
| 410 | + // model, and makes sure they're identical to the existing computation of the set-of-points |
| 411 | + // model. |
| 412 | + if tcx.sess.opts.unstable_opts.polonius.is_next_enabled() { |
| 413 | + let mut polonius_prec = PoloniusOutOfScopePrecomputer::new(body, regioncx); |
| 414 | + for (loan_idx, loan_data) in borrow_set.iter_enumerated() { |
| 415 | + let issuing_region = loan_data.region; |
| 416 | + let issued_location = loan_data.reserve_location; |
| 417 | + |
| 418 | + polonius_prec.precompute_loans_out_of_scope( |
| 419 | + loan_idx, |
| 420 | + issuing_region, |
| 421 | + issued_location, |
| 422 | + ); |
| 423 | + } |
| 424 | + |
| 425 | + assert_eq!( |
| 426 | + borrows_out_of_scope_at_location, polonius_prec.loans_out_of_scope_at_location, |
| 427 | + "the loans out of scope must be the same as the borrows out of scope" |
| 428 | + ); |
| 429 | + |
| 430 | + borrows_out_of_scope_at_location = polonius_prec.loans_out_of_scope_at_location; |
| 431 | + } |
| 432 | + |
251 | 433 | Borrows { tcx, body, borrow_set, borrows_out_of_scope_at_location }
|
252 | 434 | }
|
253 | 435 |
|
|
0 commit comments