#![allow(unknown_lints)] #![allow(float_cmp)] use std::mem::transmute; use rustc::mir::repr as mir; use error::{EvalError, EvalResult}; use memory::{AllocId, Pointer}; fn bits_to_f32(bits: u64) -> f32 { unsafe { transmute::(bits as u32) } } fn bits_to_f64(bits: u64) -> f64 { unsafe { transmute::(bits) } } fn f32_to_bits(f: f32) -> u64 { unsafe { transmute::(f) as u64 } } fn f64_to_bits(f: f64) -> u64 { unsafe { transmute::(f) } } fn bits_to_bool(n: u64) -> bool { // FIXME(solson): Can we reach here due to user error? debug_assert!(n == 0 || n == 1, "bits interpreted as bool were {}", n); n & 1 == 1 } #[derive(Clone, Copy, Debug, PartialEq)] pub struct PrimVal { pub bits: u64, pub kind: PrimValKind, } #[derive(Clone, Copy, Debug, PartialEq)] pub enum PrimValKind { I8, I16, I32, I64, U8, U16, U32, U64, F32, F64, Bool, Char, Ptr(AllocId), FnPtr(AllocId), } impl PrimValKind { pub fn is_int(self) -> bool { use self::PrimValKind::*; match self { I8 | I16 | I32 | I64 | U8 | U16 | U32 | U64 => true, _ => false, } } } impl PrimVal { pub fn new(bits: u64, kind: PrimValKind) -> Self { PrimVal { bits: bits, kind: kind } } pub fn from_ptr(ptr: Pointer) -> Self { PrimVal::new(ptr.offset as u64, PrimValKind::Ptr(ptr.alloc_id)) } pub fn from_fn_ptr(ptr: Pointer) -> Self { PrimVal::new(ptr.offset as u64, PrimValKind::FnPtr(ptr.alloc_id)) } pub fn from_bool(b: bool) -> Self { PrimVal::new(b as u64, PrimValKind::Bool) } pub fn from_char(c: char) -> Self { PrimVal::new(c as u64, PrimValKind::Char) } pub fn from_f32(f: f32) -> Self { PrimVal::new(f32_to_bits(f), PrimValKind::F32) } pub fn from_f64(f: f64) -> Self { PrimVal::new(f64_to_bits(f), PrimValKind::F64) } pub fn from_uint_with_size(n: u64, size: usize) -> Self { let kind = match size { 1 => PrimValKind::U8, 2 => PrimValKind::U16, 4 => PrimValKind::U32, 8 => PrimValKind::U64, _ => bug!("can't make uint ({}) with size {}", n, size), }; PrimVal::new(n, kind) } pub fn from_int_with_size(n: i64, size: usize) -> Self { let kind = match size { 1 => PrimValKind::I8, 2 => PrimValKind::I16, 4 => PrimValKind::I32, 8 => PrimValKind::I64, _ => bug!("can't make int ({}) with size {}", n, size), }; PrimVal::new(n as u64, kind) } pub fn to_f32(self) -> f32 { bits_to_f32(self.bits) } pub fn to_f64(self) -> f64 { bits_to_f64(self.bits) } pub fn expect_uint(self, error_msg: &str) -> u64 { use self::PrimValKind::*; match self.kind { U8 | U16 | U32 | U64 => self.bits, Ptr(alloc_id) => { let ptr = Pointer::new(alloc_id, self.bits as usize); ptr.to_int().expect("non abstract ptr") as u64 } _ => bug!("{}", error_msg), } } pub fn expect_int(self, error_msg: &str) -> i64 { use self::PrimValKind::*; match self.kind { I8 | I16 | I32 | I64 => self.bits as i64, Ptr(alloc_id) => { let ptr = Pointer::new(alloc_id, self.bits as usize); ptr.to_int().expect("non abstract ptr") as i64 } _ => bug!("{}", error_msg), } } pub fn expect_bool(self, error_msg: &str) -> bool { match (self.kind, self.bits) { (PrimValKind::Bool, 0) => false, (PrimValKind::Bool, 1) => true, _ => bug!("{}", error_msg), } } pub fn expect_f32(self, error_msg: &str) -> f32 { match self.kind { PrimValKind::F32 => bits_to_f32(self.bits), _ => bug!("{}", error_msg), } } pub fn expect_f64(self, error_msg: &str) -> f64 { match self.kind { PrimValKind::F32 => bits_to_f64(self.bits), _ => bug!("{}", error_msg), } } pub fn expect_ptr(self, error_msg: &str) -> Pointer { match self.kind { PrimValKind::Ptr(alloc_id) => Pointer::new(alloc_id, self.bits as usize), _ => bug!("{}", error_msg), } } pub fn expect_fn_ptr(self, error_msg: &str) -> Pointer { match self.kind { PrimValKind::FnPtr(alloc_id) => Pointer::new(alloc_id, self.bits as usize), _ => bug!("{}", error_msg), } } } //////////////////////////////////////////////////////////////////////////////// // MIR operator evaluation //////////////////////////////////////////////////////////////////////////////// macro_rules! overflow { ($kind:expr, $op:ident, $l:expr, $r:expr) => ({ let (val, overflowed) = $l.$op($r); let primval = PrimVal::new(val as u64, $kind); Ok((primval, overflowed)) }) } macro_rules! int_arithmetic { ($kind:expr, $int_op:ident, $l:expr, $r:expr) => ({ let l = $l; let r = $r; match $kind { I8 => overflow!(I8, $int_op, l as i8, r as i8), I16 => overflow!(I16, $int_op, l as i16, r as i16), I32 => overflow!(I32, $int_op, l as i32, r as i32), I64 => overflow!(I64, $int_op, l as i64, r as i64), U8 => overflow!(U8, $int_op, l as u8, r as u8), U16 => overflow!(U16, $int_op, l as u16, r as u16), U32 => overflow!(U32, $int_op, l as u32, r as u32), U64 => overflow!(U64, $int_op, l as u64, r as u64), _ => bug!("int_arithmetic should only be called on int primvals"), } }) } macro_rules! int_shift { ($kind:expr, $int_op:ident, $l:expr, $r:expr) => ({ let l = $l; let r = $r; match $kind { I8 => overflow!(I8, $int_op, l as i8, r), I16 => overflow!(I16, $int_op, l as i16, r), I32 => overflow!(I32, $int_op, l as i32, r), I64 => overflow!(I64, $int_op, l as i64, r), U8 => overflow!(U8, $int_op, l as u8, r), U16 => overflow!(U16, $int_op, l as u16, r), U32 => overflow!(U32, $int_op, l as u32, r), U64 => overflow!(U64, $int_op, l as u64, r), _ => bug!("int_shift should only be called on int primvals"), } }) } macro_rules! float_arithmetic { ($kind:expr, $from_bits:ident, $to_bits:ident, $float_op:tt, $l:expr, $r:expr) => ({ let l = $from_bits($l); let r = $from_bits($r); let bits = $to_bits(l $float_op r); PrimVal::new(bits, $kind) }) } macro_rules! f32_arithmetic { ($float_op:tt, $l:expr, $r:expr) => ( float_arithmetic!(F32, bits_to_f32, f32_to_bits, $float_op, $l, $r) ) } macro_rules! f64_arithmetic { ($float_op:tt, $l:expr, $r:expr) => ( float_arithmetic!(F64, bits_to_f64, f64_to_bits, $float_op, $l, $r) ) } /// Returns the result of the specified operation and whether it overflowed. pub fn binary_op<'tcx>( bin_op: mir::BinOp, left: PrimVal, right: PrimVal ) -> EvalResult<'tcx, (PrimVal, bool)> { use rustc::mir::repr::BinOp::*; use self::PrimValKind::*; match (left.kind, right.kind) { (FnPtr(_), Ptr(_)) | (Ptr(_), FnPtr(_)) => return Ok((unrelated_ptr_ops(bin_op)?, false)), (Ptr(l_alloc), Ptr(r_alloc)) if l_alloc != r_alloc => return Ok((unrelated_ptr_ops(bin_op)?, false)), (FnPtr(l_alloc), FnPtr(r_alloc)) => { match bin_op { Eq => return Ok((PrimVal::from_bool(l_alloc == r_alloc), false)), Ne => return Ok((PrimVal::from_bool(l_alloc != r_alloc), false)), _ => {} } } _ => {} } let (l, r) = (left.bits, right.bits); // These ops can have an RHS with a different numeric type. if bin_op == Shl || bin_op == Shr { // These are the maximum values a bitshift RHS could possibly have. For example, u16 // can be bitshifted by 0..16, so masking with 0b1111 (16 - 1) will ensure we are in // that range. let type_bits: u32 = match left.kind { I8 | U8 => 8, I16 | U16 => 16, I32 | U32 => 32, I64 | U64 => 64, _ => bug!("bad MIR: bitshift lhs is not integral"), }; // Cast to `u32` because `overflowing_sh{l,r}` only take `u32`, then apply the bitmask // to ensure it's within the valid shift value range. let r = (right.bits as u32) & (type_bits - 1); return match bin_op { Shl => int_shift!(left.kind, overflowing_shl, l, r), Shr => int_shift!(left.kind, overflowing_shr, l, r), _ => bug!("it has already been checked that this is a shift op"), }; } if left.kind != right.kind { let msg = format!("unimplemented binary op: {:?}, {:?}, {:?}", left, right, bin_op); return Err(EvalError::Unimplemented(msg)); } let val = match (bin_op, left.kind) { (Eq, F32) => PrimVal::from_bool(bits_to_f32(l) == bits_to_f32(r)), (Ne, F32) => PrimVal::from_bool(bits_to_f32(l) != bits_to_f32(r)), (Lt, F32) => PrimVal::from_bool(bits_to_f32(l) < bits_to_f32(r)), (Le, F32) => PrimVal::from_bool(bits_to_f32(l) <= bits_to_f32(r)), (Gt, F32) => PrimVal::from_bool(bits_to_f32(l) > bits_to_f32(r)), (Ge, F32) => PrimVal::from_bool(bits_to_f32(l) >= bits_to_f32(r)), (Eq, F64) => PrimVal::from_bool(bits_to_f64(l) == bits_to_f64(r)), (Ne, F64) => PrimVal::from_bool(bits_to_f64(l) != bits_to_f64(r)), (Lt, F64) => PrimVal::from_bool(bits_to_f64(l) < bits_to_f64(r)), (Le, F64) => PrimVal::from_bool(bits_to_f64(l) <= bits_to_f64(r)), (Gt, F64) => PrimVal::from_bool(bits_to_f64(l) > bits_to_f64(r)), (Ge, F64) => PrimVal::from_bool(bits_to_f64(l) >= bits_to_f64(r)), (Add, F32) => f32_arithmetic!(+, l, r), (Sub, F32) => f32_arithmetic!(-, l, r), (Mul, F32) => f32_arithmetic!(*, l, r), (Div, F32) => f32_arithmetic!(/, l, r), (Rem, F32) => f32_arithmetic!(%, l, r), (Add, F64) => f64_arithmetic!(+, l, r), (Sub, F64) => f64_arithmetic!(-, l, r), (Mul, F64) => f64_arithmetic!(*, l, r), (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, _) => PrimVal::from_bool(l < r), (Le, _) => PrimVal::from_bool(l <= r), (Gt, _) => PrimVal::from_bool(l > r), (Ge, _) => PrimVal::from_bool(l >= r), (BitOr, k) => PrimVal::new(l | r, k), (BitAnd, k) => PrimVal::new(l & r, k), (BitXor, k) => PrimVal::new(l ^ r, k), (Add, k) if k.is_int() => return int_arithmetic!(k, overflowing_add, l, r), (Sub, k) if k.is_int() => return int_arithmetic!(k, overflowing_sub, l, r), (Mul, k) if k.is_int() => return int_arithmetic!(k, overflowing_mul, l, r), (Div, k) if k.is_int() => return int_arithmetic!(k, overflowing_div, l, r), (Rem, k) if k.is_int() => return int_arithmetic!(k, overflowing_rem, l, r), _ => { let msg = format!("unimplemented binary op: {:?}, {:?}, {:?}", left, right, bin_op); return Err(EvalError::Unimplemented(msg)); } }; Ok((val, false)) } fn unrelated_ptr_ops<'tcx>(bin_op: mir::BinOp) -> EvalResult<'tcx, PrimVal> { use rustc::mir::repr::BinOp::*; match bin_op { Eq => Ok(PrimVal::from_bool(false)), Ne => Ok(PrimVal::from_bool(true)), Lt | Le | Gt | Ge => Err(EvalError::InvalidPointerMath), _ => bug!(), } } pub fn unary_op<'tcx>(un_op: mir::UnOp, val: PrimVal) -> EvalResult<'tcx, PrimVal> { use rustc::mir::repr::UnOp::*; use self::PrimValKind::*; let bits = match (un_op, val.kind) { (Not, Bool) => !bits_to_bool(val.bits) as u64, (Not, U8) => !(val.bits as u8) as u64, (Not, U16) => !(val.bits as u16) as u64, (Not, U32) => !(val.bits as u32) as u64, (Not, U64) => !val.bits, (Not, I8) => !(val.bits as i8) as u64, (Not, I16) => !(val.bits as i16) as u64, (Not, I32) => !(val.bits as i32) as u64, (Not, I64) => !(val.bits as i64) as u64, (Neg, I8) => -(val.bits as i8) as u64, (Neg, I16) => -(val.bits as i16) as u64, (Neg, I32) => -(val.bits as i32) as u64, (Neg, I64) => -(val.bits as i64) as u64, (Neg, F32) => f32_to_bits(-bits_to_f32(val.bits)), (Neg, F64) => f64_to_bits(-bits_to_f64(val.bits)), _ => { let msg = format!("unimplemented unary op: {:?}, {:?}", un_op, val); return Err(EvalError::Unimplemented(msg)); } }; Ok(PrimVal::new(bits, val.kind)) }