use rustc::ty::{self, TyCtxt, Ty, Instance}; use rustc::ty::layout::{self, LayoutOf}; use rustc::ty::subst::Substs; use rustc::hir::def_id::DefId; use rustc::mir; use rustc::middle::const_val::ErrKind::{CheckMatchError, TypeckError}; use rustc::middle::const_val::{ConstEvalErr, ConstVal}; use rustc_const_eval::{lookup_const_by_id, ConstContext}; use rustc::mir::Field; use rustc_data_structures::indexed_vec::Idx; use syntax::ast::Mutability; use syntax::codemap::Span; use rustc::mir::interpret::{EvalResult, EvalError, EvalErrorKind, GlobalId, Value, Pointer, PrimVal}; use super::{Place, EvalContext, StackPopCleanup, ValTy}; use rustc_const_math::ConstInt; use std::fmt; use std::error::Error; pub fn mk_eval_cx<'a, 'tcx>( tcx: TyCtxt<'a, 'tcx, 'tcx>, instance: Instance<'tcx>, param_env: ty::ParamEnv<'tcx>, ) -> EvalResult<'tcx, EvalContext<'a, 'tcx, CompileTimeEvaluator>> { debug!("mk_eval_cx: {:?}, {:?}", instance, param_env); let limits = super::ResourceLimits::default(); let mut ecx = EvalContext::new(tcx, param_env, limits, CompileTimeEvaluator, ()); let mir = ecx.load_mir(instance.def)?; // insert a stack frame so any queries have the correct substs ecx.push_stack_frame( instance, mir.span, mir, Place::undef(), StackPopCleanup::None, )?; Ok(ecx) } pub fn eval_body<'a, 'tcx>( tcx: TyCtxt<'a, 'tcx, 'tcx>, instance: Instance<'tcx>, param_env: ty::ParamEnv<'tcx>, ) -> EvalResult<'tcx, (Pointer, Ty<'tcx>)> { debug!("eval_body: {:?}, {:?}", instance, param_env); let limits = super::ResourceLimits::default(); let mut ecx = EvalContext::new(tcx, param_env, limits, CompileTimeEvaluator, ()); let cid = GlobalId { instance, promoted: None, }; if ecx.tcx.has_attr(instance.def_id(), "linkage") { return Err(ConstEvalError::NotConst("extern global".to_string()).into()); } let instance_ty = instance.ty(tcx); if tcx.interpret_interner.borrow().get_cached(cid).is_none() { let mir = ecx.load_mir(instance.def)?; let layout = ecx.layout_of(instance_ty)?; assert!(!layout.is_unsized()); let ptr = ecx.memory.allocate( layout.size.bytes(), layout.align, None, )?; tcx.interpret_interner.borrow_mut().cache(cid, ptr.into()); let cleanup = StackPopCleanup::MarkStatic(Mutability::Immutable); let name = ty::tls::with(|tcx| tcx.item_path_str(instance.def_id())); trace!("const_eval: pushing stack frame for global: {}", name); ecx.push_stack_frame( instance, mir.span, mir, Place::from_ptr(ptr, layout.align), cleanup.clone(), )?; while ecx.step()? {} } let value = tcx.interpret_interner.borrow().get_cached(cid).expect("global not cached"); Ok((value, instance_ty)) } pub fn eval_body_as_integer<'a, 'tcx>( tcx: TyCtxt<'a, 'tcx, 'tcx>, param_env: ty::ParamEnv<'tcx>, instance: Instance<'tcx>, ) -> EvalResult<'tcx, ConstInt> { let ptr_ty = eval_body(tcx, instance, param_env); let (ptr, ty) = ptr_ty?; let ecx = mk_eval_cx(tcx, instance, param_env)?; let prim = match ecx.try_read_value(ptr, ecx.layout_of(ty)?.align, ty)? { Some(Value::ByVal(prim)) => prim.to_bytes()?, _ => return err!(TypeNotPrimitive(ty)), }; use syntax::ast::{IntTy, UintTy}; use rustc::ty::TypeVariants::*; use rustc_const_math::{ConstIsize, ConstUsize}; Ok(match ty.sty { TyInt(IntTy::I8) => ConstInt::I8(prim as i128 as i8), TyInt(IntTy::I16) => ConstInt::I16(prim as i128 as i16), TyInt(IntTy::I32) => ConstInt::I32(prim as i128 as i32), TyInt(IntTy::I64) => ConstInt::I64(prim as i128 as i64), TyInt(IntTy::I128) => ConstInt::I128(prim as i128), TyInt(IntTy::Is) => ConstInt::Isize( ConstIsize::new(prim as i128 as i64, tcx.sess.target.isize_ty) .expect("miri should already have errored"), ), TyUint(UintTy::U8) => ConstInt::U8(prim as u8), TyUint(UintTy::U16) => ConstInt::U16(prim as u16), TyUint(UintTy::U32) => ConstInt::U32(prim as u32), TyUint(UintTy::U64) => ConstInt::U64(prim as u64), TyUint(UintTy::U128) => ConstInt::U128(prim), TyUint(UintTy::Us) => ConstInt::Usize( ConstUsize::new(prim as u64, tcx.sess.target.usize_ty) .expect("miri should already have errored"), ), _ => { return Err( ConstEvalError::NeedsRfc( "evaluating anything other than isize/usize during typeck".to_string(), ).into(), ) } }) } pub struct CompileTimeEvaluator; impl<'tcx> Into> for ConstEvalError { fn into(self) -> EvalError<'tcx> { EvalErrorKind::MachineError(Box::new(self)).into() } } #[derive(Clone, Debug)] enum ConstEvalError { NeedsRfc(String), NotConst(String), } impl fmt::Display for ConstEvalError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::ConstEvalError::*; match *self { NeedsRfc(ref msg) => { write!( f, "\"{}\" needs an rfc before being allowed inside constants", msg ) } NotConst(ref msg) => write!(f, "Cannot evaluate within constants: \"{}\"", msg), } } } impl Error for ConstEvalError { fn description(&self) -> &str { use self::ConstEvalError::*; match *self { NeedsRfc(_) => "this feature needs an rfc before being allowed inside constants", NotConst(_) => "this feature is not compatible with constant evaluation", } } fn cause(&self) -> Option<&Error> { None } } impl<'tcx> super::Machine<'tcx> for CompileTimeEvaluator { type MemoryData = (); type MemoryKinds = !; fn eval_fn_call<'a>( ecx: &mut EvalContext<'a, 'tcx, Self>, instance: ty::Instance<'tcx>, destination: Option<(Place, mir::BasicBlock)>, _args: &[ValTy<'tcx>], span: Span, _sig: ty::FnSig<'tcx>, ) -> EvalResult<'tcx, bool> { debug!("eval_fn_call: {:?}", instance); if !ecx.tcx.is_const_fn(instance.def_id()) { return Err( ConstEvalError::NotConst(format!("calling non-const fn `{}`", instance)).into(), ); } let mir = match ecx.load_mir(instance.def) { Ok(mir) => mir, Err(EvalError { kind: EvalErrorKind::NoMirFor(path), .. }) => { // some simple things like `malloc` might get accepted in the future return Err( ConstEvalError::NeedsRfc(format!("calling extern function `{}`", path)) .into(), ); } Err(other) => return Err(other), }; let (return_place, return_to_block) = match destination { Some((place, block)) => (place, StackPopCleanup::Goto(block)), None => (Place::undef(), StackPopCleanup::None), }; ecx.push_stack_frame( instance, span, mir, return_place, return_to_block, )?; Ok(false) } fn call_intrinsic<'a>( ecx: &mut EvalContext<'a, 'tcx, Self>, instance: ty::Instance<'tcx>, _args: &[ValTy<'tcx>], dest: Place, dest_layout: layout::TyLayout<'tcx>, target: mir::BasicBlock, ) -> EvalResult<'tcx> { let substs = instance.substs; let intrinsic_name = &ecx.tcx.item_name(instance.def_id())[..]; match intrinsic_name { "min_align_of" => { let elem_ty = substs.type_at(0); let elem_align = ecx.layout_of(elem_ty)?.align.abi(); let align_val = PrimVal::from_u128(elem_align as u128); ecx.write_primval(dest, align_val, dest_layout.ty)?; } "size_of" => { let ty = substs.type_at(0); let size = ecx.layout_of(ty)?.size.bytes() as u128; ecx.write_primval(dest, PrimVal::from_u128(size), dest_layout.ty)?; } name => return Err(ConstEvalError::NeedsRfc(format!("calling intrinsic `{}`", name)).into()), } ecx.goto_block(target); // Since we pushed no stack frame, the main loop will act // as if the call just completed and it's returning to the // current frame. Ok(()) } fn try_ptr_op<'a>( _ecx: &EvalContext<'a, 'tcx, Self>, _bin_op: mir::BinOp, left: PrimVal, _left_ty: Ty<'tcx>, right: PrimVal, _right_ty: Ty<'tcx>, ) -> EvalResult<'tcx, Option<(PrimVal, bool)>> { if left.is_bytes() && right.is_bytes() { Ok(None) } else { Err( ConstEvalError::NeedsRfc("Pointer arithmetic or comparison".to_string()).into(), ) } } fn mark_static_initialized(m: !) -> EvalResult<'tcx> { m } fn box_alloc<'a>( _ecx: &mut EvalContext<'a, 'tcx, Self>, _ty: Ty<'tcx>, _dest: Place, ) -> EvalResult<'tcx> { Err( ConstEvalError::NeedsRfc("Heap allocations via `box` keyword".to_string()).into(), ) } fn global_item_with_linkage<'a>( _ecx: &mut EvalContext<'a, 'tcx, Self>, _instance: ty::Instance<'tcx>, _mutability: Mutability, ) -> EvalResult<'tcx> { Err( ConstEvalError::NotConst("statics with `linkage` attribute".to_string()).into(), ) } } pub fn const_eval_provider<'a, 'tcx>( tcx: TyCtxt<'a, 'tcx, 'tcx>, key: ty::ParamEnvAnd<'tcx, (DefId, &'tcx Substs<'tcx>)>, ) -> ::rustc::middle::const_val::EvalResult<'tcx> { trace!("const eval: {:?}", key); let (def_id, substs) = if let Some(resolved) = lookup_const_by_id(tcx, key) { resolved } else { return Err(ConstEvalErr { span: tcx.def_span(key.value.0), kind: TypeckError }); }; let tables = tcx.typeck_tables_of(def_id); let body = if let Some(id) = tcx.hir.as_local_node_id(def_id) { let body_id = tcx.hir.body_owned_by(id); // Do match-check before building MIR if tcx.check_match(def_id).is_err() { return Err(ConstEvalErr { span: tcx.def_span(key.value.0), kind: CheckMatchError, }); } tcx.mir_const_qualif(def_id); tcx.hir.body(body_id) } else { tcx.extern_const_body(def_id).body }; // do not continue into miri if typeck errors occurred // it will fail horribly if tables.tainted_by_errors { return Err(ConstEvalErr { span: body.value.span, kind: TypeckError }) } trace!("running old const eval"); let old_result = ConstContext::new(tcx, key.param_env.and(substs), tables).eval(&body.value); trace!("old const eval produced {:?}", old_result); if tcx.sess.opts.debugging_opts.miri { let instance = ty::Instance::new(def_id, substs); trace!("const eval instance: {:?}, {:?}", instance, key.param_env); let miri_result = ::interpret::eval_body(tcx, instance, key.param_env); match (miri_result, old_result) { (Err(err), Ok(ok)) => { trace!("miri failed, ctfe returned {:?}", ok); tcx.sess.span_warn( tcx.def_span(key.value.0), "miri failed to eval, while ctfe succeeded", ); let ecx = mk_eval_cx(tcx, instance, key.param_env).unwrap(); let () = unwrap_miri(&ecx, Err(err)); Ok(ok) }, (_, Err(err)) => Err(err), (Ok((miri_val, miri_ty)), Ok(ctfe)) => { let mut ecx = mk_eval_cx(tcx, instance, key.param_env).unwrap(); let layout = ecx.layout_of(miri_ty).unwrap(); let miri_place = Place::from_primval_ptr(miri_val, layout.align); check_ctfe_against_miri(&mut ecx, miri_place, miri_ty, ctfe.val); Ok(ctfe) } } } else { old_result } } fn check_ctfe_against_miri<'a, 'tcx>( ecx: &mut EvalContext<'a, 'tcx, CompileTimeEvaluator>, miri_place: Place, miri_ty: Ty<'tcx>, ctfe: ConstVal<'tcx>, ) { use rustc::middle::const_val::ConstAggregate::*; use rustc_const_math::ConstFloat; use rustc::ty::TypeVariants::*; let miri_val = ValTy { value: ecx.read_place(miri_place).unwrap(), ty: miri_ty }; match miri_ty.sty { TyInt(int_ty) => { let prim = get_prim(ecx, miri_val); let c = ConstInt::new_signed_truncating(prim as i128, int_ty, ecx.tcx.sess.target.isize_ty); let c = ConstVal::Integral(c); assert_eq!(c, ctfe, "miri evaluated to {:?}, but ctfe yielded {:?}", c, ctfe); }, TyUint(uint_ty) => { let prim = get_prim(ecx, miri_val); let c = ConstInt::new_unsigned_truncating(prim, uint_ty, ecx.tcx.sess.target.usize_ty); let c = ConstVal::Integral(c); assert_eq!(c, ctfe, "miri evaluated to {:?}, but ctfe yielded {:?}", c, ctfe); }, TyFloat(ty) => { let prim = get_prim(ecx, miri_val); let f = ConstVal::Float(ConstFloat { bits: prim, ty }); assert_eq!(f, ctfe, "miri evaluated to {:?}, but ctfe yielded {:?}", f, ctfe); }, TyBool => { let bits = get_prim(ecx, miri_val); if bits > 1 { bug!("miri evaluated to {}, but expected a bool {:?}", bits, ctfe); } let b = ConstVal::Bool(bits == 1); assert_eq!(b, ctfe, "miri evaluated to {:?}, but ctfe yielded {:?}", b, ctfe); }, TyChar => { let bits = get_prim(ecx, miri_val); if let Some(cm) = ::std::char::from_u32(bits as u32) { assert_eq!( ConstVal::Char(cm), ctfe, "miri evaluated to {:?}, but expected {:?}", cm, ctfe, ); } else { bug!("miri evaluated to {}, but expected a char {:?}", bits, ctfe); } }, TyStr => { let value = ecx.follow_by_ref_value(miri_val.value, miri_val.ty); if let Ok(Value::ByValPair(PrimVal::Ptr(ptr), PrimVal::Bytes(len))) = value { let bytes = ecx .memory .read_bytes(ptr.into(), len as u64) .expect("bad miri memory for str"); if let Ok(s) = ::std::str::from_utf8(bytes) { if let ConstVal::Str(s2) = ctfe { assert_eq!(s, s2, "miri produced {:?}, but expected {:?}", s, s2); } else { bug!("miri produced {:?}, but expected {:?}", s, ctfe); } } else { bug!( "miri failed to produce valid utf8 {:?}, while ctfe produced {:?}", bytes, ctfe, ); } } else { bug!("miri evaluated to {:?}, but expected a str {:?}", value, ctfe); } }, TyArray(elem_ty, n) => { let n = n.val.to_const_int().unwrap().to_u64().unwrap(); let vec: Vec<(ConstVal, Ty<'tcx>)> = match ctfe { ConstVal::ByteStr(arr) => arr.data.iter().map(|&b| { (ConstVal::Integral(ConstInt::U8(b)), ecx.tcx.types.u8) }).collect(), ConstVal::Aggregate(Array(v)) => { v.iter().map(|c| (c.val, c.ty)).collect() }, ConstVal::Aggregate(Repeat(v, n)) => { vec![(v.val, v.ty); n as usize] }, _ => bug!("miri produced {:?}, but ctfe yielded {:?}", miri_ty, ctfe), }; let layout = ecx.layout_of(miri_ty).unwrap(); for (i, elem) in vec.into_iter().enumerate() { assert!((i as u64) < n); let (field_place, _) = ecx.place_field(miri_place, Field::new(i), layout).unwrap(); check_ctfe_against_miri(ecx, field_place, elem_ty, elem.0); } }, TyTuple(..) => { let vec = match ctfe { ConstVal::Aggregate(Tuple(v)) => v, _ => bug!("miri produced {:?}, but ctfe yielded {:?}", miri_ty, ctfe), }; let layout = ecx.layout_of(miri_ty).unwrap(); for (i, elem) in vec.into_iter().enumerate() { let (field_place, _) = ecx.place_field(miri_place, Field::new(i), layout).unwrap(); check_ctfe_against_miri(ecx, field_place, elem.ty, elem.val); } }, TyAdt(def, _) => { let mut miri_place = miri_place; let struct_variant = if def.is_enum() { let discr = ecx.read_discriminant_value(miri_place, miri_ty).unwrap(); let variant = def.discriminants(ecx.tcx).position(|variant_discr| { variant_discr.to_u128_unchecked() == discr }).expect("miri produced invalid enum discriminant"); miri_place = ecx.place_downcast(miri_place, variant).unwrap(); &def.variants[variant] } else { def.struct_variant() }; let vec = match ctfe { ConstVal::Aggregate(Struct(v)) => v, ConstVal::Variant(did) => { assert_eq!(struct_variant.fields.len(), 0); assert_eq!(did, struct_variant.did); return; }, ctfe => bug!("miri produced {:?}, but ctfe yielded {:?}", miri_ty, ctfe), }; let layout = ecx.layout_of(miri_ty).unwrap(); for &(name, elem) in vec.into_iter() { let field = struct_variant.fields.iter().position(|f| f.name == name).unwrap(); let (field_place, _) = ecx.place_field(miri_place, Field::new(field), layout).unwrap(); check_ctfe_against_miri(ecx, field_place, elem.ty, elem.val); } }, TySlice(_) => bug!("miri produced a slice?"), // not supported by ctfe TyRawPtr(_) | TyRef(..) => {} TyDynamic(..) => bug!("miri produced a trait object"), TyClosure(..) => bug!("miri produced a closure"), TyGenerator(..) => bug!("miri produced a generator"), TyNever => bug!("miri produced a value of the never type"), TyProjection(_) => bug!("miri produced a projection"), TyAnon(..) => bug!("miri produced an impl Trait type"), TyParam(_) => bug!("miri produced an unmonomorphized type"), TyInfer(_) => bug!("miri produced an uninferred type"), TyError => bug!("miri produced a type error"), TyForeign(_) => bug!("miri produced an extern type"), // should be fine TyFnDef(..) => {} TyFnPtr(_) => { let value = ecx.value_to_primval(miri_val); let ptr = match value { Ok(PrimVal::Ptr(ptr)) => ptr, value => bug!("expected fn ptr, got {:?}", value), }; let inst = ecx.memory.get_fn(ptr).unwrap(); match ctfe { ConstVal::Function(did, substs) => { let ctfe = ty::Instance::resolve( ecx.tcx, ecx.param_env, did, substs, ).unwrap(); assert_eq!(inst, ctfe, "expected fn ptr {:?}, but got {:?}", ctfe, inst); }, _ => bug!("ctfe produced {:?}, but miri produced function {:?}", ctfe, inst), } }, } } fn get_prim<'a, 'tcx>( ecx: &mut EvalContext<'a, 'tcx, CompileTimeEvaluator>, val: ValTy<'tcx>, ) -> u128 { let res = ecx.value_to_primval(val).and_then(|prim| prim.to_bytes()); unwrap_miri(ecx, res) } fn unwrap_miri<'a, 'tcx, T>( ecx: &EvalContext<'a, 'tcx, CompileTimeEvaluator>, res: Result>, ) -> T { match res { Ok(val) => val, Err(mut err) => { ecx.report(&mut err); ecx.tcx.sess.abort_if_errors(); bug!("{:#?}", err); } } }