diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 5592783d863..ec830fd008f 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -20,6 +20,8 @@ use error::{EvalError, EvalResult}; use memory::{Memory, Pointer}; use primval::{self, PrimVal}; +use std::collections::HashMap; + mod stepper; struct GlobalEvalContext<'a, 'tcx: 'a> { @@ -45,6 +47,9 @@ struct GlobalEvalContext<'a, 'tcx: 'a> { /// * 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 and constants + statics: DefIdMap, } struct FnEvalContext<'a, 'b: 'a + 'mir, 'mir, 'tcx: 'b> { @@ -88,6 +93,9 @@ struct Frame<'a, 'tcx: 'a> { /// The offset of the first temporary in `self.locals`. temp_offset: usize, + + /// List of precomputed promoted constants + promoted: HashMap, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -122,6 +130,13 @@ enum TerminatorTarget { Return, } +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +enum ConstantId { + Promoted { index: usize }, + Static { def_id: DefId }, +} + + impl<'a, 'tcx> GlobalEvalContext<'a, 'tcx> { fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>, mir_map: &'a MirMap<'tcx>) -> Self { GlobalEvalContext { @@ -135,10 +150,11 @@ impl<'a, 'tcx> GlobalEvalContext<'a, 'tcx> { .expect("Session::target::uint_type was usize")/8), substs_stack: Vec::new(), name_stack: Vec::new(), + statics: DefIdMap(), } } - fn call(&mut self, mir: &mir::Mir<'tcx>) -> EvalResult> { + fn call(&mut self, mir: &mir::Mir<'tcx>, def_id: DefId) -> EvalResult> { let mut nested_fecx = FnEvalContext::new(self); let return_ptr = match mir.return_ty { @@ -150,6 +166,7 @@ impl<'a, 'tcx> GlobalEvalContext<'a, 'tcx> { }; let substs = nested_fecx.substs(); + nested_fecx.name_stack.push((def_id, substs, mir.span)); nested_fecx.push_stack_frame(CachedMir::Ref(mir), substs, return_ptr); nested_fecx.run()?; Ok(return_ptr) @@ -193,9 +210,9 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> { loop { match stepper.step()? { - Assignment(statement) => trace!("{:?}", statement), - Terminator(terminator) => { - trace!("{:?}", terminator.kind); + Assignment => trace!("{:?}", stepper.stmt()), + Terminator => { + trace!("{:?}", stepper.term().kind); continue 'outer; }, Done => return Ok(()), @@ -230,6 +247,7 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> { locals: locals, var_offset: num_args, temp_offset: num_args + num_vars, + promoted: HashMap::new(), }); } @@ -983,13 +1001,7 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> { match *literal { Value { ref value } => Ok(self.const_to_ptr(value)?), Item { .. } => Err(EvalError::Unimplemented(format!("literal items (e.g. mentions of function items) are unimplemented"))), - 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.gecx.call(mir).map(Option::unwrap) - } + Promoted { index } => Ok(*self.frame().promoted.get(&index).expect("a promoted constant hasn't been precomputed")), } } } @@ -1004,11 +1016,7 @@ impl<'a, 'b, 'mir, 'tcx> FnEvalContext<'a, 'b, 'mir, 'tcx> { Var(i) => self.frame().locals[self.frame().var_offset + i as usize], 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.gecx.call(&mir)?.unwrap() - } + Static(def_id) => *self.gecx.statics.get(&def_id).expect("static should have been cached"), Projection(ref proj) => { let base = self.eval_lvalue(&proj.base)?; @@ -1412,7 +1420,7 @@ pub fn interpret_start_points<'a, 'tcx>( debug!("Interpreting: {}", item.name); let mut gecx = GlobalEvalContext::new(tcx, mir_map); - match gecx.call(mir) { + match gecx.call(mir, tcx.map.local_def_id(id)) { Ok(Some(return_ptr)) => if log_enabled!(::log::LogLevel::Debug) { gecx.memory.dump(return_ptr.alloc_id); }, diff --git a/src/interpreter/stepper.rs b/src/interpreter/stepper.rs index 4c8d0c7292b..0543b40c8f5 100644 --- a/src/interpreter/stepper.rs +++ b/src/interpreter/stepper.rs @@ -2,49 +2,61 @@ use super::{ FnEvalContext, CachedMir, TerminatorTarget, + ConstantId, }; use error::EvalResult; use rustc::mir::repr as mir; +use rustc::ty::{self, subst}; +use rustc::mir::visit::Visitor; +use syntax::codemap::Span; +use memory::Pointer; +use std::rc::Rc; -pub enum Event<'a, 'tcx: 'a> { - Assignment(&'a mir::Statement<'tcx>), - Terminator(&'a mir::Terminator<'tcx>), +pub enum Event { + Assignment, + Terminator, Done, } pub struct Stepper<'fncx, 'a: 'fncx, 'b: 'a + 'mir, 'mir: 'fncx, 'tcx: 'b>{ fncx: &'fncx mut FnEvalContext<'a, 'b, 'mir, 'tcx>, block: mir::BasicBlock, - stmt: usize, + // a stack of statement positions + stmt: Vec, mir: CachedMir<'mir, 'tcx>, process: fn (&mut Stepper<'fncx, 'a, 'b, 'mir, 'tcx>) -> EvalResult<()>, + // a stack of constants + constants: Vec>, } 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 { + let mut stepper = Stepper { block: fncx.frame().next_block, mir: fncx.mir(), fncx: fncx, - stmt: 0, + stmt: vec![0], process: Self::dummy, - } + constants: Vec::new(), + }; + stepper.extract_constants(); + stepper } fn dummy(&mut self) -> EvalResult<()> { Ok(()) } fn statement(&mut self) -> EvalResult<()> { let block_data = self.mir.basic_block_data(self.block); - let stmt = &block_data.statements[self.stmt]; + let stmt = &block_data.statements[*self.stmt.last().unwrap()]; let mir::StatementKind::Assign(ref lvalue, ref rvalue) = stmt.kind; let result = self.fncx.eval_assignment(lvalue, rvalue); self.fncx.maybe_report(stmt.span, result)?; - self.stmt += 1; + *self.stmt.last_mut().unwrap() += 1; Ok(()) } fn terminator(&mut self) -> EvalResult<()> { - self.stmt = 0; + *self.stmt.last_mut().unwrap() = 0; let term = { let block_data = self.mir.basic_block_data(self.block); let terminator = block_data.terminator(); @@ -58,6 +70,9 @@ impl<'fncx, 'a, 'b: 'a + 'mir, 'mir, 'tcx: 'b> Stepper<'fncx, 'a, 'b, 'mir, 'tcx TerminatorTarget::Return => { self.fncx.pop_stack_frame(); self.fncx.name_stack.pop(); + self.stmt.pop(); + assert!(self.constants.last().unwrap().is_empty()); + self.constants.pop(); if !self.fncx.stack.is_empty() { self.block = self.fncx.frame().next_block; self.mir = self.fncx.mir(); @@ -66,12 +81,24 @@ impl<'fncx, 'a, 'b: 'a + 'mir, 'mir, 'tcx: 'b> Stepper<'fncx, 'a, 'b, 'mir, 'tcx TerminatorTarget::Call => { self.block = self.fncx.frame().next_block; self.mir = self.fncx.mir(); + self.stmt.push(0); + self.extract_constants(); }, } Ok(()) } - pub fn step<'step>(&'step mut self) -> EvalResult> { + fn alloc(&mut self, ty: ty::FnOutput<'tcx>) -> Pointer { + match ty { + ty::FnConverging(ty) => { + let size = self.fncx.type_size(ty); + self.fncx.memory.allocate(size) + } + ty::FnDiverging => panic!("there's no such thing as an unreachable static"), + } + } + + pub fn step(&mut self) -> EvalResult { (self.process)(self)?; if self.fncx.stack.is_empty() { @@ -80,18 +107,97 @@ impl<'fncx, 'a, 'b: 'a + 'mir, 'mir, 'tcx: 'b> Stepper<'fncx, 'a, 'b, 'mir, 'tcx return Ok(Event::Done); } + match self.constants.last_mut().unwrap().pop() { + Some((ConstantId::Promoted { index }, span)) => { + trace!("adding promoted constant {}", index); + let mir = self.mir.promoted[index].clone(); + let return_ptr = self.alloc(mir.return_ty); + self.fncx.frame_mut().promoted.insert(index, return_ptr); + let substs = self.fncx.substs(); + // FIXME: somehow encode that this is a promoted constant's frame + println!("{}, {}, {}", self.fncx.stack.len(), self.fncx.name_stack.len(), self.fncx.substs_stack.len()); + let def_id = self.fncx.name_stack.last().unwrap().0; + self.fncx.name_stack.push((def_id, substs, span)); + self.fncx.push_stack_frame(CachedMir::Owned(Rc::new(mir)), substs, Some(return_ptr)); + self.stmt.push(0); + self.constants.push(Vec::new()); + self.block = self.fncx.frame().next_block; + self.mir = self.fncx.mir(); + }, + Some((ConstantId::Static { def_id }, span)) => { + trace!("adding static {:?}", def_id); + let mir = self.fncx.load_mir(def_id); + let return_ptr = self.alloc(mir.return_ty); + self.fncx.gecx.statics.insert(def_id, return_ptr); + let substs = self.fncx.tcx.mk_substs(subst::Substs::empty()); + self.fncx.name_stack.push((def_id, substs, span)); + self.fncx.push_stack_frame(mir, substs, Some(return_ptr)); + self.stmt.push(0); + self.constants.push(Vec::new()); + self.block = self.fncx.frame().next_block; + self.mir = self.fncx.mir(); + }, + None => {}, + } + let basic_block = self.mir.basic_block_data(self.block); - if let Some(stmt) = basic_block.statements.get(self.stmt) { + if basic_block.statements.len() > *self.stmt.last().unwrap() { self.process = Self::statement; - return Ok(Event::Assignment(&stmt)); + return Ok(Event::Assignment); } self.process = Self::terminator; - Ok(Event::Terminator(basic_block.terminator())) + Ok(Event::Terminator) } - + + /// returns the basic block index of the currently processed block pub fn block(&self) -> mir::BasicBlock { self.block } + + /// returns the statement that will be processed next + pub fn stmt(&self) -> &mir::Statement { + let block_data = self.mir.basic_block_data(self.block); + &block_data.statements[*self.stmt.last().unwrap()] + } + + /// returns the terminator of the current block + pub fn term(&self) -> &mir::Terminator { + let block_data = self.mir.basic_block_data(self.block); + block_data.terminator() + } + + fn extract_constants(&mut self) { + let mut extractor = ConstantExtractor { + constants: Vec::new(), + }; + extractor.visit_mir(&self.mir); + self.constants.push(extractor.constants); + } +} + +struct ConstantExtractor { + constants: Vec<(ConstantId, Span)>, +} + +impl<'tcx> Visitor<'tcx> for ConstantExtractor { + 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 { .. } => {}, // FIXME: unimplemented + mir::Literal::Promoted { index } => { + self.constants.push((ConstantId::Promoted { index: index }, constant.span)); + } + } + } + + fn visit_statement(&mut self, block: mir::BasicBlock, stmt: &mir::Statement<'tcx>) { + self.super_statement(block, stmt); + if let mir::StatementKind::Assign(mir::Lvalue::Static(def_id), _) = stmt.kind { + self.constants.push((ConstantId::Static { def_id: def_id }, stmt.span)); + } + } } diff --git a/tests/compile-fail/unimplemented.rs b/tests/compile-fail/unimplemented.rs index 3b99fda9477..7ff4b1c191c 100644 --- a/tests/compile-fail/unimplemented.rs +++ b/tests/compile-fail/unimplemented.rs @@ -1,16 +1,12 @@ #![feature(custom_attribute)] #![allow(dead_code, unused_attributes)] -//error-pattern:literal items (e.g. mentions of function items) are unimplemented +//error-pattern:static should have been cached -static mut X: usize = 5; #[miri_run] -fn static_mut() { - unsafe { - X = 6; - assert_eq!(X, 6); - } +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 00000000000..3006da2c163 --- /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() {}