]> git.lizzy.rs Git - rust.git/blobdiff - src/helpers.rs
Fix bug with reporting wrong thread for races with reads & add thread ids to data...
[rust.git] / src / helpers.rs
index 76c5308cfd35b1ecd20c84ff8c3e42947c540245..4c989db0170b5408c71116555a8fcfc0702aab17 100644 (file)
@@ -1,21 +1,20 @@
-use std::ffi::{OsStr, OsString};
-use std::{iter, mem};
-use std::convert::TryFrom;
-
-use rustc::mir;
-use rustc::ty::{
-    self,
-    layout::{self, LayoutOf, Size, TyLayout},
-    List, TyCtxt,
-};
+use std::convert::{TryFrom, TryInto};
+use std::mem;
+use std::num::NonZeroUsize;
+use std::time::Duration;
+
+use log::trace;
+
+use rustc_middle::mir;
+use rustc_middle::ty::{self, List, TyCtxt, layout::TyAndLayout};
 use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX};
-use rustc_span::source_map::DUMMY_SP;
+use rustc_target::abi::{LayoutOf, Size, FieldsShape, Variants};
 
 use rand::RngCore;
 
 use crate::*;
 
-impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
 
 /// Gets an instance for a path.
 fn try_resolve_did<'mir, 'tcx>(tcx: TyCtxt<'tcx>, path: &[&str]) -> Option<DefId> {
@@ -56,11 +55,11 @@ fn resolve_path(&self, path: &[&str]) -> ty::Instance<'tcx> {
     fn eval_path_scalar(
         &mut self,
         path: &[&str],
-    ) -> InterpResult<'tcx, ScalarMaybeUndef<Tag>> {
+    ) -> InterpResult<'tcx, ScalarMaybeUninit<Tag>> {
         let this = self.eval_context_mut();
         let instance = this.resolve_path(path);
         let cid = GlobalId { instance, promoted: None };
-        let const_val = this.const_eval_raw(cid)?;
+        let const_val = this.eval_to_allocation(cid)?;
         let const_val = this.read_scalar(const_val.into())?;
         return Ok(const_val);
     }
@@ -69,18 +68,39 @@ fn eval_path_scalar(
     fn eval_libc(&mut self, name: &str) -> InterpResult<'tcx, Scalar<Tag>> {
         self.eval_context_mut()
             .eval_path_scalar(&["libc", name])?
-            .not_undef()
+            .check_init()
     }
 
     /// Helper function to get a `libc` constant as an `i32`.
     fn eval_libc_i32(&mut self, name: &str) -> InterpResult<'tcx, i32> {
+        // TODO: Cache the result.
         self.eval_libc(name)?.to_i32()
     }
 
-    /// Helper function to get the `TyLayout` of a `libc` type
-    fn libc_ty_layout(&mut self, name: &str) -> InterpResult<'tcx, TyLayout<'tcx>> {
+    /// Helper function to get a `windows` constant as a `Scalar`.
+    fn eval_windows(&mut self, module: &str, name: &str) -> InterpResult<'tcx, Scalar<Tag>> {
+        self.eval_context_mut()
+            .eval_path_scalar(&["std", "sys", "windows", module, name])?
+            .check_init()
+    }
+
+    /// Helper function to get a `windows` constant as an `u64`.
+    fn eval_windows_u64(&mut self, module: &str, name: &str) -> InterpResult<'tcx, u64> {
+        // TODO: Cache the result.
+        self.eval_windows(module, name)?.to_u64()
+    }
+
+    /// Helper function to get the `TyAndLayout` of a `libc` type
+    fn libc_ty_layout(&mut self, name: &str) -> InterpResult<'tcx, TyAndLayout<'tcx>> {
+        let this = self.eval_context_mut();
+        let ty = this.resolve_path(&["libc", name]).ty(*this.tcx, ty::ParamEnv::reveal_all());
+        this.layout_of(ty)
+    }
+
+    /// Helper function to get the `TyAndLayout` of a `windows` type
+    fn windows_ty_layout(&mut self, name: &str) -> InterpResult<'tcx, TyAndLayout<'tcx>> {
         let this = self.eval_context_mut();
-        let ty = this.resolve_path(&["libc", name]).monomorphic_ty(*this.tcx);
+        let ty = this.resolve_path(&["std", "sys", "windows", "c", name]).ty(*this.tcx, ty::ParamEnv::reveal_all());
         this.layout_of(ty)
     }
 
@@ -92,7 +112,7 @@ fn write_null(&mut self, dest: PlaceTy<'tcx, Tag>) -> InterpResult<'tcx> {
     /// Test if this immediate equals 0.
     fn is_null(&self, val: Scalar<Tag>) -> InterpResult<'tcx, bool> {
         let this = self.eval_context_ref();
-        let null = Scalar::from_int(0, this.memory.pointer_size());
+        let null = Scalar::null_ptr(this);
         this.ptr_eq(val, null)
     }
 
@@ -106,7 +126,7 @@ fn test_null(&self, val: Scalar<Tag>) -> InterpResult<'tcx, Option<Scalar<Tag>>>
     fn local_place(&mut self, local: mir::Local) -> InterpResult<'tcx, PlaceTy<'tcx, Tag>> {
         let this = self.eval_context_mut();
         let place = mir::Place { local: local, projection: List::empty() };
-        this.eval_place(&place)
+        this.eval_place(place)
     }
 
     /// Generate some random bytes, and write them to `dest`.
@@ -148,13 +168,7 @@ fn call_function(
 
         // Push frame.
         let mir = &*this.load_mir(f.def, None)?;
-        let span = this
-            .stack()
-            .last()
-            .and_then(Frame::current_source_info)
-            .map(|si| si.span)
-            .unwrap_or(DUMMY_SP);
-        this.push_stack_frame(f, span, mir, dest, stack_pop)?;
+        this.push_stack_frame(f, mir, dest, stack_pop)?;
 
         // Initialize arguments.
         let mut callee_args = this.frame().body.args_iter();
@@ -252,7 +266,7 @@ struct UnsafeCellVisitor<'ecx, 'mir, 'tcx, F>
             unsafe_cell_action: F,
         }
 
-        impl<'ecx, 'mir, 'tcx, F> ValueVisitor<'mir, 'tcx, Evaluator<'tcx>>
+        impl<'ecx, 'mir, 'tcx: 'mir, F> ValueVisitor<'mir, 'tcx, Evaluator<'mir, 'tcx>>
             for UnsafeCellVisitor<'ecx, 'mir, 'tcx, F>
         where
             F: FnMut(MPlaceTy<'tcx, Tag>) -> InterpResult<'tcx>,
@@ -267,7 +281,7 @@ fn ecx(&self) -> &MiriEvalContext<'mir, 'tcx> {
             // Hook to detect `UnsafeCell`.
             fn visit_value(&mut self, v: MPlaceTy<'tcx, Tag>) -> InterpResult<'tcx> {
                 trace!("UnsafeCellVisitor: {:?} {:?}", *v, v.layout.ty);
-                let is_unsafe_cell = match v.layout.ty.kind {
+                let is_unsafe_cell = match v.layout.ty.kind() {
                     ty::Adt(adt, _) =>
                         Some(adt.did) == self.ecx.tcx.lang_items().unsafe_cell_type(),
                     _ => false,
@@ -278,12 +292,15 @@ fn visit_value(&mut self, v: MPlaceTy<'tcx, Tag>) -> InterpResult<'tcx> {
                 } else if self.ecx.type_is_freeze(v.layout.ty) {
                     // This is `Freeze`, there cannot be an `UnsafeCell`
                     Ok(())
+                } else if matches!(v.layout.fields, FieldsShape::Union(..)) {
+                    // A (non-frozen) union. We fall back to whatever the type says.
+                    (self.unsafe_cell_action)(v)
                 } else {
                     // We want to not actually read from memory for this visit. So, before
                     // walking this value, we have to make sure it is not a
                     // `Variants::Multiple`.
                     match v.layout.variants {
-                        layout::Variants::Multiple { .. } => {
+                        Variants::Multiple { .. } => {
                             // A multi-variant enum, or generator, or so.
                             // Treat this like a union: without reading from memory,
                             // we cannot determine the variant we are in. Reading from
@@ -293,7 +310,7 @@ fn visit_value(&mut self, v: MPlaceTy<'tcx, Tag>) -> InterpResult<'tcx> {
                             // `UnsafeCell` action.
                             (self.unsafe_cell_action)(v)
                         }
-                        layout::Variants::Single { .. } => {
+                        Variants::Single { .. } => {
                             // Proceed further, try to find where exactly that `UnsafeCell`
                             // is hiding.
                             self.walk_value(v)
@@ -309,39 +326,32 @@ fn visit_aggregate(
                 fields: impl Iterator<Item = InterpResult<'tcx, MPlaceTy<'tcx, Tag>>>,
             ) -> InterpResult<'tcx> {
                 match place.layout.fields {
-                    layout::FieldPlacement::Array { .. } => {
+                    FieldsShape::Array { .. } => {
                         // For the array layout, we know the iterator will yield sorted elements so
                         // we can avoid the allocation.
                         self.walk_aggregate(place, fields)
                     }
-                    layout::FieldPlacement::Arbitrary { .. } => {
+                    FieldsShape::Arbitrary { .. } => {
                         // Gather the subplaces and sort them before visiting.
                         let mut places =
                             fields.collect::<InterpResult<'tcx, Vec<MPlaceTy<'tcx, Tag>>>>()?;
                         places.sort_by_key(|place| place.ptr.assert_ptr().offset);
                         self.walk_aggregate(place, places.into_iter().map(Ok))
                     }
-                    layout::FieldPlacement::Union { .. } => {
+                    FieldsShape::Union { .. } | FieldsShape::Primitive => {
                         // Uh, what?
-                        bug!("a union is not an aggregate we should ever visit")
+                        bug!("unions/primitives are not aggregates we should ever visit")
                     }
                 }
             }
 
-            // We have to do *something* for unions.
-            fn visit_union(&mut self, v: MPlaceTy<'tcx, Tag>, fields: usize) -> InterpResult<'tcx> {
-                assert!(fields > 0); // we should never reach "pseudo-unions" with 0 fields, like primitives
-
-                // With unions, we fall back to whatever the type says, to hopefully be consistent
-                // with LLVM IR.
-                // FIXME: are we consistent, and is this really the behavior we want?
-                let frozen = self.ecx.type_is_freeze(v.layout.ty);
-                if frozen { Ok(()) } else { (self.unsafe_cell_action)(v) }
+            fn visit_union(&mut self, _v: MPlaceTy<'tcx, Tag>, _fields: NonZeroUsize) -> InterpResult<'tcx> {
+                bug!("we should have already handled unions in `visit_value`")
             }
         }
     }
 
-    // Writes several `ImmTy`s contiguosly into memory. This is useful when you have to pack
+    // Writes several `ImmTy`s contiguously into memory. This is useful when you have to pack
     // different values into a struct.
     fn write_packed_immediates(
         &mut self,
@@ -367,19 +377,17 @@ fn write_packed_immediates(
     /// case.
     fn check_no_isolation(&self, name: &str) -> InterpResult<'tcx> {
         if !self.eval_context_ref().machine.communicate {
-            throw_machine_stop!(TerminationInfo::UnsupportedInIsolation(format!(
-                "`{}` not available when isolation is enabled",
-                name,
-            )))
+            isolation_error(name)?;
         }
         Ok(())
     }
+
     /// Helper function used inside the shims of foreign functions to assert that the target OS
     /// is `target_os`. It panics showing a message with the `name` of the foreign function
     /// if this is not the case.
     fn assert_target_os(&self, target_os: &str, name: &str) {
         assert_eq!(
-            self.eval_context_ref().tcx.sess.target.target.target_os,
+            self.eval_context_ref().tcx.sess.target.os,
             target_os,
             "`{}` is only available on the `{}` target OS",
             name,
@@ -387,18 +395,34 @@ fn assert_target_os(&self, target_os: &str, name: &str) {
         )
     }
 
+    /// Get last error variable as a place, lazily allocating thread-local storage for it if
+    /// necessary.
+    fn last_error_place(&mut self) -> InterpResult<'tcx, MPlaceTy<'tcx, Tag>> {
+        let this = self.eval_context_mut();
+        if let Some(errno_place) = this.active_thread_ref().last_error {
+            Ok(errno_place)
+        } else {
+            // Allocate new place, set initial value to 0.
+            let errno_layout = this.machine.layouts.u32;
+            let errno_place = this.allocate(errno_layout, MiriMemoryKind::Machine.into());
+            this.write_scalar(Scalar::from_u32(0), errno_place.into())?;
+            this.active_thread_mut().last_error = Some(errno_place);
+            Ok(errno_place)
+        }
+    }
+
     /// Sets the last error variable.
     fn set_last_error(&mut self, scalar: Scalar<Tag>) -> InterpResult<'tcx> {
         let this = self.eval_context_mut();
-        let errno_place = this.machine.last_error.unwrap();
+        let errno_place = this.last_error_place()?;
         this.write_scalar(scalar, errno_place.into())
     }
 
     /// Gets the last error variable.
-    fn get_last_error(&self) -> InterpResult<'tcx, Scalar<Tag>> {
-        let this = self.eval_context_ref();
-        let errno_place = this.machine.last_error.unwrap();
-        this.read_scalar(errno_place.into())?.not_undef()
+    fn get_last_error(&mut self) -> InterpResult<'tcx, Scalar<Tag>> {
+        let this = self.eval_context_mut();
+        let errno_place = this.last_error_place()?;
+        this.read_scalar(errno_place.into())?.check_init()
     }
 
     /// Sets the last OS error using a `std::io::Error`. This function tries to produce the most
@@ -406,8 +430,9 @@ fn get_last_error(&self) -> InterpResult<'tcx, Scalar<Tag>> {
     fn set_last_error_from_io_error(&mut self, e: std::io::Error) -> InterpResult<'tcx> {
         use std::io::ErrorKind::*;
         let this = self.eval_context_mut();
-        let target = &this.tcx.tcx.sess.target.target;
-        let last_error = if target.options.target_family == Some("unix".to_owned()) {
+        let target = &this.tcx.sess.target;
+        let target_os = &target.os;
+        let last_error = if target.os_family == Some("unix".to_owned()) {
             this.eval_libc(match e.kind() {
                 ConnectionRefused => "ECONNREFUSED",
                 ConnectionReset => "ECONNRESET",
@@ -427,12 +452,14 @@ fn set_last_error_from_io_error(&mut self, e: std::io::Error) -> InterpResult<'t
                     throw_unsup_format!("io error {} cannot be transformed into a raw os error", e)
                 }
             })?
+        } else if target_os == "windows" {
+            // FIXME: we have to finish implementing the Windows equivalent of this.
+            this.eval_windows("c", match e.kind() {
+                NotFound => "ERROR_FILE_NOT_FOUND",
+                _ => throw_unsup_format!("io error {} cannot be transformed into a raw os error", e)
+            })?
         } else {
-            // FIXME: we have to implement the Windows equivalent of this.
-            throw_unsup_format!(
-                "setting the last OS error from an io::Error is unsupported for {}.",
-                target.target_os
-            )
+            throw_unsup_format!("setting the last OS error from an io::Error is unsupported for {}.", target_os)
         };
         this.set_last_error(last_error)
     }
@@ -456,195 +483,86 @@ fn try_unwrap_io_result<T: From<i32>>(
         }
     }
 
-    /// Dispatches to appropriate implementations for reading an OsString from Memory,
-    /// depending on the interpretation target.
-    /// FIXME: Use `Cow` to avoid copies
-    fn read_os_str_from_target_str(&self, scalar: Scalar<Tag>) -> InterpResult<'tcx, OsString> {
-        let target_os = self.eval_context_ref().tcx.sess.target.target.target_os.as_str();
-        match target_os {
-            "linux" | "macos" => self.read_os_str_from_c_str(scalar).map(|x| x.to_os_string()),
-            "windows" => self.read_os_str_from_wide_str(scalar),
-            unsupported => throw_unsup_format!("OsString support for target OS `{}` not yet available", unsupported),
-        }
-    }
-
-    /// Helper function to read an OsString from a null-terminated sequence of bytes, which is what
-    /// the Unix APIs usually handle.
-    fn read_os_str_from_c_str<'a>(&'a self, scalar: Scalar<Tag>) -> InterpResult<'tcx, &'a OsStr>
-    where
-        'tcx: 'a,
-        'mir: 'a,
-    {
-        #[cfg(unix)]
-        fn bytes_to_os_str<'tcx, 'a>(bytes: &'a [u8]) -> InterpResult<'tcx, &'a OsStr> {
-            Ok(std::os::unix::ffi::OsStrExt::from_bytes(bytes))
-        }
-        #[cfg(not(unix))]
-        fn bytes_to_os_str<'tcx, 'a>(bytes: &'a [u8]) -> InterpResult<'tcx, &'a OsStr> {
-            let s = std::str::from_utf8(bytes)
-                .map_err(|_| err_unsup_format!("{:?} is not a valid utf-8 string", bytes))?;
-            Ok(OsStr::new(s))
-        }
-
+    fn read_scalar_at_offset(
+        &self,
+        op: OpTy<'tcx, Tag>,
+        offset: u64,
+        layout: TyAndLayout<'tcx>,
+    ) -> InterpResult<'tcx, ScalarMaybeUninit<Tag>> {
         let this = self.eval_context_ref();
-        let bytes = this.memory.read_c_str(scalar)?;
-        bytes_to_os_str(bytes)
+        let op_place = this.deref_operand(op)?;
+        let offset = Size::from_bytes(offset);
+        // Ensure that the following read at an offset is within bounds
+        assert!(op_place.layout.size >= offset + layout.size);
+        let value_place = op_place.offset(offset, MemPlaceMeta::None, layout, this)?;
+        this.read_scalar(value_place.into())
     }
 
-    /// Helper function to read an OsString from a 0x0000-terminated sequence of u16,
-    /// which is what the Windows APIs usually handle.
-    fn read_os_str_from_wide_str<'a>(&'a self, scalar: Scalar<Tag>) -> InterpResult<'tcx, OsString>
-    where
-        'tcx: 'a,
-        'mir: 'a,
-    {
-        #[cfg(windows)]
-        pub fn u16vec_to_osstring<'tcx, 'a>(u16_vec: Vec<u16>) -> InterpResult<'tcx, OsString> {
-            Ok(std::os::windows::ffi::OsStringExt::from_wide(&u16_vec[..]))
-        }
-        #[cfg(not(windows))]
-        pub fn u16vec_to_osstring<'tcx, 'a>(u16_vec: Vec<u16>) -> InterpResult<'tcx, OsString> {
-            let s = String::from_utf16(&u16_vec[..])
-                .map_err(|_| err_unsup_format!("{:?} is not a valid utf-16 string", u16_vec))?;
-            Ok(s.into())
-        }
-
-        let u16_vec = self.eval_context_ref().memory.read_wide_str(scalar)?;
-        u16vec_to_osstring(u16_vec)
-    }
-
-
-    /// Helper function to write an OsStr as a null-terminated sequence of bytes, which is what
-    /// the Unix APIs usually handle. This function returns `Ok((false, length))` without trying
-    /// to write if `size` is not large enough to fit the contents of `os_string` plus a null
-    /// terminator. It returns `Ok((true, length))` if the writing process was successful. The
-    /// string length returned does not include the null terminator.
-    fn write_os_str_to_c_str(
-        &mut self,
-        os_str: &OsStr,
-        scalar: Scalar<Tag>,
-        size: u64,
-    ) -> InterpResult<'tcx, (bool, u64)> {
-        #[cfg(unix)]
-        fn os_str_to_bytes<'tcx, 'a>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> {
-            Ok(std::os::unix::ffi::OsStrExt::as_bytes(os_str))
-        }
-        #[cfg(not(unix))]
-        fn os_str_to_bytes<'tcx, 'a>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> {
-            // On non-unix platforms the best we can do to transform bytes from/to OS strings is to do the
-            // intermediate transformation into strings. Which invalidates non-utf8 paths that are actually
-            // valid.
-            os_str
-                .to_str()
-                .map(|s| s.as_bytes())
-                .ok_or_else(|| err_unsup_format!("{:?} is not a valid utf-8 string", os_str).into())
-        }
-
-        let bytes = os_str_to_bytes(os_str)?;
-        // If `size` is smaller or equal than `bytes.len()`, writing `bytes` plus the required null
-        // terminator to memory using the `ptr` pointer would cause an out-of-bounds access.
-        let string_length = u64::try_from(bytes.len()).unwrap();
-        if size <= string_length {
-            return Ok((false, string_length));
-        }
-        self.eval_context_mut()
-            .memory
-            .write_bytes(scalar, bytes.iter().copied().chain(iter::once(0u8)))?;
-        Ok((true, string_length))
-    }
-
-    /// Helper function to write an OsStr as a 0x0000-terminated u16-sequence, which is what
-    /// the Windows APIs usually handle. This function returns `Ok((false, length))` without trying
-    /// to write if `size` is not large enough to fit the contents of `os_string` plus a null
-    /// terminator. It returns `Ok((true, length))` if the writing process was successful. The
-    /// string length returned does not include the null terminator.
-    fn write_os_str_to_wide_str(
+    fn write_scalar_at_offset(
         &mut self,
-        os_str: &OsStr,
-        mplace: MPlaceTy<'tcx, Tag>,
-        size: u64,
-    ) -> InterpResult<'tcx, (bool, u64)> {
-        #[cfg(windows)]
-        fn os_str_to_u16vec<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, Vec<u16>> {
-            Ok(std::os::windows::ffi::OsStrExt::encode_wide(os_str).collect())
-        }
-        #[cfg(not(windows))]
-        fn os_str_to_u16vec<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, Vec<u16>> {
-            // On non-Windows platforms the best we can do to transform Vec<u16> from/to OS strings is to do the
-            // intermediate transformation into strings. Which invalidates non-utf8 paths that are actually
-            // valid.
-            os_str
-                .to_str()
-                .map(|s| s.encode_utf16().collect())
-                .ok_or_else(|| err_unsup_format!("{:?} is not a valid utf-8 string", os_str).into())
-        }
-
-        let u16_vec = os_str_to_u16vec(os_str)?;
-        // If `size` is smaller or equal than `bytes.len()`, writing `bytes` plus the required
-        // 0x0000 terminator to memory would cause an out-of-bounds access.
-        let string_length = u64::try_from(u16_vec.len()).unwrap();
-        if size <= string_length {
-            return Ok((false, string_length));
-        }
-
+        op: OpTy<'tcx, Tag>,
+        offset: u64,
+        value: impl Into<ScalarMaybeUninit<Tag>>,
+        layout: TyAndLayout<'tcx>,
+    ) -> InterpResult<'tcx, ()> {
         let this = self.eval_context_mut();
-
-        // Store the UTF-16 string.
-        let char_size = Size::from_bytes(2);
-        for (idx, c) in u16_vec.into_iter().chain(iter::once(0x0000)).enumerate() {
-            let place = this.mplace_field(mplace, idx as u64)?; 
-            this.write_scalar(Scalar::from_uint(c, char_size), place.into())?;
-        }
-        Ok((true, string_length))
-    }
-
-    /// Dispatches to appropriate implementations for allocating & writing OsString in Memory,
-    /// depending on the interpretation target.
-    fn alloc_os_str_as_target_str(
-        &mut self,
-        os_str: &OsStr,
-        memkind: MemoryKind<MiriMemoryKind>,
-    ) -> InterpResult<'tcx, Pointer<Tag>> {
-        let target_os = self.eval_context_ref().tcx.sess.target.target.target_os.as_str();
-        match target_os {
-            "linux" | "macos" => Ok(self.alloc_os_str_as_c_str(os_str, memkind)),
-            "windows" => Ok(self.alloc_os_str_as_wide_str(os_str, memkind)),
-            unsupported => throw_unsup_format!("OsString support for target OS `{}` not yet available", unsupported),
-        }
+        let op_place = this.deref_operand(op)?;
+        let offset = Size::from_bytes(offset);
+        // Ensure that the following read at an offset is within bounds
+        assert!(op_place.layout.size >= offset + layout.size);
+        let value_place = op_place.offset(offset, MemPlaceMeta::None, layout, this)?;
+        this.write_scalar(value, value_place.into())
     }
 
-    fn alloc_os_str_as_c_str(
+    /// Parse a `timespec` struct and return it as a `std::time::Duration`. It returns `None`
+    /// if the value in the `timespec` struct is invalid. Some libc functions will return
+    /// `EINVAL` in this case.
+    fn read_timespec(
         &mut self,
-        os_str: &OsStr,
-        memkind: MemoryKind<MiriMemoryKind>,
-    ) -> Pointer<Tag> {
-        let size = u64::try_from(os_str.len()).unwrap().checked_add(1).unwrap(); // Make space for `0` terminator.
+        timespec_ptr_op: OpTy<'tcx, Tag>,
+    ) -> InterpResult<'tcx, Option<Duration>> {
         let this = self.eval_context_mut();
-
-        let arg_type = this.tcx.mk_array(this.tcx.types.u8, size);
-        let arg_place = this.allocate(this.layout_of(arg_type).unwrap(), memkind);
-        assert!(self.write_os_str_to_c_str(os_str, arg_place.ptr, size).unwrap().0);
-        arg_place.ptr.assert_ptr()
+        let tp = this.deref_operand(timespec_ptr_op)?;
+        let seconds_place = this.mplace_field(tp, 0)?;
+        let seconds_scalar = this.read_scalar(seconds_place.into())?;
+        let seconds = seconds_scalar.to_machine_isize(this)?;
+        let nanoseconds_place = this.mplace_field(tp, 1)?;
+        let nanoseconds_scalar = this.read_scalar(nanoseconds_place.into())?;
+        let nanoseconds = nanoseconds_scalar.to_machine_isize(this)?;
+
+        Ok(try {
+            // tv_sec must be non-negative.
+            let seconds: u64 = seconds.try_into().ok()?;
+            // tv_nsec must be non-negative.
+            let nanoseconds: u32 = nanoseconds.try_into().ok()?;
+            if nanoseconds >= 1_000_000_000 {
+                // tv_nsec must not be greater than 999,999,999.
+                None?
+            }
+            Duration::new(seconds, nanoseconds)
+        })
     }
+}
 
-    fn alloc_os_str_as_wide_str(
-        &mut self,
-        os_str: &OsStr,
-        memkind: MemoryKind<MiriMemoryKind>,
-    ) -> Pointer<Tag> {
-        let size = u64::try_from(os_str.len()).unwrap().checked_add(1).unwrap(); // Make space for `0x0000` terminator.
-        let this = self.eval_context_mut();
-
-        let arg_type = this.tcx.mk_array(this.tcx.types.u16, size);
-        let arg_place = this.allocate(this.layout_of(arg_type).unwrap(), memkind);
-        assert!(self.write_os_str_to_wide_str(os_str, arg_place, size).unwrap().0);
-        arg_place.ptr.assert_ptr()
+/// Check that the number of args is what we expect.
+pub fn check_arg_count<'a, 'tcx, const N: usize>(args: &'a [OpTy<'tcx, Tag>]) -> InterpResult<'tcx, &'a [OpTy<'tcx, Tag>; N]>
+    where &'a [OpTy<'tcx, Tag>; N]: TryFrom<&'a [OpTy<'tcx, Tag>]> {
+    if let Ok(ops) = args.try_into() {
+        return Ok(ops);
     }
+    throw_ub_format!("incorrect number of arguments: got {}, expected {}", args.len(), N)
+}
+
+pub fn isolation_error(name: &str) -> InterpResult<'static> {
+    throw_machine_stop!(TerminationInfo::UnsupportedInIsolation(format!(
+        "{} not available when isolation is enabled",
+        name,
+    )))
 }
 
 pub fn immty_from_int_checked<'tcx>(
     int: impl Into<i128>,
-    layout: TyLayout<'tcx>,
+    layout: TyAndLayout<'tcx>,
 ) -> InterpResult<'tcx, ImmTy<'tcx, Tag>> {
     let int = int.into();
     Ok(ImmTy::try_from_int(int, layout).ok_or_else(|| {
@@ -654,7 +572,7 @@ pub fn immty_from_int_checked<'tcx>(
 
 pub fn immty_from_uint_checked<'tcx>(
     int: impl Into<u128>,
-    layout: TyLayout<'tcx>,
+    layout: TyAndLayout<'tcx>,
 ) -> InterpResult<'tcx, ImmTy<'tcx, Tag>> {
     let int = int.into();
     Ok(ImmTy::try_from_uint(int, layout).ok_or_else(|| {