Move all intrinsics out of interpret and fail CTFE on intrinsic calls

This commit is contained in:
Oliver Schneider 2017-07-28 13:08:27 +02:00
parent 45b7cfdb6d
commit f16b9e280b
No known key found for this signature in database
GPG key ID: A69F8D225B3AD7D9
13 changed files with 148 additions and 59 deletions

69
miri/helpers.rs Normal file
View file

@ -0,0 +1,69 @@
use rustc_miri::interpret::{
Pointer,
EvalResult, EvalError,
PrimVal,
EvalContext,
};
use rustc::ty::Ty;
pub trait EvalContextExt<'tcx> {
fn wrapping_pointer_offset(
&self,
ptr: Pointer,
pointee_ty: Ty<'tcx>,
offset: i64,
) -> EvalResult<'tcx, Pointer>;
fn pointer_offset(
&self,
ptr: Pointer,
pointee_ty: Ty<'tcx>,
offset: i64,
) -> EvalResult<'tcx, Pointer>;
}
impl<'a, 'tcx> EvalContextExt<'tcx> for EvalContext<'a, 'tcx, super::Evaluator> {
fn wrapping_pointer_offset(
&self,
ptr: Pointer,
pointee_ty: Ty<'tcx>,
offset: i64,
) -> EvalResult<'tcx, Pointer> {
// FIXME: assuming here that type size is < i64::max_value()
let pointee_size = self.type_size(pointee_ty)?.expect("cannot offset a pointer to an unsized type") as i64;
let offset = offset.overflowing_mul(pointee_size).0;
ptr.wrapping_signed_offset(offset, self)
}
fn pointer_offset(
&self,
ptr: Pointer,
pointee_ty: Ty<'tcx>,
offset: i64,
) -> EvalResult<'tcx, Pointer> {
// This function raises an error if the offset moves the pointer outside of its allocation. We consider
// ZSTs their own huge allocation that doesn't overlap with anything (and nothing moves in there because the size is 0).
// We also consider the NULL pointer its own separate allocation, and all the remaining integers pointers their own
// allocation.
if ptr.is_null()? { // NULL pointers must only be offset by 0
return if offset == 0 { Ok(ptr) } else { Err(EvalError::InvalidNullPointerUsage) };
}
// FIXME: assuming here that type size is < i64::max_value()
let pointee_size = self.type_size(pointee_ty)?.expect("cannot offset a pointer to an unsized type") as i64;
return if let Some(offset) = offset.checked_mul(pointee_size) {
let ptr = ptr.signed_offset(offset, self)?;
// Do not do bounds-checking for integers; they can never alias a normal pointer anyway.
if let PrimVal::Ptr(ptr) = ptr.into_inner_primval() {
self.memory.check_bounds(ptr, false)?;
} else if ptr.is_null()? {
// We moved *to* a NULL pointer. That seems wrong, LLVM considers the NULL pointer its own small allocation. Reject this, for now.
return Err(EvalError::InvalidNullPointerUsage);
}
Ok(ptr)
} else {
Err(EvalError::OverflowingMath)
}
}
}

View file

@ -3,17 +3,30 @@ use rustc::traits::Reveal;
use rustc::ty::layout::Layout;
use rustc::ty::{self, Ty};
use interpret::{
use rustc_miri::interpret::{
EvalError, EvalResult,
EvalContext,
Lvalue, LvalueExtra,
PrimVal, PrimValKind, Value, Pointer,
HasMemory,
Machine,
EvalContext,
};
impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
pub(super) fn call_intrinsic(
use helpers::EvalContextExt as HelperEvalContextExt;
pub trait EvalContextExt<'tcx> {
fn call_intrinsic(
&mut self,
instance: ty::Instance<'tcx>,
args: &[mir::Operand<'tcx>],
dest: Lvalue<'tcx>,
dest_ty: Ty<'tcx>,
dest_layout: &'tcx Layout,
target: mir::BasicBlock,
) -> EvalResult<'tcx>;
}
impl<'a, 'tcx> EvalContextExt<'tcx> for EvalContext<'a, 'tcx, super::Evaluator> {
fn call_intrinsic(
&mut self,
instance: ty::Instance<'tcx>,
args: &[mir::Operand<'tcx>],
@ -495,7 +508,7 @@ fn numeric_intrinsic<'tcx>(
) -> EvalResult<'tcx, PrimVal> {
macro_rules! integer_intrinsic {
($method:ident) => ({
use interpret::PrimValKind::*;
use rustc_miri::interpret::PrimValKind::*;
let result_bytes = match kind {
I8 => (bytes as i8).$method() as u128,
U8 => (bytes as u8).$method() as u128,

View file

@ -14,6 +14,7 @@ extern crate rustc_data_structures;
extern crate syntax;
use rustc::ty::{self, TyCtxt};
use rustc::ty::layout::Layout;
use rustc::hir::def_id::DefId;
use rustc::mir;
@ -29,9 +30,12 @@ pub use rustc_miri::interpret::*;
mod fn_call;
mod operator;
mod intrinsic;
mod helpers;
use fn_call::EvalContextExt as MissingFnsEvalContextExt;
use operator::EvalContextExt as OperatorEvalContextExt;
use intrinsic::EvalContextExt as IntrinsicEvalContextExt;
pub fn eval_main<'a, 'tcx: 'a>(
tcx: TyCtxt<'a, 'tcx, 'tcx>,
@ -287,6 +291,18 @@ impl<'tcx> Machine<'tcx> for Evaluator {
ecx.eval_fn_call(instance, destination, arg_operands, span, sig)
}
fn call_intrinsic<'a>(
ecx: &mut rustc_miri::interpret::EvalContext<'a, 'tcx, Self>,
instance: ty::Instance<'tcx>,
args: &[mir::Operand<'tcx>],
dest: Lvalue<'tcx>,
dest_ty: ty::Ty<'tcx>,
dest_layout: &'tcx Layout,
target: mir::BasicBlock,
) -> EvalResult<'tcx> {
ecx.call_intrinsic(instance, args, dest, dest_ty, dest_layout, target)
}
fn ptr_op<'a>(
ecx: &rustc_miri::interpret::EvalContext<'a, 'tcx, Self>,
bin_op: mir::BinOp,

View file

@ -3,6 +3,8 @@ use rustc::mir;
use rustc_miri::interpret::*;
use helpers::EvalContextExt as HelperEvalContextExt;
pub trait EvalContextExt<'tcx> {
fn ptr_op(
&self,

View file

@ -1,5 +1,5 @@
use rustc::traits::Reveal;
use rustc::ty::{self, TyCtxt, Ty, Instance};
use rustc::ty::{self, TyCtxt, Ty, Instance, layout};
use rustc::mir;
use syntax::ast::Mutability;
@ -163,6 +163,18 @@ impl<'tcx> super::Machine<'tcx> for CompileTimeFunctionEvaluator {
Ok(false)
}
fn call_intrinsic<'a>(
_ecx: &mut EvalContext<'a, 'tcx, Self>,
_instance: ty::Instance<'tcx>,
_args: &[mir::Operand<'tcx>],
_dest: Lvalue<'tcx>,
_dest_ty: Ty<'tcx>,
_dest_layout: &'tcx layout::Layout,
_target: mir::BasicBlock,
) -> EvalResult<'tcx> {
Err(ConstEvalError::NeedsRfc("calling intrinsics".to_string()).into())
}
fn ptr_op<'a>(
_ecx: &EvalContext<'a, 'tcx, Self>,
_bin_op: mir::BinOp,

View file

@ -211,7 +211,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
false
}
pub(crate) fn str_to_value(&mut self, s: &str) -> EvalResult<'tcx, Value> {
pub fn str_to_value(&mut self, s: &str) -> EvalResult<'tcx, Value> {
let ptr = self.memory.allocate_cached(s.as_bytes())?;
Ok(Value::ByValPair(PrimVal::Ptr(ptr), PrimVal::from_u128(s.len() as u128)))
}
@ -369,11 +369,11 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
self.tcx.normalize_associated_type(&f.ty(self.tcx, param_substs))
}
pub(super) fn type_size(&self, ty: Ty<'tcx>) -> EvalResult<'tcx, Option<u64>> {
pub fn type_size(&self, ty: Ty<'tcx>) -> EvalResult<'tcx, Option<u64>> {
self.type_size_with_substs(ty, self.substs())
}
pub(super) fn type_align(&self, ty: Ty<'tcx>) -> EvalResult<'tcx, u64> {
pub fn type_align(&self, ty: Ty<'tcx>) -> EvalResult<'tcx, u64> {
self.type_align_with_substs(ty, self.substs())
}
@ -1022,39 +1022,6 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
}
}
pub(super) fn wrapping_pointer_offset(&self, ptr: Pointer, pointee_ty: Ty<'tcx>, offset: i64) -> EvalResult<'tcx, Pointer> {
// FIXME: assuming here that type size is < i64::max_value()
let pointee_size = self.type_size(pointee_ty)?.expect("cannot offset a pointer to an unsized type") as i64;
let offset = offset.overflowing_mul(pointee_size).0;
ptr.wrapping_signed_offset(offset, self)
}
pub fn pointer_offset(&self, ptr: Pointer, pointee_ty: Ty<'tcx>, offset: i64) -> EvalResult<'tcx, Pointer> {
// This function raises an error if the offset moves the pointer outside of its allocation. We consider
// ZSTs their own huge allocation that doesn't overlap with anything (and nothing moves in there because the size is 0).
// We also consider the NULL pointer its own separate allocation, and all the remaining integers pointers their own
// allocation.
if ptr.is_null()? { // NULL pointers must only be offset by 0
return if offset == 0 { Ok(ptr) } else { Err(EvalError::InvalidNullPointerUsage) };
}
// FIXME: assuming here that type size is < i64::max_value()
let pointee_size = self.type_size(pointee_ty)?.expect("cannot offset a pointer to an unsized type") as i64;
return if let Some(offset) = offset.checked_mul(pointee_size) {
let ptr = ptr.signed_offset(offset, self)?;
// Do not do bounds-checking for integers; they can never alias a normal pointer anyway.
if let PrimVal::Ptr(ptr) = ptr.into_inner_primval() {
self.memory.check_bounds(ptr, false)?;
} else if ptr.is_null()? {
// We moved *to* a NULL pointer. That seems wrong, LLVM considers the NULL pointer its own small allocation. Reject this, for now.
return Err(EvalError::InvalidNullPointerUsage);
}
Ok(ptr)
} else {
Err(EvalError::OverflowingMath)
}
}
pub(super) fn eval_operand_to_primval(&mut self, op: &mir::Operand<'tcx>) -> EvalResult<'tcx, PrimVal> {
let value = self.eval_operand(op)?;
let ty = self.operand_ty(op);
@ -1103,7 +1070,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
Ok(())
}
pub(super) fn force_allocation(
pub fn force_allocation(
&mut self,
lvalue: Lvalue<'tcx>,
) -> EvalResult<'tcx, Lvalue<'tcx>> {
@ -1297,7 +1264,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
Ok(())
}
pub(super) fn write_value_to_ptr(
pub fn write_value_to_ptr(
&mut self,
value: Value,
dest: Pointer,
@ -1315,7 +1282,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
}
}
pub(super) fn write_pair_to_ptr(
pub fn write_pair_to_ptr(
&mut self,
a: PrimVal,
b: PrimVal,
@ -1445,7 +1412,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
}
}
pub(super) fn read_value(&self, ptr: Pointer, ty: Ty<'tcx>) -> EvalResult<'tcx, Value> {
pub fn read_value(&self, ptr: Pointer, ty: Ty<'tcx>) -> EvalResult<'tcx, Value> {
if let Some(val) = self.try_read_value(ptr, ty)? {
Ok(val)
} else {

View file

@ -73,7 +73,7 @@ impl<'tcx> Lvalue<'tcx> {
Self::from_primval_ptr(PrimVal::Undef.into())
}
pub(crate) fn from_primval_ptr(ptr: Pointer) -> Self {
pub fn from_primval_ptr(ptr: Pointer) -> Self {
Lvalue::Ptr { ptr, extra: LvalueExtra::None, aligned: true }
}
@ -89,7 +89,7 @@ impl<'tcx> Lvalue<'tcx> {
}
}
pub(super) fn to_ptr(self) -> EvalResult<'tcx, MemoryPointer> {
pub fn to_ptr(self) -> EvalResult<'tcx, MemoryPointer> {
let (ptr, extra, _aligned) = self.to_ptr_extra_aligned();
// At this point, we forget about the alignment information -- the lvalue has been turned into a reference,
// and no matter where it came from, it now must be aligned.

View file

@ -36,6 +36,17 @@ pub trait Machine<'tcx>: Sized {
sig: ty::FnSig<'tcx>,
) -> EvalResult<'tcx, bool>;
/// directly process an intrinsic without pushing a stack frame.
fn call_intrinsic<'a>(
ecx: &mut EvalContext<'a, 'tcx, Self>,
instance: ty::Instance<'tcx>,
args: &[mir::Operand<'tcx>],
dest: Lvalue<'tcx>,
dest_ty: ty::Ty<'tcx>,
dest_layout: &'tcx ty::layout::Layout,
target: mir::BasicBlock,
) -> EvalResult<'tcx>;
/// Called when operating on the value of pointers.
///
/// Returns `None` if the operation should be handled by the integer

View file

@ -434,7 +434,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> Memory<'a, 'tcx, M> {
}
}
pub(crate) fn check_bounds(&self, ptr: MemoryPointer, access: bool) -> EvalResult<'tcx> {
pub fn check_bounds(&self, ptr: MemoryPointer, access: bool) -> EvalResult<'tcx> {
let alloc = self.get(ptr.alloc_id)?;
let allocation_size = alloc.bytes.len() as u64;
if ptr.offset > allocation_size {
@ -1311,7 +1311,7 @@ fn bit_index(bits: u64) -> (usize, usize) {
// Unaligned accesses
////////////////////////////////////////////////////////////////////////////////
pub(crate) trait HasMemory<'a, 'tcx, M: Machine<'tcx>> {
pub trait HasMemory<'a, 'tcx, M: Machine<'tcx>> {
fn memory_mut(&mut self) -> &mut Memory<'a, 'tcx, M>;
fn memory(&self) -> &Memory<'a, 'tcx, M>;

View file

@ -40,10 +40,10 @@ pub use self::memory::{
Memory,
MemoryPointer,
Kind,
HasMemory,
};
use self::memory::{
HasMemory,
PointerArithmetic,
LockInfo,
AccessKind,

View file

@ -34,7 +34,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
/// Applies the binary operation `op` to the two operands and writes a tuple of the result
/// and a boolean signifying the potential overflow to the destination.
pub(super) fn intrinsic_with_overflow(
pub fn intrinsic_with_overflow(
&mut self,
op: mir::BinOp,
left: &mir::Operand<'tcx>,
@ -49,7 +49,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
/// Applies the binary operation `op` to the arguments and writes the result to the
/// destination. Returns `true` if the operation overflowed.
pub(super) fn intrinsic_overflowing(
pub fn intrinsic_overflowing(
&mut self,
op: mir::BinOp,
left: &mir::Operand<'tcx>,

View file

@ -18,7 +18,6 @@ use super::eval_context::IntegerExt;
use rustc_data_structures::indexed_vec::Idx;
mod drop;
mod intrinsic;
impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
pub fn goto_block(&mut self, target: mir::BasicBlock) {
@ -222,7 +221,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
return Err(EvalError::Unreachable);
}
let layout = self.type_layout(ty)?;
self.call_intrinsic(instance, arg_operands, ret, ty, layout, target)?;
M::call_intrinsic(self, instance, arg_operands, ret, ty, layout, target)?;
self.dump_local(ret);
Ok(())
},

View file

@ -64,7 +64,7 @@ impl<'tcx> Pointer {
self.primval
}
pub(crate) fn signed_offset<C: HasDataLayout>(self, i: i64, cx: C) -> EvalResult<'tcx, Self> {
pub fn signed_offset<C: HasDataLayout>(self, i: i64, cx: C) -> EvalResult<'tcx, Self> {
let layout = cx.data_layout();
match self.primval {
PrimVal::Bytes(b) => {
@ -88,7 +88,7 @@ impl<'tcx> Pointer {
}
}
pub(crate) fn wrapping_signed_offset<C: HasDataLayout>(self, i: i64, cx: C) -> EvalResult<'tcx, Self> {
pub fn wrapping_signed_offset<C: HasDataLayout>(self, i: i64, cx: C) -> EvalResult<'tcx, Self> {
let layout = cx.data_layout();
match self.primval {
PrimVal::Bytes(b) => {
@ -165,7 +165,7 @@ pub enum PrimValKind {
impl<'a, 'tcx: 'a> Value {
#[inline]
pub(super) fn by_ref(ptr: Pointer) -> Self {
pub fn by_ref(ptr: Pointer) -> Self {
Value::ByRef { ptr, aligned: true }
}