dropck_outlives check generator witness needs_drop

This commit is contained in:
lcnr 2023-10-24 19:16:15 +02:00
parent f0df3d2dfb
commit 57253552de
8 changed files with 69 additions and 58 deletions

View File

@ -1107,8 +1107,10 @@ pub fn needs_drop(self, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> boo
// This doesn't depend on regions, so try to minimize distinct // This doesn't depend on regions, so try to minimize distinct
// query keys used. // query keys used.
// If normalization fails, we just use `query_ty`. // If normalization fails, we just use `query_ty`.
let query_ty = let param_env = tcx.erase_regions(param_env);
tcx.try_normalize_erasing_regions(param_env, query_ty).unwrap_or(query_ty); let query_ty = tcx
.try_normalize_erasing_regions(param_env, query_ty)
.unwrap_or_else(|_| tcx.erase_regions(query_ty));
tcx.needs_drop_raw(param_env.and(query_ty)) tcx.needs_drop_raw(param_env.and(query_ty))
} }
@ -1297,7 +1299,6 @@ pub fn needs_drop_components<'tcx>(
| ty::FnDef(..) | ty::FnDef(..)
| ty::FnPtr(_) | ty::FnPtr(_)
| ty::Char | ty::Char
| ty::CoroutineWitness(..)
| ty::RawPtr(_) | ty::RawPtr(_)
| ty::Ref(..) | ty::Ref(..)
| ty::Str => Ok(SmallVec::new()), | ty::Str => Ok(SmallVec::new()),
@ -1337,7 +1338,8 @@ pub fn needs_drop_components<'tcx>(
| ty::Placeholder(..) | ty::Placeholder(..)
| ty::Infer(_) | ty::Infer(_)
| ty::Closure(..) | ty::Closure(..)
| ty::Coroutine(..) => Ok(smallvec![ty]), | ty::Coroutine(..)
| ty::CoroutineWitness(..) => Ok(smallvec![ty]),
} }
} }

View File

@ -133,7 +133,7 @@ pub fn compute_dropck_outlives_inner<'tcx>(
result.overflows.len(), result.overflows.len(),
ty_stack.len() ty_stack.len()
); );
dtorck_constraint_for_ty_inner(tcx, DUMMY_SP, for_ty, depth, ty, &mut constraints)?; dtorck_constraint_for_ty_inner(tcx, param_env, DUMMY_SP, depth, ty, &mut constraints)?;
// "outlives" represent types/regions that may be touched // "outlives" represent types/regions that may be touched
// by a destructor. // by a destructor.
@ -185,16 +185,15 @@ pub fn compute_dropck_outlives_inner<'tcx>(
/// Returns a set of constraints that needs to be satisfied in /// Returns a set of constraints that needs to be satisfied in
/// order for `ty` to be valid for destruction. /// order for `ty` to be valid for destruction.
#[instrument(level = "debug", skip(tcx, param_env, span, constraints))]
pub fn dtorck_constraint_for_ty_inner<'tcx>( pub fn dtorck_constraint_for_ty_inner<'tcx>(
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
span: Span, span: Span,
for_ty: Ty<'tcx>,
depth: usize, depth: usize,
ty: Ty<'tcx>, ty: Ty<'tcx>,
constraints: &mut DropckConstraint<'tcx>, constraints: &mut DropckConstraint<'tcx>,
) -> Result<(), NoSolution> { ) -> Result<(), NoSolution> {
debug!("dtorck_constraint_for_ty_inner({:?}, {:?}, {:?}, {:?})", span, for_ty, depth, ty);
if !tcx.recursion_limit().value_within_limit(depth) { if !tcx.recursion_limit().value_within_limit(depth) {
constraints.overflows.push(ty); constraints.overflows.push(ty);
return Ok(()); return Ok(());
@ -224,13 +223,13 @@ pub fn dtorck_constraint_for_ty_inner<'tcx>(
ty::Array(ety, _) | ty::Slice(ety) => { ty::Array(ety, _) | ty::Slice(ety) => {
// single-element containers, behave like their element // single-element containers, behave like their element
rustc_data_structures::stack::ensure_sufficient_stack(|| { rustc_data_structures::stack::ensure_sufficient_stack(|| {
dtorck_constraint_for_ty_inner(tcx, span, for_ty, depth + 1, *ety, constraints) dtorck_constraint_for_ty_inner(tcx, param_env, span, depth + 1, *ety, constraints)
})?; })?;
} }
ty::Tuple(tys) => rustc_data_structures::stack::ensure_sufficient_stack(|| { ty::Tuple(tys) => rustc_data_structures::stack::ensure_sufficient_stack(|| {
for ty in tys.iter() { for ty in tys.iter() {
dtorck_constraint_for_ty_inner(tcx, span, for_ty, depth + 1, ty, constraints)?; dtorck_constraint_for_ty_inner(tcx, param_env, span, depth + 1, ty, constraints)?;
} }
Ok::<_, NoSolution>(()) Ok::<_, NoSolution>(())
})?, })?,
@ -249,7 +248,14 @@ pub fn dtorck_constraint_for_ty_inner<'tcx>(
rustc_data_structures::stack::ensure_sufficient_stack(|| { rustc_data_structures::stack::ensure_sufficient_stack(|| {
for ty in args.as_closure().upvar_tys() { for ty in args.as_closure().upvar_tys() {
dtorck_constraint_for_ty_inner(tcx, span, for_ty, depth + 1, ty, constraints)?; dtorck_constraint_for_ty_inner(
tcx,
param_env,
span,
depth + 1,
ty,
constraints,
)?;
} }
Ok::<_, NoSolution>(()) Ok::<_, NoSolution>(())
})? })?
@ -278,8 +284,8 @@ pub fn dtorck_constraint_for_ty_inner<'tcx>(
// only take place through references with lifetimes // only take place through references with lifetimes
// derived from lifetimes attached to the upvars and resume // derived from lifetimes attached to the upvars and resume
// argument, and we *do* incorporate those here. // argument, and we *do* incorporate those here.
let args = args.as_coroutine();
if !args.as_coroutine().is_valid() { if !args.is_valid() {
// By the time this code runs, all type variables ought to // By the time this code runs, all type variables ought to
// be fully resolved. // be fully resolved.
tcx.sess.delay_span_bug( tcx.sess.delay_span_bug(
@ -289,10 +295,13 @@ pub fn dtorck_constraint_for_ty_inner<'tcx>(
return Err(NoSolution); return Err(NoSolution);
} }
constraints // While we conservatively assume that all coroutines require drop
.outlives // to avoid query cycles during MIR building, we can check the actual
.extend(args.as_coroutine().upvar_tys().iter().map(ty::GenericArg::from)); // witness during borrowck to avoid unnecessary liveness constraints.
constraints.outlives.push(args.as_coroutine().resume_ty().into()); if args.witness().needs_drop(tcx, param_env) {
constraints.outlives.extend(args.upvar_tys().iter().map(ty::GenericArg::from));
constraints.outlives.push(args.resume_ty().into());
}
} }
ty::Adt(def, args) => { ty::Adt(def, args) => {

View File

@ -34,6 +34,7 @@ pub(crate) fn adt_dtorck_constraint(
) -> Result<&DropckConstraint<'_>, NoSolution> { ) -> Result<&DropckConstraint<'_>, NoSolution> {
let def = tcx.adt_def(def_id); let def = tcx.adt_def(def_id);
let span = tcx.def_span(def_id); let span = tcx.def_span(def_id);
let param_env = tcx.param_env(def_id);
debug!("dtorck_constraint: {:?}", def); debug!("dtorck_constraint: {:?}", def);
if def.is_manually_drop() { if def.is_manually_drop() {
@ -55,7 +56,7 @@ pub(crate) fn adt_dtorck_constraint(
let mut result = DropckConstraint::empty(); let mut result = DropckConstraint::empty();
for field in def.all_fields() { for field in def.all_fields() {
let fty = tcx.type_of(field.did).instantiate_identity(); let fty = tcx.type_of(field.did).instantiate_identity();
dtorck_constraint_for_ty_inner(tcx, span, fty, 0, fty, &mut result)?; dtorck_constraint_for_ty_inner(tcx, param_env, span, 0, fty, &mut result)?;
} }
result.outlives.extend(tcx.destructor_constraints(def)); result.outlives.extend(tcx.destructor_constraints(def));
dedup_dtorck_constraint(&mut result); dedup_dtorck_constraint(&mut result);

View File

@ -66,6 +66,9 @@ fn has_significant_drop_raw<'tcx>(
struct NeedsDropTypes<'tcx, F> { struct NeedsDropTypes<'tcx, F> {
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>, param_env: ty::ParamEnv<'tcx>,
// Whether to reveal coroutine witnesses, this is set
// to `false` unless we compute `needs_drop` for a generator witness.
reveal_coroutine_witnesses: bool,
query_ty: Ty<'tcx>, query_ty: Ty<'tcx>,
seen_tys: FxHashSet<Ty<'tcx>>, seen_tys: FxHashSet<Ty<'tcx>>,
/// A stack of types left to process, and the recursion depth when we /// A stack of types left to process, and the recursion depth when we
@ -89,6 +92,7 @@ fn new(
Self { Self {
tcx, tcx,
param_env, param_env,
reveal_coroutine_witnesses: false,
seen_tys, seen_tys,
query_ty: ty, query_ty: ty,
unchecked_tys: vec![(ty, 0)], unchecked_tys: vec![(ty, 0)],
@ -133,9 +137,32 @@ fn next(&mut self) -> Option<NeedsDropResult<Ty<'tcx>>> {
// The information required to determine whether a coroutine has drop is // The information required to determine whether a coroutine has drop is
// computed on MIR, while this very method is used to build MIR. // computed on MIR, while this very method is used to build MIR.
// To avoid cycles, we consider that coroutines always require drop. // To avoid cycles, we consider that coroutines always require drop.
ty::Coroutine(..) => { //
// HACK: Because we erase regions contained in the generator witness, we
// have to conservatively assume that every region captured by the
// generator has to be live when dropped. This results in a lot of
// undesirable borrowck errors. During borrowck, we call `needs_drop`
// for the generator witness and check whether any of the contained types
// need to be dropped, and only require the captured types to be live
// if they do.
ty::Coroutine(_, args, _) => {
if self.reveal_coroutine_witnesses {
queue_type(self, args.as_coroutine().witness());
} else {
return Some(Err(AlwaysRequiresDrop)); return Some(Err(AlwaysRequiresDrop));
} }
}
ty::CoroutineWitness(def_id, args) => {
if let Some(witness) = tcx.mir_coroutine_witnesses(def_id) {
self.reveal_coroutine_witnesses = true;
for field_ty in &witness.field_tys {
queue_type(
self,
EarlyBinder::bind(field_ty.ty).instantiate(tcx, args),
);
}
}
}
_ if component.is_copy_modulo_regions(tcx, self.param_env) => (), _ if component.is_copy_modulo_regions(tcx, self.param_env) => (),
@ -191,7 +218,6 @@ fn next(&mut self) -> Option<NeedsDropResult<Ty<'tcx>>> {
| ty::FnPtr(..) | ty::FnPtr(..)
| ty::Tuple(_) | ty::Tuple(_)
| ty::Bound(..) | ty::Bound(..)
| ty::CoroutineWitness(..)
| ty::Never | ty::Never
| ty::Infer(_) | ty::Infer(_)
| ty::Error(_) => { | ty::Error(_) => {

View File

@ -1,24 +1,16 @@
error[E0597]: `a` does not live long enough error[E0597]: `a` does not live long enough
--> $DIR/borrowing.rs:9:33 --> $DIR/borrowing.rs:9:33
| |
LL | let _b = {
| -- borrow later stored here
LL | let a = 3;
LL | Pin::new(&mut || yield &a).resume(()) LL | Pin::new(&mut || yield &a).resume(())
| ----------^ | -- ^ borrowed value does not live long enough
| | | | |
| | borrowed value does not live long enough
| value captured here by coroutine | value captured here by coroutine
| a temporary with access to the borrow is created here ...
LL | LL |
LL | }; LL | };
| -- ... and the borrow might be used here, when that temporary is dropped and runs the destructor for coroutine | - `a` dropped here while still borrowed
| |
| `a` dropped here while still borrowed
|
= note: the temporary is part of an expression at the end of a block;
consider forcing this temporary to be dropped sooner, before the block's local variables are dropped
help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block
|
LL | let x = Pin::new(&mut || yield &a).resume(()); x
| +++++++ +++
error[E0597]: `a` does not live long enough error[E0597]: `a` does not live long enough
--> $DIR/borrowing.rs:16:20 --> $DIR/borrowing.rs:16:20

View File

@ -1,4 +1,5 @@
// edition:2021 // edition:2021
// check-pass
#![feature(coroutines)] #![feature(coroutines)]
fn main() { fn main() {
@ -6,6 +7,5 @@ fn main() {
|| { || {
let _c = || yield *&mut *x; let _c = || yield *&mut *x;
|| _ = &mut *x; || _ = &mut *x;
//~^ cannot borrow `*x` as mutable more than once at a time
}; };
} }

View File

@ -1,18 +0,0 @@
error[E0499]: cannot borrow `*x` as mutable more than once at a time
--> $DIR/issue-110929-coroutine-conflict-error-ice.rs:8:9
|
LL | let _c = || yield *&mut *x;
| -- -- first borrow occurs due to use of `*x` in coroutine
| |
| first mutable borrow occurs here
LL | || _ = &mut *x;
| ^^ -- second borrow occurs due to use of `*x` in closure
| |
| second mutable borrow occurs here
LL |
LL | };
| - first borrow might be used here, when `_c` is dropped and runs the destructor for coroutine
error: aborting due to previous error
For more information about this error, try `rustc --explain E0499`.

View File

@ -4,10 +4,9 @@ error[E0499]: cannot borrow `thing` as mutable more than once at a time
LL | gen.as_mut().resume(&mut thing); LL | gen.as_mut().resume(&mut thing);
| ---------- first mutable borrow occurs here | ---------- first mutable borrow occurs here
LL | gen.as_mut().resume(&mut thing); LL | gen.as_mut().resume(&mut thing);
| ^^^^^^^^^^ second mutable borrow occurs here | ------ ^^^^^^^^^^ second mutable borrow occurs here
LL | | |
LL | } | first borrow later used by call
| - first borrow might be used here, when `gen` is dropped and runs the destructor for coroutine
error: aborting due to previous error error: aborting due to previous error