Merge pull request #74 from oli-obk/master
call user defined drop impls
This commit is contained in:
commit
ce8806d141
9 changed files with 249 additions and 65 deletions
|
@ -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()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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))),
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
//error-pattern: no mir for `std::result::unwrap_failed::__STATIC_FMTSTR`
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
|
@ -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);
|
||||
|
|
8
tests/run-pass/zst_box.rs
Normal file
8
tests/run-pass/zst_box.rs
Normal 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);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue