//! This module contains the `EvalContext` methods for executing a single step of the interpreter. //! //! The main entry point is the `step` method. use rustc::hir::def_id::DefId; use rustc::hir; use rustc::mir::visit::{Visitor, LvalueContext}; use rustc::mir; use rustc::traits::Reveal; use rustc::ty; use rustc::ty::layout::Layout; use rustc::ty::subst::Substs; use super::{EvalResult, EvalContext, StackPopCleanup, PtrAndAlign, GlobalId, Lvalue, MemoryKind, Machine, PrimVal}; use syntax::codemap::Span; use syntax::ast::Mutability; impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> { pub fn inc_step_counter_and_check_limit(&mut self, n: u64) -> EvalResult<'tcx> { self.steps_remaining = self.steps_remaining.saturating_sub(n); if self.steps_remaining > 0 { Ok(()) } else { err!(ExecutionTimeLimitReached) } } /// Returns true as long as there are more things to do. pub fn step(&mut self) -> EvalResult<'tcx, bool> { self.inc_step_counter_and_check_limit(1)?; if self.stack.is_empty() { return Ok(false); } let block = self.frame().block; let stmt_id = self.frame().stmt; let mir = self.mir(); let basic_block = &mir.basic_blocks()[block]; if let Some(stmt) = basic_block.statements.get(stmt_id) { let mut new = Ok(0); ConstantExtractor { span: stmt.source_info.span, instance: self.frame().instance, ecx: self, mir, new_constants: &mut new, }.visit_statement( block, stmt, mir::Location { block, statement_index: stmt_id, }, ); // if ConstantExtractor added new frames, we don't execute anything here // but await the next call to step if new? == 0 { self.statement(stmt)?; } return Ok(true); } let terminator = basic_block.terminator(); let mut new = Ok(0); ConstantExtractor { span: terminator.source_info.span, instance: self.frame().instance, ecx: self, mir, new_constants: &mut new, }.visit_terminator( block, terminator, mir::Location { block, statement_index: stmt_id, }, ); // if ConstantExtractor added new frames, we don't execute anything here // but await the next call to step if new? == 0 { self.terminator(terminator)?; } Ok(true) } fn statement(&mut self, stmt: &mir::Statement<'tcx>) -> EvalResult<'tcx> { trace!("{:?}", stmt); use rustc::mir::StatementKind::*; match stmt.kind { Assign(ref lvalue, ref rvalue) => self.eval_rvalue_into_lvalue(rvalue, lvalue)?, SetDiscriminant { ref lvalue, variant_index, } => { let dest = self.eval_lvalue(lvalue)?; let dest_ty = self.lvalue_ty(lvalue); let dest_layout = self.type_layout(dest_ty)?; match *dest_layout { Layout::General { discr, .. } => { let discr_size = discr.size().bytes(); let dest_ptr = self.force_allocation(dest)?.to_ptr()?; self.memory.write_primval( dest_ptr, PrimVal::Bytes(variant_index as u128), discr_size, false )? } Layout::RawNullablePointer { nndiscr, .. } => { if variant_index as u64 != nndiscr { self.write_null(dest, dest_ty)?; } } Layout::StructWrappedNullablePointer { nndiscr, ref discrfield_source, .. } => { if variant_index as u64 != nndiscr { self.write_struct_wrapped_null_pointer( dest_ty, nndiscr, discrfield_source, dest, )?; } } _ => { bug!( "SetDiscriminant on {} represented as {:#?}", dest_ty, dest_layout ) } } } // Mark locals as dead or alive. StorageLive(ref lvalue) | StorageDead(ref lvalue) => { let (frame, local) = match self.eval_lvalue(lvalue)? { Lvalue::Local { frame, local } if self.cur_frame() == frame => ( frame, local, ), _ => return err!(Unimplemented("Storage annotations must refer to locals of the topmost stack frame.".to_owned())), // FIXME maybe this should get its own error type }; let old_val = match stmt.kind { StorageLive(_) => self.stack[frame].storage_live(local)?, StorageDead(_) => self.stack[frame].storage_dead(local)?, _ => bug!("We already checked that we are a storage stmt"), }; self.deallocate_local(old_val)?; } // Validity checks. Validate(op, ref lvalues) => { for operand in lvalues { self.validation_op(op, operand)?; } } EndRegion(ce) => { self.end_region(ce)?; } // Defined to do nothing. These are added by optimization passes, to avoid changing the // size of MIR constantly. Nop => {} InlineAsm { .. } => return err!(InlineAsm), } self.frame_mut().stmt += 1; Ok(()) } fn terminator(&mut self, terminator: &mir::Terminator<'tcx>) -> EvalResult<'tcx> { trace!("{:?}", terminator.kind); self.eval_terminator(terminator)?; if !self.stack.is_empty() { trace!("// {:?}", self.frame().block); } Ok(()) } /// returns `true` if a stackframe was pushed fn global_item( &mut self, def_id: DefId, substs: &'tcx Substs<'tcx>, span: Span, mutability: Mutability, ) -> EvalResult<'tcx, bool> { let instance = self.resolve_associated_const(def_id, substs); let cid = GlobalId { instance, promoted: None, }; if self.globals.contains_key(&cid) { return Ok(false); } if self.tcx.has_attr(def_id, "linkage") { // FIXME: check that it's `#[linkage = "extern_weak"]` trace!("Initializing an extern global with NULL"); let ptr_size = self.memory.pointer_size(); let ptr = self.memory.allocate( ptr_size, ptr_size, MemoryKind::UninitializedStatic, )?; self.memory.write_ptr_sized_unsigned(ptr, PrimVal::Bytes(0))?; self.memory.mark_static_initalized(ptr.alloc_id, mutability)?; self.globals.insert( cid, PtrAndAlign { ptr: ptr.into(), aligned: true, }, ); return Ok(false); } let mir = self.load_mir(instance.def)?; let size = self.type_size_with_substs(mir.return_ty, substs)?.expect( "unsized global", ); let align = self.type_align_with_substs(mir.return_ty, substs)?; let ptr = self.memory.allocate( size, align, MemoryKind::UninitializedStatic, )?; let aligned = !self.is_packed(mir.return_ty)?; self.globals.insert( cid, PtrAndAlign { ptr: ptr.into(), aligned, }, ); let internally_mutable = !mir.return_ty.is_freeze( self.tcx, ty::ParamEnv::empty(Reveal::All), span, ); let mutability = if mutability == Mutability::Mutable || internally_mutable { Mutability::Mutable } else { Mutability::Immutable }; let cleanup = StackPopCleanup::MarkStatic(mutability); let name = ty::tls::with(|tcx| tcx.item_path_str(def_id)); trace!("pushing stack frame for global: {}", name); self.push_stack_frame( instance, span, mir, Lvalue::from_ptr(ptr), cleanup, )?; Ok(true) } } // WARNING: This code pushes new stack frames. Make sure that any methods implemented on this // type don't ever access ecx.stack[ecx.cur_frame()], as that will change. This includes, e.g., // using the current stack frame's substitution. // Basically don't call anything other than `load_mir`, `alloc_ptr`, `push_stack_frame`. struct ConstantExtractor<'a, 'b: 'a, 'tcx: 'b, M: Machine<'tcx> + 'a> { span: Span, ecx: &'a mut EvalContext<'b, 'tcx, M>, mir: &'tcx mir::Mir<'tcx>, instance: ty::Instance<'tcx>, new_constants: &'a mut EvalResult<'tcx, u64>, } impl<'a, 'b, 'tcx, M: Machine<'tcx>> ConstantExtractor<'a, 'b, 'tcx, M> { fn try EvalResult<'tcx, bool>>(&mut self, f: F) { // previous constant errored let n = match *self.new_constants { Ok(n) => n, Err(_) => return, }; match f(self) { // everything ok + a new stackframe Ok(true) => *self.new_constants = Ok(n + 1), // constant correctly evaluated, but no new stackframe Ok(false) => {} // constant eval errored Err(err) => *self.new_constants = Err(err), } } } impl<'a, 'b, 'tcx, M: Machine<'tcx>> Visitor<'tcx> for ConstantExtractor<'a, 'b, 'tcx, M> { fn visit_constant(&mut self, constant: &mir::Constant<'tcx>, location: mir::Location) { self.super_constant(constant, location); match constant.literal { // already computed by rustc mir::Literal::Value { .. } => {} mir::Literal::Item { def_id, substs } => { self.try(|this| { this.ecx.global_item( def_id, substs, constant.span, Mutability::Immutable, ) }); } mir::Literal::Promoted { index } => { let cid = GlobalId { instance: self.instance, promoted: Some(index), }; if self.ecx.globals.contains_key(&cid) { return; } let mir = &self.mir.promoted[index]; self.try(|this| { let size = this.ecx .type_size_with_substs(mir.return_ty, this.instance.substs)? .expect("unsized global"); let align = this.ecx.type_align_with_substs( mir.return_ty, this.instance.substs, )?; let ptr = this.ecx.memory.allocate( size, align, MemoryKind::UninitializedStatic, )?; let aligned = !this.ecx.is_packed(mir.return_ty)?; this.ecx.globals.insert( cid, PtrAndAlign { ptr: ptr.into(), aligned, }, ); trace!("pushing stack frame for {:?}", index); this.ecx.push_stack_frame( this.instance, constant.span, mir, Lvalue::from_ptr(ptr), StackPopCleanup::MarkStatic(Mutability::Immutable), )?; Ok(true) }); } } } fn visit_lvalue( &mut self, lvalue: &mir::Lvalue<'tcx>, context: LvalueContext<'tcx>, location: mir::Location, ) { self.super_lvalue(lvalue, context, location); if let mir::Lvalue::Static(ref static_) = *lvalue { let def_id = static_.def_id; let substs = self.ecx.tcx.intern_substs(&[]); let span = self.span; if let Some(node_item) = self.ecx.tcx.hir.get_if_local(def_id) { if let hir::map::Node::NodeItem(&hir::Item { ref node, .. }) = node_item { if let hir::ItemStatic(_, m, _) = *node { self.try(|this| { this.ecx.global_item( def_id, substs, span, if m == hir::MutMutable { Mutability::Mutable } else { Mutability::Immutable }, ) }); return; } else { bug!("static def id doesn't point to static"); } } else { bug!("static def id doesn't point to item"); } } else { let def = self.ecx.tcx.describe_def(def_id).expect("static not found"); if let hir::def::Def::Static(_, mutable) = def { self.try(|this| { this.ecx.global_item( def_id, substs, span, if mutable { Mutability::Mutable } else { Mutability::Immutable }, ) }); } else { bug!("static found but isn't a static: {:?}", def); } } } } }