-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Immovable generators #45337
Immovable generators #45337
Conversation
r? @arielb1 (rust_highfive has picked a reviewer for you, use r? to override) |
c512527
to
0ffc23a
Compare
src/librustc/ty/relate.rs
Outdated
@@ -404,6 +404,19 @@ pub fn super_relate_tys<'a, 'gcx, 'tcx, R>(relation: &mut R, | |||
Ok(tcx.mk_generator(a_id, substs, interior)) | |||
} | |||
|
|||
(&ty::TyGeneratorWitness(a_types), &ty::TyGeneratorWitness(b_types)) => | |||
{ | |||
// FIXME: Relate this without using tuples |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't you replicate the zip loop that the TyTuple
branch does?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to deal with the Binder
, which needs an intermediate type. I could create one though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could implement Relate
for &'tcx Slice<Ty<'tcx>>
using the same algorithm as Substs
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed this to use an intermediate type to make it clear the logic only applies to TyGeneratorWitness.
substs.upvar_tys(def_id, self).chain(iter::once(interior.witness)).map(|ty| { | ||
ty::TyGenerator(def_id, substs, _) => { | ||
// Note that the interior types are ignored here. | ||
// Any type reachable inside the interior must also be reachable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't feel right. If the generator captures an upvar through a mutable reference, the generator doesn't have dropck constraints:
#![feature(generators, generator_trait)]
use std::cell::RefCell;
use std::ops::Generator;
fn static_alloc<'a, T>(t: T) -> &'a mut T {
// this is an unsafe function, but it is sound (not running destructors
// is safe), can be implemented using a static dropless arena of
// `ManuallyDrop` data, and was planned to be added to the standard
// library at some point.
unsafe {
use std::mem;
let mut b = Box::new(t);
let r = &mut *(&mut *b as *mut T);
mem::forget(b);
r
}
}
fn main() {
let (cell, mut gen);
cell = Box::new(RefCell::new(0));
let ref_ = static_alloc(Some(cell.borrow_mut()));
// the upvar is the non-dropck `&mut Option<Ref<'a, i32>>`.
gen = || {
// but the generator can use it to drop a `Ref<'a, i32>`.
let _d = ref_.take();
yield;
};
gen.resume();
// drops the RefCell and then the Ref, leading to use-after-free
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be a better idea to treat generators like trait object and have them always be dtorck. If generators are "supposed to be" captured by an impl Trait
or trait object, this shouldn't be a problem in practice..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This gives the following error.:
error[E0597]: `ref_` does not live long enough
--> dtorck.rs:27:18
|
25 | gen = || {
| -- capture occurs here
26 | // but the generator can use it to drop a `Ref<'a, i32>`.
27 | let _d = ref_.take();
| ^^^^ does not live long enough
...
32 | }
| - borrowed value dropped before borrower
|
= note: values in a scope are dropped in the opposite order they are created
error: aborting due to previous error
Note that upvars are still checked. Even if we didn't ignore the interior, would higher-ranked regions have any effect on dtorck?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this still give the error with your PR? the only upvar should be ref_
, which should have an empty dtorck constraint. Maybe I just need to check this PR out to see that this holds?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That error is from this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you make this into a test?
src/librustc/ty/walk.rs
Outdated
} | ||
ty::TyGeneratorWitness(ts) => { | ||
// FIXME: Should we skip the binder here? | ||
stack.extend(ts.skip_binder().iter().cloned().rev()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TyFnPtr
skips binders blindly in the next 5 lines, so sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. Removed this FIXME.
Lvalue::Projection(ref proj) => { | ||
match proj.elem { | ||
// For derefs there's no underlying local that gets borrowed | ||
// It is either a Box or some already borrowed local. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// It is either a Box or some already borrowed local.
nit: it can also be a borrow from something based on an upvar, e.g. &fcx.tcx
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking of MIR locals here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(*fcx).tcx
is not an already borrowed MIR local, it's from the place where fcx
was allocated from, which can be everywhere (e.g. it could be a field of an arena or Vec
, which are not a Box
).
I just want to avoid comments that lie to avoid rumors spreading.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed this comment.
@@ -369,6 +402,12 @@ fn locals_live_across_suspend_points<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, | |||
// Mark locals without storage statements as always having live storage | |||
live_locals.union(&ignored.0); | |||
|
|||
if !movable { | |||
// For immovable generators we consider borrowed locals to always be live. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could also only promote locals that are borrowed across a yield if you don't want to promote everything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd need a liveness analysis which properly handles borrows then. I'm waiting on NLL before I do that. My plan is to have a pass which optimizes Storage*
instructions based on NLL and then let the generator transformation only use the dataflow analysis on those instructions.
let witness = fcx.tcx.mk_generator_witness(ty::Binder(type_list)); | ||
|
||
debug!("Types in generator after region replacement {:?}, span = {:?}", | ||
witness, body.value.span); | ||
|
||
// Unify the tuple with the witness |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not a tuple anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
|
||
debug!("Types in generator {:?}, span = {:?}", tuple, body.value.span); | ||
let mut counter = 0; | ||
let type_list = fcx.tcx.fold_regions(&type_list, &mut false, |_, current_depth| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add a comment to the following effect:
The types in the generator interior contain lifetimes local to the generator itself, which should not be exposed outside of the generator. Therefore, we replace these lifetimes with existentially-bound lifetimes, which reflect the exact value of the lifetimes not being known by users.
These lifetimes are used in auto trait impl checking (for example, if a Sync
generator contains an &'α T
, we need to check whether &'α T: Sync
), so knowledge of the exact relationships between them isn't particularly important.
Note that lifetimes in the type of a local that is currently dead need not be alive. It's up to the generator to ensure that captured lifetimes that are actually used are alive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that lifetimes in the type of a local that is currently dead need not be alive. It's up to the generator to ensure that captured lifetimes that are actually used are alive.
I'm not sure what you mean by this. I added your other paragraphs as comments.
@@ -0,0 +1,19 @@ | |||
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a test that static generators actually work - that you can yield *b
and that it stays valid?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could do yield *b
in movable generator, given NLL. Did you mean I should use the reference after the yield
so the test still works with NLL?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's one thing. Another thing is that you should have a test that actually runs the generator and asserts that the references have the same value between resumes, to quickly catch if some change horribly breaks immovable generator codegen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed this test.
@@ -0,0 +1,19 @@ | |||
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT | |||
// file at the top-level directory of this distribution and at |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you also add an rpass test for the "vexpr lifetime" case (aka #44197)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also need to think of more tests to add here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a test case for #44197.
☔ The latest upstream changes (presumably #45381) made this pull request unmergeable. Please resolve the merge conflicts. |
Triage ping @Zoxc -- looks like there are merge conflicts again and some test failures! |
This PR is blocked on landing #44917. |
398c882
to
233b261
Compare
☔ The latest upstream changes (presumably #45072) made this pull request unmergeable. Please resolve the merge conflicts. |
☔ The latest upstream changes (presumably #45735) made this pull request unmergeable. Please resolve the merge conflicts. |
☔ The latest upstream changes (presumably #45944) made this pull request unmergeable. Please resolve the merge conflicts. |
triage: still blocked by #44917. |
As an aside, It doesn't seem to me that creating a "static generator" has to be unsafe, but I don't mind that it is. Am I missing something though? That is, is it UB to create a static generator under some conditions (even if you never invoke next?). I would have thought that the proof burden would be:
|
In the generator's contents signature, all regions are replaced with bound regions. This means a generator type would be something of the form:
Basically, the generator contains some of the interior times for some set of substituted lifetimes, so if an Note that we lose all information about relations between the lifetimes - we might have had |
ATM static generators use the "standard" safe Generator trait. This means that static generators, once created, are a safety hazard - moving them and calling The above is probably a bad idea - it is very easy to make an unsafe API using them - and we should create an new |
@arielb1 oh, I hadn't even looked at the trait part of this PR, I just assumed we would have an I think that's what we want, yeah. |
It is always safe to create a generator.
You have to ensure that once you call |
By this I guess you mean a non-static generator?
Right, and for this reason I think that the But I think we can tweak this in follow-up work. I'm pretty happy with this PR. r=me once rebased. |
…ws across suspension points to borrowck. Fixes rust-lang#44197, rust-lang#45259 and rust-lang#45093.
@bors r=nikomatsakis |
📌 Commit 55c6c88 has been approved by |
Immovable generators This adds support for immovable generators which allow you to borrow local values inside generator across suspension points. These are declared using a `static` keyword: ```rust let mut generator = static || { let local = &Vec::new(); yield; local.push(0i8); }; generator.resume(); // ERROR moving the generator after it has resumed would invalidate the interior reference // drop(generator); ``` Region inference is no longer affected by the types stored in generators so the regions inside should be similar to other code (and unaffected by the presence of `yield` expressions). The borrow checker is extended to pick up the slack so interior references still result in errors for movable generators. This fixes #44197, #45259 and #45093. This PR depends on [PR #44917 (immovable types)](#44917), I suggest potential reviewers ignore the first commit as it adds immovable types.
☀️ Test successful - status-appveyor, status-travis |
This adds support for immovable generators which allow you to borrow local values inside generator across suspension points. These are declared using a
static
keyword:Region inference is no longer affected by the types stored in generators so the regions inside should be similar to other code (and unaffected by the presence of
yield
expressions). The borrow checker is extended to pick up the slack so interior references still result in errors for movable generators. This fixes #44197, #45259 and #45093.This PR depends on PR #44917 (immovable types), I suggest potential reviewers ignore the first commit as it adds immovable types.