diff --git a/src/error.rs b/src/error.rs index a44b8cc76eb..86f22d33a25 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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>; @@ -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()), } } diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index b72feba8bc0..0cd7190db99 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -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) -> EvalResult<'tcx, Option>, + >( + &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) -> EvalResult<'tcx, Option>, + >( + &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> { diff --git a/src/interpreter/terminator/intrinsics.rs b/src/interpreter/terminator/intrinsics.rs index 48098e4f497..71fb83d580e 100644 --- a/src/interpreter/terminator/intrinsics.rs +++ b/src/interpreter/terminator/intrinsics.rs @@ -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| { + 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| { + 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))), diff --git a/src/interpreter/terminator/mod.rs b/src/interpreter/terminator/mod.rs index 823caae623e..fdd703e940b 100644 --- a/src/interpreter/terminator/mod.rs +++ b/src/interpreter/terminator/mod.rs @@ -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(()) diff --git a/src/interpreter/value.rs b/src/interpreter/value.rs index 22698f97818..d9285a41e41 100644 --- a/src/interpreter/value.rs +++ b/src/interpreter/value.rs @@ -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!(), } } diff --git a/tests/run-pass/heap.rs b/tests/run-pass/heap.rs index 4bf6a085e35..b533f916469 100644 --- a/tests/run-pass/heap.rs +++ b/tests/run-pass/heap.rs @@ -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() { diff --git a/tests/compile-fail/rc.rs b/tests/run-pass/rc.rs similarity index 72% rename from tests/compile-fail/rc.rs rename to tests/run-pass/rc.rs index 001dec5b427..c96818932d7 100644 --- a/tests/compile-fail/rc.rs +++ b/tests/run-pass/rc.rs @@ -1,5 +1,3 @@ -//error-pattern: no mir for `std::result::unwrap_failed::__STATIC_FMTSTR` - use std::cell::RefCell; use std::rc::Rc; diff --git a/tests/run-pass/vecs.rs b/tests/run-pass/vecs.rs index 0ad7a371762..b3a88014e6f 100644 --- a/tests/run-pass/vecs.rs +++ b/tests/run-pass/vecs.rs @@ -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 { let mut v = Vec::with_capacity(4); v.push(1); @@ -16,22 +6,22 @@ fn make_vec() -> Vec { } fn make_vec_macro() -> Vec { - miri_vec![1, 2] + vec![1, 2] } fn make_vec_macro_repeat() -> Vec { - 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 { - let mut v = miri_vec![1, 2]; + let mut v = vec![1, 2]; v.push(3); v.push(4); v.push(5); diff --git a/tests/run-pass/zst_box.rs b/tests/run-pass/zst_box.rs new file mode 100644 index 00000000000..12138be5af9 --- /dev/null +++ b/tests/run-pass/zst_box.rs @@ -0,0 +1,8 @@ +fn main() { + let x = Box::new(()); + let y = Box::new(()); + drop(y); + let z = Box::new(()); + drop(x); + drop(z); +}