1
Fork 0

Merge pull request #74 from oli-obk/master

call user defined drop impls
This commit is contained in:
Scott Olson 2016-11-04 01:26:23 -07:00 committed by GitHub
commit ce8806d141
9 changed files with 249 additions and 65 deletions

View file

@ -1,7 +1,7 @@
use std::error::Error;
use std::fmt;
use rustc::mir;
use rustc::ty::BareFnTy;
use rustc::ty::{BareFnTy, Ty};
use memory::Pointer;
use rustc_const_math::ConstMathErr;
use syntax::codemap::Span;
@ -46,6 +46,7 @@ pub enum EvalError<'tcx> {
ModifiedConstantMemory,
AssumptionNotHeld,
InlineAsm,
TypeNotPrimitive(Ty<'tcx>),
}
pub type EvalResult<'tcx, T> = Result<T, EvalError<'tcx>>;
@ -106,6 +107,8 @@ impl<'tcx> Error for EvalError<'tcx> {
"`assume` argument was false",
EvalError::InlineAsm =>
"cannot evaluate inline assembly",
EvalError::TypeNotPrimitive(_) =>
"expected primitive type, got nonprimitive",
}
}
@ -134,6 +137,8 @@ impl<'tcx> fmt::Display for EvalError<'tcx> {
EvalError::AlignmentCheckFailed { required, has } =>
write!(f, "tried to access memory with alignment {}, but alignment {} is required",
has, required),
EvalError::TypeNotPrimitive(ref ty) =>
write!(f, "expected primitive type, got {}", ty),
_ => write!(f, "{}", self.description()),
}
}

View file

@ -1325,11 +1325,11 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
PrimValKind::from_uint_size(size)
}
} else {
bug!("primitive read of non-clike enum: {:?}", ty);
return Err(EvalError::TypeNotPrimitive(ty));
}
},
_ => bug!("primitive read of non-primitive type: {:?}", ty),
_ => return Err(EvalError::TypeNotPrimitive(ty)),
};
Ok(kind)
@ -1552,6 +1552,39 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
}
}
}
/// convenience function to ensure correct usage of globals and code-sharing with locals
pub fn modify_global<
F: FnOnce(&mut Self, Option<Value>) -> EvalResult<'tcx, Option<Value>>,
>(
&mut self,
cid: GlobalId<'tcx>,
f: F,
) -> EvalResult<'tcx, ()> {
let mut val = *self.globals.get(&cid).expect("global not cached");
if !val.mutable {
return Err(EvalError::ModifiedConstantMemory);
}
val.data = f(self, val.data)?;
*self.globals.get_mut(&cid).expect("already checked") = val;
Ok(())
}
/// convenience function to ensure correct usage of locals and code-sharing with globals
pub fn modify_local<
F: FnOnce(&mut Self, Option<Value>) -> EvalResult<'tcx, Option<Value>>,
>(
&mut self,
frame: usize,
local: mir::Local,
f: F,
) -> EvalResult<'tcx, ()> {
let val = self.stack[frame].get_local(local);
let val = f(self, val)?;
// can't use `set_local` here, because that's only meant for going to an initialized value
self.stack[frame].locals[local.index() - 1] = val;
Ok(())
}
}
impl<'tcx> Frame<'tcx> {

View file

@ -6,7 +6,7 @@ use rustc::ty::{self, Ty};
use error::{EvalError, EvalResult};
use interpreter::value::Value;
use interpreter::{EvalContext, Lvalue};
use interpreter::{EvalContext, Lvalue, LvalueExtra};
use primval::{self, PrimVal, PrimValKind};
impl<'a, 'tcx> EvalContext<'a, 'tcx> {
@ -69,6 +69,26 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
self.write_value_to_ptr(arg_vals[1], dest, ty)?;
}
"atomic_fence_acq" => {
// we are inherently singlethreaded and singlecored, this is a nop
}
"atomic_xsub_rel" => {
let ty = substs.type_at(0);
let ptr = arg_vals[0].read_ptr(&self.memory)?;
let change = self.value_to_primval(arg_vals[1], ty)?;
let old = self.read_value(ptr, ty)?;
let old = match old {
Value::ByVal(val) => val,
Value::ByRef(_) => bug!("just read the value, can't be byref"),
Value::ByValPair(..) => bug!("atomic_xsub_rel doesn't work with nonprimitives"),
};
self.write_primval(dest, old)?;
// FIXME: what do atomics do on overflow?
let (val, _) = primval::binary_op(mir::BinOp::Sub, old, change)?;
self.write_primval(Lvalue::from_ptr(ptr), val)?;
}
"breakpoint" => unimplemented!(), // halt miri
"copy" |
@ -101,6 +121,14 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
self.write_primval(dest, PrimVal::new(discr_val, PrimValKind::U64))?;
}
"drop_in_place" => {
let ty = substs.type_at(0);
let ptr = arg_vals[0].read_ptr(&self.memory)?;
let mut drops = Vec::new();
self.drop(Lvalue::from_ptr(ptr), ty, &mut drops)?;
self.eval_drop_impls(drops)?;
}
"fabsf32" => {
let f = self.value_to_primval(arg_vals[2], f32)?
.expect_f32("fabsf32 read non f32");
@ -126,11 +154,34 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
"forget" => {}
"init" => {
// FIXME(solson)
let dest = self.force_allocation(dest)?.to_ptr();
let size = dest_layout.size(&self.tcx.data_layout).bytes() as usize;
self.memory.write_repeat(dest, 0, size)?;
let init = |this: &mut Self, val: Option<Value>| {
match val {
Some(Value::ByRef(ptr)) => {
this.memory.write_repeat(ptr, 0, size)?;
Ok(Some(Value::ByRef(ptr)))
},
None => match this.ty_to_primval_kind(dest_ty) {
Ok(kind) => Ok(Some(Value::ByVal(PrimVal::new(0, kind)))),
Err(_) => {
let ptr = this.alloc_ptr_with_substs(dest_ty, substs)?;
this.memory.write_repeat(ptr, 0, size)?;
Ok(Some(Value::ByRef(ptr)))
}
},
Some(Value::ByVal(value)) => Ok(Some(Value::ByVal(PrimVal::new(0, value.kind)))),
Some(Value::ByValPair(a, b)) => Ok(Some(Value::ByValPair(
PrimVal::new(0, a.kind),
PrimVal::new(0, b.kind),
))),
}
};
match dest {
Lvalue::Local { frame, local } => self.modify_local(frame, local, init)?,
Lvalue::Ptr { ptr, extra: LvalueExtra::None } => self.memory.write_repeat(ptr, 0, size)?,
Lvalue::Ptr { .. } => bug!("init intrinsic tried to write to fat ptr target"),
Lvalue::Global(cid) => self.modify_global(cid, init)?,
}
}
"min_align_of" => {
@ -225,6 +276,15 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
let size_val = self.usize_primval(size);
self.write_primval(dest, size_val)?;
}
"min_align_of_val" |
"align_of_val" => {
let ty = substs.type_at(0);
let (_, align) = self.size_and_align_of_dst(ty, arg_vals[0])?;
let align_val = self.usize_primval(align);
self.write_primval(dest, align_val)?;
}
"type_name" => {
let ty = substs.type_at(0);
let ty_name = ty.to_string();
@ -248,12 +308,23 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
}
"uninit" => {
// FIXME(solson): Attempt writing a None over the destination when it's an
// Lvalue::Local (that is not ByRef). Otherwise do the mark_definedness as usual.
let dest = self.force_allocation(dest)?.to_ptr();
let size = dest_layout.size(&self.tcx.data_layout).bytes() as usize;
self.memory.mark_definedness(dest, size, false)?;
let uninit = |this: &mut Self, val: Option<Value>| {
match val {
Some(Value::ByRef(ptr)) => {
this.memory.mark_definedness(ptr, size, false)?;
Ok(Some(Value::ByRef(ptr)))
},
None => Ok(None),
Some(_) => Ok(None),
}
};
match dest {
Lvalue::Local { frame, local } => self.modify_local(frame, local, uninit)?,
Lvalue::Ptr { ptr, extra: LvalueExtra::None } => self.memory.mark_definedness(ptr, size, false)?,
Lvalue::Ptr { .. } => bug!("uninit intrinsic tried to write to fat ptr target"),
Lvalue::Global(cid) => self.modify_global(cid, uninit)?,
}
}
name => return Err(EvalError::Unimplemented(format!("unimplemented intrinsic: {}", name))),

View file

@ -104,14 +104,17 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
}
Drop { ref location, target, .. } => {
// FIXME(solson)
let lvalue = self.eval_lvalue(location)?;
let lvalue = self.force_allocation(lvalue)?;
let lval = self.eval_lvalue(location)?;
let ptr = lvalue.to_ptr();
let ty = self.lvalue_ty(location);
self.drop(ptr, ty)?;
// we can't generate the drop stack frames on the fly,
// because that would change our call stack
// and very much confuse the further processing of the drop glue
let mut drops = Vec::new();
self.drop(lval, ty, &mut drops)?;
self.goto_block(target);
self.eval_drop_impls(drops)?;
}
Assert { ref cond, expected, ref msg, target, .. } => {
@ -143,6 +146,30 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
Ok(())
}
fn eval_drop_impls(&mut self, drops: Vec<(DefId, Pointer, &'tcx Substs<'tcx>)>) -> EvalResult<'tcx, ()> {
let span = self.frame().span;
for (drop_def_id, adt_ptr, substs) in drops {
// FIXME: supply a real span
let mir = self.load_mir(drop_def_id)?;
trace!("substs for drop glue: {:?}", substs);
self.push_stack_frame(
drop_def_id,
span,
mir,
substs,
Lvalue::from_ptr(Pointer::zst_ptr()),
StackPopCleanup::None,
)?;
let mut arg_locals = self.frame().mir.args_iter();
let first = arg_locals.next().expect("drop impl has self arg");
assert!(arg_locals.next().is_none(), "drop impl should have only one arg");
let dest = self.eval_lvalue(&mir::Lvalue::Local(first))?;
let ty = self.frame().mir.local_decls[first].ty;
self.write_value(Value::ByVal(PrimVal::from_ptr(adt_ptr)), dest, ty)?;
}
Ok(())
}
fn eval_fn_call(
&mut self,
def_id: DefId,
@ -294,6 +321,16 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
self.write_primval(dest, PrimVal::from_ptr(ptr))?;
}
"__rust_deallocate" => {
let ptr = args[0].read_ptr(&self.memory)?;
// FIXME: insert sanity check for size and align?
let _old_size = self.value_to_primval(args[1], usize)?
.expect_uint("__rust_deallocate second arg not usize");
let _align = self.value_to_primval(args[2], usize)?
.expect_uint("__rust_deallocate third arg not usize");
self.memory.deallocate(ptr)?;
},
"__rust_reallocate" => {
let ptr = args[0].read_ptr(&self.memory)?;
let size = self.value_to_primval(args[2], usize)?.expect_uint("__rust_reallocate third arg not usize");
@ -471,25 +508,75 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
self.tcx.type_needs_drop_given_env(ty, &self.tcx.empty_parameter_environment())
}
fn drop(&mut self, ptr: Pointer, ty: Ty<'tcx>) -> EvalResult<'tcx, ()> {
/// push DefIds of drop impls and their argument on the given vector
fn drop(
&mut self,
lval: Lvalue<'tcx>,
ty: Ty<'tcx>,
drop: &mut Vec<(DefId, Pointer, &'tcx Substs<'tcx>)>,
) -> EvalResult<'tcx, ()> {
if !self.type_needs_drop(ty) {
debug!("no need to drop {:?}", ty);
return Ok(());
}
trace!("-need to drop {:?}", ty);
// TODO(solson): Call user-defined Drop::drop impls.
trace!("-need to drop {:?} at {:?}", ty, lval);
match ty.sty {
ty::TyBox(_contents_ty) => {
let contents_ptr = self.memory.read_ptr(ptr)?;
// self.drop(contents_ptr, contents_ty)?;
// special case `Box` to deallocate the inner allocation
ty::TyBox(contents_ty) => {
let val = self.read_lvalue(lval)?;
let contents_ptr = val.read_ptr(&self.memory)?;
self.drop(Lvalue::from_ptr(contents_ptr), contents_ty, drop)?;
trace!("-deallocating box");
self.memory.deallocate(contents_ptr)?;
}
},
// TODO(solson): Implement drop for other relevant types (e.g. aggregates).
_ => {}
ty::TyAdt(adt_def, substs) => {
// FIXME: some structs are represented as ByValPair
let adt_ptr = self.force_allocation(lval)?.to_ptr();
// run drop impl before the fields' drop impls
if let Some(drop_def_id) = adt_def.destructor() {
drop.push((drop_def_id, adt_ptr, substs));
}
let layout = self.type_layout(ty);
let fields = match *layout {
Layout::Univariant { ref variant, .. } => {
adt_def.struct_variant().fields.iter().zip(&variant.offsets)
},
Layout::General { ref variants, .. } => {
let discr_val = self.read_discriminant_value(adt_ptr, ty)?;
match adt_def.variants.iter().position(|v| discr_val == v.disr_val.to_u64_unchecked()) {
// start at offset 1, to skip over the discriminant
Some(i) => adt_def.variants[i].fields.iter().zip(&variants[i].offsets[1..]),
None => return Err(EvalError::InvalidDiscriminant),
}
},
Layout::StructWrappedNullablePointer { nndiscr, ref nonnull, .. } => {
let discr = self.read_discriminant_value(adt_ptr, ty)?;
if discr == nndiscr {
adt_def.variants[discr as usize].fields.iter().zip(&nonnull.offsets)
} else {
// FIXME: the zst variant might contain zst types that impl Drop
return Ok(()); // nothing to do, this is zero sized (e.g. `None`)
}
},
_ => bug!("{:?} is not an adt layout", layout),
};
for (field_ty, offset) in fields {
let field_ty = self.monomorphize_field_ty(field_ty, substs);
self.drop(Lvalue::from_ptr(adt_ptr.offset(offset.bytes() as isize)), field_ty, drop)?;
}
},
ty::TyTuple(fields) => {
// FIXME: some tuples are represented as ByValPair
let ptr = self.force_allocation(lval)?.to_ptr();
for (i, field_ty) in fields.iter().enumerate() {
let offset = self.get_field_offset(ty, i)?.bytes() as isize;
self.drop(Lvalue::from_ptr(ptr.offset(offset)), field_ty, drop)?;
}
},
// other types do not need to process drop
_ => {},
}
Ok(())

View file

@ -23,12 +23,9 @@ impl<'a, 'tcx: 'a> Value {
match *self {
ByRef(ptr) => mem.read_ptr(ptr),
ByVal(ptr) if ptr.try_as_ptr().is_some() => {
Ok(ptr.try_as_ptr().unwrap())
ByVal(ptr) | ByValPair(ptr, _) => {
Ok(ptr.try_as_ptr().expect("unimplemented: `read_ptr` on non-ptr primval"))
}
ByValPair(..) => unimplemented!(),
ByVal(_other) => unimplemented!(),
}
}

View file

@ -11,25 +11,20 @@ fn make_box_syntax() -> Box<(i16, i16)> {
fn allocate_reallocate() {
let mut s = String::new();
// 4 byte heap alloc (__rust_allocate)
s.push('f');
assert_eq!(s.len(), 1);
assert_eq!(s.capacity(), 4);
// 6 byte heap alloc (__rust_allocate)
s.push_str("foobar");
assert_eq!(s.len(), 6);
assert_eq!(s.capacity(), 6);
// heap size doubled to 8 (__rust_reallocate)
// FIXME: String::push_str is broken because it hits the std::vec::SetLenOnDrop code and we
// don't call destructors in miri yet.
s.push('o');
s.push('o');
s.push('o');
s.push('o');
assert_eq!(s.len(), 5);
assert_eq!(s.capacity(), 8);
// heap size doubled to 12 (__rust_reallocate)
s.push_str("baz");
assert_eq!(s.len(), 9);
assert_eq!(s.capacity(), 12);
// heap size reduced to 5 (__rust_reallocate)
// heap size reduced to 9 (__rust_reallocate)
s.shrink_to_fit();
assert_eq!(s.len(), 5);
assert_eq!(s.capacity(), 5);
assert_eq!(s.len(), 9);
assert_eq!(s.capacity(), 9);
}
fn main() {

View file

@ -1,5 +1,3 @@
//error-pattern: no mir for `std::result::unwrap_failed::__STATIC_FMTSTR`
use std::cell::RefCell;
use std::rc::Rc;

View file

@ -1,13 +1,3 @@
// FIXME: The normal `vec!` macro is currently broken in Miri because it hits the
// std::vec::SetLenOnDrop code and Miri doesn't call destructors yet.
macro_rules! miri_vec {
($($e:expr),*) => ({
let mut v = Vec::new();
$(v.push($e);)*
v
});
}
fn make_vec() -> Vec<u8> {
let mut v = Vec::with_capacity(4);
v.push(1);
@ -16,22 +6,22 @@ fn make_vec() -> Vec<u8> {
}
fn make_vec_macro() -> Vec<u8> {
miri_vec![1, 2]
vec![1, 2]
}
fn make_vec_macro_repeat() -> Vec<u8> {
miri_vec![42, 42, 42, 42, 42]
vec![42; 5]
}
fn vec_into_iter() -> u8 {
miri_vec![1, 2, 3, 4]
vec![1, 2, 3, 4]
.into_iter()
.map(|x| x * x)
.fold(0, |x, y| x + y)
}
fn vec_reallocate() -> Vec<u8> {
let mut v = miri_vec![1, 2];
let mut v = vec![1, 2];
v.push(3);
v.push(4);
v.push(5);

View file

@ -0,0 +1,8 @@
fn main() {
let x = Box::new(());
let y = Box::new(());
drop(y);
let z = Box::new(());
drop(x);
drop(z);
}