Move more non-CTFE operations to the Machine
This commit is contained in:
parent
dc6e877ead
commit
4a4640a331
9 changed files with 282 additions and 153 deletions
17
miri/lib.rs
17
miri/lib.rs
|
@ -7,6 +7,7 @@
|
|||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate log_settings;
|
||||
#[macro_use]
|
||||
extern crate rustc;
|
||||
extern crate rustc_const_math;
|
||||
extern crate rustc_data_structures;
|
||||
|
@ -25,8 +26,10 @@ extern crate rustc_miri;
|
|||
pub use rustc_miri::interpret::*;
|
||||
|
||||
mod missing_fns;
|
||||
mod operator;
|
||||
|
||||
use missing_fns::EvalContextExt as MissingFnsEvalContextExt;
|
||||
use operator::EvalContextExt as OperatorEvalContextExt;
|
||||
|
||||
pub fn eval_main<'a, 'tcx: 'a>(
|
||||
tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
|
@ -280,4 +283,18 @@ impl<'tcx> Machine<'tcx> for Evaluator {
|
|||
) -> EvalResult<'tcx> {
|
||||
ecx.call_missing_fn(instance, destination, arg_operands, sig, path)
|
||||
}
|
||||
fn ptr_op<'a>(
|
||||
ecx: &rustc_miri::interpret::EvalContext<'a, 'tcx, Self>,
|
||||
bin_op: mir::BinOp,
|
||||
left: PrimVal,
|
||||
left_ty: ty::Ty<'tcx>,
|
||||
right: PrimVal,
|
||||
right_ty: ty::Ty<'tcx>,
|
||||
) -> EvalResult<'tcx, Option<(PrimVal, bool)>> {
|
||||
ecx.ptr_op(bin_op, left, left_ty, right, right_ty)
|
||||
}
|
||||
|
||||
fn check_non_const_fn_call(_instance: ty::Instance<'tcx>) -> EvalResult<'tcx> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
152
miri/operator.rs
Normal file
152
miri/operator.rs
Normal file
|
@ -0,0 +1,152 @@
|
|||
use rustc::ty;
|
||||
use rustc::mir;
|
||||
|
||||
use rustc_miri::interpret::*;
|
||||
|
||||
pub trait EvalContextExt<'tcx> {
|
||||
fn ptr_op(
|
||||
&self,
|
||||
bin_op: mir::BinOp,
|
||||
left: PrimVal,
|
||||
left_ty: ty::Ty<'tcx>,
|
||||
right: PrimVal,
|
||||
right_ty: ty::Ty<'tcx>,
|
||||
) -> EvalResult<'tcx, Option<(PrimVal, bool)>>;
|
||||
|
||||
fn ptr_int_arithmetic(
|
||||
&self,
|
||||
bin_op: mir::BinOp,
|
||||
left: MemoryPointer,
|
||||
right: i128,
|
||||
signed: bool,
|
||||
) -> EvalResult<'tcx, (PrimVal, bool)>;
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> EvalContextExt<'tcx> for EvalContext<'a, 'tcx, super::Evaluator> {
|
||||
fn ptr_op(
|
||||
&self,
|
||||
bin_op: mir::BinOp,
|
||||
left: PrimVal,
|
||||
left_ty: ty::Ty<'tcx>,
|
||||
right: PrimVal,
|
||||
right_ty: ty::Ty<'tcx>,
|
||||
) -> EvalResult<'tcx, Option<(PrimVal, bool)>> {
|
||||
use rustc_miri::interpret::PrimValKind::*;
|
||||
use rustc::mir::BinOp::*;
|
||||
let usize = PrimValKind::from_uint_size(self.memory.pointer_size());
|
||||
let isize = PrimValKind::from_int_size(self.memory.pointer_size());
|
||||
let left_kind = self.ty_to_primval_kind(left_ty)?;
|
||||
let right_kind = self.ty_to_primval_kind(right_ty)?;
|
||||
match bin_op {
|
||||
Offset if left_kind == Ptr && right_kind == usize => {
|
||||
let pointee_ty = left_ty.builtin_deref(true, ty::LvaluePreference::NoPreference).expect("Offset called on non-ptr type").ty;
|
||||
let ptr = self.pointer_offset(left.into(), pointee_ty, right.to_bytes()? as i64)?;
|
||||
Ok(Some((ptr.into_inner_primval(), false)))
|
||||
},
|
||||
// These work on anything
|
||||
Eq if left_kind == right_kind => {
|
||||
let result = match (left, right) {
|
||||
(PrimVal::Bytes(left), PrimVal::Bytes(right)) => left == right,
|
||||
(PrimVal::Ptr(left), PrimVal::Ptr(right)) => left == right,
|
||||
(PrimVal::Undef, _) | (_, PrimVal::Undef) => return Err(EvalError::ReadUndefBytes),
|
||||
_ => false,
|
||||
};
|
||||
Ok(Some((PrimVal::from_bool(result), false)))
|
||||
}
|
||||
Ne if left_kind == right_kind => {
|
||||
let result = match (left, right) {
|
||||
(PrimVal::Bytes(left), PrimVal::Bytes(right)) => left != right,
|
||||
(PrimVal::Ptr(left), PrimVal::Ptr(right)) => left != right,
|
||||
(PrimVal::Undef, _) | (_, PrimVal::Undef) => return Err(EvalError::ReadUndefBytes),
|
||||
_ => true,
|
||||
};
|
||||
Ok(Some((PrimVal::from_bool(result), false)))
|
||||
}
|
||||
// These need both pointers to be in the same allocation
|
||||
Lt | Le | Gt | Ge | Sub
|
||||
if left_kind == right_kind
|
||||
&& (left_kind == Ptr || left_kind == usize || left_kind == isize)
|
||||
&& left.is_ptr() && right.is_ptr() => {
|
||||
let left = left.to_ptr()?;
|
||||
let right = right.to_ptr()?;
|
||||
if left.alloc_id == right.alloc_id {
|
||||
let res = match bin_op {
|
||||
Lt => left.offset < right.offset,
|
||||
Le => left.offset <= right.offset,
|
||||
Gt => left.offset > right.offset,
|
||||
Ge => left.offset >= right.offset,
|
||||
Sub => return self.binary_op(
|
||||
Sub,
|
||||
PrimVal::Bytes(left.offset as u128),
|
||||
self.tcx.types.usize,
|
||||
PrimVal::Bytes(right.offset as u128),
|
||||
self.tcx.types.usize,
|
||||
).map(Some),
|
||||
_ => bug!("We already established it has to be one of these operators."),
|
||||
};
|
||||
Ok(Some((PrimVal::from_bool(res), false)))
|
||||
} else {
|
||||
// Both are pointers, but from different allocations.
|
||||
Err(EvalError::InvalidPointerMath)
|
||||
}
|
||||
}
|
||||
// These work if one operand is a pointer, the other an integer
|
||||
Add | BitAnd | Sub
|
||||
if left_kind == right_kind && (left_kind == usize || left_kind == isize)
|
||||
&& left.is_ptr() && right.is_bytes() => {
|
||||
// Cast to i128 is fine as we checked the kind to be ptr-sized
|
||||
self.ptr_int_arithmetic(bin_op, left.to_ptr()?, right.to_bytes()? as i128, left_kind == isize).map(Some)
|
||||
}
|
||||
Add | BitAnd
|
||||
if left_kind == right_kind && (left_kind == usize || left_kind == isize)
|
||||
&& left.is_bytes() && right.is_ptr() => {
|
||||
// This is a commutative operation, just swap the operands
|
||||
self.ptr_int_arithmetic(bin_op, right.to_ptr()?, left.to_bytes()? as i128, left_kind == isize).map(Some)
|
||||
}
|
||||
_ => Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn ptr_int_arithmetic(
|
||||
&self,
|
||||
bin_op: mir::BinOp,
|
||||
left: MemoryPointer,
|
||||
right: i128,
|
||||
signed: bool,
|
||||
) -> EvalResult<'tcx, (PrimVal, bool)> {
|
||||
use rustc::mir::BinOp::*;
|
||||
|
||||
fn map_to_primval((res, over) : (MemoryPointer, bool)) -> (PrimVal, bool) {
|
||||
(PrimVal::Ptr(res), over)
|
||||
}
|
||||
|
||||
Ok(match bin_op {
|
||||
Sub =>
|
||||
// The only way this can overflow is by underflowing, so signdeness of the right operands does not matter
|
||||
map_to_primval(left.overflowing_signed_offset(-right, self)),
|
||||
Add if signed =>
|
||||
map_to_primval(left.overflowing_signed_offset(right, self)),
|
||||
Add if !signed =>
|
||||
map_to_primval(left.overflowing_offset(right as u64, self)),
|
||||
|
||||
BitAnd if !signed => {
|
||||
let base_mask : u64 = !(self.memory.get(left.alloc_id)?.align - 1);
|
||||
let right = right as u64;
|
||||
if right & base_mask == base_mask {
|
||||
// Case 1: The base address bits are all preserved, i.e., right is all-1 there
|
||||
(PrimVal::Ptr(MemoryPointer::new(left.alloc_id, left.offset & right)), false)
|
||||
} else if right & base_mask == 0 {
|
||||
// Case 2: The base address bits are all taken away, i.e., right is all-0 there
|
||||
(PrimVal::from_u128((left.offset & right) as u128), false)
|
||||
} else {
|
||||
return Err(EvalError::ReadPointerAsBytes);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
let msg = format!("unimplemented binary op on pointer {:?}: {:?}, {:?} ({})", bin_op, left, right, if signed { "signed" } else { "unsigned" });
|
||||
return Err(EvalError::Unimplemented(msg));
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ use rustc::mir;
|
|||
use syntax::ast::Mutability;
|
||||
|
||||
use super::{
|
||||
EvalError, EvalResult,
|
||||
EvalResult, EvalError,
|
||||
Global, GlobalId, Lvalue,
|
||||
PrimVal,
|
||||
EvalContext, StackPopCleanup,
|
||||
|
@ -13,6 +13,9 @@ use super::{
|
|||
|
||||
use rustc_const_math::ConstInt;
|
||||
|
||||
use std::fmt;
|
||||
use std::error::Error;
|
||||
|
||||
pub fn eval_body_as_primval<'a, 'tcx>(
|
||||
tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
instance: Instance<'tcx>,
|
||||
|
@ -21,7 +24,7 @@ pub fn eval_body_as_primval<'a, 'tcx>(
|
|||
let mut ecx = EvalContext::<Evaluator>::new(tcx, limits, (), ());
|
||||
let cid = GlobalId { instance, promoted: None };
|
||||
if ecx.tcx.has_attr(instance.def_id(), "linkage") {
|
||||
return Err(EvalError::NotConst("extern global".to_string()));
|
||||
return Err(ConstEvalError::NotConst("extern global".to_string()).into());
|
||||
}
|
||||
|
||||
let mir = ecx.load_mir(instance.def)?;
|
||||
|
@ -75,12 +78,52 @@ pub fn eval_body_as_integer<'a, 'tcx>(
|
|||
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.uint_type).expect("miri should already have errored")),
|
||||
_ => return Err(EvalError::NeedsRfc("evaluating anything other than isize/usize during typeck".to_string())),
|
||||
_ => return Err(ConstEvalError::NeedsRfc("evaluating anything other than isize/usize during typeck".to_string()).into()),
|
||||
})
|
||||
}
|
||||
|
||||
struct Evaluator;
|
||||
|
||||
impl<'tcx> Into<EvalError<'tcx>> for ConstEvalError {
|
||||
fn into(self) -> EvalError<'tcx> {
|
||||
EvalError::MachineError(Box::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
#[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 Evaluator {
|
||||
type Data = ();
|
||||
type MemoryData = ();
|
||||
|
@ -93,6 +136,21 @@ impl<'tcx> super::Machine<'tcx> for Evaluator {
|
|||
path: String,
|
||||
) -> EvalResult<'tcx> {
|
||||
// some simple things like `malloc` might get accepted in the future
|
||||
Err(EvalError::NeedsRfc(format!("calling extern function `{}`", path)))
|
||||
Err(ConstEvalError::NeedsRfc(format!("calling extern function `{}`", path)).into())
|
||||
}
|
||||
|
||||
fn 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)>> {
|
||||
Err(ConstEvalError::NeedsRfc("Pointer arithmetic or comparison".to_string()).into())
|
||||
}
|
||||
|
||||
fn check_non_const_fn_call(instance: ty::Instance<'tcx>) -> EvalResult<'tcx> {
|
||||
return Err(ConstEvalError::NotConst(format!("calling non-const fn `{}`", instance)).into());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,8 +10,11 @@ use super::{
|
|||
use rustc_const_math::ConstMathErr;
|
||||
use syntax::codemap::Span;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub enum EvalError<'tcx> {
|
||||
/// This variant is used by machines to signal their own errors that do not
|
||||
/// match an existing variant
|
||||
MachineError(Box<Error>),
|
||||
FunctionPointerTyMismatch(FnSig<'tcx>, FnSig<'tcx>),
|
||||
NoMirFor(String),
|
||||
UnterminatedCString(MemoryPointer),
|
||||
|
@ -95,8 +98,6 @@ pub enum EvalError<'tcx> {
|
|||
HeapAllocNonPowerOfTwoAlignment(u64),
|
||||
Unreachable,
|
||||
Panic,
|
||||
NeedsRfc(String),
|
||||
NotConst(String),
|
||||
ReadFromReturnPointer,
|
||||
PathNotFound(Vec<String>),
|
||||
}
|
||||
|
@ -107,6 +108,7 @@ impl<'tcx> Error for EvalError<'tcx> {
|
|||
fn description(&self) -> &str {
|
||||
use self::EvalError::*;
|
||||
match *self {
|
||||
MachineError(ref inner) => inner.description(),
|
||||
FunctionPointerTyMismatch(..) =>
|
||||
"tried to call a function through a function pointer of a different type",
|
||||
InvalidMemoryAccess =>
|
||||
|
@ -211,10 +213,6 @@ impl<'tcx> Error for EvalError<'tcx> {
|
|||
"entered unreachable code",
|
||||
Panic =>
|
||||
"the evaluated program panicked",
|
||||
NeedsRfc(_) =>
|
||||
"this feature needs an rfc before being allowed inside constants",
|
||||
NotConst(_) =>
|
||||
"this feature is not compatible with constant evaluation",
|
||||
ReadFromReturnPointer =>
|
||||
"tried to read from the return pointer",
|
||||
EvalError::PathNotFound(_) =>
|
||||
|
@ -222,7 +220,13 @@ impl<'tcx> Error for EvalError<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&Error> { None }
|
||||
fn cause(&self) -> Option<&Error> {
|
||||
use self::EvalError::*;
|
||||
match *self {
|
||||
MachineError(ref inner) => Some(&**inner),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> fmt::Display for EvalError<'tcx> {
|
||||
|
@ -278,12 +282,10 @@ impl<'tcx> fmt::Display for EvalError<'tcx> {
|
|||
write!(f, "expected primitive type, got {}", ty),
|
||||
Layout(ref err) =>
|
||||
write!(f, "rustc layout computation failed: {:?}", err),
|
||||
NeedsRfc(ref msg) =>
|
||||
write!(f, "\"{}\" needs an rfc before being allowed inside constants", msg),
|
||||
NotConst(ref msg) =>
|
||||
write!(f, "Cannot evaluate within constants: \"{}\"", msg),
|
||||
EvalError::PathNotFound(ref path) =>
|
||||
PathNotFound(ref path) =>
|
||||
write!(f, "Cannot find path {:?}", path),
|
||||
MachineError(ref inner) =>
|
||||
write!(f, "machine error: {}", inner),
|
||||
_ => write!(f, "{}", self.description()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -666,9 +666,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
|
|||
}
|
||||
|
||||
Len(ref lvalue) => {
|
||||
if self.const_env() {
|
||||
return Err(EvalError::NeedsRfc("computing the length of arrays".to_string()));
|
||||
}
|
||||
// FIXME(CTFE): don't allow computing the length of arrays in const eval
|
||||
let src = self.eval_lvalue(lvalue)?;
|
||||
let ty = self.lvalue_ty(lvalue);
|
||||
let (_, len) = src.elem_ty_and_len(ty);
|
||||
|
@ -692,9 +690,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
|
|||
}
|
||||
|
||||
NullaryOp(mir::NullOp::Box, ty) => {
|
||||
if self.const_env() {
|
||||
return Err(EvalError::NeedsRfc("\"heap\" allocations".to_string()));
|
||||
}
|
||||
// FIXME(CTFE): don't allow heap allocations in const eval
|
||||
// FIXME: call the `exchange_malloc` lang item if available
|
||||
let size = self.type_size(ty)?.expect("box only works with sized types");
|
||||
if size == 0 {
|
||||
|
@ -708,9 +704,6 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
|
|||
}
|
||||
|
||||
NullaryOp(mir::NullOp::SizeOf, ty) => {
|
||||
if self.const_env() {
|
||||
return Err(EvalError::NeedsRfc("computing the size of types (size_of)".to_string()));
|
||||
}
|
||||
let size = self.type_size(ty)?.expect("SizeOf nullary MIR operator called for unsized type");
|
||||
self.write_primval(dest, PrimVal::from_u128(size as u128), dest_ty)?;
|
||||
}
|
||||
|
@ -944,7 +937,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
|
|||
ptr.wrapping_signed_offset(offset, self)
|
||||
}
|
||||
|
||||
pub(super) fn pointer_offset(&self, ptr: Pointer, pointee_ty: Ty<'tcx>, offset: i64) -> EvalResult<'tcx, Pointer> {
|
||||
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
|
||||
|
|
|
@ -6,6 +6,7 @@ use super::{
|
|||
EvalResult,
|
||||
EvalContext,
|
||||
Lvalue,
|
||||
PrimVal
|
||||
};
|
||||
|
||||
use rustc::{mir, ty};
|
||||
|
@ -29,5 +30,25 @@ pub trait Machine<'tcx>: Sized {
|
|||
sig: ty::FnSig<'tcx>,
|
||||
path: String,
|
||||
) -> EvalResult<'tcx>;
|
||||
|
||||
/// Called when operating on the value of pointers.
|
||||
///
|
||||
/// Returns `None` if the operation should be handled by the integer
|
||||
/// op code
|
||||
///
|
||||
/// Returns a (value, overflowed) pair otherwise
|
||||
fn ptr_op<'a>(
|
||||
ecx: &EvalContext<'a, 'tcx, Self>,
|
||||
bin_op: mir::BinOp,
|
||||
left: PrimVal,
|
||||
left_ty: ty::Ty<'tcx>,
|
||||
right: PrimVal,
|
||||
right_ty: ty::Ty<'tcx>,
|
||||
) -> EvalResult<'tcx, Option<(PrimVal, bool)>>;
|
||||
|
||||
/// Called when adding a frame for a function that's not `const fn`
|
||||
///
|
||||
/// Const eval returns `Err`, miri returns `Ok`
|
||||
fn check_non_const_fn_call(instance: ty::Instance<'tcx>) -> EvalResult<'tcx>;
|
||||
}
|
||||
|
||||
|
|
|
@ -198,7 +198,7 @@ impl<'tcx> MemoryPointer {
|
|||
MemoryPointer::new(self.alloc_id, cx.data_layout().wrapping_signed_offset(self.offset, i))
|
||||
}
|
||||
|
||||
pub(crate) fn overflowing_signed_offset<C: HasDataLayout>(self, i: i128, cx: C) -> (Self, bool) {
|
||||
pub fn overflowing_signed_offset<C: HasDataLayout>(self, i: i128, cx: C) -> (Self, bool) {
|
||||
let (res, over) = cx.data_layout().overflowing_signed_offset(self.offset, i);
|
||||
(MemoryPointer::new(self.alloc_id, res), over)
|
||||
}
|
||||
|
@ -207,7 +207,7 @@ impl<'tcx> MemoryPointer {
|
|||
Ok(MemoryPointer::new(self.alloc_id, cx.data_layout().signed_offset(self.offset, i)?))
|
||||
}
|
||||
|
||||
pub(crate) fn overflowing_offset<C: HasDataLayout>(self, i: u64, cx: C) -> (Self, bool) {
|
||||
pub fn overflowing_offset<C: HasDataLayout>(self, i: u64, cx: C) -> (Self, bool) {
|
||||
let (res, over) = cx.data_layout().overflowing_offset(self.offset, i);
|
||||
(MemoryPointer::new(self.alloc_id, res), over)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
use rustc::mir;
|
||||
use rustc::ty::{self, Ty};
|
||||
use rustc::ty::Ty;
|
||||
|
||||
use super::{
|
||||
EvalError, EvalResult,
|
||||
EvalContext,
|
||||
MemoryPointer,
|
||||
Lvalue,
|
||||
Machine,
|
||||
};
|
||||
|
@ -153,75 +152,9 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
|
|||
//trace!("Running binary op {:?}: {:?} ({:?}), {:?} ({:?})", bin_op, left, left_kind, right, right_kind);
|
||||
|
||||
// I: Handle operations that support pointers
|
||||
let usize = PrimValKind::from_uint_size(self.memory.pointer_size());
|
||||
let isize = PrimValKind::from_int_size(self.memory.pointer_size());
|
||||
if !left_kind.is_float() && !right_kind.is_float() {
|
||||
if (!left.is_bytes() && !right.is_bytes()) && self.const_env() {
|
||||
return Err(EvalError::NeedsRfc("Pointer arithmetic or comparison".to_string()));
|
||||
}
|
||||
match bin_op {
|
||||
Offset if left_kind == Ptr && right_kind == usize => {
|
||||
let pointee_ty = left_ty.builtin_deref(true, ty::LvaluePreference::NoPreference).expect("Offset called on non-ptr type").ty;
|
||||
let ptr = self.pointer_offset(left.into(), pointee_ty, right.to_bytes()? as i64)?;
|
||||
return Ok((ptr.into_inner_primval(), false));
|
||||
},
|
||||
// These work on anything
|
||||
Eq if left_kind == right_kind => {
|
||||
let result = match (left, right) {
|
||||
(PrimVal::Bytes(left), PrimVal::Bytes(right)) => left == right,
|
||||
(PrimVal::Ptr(left), PrimVal::Ptr(right)) => left == right,
|
||||
(PrimVal::Undef, _) | (_, PrimVal::Undef) => return Err(EvalError::ReadUndefBytes),
|
||||
_ => false,
|
||||
};
|
||||
return Ok((PrimVal::from_bool(result), false));
|
||||
}
|
||||
Ne if left_kind == right_kind => {
|
||||
let result = match (left, right) {
|
||||
(PrimVal::Bytes(left), PrimVal::Bytes(right)) => left != right,
|
||||
(PrimVal::Ptr(left), PrimVal::Ptr(right)) => left != right,
|
||||
(PrimVal::Undef, _) | (_, PrimVal::Undef) => return Err(EvalError::ReadUndefBytes),
|
||||
_ => true,
|
||||
};
|
||||
return Ok((PrimVal::from_bool(result), false));
|
||||
}
|
||||
// These need both pointers to be in the same allocation
|
||||
Lt | Le | Gt | Ge | Sub
|
||||
if left_kind == right_kind
|
||||
&& (left_kind == Ptr || left_kind == usize || left_kind == isize)
|
||||
&& left.is_ptr() && right.is_ptr() => {
|
||||
let left = left.to_ptr()?;
|
||||
let right = right.to_ptr()?;
|
||||
if left.alloc_id == right.alloc_id {
|
||||
let res = match bin_op {
|
||||
Lt => left.offset < right.offset,
|
||||
Le => left.offset <= right.offset,
|
||||
Gt => left.offset > right.offset,
|
||||
Ge => left.offset >= right.offset,
|
||||
Sub => {
|
||||
return int_arithmetic!(left_kind, overflowing_sub, left.offset, right.offset);
|
||||
}
|
||||
_ => bug!("We already established it has to be one of these operators."),
|
||||
};
|
||||
return Ok((PrimVal::from_bool(res), false));
|
||||
} else {
|
||||
// Both are pointers, but from different allocations.
|
||||
return Err(EvalError::InvalidPointerMath);
|
||||
}
|
||||
}
|
||||
// These work if one operand is a pointer, the other an integer
|
||||
Add | BitAnd | Sub
|
||||
if left_kind == right_kind && (left_kind == usize || left_kind == isize)
|
||||
&& left.is_ptr() && right.is_bytes() => {
|
||||
// Cast to i128 is fine as we checked the kind to be ptr-sized
|
||||
return self.ptr_int_arithmetic(bin_op, left.to_ptr()?, right.to_bytes()? as i128, left_kind == isize);
|
||||
}
|
||||
Add | BitAnd
|
||||
if left_kind == right_kind && (left_kind == usize || left_kind == isize)
|
||||
&& left.is_bytes() && right.is_ptr() => {
|
||||
// This is a commutative operation, just swap the operands
|
||||
return self.ptr_int_arithmetic(bin_op, right.to_ptr()?, left.to_bytes()? as i128, left_kind == isize);
|
||||
}
|
||||
_ => {}
|
||||
if let Some(handled) = M::ptr_op(self, bin_op, left, left_ty, right, right_ty)? {
|
||||
return Ok(handled);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,6 +203,9 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
|
|||
(Div, F64) => f64_arithmetic!(/, l, r),
|
||||
(Rem, F64) => f64_arithmetic!(%, l, r),
|
||||
|
||||
(Eq, _) => PrimVal::from_bool(l == r),
|
||||
(Ne, _) => PrimVal::from_bool(l != r),
|
||||
|
||||
(Lt, k) if k.is_signed_int() => PrimVal::from_bool((l as i128) < (r as i128)),
|
||||
(Lt, _) => PrimVal::from_bool(l < r),
|
||||
(Le, k) if k.is_signed_int() => PrimVal::from_bool((l as i128) <= (r as i128)),
|
||||
|
@ -297,49 +233,6 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
|
|||
|
||||
Ok((val, false))
|
||||
}
|
||||
|
||||
fn ptr_int_arithmetic(
|
||||
&self,
|
||||
bin_op: mir::BinOp,
|
||||
left: MemoryPointer,
|
||||
right: i128,
|
||||
signed: bool,
|
||||
) -> EvalResult<'tcx, (PrimVal, bool)> {
|
||||
use rustc::mir::BinOp::*;
|
||||
|
||||
fn map_to_primval((res, over) : (MemoryPointer, bool)) -> (PrimVal, bool) {
|
||||
(PrimVal::Ptr(res), over)
|
||||
}
|
||||
|
||||
Ok(match bin_op {
|
||||
Sub =>
|
||||
// The only way this can overflow is by underflowing, so signdeness of the right operands does not matter
|
||||
map_to_primval(left.overflowing_signed_offset(-right, self)),
|
||||
Add if signed =>
|
||||
map_to_primval(left.overflowing_signed_offset(right, self)),
|
||||
Add if !signed =>
|
||||
map_to_primval(left.overflowing_offset(right as u64, self)),
|
||||
|
||||
BitAnd if !signed => {
|
||||
let base_mask : u64 = !(self.memory.get(left.alloc_id)?.align - 1);
|
||||
let right = right as u64;
|
||||
if right & base_mask == base_mask {
|
||||
// Case 1: The base address bits are all preserved, i.e., right is all-1 there
|
||||
(PrimVal::Ptr(MemoryPointer::new(left.alloc_id, left.offset & right)), false)
|
||||
} else if right & base_mask == 0 {
|
||||
// Case 2: The base address bits are all taken away, i.e., right is all-0 there
|
||||
(PrimVal::from_u128((left.offset & right) as u128), false)
|
||||
} else {
|
||||
return Err(EvalError::ReadPointerAsBytes);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
let msg = format!("unimplemented binary op on pointer {:?}: {:?}, {:?} ({})", bin_op, left, right, if signed { "signed" } else { "unsigned" });
|
||||
return Err(EvalError::Unimplemented(msg));
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unary_op<'tcx>(
|
||||
|
|
|
@ -40,9 +40,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
|
|||
Goto { target } => self.goto_block(target),
|
||||
|
||||
SwitchInt { ref discr, ref values, ref targets, .. } => {
|
||||
if self.const_env() {
|
||||
return Err(EvalError::NeedsRfc("branching (if, match, loop, ...)".to_string()));
|
||||
}
|
||||
// FIXME(CTFE): forbid branching
|
||||
let discr_val = self.eval_operand(discr)?;
|
||||
let discr_ty = self.operand_ty(discr);
|
||||
let discr_prim = self.value_to_primval(discr_val, discr_ty)?;
|
||||
|
@ -100,9 +98,7 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
|
|||
|
||||
Drop { ref location, target, .. } => {
|
||||
trace!("TerminatorKind::drop: {:?}, {:?}", location, self.substs());
|
||||
if self.const_env() {
|
||||
return Err(EvalError::NeedsRfc("invoking `Drop::drop`".to_string()));
|
||||
}
|
||||
// FIXME(CTFE): forbid drop in const eval
|
||||
let lval = self.eval_lvalue(location)?;
|
||||
let ty = self.lvalue_ty(location);
|
||||
self.goto_block(target);
|
||||
|
@ -436,17 +432,14 @@ impl<'a, 'tcx, M: Machine<'tcx>> EvalContext<'a, 'tcx, M> {
|
|||
let mir = match self.load_mir(instance.def) {
|
||||
Ok(mir) => mir,
|
||||
Err(EvalError::NoMirFor(path)) => {
|
||||
if self.const_env() {
|
||||
return Err(EvalError::NeedsRfc(format!("calling extern function `{}`", path)));
|
||||
}
|
||||
M::call_missing_fn(self, instance, destination, arg_operands, sig, path)?;
|
||||
return Ok(true);
|
||||
},
|
||||
Err(other) => return Err(other),
|
||||
};
|
||||
|
||||
if self.const_env() && !self.tcx.is_const_fn(instance.def_id()) {
|
||||
return Err(EvalError::NotConst(format!("calling non-const fn `{}`", instance)));
|
||||
if !self.tcx.is_const_fn(instance.def_id()) {
|
||||
M::check_non_const_fn_call(instance)?;
|
||||
}
|
||||
|
||||
let (return_lvalue, return_to_block) = match destination {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue