implement calling methods through trait objects

This commit is contained in:
Oliver Schneider 2016-09-09 12:51:14 +02:00
parent 168d9e7745
commit 00c551c5f0
No known key found for this signature in database
GPG key ID: 56D6EEA0FC67AC46
6 changed files with 347 additions and 47 deletions

View file

@ -40,6 +40,8 @@ pub enum EvalError<'tcx> {
required: usize,
has: usize,
},
CalledClosureAsFunction,
VtableForArgumentlessMethod,
}
pub type EvalResult<'tcx, T> = Result<T, EvalError<'tcx>>;
@ -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",
}
}

View file

@ -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 });

View file

@ -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,24 @@ 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 {
self.memory.dump(first_arg.alloc_id);
println!("idx: {}", idx);
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 +577,14 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
}
#[derive(Debug)]
struct ImplMethod<'tcx> {
method: Rc<ty::Method<'tcx>>,
substs: &'tcx Substs<'tcx>,
is_provided: bool,
pub (super) struct ImplMethod<'tcx> {
pub (super) method: Rc<ty::Method<'tcx>>,
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,

195
src/interpreter/vtable.rs Normal file
View file

@ -0,0 +1,195 @@
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<Trait>` from a value of type `Foo<T>`, 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::<Vec<_>>()
.into_iter()
}
traits::VtableClosure(
traits::VtableClosureData {
closure_def_id,
substs,
nested: _ }) => {
let closure_type = self.tcx.closure_type(closure_def_id, substs);
vec![Some(self.memory.create_closure_ptr(closure_def_id, substs, closure_type))].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<Option<ImplMethod<'tcx>>> {
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<ty::Predicate<'tcx>>) -> 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()
})
}
}

View file

@ -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),

16
tests/run-pass/traits.rs Normal file
View file

@ -0,0 +1,16 @@
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();
}