Skip to content

Convert typeck constraints in location-sensitive polonius #134920

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 8, 2025

Conversation

lqd
Copy link
Member

@lqd lqd commented Dec 30, 2024

In this PR, we do a big chunk of the work of localizing regular outlives constraints.

The slightly annoying thing is handling effectful statements: usually the subset graph propagates loans at a single point between regions, and liveness propagates loans between points within a single region, but some statements have effects applied on exit.

This was also a problem before, in datalog polonius terms and Niko's solution at the time, this is about: the mid-point. The idea was to duplicate all MIR locations into two physical points, and orchestrate the effects with that. Somewhat easier to do, but double the CFG.

We've always believed we didn't need midpoints in principle, as we can represent changes on exit as on happening entry to the successor, but there's some difficulty in tracking the position information at sufficient granularity through outlives relation (especially since we also have bidirectional edges and time-traveling now).

Now, that is surely what we should be doing in the future. In the mean time, I infer this from the kind of statement/terminator where an outlives constraint arose. It's not particularly complicated but some explanation will help clarify the code.

Assignments (in their various forms) are the quintessential example of these crossover cases: loans that would flow into the LHS would not be visible on entry to the point but on exit -- so we'll localize these edges to the successor. Let's look at a real-world example, involving invariance for bidirectional edges:

let mut _1: HashMap<i32, &'7 i32>;
let mut _3: &'9 mut HashMap<i32, &'10 i32>;
...
/* at bb1[3]: */ _3 = &'3 mut _1;

Here, typeck expectedly produces 3 outlives constraints today:

  1. '3 -> '9
  2. '7 -> '10
  3. '10 -> '7

And we localize them like so,

  1. '3 -> '9 flows into the LHS and becomes: 3_bb1_3 -> 9_bb1_4
  2. '7 -> '10 flows into the LHS and becomes: 7_bb1_3 -> 10_bb1_4
  3. '10 -> '7 flows from the LHS and becomes: 10_bb1_4 -> 7_bb1_3 (time traveling 👌)

r? @jackh726

To keep you entertained during the holidays I also threw in a couple of small changes removing cruft in the borrow checker.

We're actually getting there. The next PR will be the last one needed to get end-to-end tests working.

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Dec 30, 2024
Comment on lines +219 to +233
tcx.for_each_free_region(value, |region| {
let region = universal_regions.to_region_vid(region);
if region == outlives_constraint.sub {
// This constraint flows into the result, its effects start becoming visible on exit.
to = successor_point;
} else if region == outlives_constraint.sup {
// This constraint flows from the result, its effects start becoming visible on exit.
from = successor_point;
}
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is right, but it feels off to me. The loop using for_each_free_region feels like the wrong construct to me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also prefer if this differentiation was done in MIR typeck instead, if that's what you mean. It wasn't trivial in the NLL days, according to Niko and some comments in the crate, but should still be possible I think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think given the FIXME further up in the file, we don't need to do anything in this PR, just wanted to point out that it sure does feel weird.

Comment on lines 81 to 86
localized_outlives_constraints.push(LocalizedOutlivesConstraint {
source: outlives_constraint.sup,
from,
target: outlives_constraint.sub,
to,
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if readability would be better here if you moved the LocalizedOutlivesConstraint construction deeper (into compute_constraint_direction), instead of propagating up a (PointIndex, PointIndex). It'll then be much more clear the matching of sup/sub to from/to.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I initially has something similar indeed, but the duplication was a bit much and I turned it into this. I can bring it back.

I've pushed a commit to do so, let me know what you think (especially if you didn't mean to also inline the helper function compute_constraint_direction) and I can then squash it in. Inlining the helper makes the link between the sub/sup and from/to clearer with the actual result type we're checking.

}
_ => {
// For the other cases, we localize an outlives constraint to where it arises.
(current_point, current_point)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To add to this, are these actually useful? (I think I remember that these get filtered somewhere? Or was that something else?)

If these aren't useful, then you can make this branch empty.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I think I remember that these get filtered somewhere? Or was that something else?)

I think that may be something else? Only an edge from and to the same node is filtered out, if that's what you remembered: if the regions are different but from/to the same point, they're not filtered out.

To add to this, are these actually useful?

I think handling some statements should be needed in addition to assignments, like maybe fake reads and type ascriptions (but the latter should hold everywhere and not go through this code path here, but the Locations::All path).

I'll try to look into it soon; I don't think we'll have tests that exercise this anyways.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I do still even if these are needed that duplicating the LocalizedOutlivesConstraint (instead of propagating up the PointIndex tuple will improve readability.

lqd added 6 commits January 1, 2025 12:13
it's still partially a skeleton, but works well enough for almost all tests to pass
this is to remove the entire `util` module
it's been simplified over the years, but now it's no longer useful.

- document its replacement in `BorrowKind`
- use that everywhere instead
@lqd lqd force-pushed the polonius-next-episode-6 branch from 490c17a to 9eef43a Compare January 1, 2025 15:37
@jackh726
Copy link
Member

jackh726 commented Jan 1, 2025

r=me with or without moving the LocalizedOutlivesConstraint construction deeper

push constraint creation to where the statement/terminator info is gathered
@lqd lqd force-pushed the polonius-next-episode-6 branch from 9eef43a to fc7ee23 Compare January 6, 2025 14:25
@jackh726
Copy link
Member

jackh726 commented Jan 6, 2025

@bors r+

@bors
Copy link
Collaborator

bors commented Jan 6, 2025

📌 Commit fc7ee23 has been approved by jackh726

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jan 6, 2025
@lqd
Copy link
Member Author

lqd commented Jan 7, 2025

This is feature gated and off by default, and should be safe to rollup.

@bors rollup

matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Jan 8, 2025
…kh726

Convert typeck constraints in location-sensitive polonius

In this PR, we do a big chunk of the work of localizing regular outlives constraints.

The slightly annoying thing is handling effectful statements: usually the subset graph propagates loans at a single point between regions, and liveness propagates loans between points within a single region, but some statements have effects applied on exit.

This was also a problem before, in datalog polonius terms and Niko's solution at the time, this is about: the mid-point. The idea was to duplicate all MIR locations into two physical points, and orchestrate the effects with that. Somewhat easier to do, but double the CFG.

We've always believed we didn't _need_ midpoints in principle, as we can represent changes on exit as on happening entry to the successor, but there's some difficulty in tracking the position information at sufficient granularity through outlives relation (especially since we also have bidirectional edges and time-traveling now).

Now, that is surely what we should be doing in the future. In the mean time, I infer this from the kind of statement/terminator where an outlives constraint arose. It's not particularly complicated but some explanation will help clarify the code.

Assignments (in their various forms) are the quintessential example of these crossover cases: loans that would flow into the LHS would not be visible on entry to the point but on exit -- so we'll localize these edges to the successor. Let's look at a real-world example, involving invariance for bidirectional edges:

```rust
let mut _1: HashMap<i32, &'7 i32>;
let mut _3: &'9 mut HashMap<i32, &'10 i32>;
...
/* at bb1[3]: */ _3 = &'3 mut _1;
```

Here, typeck expectedly produces 3 outlives constraints today:
1. `'3 -> '9`
2. `'7 -> '10`
3. `'10 -> '7`

And we localize them like so,

1. `'3 -> '9` flows into the LHS and becomes: `3_bb1_3 -> 9_bb1_4`
2. `'7 -> '10` flows into the LHS and becomes: `7_bb1_3 -> 10_bb1_4`
3. `'10 -> '7` flows from the LHS and becomes: `10_bb1_4 -> 7_bb1_3` (time traveling 👌)

---

r? `@jackh726`

To keep you entertained during the holidays I also threw in a couple of small changes removing cruft in the borrow checker.

We're actually getting there. The next PR will be the last one needed to get end-to-end tests working.
bors added a commit to rust-lang-ci/rust that referenced this pull request Jan 8, 2025
…iaskrgr

Rollup of 7 pull requests

Successful merges:

 - rust-lang#134389 (Condvar: implement wait_timeout for targets without threads)
 - rust-lang#134920 (Convert typeck constraints in location-sensitive polonius)
 - rust-lang#135032 (triagebot: register `relnotes-interest-group` ping group)
 - rust-lang#135176 (More compelling env_clear() examples)
 - rust-lang#135184 (Reserve x18 register for aarch64 wrs vxworks target)
 - rust-lang#135203 (arm: add unstable soft-float target feature)
 - rust-lang#135219 (warn about broken simd not only on structs but also enums and unions when we didn't opt in to it)

r? `@ghost`
`@rustbot` modify labels: rollup
bors added a commit to rust-lang-ci/rust that referenced this pull request Jan 8, 2025
Rollup of 7 pull requests

Successful merges:

 - rust-lang#134389 (Condvar: implement wait_timeout for targets without threads)
 - rust-lang#134920 (Convert typeck constraints in location-sensitive polonius)
 - rust-lang#135032 (triagebot: register `relnotes-interest-group` ping group)
 - rust-lang#135176 (More compelling env_clear() examples)
 - rust-lang#135184 (Reserve x18 register for aarch64 wrs vxworks target)
 - rust-lang#135203 (arm: add unstable soft-float target feature)
 - rust-lang#135219 (warn about broken simd not only on structs but also enums and unions when we didn't opt in to it)

r? `@ghost`
`@rustbot` modify labels: rollup

try-job: i686-mingw
bors added a commit to rust-lang-ci/rust that referenced this pull request Jan 8, 2025
Rollup of 8 pull requests

Successful merges:

 - rust-lang#133057 (Impl String::into_chars)
 - rust-lang#134389 (Condvar: implement wait_timeout for targets without threads)
 - rust-lang#134920 (Convert typeck constraints in location-sensitive polonius)
 - rust-lang#135032 (triagebot: register `relnotes-interest-group` ping group)
 - rust-lang#135176 (More compelling env_clear() examples)
 - rust-lang#135184 (Reserve x18 register for aarch64 wrs vxworks target)
 - rust-lang#135203 (arm: add unstable soft-float target feature)
 - rust-lang#135219 (warn about broken simd not only on structs but also enums and unions when we didn't opt in to it)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit 808c8f8 into rust-lang:master Jan 8, 2025
6 checks passed
@rustbot rustbot added this to the 1.86.0 milestone Jan 8, 2025
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Jan 8, 2025
Rollup merge of rust-lang#134920 - lqd:polonius-next-episode-6, r=jackh726

Convert typeck constraints in location-sensitive polonius

In this PR, we do a big chunk of the work of localizing regular outlives constraints.

The slightly annoying thing is handling effectful statements: usually the subset graph propagates loans at a single point between regions, and liveness propagates loans between points within a single region, but some statements have effects applied on exit.

This was also a problem before, in datalog polonius terms and Niko's solution at the time, this is about: the mid-point. The idea was to duplicate all MIR locations into two physical points, and orchestrate the effects with that. Somewhat easier to do, but double the CFG.

We've always believed we didn't _need_ midpoints in principle, as we can represent changes on exit as on happening entry to the successor, but there's some difficulty in tracking the position information at sufficient granularity through outlives relation (especially since we also have bidirectional edges and time-traveling now).

Now, that is surely what we should be doing in the future. In the mean time, I infer this from the kind of statement/terminator where an outlives constraint arose. It's not particularly complicated but some explanation will help clarify the code.

Assignments (in their various forms) are the quintessential example of these crossover cases: loans that would flow into the LHS would not be visible on entry to the point but on exit -- so we'll localize these edges to the successor. Let's look at a real-world example, involving invariance for bidirectional edges:

```rust
let mut _1: HashMap<i32, &'7 i32>;
let mut _3: &'9 mut HashMap<i32, &'10 i32>;
...
/* at bb1[3]: */ _3 = &'3 mut _1;
```

Here, typeck expectedly produces 3 outlives constraints today:
1. `'3 -> '9`
2. `'7 -> '10`
3. `'10 -> '7`

And we localize them like so,

1. `'3 -> '9` flows into the LHS and becomes: `3_bb1_3 -> 9_bb1_4`
2. `'7 -> '10` flows into the LHS and becomes: `7_bb1_3 -> 10_bb1_4`
3. `'10 -> '7` flows from the LHS and becomes: `10_bb1_4 -> 7_bb1_3` (time traveling 👌)

---

r? ``@jackh726``

To keep you entertained during the holidays I also threw in a couple of small changes removing cruft in the borrow checker.

We're actually getting there. The next PR will be the last one needed to get end-to-end tests working.
@lqd lqd deleted the polonius-next-episode-6 branch January 8, 2025 10:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants