diff --git a/benches/miri_helper.rs b/benches/miri_helper.rs index 54c15a27ed..e085373a36 100644 --- a/benches/miri_helper.rs +++ b/benches/miri_helper.rs @@ -10,7 +10,7 @@ extern crate test; use self::miri::interpreter; use self::rustc::session::Session; -use self::rustc_driver::{driver, CompilerCalls}; +use self::rustc_driver::{driver, CompilerCalls, Compilation}; use std::cell::RefCell; use std::rc::Rc; use std::env::var; @@ -35,6 +35,7 @@ impl<'a> CompilerCalls<'a> for MiriCompilerCalls<'a> { let bencher = self.0.clone(); + control.after_analysis.stop = Compilation::Stop; control.after_analysis.callback = Box::new(move |state| { state.session.abort_if_errors(); bencher.borrow_mut().iter(|| { diff --git a/src/interpreter.rs b/src/interpreter/mod.rs similarity index 88% rename from src/interpreter.rs rename to src/interpreter/mod.rs index 0f9cc89919..6c0d58f1c4 100644 --- a/src/interpreter.rs +++ b/src/interpreter/mod.rs @@ -20,6 +20,10 @@ use error::{EvalError, EvalResult}; use memory::{Memory, Pointer}; use primval::{self, PrimVal}; +use std::collections::HashMap; + +mod stepper; + struct GlobalEvalContext<'a, 'tcx: 'a> { /// The results of the type checker, from rustc. tcx: TyCtxt<'a, 'tcx, 'tcx>, @@ -33,16 +37,8 @@ struct GlobalEvalContext<'a, 'tcx: 'a> { /// The virtual memory system. memory: Memory, - /// Another stack containing the type substitutions for the current function invocation. It - /// exists separately from `stack` because it must contain the `Substs` for a function while - /// *creating* the `Frame` for that same function. - substs_stack: Vec<&'tcx Substs<'tcx>>, - - // TODO(solson): Merge with `substs_stack`. Also try restructuring `Frame` to accomodate. - /// A stack of the things necessary to print good strack traces: - /// * Function DefIds and Substs to print proper substituted function names. - /// * Spans pointing to specific function calls in the source. - name_stack: Vec<(DefId, &'tcx Substs<'tcx>, codemap::Span)>, + /// Precomputed statics, constants and promoteds + statics: HashMap, Pointer>, } struct FnEvalContext<'a, 'b: 'a + 'mir, 'mir, 'tcx: 'b> { @@ -67,10 +63,19 @@ impl<'a, 'b, 'mir, 'tcx> DerefMut for FnEvalContext<'a, 'b, 'mir, 'tcx> { /// A stack frame. struct Frame<'a, 'tcx: 'a> { + /// The def_id of the current function + def_id: DefId, + + /// The span of the call site + span: codemap::Span, + + /// type substitutions for the current function invocation + substs: &'tcx Substs<'tcx>, + /// The MIR for the function called on this frame. mir: CachedMir<'a, 'tcx>, - /// The block this frame will execute when a function call returns back to this frame. + /// The block that is currently executed (or will be executed after the above call stacks return) next_block: mir::BasicBlock, /// A pointer for writing the return value of the current call if it's not a diverging call. @@ -86,6 +91,9 @@ struct Frame<'a, 'tcx: 'a> { /// The offset of the first temporary in `self.locals`. temp_offset: usize, + + /// The index of the currently evaluated statment + stmt: usize, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -110,8 +118,8 @@ enum CachedMir<'mir, 'tcx: 'mir> { /// Represents the action to be taken in the main loop as a result of executing a terminator. enum TerminatorTarget { - /// Make a local jump to the given block. - Block(mir::BasicBlock), + /// Make a local jump to the next block + Block, /// Start executing from the new current frame. (For function calls.) Call, @@ -120,6 +128,26 @@ enum TerminatorTarget { Return, } +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +/// Uniquely identifies a specific constant or static +struct ConstantId<'tcx> { + /// the def id of the constant/static or in case of promoteds, the def id of the function they belong to + def_id: DefId, + /// In case of statics and constants this is `Substs::empty()`, so only promoteds and associated + /// constants actually have something useful here. We could special case statics and constants, + /// but that would only require more branching when working with constants, and not bring any + /// real benefits. + substs: &'tcx Substs<'tcx>, + kind: ConstantKind, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +enum ConstantKind { + Promoted(usize), + /// Statics, constants and associated constants + Global, +} + impl<'a, 'tcx> GlobalEvalContext<'a, 'tcx> { fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>, mir_map: &'a MirMap<'tcx>) -> Self { GlobalEvalContext { @@ -131,107 +159,253 @@ impl<'a, 'tcx> GlobalEvalContext<'a, 'tcx> { .uint_type .bit_width() .expect("Session::target::uint_type was usize")/8), - substs_stack: Vec::new(), - name_stack: Vec::new(), + statics: HashMap::new(), } } -} -impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> { - fn new(gecx: &'a mut GlobalEvalContext<'b, 'tcx>) -> Self { - FnEvalContext { - gecx: gecx, - stack: Vec::new(), + fn call(&mut self, mir: &mir::Mir<'tcx>, def_id: DefId) -> EvalResult> { + let substs = self.tcx.mk_substs(subst::Substs::empty()); + let return_ptr = self.alloc_ret_ptr(mir.return_ty, substs); + + let mut nested_fecx = FnEvalContext::new(self); + + nested_fecx.push_stack_frame(def_id, mir.span, CachedMir::Ref(mir), substs, None); + + nested_fecx.frame_mut().return_ptr = return_ptr; + + nested_fecx.run()?; + Ok(return_ptr) + } + + fn alloc_ret_ptr(&mut self, output_ty: ty::FnOutput<'tcx>, substs: &'tcx Substs<'tcx>) -> Option { + match output_ty { + ty::FnConverging(ty) => { + let size = self.type_size(ty, substs); + Some(self.memory.allocate(size)) + } + ty::FnDiverging => None, } } - fn maybe_report(&self, span: codemap::Span, r: EvalResult) -> EvalResult { - if let Err(ref e) = r { - let mut err = self.tcx.sess.struct_span_err(span, &e.to_string()); - for &(def_id, substs, span) in self.name_stack.iter().rev() { - // FIXME(solson): Find a way to do this without this Display impl hack. - use rustc::util::ppaux; - use std::fmt; - struct Instance<'tcx>(DefId, &'tcx Substs<'tcx>); - impl<'tcx> fmt::Display for Instance<'tcx> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ppaux::parameterized(f, self.1, self.0, ppaux::Ns::Value, &[], - |tcx| tcx.lookup_item_type(self.0).generics) - } - } - err.span_note(span, &format!("inside call to {}", Instance(def_id, substs))); + // TODO(solson): Try making const_to_primval instead. + fn const_to_ptr(&mut self, const_val: &const_val::ConstVal) -> EvalResult { + use rustc::middle::const_val::ConstVal::*; + match *const_val { + Float(_f) => unimplemented!(), + Integral(int) => { + // TODO(solson): Check int constant type. + let ptr = self.memory.allocate(8); + self.memory.write_uint(ptr, int.to_u64_unchecked(), 8)?; + Ok(ptr) } - err.emit(); + Str(ref s) => { + let psize = self.memory.pointer_size; + let static_ptr = self.memory.allocate(s.len()); + let ptr = self.memory.allocate(psize * 2); + self.memory.write_bytes(static_ptr, s.as_bytes())?; + self.memory.write_ptr(ptr, static_ptr)?; + self.memory.write_usize(ptr.offset(psize as isize), s.len() as u64)?; + Ok(ptr) + } + ByteStr(ref bs) => { + let psize = self.memory.pointer_size; + let static_ptr = self.memory.allocate(bs.len()); + let ptr = self.memory.allocate(psize); + self.memory.write_bytes(static_ptr, bs)?; + self.memory.write_ptr(ptr, static_ptr)?; + Ok(ptr) + } + Bool(b) => { + let ptr = self.memory.allocate(1); + self.memory.write_bool(ptr, b)?; + Ok(ptr) + } + Char(_c) => unimplemented!(), + Struct(_node_id) => unimplemented!(), + Tuple(_node_id) => unimplemented!(), + Function(_def_id) => unimplemented!(), + Array(_, _) => unimplemented!(), + Repeat(_, _) => unimplemented!(), + Dummy => unimplemented!(), } - r } - fn run(&mut self) -> EvalResult<()> { - 'outer: while !self.stack.is_empty() { - let mut current_block = self.frame().next_block; - - loop { - trace!("// {:?}", current_block); - let current_mir = self.mir().clone(); // Cloning a reference. - let block_data = current_mir.basic_block_data(current_block); - - for stmt in &block_data.statements { - trace!("{:?}", stmt); - let mir::StatementKind::Assign(ref lvalue, ref rvalue) = stmt.kind; - let result = self.eval_assignment(lvalue, rvalue); - self.maybe_report(stmt.span, result)?; - } + fn type_needs_drop(&self, ty: Ty<'tcx>) -> bool { + self.tcx.type_needs_drop_given_env(ty, &self.tcx.empty_parameter_environment()) + } - let terminator = block_data.terminator(); - trace!("{:?}", terminator.kind); + fn type_is_sized(&self, ty: Ty<'tcx>) -> bool { + ty.is_sized(self.tcx, &self.tcx.empty_parameter_environment(), DUMMY_SP) + } - let result = self.eval_terminator(terminator); - match self.maybe_report(terminator.span, result)? { - TerminatorTarget::Block(block) => current_block = block, - TerminatorTarget::Return => { - self.pop_stack_frame(); - self.name_stack.pop(); - continue 'outer; - } - TerminatorTarget::Call => continue 'outer, + fn fulfill_obligation(&self, trait_ref: ty::PolyTraitRef<'tcx>) -> traits::Vtable<'tcx, ()> { + // Do the initial selection for the obligation. This yields the shallow result we are + // looking for -- that is, what specific impl. + self.tcx.normalizing_infer_ctxt(ProjectionMode::Any).enter(|infcx| { + let mut selcx = traits::SelectionContext::new(&infcx); + + let obligation = traits::Obligation::new( + traits::ObligationCause::misc(DUMMY_SP, ast::DUMMY_NODE_ID), + trait_ref.to_poly_trait_predicate(), + ); + let selection = selcx.select(&obligation).unwrap().unwrap(); + + // Currently, we use a fulfillment context to completely resolve all nested obligations. + // This is because they can inform the inference of the impl's type parameters. + let mut fulfill_cx = traits::FulfillmentContext::new(); + let vtable = selection.map(|predicate| { + fulfill_cx.register_predicate_obligation(&infcx, predicate); + }); + infcx.drain_fulfillment_cx_or_panic(DUMMY_SP, &mut fulfill_cx, &vtable) + }) + } + + /// Trait method, which has to be resolved to an impl method. + pub fn trait_method( + &self, + def_id: DefId, + substs: &'tcx Substs<'tcx> + ) -> (DefId, &'tcx Substs<'tcx>) { + let method_item = self.tcx.impl_or_trait_item(def_id); + let trait_id = method_item.container().id(); + let trait_ref = ty::Binder(substs.to_trait_ref(self.tcx, trait_id)); + match self.fulfill_obligation(trait_ref) { + traits::VtableImpl(vtable_impl) => { + let impl_did = vtable_impl.impl_def_id; + let mname = self.tcx.item_name(def_id); + // Create a concatenated set of substitutions which includes those from the impl + // and those from the method: + let impl_substs = vtable_impl.substs.with_method_from(substs); + let substs = self.tcx.mk_substs(impl_substs); + let mth = get_impl_method(self.tcx, impl_did, substs, mname); + + (mth.method.def_id, mth.substs) + } + + traits::VtableClosure(vtable_closure) => + (vtable_closure.closure_def_id, vtable_closure.substs.func_substs), + + traits::VtableFnPointer(_fn_ty) => { + let _trait_closure_kind = self.tcx.lang_items.fn_trait_kind(trait_id).unwrap(); + unimplemented!() + // let llfn = trans_fn_pointer_shim(ccx, trait_closure_kind, fn_ty); + + // let method_ty = def_ty(tcx, def_id, substs); + // let fn_ptr_ty = match method_ty.sty { + // ty::TyFnDef(_, _, fty) => tcx.mk_ty(ty::TyFnPtr(fty)), + // _ => unreachable!("expected fn item type, found {}", + // method_ty) + // }; + // Callee::ptr(immediate_rvalue(llfn, fn_ptr_ty)) + } + + traits::VtableObject(ref _data) => { + unimplemented!() + // Callee { + // data: Virtual(traits::get_vtable_index_of_object_method( + // tcx, data, def_id)), + // ty: def_ty(tcx, def_id, substs) + // } + } + vtable => unreachable!("resolved vtable bad vtable {:?} in trans", vtable), + } + } + + fn load_mir(&self, def_id: DefId) -> CachedMir<'a, 'tcx> { + match self.tcx.map.as_local_node_id(def_id) { + Some(node_id) => CachedMir::Ref(self.mir_map.map.get(&node_id).unwrap()), + None => { + let mut mir_cache = self.mir_cache.borrow_mut(); + if let Some(mir) = mir_cache.get(&def_id) { + return CachedMir::Owned(mir.clone()); } + + let cs = &self.tcx.sess.cstore; + let mir = cs.maybe_get_item_mir(self.tcx, def_id).unwrap_or_else(|| { + panic!("no mir for {:?}", def_id); + }); + let cached = Rc::new(mir); + mir_cache.insert(def_id, cached.clone()); + CachedMir::Owned(cached) } } + } - Ok(()) + fn monomorphize(&self, ty: Ty<'tcx>, substs: &'tcx Substs<'tcx>) -> Ty<'tcx> { + let substituted = ty.subst(self.tcx, substs); + self.tcx.normalize_associated_type(&substituted) + } + + fn type_size(&self, ty: Ty<'tcx>, substs: &'tcx Substs<'tcx>) -> usize { + self.type_layout(ty, substs).size(&self.tcx.data_layout).bytes() as usize } - fn call_nested(&mut self, mir: &mir::Mir<'tcx>) -> EvalResult> { - let mut nested_fecx = FnEvalContext::new(self.gecx); + fn type_layout(&self, ty: Ty<'tcx>, substs: &'tcx Substs<'tcx>) -> &'tcx Layout { + // TODO(solson): Is this inefficient? Needs investigation. + let ty = self.monomorphize(ty, substs); - let return_ptr = match mir.return_ty { - ty::FnConverging(ty) => { - let size = nested_fecx.type_size(ty); - Some(nested_fecx.memory.allocate(size)) - } - ty::FnDiverging => None, + self.tcx.normalizing_infer_ctxt(ProjectionMode::Any).enter(|infcx| { + // TODO(solson): Report this error properly. + ty.layout(&infcx).unwrap() + }) + } +} + +impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> { + fn new(gecx: &'a mut GlobalEvalContext<'b, 'tcx>) -> Self { + FnEvalContext { + gecx: gecx, + stack: Vec::new(), + } + } + + #[inline(never)] + #[cold] + fn report(&self, e: &EvalError) { + let stmt = self.frame().stmt; + let block = self.basic_block(); + let span = if stmt < block.statements.len() { + block.statements[stmt].span + } else { + block.terminator().span }; + let mut err = self.tcx.sess.struct_span_err(span, &e.to_string()); + for &Frame{ def_id, substs, span, .. } in self.stack.iter().rev() { + // FIXME(solson): Find a way to do this without this Display impl hack. + use rustc::util::ppaux; + use std::fmt; + struct Instance<'tcx>(DefId, &'tcx Substs<'tcx>); + impl<'tcx> fmt::Display for Instance<'tcx> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + ppaux::parameterized(f, self.1, self.0, ppaux::Ns::Value, &[], + |tcx| tcx.lookup_item_type(self.0).generics) + } + } + err.span_note(span, &format!("inside call to {}", Instance(def_id, substs))); + } + err.emit(); + } - let substs = nested_fecx.substs(); - nested_fecx.push_stack_frame(CachedMir::Ref(mir), substs, return_ptr); - nested_fecx.run()?; - Ok(return_ptr) + fn maybe_report(&self, r: EvalResult) -> EvalResult { + if let Err(ref e) = r { + self.report(e); + } + r + } + + fn run(&mut self) -> EvalResult<()> { + let mut stepper = stepper::Stepper::new(self); + while stepper.step()? {} + Ok(()) } - fn push_stack_frame(&mut self, mir: CachedMir<'mir, 'tcx>, substs: &'tcx Substs<'tcx>, + fn push_stack_frame(&mut self, def_id: DefId, span: codemap::Span, mir: CachedMir<'mir, 'tcx>, substs: &'tcx Substs<'tcx>, return_ptr: Option) { - self.substs_stack.push(substs); - let arg_tys = mir.arg_decls.iter().map(|a| a.ty); let var_tys = mir.var_decls.iter().map(|v| v.ty); let temp_tys = mir.temp_decls.iter().map(|t| t.ty); - let locals: Vec = arg_tys.chain(var_tys).chain(temp_tys).map(|ty| { - let size = self.type_size(ty); - self.memory.allocate(size) - }).collect(); - let num_args = mir.arg_decls.len(); let num_vars = mir.var_decls.len(); @@ -241,17 +415,27 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> { mir: mir.clone(), next_block: mir::START_BLOCK, return_ptr: return_ptr, - locals: locals, + locals: Vec::new(), var_offset: num_args, temp_offset: num_args + num_vars, + span: span, + def_id: def_id, + substs: substs, + stmt: 0, }); + + let locals: Vec = arg_tys.chain(var_tys).chain(temp_tys).map(|ty| { + let size = self.type_size(ty); + self.memory.allocate(size) + }).collect(); + + self.frame_mut().locals = locals; } fn pop_stack_frame(&mut self) { ::log_settings::settings().indentation -= 1; let _frame = self.stack.pop().expect("tried to pop a stack frame, but there were none"); // TODO(solson): Deallocate local variables. - self.substs_stack.pop(); } fn eval_terminator(&mut self, terminator: &mir::Terminator<'tcx>) @@ -260,12 +444,16 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> { let target = match terminator.kind { Return => TerminatorTarget::Return, - Goto { target } => TerminatorTarget::Block(target), + Goto { target } => { + self.frame_mut().next_block = target; + TerminatorTarget::Block + }, If { ref cond, targets: (then_target, else_target) } => { let cond_ptr = self.eval_operand(cond)?; let cond_val = self.memory.read_bool(cond_ptr)?; - TerminatorTarget::Block(if cond_val { then_target } else { else_target }) + self.frame_mut().next_block = if cond_val { then_target } else { else_target }; + TerminatorTarget::Block } SwitchInt { ref discr, ref values, ref targets, .. } => { @@ -288,7 +476,8 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> { } } - TerminatorTarget::Block(target_block) + self.frame_mut().next_block = target_block; + TerminatorTarget::Block } Switch { ref discr, ref targets, adt_def } => { @@ -299,7 +488,10 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> { .position(|v| discr_val == v.disr_val.to_u64_unchecked()); match matching { - Some(i) => TerminatorTarget::Block(targets[i]), + Some(i) => { + self.frame_mut().next_block = targets[i]; + TerminatorTarget::Block + }, None => return Err(EvalError::InvalidDiscriminant), } } @@ -378,8 +570,7 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> { } let mir = self.load_mir(resolved_def_id); - self.name_stack.push((def_id, substs, terminator.span)); - self.push_stack_frame(mir, resolved_substs, return_ptr); + self.push_stack_frame(def_id, terminator.span, mir, resolved_substs, return_ptr); for (i, (src, src_ty)) in arg_srcs.into_iter().enumerate() { let dest = self.frame().locals[i]; @@ -401,7 +592,8 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> { let ptr = self.eval_lvalue(value)?.to_ptr(); let ty = self.lvalue_ty(value); self.drop(ptr, ty)?; - TerminatorTarget::Block(target) + self.frame_mut().next_block = target; + TerminatorTarget::Block } Resume => unimplemented!(), @@ -992,18 +1184,30 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> { use rustc::mir::repr::Operand::*; match *op { Consume(ref lvalue) => Ok(self.eval_lvalue(lvalue)?.to_ptr()), - Constant(mir::Constant { ref literal, .. }) => { + Constant(mir::Constant { ref literal, ty, .. }) => { use rustc::mir::repr::Literal::*; match *literal { Value { ref value } => Ok(self.const_to_ptr(value)?), - Item { .. } => unimplemented!(), + Item { def_id, substs } => { + if let ty::TyFnDef(..) = ty.sty { + Err(EvalError::Unimplemented("unimplemented: mentions of function items".to_string())) + } else { + let cid = ConstantId { + def_id: def_id, + substs: substs, + kind: ConstantKind::Global, + }; + Ok(*self.statics.get(&cid).expect("static should have been cached (rvalue)")) + } + }, Promoted { index } => { - // TODO(solson): Mark constants and statics as read-only and cache their - // values. - let current_mir = self.mir(); - let mir = ¤t_mir.promoted[index]; - self.call_nested(mir).map(Option::unwrap) - } + let cid = ConstantId { + def_id: self.frame().def_id, + substs: self.substs(), + kind: ConstantKind::Promoted(index), + }; + Ok(*self.statics.get(&cid).expect("a promoted constant hasn't been precomputed")) + }, } } } @@ -1019,10 +1223,14 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> { Temp(i) => self.frame().locals[self.frame().temp_offset + i as usize], Static(def_id) => { - // TODO(solson): Mark constants and statics as read-only and cache their values. - let mir = self.load_mir(def_id); - self.call_nested(&mir)?.unwrap() - } + let substs = self.tcx.mk_substs(subst::Substs::empty()); + let cid = ConstantId { + def_id: def_id, + substs: substs, + kind: ConstantKind::Global, + }; + *self.gecx.statics.get(&cid).expect("static should have been cached (lvalue)") + }, Projection(ref proj) => { let base = self.eval_lvalue(&proj.base)?; @@ -1104,49 +1312,6 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> { Ok(Lvalue { ptr: ptr, extra: LvalueExtra::None }) } - // TODO(solson): Try making const_to_primval instead. - fn const_to_ptr(&mut self, const_val: &const_val::ConstVal) -> EvalResult { - use rustc::middle::const_val::ConstVal::*; - match *const_val { - Float(_f) => unimplemented!(), - Integral(int) => { - // TODO(solson): Check int constant type. - let ptr = self.memory.allocate(8); - self.memory.write_uint(ptr, int.to_u64_unchecked(), 8)?; - Ok(ptr) - } - Str(ref s) => { - let psize = self.memory.pointer_size; - let static_ptr = self.memory.allocate(s.len()); - let ptr = self.memory.allocate(psize * 2); - self.memory.write_bytes(static_ptr, s.as_bytes())?; - self.memory.write_ptr(ptr, static_ptr)?; - self.memory.write_usize(ptr.offset(psize as isize), s.len() as u64)?; - Ok(ptr) - } - ByteStr(ref bs) => { - let psize = self.memory.pointer_size; - let static_ptr = self.memory.allocate(bs.len()); - let ptr = self.memory.allocate(psize); - self.memory.write_bytes(static_ptr, bs)?; - self.memory.write_ptr(ptr, static_ptr)?; - Ok(ptr) - } - Bool(b) => { - let ptr = self.memory.allocate(1); - self.memory.write_bool(ptr, b)?; - Ok(ptr) - } - Char(_c) => unimplemented!(), - Struct(_node_id) => unimplemented!(), - Tuple(_node_id) => unimplemented!(), - Function(_def_id) => unimplemented!(), - Array(_, _) => unimplemented!(), - Repeat(_, _) => unimplemented!(), - Dummy => unimplemented!(), - } - } - fn lvalue_ty(&self, lvalue: &mir::Lvalue<'tcx>) -> Ty<'tcx> { self.monomorphize(self.mir().lvalue_ty(self.tcx, lvalue).to_ty(self.tcx)) } @@ -1156,12 +1321,7 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> { } fn monomorphize(&self, ty: Ty<'tcx>) -> Ty<'tcx> { - let substituted = ty.subst(self.tcx, self.substs()); - self.tcx.normalize_associated_type(&substituted) - } - - fn type_needs_drop(&self, ty: Ty<'tcx>) -> bool { - self.tcx.type_needs_drop_given_env(ty, &self.tcx.empty_parameter_environment()) + self.gecx.monomorphize(ty, self.substs()) } fn move_(&mut self, src: Pointer, dest: Pointer, ty: Ty<'tcx>) -> EvalResult<()> { @@ -1173,22 +1333,12 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> { Ok(()) } - fn type_is_sized(&self, ty: Ty<'tcx>) -> bool { - ty.is_sized(self.tcx, &self.tcx.empty_parameter_environment(), DUMMY_SP) - } - fn type_size(&self, ty: Ty<'tcx>) -> usize { - self.type_layout(ty).size(&self.tcx.data_layout).bytes() as usize + self.gecx.type_size(ty, self.substs()) } fn type_layout(&self, ty: Ty<'tcx>) -> &'tcx Layout { - // TODO(solson): Is this inefficient? Needs investigation. - let ty = self.monomorphize(ty); - - self.tcx.normalizing_infer_ctxt(ProjectionMode::Any).enter(|infcx| { - // TODO(solson): Report this error properly. - ty.layout(&infcx).unwrap() - }) + self.gecx.type_layout(ty, self.substs()) } pub fn read_primval(&mut self, ptr: Pointer, ty: Ty<'tcx>) -> EvalResult { @@ -1234,6 +1384,11 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> { self.stack.last().expect("no call frames exist") } + fn basic_block(&self) -> &mir::BasicBlockData<'tcx> { + let frame = self.frame(); + frame.mir.basic_block_data(frame.next_block) + } + fn frame_mut(&mut self) -> &mut Frame<'mir, 'tcx> { self.stack.last_mut().expect("no call frames exist") } @@ -1243,100 +1398,7 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> { } fn substs(&self) -> &'tcx Substs<'tcx> { - self.substs_stack.last().cloned().unwrap_or_else(|| self.tcx.mk_substs(Substs::empty())) - } - - fn load_mir(&self, def_id: DefId) -> CachedMir<'mir, 'tcx> { - match self.tcx.map.as_local_node_id(def_id) { - Some(node_id) => CachedMir::Ref(self.mir_map.map.get(&node_id).unwrap()), - None => { - let mut mir_cache = self.mir_cache.borrow_mut(); - if let Some(mir) = mir_cache.get(&def_id) { - return CachedMir::Owned(mir.clone()); - } - - let cs = &self.tcx.sess.cstore; - let mir = cs.maybe_get_item_mir(self.tcx, def_id).unwrap_or_else(|| { - panic!("no mir for {:?}", def_id); - }); - let cached = Rc::new(mir); - mir_cache.insert(def_id, cached.clone()); - CachedMir::Owned(cached) - } - } - } - - fn fulfill_obligation(&self, trait_ref: ty::PolyTraitRef<'tcx>) -> traits::Vtable<'tcx, ()> { - // Do the initial selection for the obligation. This yields the shallow result we are - // looking for -- that is, what specific impl. - self.tcx.normalizing_infer_ctxt(ProjectionMode::Any).enter(|infcx| { - let mut selcx = traits::SelectionContext::new(&infcx); - - let obligation = traits::Obligation::new( - traits::ObligationCause::misc(DUMMY_SP, ast::DUMMY_NODE_ID), - trait_ref.to_poly_trait_predicate(), - ); - let selection = selcx.select(&obligation).unwrap().unwrap(); - - // Currently, we use a fulfillment context to completely resolve all nested obligations. - // This is because they can inform the inference of the impl's type parameters. - let mut fulfill_cx = traits::FulfillmentContext::new(); - let vtable = selection.map(|predicate| { - fulfill_cx.register_predicate_obligation(&infcx, predicate); - }); - infcx.drain_fulfillment_cx_or_panic(DUMMY_SP, &mut fulfill_cx, &vtable) - }) - } - - /// Trait method, which has to be resolved to an impl method. - pub fn trait_method( - &self, - def_id: DefId, - substs: &'tcx Substs<'tcx> - ) -> (DefId, &'tcx Substs<'tcx>) { - let method_item = self.tcx.impl_or_trait_item(def_id); - let trait_id = method_item.container().id(); - let trait_ref = ty::Binder(substs.to_trait_ref(self.tcx, trait_id)); - match self.fulfill_obligation(trait_ref) { - traits::VtableImpl(vtable_impl) => { - let impl_did = vtable_impl.impl_def_id; - let mname = self.tcx.item_name(def_id); - // Create a concatenated set of substitutions which includes those from the impl - // and those from the method: - let impl_substs = vtable_impl.substs.with_method_from(substs); - let substs = self.tcx.mk_substs(impl_substs); - let mth = get_impl_method(self.tcx, impl_did, substs, mname); - - (mth.method.def_id, mth.substs) - } - - traits::VtableClosure(vtable_closure) => - (vtable_closure.closure_def_id, vtable_closure.substs.func_substs), - - traits::VtableFnPointer(_fn_ty) => { - let _trait_closure_kind = self.tcx.lang_items.fn_trait_kind(trait_id).unwrap(); - unimplemented!() - // let llfn = trans_fn_pointer_shim(ccx, trait_closure_kind, fn_ty); - - // let method_ty = def_ty(tcx, def_id, substs); - // let fn_ptr_ty = match method_ty.sty { - // ty::TyFnDef(_, _, fty) => tcx.mk_ty(ty::TyFnPtr(fty)), - // _ => unreachable!("expected fn item type, found {}", - // method_ty) - // }; - // Callee::ptr(immediate_rvalue(llfn, fn_ptr_ty)) - } - - traits::VtableObject(ref _data) => { - unimplemented!() - // Callee { - // data: Virtual(traits::get_vtable_index_of_object_method( - // tcx, data, def_id)), - // ty: def_ty(tcx, def_id, substs) - // } - } - vtable => unreachable!("resolved vtable bad vtable {:?} in trans", vtable), - } + self.frame().substs } } @@ -1426,10 +1488,9 @@ pub fn interpret_start_points<'a, 'tcx>( debug!("Interpreting: {}", item.name); let mut gecx = GlobalEvalContext::new(tcx, mir_map); - let mut fecx = FnEvalContext::new(&mut gecx); - match fecx.call_nested(mir) { + match gecx.call(mir, tcx.map.local_def_id(id)) { Ok(Some(return_ptr)) => if log_enabled!(::log::LogLevel::Debug) { - fecx.memory.dump(return_ptr.alloc_id); + gecx.memory.dump(return_ptr.alloc_id); }, Ok(None) => warn!("diverging function returned"), Err(_e) => { diff --git a/src/interpreter/stepper.rs b/src/interpreter/stepper.rs new file mode 100644 index 0000000000..a113b8f98e --- /dev/null +++ b/src/interpreter/stepper.rs @@ -0,0 +1,189 @@ +use super::{ + FnEvalContext, + CachedMir, + TerminatorTarget, + ConstantId, + GlobalEvalContext, + ConstantKind, +}; +use error::EvalResult; +use rustc::mir::repr as mir; +use rustc::ty::{subst, self}; +use rustc::hir::def_id::DefId; +use rustc::mir::visit::{Visitor, LvalueContext}; +use syntax::codemap::Span; +use std::rc::Rc; +use memory::Pointer; + +pub struct Stepper<'fncx, 'a: 'fncx, 'b: 'a + 'mir, 'mir: 'fncx, 'tcx: 'b>{ + fncx: &'fncx mut FnEvalContext<'a, 'b, 'mir, 'tcx>, + + // a cache of the constants to be computed before the next statement/terminator + // this is an optimization, so we don't have to allocate a new vector for every statement + constants: Vec<(ConstantId<'tcx>, Span, Pointer, CachedMir<'mir, 'tcx>)>, +} + +impl<'fncx, 'a, 'b: 'a + 'mir, 'mir, 'tcx: 'b> Stepper<'fncx, 'a, 'b, 'mir, 'tcx> { + pub(super) fn new(fncx: &'fncx mut FnEvalContext<'a, 'b, 'mir, 'tcx>) -> Self { + Stepper { + fncx: fncx, + constants: Vec::new(), + } + } + + fn statement(&mut self, stmt: &mir::Statement<'tcx>) -> EvalResult<()> { + trace!("{:?}", stmt); + let mir::StatementKind::Assign(ref lvalue, ref rvalue) = stmt.kind; + let result = self.fncx.eval_assignment(lvalue, rvalue); + self.fncx.maybe_report(result)?; + self.fncx.frame_mut().stmt += 1; + Ok(()) + } + + fn terminator(&mut self, terminator: &mir::Terminator<'tcx>) -> EvalResult<()> { + // after a terminator we go to a new block + self.fncx.frame_mut().stmt = 0; + let term = { + trace!("{:?}", terminator.kind); + let result = self.fncx.eval_terminator(terminator); + self.fncx.maybe_report(result)? + }; + match term { + TerminatorTarget::Return => { + self.fncx.pop_stack_frame(); + }, + TerminatorTarget::Block | + TerminatorTarget::Call => trace!("// {:?}", self.fncx.frame().next_block), + } + Ok(()) + } + + // returns true as long as there are more things to do + pub fn step(&mut self) -> EvalResult { + if self.fncx.stack.is_empty() { + return Ok(false); + } + + let block = self.fncx.frame().next_block; + let stmt = self.fncx.frame().stmt; + let mir = self.fncx.mir(); + let basic_block = mir.basic_block_data(block); + + if let Some(ref stmt) = basic_block.statements.get(stmt) { + assert!(self.constants.is_empty()); + ConstantExtractor { + span: stmt.span, + substs: self.fncx.substs(), + def_id: self.fncx.frame().def_id, + gecx: self.fncx.gecx, + constants: &mut self.constants, + mir: &mir, + }.visit_statement(block, stmt); + if self.constants.is_empty() { + self.statement(stmt)?; + } else { + self.extract_constants()?; + } + return Ok(true); + } + + let terminator = basic_block.terminator(); + assert!(self.constants.is_empty()); + ConstantExtractor { + span: terminator.span, + substs: self.fncx.substs(), + def_id: self.fncx.frame().def_id, + gecx: self.fncx.gecx, + constants: &mut self.constants, + mir: &mir, + }.visit_terminator(block, terminator); + if self.constants.is_empty() { + self.terminator(terminator)?; + } else { + self.extract_constants()?; + } + Ok(true) + } + + fn extract_constants(&mut self) -> EvalResult<()> { + assert!(!self.constants.is_empty()); + for (cid, span, return_ptr, mir) in self.constants.drain(..) { + trace!("queuing a constant"); + self.fncx.push_stack_frame(cid.def_id, span, mir, cid.substs, Some(return_ptr)); + } + // self.step() can't be "done", so it can't return false + assert!(self.step()?); + Ok(()) + } +} + +struct ConstantExtractor<'a, 'b: 'mir, 'mir: 'a, 'tcx: 'b> { + span: Span, + // FIXME: directly push the new stackframes instead of doing this intermediate caching + constants: &'a mut Vec<(ConstantId<'tcx>, Span, Pointer, CachedMir<'mir, 'tcx>)>, + gecx: &'a mut GlobalEvalContext<'b, 'tcx>, + mir: &'a mir::Mir<'tcx>, + def_id: DefId, + substs: &'tcx subst::Substs<'tcx>, +} + +impl<'a, 'b, 'mir, 'tcx> ConstantExtractor<'a, 'b, 'mir, 'tcx> { + fn global_item(&mut self, def_id: DefId, substs: &'tcx subst::Substs<'tcx>, span: Span) { + let cid = ConstantId { + def_id: def_id, + substs: substs, + kind: ConstantKind::Global, + }; + if self.gecx.statics.contains_key(&cid) { + return; + } + let mir = self.gecx.load_mir(def_id); + let ptr = self.gecx.alloc_ret_ptr(mir.return_ty, substs).expect("there's no such thing as an unreachable static"); + self.gecx.statics.insert(cid.clone(), ptr); + self.constants.push((cid, span, ptr, mir)); + } +} + +impl<'a, 'b, 'mir, 'tcx> Visitor<'tcx> for ConstantExtractor<'a, 'b, 'mir, 'tcx> { + fn visit_constant(&mut self, constant: &mir::Constant<'tcx>) { + self.super_constant(constant); + match constant.literal { + // already computed by rustc + mir::Literal::Value { .. } => {} + mir::Literal::Item { def_id, substs } => { + if let ty::TyFnDef(..) = constant.ty.sty { + // No need to do anything here, even if function pointers are implemented, + // because the type is the actual function, not the signature of the function. + // Thus we can simply create a zero sized allocation in `evaluate_operand` + } else { + self.global_item(def_id, substs, constant.span); + } + }, + mir::Literal::Promoted { index } => { + let cid = ConstantId { + def_id: self.def_id, + substs: self.substs, + kind: ConstantKind::Promoted(index), + }; + if self.gecx.statics.contains_key(&cid) { + return; + } + let mir = self.mir.promoted[index].clone(); + let return_ty = mir.return_ty; + let return_ptr = self.gecx.alloc_ret_ptr(return_ty, cid.substs).expect("there's no such thing as an unreachable static"); + let mir = CachedMir::Owned(Rc::new(mir)); + self.gecx.statics.insert(cid.clone(), return_ptr); + self.constants.push((cid, constant.span, return_ptr, mir)); + } + } + } + + fn visit_lvalue(&mut self, lvalue: &mir::Lvalue<'tcx>, context: LvalueContext) { + self.super_lvalue(lvalue, context); + if let mir::Lvalue::Static(def_id) = *lvalue { + let substs = self.gecx.tcx.mk_substs(subst::Substs::empty()); + let span = self.span; + self.global_item(def_id, substs, span); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 80d89c164a..4e06a3ce38 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ filling_drop, question_mark, rustc_private, + pub_restricted, )] // From rustc. diff --git a/src/memory.rs b/src/memory.rs index 728d5310f8..ab527a47c0 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -88,7 +88,7 @@ impl Memory { alloc.bytes.extend(iter::repeat(0).take(amount)); alloc.undef_mask.grow(amount, false); } else if size > new_size { - return Err(EvalError::Unimplemented(format!("unimplemented allocation relocation"))); + return Err(EvalError::Unimplemented(format!("unimplemented allocation relocation (from {} to {})", size, new_size))); // alloc.bytes.truncate(new_size); // alloc.undef_mask.len = new_size; // TODO: potentially remove relocations diff --git a/tests/compile-fail/unimplemented.rs b/tests/compile-fail/unimplemented.rs new file mode 100644 index 0000000000..754d3d9ee7 --- /dev/null +++ b/tests/compile-fail/unimplemented.rs @@ -0,0 +1,12 @@ +#![feature(custom_attribute)] +#![allow(dead_code, unused_attributes)] + +//error-pattern:unimplemented: mentions of function items + + +#[miri_run] +fn failed_assertions() { + assert_eq!(5, 6); +} + +fn main() {} diff --git a/tests/run-pass/bug.rs b/tests/run-pass/bug.rs new file mode 100644 index 0000000000..3006da2c16 --- /dev/null +++ b/tests/run-pass/bug.rs @@ -0,0 +1,14 @@ +#![feature(custom_attribute)] +#![allow(dead_code, unused_attributes)] + +static mut X: usize = 5; + +#[miri_run] +fn static_mut() { + unsafe { + X = 6; + assert_eq!(X, 6); + } +} + +fn main() {} diff --git a/tests/run-pass/calls.rs b/tests/run-pass/calls.rs index be975320bb..ba68fb44a6 100644 --- a/tests/run-pass/calls.rs +++ b/tests/run-pass/calls.rs @@ -1,4 +1,4 @@ -#![feature(custom_attribute)] +#![feature(custom_attribute, const_fn)] #![allow(dead_code, unused_attributes)] #[miri_run] @@ -33,6 +33,15 @@ fn cross_crate_fn_call() -> i64 { if 1i32.is_positive() { 1 } else { 0 } } +const fn foo(i: i64) -> i64 { *&i + 1 } + +#[miri_run] +fn const_fn_call() -> i64 { + let x = 5 + foo(5); + assert_eq!(x, 11); + x +} + #[miri_run] fn main() { assert_eq!(call(), 2); diff --git a/tests/run-pass/constants.rs b/tests/run-pass/constants.rs new file mode 100644 index 0000000000..3d6b08c340 --- /dev/null +++ b/tests/run-pass/constants.rs @@ -0,0 +1,11 @@ +#![feature(custom_attribute)] +#![allow(dead_code, unused_attributes)] + +const A: usize = *&5; + +#[miri_run] +fn foo() -> usize { + A +} + +fn main() {}