From 8a0aa9291a423cd031d040b1d7a38d728cc6d60a Mon Sep 17 00:00:00 2001 From: Scott Olson Date: Wed, 6 Apr 2016 03:45:06 -0600 Subject: [PATCH] Switch to bitmask-based undef mask. --- src/interpreter.rs | 35 ++--- src/memory.rs | 337 ++++++++++++++------------------------------- test/products.rs | 6 +- 3 files changed, 122 insertions(+), 256 deletions(-) diff --git a/src/interpreter.rs b/src/interpreter.rs index 94e306140e6d..b050e042f711 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -19,7 +19,7 @@ use syntax::attr; use syntax::codemap::{self, DUMMY_SP}; use error::{EvalError, EvalResult}; -use memory::{self, FieldRepr, Memory, Pointer, Repr}; +use memory::{FieldRepr, Memory, Pointer, Repr}; use primval::{self, PrimVal}; const TRACE_EXECUTION: bool = false; @@ -150,31 +150,32 @@ impl<'a, 'tcx: 'a, 'arena> Interpreter<'a, 'tcx, 'arena> { r } - fn run(&mut self) -> EvalResult<()> { - use std::fmt::Debug; - fn print_trace(t: &T, suffix: &'static str, indent: usize) { - if !TRACE_EXECUTION { return; } - for _ in 0..indent { print!(" "); } - println!("{:?}{}", t, suffix); - } + fn log(&self, extra_indent: usize, f: F) where F: FnOnce() { + let indent = self.stack.len() - 1 + extra_indent; + if !TRACE_EXECUTION { return; } + for _ in 0..indent { print!(" "); } + f(); + println!(""); + } + fn run(&mut self) -> EvalResult<()> { 'outer: while !self.stack.is_empty() { let mut current_block = self.frame().next_block; loop { - print_trace(¤t_block, ":", self.stack.len()); + self.log(0, || print!("{:?}", current_block)); let current_mir = self.mir().clone(); // Cloning a reference. let block_data = current_mir.basic_block_data(current_block); for stmt in &block_data.statements { - print_trace(stmt, "", self.stack.len() + 1); + self.log(1, || print!("{:?}", stmt)); let mir::StatementKind::Assign(ref lvalue, ref rvalue) = stmt.kind; let result = self.eval_assignment(lvalue, rvalue); try!(self.maybe_report(stmt.span, result)); } let terminator = block_data.terminator(); - print_trace(&terminator.kind, "", self.stack.len() + 1); + self.log(1, || print!("{:?}", terminator.kind)); let result = self.eval_terminator(terminator); match try!(self.maybe_report(terminator.span, result)) { @@ -1154,15 +1155,6 @@ pub fn get_impl_method<'tcx>( } pub fn interpret_start_points<'tcx>(tcx: &TyCtxt<'tcx>, mir_map: &MirMap<'tcx>) { - /// Print the given allocation and all allocations it depends on. - fn print_allocation_tree(memory: &Memory, alloc_id: memory::AllocId) { - let alloc = memory.get(alloc_id).unwrap(); - println!(" {:?}: {:?}", alloc_id, alloc); - for &target_alloc in alloc.relocations.values() { - print_allocation_tree(memory, target_alloc); - } - } - for (&id, mir) in &mir_map.map { for attr in tcx.map.attrs(id) { use syntax::attr::AttrMetaMethods; @@ -1188,8 +1180,7 @@ pub fn interpret_start_points<'tcx>(tcx: &TyCtxt<'tcx>, mir_map: &MirMap<'tcx>) tcx.sess.abort_if_errors(); if let Some(ret) = return_ptr { - println!("Result:"); - print_allocation_tree(&miri.memory, ret.alloc_id); + miri.memory.dump(ret.alloc_id); println!(""); } } diff --git a/src/memory.rs b/src/memory.rs index ccb9396e95e6..113930f986d2 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -1,8 +1,7 @@ use byteorder::{ByteOrder, NativeEndian, ReadBytesExt, WriteBytesExt}; -use std::collections::{btree_map, BTreeMap, HashMap}; use std::collections::Bound::{Included, Excluded}; -use std::mem; -use std::ptr; +use std::collections::{btree_map, BTreeMap, HashMap, HashSet, VecDeque}; +use std::{iter, mem, ptr}; use error::{EvalError, EvalResult}; use primval::PrimVal; @@ -66,7 +65,7 @@ pub struct AllocId(u64); pub struct Allocation { pub bytes: Box<[u8]>, pub relocations: BTreeMap, - pub undef_mask: Option>, + pub undef_mask: UndefMask, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -107,7 +106,7 @@ impl Memory { let alloc = Allocation { bytes: vec![0; size].into_boxed_slice(), relocations: BTreeMap::new(), - undef_mask: None, + undef_mask: UndefMask::new(), }; self.alloc_map.insert(self.next_id, alloc); self.next_id += 1; @@ -129,6 +128,48 @@ impl Memory { self.alloc_map.get_mut(&id.0).ok_or(EvalError::DanglingPointerDeref) } + /// Print an allocation and all allocations it points to, recursively. + pub fn dump(&self, id: AllocId) { + let mut allocs_seen = HashSet::new(); + let mut allocs_to_print = VecDeque::new(); + allocs_to_print.push_back(id); + + while let Some(id) = allocs_to_print.pop_front() { + allocs_seen.insert(id.0); + let alloc = self.get(id).unwrap(); + let prefix = format!("Alloc {:<5} ", format!("{}:", id.0)); + print!("{}", prefix); + let mut relocations = vec![]; + + for i in 0..alloc.bytes.len() { + if let Some(&target_id) = alloc.relocations.get(&i) { + if !allocs_seen.contains(&target_id.0) { + allocs_to_print.push_back(target_id); + } + relocations.push((i, target_id.0)); + } + if alloc.undef_mask.is_range_defined(i, i+1) { + print!("{:02x} ", alloc.bytes[i]); + } else { + print!("__ "); + } + } + println!(""); + + if !relocations.is_empty() { + print!("{:1$}", "", prefix.len()); // Print spaces. + let mut pos = 0; + let relocation_width = (self.pointer_size - 1) * 3; + for (i, target_id) in relocations { + print!("{:1$}", "", (i - pos) * 3); + print!("└{0:─^1$}┘ ", format!("({})", target_id), relocation_width); + pos = i + self.pointer_size; + } + println!(""); + } + } + } + //////////////////////////////////////////////////////////////////////////////// // Byte accessors //////////////////////////////////////////////////////////////////////////////// @@ -305,8 +346,8 @@ impl Memory { // Mark parts of the outermost relocations as undefined if they partially fall outside the // given range. - if first < start { alloc.mark_definedness(first, start, false); } - if last > end { alloc.mark_definedness(end, last, false); } + if first < start { alloc.undef_mask.set_range(first, start, false); } + if last > end { alloc.undef_mask.set_range(end, last, false); } // Forget all the relocations. for k in keys { alloc.relocations.remove(&k); } @@ -340,7 +381,7 @@ impl Memory { fn check_defined(&self, ptr: Pointer, size: usize) -> EvalResult<()> { let alloc = try!(self.get(ptr.alloc_id)); - if !alloc.is_range_defined(ptr.offset, ptr.offset + size) { + if !alloc.undef_mask.is_range_defined(ptr.offset, ptr.offset + size) { return Err(EvalError::ReadUndefBytes); } Ok(()) @@ -350,7 +391,7 @@ impl Memory { -> EvalResult<()> { let mut alloc = try!(self.get_mut(ptr.alloc_id)); - alloc.mark_definedness(ptr.offset, ptr.offset + size, new_state); + alloc.undef_mask.set_range(ptr.offset, ptr.offset + size, new_state); Ok(()) } } @@ -359,238 +400,72 @@ impl Memory { // Undefined byte tracking //////////////////////////////////////////////////////////////////////////////// -impl Allocation { - /// Check whether the range `start..end` (end-exclusive) in this allocation is entirely - /// defined. +type Block = u64; +const BLOCK_SIZE: usize = 64; + +#[derive(Clone, Debug)] +pub struct UndefMask { + blocks: Vec, + len: usize, +} + +impl UndefMask { + fn new() -> Self { + UndefMask { + blocks: vec![], + len: 0, + } + } + + /// Check whether the range `start..end` (end-exclusive) is entirely defined. fn is_range_defined(&self, start: usize, end: usize) -> bool { - debug_assert!(start <= end); - debug_assert!(end <= self.bytes.len()); - - // An empty range is always fully defined. - if start == end { - return true; + if end > self.len { return false; } + for i in start..end { + if !self.get(i) { return false; } } + true + } - match self.undef_mask { - Some(ref undef_mask) => { - // If `start` lands directly on a boundary, it belongs to the range after the - // boundary, hence the increment in the `Ok` arm. - let i = match undef_mask.binary_search(&start) { Ok(j) => j + 1, Err(j) => j }; + fn set_range(&mut self, start: usize, end: usize, new_state: bool) { + let len = self.len; + if end > len { self.grow(end - len, new_state); } + self.set_range_inbounds(start, end, new_state); + } - // The range is fully defined if and only if both: - // 1. The start value falls into a defined range (with even parity). - // 2. The end value is in the same range as the start value. - i % 2 == 0 && undef_mask.get(i).map(|&x| end <= x).unwrap_or(true) - } - None => false, + fn set_range_inbounds(&mut self, start: usize, end: usize, new_state: bool) { + for i in start..end { self.set(i, new_state); } + } + + fn get(&self, i: usize) -> bool { + let (block, bit) = bit_index(i); + (self.blocks[block] & 1 << bit) != 0 + } + + fn set(&mut self, i: usize, new_state: bool) { + let (block, bit) = bit_index(i); + if new_state { + self.blocks[block] |= 1 << bit; + } else { + self.blocks[block] &= !(1 << bit); } } - /// Mark the range `start..end` (end-exclusive) as defined or undefined, depending on - /// `new_state`. - fn mark_definedness(&mut self, start: usize, end: usize, new_state: bool) { - debug_assert!(start <= end); - debug_assert!(end <= self.bytes.len()); - - // There is no need to track undef masks for zero-sized allocations. - let len = self.bytes.len(); - if len == 0 { - return; - } - - // Returns whether the new state matches the state of a given undef mask index. The way - // undef masks are represented, boundaries at even indices are undefined and those at odd - // indices are defined. - let index_matches_new_state = |i| i % 2 == new_state as usize; - - // Lookup the undef mask index where the given endpoint `i` is or should be inserted. - let lookup_endpoint = |undef_mask: &[usize], i: usize| -> (usize, bool) { - let (index, should_insert); - match undef_mask.binary_search(&i) { - // Region endpoint is on an undef mask boundary. - Ok(j) => { - // This endpoint's index must be incremented if the boundary's state matches - // the region's new state so that the boundary is: - // 1. Excluded from deletion when handling the inclusive left-hand endpoint. - // 2. Included for deletion when handling the exclusive right-hand endpoint. - index = j + index_matches_new_state(j) as usize; - - // Don't insert a new mask boundary; simply reuse or delete the matched one. - should_insert = false; - } - - // Region endpoint is not on a mask boundary. - Err(j) => { - // This is the index after the nearest mask boundary which has the same state. - index = j; - - // Insert a new boundary if this endpoint's state doesn't match the state of - // this position. - should_insert = index_matches_new_state(j); - } - } - (index, should_insert) - }; - - match self.undef_mask { - // There is an existing undef mask, with arbitrary existing boundaries. - Some(ref mut undef_mask) => { - // Determine where the new range's endpoints fall within the current undef mask. - let (start_index, insert_start) = lookup_endpoint(undef_mask, start); - let (end_index, insert_end) = lookup_endpoint(undef_mask, end); - - // Delete all the undef mask boundaries overwritten by the new range. - undef_mask.drain(start_index..end_index); - - // Insert any new boundaries deemed necessary with two exceptions: - // 1. Never insert an endpoint equal to the allocation length; it's implicit. - // 2. Never insert a start boundary equal to the end boundary. - if insert_end && end != len { - undef_mask.insert(start_index, end); - } - if insert_start && start != end { - undef_mask.insert(start_index, start); - } - } - - // There is no existing undef mask. This is taken as meaning the entire allocation is - // currently undefined. If the new state is false, meaning undefined, do nothing. - None => if new_state { - let mut mask = if start == 0 { - // 0..end is defined. - Vec::new() - } else { - // 0..0 is defined, 0..start is undefined, start..end is defined. - vec![0, start] - }; - - // Don't insert the end boundary if it's equal to the allocation length; that - // boundary is implicit. - if end != len { - mask.push(end); - } - self.undef_mask = Some(mask); - }, + fn grow(&mut self, amount: usize, new_state: bool) { + let unused_trailing_bits = self.blocks.len() * BLOCK_SIZE - self.len; + if amount > unused_trailing_bits { + let additional_blocks = amount / BLOCK_SIZE + 1; + self.blocks.extend(iter::repeat(0).take(additional_blocks)); } + let start = self.len; + self.len += amount; + self.set_range_inbounds(start, start + amount, new_state); } } -#[cfg(test)] -mod test { - use memory::Allocation; - use std::collections::BTreeMap; +// fn uniform_block(state: bool) -> Block { +// if state { !0 } else { 0 } +// } - fn alloc_with_mask(len: usize, undef_mask: Option>) -> Allocation { - Allocation { - bytes: vec![0; len].into_boxed_slice(), - relocations: BTreeMap::new(), - undef_mask: undef_mask, - } - } - - #[test] - fn large_undef_mask() { - let mut alloc = alloc_with_mask(20, Some(vec![4, 8, 12, 16])); - - assert!(alloc.is_range_defined(0, 0)); - assert!(alloc.is_range_defined(0, 3)); - assert!(alloc.is_range_defined(0, 4)); - assert!(alloc.is_range_defined(1, 3)); - assert!(alloc.is_range_defined(1, 4)); - assert!(alloc.is_range_defined(4, 4)); - assert!(!alloc.is_range_defined(0, 5)); - assert!(!alloc.is_range_defined(1, 5)); - assert!(!alloc.is_range_defined(4, 5)); - assert!(!alloc.is_range_defined(4, 8)); - assert!(alloc.is_range_defined(8, 12)); - assert!(!alloc.is_range_defined(12, 16)); - assert!(alloc.is_range_defined(16, 20)); - assert!(!alloc.is_range_defined(15, 20)); - assert!(!alloc.is_range_defined(0, 20)); - - alloc.mark_definedness(8, 11, false); - assert_eq!(alloc.undef_mask, Some(vec![4, 11, 12, 16])); - - alloc.mark_definedness(8, 11, true); - assert_eq!(alloc.undef_mask, Some(vec![4, 8, 12, 16])); - - alloc.mark_definedness(8, 12, false); - assert_eq!(alloc.undef_mask, Some(vec![4, 16])); - - alloc.mark_definedness(8, 12, true); - assert_eq!(alloc.undef_mask, Some(vec![4, 8, 12, 16])); - - alloc.mark_definedness(9, 11, true); - assert_eq!(alloc.undef_mask, Some(vec![4, 8, 12, 16])); - - alloc.mark_definedness(9, 11, false); - assert_eq!(alloc.undef_mask, Some(vec![4, 8, 9, 11, 12, 16])); - - alloc.mark_definedness(9, 10, true); - assert_eq!(alloc.undef_mask, Some(vec![4, 8, 10, 11, 12, 16])); - - alloc.mark_definedness(8, 12, true); - assert_eq!(alloc.undef_mask, Some(vec![4, 8, 12, 16])); - } - - #[test] - fn empty_undef_mask() { - let mut alloc = alloc_with_mask(0, None); - assert!(alloc.is_range_defined(0, 0)); - - alloc.mark_definedness(0, 0, false); - assert_eq!(alloc.undef_mask, None); - assert!(alloc.is_range_defined(0, 0)); - - alloc.mark_definedness(0, 0, true); - assert_eq!(alloc.undef_mask, None); - assert!(alloc.is_range_defined(0, 0)); - } - - #[test] - fn small_undef_mask() { - let mut alloc = alloc_with_mask(8, None); - - alloc.mark_definedness(0, 4, false); - assert_eq!(alloc.undef_mask, None); - - alloc.mark_definedness(0, 4, true); - assert_eq!(alloc.undef_mask, Some(vec![4])); - - alloc.mark_definedness(4, 8, false); - assert_eq!(alloc.undef_mask, Some(vec![4])); - - alloc.mark_definedness(4, 8, true); - assert_eq!(alloc.undef_mask, Some(vec![])); - - alloc.mark_definedness(0, 8, true); - assert_eq!(alloc.undef_mask, Some(vec![])); - - alloc.mark_definedness(0, 8, false); - assert_eq!(alloc.undef_mask, Some(vec![0])); - - alloc.mark_definedness(0, 8, true); - assert_eq!(alloc.undef_mask, Some(vec![])); - - alloc.mark_definedness(4, 8, false); - assert_eq!(alloc.undef_mask, Some(vec![4])); - - alloc.mark_definedness(0, 8, false); - assert_eq!(alloc.undef_mask, Some(vec![0])); - - alloc.mark_definedness(2, 5, true); - assert_eq!(alloc.undef_mask, Some(vec![0, 2, 5])); - - alloc.mark_definedness(4, 6, false); - assert_eq!(alloc.undef_mask, Some(vec![0, 2, 4])); - - alloc.mark_definedness(0, 3, true); - assert_eq!(alloc.undef_mask, Some(vec![4])); - - alloc.mark_definedness(2, 6, true); - assert_eq!(alloc.undef_mask, Some(vec![6])); - - alloc.mark_definedness(3, 7, false); - assert_eq!(alloc.undef_mask, Some(vec![3])); - } +fn bit_index(bits: usize) -> (usize, usize) { + (bits / BLOCK_SIZE, bits % BLOCK_SIZE) } diff --git a/test/products.rs b/test/products.rs index 4028670e6f2b..ba72bfb52a70 100644 --- a/test/products.rs +++ b/test/products.rs @@ -2,17 +2,17 @@ #![allow(dead_code, unused_attributes)] #[miri_run] -fn tuple() -> (i64,) { +fn tuple() -> (i16,) { (1,) } #[miri_run] -fn tuple_2() -> (i64, i64) { +fn tuple_2() -> (i16, i16) { (1, 2) } #[miri_run] -fn tuple_5() -> (i64, i64, i64, i64, i64) { +fn tuple_5() -> (i16, i16, i16, i16, i16) { (1, 2, 3, 4, 5) }