about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGabriel Majeri <gabriel.majeri6@gmail.com>2018-09-02 17:22:46 +0300
committerGabriel Majeri <gabriel.majeri6@gmail.com>2018-09-08 07:13:19 +0300
commit82cde902c544c4b13a2d0b4aebd406241cca19d8 (patch)
tree5d4af3cc391d9439ce6aad754dcd9f309faa3885
parent4efc0a7811ee0e422dfc5d8a871b54160c18e35b (diff)
downloadrust-82cde902c544c4b13a2d0b4aebd406241cca19d8.tar.gz
rust-82cde902c544c4b13a2d0b4aebd406241cca19d8.zip
Optimize miri checking of integer array/slices
Instead of checking every element, we can check the whole memory
range at once.
-rw-r--r--src/librustc/ich/impls_ty.rs2
-rw-r--r--src/librustc/mir/interpret/error.rs4
-rw-r--r--src/librustc/mir/interpret/mod.rs21
-rw-r--r--src/librustc/mir/interpret/value.rs2
-rw-r--r--src/librustc/ty/structural_impls.rs2
-rw-r--r--src/librustc_mir/interpret/memory.rs22
-rw-r--r--src/librustc_mir/interpret/validity.rs57
-rw-r--r--src/librustc_mir/transform/const_prop.rs2
8 files changed, 81 insertions, 31 deletions
diff --git a/src/librustc/ich/impls_ty.rs b/src/librustc/ich/impls_ty.rs
index 6250e12f430..e078523e866 100644
--- a/src/librustc/ich/impls_ty.rs
+++ b/src/librustc/ich/impls_ty.rs
@@ -523,7 +523,6 @@ for ::mir::interpret::EvalErrorKind<'gcx, O> {
             ReadBytesAsPointer |
             ReadForeignStatic |
             InvalidPointerMath |
-            ReadUndefBytes |
             DeadLocal |
             StackFrameLimitReached |
             OutOfTls |
@@ -550,6 +549,7 @@ for ::mir::interpret::EvalErrorKind<'gcx, O> {
             GeneratorResumedAfterReturn |
             GeneratorResumedAfterPanic |
             InfiniteLoop => {}
+            ReadUndefBytes(offset) => offset.hash_stable(hcx, hasher),
             InvalidDiscriminant(val) => val.hash_stable(hcx, hasher),
             Panic { ref msg, ref file, line, col } => {
                 msg.hash_stable(hcx, hasher);
diff --git a/src/librustc/mir/interpret/error.rs b/src/librustc/mir/interpret/error.rs
index 84d55c84ce1..cccde692bf7 100644
--- a/src/librustc/mir/interpret/error.rs
+++ b/src/librustc/mir/interpret/error.rs
@@ -205,7 +205,7 @@ pub enum EvalErrorKind<'tcx, O> {
     ReadBytesAsPointer,
     ReadForeignStatic,
     InvalidPointerMath,
-    ReadUndefBytes,
+    ReadUndefBytes(Size),
     DeadLocal,
     InvalidBoolOp(mir::BinOp),
     Unimplemented(String),
@@ -331,7 +331,7 @@ impl<'tcx, O> EvalErrorKind<'tcx, O> {
             InvalidPointerMath =>
                 "attempted to do invalid arithmetic on pointers that would leak base addresses, \
                 e.g. comparing pointers into different allocations",
-            ReadUndefBytes =>
+            ReadUndefBytes(_) =>
                 "attempted to read undefined bytes",
             DeadLocal =>
                 "tried to access a dead local variable",
diff --git a/src/librustc/mir/interpret/mod.rs b/src/librustc/mir/interpret/mod.rs
index d40dbae09d2..80c91993818 100644
--- a/src/librustc/mir/interpret/mod.rs
+++ b/src/librustc/mir/interpret/mod.rs
@@ -640,16 +640,23 @@ impl UndefMask {
     }
 
     /// Check whether the range `start..end` (end-exclusive) is entirely defined.
-    pub fn is_range_defined(&self, start: Size, end: Size) -> bool {
+    ///
+    /// Returns `Ok(())` if it's defined. Otherwise returns the index of the byte
+    /// at which the first undefined access begins.
+    #[inline]
+    pub fn is_range_defined(&self, start: Size, end: Size) -> Result<(), Size> {
         if end > self.len {
-            return false;
+            return Err(self.len);
         }
-        for i in start.bytes()..end.bytes() {
-            if !self.get(Size::from_bytes(i)) {
-                return false;
-            }
+
+        let idx = (start.bytes()..end.bytes())
+            .map(|i| Size::from_bytes(i))
+            .find(|&i| !self.get(i));
+
+        match idx {
+            Some(idx) => Err(idx),
+            None => Ok(())
         }
-        true
     }
 
     pub fn set_range(&mut self, start: Size, end: Size, new_state: bool) {
diff --git a/src/librustc/mir/interpret/value.rs b/src/librustc/mir/interpret/value.rs
index 11a4f8b884e..2d72be39edf 100644
--- a/src/librustc/mir/interpret/value.rs
+++ b/src/librustc/mir/interpret/value.rs
@@ -359,7 +359,7 @@ impl<'tcx> ScalarMaybeUndef {
     pub fn not_undef(self) -> EvalResult<'static, Scalar> {
         match self {
             ScalarMaybeUndef::Scalar(scalar) => Ok(scalar),
-            ScalarMaybeUndef::Undef => err!(ReadUndefBytes),
+            ScalarMaybeUndef::Undef => err!(ReadUndefBytes(Size::from_bytes(0))),
         }
     }
 
diff --git a/src/librustc/ty/structural_impls.rs b/src/librustc/ty/structural_impls.rs
index 60b85e8a8eb..0011f8b7e2f 100644
--- a/src/librustc/ty/structural_impls.rs
+++ b/src/librustc/ty/structural_impls.rs
@@ -511,7 +511,7 @@ impl<'a, 'tcx, O: Lift<'tcx>> Lift<'tcx> for interpret::EvalErrorKind<'a, O> {
             ReadBytesAsPointer => ReadBytesAsPointer,
             ReadForeignStatic => ReadForeignStatic,
             InvalidPointerMath => InvalidPointerMath,
-            ReadUndefBytes => ReadUndefBytes,
+            ReadUndefBytes(offset) => ReadUndefBytes(offset),
             DeadLocal => DeadLocal,
             InvalidBoolOp(bop) => InvalidBoolOp(bop),
             Unimplemented(ref s) => Unimplemented(s.clone()),
diff --git a/src/librustc_mir/interpret/memory.rs b/src/librustc_mir/interpret/memory.rs
index 59bebbb87a7..b3957e40fca 100644
--- a/src/librustc_mir/interpret/memory.rs
+++ b/src/librustc_mir/interpret/memory.rs
@@ -453,7 +453,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
                     }
                     relocations.push((i, target_id));
                 }
-                if alloc.undef_mask.is_range_defined(i, i + Size::from_bytes(1)) {
+                if alloc.undef_mask.is_range_defined(i, i + Size::from_bytes(1)).is_ok() {
                     // this `as usize` is fine, since `i` came from a `usize`
                     write!(msg, "{:02x} ", alloc.bytes[i.bytes() as usize]).unwrap();
                 } else {
@@ -789,7 +789,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
         )?;
         // Undef check happens *after* we established that the alignment is correct.
         // We must not return Ok() for unaligned pointers!
-        if !self.is_defined(ptr, size)? {
+        if self.check_defined(ptr, size).is_err() {
             // this inflates undefined bytes to the entire scalar, even if only a few
             // bytes are undefined
             return Ok(ScalarMaybeUndef::Undef);
@@ -991,21 +991,15 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
         Ok(())
     }
 
-    fn is_defined(&self, ptr: Pointer, size: Size) -> EvalResult<'tcx, bool> {
+    /// Checks that a range of bytes is defined. If not, returns the `ReadUndefBytes`
+    /// error which will report the first byte which is undefined.
+    #[inline]
+    fn check_defined(&self, ptr: Pointer, size: Size) -> EvalResult<'tcx> {
         let alloc = self.get(ptr.alloc_id)?;
-        Ok(alloc.undef_mask.is_range_defined(
+        alloc.undef_mask.is_range_defined(
             ptr.offset,
             ptr.offset + size,
-        ))
-    }
-
-    #[inline]
-    fn check_defined(&self, ptr: Pointer, size: Size) -> EvalResult<'tcx> {
-        if self.is_defined(ptr, size)? {
-            Ok(())
-        } else {
-            err!(ReadUndefBytes)
-        }
+        ).or_else(|idx| err!(ReadUndefBytes(idx)))
     }
 
     pub fn mark_definedness(
diff --git a/src/librustc_mir/interpret/validity.rs b/src/librustc_mir/interpret/validity.rs
index 329651f038f..8292869ca58 100644
--- a/src/librustc_mir/interpret/validity.rs
+++ b/src/librustc_mir/interpret/validity.rs
@@ -290,7 +290,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
                             Ok(val) => val,
                             Err(err) => match err.kind {
                                 EvalErrorKind::PointerOutOfBounds { .. } |
-                                EvalErrorKind::ReadUndefBytes =>
+                                EvalErrorKind::ReadUndefBytes(_) =>
                                     return validation_failure!(
                                         "uninitialized or out-of-bounds memory", path
                                     ),
@@ -333,16 +333,16 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
                 // The fields don't need to correspond to any bit pattern of the union's fields.
                 // See https://github.com/rust-lang/rust/issues/32836#issuecomment-406875389
             },
-            layout::FieldPlacement::Array { .. } if !dest.layout.is_zst() => {
+            layout::FieldPlacement::Array { stride, .. } if !dest.layout.is_zst() => {
                 let dest = dest.to_mem_place(); // non-ZST array/slice/str cannot be immediate
-                // Special handling for strings to verify UTF-8
                 match dest.layout.ty.sty {
+                    // Special handling for strings to verify UTF-8
                     ty::Str => {
                         match self.read_str(dest) {
                             Ok(_) => {},
                             Err(err) => match err.kind {
                                 EvalErrorKind::PointerOutOfBounds { .. } |
-                                EvalErrorKind::ReadUndefBytes =>
+                                EvalErrorKind::ReadUndefBytes(_) =>
                                     // The error here looks slightly different than it does
                                     // for slices, because we do not report the index into the
                                     // str at which we are OOB.
@@ -356,6 +356,55 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
                             }
                         }
                     }
+                    // Special handling for arrays/slices of builtin integer types
+                    ty::Array(tys, ..) | ty::Slice(tys) if {
+                        // This optimization applies only for integer types
+                        match tys.sty {
+                            ty::Int(..) | ty::Uint(..) => true,
+                            _ => false,
+                        }
+                    } => {
+                        // This is the length of the array/slice.
+                        let len = dest.len(self)?;
+                        // Since primitive types are naturally aligned and tightly packed in arrays,
+                        // we can use the stride to get the size of the integral type.
+                        let ty_size = stride.bytes();
+                        // This is the size in bytes of the whole array.
+                        let size = Size::from_bytes(ty_size * len);
+
+                        match self.memory.read_bytes(dest.ptr, size) {
+                            // In the happy case, we needn't check anything else.
+                            Ok(_) => {},
+                            // Some error happened, try to provide a more detailed description.
+                            Err(err) => {
+                                // For some errors we might be able to provide extra information
+                                match err.kind {
+                                    EvalErrorKind::ReadUndefBytes(offset) => {
+                                        // Some byte was undefined, determine which
+                                        // element that byte belongs to so we can
+                                        // provide an index.
+                                        let i = (offset.bytes() / ty_size) as usize;
+                                        path.push(PathElem::ArrayElem(i));
+
+                                        return validation_failure!(
+                                            "undefined bytes", path
+                                        )
+                                    },
+                                    EvalErrorKind::PointerOutOfBounds { allocation_size, .. } => {
+                                        // If the array access is out-of-bounds, the first
+                                        // undefined access is the after the end of the array.
+                                        let i = (allocation_size.bytes() * ty_size) as usize;
+                                        path.push(PathElem::ArrayElem(i));
+                                    },
+                                    _ => (),
+                                }
+
+                                return validation_failure!(
+                                    "uninitialized or out-of-bounds memory", path
+                                )
+                            }
+                        }
+                    },
                     _ => {
                         // This handles the unsized case correctly as well, as well as
                         // SIMD an all sorts of other array-like types.
diff --git a/src/librustc_mir/transform/const_prop.rs b/src/librustc_mir/transform/const_prop.rs
index e2b1a255eac..310f5d7731f 100644
--- a/src/librustc_mir/transform/const_prop.rs
+++ b/src/librustc_mir/transform/const_prop.rs
@@ -181,7 +181,7 @@ impl<'b, 'a, 'tcx:'b> ConstPropagator<'b, 'a, 'tcx> {
                     | InvalidMemoryLockRelease { .. }
                     | DeallocatedLockedMemory { .. }
                     | InvalidPointerMath
-                    | ReadUndefBytes
+                    | ReadUndefBytes(_)
                     | DeadLocal
                     | InvalidBoolOp(_)
                     | DerefFunctionPointer