when suspending, we need to specify for which lifetime to recover

This matters if the lvalues that is suspended involves Deref'ing a reference --
that reference's lifetime will then not be in the type any more
This commit is contained in:
Ralf Jung 2017-07-21 12:43:09 -07:00
parent a233afa794
commit 60096b9e82
5 changed files with 147 additions and 56 deletions

View File

@ -684,6 +684,16 @@ pub enum Mutability {
MutImmutable,
}
impl Mutability {
/// Return MutMutable only if both arguments are mutable.
pub fn and(self, other: Self) -> Self {
match self {
MutMutable => other,
MutImmutable => MutImmutable,
}
}
}
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
pub enum BinOp_ {
/// The `+` operator (addition)

View File

@ -243,6 +243,8 @@ for mir::StatementKind<'tcx> {
}
}
impl_stable_hash_for!(struct mir::ValidationOperand<'tcx> { lval, ty, re, mutbl });
impl_stable_hash_for!(enum mir::ValidationOp { Acquire, Release, Suspend(extent) });
impl<'a, 'gcx, 'tcx> HashStable<StableHashingContext<'a, 'gcx, 'tcx>> for mir::Lvalue<'tcx> {

View File

@ -25,7 +25,7 @@ use ty::{self, AdtDef, ClosureSubsts, Region, Ty};
use ty::fold::{TypeFoldable, TypeFolder, TypeVisitor};
use util::ppaux;
use rustc_back::slice;
use hir::InlineAsm;
use hir::{self, InlineAsm};
use std::ascii;
use std::borrow::{Cow};
use std::cell::Ref;
@ -826,7 +826,7 @@ pub enum StatementKind<'tcx> {
},
/// Assert the given lvalues to be valid inhabitants of their type.
Validate(ValidationOp, Vec<(Ty<'tcx>, Lvalue<'tcx>)>),
Validate(ValidationOp, Vec<ValidationOperand<'tcx>>),
/// Mark one terminating point of an extent (i.e. static region).
/// (The starting point(s) arise implicitly from borrows.)
@ -855,6 +855,28 @@ impl Debug for ValidationOp {
}
}
#[derive(Clone, RustcEncodable, RustcDecodable)]
pub struct ValidationOperand<'tcx> {
pub lval: Lvalue<'tcx>,
pub ty: Ty<'tcx>,
pub re: Option<CodeExtent>,
pub mutbl: hir::Mutability,
}
impl<'tcx> Debug for ValidationOperand<'tcx> {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
write!(fmt, "{:?}@{:?}", self.lval, self.ty)?;
if let Some(ce) = self.re {
// (reuse lifetime rendering policy from ppaux.)
write!(fmt, "/{}", ty::ReScope(ce))?;
}
if let hir::MutImmutable = self.mutbl {
write!(fmt, " (imm)")?;
}
Ok(())
}
}
impl<'tcx> Debug for Statement<'tcx> {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
use self::StatementKind::*;
@ -1505,6 +1527,21 @@ impl<'tcx> TypeFoldable<'tcx> for BasicBlockData<'tcx> {
}
}
impl<'tcx> TypeFoldable<'tcx> for ValidationOperand<'tcx> {
fn super_fold_with<'gcx: 'tcx, F: TypeFolder<'gcx, 'tcx>>(&self, folder: &mut F) -> Self {
ValidationOperand {
lval: self.lval.fold_with(folder),
ty: self.ty.fold_with(folder),
re: self.re,
mutbl: self.mutbl,
}
}
fn super_visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> bool {
self.lval.visit_with(visitor) || self.ty.visit_with(visitor)
}
}
impl<'tcx> TypeFoldable<'tcx> for Statement<'tcx> {
fn super_fold_with<'gcx: 'tcx, F: TypeFolder<'gcx, 'tcx>>(&self, folder: &mut F) -> Self {
use mir::StatementKind::*;
@ -1531,7 +1568,7 @@ impl<'tcx> TypeFoldable<'tcx> for Statement<'tcx> {
Validate(ref op, ref lvals) =>
Validate(op.clone(),
lvals.iter().map(|ty_and_lval| ty_and_lval.fold_with(folder)).collect()),
lvals.iter().map(|operand| operand.fold_with(folder)).collect()),
Nop => Nop,
};

View File

@ -334,9 +334,10 @@ macro_rules! make_mir_visitor {
}
StatementKind::EndRegion(_) => {}
StatementKind::Validate(_, ref $($mutability)* lvalues) => {
for & $($mutability)* (ref $($mutability)* ty, ref $($mutability)* lvalue) in lvalues {
self.visit_ty(ty, Lookup::Loc(location));
self.visit_lvalue(lvalue, LvalueContext::Validate, location);
for operand in lvalues {
self.visit_lvalue(& $($mutability)* operand.lval,
LvalueContext::Validate, location);
self.visit_ty(& $($mutability)* operand.ty, Lookup::Loc(location));
}
}
StatementKind::SetDiscriminant{ ref $($mutability)* lvalue, .. } => {

View File

@ -14,34 +14,67 @@
//! of MIR building, and only after this pass we think of the program has having the
//! normal MIR semantics.
use rustc::ty::{TyCtxt, RegionKind};
use rustc::ty::{self, TyCtxt, RegionKind};
use rustc::hir;
use rustc::mir::*;
use rustc::mir::transform::{MirPass, MirSource};
use rustc::middle::region::CodeExtent;
pub struct AddValidation;
fn is_lvalue_shared<'a, 'tcx, D>(lval: &Lvalue<'tcx>, local_decls: &D, tcx: TyCtxt<'a, 'tcx, 'tcx>) -> bool
/// Determine the "context" of the lval: Mutability and region.
fn lval_context<'a, 'tcx, D>(
lval: &Lvalue<'tcx>,
local_decls: &D,
tcx: TyCtxt<'a, 'tcx, 'tcx>
) -> (Option<CodeExtent>, hir::Mutability)
where D: HasLocalDecls<'tcx>
{
use rustc::mir::Lvalue::*;
match *lval {
Local { .. } => false,
Static(_) => true,
Local { .. } => (None, hir::MutMutable),
Static(_) => (None, hir::MutImmutable),
Projection(ref proj) => {
// If the base is shared, things stay shared
if is_lvalue_shared(&proj.base, local_decls, tcx) {
return true;
}
// A Deref projection may make things shared
match proj.elem {
ProjectionElem::Deref => {
// Computing the inside the recursion makes this quadratic. We don't expect deep paths though.
// Computing the inside the recursion makes this quadratic.
// We don't expect deep paths though.
let ty = proj.base.ty(local_decls, tcx).to_ty(tcx);
!ty.is_mutable_pointer()
// A Deref projection may restrict the context, this depends on the type
// being deref'd.
let context = match ty.sty {
ty::TyRef(re, tam) => {
let re = match re {
&RegionKind::ReScope(ce) => Some(ce),
&RegionKind::ReErased =>
bug!("AddValidation pass must be run before erasing lifetimes"),
_ => None
};
(re, tam.mutbl)
}
ty::TyRawPtr(_) =>
// There is no guarantee behind even a mutable raw pointer,
// no write locks are acquired there, so we also don't want to
// release any.
(None, hir::MutImmutable),
ty::TyAdt(adt, _) if adt.is_box() => (None, hir::MutMutable),
_ => bug!("Deref on a non-pointer type {:?}", ty),
};
// "Intersect" this restriction with proj.base.
if let (Some(_), hir::MutImmutable) = context {
// This is already as restricted as it gets, no need to even recurse
context
} else {
let base_context = lval_context(&proj.base, local_decls, tcx);
// The region of the outermost Deref is always most restrictive.
let re = context.0.or(base_context.0);
let mutbl = context.1.and(base_context.1);
(re, mutbl)
}
}
_ => false,
_ => lval_context(&proj.base, local_decls, tcx),
}
}
}
@ -52,41 +85,49 @@ impl MirPass for AddValidation {
tcx: TyCtxt<'a, 'tcx, 'tcx>,
_: MirSource,
mir: &mut Mir<'tcx>) {
let local_decls = mir.local_decls.clone(); // TODO: Find a way to get rid of this clone.
/// Convert an lvalue to a validation operand.
let lval_to_operand = |lval: Lvalue<'tcx>| -> ValidationOperand<'tcx> {
let (re, mutbl) = lval_context(&lval, &local_decls, tcx);
let ty = lval.ty(&local_decls, tcx).to_ty(tcx);
ValidationOperand { lval, ty, re, mutbl }
};
// PART 1
// Add an AcquireValid at the beginning of the start block.
if mir.arg_count > 0 {
let acquire_stmt = Statement {
source_info: SourceInfo {
scope: ARGUMENT_VISIBILITY_SCOPE,
span: mir.span, // TODO: Consider using just the span covering the function argument declaration
span: mir.span, // TODO: Consider using just the span covering the function
// argument declaration.
},
kind: StatementKind::Validate(ValidationOp::Acquire,
// Skip return value, go over all the arguments
mir.local_decls.iter_enumerated().skip(1).take(mir.arg_count)
.map(|(local, local_decl)| (local_decl.ty, Lvalue::Local(local))).collect()
.map(|(local, _)| lval_to_operand(Lvalue::Local(local))).collect()
)
};
mir.basic_blocks_mut()[START_BLOCK].statements.insert(0, acquire_stmt);
}
// PART 2
// Add ReleaseValid/AcquireValid around function call terminators. We don't use a visitor because
// we need to access the block that a Call jumps to.
let mut returns : Vec<(SourceInfo, Lvalue<'tcx>, BasicBlock)> = Vec::new(); // Here we collect the destinations.
let local_decls = mir.local_decls.clone(); // TODO: Find a way to get rid of this clone.
// Add ReleaseValid/AcquireValid around function call terminators. We don't use a visitor
// because we need to access the block that a Call jumps to.
let mut returns : Vec<(SourceInfo, Lvalue<'tcx>, BasicBlock)> = Vec::new();
for block_data in mir.basic_blocks_mut() {
match block_data.terminator {
Some(Terminator { kind: TerminatorKind::Call { ref args, ref destination, .. }, source_info }) => {
Some(Terminator { kind: TerminatorKind::Call { ref args, ref destination, .. },
source_info }) => {
// Before the call: Release all arguments
let release_stmt = Statement {
source_info,
kind: StatementKind::Validate(ValidationOp::Release,
args.iter().filter_map(|op| {
match op {
&Operand::Consume(ref lval) => {
let ty = lval.ty(&local_decls, tcx).to_ty(tcx);
Some((ty, lval.clone()))
},
&Operand::Consume(ref lval) =>
Some(lval_to_operand(lval.clone())),
&Operand::Constant(..) => { None },
}
}).collect())
@ -97,13 +138,15 @@ impl MirPass for AddValidation {
returns.push((source_info, destination.0.clone(), destination.1));
}
}
Some(Terminator { kind: TerminatorKind::Drop { location: ref lval, .. }, source_info }) |
Some(Terminator { kind: TerminatorKind::DropAndReplace { location: ref lval, .. }, source_info }) => {
Some(Terminator { kind: TerminatorKind::Drop { location: ref lval, .. },
source_info }) |
Some(Terminator { kind: TerminatorKind::DropAndReplace { location: ref lval, .. },
source_info }) => {
// Before the call: Release all arguments
let ty = lval.ty(&local_decls, tcx).to_ty(tcx);
let release_stmt = Statement {
source_info,
kind: StatementKind::Validate(ValidationOp::Release, vec![(ty, lval.clone())])
kind: StatementKind::Validate(ValidationOp::Release,
vec![lval_to_operand(lval.clone())]),
};
block_data.statements.push(release_stmt);
// drop doesn't return anything, so we need no acquire.
@ -115,20 +158,20 @@ impl MirPass for AddValidation {
}
// Now we go over the returns we collected to acquire the return values.
for (source_info, dest_lval, dest_block) in returns {
let ty = dest_lval.ty(&local_decls, tcx).to_ty(tcx);
let acquire_stmt = Statement {
source_info,
kind: StatementKind::Validate(ValidationOp::Acquire, vec![(ty, dest_lval)])
kind: StatementKind::Validate(ValidationOp::Acquire,
vec![lval_to_operand(dest_lval)]),
};
mir.basic_blocks_mut()[dest_block].statements.insert(0, acquire_stmt);
}
// PART 3
// Add ReleaseValid/AcquireValid around Ref. Again an iterator does not seem very suited as
// we need to add new statements before and after each Ref.
// Add ReleaseValid/AcquireValid around Ref. Again an iterator does not seem very suited
// as we need to add new statements before and after each Ref.
for block_data in mir.basic_blocks_mut() {
// We want to insert statements around Ref commands as we iterate. To this end, we iterate backwards
// using indices.
// We want to insert statements around Ref commands as we iterate. To this end, we
// iterate backwards using indices.
for i in (0..block_data.statements.len()).rev() {
let (dest_lval, re, src_lval) = match block_data.statements[i].kind {
StatementKind::Assign(ref dest_lval, Rvalue::Ref(re, _, ref src_lval)) => {
@ -137,27 +180,25 @@ impl MirPass for AddValidation {
_ => continue,
};
// So this is a ref, and we got all the data we wanted.
let dest_ty = dest_lval.ty(&local_decls, tcx).to_ty(tcx);
let acquire_stmt = Statement {
source_info: block_data.statements[i].source_info,
kind: StatementKind::Validate(ValidationOp::Acquire, vec![(dest_ty, dest_lval)]),
kind: StatementKind::Validate(ValidationOp::Acquire,
vec![lval_to_operand(dest_lval)]),
};
block_data.statements.insert(i+1, acquire_stmt);
// The source is released until the region of the borrow ends -- but not if it is shared.
if !is_lvalue_shared(&src_lval, &local_decls, tcx) {
let src_ty = src_lval.ty(&local_decls, tcx).to_ty(tcx);
let op = match re {
&RegionKind::ReScope(ce) => ValidationOp::Suspend(ce),
&RegionKind::ReErased => bug!("AddValidation pass must be run before erasing lifetimes"),
_ => ValidationOp::Release,
};
let release_stmt = Statement {
source_info: block_data.statements[i].source_info,
kind: StatementKind::Validate(op, vec![(src_ty, src_lval)]),
};
block_data.statements.insert(i, release_stmt);
}
// The source is released until the region of the borrow ends.
let op = match re {
&RegionKind::ReScope(ce) => ValidationOp::Suspend(ce),
&RegionKind::ReErased =>
bug!("AddValidation pass must be run before erasing lifetimes"),
_ => ValidationOp::Release,
};
let release_stmt = Statement {
source_info: block_data.statements[i].source_info,
kind: StatementKind::Validate(op, vec![lval_to_operand(src_lval)]),
};
block_data.statements.insert(i, release_stmt);
}
}
}