From 269667e152e9377dfe8894b661e110fbd773bdd2 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 22 Jun 2017 00:08:19 -0700 Subject: [PATCH 1/4] implement _nonzero intrinsics --- src/error.rs | 5 ++++ src/terminator/intrinsic.rs | 19 +++++++++----- tests/run-pass/intrinsics-integer.rs | 37 ++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/error.rs b/src/error.rs index ee805695c510..496cefad33d5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -33,6 +33,7 @@ pub enum EvalError<'tcx> { ExecuteMemory, ArrayIndexOutOfBounds(Span, u64, u64), Math(Span, ConstMathErr), + Intrinsic(String), OverflowingMath, InvalidChar(u128), OutOfMemory { @@ -104,6 +105,8 @@ fn description(&self) -> &str { "array index out of bounds", EvalError::Math(..) => "mathematical operation failed", + EvalError::Intrinsic(..) => + "intrinsic failed", EvalError::OverflowingMath => "attempted to do overflowing math", EvalError::NoMirFor(..) => @@ -168,6 +171,8 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "index out of bounds: the len is {} but the index is {} at {:?}", len, index, span), EvalError::Math(span, ref err) => write!(f, "{:?} at {:?}", err, span), + EvalError::Intrinsic(ref err) => + write!(f, "{}", err), EvalError::InvalidChar(c) => write!(f, "tried to interpret an invalid 32-bit value as a char: {}", c), EvalError::OutOfMemory { allocation_size, memory_size, memory_usage } => diff --git a/src/terminator/intrinsic.rs b/src/terminator/intrinsic.rs index 0b9a37512ede..5fd0cc580220 100644 --- a/src/terminator/intrinsic.rs +++ b/src/terminator/intrinsic.rs @@ -154,12 +154,21 @@ pub(super) fn call_intrinsic( "ctpop" | "cttz" | + "cttz_nonzero" | "ctlz" | + "ctlz_nonzero" | "bswap" => { let ty = substs.type_at(0); - let num = self.value_to_primval(arg_vals[0], ty)?; + let num = self.value_to_primval(arg_vals[0], ty)?.to_bytes()?; let kind = self.ty_to_primval_kind(ty)?; - let num = numeric_intrinsic(intrinsic_name, num, kind)?; + let num = if intrinsic_name.ends_with("_nonzero") { + if num == 0 { + return Err(EvalError::Intrinsic(format!("{} called on 0", intrinsic_name))) + } + numeric_intrinsic(intrinsic_name.trim_right_matches("_nonzero"), num, kind)? + } else { + numeric_intrinsic(intrinsic_name, num, kind)? + }; self.write_primval(dest, num, ty)?; } @@ -538,13 +547,11 @@ fn field_ty( fn numeric_intrinsic<'tcx>( name: &str, - val: PrimVal, + bytes: u128, kind: PrimValKind ) -> EvalResult<'tcx, PrimVal> { macro_rules! integer_intrinsic { ($method:ident) => ({ - let bytes = val.to_bytes()?; - use value::PrimValKind::*; let result_bytes = match kind { I8 => (bytes as i8).$method() as u128, @@ -557,7 +564,7 @@ macro_rules! integer_intrinsic { U64 => (bytes as u64).$method() as u128, I128 => (bytes as i128).$method() as u128, U128 => bytes.$method() as u128, - _ => bug!("invalid `{}` argument: {:?}", name, val), + _ => bug!("invalid `{}` argument: {:?}", name, bytes), }; PrimVal::Bytes(result_bytes) diff --git a/tests/run-pass/intrinsics-integer.rs b/tests/run-pass/intrinsics-integer.rs index 759dc515456d..4896f02da20b 100644 --- a/tests/run-pass/intrinsics-integer.rs +++ b/tests/run-pass/intrinsics-integer.rs @@ -14,7 +14,9 @@ mod rusti { extern "rust-intrinsic" { pub fn ctpop(x: T) -> T; pub fn ctlz(x: T) -> T; + pub fn ctlz_nonzero(x: T) -> T; pub fn cttz(x: T) -> T; + pub fn cttz_nonzero(x: T) -> T; pub fn bswap(x: T) -> T; } } @@ -68,6 +70,21 @@ pub fn main() { assert_eq!(ctlz(100u32), 25); assert_eq!(ctlz(100i32), 25); assert_eq!(ctlz(100u64), 57); assert_eq!(ctlz(100i64), 57); + assert_eq!(ctlz_nonzero(1u8), 7); assert_eq!(ctlz_nonzero(1i8), 7); + assert_eq!(ctlz_nonzero(1u16), 15); assert_eq!(ctlz_nonzero(1i16), 15); + assert_eq!(ctlz_nonzero(1u32), 31); assert_eq!(ctlz_nonzero(1i32), 31); + assert_eq!(ctlz_nonzero(1u64), 63); assert_eq!(ctlz_nonzero(1i64), 63); + + assert_eq!(ctlz_nonzero(10u8), 4); assert_eq!(ctlz_nonzero(10i8), 4); + assert_eq!(ctlz_nonzero(10u16), 12); assert_eq!(ctlz_nonzero(10i16), 12); + assert_eq!(ctlz_nonzero(10u32), 28); assert_eq!(ctlz_nonzero(10i32), 28); + assert_eq!(ctlz_nonzero(10u64), 60); assert_eq!(ctlz_nonzero(10i64), 60); + + assert_eq!(ctlz_nonzero(100u8), 1); assert_eq!(ctlz_nonzero(100i8), 1); + assert_eq!(ctlz_nonzero(100u16), 9); assert_eq!(ctlz_nonzero(100i16), 9); + assert_eq!(ctlz_nonzero(100u32), 25); assert_eq!(ctlz_nonzero(100i32), 25); + assert_eq!(ctlz_nonzero(100u64), 57); assert_eq!(ctlz_nonzero(100i64), 57); + assert_eq!(cttz(-1i8 as u8), 0); assert_eq!(cttz(-1i8), 0); assert_eq!(cttz(-1i16 as u16), 0); assert_eq!(cttz(-1i16), 0); assert_eq!(cttz(-1i32 as u32), 0); assert_eq!(cttz(-1i32), 0); @@ -93,6 +110,26 @@ pub fn main() { assert_eq!(cttz(100u32), 2); assert_eq!(cttz(100i32), 2); assert_eq!(cttz(100u64), 2); assert_eq!(cttz(100i64), 2); + assert_eq!(cttz_nonzero(-1i8 as u8), 0); assert_eq!(cttz_nonzero(-1i8), 0); + assert_eq!(cttz_nonzero(-1i16 as u16), 0); assert_eq!(cttz_nonzero(-1i16), 0); + assert_eq!(cttz_nonzero(-1i32 as u32), 0); assert_eq!(cttz_nonzero(-1i32), 0); + assert_eq!(cttz_nonzero(-1i64 as u64), 0); assert_eq!(cttz_nonzero(-1i64), 0); + + assert_eq!(cttz_nonzero(1u8), 0); assert_eq!(cttz_nonzero(1i8), 0); + assert_eq!(cttz_nonzero(1u16), 0); assert_eq!(cttz_nonzero(1i16), 0); + assert_eq!(cttz_nonzero(1u32), 0); assert_eq!(cttz_nonzero(1i32), 0); + assert_eq!(cttz_nonzero(1u64), 0); assert_eq!(cttz_nonzero(1i64), 0); + + assert_eq!(cttz_nonzero(10u8), 1); assert_eq!(cttz_nonzero(10i8), 1); + assert_eq!(cttz_nonzero(10u16), 1); assert_eq!(cttz_nonzero(10i16), 1); + assert_eq!(cttz_nonzero(10u32), 1); assert_eq!(cttz_nonzero(10i32), 1); + assert_eq!(cttz_nonzero(10u64), 1); assert_eq!(cttz_nonzero(10i64), 1); + + assert_eq!(cttz_nonzero(100u8), 2); assert_eq!(cttz_nonzero(100i8), 2); + assert_eq!(cttz_nonzero(100u16), 2); assert_eq!(cttz_nonzero(100i16), 2); + assert_eq!(cttz_nonzero(100u32), 2); assert_eq!(cttz_nonzero(100i32), 2); + assert_eq!(cttz_nonzero(100u64), 2); assert_eq!(cttz_nonzero(100i64), 2); + assert_eq!(bswap(0x0Au8), 0x0A); // no-op assert_eq!(bswap(0x0Ai8), 0x0A); // no-op assert_eq!(bswap(0x0A0Bu16), 0x0B0A); From 14cb31fb88328a5f9a07e00b06d6b0a014fbe197 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 22 Jun 2017 00:12:47 -0700 Subject: [PATCH 2/4] permit bit-anding in pointer values below the alignment This makes HashMap work! --- src/memory.rs | 2 +- src/operator.rs | 42 ++++++++++++++----- src/value.rs | 2 +- tests/run-pass-fullmir/hashmap.rs | 22 +++++++--- .../tag-align-dyn-u64.rs | 2 +- 5 files changed, 50 insertions(+), 20 deletions(-) rename tests/{compile-fail => run-pass}/tag-align-dyn-u64.rs (87%) diff --git a/src/memory.rs b/src/memory.rs index 4cf3ecb8215a..4cb66f2acdc8 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -546,7 +546,7 @@ pub fn dump_allocs(&self, mut allocs: Vec) { StaticKind::Immutable => " (immutable)", StaticKind::NotStatic => "", }; - trace!("{}({} bytes){}", msg, alloc.bytes.len(), immutable); + trace!("{}({} bytes, alignment {}){}", msg, alloc.bytes.len(), alloc.align, immutable); if !relocations.is_empty() { msg.clear(); diff --git a/src/operator.rs b/src/operator.rs index 7cba12594f9b..58003331e637 100644 --- a/src/operator.rs +++ b/src/operator.rs @@ -190,19 +190,17 @@ pub fn binary_op( } } // These work if one operand is a pointer, the other an integer - Add | Sub + 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 - let (res, over) = self.ptr_int_arithmetic(bin_op, left.to_ptr()?, right.to_bytes()? as i128, left_kind == isize)?; - return Ok((PrimVal::Ptr(res), over)); + return self.ptr_int_arithmetic(bin_op, left.to_ptr()?, right.to_bytes()? as i128, left_kind == isize); } - Add + 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 - let (res, over) = self.ptr_int_arithmetic(bin_op, right.to_ptr()?, left.to_bytes()? as i128, left_kind == isize)?; - return Ok((PrimVal::Ptr(res), over)); + return self.ptr_int_arithmetic(bin_op, right.to_ptr()?, left.to_bytes()? as i128, left_kind == isize); } _ => {} } @@ -287,18 +285,40 @@ fn ptr_int_arithmetic( left: Pointer, right: i128, signed: bool, - ) -> EvalResult<'tcx, (Pointer, bool)> { + ) -> EvalResult<'tcx, (PrimVal, bool)> { use rustc::mir::BinOp::*; + fn map_to_primval((res, over) : (Pointer, 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 - left.overflowing_signed_offset(-right, self.memory.layout), + map_to_primval(left.overflowing_signed_offset(-right, self.memory.layout)), Add if signed => - left.overflowing_signed_offset(right, self.memory.layout), + map_to_primval(left.overflowing_signed_offset(right, self.memory.layout)), Add if !signed => - left.overflowing_offset(right as u64, self.memory.layout), - _ => bug!("ptr_int_arithmetic called on unsupported operation") + map_to_primval(left.overflowing_offset(right as u64, self.memory.layout)), + + 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(Pointer::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)); + } }) } } diff --git a/src/value.rs b/src/value.rs index 66c3049b9c7c..99630f6006f8 100644 --- a/src/value.rs +++ b/src/value.rs @@ -25,7 +25,7 @@ pub(super) fn f64_to_bytes(f: f64) -> u128 { pub(super) fn bytes_to_bool(n: u128) -> bool { // FIXME(solson): Can we reach here due to user error? - debug_assert!(n == 0 || n == 1, "bytes interpreted as bool were {}", n); + assert!(n == 0 || n == 1, "bytes interpreted as bool were {}", n); n & 1 == 1 } diff --git a/tests/run-pass-fullmir/hashmap.rs b/tests/run-pass-fullmir/hashmap.rs index c0c396240733..f4a358174f55 100644 --- a/tests/run-pass-fullmir/hashmap.rs +++ b/tests/run-pass-fullmir/hashmap.rs @@ -2,14 +2,24 @@ use std::hash::BuildHasherDefault; fn main() { - let map : HashMap> = Default::default(); + let mut map : HashMap> = Default::default(); + map.insert(0, 0); assert_eq!(map.values().fold(0, |x, y| x+y), 0); - // TODO: This performs bit operations on the least significant bit of a pointer -// for i in 0..33 { -// map.insert(format!("key_{}", i), i); -// assert_eq!(map.values().fold(0, |x, y| x+y), i*(i+1)/2); -// } + let table_base = map.get(&0).unwrap() as *const _; + + let num = 22; // large enough to trigger a resize + for i in 1..num { + map.insert(i, i); + } + assert!(table_base != map.get(&0).unwrap() as *const _); // make sure relocation happened + assert_eq!(map.values().fold(0, |x, y| x+y), num*(num-1)/2); // check the right things are in the table now + + // Inserting again replaces the existing entries + for i in 0..num { + map.insert(i, num-1-i); + } + assert_eq!(map.values().fold(0, |x, y| x+y), num*(num-1)/2); // TODO: Test Entry API } diff --git a/tests/compile-fail/tag-align-dyn-u64.rs b/tests/run-pass/tag-align-dyn-u64.rs similarity index 87% rename from tests/compile-fail/tag-align-dyn-u64.rs rename to tests/run-pass/tag-align-dyn-u64.rs index dc93965b7e55..81c19022ab08 100644 --- a/tests/compile-fail/tag-align-dyn-u64.rs +++ b/tests/run-pass/tag-align-dyn-u64.rs @@ -28,7 +28,7 @@ fn mk_rec() -> Rec { fn is_u64_aligned(u: &Tag) -> bool { let p: usize = unsafe { mem::transmute(u) }; let u64_align = std::mem::align_of::(); - return (p & (u64_align - 1)) == 0; //~ ERROR a raw memory access tried to access part of a pointer value as raw bytes + return (p & (u64_align - 1)) == 0; } pub fn main() { From 12935b6514c5e09fe67083da3e50dba18b2ae43d Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 22 Jun 2017 14:42:23 -0700 Subject: [PATCH 3/4] add some compile-fail tests --- tests/compile-fail/bitop-beyond-alignment.rs | 37 ++++++++++++++++++++ tests/compile-fail/ctlz_nonzero.rs | 15 ++++++++ tests/compile-fail/cttz_nonzero.rs | 15 ++++++++ 3 files changed, 67 insertions(+) create mode 100644 tests/compile-fail/bitop-beyond-alignment.rs create mode 100644 tests/compile-fail/ctlz_nonzero.rs create mode 100644 tests/compile-fail/cttz_nonzero.rs diff --git a/tests/compile-fail/bitop-beyond-alignment.rs b/tests/compile-fail/bitop-beyond-alignment.rs new file mode 100644 index 000000000000..a30c054ab5d0 --- /dev/null +++ b/tests/compile-fail/bitop-beyond-alignment.rs @@ -0,0 +1,37 @@ +// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(dead_code)] + +use std::mem; + +enum Tag { + Tag2(A) +} + +struct Rec { + c8: u8, + t: Tag +} + +fn mk_rec() -> Rec { + return Rec { c8:0, t:Tag::Tag2(0) }; +} + +fn is_u64_aligned(u: &Tag) -> bool { + let p: usize = unsafe { mem::transmute(u) }; + let u64_align = std::mem::align_of::(); + return (p & (u64_align + 1)) == 0; //~ ERROR a raw memory access tried to access part of a pointer value as raw bytes +} + +pub fn main() { + let x = mk_rec(); + assert!(is_u64_aligned(&x.t)); +} diff --git a/tests/compile-fail/ctlz_nonzero.rs b/tests/compile-fail/ctlz_nonzero.rs new file mode 100644 index 000000000000..704c4d4b7d46 --- /dev/null +++ b/tests/compile-fail/ctlz_nonzero.rs @@ -0,0 +1,15 @@ +#![feature(intrinsics)] + +mod rusti { + extern "rust-intrinsic" { + pub fn ctlz_nonzero(x: T) -> T; + } +} + +pub fn main() { + unsafe { + use rusti::*; + + ctlz_nonzero(0u8); //~ ERROR: ctlz_nonzero called on 0 + } +} diff --git a/tests/compile-fail/cttz_nonzero.rs b/tests/compile-fail/cttz_nonzero.rs new file mode 100644 index 000000000000..eda25c661521 --- /dev/null +++ b/tests/compile-fail/cttz_nonzero.rs @@ -0,0 +1,15 @@ +#![feature(intrinsics)] + +mod rusti { + extern "rust-intrinsic" { + pub fn cttz_nonzero(x: T) -> T; + } +} + +pub fn main() { + unsafe { + use rusti::*; + + cttz_nonzero(0u8); //~ ERROR: cttz_nonzero called on 0 + } +} From d5c031640f3835ae96f6dcf52a6845cddbc80eac Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 22 Jun 2017 20:20:26 -0700 Subject: [PATCH 4/4] use PrimVal::to_bool rather than bytes_to_bool --- src/operator.rs | 3 +-- src/value.rs | 6 ------ 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/operator.rs b/src/operator.rs index 58003331e637..ed69c8043939 100644 --- a/src/operator.rs +++ b/src/operator.rs @@ -13,7 +13,6 @@ bytes_to_f64, f32_to_bytes, f64_to_bytes, - bytes_to_bool, }; impl<'a, 'tcx> EvalContext<'a, 'tcx> { @@ -334,7 +333,7 @@ pub fn unary_op<'tcx>( let bytes = val.to_bytes()?; let result_bytes = match (un_op, val_kind) { - (Not, Bool) => !bytes_to_bool(bytes) as u128, + (Not, Bool) => !val.to_bool()? as u128, (Not, U8) => !(bytes as u8) as u128, (Not, U16) => !(bytes as u16) as u128, diff --git a/src/value.rs b/src/value.rs index 99630f6006f8..9f7d3eafe1fb 100644 --- a/src/value.rs +++ b/src/value.rs @@ -23,12 +23,6 @@ pub(super) fn f64_to_bytes(f: f64) -> u128 { unsafe { transmute::(f) as u128 } } -pub(super) fn bytes_to_bool(n: u128) -> bool { - // FIXME(solson): Can we reach here due to user error? - assert!(n == 0 || n == 1, "bytes interpreted as bool were {}", n); - n & 1 == 1 -} - /// A `Value` represents a single self-contained Rust value. /// /// A `Value` can either refer to a block of memory inside an allocation (`ByRef`) or to a primitve