diff --git a/src/error.rs b/src/error.rs index 726a4dcfa3fe..7129fa0dfcd8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -40,6 +40,8 @@ pub enum EvalError<'tcx> { required: usize, has: usize, }, + CalledClosureAsFunction, + VtableForArgumentlessMethod, } pub type EvalResult<'tcx, T> = Result>; @@ -88,6 +90,10 @@ impl<'tcx> Error for EvalError<'tcx> { "reached the configured maximum number of stack frames", EvalError::AlignmentCheckFailed{..} => "tried to execute a misaligned read or write", + EvalError::CalledClosureAsFunction => + "tried to call a closure through a function pointer", + EvalError::VtableForArgumentlessMethod => + "tried to call a vtable function without arguments", } } diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index e77ec942bbf5..370b1efc4991 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -23,6 +23,7 @@ use std::collections::HashMap; mod step; mod terminator; mod cast; +mod vtable; pub struct EvalContext<'a, 'tcx: 'a> { /// The results of the type checker, from rustc. @@ -108,7 +109,7 @@ struct Lvalue { enum LvalueExtra { None, Length(u64), - // TODO(solson): Vtable(memory::AllocId), + Vtable(Pointer), DowncastVariant(usize), } @@ -569,6 +570,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { LvalueExtra::Length(len) => { self.memory.write_usize(extra, len)?; } + LvalueExtra::Vtable(ptr) => { + self.memory.write_ptr(extra, ptr)?; + }, LvalueExtra::DowncastVariant(..) => bug!("attempted to take a reference to an enum downcast lvalue"), } @@ -587,6 +591,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { Unsize => { let src = self.eval_operand(operand)?; let src_ty = self.operand_ty(operand); + let dest_ty = self.monomorphize(dest_ty, self.substs()); + assert!(self.type_is_fat_ptr(dest_ty)); let (ptr, extra) = self.get_fat_ptr(dest); self.move_(src, ptr, src_ty)?; let src_pointee_ty = pointee_type(src_ty).unwrap(); @@ -596,8 +602,22 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { (&ty::TyArray(_, length), &ty::TySlice(_)) => { self.memory.write_usize(extra, length as u64)?; } + (&ty::TyTrait(_), &ty::TyTrait(_)) => { + // For now, upcasts are limited to changes in marker + // traits, and hence never actually require an actual + // change to the vtable. + let (_, src_extra) = self.get_fat_ptr(src); + let src_extra = self.memory.read_ptr(src_extra)?; + self.memory.write_ptr(extra, src_extra)?; + }, + (_, &ty::TyTrait(ref data)) => { + let trait_ref = data.principal.with_self_ty(self.tcx, src_pointee_ty); + let trait_ref = self.tcx.erase_regions(&trait_ref); + let vtable = self.get_vtable(trait_ref)?; + self.memory.write_ptr(extra, vtable)?; + }, - _ => return Err(EvalError::Unimplemented(format!("can't handle cast: {:?}", rvalue))), + _ => bug!("invalid unsizing {:?} -> {:?}", src_ty, dest_ty), } } @@ -638,8 +658,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { ty::TyFnPtr(unsafe_fn_ty) => { let src = self.eval_operand(operand)?; let ptr = self.memory.read_ptr(src)?; - let fn_def = self.memory.get_fn(ptr.alloc_id)?; - let fn_ptr = self.memory.create_fn_ptr(fn_def.def_id, fn_def.substs, unsafe_fn_ty); + let (def_id, substs, _) = self.memory.get_fn(ptr.alloc_id)?; + let fn_ptr = self.memory.create_fn_ptr(def_id, substs, unsafe_fn_ty); self.memory.write_ptr(dest, fn_ptr)?; }, ref other => bug!("fn to unsafe fn cast on {:?}", other), @@ -827,7 +847,6 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { Deref => { let pointee_ty = pointee_type(base_ty).expect("Deref of non-pointer"); - self.memory.dump(base.ptr.alloc_id); let ptr = self.memory.read_ptr(base.ptr)?; let extra = match pointee_ty.sty { ty::TySlice(_) | ty::TyStr => { @@ -835,7 +854,11 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let len = self.memory.read_usize(extra)?; LvalueExtra::Length(len) } - ty::TyTrait(_) => unimplemented!(), + ty::TyTrait(_) => { + let (_, extra) = self.get_fat_ptr(base.ptr); + let vtable = self.memory.read_ptr(extra)?; + LvalueExtra::Vtable(vtable) + }, _ => LvalueExtra::None, }; return Ok(Lvalue { ptr: ptr, extra: extra }); diff --git a/src/interpreter/terminator.rs b/src/interpreter/terminator.rs index afba23fac599..89a0f820ed6e 100644 --- a/src/interpreter/terminator.rs +++ b/src/interpreter/terminator.rs @@ -12,7 +12,7 @@ use syntax::codemap::{DUMMY_SP, Span}; use super::{EvalContext, IntegerExt}; use error::{EvalError, EvalResult}; -use memory::{Pointer, FunctionDefinition}; +use memory::Pointer; impl<'a, 'tcx> EvalContext<'a, 'tcx> { @@ -90,7 +90,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { ty::TyFnPtr(bare_fn_ty) => { let ptr = self.eval_operand(func)?; let fn_ptr = self.memory.read_ptr(ptr)?; - let FunctionDefinition { def_id, substs, fn_ty } = self.memory.get_fn(fn_ptr.alloc_id)?; + let (def_id, substs, fn_ty) = self.memory.get_fn(fn_ptr.alloc_id)?; if fn_ty != bare_fn_ty { return Err(EvalError::FunctionPointerTyMismatch(fn_ty, bare_fn_ty)); } @@ -172,14 +172,6 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { // TODO(solson): Adjust the first argument when calling a Fn or // FnMut closure via FnOnce::call_once. - // Only trait methods can have a Self parameter. - let (resolved_def_id, resolved_substs) = - if let Some(trait_id) = self.tcx.trait_of_item(def_id) { - self.trait_method(trait_id, def_id, substs) - } else { - (def_id, substs) - }; - let mut arg_srcs = Vec::new(); for arg in args { let src = self.eval_operand(arg)?; @@ -187,6 +179,14 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { arg_srcs.push((src, src_ty)); } + // Only trait methods can have a Self parameter. + let (resolved_def_id, resolved_substs) = + if let Some(trait_id) = self.tcx.trait_of_item(def_id) { + self.trait_method(trait_id, def_id, substs, arg_srcs.get_mut(0))? + } else { + (def_id, substs) + }; + if fn_ty.abi == Abi::RustCall && !args.is_empty() { arg_srcs.pop(); let last_arg = args.last().unwrap(); @@ -464,7 +464,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { Ok(()) } - fn fulfill_obligation(&self, trait_ref: ty::PolyTraitRef<'tcx>) -> traits::Vtable<'tcx, ()> { + pub(super) 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.infer_ctxt(None, None, Reveal::All).enter(|infcx| { @@ -491,8 +491,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { &self, trait_id: DefId, def_id: DefId, - substs: &'tcx Substs<'tcx> - ) -> (DefId, &'tcx Substs<'tcx>) { + substs: &'tcx Substs<'tcx>, + first_arg: Option<&mut (Pointer, Ty<'tcx>)>, + ) -> EvalResult<'tcx, (DefId, &'tcx Substs<'tcx>)> { let trait_ref = ty::TraitRef::from_method(self.tcx, trait_id, substs); let trait_ref = self.tcx.normalize_associated_type(&ty::Binder(trait_ref)); @@ -504,11 +505,11 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { // and those from the method: let mth = get_impl_method(self.tcx, substs, impl_did, vtable_impl.substs, mname); - (mth.method.def_id, mth.substs) + Ok((mth.method.def_id, mth.substs)) } traits::VtableClosure(vtable_closure) => - (vtable_closure.closure_def_id, vtable_closure.substs.func_substs), + Ok((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(); @@ -524,14 +525,22 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { // 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) - // } - } + traits::VtableObject(ref data) => { + let idx = self.tcx.get_vtable_index_of_object_method(data, def_id); + if let Some(&mut(first_arg, ref mut first_ty)) = first_arg { + let (_, vtable) = self.get_fat_ptr(first_arg); + let vtable = self.memory.read_ptr(vtable)?; + let idx = idx + 3; + let offset = idx * self.memory.pointer_size(); + let fn_ptr = self.memory.read_ptr(vtable.offset(offset as isize))?; + let (def_id, substs, ty) = self.memory.get_fn(fn_ptr.alloc_id)?; + // FIXME: skip_binder is wrong for HKL + *first_ty = ty.sig.skip_binder().inputs[0]; + Ok((def_id, substs)) + } else { + Err(EvalError::VtableForArgumentlessMethod) + } + }, vtable => bug!("resolved vtable bad vtable {:?} in trans", vtable), } } @@ -566,14 +575,14 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } #[derive(Debug)] -struct ImplMethod<'tcx> { - method: Rc>, - substs: &'tcx Substs<'tcx>, - is_provided: bool, +pub(super) struct ImplMethod<'tcx> { + pub(super) method: Rc>, + pub(super) substs: &'tcx Substs<'tcx>, + pub(super) is_provided: bool, } /// Locates the applicable definition of a method, given its name. -fn get_impl_method<'a, 'tcx>( +pub(super) fn get_impl_method<'a, 'tcx>( tcx: TyCtxt<'a, 'tcx, 'tcx>, substs: &'tcx Substs<'tcx>, impl_def_id: DefId, diff --git a/src/interpreter/vtable.rs b/src/interpreter/vtable.rs new file mode 100644 index 000000000000..800ded8177cb --- /dev/null +++ b/src/interpreter/vtable.rs @@ -0,0 +1,202 @@ +use rustc::hir::def_id::DefId; +use rustc::traits::{self, Reveal, SelectionContext}; +use rustc::ty::subst::{Substs, Subst}; +use rustc::ty; + +use super::EvalContext; +use error::EvalResult; +use memory::Pointer; +use super::terminator::{get_impl_method, ImplMethod}; + +impl<'a, 'tcx> EvalContext<'a, 'tcx> { + /// Creates a returns a dynamic vtable for the given type and vtable origin. + /// This is used only for objects. + /// + /// The `trait_ref` encodes the erased self type. Hence if we are + /// making an object `Foo` from a value of type `Foo`, then + /// `trait_ref` would map `T:Trait`. + pub fn get_vtable(&mut self, trait_ref: ty::PolyTraitRef<'tcx>) -> EvalResult<'tcx, Pointer> { + let tcx = self.tcx; + + debug!("get_vtable(trait_ref={:?})", trait_ref); + + let methods: Vec<_> = traits::supertraits(tcx, trait_ref.clone()).flat_map(|trait_ref| { + match self.fulfill_obligation(trait_ref.clone()) { + // Should default trait error here? + traits::VtableDefaultImpl(_) | + traits::VtableBuiltin(_) => { + Vec::new().into_iter() + } + traits::VtableImpl( + traits::VtableImplData { + impl_def_id: id, + substs, + nested: _ }) => { + self.get_vtable_methods(id, substs) + .into_iter() + .map(|opt_mth| opt_mth.map(|mth| { + self.memory.create_fn_ptr(mth.method.def_id, mth.substs, mth.method.fty) + })) + .collect::>() + .into_iter() + } + traits::VtableClosure( + traits::VtableClosureData { + closure_def_id, + substs, + nested: _ }) => { + let closure_type = self.tcx.closure_type(closure_def_id, substs); + let fn_ty = ty::BareFnTy { + unsafety: closure_type.unsafety, + abi: closure_type.abi, + sig: closure_type.sig, + }; + let fn_ty = self.tcx.mk_bare_fn(fn_ty); + unimplemented!() + //vec![Some(self.memory.create_fn_ptr(closure_def_id, substs.func_substs, fn_ty))].into_iter() + } + traits::VtableFnPointer( + traits::VtableFnPointerData { + fn_ty: bare_fn_ty, + nested: _ }) => { + let trait_closure_kind = tcx.lang_items.fn_trait_kind(trait_ref.def_id()).unwrap(); + //vec![trans_fn_pointer_shim(ccx, trait_closure_kind, bare_fn_ty)].into_iter() + unimplemented!() + } + traits::VtableObject(ref data) => { + // this would imply that the Self type being erased is + // an object type; this cannot happen because we + // cannot cast an unsized type into a trait object + bug!("cannot get vtable for an object type: {:?}", + data); + } + vtable @ traits::VtableParam(..) => { + bug!("resolved vtable for {:?} to bad vtable {:?} in trans", + trait_ref, + vtable); + } + } + }).collect(); + + let size = self.type_size(trait_ref.self_ty()); + let align = self.type_align(trait_ref.self_ty()); + + let ptr_size = self.memory.pointer_size(); + let vtable = self.memory.allocate(ptr_size * (3 + methods.len()), ptr_size)?; + + // FIXME: generate a destructor for the vtable. + // trans does this with glue::get_drop_glue(ccx, trait_ref.self_ty()) + + self.memory.write_usize(vtable.offset(ptr_size as isize), size as u64)?; + self.memory.write_usize(vtable.offset((ptr_size * 2) as isize), align as u64)?; + + for (i, method) in methods.into_iter().enumerate() { + if let Some(method) = method { + self.memory.write_ptr(vtable.offset(ptr_size as isize * (3 + i as isize)), method)?; + } + } + + Ok(vtable) + } + + fn get_vtable_methods(&mut self, impl_id: DefId, substs: &'tcx Substs<'tcx>) -> Vec>> { + debug!("get_vtable_methods(impl_id={:?}, substs={:?}", impl_id, substs); + + let trait_id = match self.tcx.impl_trait_ref(impl_id) { + Some(t_id) => t_id.def_id, + None => bug!("make_impl_vtable: don't know how to \ + make a vtable for a type impl!") + }; + + self.tcx.populate_implementations_for_trait_if_necessary(trait_id); + + let trait_item_def_ids = self.tcx.trait_item_def_ids(trait_id); + trait_item_def_ids + .iter() + + // Filter out non-method items. + .filter_map(|item_def_id| { + match *item_def_id { + ty::MethodTraitItemId(def_id) => Some(def_id), + _ => None, + } + }) + + // Now produce pointers for each remaining method. If the + // method could never be called from this object, just supply + // null. + .map(|trait_method_def_id| { + debug!("get_vtable_methods: trait_method_def_id={:?}", + trait_method_def_id); + + let trait_method_type = match self.tcx.impl_or_trait_item(trait_method_def_id) { + ty::MethodTraitItem(m) => m, + _ => bug!("should be a method, not other assoc item"), + }; + let name = trait_method_type.name; + + // Some methods cannot be called on an object; skip those. + if !self.tcx.is_vtable_safe_method(trait_id, &trait_method_type) { + debug!("get_vtable_methods: not vtable safe"); + return None; + } + + debug!("get_vtable_methods: trait_method_type={:?}", + trait_method_type); + + // the method may have some early-bound lifetimes, add + // regions for those + let method_substs = Substs::for_item(self.tcx, trait_method_def_id, + |_, _| self.tcx.mk_region(ty::ReErased), + |_, _| self.tcx.types.err); + + // The substitutions we have are on the impl, so we grab + // the method type from the impl to substitute into. + let mth = get_impl_method(self.tcx, method_substs, impl_id, substs, name); + + debug!("get_vtable_methods: mth={:?}", mth); + + // If this is a default method, it's possible that it + // relies on where clauses that do not hold for this + // particular set of type parameters. Note that this + // method could then never be called, so we do not want to + // try and trans it, in that case. Issue #23435. + if mth.is_provided { + let predicates = mth.method.predicates.predicates.subst(self.tcx, &mth.substs); + if !self.normalize_and_test_predicates(predicates) { + debug!("get_vtable_methods: predicates do not hold"); + return None; + } + } + + Some(mth) + }) + .collect() + } + + /// Normalizes the predicates and checks whether they hold. If this + /// returns false, then either normalize encountered an error or one + /// of the predicates did not hold. Used when creating vtables to + /// check for unsatisfiable methods. + fn normalize_and_test_predicates(&mut self, predicates: Vec>) -> bool { + debug!("normalize_and_test_predicates(predicates={:?})", + predicates); + + self.tcx.infer_ctxt(None, None, Reveal::All).enter(|infcx| { + let mut selcx = SelectionContext::new(&infcx); + let mut fulfill_cx = traits::FulfillmentContext::new(); + let cause = traits::ObligationCause::dummy(); + let traits::Normalized { value: predicates, obligations } = + traits::normalize(&mut selcx, cause.clone(), &predicates); + for obligation in obligations { + fulfill_cx.register_predicate_obligation(&infcx, obligation); + } + for predicate in predicates { + let obligation = traits::Obligation::new(cause.clone(), predicate); + fulfill_cx.register_predicate_obligation(&infcx, obligation); + } + + fulfill_cx.select_all_or_error(&infcx).is_ok() + }) + } +} diff --git a/src/memory.rs b/src/memory.rs index 75cbb16122f9..25ae6b3d995b 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -4,7 +4,7 @@ use std::collections::{btree_map, BTreeMap, HashMap, HashSet, VecDeque}; use std::{fmt, iter, ptr}; use rustc::hir::def_id::DefId; -use rustc::ty::BareFnTy; +use rustc::ty::{BareFnTy, ClosureTy, ClosureSubsts}; use rustc::ty::subst::Substs; use rustc::ty::layout::{self, TargetDataLayout}; @@ -53,11 +53,22 @@ impl Pointer { } } -#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] -pub struct FunctionDefinition<'tcx> { +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +struct FunctionDefinition<'tcx> { pub def_id: DefId, - pub substs: &'tcx Substs<'tcx>, - pub fn_ty: &'tcx BareFnTy<'tcx>, + pub kind: FunctionKind<'tcx>, +} + +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +enum FunctionKind<'tcx> { + Closure { + substs: ClosureSubsts<'tcx>, + ty: ClosureTy<'tcx>, + }, + Function { + substs: &'tcx Substs<'tcx>, + ty: &'tcx BareFnTy<'tcx>, + } } //////////////////////////////////////////////////////////////////////////////// @@ -112,12 +123,27 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { self.alloc_map.iter() } - pub fn create_fn_ptr(&mut self, def_id: DefId, substs: &'tcx Substs<'tcx>, fn_ty: &'tcx BareFnTy<'tcx>) -> Pointer { - let def = FunctionDefinition { + pub fn create_closure_ptr(&mut self, def_id: DefId, substs: ClosureSubsts<'tcx>, fn_ty: ClosureTy<'tcx>) -> Pointer { + self.create_fn_alloc(FunctionDefinition { def_id: def_id, - substs: substs, - fn_ty: fn_ty, - }; + kind: FunctionKind::Closure { + substs: substs, + ty: fn_ty, + } + }) + } + + pub fn create_fn_ptr(&mut self, def_id: DefId, substs: &'tcx Substs<'tcx>, fn_ty: &'tcx BareFnTy<'tcx>) -> Pointer { + self.create_fn_alloc(FunctionDefinition { + def_id: def_id, + kind: FunctionKind::Function { + substs: substs, + ty: fn_ty, + } + }) + } + + fn create_fn_alloc(&mut self, def: FunctionDefinition<'tcx>) -> Pointer { if let Some(&alloc_id) = self.function_alloc_cache.get(&def) { return Pointer { alloc_id: alloc_id, @@ -127,7 +153,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { let id = self.next_id; debug!("creating fn ptr: {}", id); self.next_id.0 += 1; - self.functions.insert(id, def); + self.functions.insert(id, def.clone()); self.function_alloc_cache.insert(def, id); Pointer { alloc_id: id, @@ -269,10 +295,33 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { } } - pub fn get_fn(&self, id: AllocId) -> EvalResult<'tcx, FunctionDefinition<'tcx>> { + pub fn get_closure(&self, id: AllocId) -> EvalResult<'tcx, (DefId, ClosureSubsts<'tcx>, ClosureTy<'tcx>)> { + debug!("reading closure fn ptr: {}", id); + match self.functions.get(&id) { + Some(&FunctionDefinition { + def_id, + kind: FunctionKind::Closure { ref substs, ref ty } + }) => Ok((def_id, substs.clone(), ty.clone())), + Some(&FunctionDefinition { + kind: FunctionKind::Function { .. }, .. + }) => Err(EvalError::CalledClosureAsFunction), + None => match self.alloc_map.get(&id) { + Some(_) => Err(EvalError::ExecuteMemory), + None => Err(EvalError::InvalidFunctionPointer), + } + } + } + + pub fn get_fn(&self, id: AllocId) -> EvalResult<'tcx, (DefId, &'tcx Substs<'tcx>, &'tcx BareFnTy<'tcx>)> { debug!("reading fn ptr: {}", id); match self.functions.get(&id) { - Some(&fn_id) => Ok(fn_id), + Some(&FunctionDefinition { + def_id, + kind: FunctionKind::Function { substs, ty } + }) => Ok((def_id, substs, ty)), + Some(&FunctionDefinition { + kind: FunctionKind::Closure { .. }, .. + }) => Err(EvalError::CalledClosureAsFunction), None => match self.alloc_map.get(&id) { Some(_) => Err(EvalError::ExecuteMemory), None => Err(EvalError::InvalidFunctionPointer), diff --git a/tests/run-pass/traits.rs b/tests/run-pass/traits.rs new file mode 100644 index 000000000000..acef02d2bc88 --- /dev/null +++ b/tests/run-pass/traits.rs @@ -0,0 +1,26 @@ +struct Struct(i32); + +trait Trait { + fn method(&self); +} + +impl Trait for Struct { + fn method(&self) { + assert_eq!(self.0, 42); + } +} + +fn main() { + let y: &Trait = &Struct(42); + y.method(); + /* + let x: Box i32> = Box::new(|x| x * 2); + assert_eq!(x(21), 42); + let mut i = 5; + { + let mut x: Box = Box::new(|| i *= 2); + x(); x(); + } + assert_eq!(i, 20); + */ +}