Merge remote-tracking branch 'oli/function_pointers2' into fixup-function_pointers2
This commit is contained in:
commit
024b3d2b45
8 changed files with 190 additions and 93 deletions
|
|
@ -6,6 +6,7 @@ use memory::Pointer;
|
|||
#[derive(Clone, Debug)]
|
||||
pub enum EvalError {
|
||||
DanglingPointerDeref,
|
||||
InvalidFunctionPointer,
|
||||
InvalidBool,
|
||||
InvalidDiscriminant,
|
||||
PointerOutOfBounds {
|
||||
|
|
@ -28,6 +29,8 @@ impl Error for EvalError {
|
|||
match *self {
|
||||
EvalError::DanglingPointerDeref =>
|
||||
"dangling pointer was dereferenced",
|
||||
EvalError::InvalidFunctionPointer =>
|
||||
"tried to use a pointer as a function pointer",
|
||||
EvalError::InvalidBool =>
|
||||
"invalid boolean value read",
|
||||
EvalError::InvalidDiscriminant =>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use rustc::traits::{self, ProjectionMode};
|
|||
use rustc::ty::fold::TypeFoldable;
|
||||
use rustc::ty::layout::{self, Layout, Size};
|
||||
use rustc::ty::subst::{self, Subst, Substs};
|
||||
use rustc::ty::{self, Ty, TyCtxt};
|
||||
use rustc::ty::{self, Ty, TyCtxt, BareFnTy};
|
||||
use rustc::util::nodemap::DefIdMap;
|
||||
use rustc_data_structures::indexed_vec::Idx;
|
||||
use std::cell::RefCell;
|
||||
|
|
@ -15,7 +15,7 @@ use std::rc::Rc;
|
|||
use std::{iter, mem};
|
||||
use syntax::ast;
|
||||
use syntax::attr;
|
||||
use syntax::codemap::{self, DUMMY_SP};
|
||||
use syntax::codemap::{self, DUMMY_SP, Span};
|
||||
|
||||
use error::{EvalError, EvalResult};
|
||||
use memory::{Memory, Pointer};
|
||||
|
|
@ -40,7 +40,7 @@ pub struct EvalContext<'a, 'tcx: 'a> {
|
|||
mir_cache: RefCell<DefIdMap<Rc<mir::Mir<'tcx>>>>,
|
||||
|
||||
/// The virtual memory system.
|
||||
memory: Memory,
|
||||
memory: Memory<'tcx>,
|
||||
|
||||
/// Precomputed statics, constants and promoteds
|
||||
statics: HashMap<ConstantId<'tcx>, Pointer>,
|
||||
|
|
@ -283,6 +283,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
|
|||
}
|
||||
|
||||
fn load_mir(&self, def_id: DefId) -> CachedMir<'a, 'tcx> {
|
||||
use rustc_trans::back::symbol_names::def_id_to_string;
|
||||
match self.tcx.map.as_local_node_id(def_id) {
|
||||
Some(node_id) => CachedMir::Ref(self.mir_map.map.get(&node_id).unwrap()),
|
||||
None => {
|
||||
|
|
@ -293,7 +294,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
|
|||
|
||||
let cs = &self.tcx.sess.cstore;
|
||||
let mir = cs.maybe_get_item_mir(self.tcx, def_id).unwrap_or_else(|| {
|
||||
panic!("no mir for {:?}", def_id);
|
||||
panic!("no mir for `{}`", def_id_to_string(self.tcx, def_id));
|
||||
});
|
||||
let cached = Rc::new(mir);
|
||||
mir_cache.insert(def_id, cached.clone());
|
||||
|
|
@ -421,84 +422,17 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
|
|||
|
||||
let func_ty = self.operand_ty(func);
|
||||
match func_ty.sty {
|
||||
ty::TyFnPtr(bare_fn_ty) => {
|
||||
let ptr = self.eval_operand(func)?;
|
||||
assert_eq!(ptr.offset, 0);
|
||||
let fn_ptr = self.memory.read_ptr(ptr)?;
|
||||
let (def_id, substs) = self.memory.get_fn(fn_ptr.alloc_id)?;
|
||||
self.eval_fn_call(def_id, substs, bare_fn_ty, return_ptr, args,
|
||||
terminator.source_info.span)?
|
||||
},
|
||||
ty::TyFnDef(def_id, substs, fn_ty) => {
|
||||
use syntax::abi::Abi;
|
||||
match fn_ty.abi {
|
||||
Abi::RustIntrinsic => {
|
||||
let name = self.tcx.item_name(def_id).as_str();
|
||||
match fn_ty.sig.0.output {
|
||||
ty::FnConverging(ty) => {
|
||||
let size = self.type_size(ty, self.substs());
|
||||
let ret = return_ptr.unwrap();
|
||||
self.call_intrinsic(&name, substs, args, ret, size)?
|
||||
}
|
||||
ty::FnDiverging => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
Abi::C => {
|
||||
match fn_ty.sig.0.output {
|
||||
ty::FnConverging(ty) => {
|
||||
let size = self.type_size(ty, self.substs());
|
||||
self.call_c_abi(def_id, args, return_ptr.unwrap(), size)?
|
||||
}
|
||||
ty::FnDiverging => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
Abi::Rust | Abi::RustCall => {
|
||||
// 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 substs.self_ty().is_some() {
|
||||
self.trait_method(def_id, substs)
|
||||
} else {
|
||||
(def_id, substs)
|
||||
};
|
||||
|
||||
let mut arg_srcs = Vec::new();
|
||||
for arg in args {
|
||||
let src = self.eval_operand(arg)?;
|
||||
let src_ty = self.operand_ty(arg);
|
||||
arg_srcs.push((src, src_ty));
|
||||
}
|
||||
|
||||
if fn_ty.abi == Abi::RustCall && !args.is_empty() {
|
||||
arg_srcs.pop();
|
||||
let last_arg = args.last().unwrap();
|
||||
let last = self.eval_operand(last_arg)?;
|
||||
let last_ty = self.operand_ty(last_arg);
|
||||
let last_layout = self.type_layout(last_ty, self.substs());
|
||||
match (&last_ty.sty, last_layout) {
|
||||
(&ty::TyTuple(fields),
|
||||
&Layout::Univariant { ref variant, .. }) => {
|
||||
let offsets = iter::once(0)
|
||||
.chain(variant.offset_after_field.iter()
|
||||
.map(|s| s.bytes()));
|
||||
for (offset, ty) in offsets.zip(fields) {
|
||||
let src = last.offset(offset as isize);
|
||||
arg_srcs.push((src, ty));
|
||||
}
|
||||
}
|
||||
ty => panic!("expected tuple as last argument in function with 'rust-call' ABI, got {:?}", ty),
|
||||
}
|
||||
}
|
||||
|
||||
let mir = self.load_mir(resolved_def_id);
|
||||
self.push_stack_frame(
|
||||
def_id, terminator.source_info.span, mir, resolved_substs,
|
||||
return_ptr
|
||||
);
|
||||
|
||||
for (i, (src, src_ty)) in arg_srcs.into_iter().enumerate() {
|
||||
let dest = self.frame().locals[i];
|
||||
self.move_(src, dest, src_ty)?;
|
||||
}
|
||||
}
|
||||
|
||||
abi => return Err(EvalError::Unimplemented(format!("can't handle function with {:?} ABI", abi))),
|
||||
}
|
||||
self.eval_fn_call(def_id, substs, fn_ty, return_ptr, args,
|
||||
terminator.source_info.span)?
|
||||
}
|
||||
|
||||
_ => return Err(EvalError::Unimplemented(format!("can't handle callee of type {:?}", func_ty))),
|
||||
|
|
@ -530,6 +464,93 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn eval_fn_call(
|
||||
&mut self,
|
||||
def_id: DefId,
|
||||
substs: &'tcx Substs<'tcx>,
|
||||
fn_ty: &'tcx BareFnTy,
|
||||
return_ptr: Option<Pointer>,
|
||||
args: &[mir::Operand<'tcx>],
|
||||
span: Span,
|
||||
) -> EvalResult<()> {
|
||||
use syntax::abi::Abi;
|
||||
match fn_ty.abi {
|
||||
Abi::RustIntrinsic => {
|
||||
let name = self.tcx.item_name(def_id).as_str();
|
||||
match fn_ty.sig.0.output {
|
||||
ty::FnConverging(ty) => {
|
||||
let size = self.type_size(ty, self.substs());
|
||||
let ret = return_ptr.unwrap();
|
||||
self.call_intrinsic(&name, substs, args, ret, size)
|
||||
}
|
||||
ty::FnDiverging => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
Abi::C => {
|
||||
match fn_ty.sig.0.output {
|
||||
ty::FnConverging(ty) => {
|
||||
let size = self.type_size(ty, self.substs());
|
||||
self.call_c_abi(def_id, args, return_ptr.unwrap(), size)
|
||||
}
|
||||
ty::FnDiverging => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
Abi::Rust | Abi::RustCall => {
|
||||
// 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 substs.self_ty().is_some() {
|
||||
self.trait_method(def_id, substs)
|
||||
} else {
|
||||
(def_id, substs)
|
||||
};
|
||||
|
||||
let mut arg_srcs = Vec::new();
|
||||
for arg in args {
|
||||
let src = self.eval_operand(arg)?;
|
||||
let src_ty = self.operand_ty(arg);
|
||||
arg_srcs.push((src, src_ty));
|
||||
}
|
||||
|
||||
if fn_ty.abi == Abi::RustCall && !args.is_empty() {
|
||||
arg_srcs.pop();
|
||||
let last_arg = args.last().unwrap();
|
||||
let last = self.eval_operand(last_arg)?;
|
||||
let last_ty = self.operand_ty(last_arg);
|
||||
let last_layout = self.type_layout(last_ty, self.substs());
|
||||
match (&last_ty.sty, last_layout) {
|
||||
(&ty::TyTuple(fields),
|
||||
&Layout::Univariant { ref variant, .. }) => {
|
||||
let offsets = iter::once(0)
|
||||
.chain(variant.offset_after_field.iter()
|
||||
.map(|s| s.bytes()));
|
||||
for (offset, ty) in offsets.zip(fields) {
|
||||
let src = last.offset(offset as isize);
|
||||
arg_srcs.push((src, ty));
|
||||
}
|
||||
}
|
||||
ty => panic!("expected tuple as last argument in function with 'rust-call' ABI, got {:?}", ty),
|
||||
}
|
||||
}
|
||||
|
||||
let mir = self.load_mir(resolved_def_id);
|
||||
self.push_stack_frame(def_id, span, mir, resolved_substs, return_ptr);
|
||||
|
||||
for (i, (src, src_ty)) in arg_srcs.into_iter().enumerate() {
|
||||
let dest = self.frame().locals[i];
|
||||
self.move_(src, dest, src_ty)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
abi => Err(EvalError::Unimplemented(format!("can't handle function with {:?} ABI", abi))),
|
||||
}
|
||||
}
|
||||
|
||||
fn drop(&mut self, ptr: Pointer, ty: Ty<'tcx>) -> EvalResult<()> {
|
||||
if !self.type_needs_drop(ty) {
|
||||
debug!("no need to drop {:?}", ty);
|
||||
|
|
@ -1023,12 +1044,11 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
|
|||
}
|
||||
|
||||
Cast(kind, ref operand, dest_ty) => {
|
||||
let src = self.eval_operand(operand)?;
|
||||
let src_ty = self.operand_ty(operand);
|
||||
|
||||
use rustc::mir::repr::CastKind::*;
|
||||
match kind {
|
||||
Unsize => {
|
||||
let src = self.eval_operand(operand)?;
|
||||
let src_ty = self.operand_ty(operand);
|
||||
self.move_(src, dest, src_ty)?;
|
||||
let src_pointee_ty = pointee_type(src_ty).unwrap();
|
||||
let dest_pointee_ty = pointee_type(dest_ty).unwrap();
|
||||
|
|
@ -1044,11 +1064,20 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
|
|||
}
|
||||
|
||||
Misc => {
|
||||
let src = self.eval_operand(operand)?;
|
||||
// FIXME(solson): Wrong for almost everything.
|
||||
let size = dest_layout.size(&self.tcx.data_layout).bytes() as usize;
|
||||
self.memory.copy(src, dest, size)?;
|
||||
}
|
||||
|
||||
ReifyFnPointer => match self.operand_ty(operand).sty {
|
||||
ty::TyFnDef(def_id, substs, _) => {
|
||||
let fn_ptr = self.memory.create_fn_ptr(def_id, substs);
|
||||
self.memory.write_ptr(dest, fn_ptr)?;
|
||||
},
|
||||
ref other => panic!("reify fn pointer on {:?}", other),
|
||||
},
|
||||
|
||||
_ => return Err(EvalError::Unimplemented(format!("can't handle cast: {:?}", rvalue))),
|
||||
}
|
||||
}
|
||||
|
|
@ -1136,7 +1165,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
|
|||
Value { ref value } => Ok(self.const_to_ptr(value)?),
|
||||
Item { def_id, substs } => {
|
||||
if let ty::TyFnDef(..) = ty.sty {
|
||||
Err(EvalError::Unimplemented("unimplemented: mentions of function items".to_string()))
|
||||
// function items are zero sized
|
||||
Ok(self.memory.allocate(0))
|
||||
} else {
|
||||
let cid = ConstantId {
|
||||
def_id: def_id,
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for ConstantExtractor<'a, 'b, 'tcx> {
|
|||
mir::Literal::Value { .. } => {}
|
||||
mir::Literal::Item { def_id, substs } => {
|
||||
if let ty::TyFnDef(..) = constant.ty.sty {
|
||||
// No need to do anything here, even if function pointers are implemented,
|
||||
// No need to do anything here,
|
||||
// because the type is the actual function, not the signature of the function.
|
||||
// Thus we can simply create a zero sized allocation in `evaluate_operand`
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#[macro_use] extern crate rustc;
|
||||
extern crate rustc_data_structures;
|
||||
extern crate rustc_mir;
|
||||
extern crate rustc_trans;
|
||||
extern crate syntax;
|
||||
#[macro_use] extern crate log;
|
||||
extern crate log_settings;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ use std::collections::Bound::{Included, Excluded};
|
|||
use std::collections::{btree_map, BTreeMap, HashMap, HashSet, VecDeque};
|
||||
use std::{fmt, iter, mem, ptr};
|
||||
|
||||
use rustc::hir::def_id::DefId;
|
||||
use rustc::ty::subst::Substs;
|
||||
|
||||
use error::{EvalError, EvalResult};
|
||||
use primval::PrimVal;
|
||||
|
||||
|
|
@ -42,22 +45,37 @@ impl Pointer {
|
|||
// Top-level interpreter memory
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub struct Memory {
|
||||
pub struct Memory<'tcx> {
|
||||
alloc_map: HashMap<AllocId, Allocation>,
|
||||
functions: HashMap<AllocId, (DefId, &'tcx Substs<'tcx>)>,
|
||||
next_id: AllocId,
|
||||
pub pointer_size: usize,
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
impl<'tcx> Memory<'tcx> {
|
||||
// FIXME: pass tcx.data_layout (This would also allow it to use primitive type alignments to diagnose unaligned memory accesses.)
|
||||
pub fn new(pointer_size: usize) -> Self {
|
||||
Memory {
|
||||
alloc_map: HashMap::new(),
|
||||
functions: HashMap::new(),
|
||||
next_id: AllocId(0),
|
||||
pointer_size: pointer_size,
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: never create two pointers to the same def_id + substs combination
|
||||
// maybe re-use the statics cache of the gecx?
|
||||
pub fn create_fn_ptr(&mut self, def_id: DefId, substs: &'tcx Substs<'tcx>) -> Pointer {
|
||||
let id = self.next_id;
|
||||
debug!("creating fn ptr: {}", id);
|
||||
self.next_id.0 += 1;
|
||||
self.functions.insert(id, (def_id, substs));
|
||||
Pointer {
|
||||
alloc_id: id,
|
||||
offset: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allocate(&mut self, size: usize) -> Pointer {
|
||||
let alloc = Allocation {
|
||||
bytes: vec![0; size],
|
||||
|
|
@ -125,6 +143,11 @@ impl Memory {
|
|||
self.alloc_map.get_mut(&id).ok_or(EvalError::DanglingPointerDeref)
|
||||
}
|
||||
|
||||
pub fn get_fn(&self, id: AllocId) -> EvalResult<(DefId, &'tcx Substs<'tcx>)> {
|
||||
debug!("reading fn ptr: {}", id);
|
||||
self.functions.get(&id).map(|&did| did).ok_or(EvalError::InvalidFunctionPointer)
|
||||
}
|
||||
|
||||
/// Print an allocation and all allocations it points to, recursively.
|
||||
pub fn dump(&self, id: AllocId) {
|
||||
let mut allocs_seen = HashSet::new();
|
||||
|
|
@ -137,12 +160,18 @@ impl Memory {
|
|||
print!("{}", prefix);
|
||||
let mut relocations = vec![];
|
||||
|
||||
let alloc = match self.alloc_map.get(&id) {
|
||||
Some(a) => a,
|
||||
None => {
|
||||
let alloc = match (self.alloc_map.get(&id), self.functions.get(&id)) {
|
||||
(Some(a), None) => a,
|
||||
(None, Some(_)) => {
|
||||
// FIXME: print function name
|
||||
println!("function pointer");
|
||||
continue;
|
||||
},
|
||||
(None, None) => {
|
||||
println!("(deallocated)");
|
||||
continue;
|
||||
}
|
||||
},
|
||||
(Some(_), Some(_)) => unreachable!(),
|
||||
};
|
||||
|
||||
for i in 0..alloc.bytes.len() {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#![feature(custom_attribute)]
|
||||
#![allow(dead_code, unused_attributes)]
|
||||
|
||||
//error-pattern:unimplemented: mentions of function items
|
||||
//error-pattern:begin_panic_fmt
|
||||
|
||||
|
||||
#[miri_run]
|
||||
|
|
|
|||
17
tests/run-pass/function_pointers.rs
Normal file
17
tests/run-pass/function_pointers.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#![feature(custom_attribute)]
|
||||
#![allow(dead_code, unused_attributes)]
|
||||
|
||||
fn f() -> i32 {
|
||||
42
|
||||
}
|
||||
|
||||
fn return_fn_ptr() -> fn() -> i32 {
|
||||
f
|
||||
}
|
||||
|
||||
#[miri_run]
|
||||
fn call_fn_ptr() -> i32 {
|
||||
return_fn_ptr()()
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
17
tests/run-pass/zst.rs
Normal file
17
tests/run-pass/zst.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#![feature(custom_attribute)]
|
||||
#![allow(dead_code, unused_attributes)]
|
||||
|
||||
struct A;
|
||||
|
||||
#[miri_run]
|
||||
fn zst_ret() -> A {
|
||||
A
|
||||
}
|
||||
|
||||
#[miri_run]
|
||||
fn use_zst() -> A {
|
||||
let a = A;
|
||||
a
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
Loading…
Add table
Add a link
Reference in a new issue