diff --git a/src/eval_context.rs b/src/eval_context.rs index b2cd1a665d3..142d90f8dc4 100644 --- a/src/eval_context.rs +++ b/src/eval_context.rs @@ -451,18 +451,6 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { 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) => { // ignore overflow bit, rustc inserts check branches for us 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> { let value = self.eval_operand(op)?; let ty = self.operand_ty(op); diff --git a/src/operator.rs b/src/operator.rs index 155d5574daa..0109cddb573 100644 --- a/src/operator.rs +++ b/src/operator.rs @@ -1,5 +1,5 @@ use rustc::mir; -use rustc::ty::Ty; +use rustc::ty::{self, Ty}; use error::{EvalError, EvalResult}; use eval_context::EvalContext; @@ -25,11 +25,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { ) -> EvalResult<'tcx, (PrimVal, bool)> { let left_ty = self.operand_ty(left); 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 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 @@ -132,119 +130,141 @@ macro_rules! f64_arithmetic { ) } -/// Returns the result of the specified operation and whether it overflowed. -pub fn binary_op<'tcx>( - bin_op: mir::BinOp, - left: PrimVal, - left_kind: PrimValKind, - right: PrimVal, - right_kind: PrimValKind, -) -> EvalResult<'tcx, (PrimVal, bool)> { - use rustc::mir::BinOp::*; - use value::PrimValKind::*; +impl<'a, 'tcx> EvalContext<'a, 'tcx> { + /// Returns the result of the specified operation and whether it overflowed. + pub fn binary_op( + &self, + bin_op: mir::BinOp, + left: PrimVal, + left_ty: Ty<'tcx>, + right: PrimVal, + right_ty: Ty<'tcx>, + ) -> 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 - // plain bytes, and leave that to PrimVal::Bytes. - fn normalize(val: PrimVal) -> PrimVal { - if let PrimVal::Ptr(ptr) = val { - if let Ok(bytes) = ptr.to_int() { - return PrimVal::Bytes(bytes as u128); + // 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. + fn normalize(val: PrimVal) -> PrimVal { + if let PrimVal::Ptr(ptr) = val { + if let Ok(bytes) = ptr.to_int() { + 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) { - (PrimVal::Bytes(left_bytes), PrimVal::Bytes(right_bytes)) => (left_bytes, right_bytes), + // 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. + 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)) => { - if left_ptr.alloc_id == right_ptr.alloc_id { - // If the pointers are into the same allocation, fall through to the more general - // 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)); + let (l, r) = match (left, right) { + (PrimVal::Bytes(left_bytes), PrimVal::Bytes(right_bytes)) => (left_bytes, right_bytes), + + (PrimVal::Ptr(left_ptr), PrimVal::Ptr(right_ptr)) => { + if left_ptr.alloc_id == right_ptr.alloc_id { + // If the pointers are into the same allocation, fall through to the more general + // 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::Bytes(bytes), PrimVal::Ptr(ptr)) => { - return Ok((unrelated_ptr_ops(bin_op, ptr, Pointer::from_int(bytes as u64))?, false)); - } + (PrimVal::Ptr(ptr), PrimVal::Bytes(bytes)) | + (PrimVal::Bytes(bytes), PrimVal::Ptr(ptr)) => { + return Ok((unrelated_ptr_ops(bin_op, ptr, Pointer::from_int(bytes as u64))?, false)); + } - (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"), + (PrimVal::Undef, _) | (_, PrimVal::Undef) => return Err(EvalError::ReadUndefBytes), }; - } - if left_kind != right_kind { - let msg = format!("unimplemented binary op: {:?}, {:?}, {:?}", left, right, bin_op); - return Err(EvalError::Unimplemented(msg)); - } + let left_kind = self.ty_to_primval_kind(left_ty)?; + let right_kind = self.ty_to_primval_kind(right_ty)?; - 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)), + // 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 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)), - (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), - - _ => { + if left_kind != right_kind { let msg = format!("unimplemented binary op: {:?}, {:?}, {:?}", left, right, bin_op); 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> { diff --git a/src/terminator/intrinsic.rs b/src/terminator/intrinsic.rs index 0ca12ee5063..843a5b06b6a 100644 --- a/src/terminator/intrinsic.rs +++ b/src/terminator/intrinsic.rs @@ -7,7 +7,6 @@ use rustc::ty::{self, Ty}; use error::{EvalError, EvalResult}; use eval_context::EvalContext; use lvalue::{Lvalue, LvalueExtra}; -use operator; use value::{PrimVal, PrimValKind, Value}; 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::ByValPair(..) => bug!("atomic_cxchg doesn't work with nonprimitives"), }; - let kind = self.ty_to_primval_kind(ty)?; - let (val, _) = operator::binary_op(mir::BinOp::Eq, old, kind, expect_old, kind)?; + let (val, _) = self.binary_op(mir::BinOp::Eq, old, ty, expect_old, ty)?; let dest = self.force_allocation(dest)?.to_ptr(); self.write_pair_to_ptr(old, val, dest, dest_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"), }; self.write_primval(dest, old, ty)?; - let kind = self.ty_to_primval_kind(ty)?; let op = match intrinsic_name.split('_').nth(1).unwrap() { "or" => mir::BinOp::BitOr, "xor" => mir::BinOp::BitXor, @@ -135,7 +132,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { _ => bug!(), }; // 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)?; }, @@ -219,7 +216,6 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { "fadd_fast" | "fsub_fast" | "fmul_fast" | "fdiv_fast" | "frem_fast" => { 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 b = self.value_to_primval(arg_vals[1], ty)?; let op = match intrinsic_name { @@ -230,7 +226,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { "frem_fast" => mir::BinOp::Rem, _ => 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)?; } @@ -298,13 +294,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } "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 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)?; } diff --git a/tests/run-pass/call_drop_on_array_elements.rs b/tests/run-pass/call_drop_on_array_elements.rs index 68dbf61da4e..c9b59f635e1 100644 --- a/tests/run-pass/call_drop_on_array_elements.rs +++ b/tests/run-pass/call_drop_on_array_elements.rs @@ -1,15 +1,16 @@ -struct Bar(i32); // ZSTs are tested separately +struct Bar(u16); // ZSTs are tested separately static mut DROP_COUNT: usize = 0; impl Drop for Bar { 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; } } } 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); drop(b); assert_eq!(unsafe { DROP_COUNT }, 4);