Skip to content
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

[generator] Special cases for match guard when analyzing interior types in generators #75213

Merged
merged 7 commits into from
Oct 13, 2020

Conversation

dingxiangfei2009
Copy link
Contributor

Fix #72651

This proposes one of ways to fix the mentioned issue. One cause of #72651 is that the interior type analysis misses out types of match pattern locals. Those locals are manifested as temporary borrows in the scopes of match arm guards. If uses of these locals appear after yield points, the borrows from them were not considered live across the yield points. However, this is not the case since the borrowing always happens at the very beginning of the match guard.

This calls for special treatment to analysis of types appearing in the match guard. Those borrows are recorded as the HIR tree is walked by InteriorVisitor and their uses are recorded whenever a yield point is crossed.

@rust-highfive
Copy link
Collaborator

r? @eddyb

(rust_highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Aug 6, 2020
@dingxiangfei2009
Copy link
Contributor Author

@eddyb I just know that reviewers are randomly assigned. Would you mind suggesting another reviewer if you find the current assignment is not suitable?

@eddyb
Copy link
Member

eddyb commented Aug 16, 2020

Sorry! I should've asked someone to remove me from the list of reviewers, as I'm really behind.

r? @matthewjasper

@rust-highfive rust-highfive assigned matthewjasper and unassigned eddyb Aug 16, 2020
@@ -0,0 +1,29 @@
// check-pass
Copy link
Member

Choose a reason for hiding this comment

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

This MVCE no longer ICEs for me, whereas this one does: #72651 (comment). No need to leave credit, just an issue number is enough.

(Feel free to also include this one if you can confirm it did ICE at one point, though).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The test case has been updated to refer to the said MVCE.

Copy link
Contributor Author

@dingxiangfei2009 dingxiangfei2009 Aug 27, 2020

Choose a reason for hiding this comment

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

@tmandry Forgot to mention this. This test case compiles if the async functions are not used in fn main. As you can see, as soon as async function g is called in main, broken MIR ICE can be reproduced. I only found this out recently.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, in that case let's put both test cases in!

@dingxiangfei2009 dingxiangfei2009 force-pushed the yield-point-in-match-guard branch 2 times, most recently from 31131dd to 9291c31 Compare August 20, 2020 06:49
@bors
Copy link
Contributor

bors commented Aug 30, 2020

☔ The latest upstream changes (presumably #74862) made this pull request unmergeable. Please resolve the merge conflicts.

@dingxiangfei2009
Copy link
Contributor Author

Hi @matthewjasper would you mind a comment or feedback on this change?

@tmandry
Copy link
Member

tmandry commented Sep 1, 2020

@rust-lang/wg-async-foundations has a meeting tomorrow focused on how this code works; after that would be a natural time for one of us to review this change.

@crlf0710
Copy link
Member

r? @tmandry

@crlf0710 crlf0710 added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 18, 2020
fn visit_arm(&mut self, arm: &'tcx Arm<'tcx>) {
if arm.guard.is_some() {
self.nested_scope_of_guards.push(<_>::default());
self.arm_has_guard = true;
Copy link
Member

Choose a reason for hiding this comment

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

It seems like we'd only want to set this while visiting the guard if expression, not the pattern.

"variable should be placed in scope earlier"
);
}
self.arm_has_guard = false;
Copy link
Member

Choose a reason for hiding this comment

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

We should reset back to the original value, which won't always be false (if there were a match nested inside the guard). Here's a (very contrived) example..

match x {
  Some(y) if match y.len() { 0 => false, l if l % 2 == 0 => true, _ => false } => (),
  _ => (),
}

In fact, I'm tempted to say this flag is redundant and we should remove it. We can query current_scope_of_guards every time, and only find a way to optimize that if it's slow.

let scope = self.region_scope_tree.var_scope(pat.hir_id.local_id);
let ty = self.fcx.typeck_results.borrow().pat_ty(pat);
self.record(ty, Some(scope), None, pat.span);
self.record(ty, Some(scope), None, pat.span, false);
if self.arm_has_guard {
Copy link
Member

Choose a reason for hiding this comment

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

We may want to check that this binding is actually introduced by the if guard, instead of by some arbitrary expression inside it.

That said, it's okay if we overapproximate the set of types inside our generator. Since it's very rare to have other bindings introduced inside a match guard, we could leave this as is. If we do that we should leave a comment explaining that this is an overapproximation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I gave some thoughts about it. I think a better way is to use a separate and simple visitor that only walks the patterns in the arm, given that the arm has a guard. Most part of the original visit_pat will be kept the same.

@@ -134,6 +147,9 @@ pub fn resolve_interior<'a, 'tcx>(
expr_count: 0,
kind,
prev_unresolved_span: None,
nested_scope_of_guards: <_>::default(),
current_scope_of_guards: <_>::default(),
Copy link
Member

Choose a reason for hiding this comment

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

naming suggestion: guard_bindings_set

@@ -134,6 +147,9 @@ pub fn resolve_interior<'a, 'tcx>(
expr_count: 0,
kind,
prev_unresolved_span: None,
nested_scope_of_guards: <_>::default(),
Copy link
Member

Choose a reason for hiding this comment

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

naming suggestion: guard_bindings

/// such borrows can span across this yield point.
/// As such, we need to track these borrows and record them despite of the fact
/// that they may succeed the said yield point in the post-order.
nested_scope_of_guards: SmallVec<[SmallVec<[HirId; 4]>; 4]>,
Copy link
Member

Choose a reason for hiding this comment

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

The outer SmallVec could probably be of len 1, since nesting these would be extremely rare in practice.

@tmandry
Copy link
Member

tmandry commented Oct 6, 2020

Friendly ping @dingxiangfei2009, are you still able to work on this?

@dingxiangfei2009
Copy link
Contributor Author

Sorry for keeping you waiting, @tmandry , I will apply your suggestions this morning.

@tmandry
Copy link
Member

tmandry commented Oct 12, 2020

Sorry, I was on call last week but am taking a look now.

Copy link
Member

@tmandry tmandry left a comment

Choose a reason for hiding this comment

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

Okay, after my comments I think this is good to go. Thanks for doing this!

@@ -53,7 +63,9 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
yield_data.expr_and_pat_count, self.expr_count, source_span
);

if yield_data.expr_and_pat_count >= self.expr_count {
if guard_borrowing_from_pattern
Copy link
Member

Choose a reason for hiding this comment

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

nit: It may worth be restating what you said above in a comment here, i.e. that we need to record these regardless of where they appear in the post-order traversal.

self.guard_bindings.push(<_>::default());
}
self.visit_pat(&arm.pat);
if let Some(ref g) = arm.guard {
Copy link
Member

Choose a reason for hiding this comment

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

instead of accessing the arm's fields directly, could you destructure it so if new fields are added, this gets updated? e.g. let Arm { guard, pat } = arm;

This code is duplicating what normally goes in super_visit_arm() so it's helpful to have static checks like this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is a good idea. I also updated other field accesses to arm towards the end of this function. There are other fields in arm such as hir_id, but I am going to elide them for now.

Copy link
Member

Choose a reason for hiding this comment

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

If you use .. it sort of defeats the purpose of what I was suggesting. I'd rather list out all fields and assign them to _.

Comment on lines 228 to 230
if arm.guard.is_some() {
self.guard_bindings.push(<_>::default());
}
Copy link
Member

Choose a reason for hiding this comment

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

This is duplicated below, I think maybe you meant to take it out?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. I need to remove it.

@tmandry
Copy link
Member

tmandry commented Oct 13, 2020

@bors r+
@bors rollup=never in case there's a perf impact on async code

@bors
Copy link
Contributor

bors commented Oct 13, 2020

📌 Commit 50627a3 has been approved by tmandry

@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 Oct 13, 2020
@bors
Copy link
Contributor

bors commented Oct 13, 2020

⌛ Testing commit 50627a3 with merge adef9da...

@bors
Copy link
Contributor

bors commented Oct 13, 2020

☀️ Test successful - checks-actions, checks-azure
Approved by: tmandry
Pushing adef9da to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Oct 13, 2020
@bors bors merged commit adef9da into rust-lang:master Oct 13, 2020
@rustbot rustbot added this to the 1.49.0 milestone Oct 13, 2020
bors added a commit to rust-lang-ci/rust that referenced this pull request Oct 30, 2020
Always record reference to binding in match if guards

When encountering a binding from a `match` pattern in its `if` guard when computing a generator's interior types, we must always record the type of a reference to the binding because of how `if` guards are lowered to MIR. This was missed in rust-lang#75213 because the binding in that test case was autorefed and we recorded that adjusted type anyway.

Fixes rust-lang#78366
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
merged-by-bors This PR was explicitly merged by bors. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion.
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

ICE: Broken MIR
8 participants