1
Fork 0

Implement Offset like the other binary operators, share code with the intrinsic

Also improve drop glue tests
This commit is contained in:
Ralf Jung 2017-06-04 17:26:19 -07:00
parent 31cf66d0e8
commit 1b5f77e4c1
4 changed files with 135 additions and 127 deletions

View file

@ -451,18 +451,6 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
self.write_value(value, dest, dest_ty)?; self.write_value(value, dest, dest_ty)?;
} }
BinaryOp(mir::BinOp::Offset, ref left, ref right) => {
let pointer_ty = self.operand_ty(left);
let pointee_ty = pointer_ty.builtin_deref(true, ty::LvaluePreference::NoPreference).expect("Offset called on non-ptr type").ty;
// 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 = self.eval_operand_to_primval(right)?.to_i128()? as i64;
let ptr = self.eval_operand_to_primval(left)?.to_ptr()?;
let result_ptr = ptr.signed_offset(offset * pointee_size);
self.write_primval(dest, PrimVal::Ptr(result_ptr), dest_ty)?;
}
BinaryOp(bin_op, ref left, ref right) => { BinaryOp(bin_op, ref left, ref right) => {
// ignore overflow bit, rustc inserts check branches for us // ignore overflow bit, rustc inserts check branches for us
self.intrinsic_overflowing(bin_op, left, right, dest, dest_ty)?; self.intrinsic_overflowing(bin_op, left, right, dest, dest_ty)?;
@ -853,6 +841,13 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
} }
} }
pub(super) fn 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;
// FIXME: Check overflow, out-of-bounds
Ok(ptr.signed_offset(offset * pointee_size))
}
pub(super) fn eval_operand_to_primval(&mut self, op: &mir::Operand<'tcx>) -> EvalResult<'tcx, PrimVal> { pub(super) fn eval_operand_to_primval(&mut self, op: &mir::Operand<'tcx>) -> EvalResult<'tcx, PrimVal> {
let value = self.eval_operand(op)?; let value = self.eval_operand(op)?;
let ty = self.operand_ty(op); let ty = self.operand_ty(op);

View file

@ -1,5 +1,5 @@
use rustc::mir; use rustc::mir;
use rustc::ty::Ty; use rustc::ty::{self, Ty};
use error::{EvalError, EvalResult}; use error::{EvalError, EvalResult};
use eval_context::EvalContext; use eval_context::EvalContext;
@ -25,11 +25,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
) -> EvalResult<'tcx, (PrimVal, bool)> { ) -> EvalResult<'tcx, (PrimVal, bool)> {
let left_ty = self.operand_ty(left); let left_ty = self.operand_ty(left);
let right_ty = self.operand_ty(right); let right_ty = self.operand_ty(right);
let left_kind = self.ty_to_primval_kind(left_ty)?;
let right_kind = self.ty_to_primval_kind(right_ty)?;
let left_val = self.eval_operand_to_primval(left)?; let left_val = self.eval_operand_to_primval(left)?;
let right_val = self.eval_operand_to_primval(right)?; let right_val = self.eval_operand_to_primval(right)?;
binary_op(op, left_val, left_kind, right_val, right_kind) self.binary_op(op, left_val, left_ty, right_val, right_ty)
} }
/// Applies the binary operation `op` to the two operands and writes a tuple of the result /// Applies the binary operation `op` to the two operands and writes a tuple of the result
@ -132,119 +130,141 @@ macro_rules! f64_arithmetic {
) )
} }
/// Returns the result of the specified operation and whether it overflowed. impl<'a, 'tcx> EvalContext<'a, 'tcx> {
pub fn binary_op<'tcx>( /// Returns the result of the specified operation and whether it overflowed.
bin_op: mir::BinOp, pub fn binary_op(
left: PrimVal, &self,
left_kind: PrimValKind, bin_op: mir::BinOp,
right: PrimVal, left: PrimVal,
right_kind: PrimValKind, left_ty: Ty<'tcx>,
) -> EvalResult<'tcx, (PrimVal, bool)> { right: PrimVal,
use rustc::mir::BinOp::*; right_ty: Ty<'tcx>,
use value::PrimValKind::*; ) -> EvalResult<'tcx, (PrimVal, bool)> {
use rustc::mir::BinOp::*;
use value::PrimValKind::*;
// FIXME(solson): Temporary hack. It will go away when we get rid of Pointer's ability to store // FIXME(solson): Temporary hack. It will go away when we get rid of Pointer's ability to store
// plain bytes, and leave that to PrimVal::Bytes. // plain bytes, and leave that to PrimVal::Bytes.
fn normalize(val: PrimVal) -> PrimVal { fn normalize(val: PrimVal) -> PrimVal {
if let PrimVal::Ptr(ptr) = val { if let PrimVal::Ptr(ptr) = val {
if let Ok(bytes) = ptr.to_int() { if let Ok(bytes) = ptr.to_int() {
return PrimVal::Bytes(bytes as u128); return PrimVal::Bytes(bytes as u128);
}
} }
val
} }
val let (left, right) = (normalize(left), normalize(right));
}
let (left, right) = (normalize(left), normalize(right));
let (l, r) = match (left, right) { // Offset is handled early, before we dispatch to unrelated_ptr_ops. We have to also catch the case where both arguments *are* convertible to integers.
(PrimVal::Bytes(left_bytes), PrimVal::Bytes(right_bytes)) => (left_bytes, right_bytes), if bin_op == Offset {
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.to_ptr()?, pointee_ty, right.to_bytes()? as i64)?;
return Ok((PrimVal::Ptr(ptr), false));
}
(PrimVal::Ptr(left_ptr), PrimVal::Ptr(right_ptr)) => { let (l, r) = match (left, right) {
if left_ptr.alloc_id == right_ptr.alloc_id { (PrimVal::Bytes(left_bytes), PrimVal::Bytes(right_bytes)) => (left_bytes, right_bytes),
// If the pointers are into the same allocation, fall through to the more general
// match later, which will do comparisons on the pointer offsets. (PrimVal::Ptr(left_ptr), PrimVal::Ptr(right_ptr)) => {
(left_ptr.offset as u128, right_ptr.offset as u128) if left_ptr.alloc_id == right_ptr.alloc_id {
} else { // If the pointers are into the same allocation, fall through to the more general
return Ok((unrelated_ptr_ops(bin_op, left_ptr, right_ptr)?, false)); // match later, which will do comparisons on the pointer offsets.
(left_ptr.offset as u128, right_ptr.offset as u128)
} else {
return Ok((unrelated_ptr_ops(bin_op, left_ptr, right_ptr)?, false));
}
} }
}
(PrimVal::Ptr(ptr), PrimVal::Bytes(bytes)) | (PrimVal::Ptr(ptr), PrimVal::Bytes(bytes)) |
(PrimVal::Bytes(bytes), PrimVal::Ptr(ptr)) => { (PrimVal::Bytes(bytes), PrimVal::Ptr(ptr)) => {
return Ok((unrelated_ptr_ops(bin_op, ptr, Pointer::from_int(bytes as u64))?, false)); return Ok((unrelated_ptr_ops(bin_op, ptr, Pointer::from_int(bytes as u64))?, false));
} }
(PrimVal::Undef, _) | (_, PrimVal::Undef) => return Err(EvalError::ReadUndefBytes), (PrimVal::Undef, _) | (_, PrimVal::Undef) => return Err(EvalError::ReadUndefBytes),
};
// These ops can have an RHS with a different numeric type.
if bin_op == Shl || bin_op == Shr {
return match bin_op {
Shl => int_shift!(left_kind, overflowing_shl, l, r as u32),
Shr => int_shift!(left_kind, overflowing_shr, l, r as u32),
_ => bug!("it has already been checked that this is a shift op"),
}; };
}
if left_kind != right_kind { let left_kind = self.ty_to_primval_kind(left_ty)?;
let msg = format!("unimplemented binary op: {:?}, {:?}, {:?}", left, right, bin_op); let right_kind = self.ty_to_primval_kind(right_ty)?;
return Err(EvalError::Unimplemented(msg));
}
let val = match (bin_op, left_kind) { // These ops can have an RHS with a different numeric type.
(Eq, F32) => PrimVal::from_bool(bytes_to_f32(l) == bytes_to_f32(r)), if bin_op == Shl || bin_op == Shr {
(Ne, F32) => PrimVal::from_bool(bytes_to_f32(l) != bytes_to_f32(r)), return match bin_op {
(Lt, F32) => PrimVal::from_bool(bytes_to_f32(l) < bytes_to_f32(r)), Shl => int_shift!(left_kind, overflowing_shl, l, r as u32),
(Le, F32) => PrimVal::from_bool(bytes_to_f32(l) <= bytes_to_f32(r)), Shr => int_shift!(left_kind, overflowing_shr, l, r as u32),
(Gt, F32) => PrimVal::from_bool(bytes_to_f32(l) > bytes_to_f32(r)), _ => bug!("it has already been checked that this is a shift op"),
(Ge, F32) => PrimVal::from_bool(bytes_to_f32(l) >= bytes_to_f32(r)), };
}
if bin_op == Offset {
// We permit offset-by-0 in any case. Drop glue actually does this, and it's probably (TM) fine for LLVM.
if left_kind == PrimValKind::Ptr && right_kind.is_int() && r == 0 {
return Ok((PrimVal::Bytes(l), false));
} else {
let msg = format!("unimplemented Offset: {:?}, {:?}", left, right);
return Err(EvalError::Unimplemented(msg));
}
}
(Eq, F64) => PrimVal::from_bool(bytes_to_f64(l) == bytes_to_f64(r)), if left_kind != right_kind {
(Ne, F64) => PrimVal::from_bool(bytes_to_f64(l) != bytes_to_f64(r)),
(Lt, F64) => PrimVal::from_bool(bytes_to_f64(l) < bytes_to_f64(r)),
(Le, F64) => PrimVal::from_bool(bytes_to_f64(l) <= bytes_to_f64(r)),
(Gt, F64) => PrimVal::from_bool(bytes_to_f64(l) > bytes_to_f64(r)),
(Ge, F64) => PrimVal::from_bool(bytes_to_f64(l) >= bytes_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, 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)),
(Le, _) => PrimVal::from_bool(l <= r),
(Gt, k) if k.is_signed_int() => PrimVal::from_bool((l as i128) > (r as i128)),
(Gt, _) => PrimVal::from_bool(l > r),
(Ge, k) if k.is_signed_int() => PrimVal::from_bool((l as i128) >= (r as i128)),
(Ge, _) => PrimVal::from_bool(l >= r),
(BitOr, _) => PrimVal::Bytes(l | r),
(BitAnd, _) => PrimVal::Bytes(l & r),
(BitXor, _) => PrimVal::Bytes(l ^ r),
(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); let msg = format!("unimplemented binary op: {:?}, {:?}, {:?}", left, right, bin_op);
return Err(EvalError::Unimplemented(msg)); return Err(EvalError::Unimplemented(msg));
} }
};
Ok((val, false)) let val = match (bin_op, left_kind) {
(Eq, F32) => PrimVal::from_bool(bytes_to_f32(l) == bytes_to_f32(r)),
(Ne, F32) => PrimVal::from_bool(bytes_to_f32(l) != bytes_to_f32(r)),
(Lt, F32) => PrimVal::from_bool(bytes_to_f32(l) < bytes_to_f32(r)),
(Le, F32) => PrimVal::from_bool(bytes_to_f32(l) <= bytes_to_f32(r)),
(Gt, F32) => PrimVal::from_bool(bytes_to_f32(l) > bytes_to_f32(r)),
(Ge, F32) => PrimVal::from_bool(bytes_to_f32(l) >= bytes_to_f32(r)),
(Eq, F64) => PrimVal::from_bool(bytes_to_f64(l) == bytes_to_f64(r)),
(Ne, F64) => PrimVal::from_bool(bytes_to_f64(l) != bytes_to_f64(r)),
(Lt, F64) => PrimVal::from_bool(bytes_to_f64(l) < bytes_to_f64(r)),
(Le, F64) => PrimVal::from_bool(bytes_to_f64(l) <= bytes_to_f64(r)),
(Gt, F64) => PrimVal::from_bool(bytes_to_f64(l) > bytes_to_f64(r)),
(Ge, F64) => PrimVal::from_bool(bytes_to_f64(l) >= bytes_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, 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)),
(Le, _) => PrimVal::from_bool(l <= r),
(Gt, k) if k.is_signed_int() => PrimVal::from_bool((l as i128) > (r as i128)),
(Gt, _) => PrimVal::from_bool(l > r),
(Ge, k) if k.is_signed_int() => PrimVal::from_bool((l as i128) >= (r as i128)),
(Ge, _) => PrimVal::from_bool(l >= r),
(BitOr, _) => PrimVal::Bytes(l | r),
(BitAnd, _) => PrimVal::Bytes(l & r),
(BitXor, _) => PrimVal::Bytes(l ^ r),
(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, left: Pointer, right: Pointer) -> EvalResult<'tcx, PrimVal> { fn unrelated_ptr_ops<'tcx>(bin_op: mir::BinOp, left: Pointer, right: Pointer) -> EvalResult<'tcx, PrimVal> {

View file

@ -7,7 +7,6 @@ use rustc::ty::{self, Ty};
use error::{EvalError, EvalResult}; use error::{EvalError, EvalResult};
use eval_context::EvalContext; use eval_context::EvalContext;
use lvalue::{Lvalue, LvalueExtra}; use lvalue::{Lvalue, LvalueExtra};
use operator;
use value::{PrimVal, PrimValKind, Value}; use value::{PrimVal, PrimValKind, Value};
impl<'a, 'tcx> EvalContext<'a, 'tcx> { impl<'a, 'tcx> EvalContext<'a, 'tcx> {
@ -103,8 +102,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
Value::ByRef(_) => bug!("just read the value, can't be byref"), Value::ByRef(_) => bug!("just read the value, can't be byref"),
Value::ByValPair(..) => bug!("atomic_cxchg doesn't work with nonprimitives"), Value::ByValPair(..) => bug!("atomic_cxchg doesn't work with nonprimitives"),
}; };
let kind = self.ty_to_primval_kind(ty)?; let (val, _) = self.binary_op(mir::BinOp::Eq, old, ty, expect_old, ty)?;
let (val, _) = operator::binary_op(mir::BinOp::Eq, old, kind, expect_old, kind)?;
let dest = self.force_allocation(dest)?.to_ptr(); let dest = self.force_allocation(dest)?.to_ptr();
self.write_pair_to_ptr(old, val, dest, dest_ty)?; self.write_pair_to_ptr(old, val, dest, dest_ty)?;
self.write_primval(Lvalue::from_ptr(ptr), change, ty)?; self.write_primval(Lvalue::from_ptr(ptr), change, ty)?;
@ -125,7 +123,6 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
Value::ByValPair(..) => bug!("atomic_xadd_relaxed doesn't work with nonprimitives"), Value::ByValPair(..) => bug!("atomic_xadd_relaxed doesn't work with nonprimitives"),
}; };
self.write_primval(dest, old, ty)?; self.write_primval(dest, old, ty)?;
let kind = self.ty_to_primval_kind(ty)?;
let op = match intrinsic_name.split('_').nth(1).unwrap() { let op = match intrinsic_name.split('_').nth(1).unwrap() {
"or" => mir::BinOp::BitOr, "or" => mir::BinOp::BitOr,
"xor" => mir::BinOp::BitXor, "xor" => mir::BinOp::BitXor,
@ -135,7 +132,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
_ => bug!(), _ => bug!(),
}; };
// FIXME: what do atomics do on overflow? // FIXME: what do atomics do on overflow?
let (val, _) = operator::binary_op(op, old, kind, change, kind)?; let (val, _) = self.binary_op(op, old, ty, change, ty)?;
self.write_primval(Lvalue::from_ptr(ptr), val, ty)?; self.write_primval(Lvalue::from_ptr(ptr), val, ty)?;
}, },
@ -219,7 +216,6 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
"fadd_fast" | "fsub_fast" | "fmul_fast" | "fdiv_fast" | "frem_fast" => { "fadd_fast" | "fsub_fast" | "fmul_fast" | "fdiv_fast" | "frem_fast" => {
let ty = substs.type_at(0); let ty = substs.type_at(0);
let kind = self.ty_to_primval_kind(ty)?;
let a = self.value_to_primval(arg_vals[0], ty)?; let a = self.value_to_primval(arg_vals[0], ty)?;
let b = self.value_to_primval(arg_vals[1], ty)?; let b = self.value_to_primval(arg_vals[1], ty)?;
let op = match intrinsic_name { let op = match intrinsic_name {
@ -230,7 +226,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
"frem_fast" => mir::BinOp::Rem, "frem_fast" => mir::BinOp::Rem,
_ => bug!(), _ => bug!(),
}; };
let result = operator::binary_op(op, a, kind, b, kind)?; let result = self.binary_op(op, a, ty, b, ty)?;
self.write_primval(dest, result.0, dest_ty)?; self.write_primval(dest, result.0, dest_ty)?;
} }
@ -298,13 +294,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
} }
"offset" => { "offset" => {
let pointee_ty = substs.type_at(0);
// 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 = self.value_to_primval(arg_vals[1], isize)?.to_i128()? as i64; let offset = self.value_to_primval(arg_vals[1], isize)?.to_i128()? as i64;
let ptr = arg_vals[0].read_ptr(&self.memory)?; let ptr = arg_vals[0].read_ptr(&self.memory)?;
let result_ptr = ptr.signed_offset(offset * pointee_size); let result_ptr = self.pointer_offset(ptr, substs.type_at(0), offset)?;
self.write_primval(dest, PrimVal::Ptr(result_ptr), dest_ty)?; self.write_primval(dest, PrimVal::Ptr(result_ptr), dest_ty)?;
} }

View file

@ -1,15 +1,16 @@
struct Bar(i32); // ZSTs are tested separately struct Bar(u16); // ZSTs are tested separately
static mut DROP_COUNT: usize = 0; static mut DROP_COUNT: usize = 0;
impl Drop for Bar { impl Drop for Bar {
fn drop(&mut self) { fn drop(&mut self) {
assert_eq!(self.0 as usize, unsafe { DROP_COUNT }); // tests whether we are called at a valid address
unsafe { DROP_COUNT += 1; } unsafe { DROP_COUNT += 1; }
} }
} }
fn main() { fn main() {
let b = [Bar(0), Bar(0), Bar(0), Bar(0)]; let b = [Bar(0), Bar(1), Bar(2), Bar(3)];
assert_eq!(unsafe { DROP_COUNT }, 0); assert_eq!(unsafe { DROP_COUNT }, 0);
drop(b); drop(b);
assert_eq!(unsafe { DROP_COUNT }, 4); assert_eq!(unsafe { DROP_COUNT }, 4);