diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index 6b8d8ed1f8018..fdc746ead51f5 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -171,6 +171,38 @@ available (which affects `cfg(target_feature)`), and it tells Miri to consider t that the interpreted program runs on as having the feature available (meaning the code is allowed to invoke the corresponding intrinsics). +### Nextest integration + +Miri can be combined with [`cargo-nextest`](https://nexte.st): + +``` +cargo install --locked cargo-nextest +cargo miri nextest run +``` + +Nextest spawns a separate instance of Miri for each test, which has several advantages: +- Tests can run in parallel. Miri itself only uses a single thread per interpreter so this can + give a massive speedup (but see the caveat below). +- Tests do not stop when a single problem is found. Miri aborts execution when it encounters + Undefined Behavior or an unsupported operation (there is often not really any way to continue), + so once a single test fails, the remaining tests cannot be executed. Nextest's process-per-test + model means that you end up with a full list of which tests worked in Miri and which tests had a + problem. + +However, there is also a big caveat: Miri will [re-compile the test crate every time it is +invoked](https://github.com/rust-lang/miri/issues/5013), which means a crate with N tests will be +compiled N+1 times. If the test crate takes a long time to build, this can outweigh the benefits of +parallelization. + +For more information about nextest, see the [`cargo-nextest` Miri +documentation](https://nexte.st/book/miri.html). + +Note: Nextest's one-test-per-process model means that `cargo miri test` is able to detect data +races where two tests race on a shared resource, but `cargo miri nextest run` will not detect +such races. + +Note: `cargo-nextest` [does not support doctests](https://github.com/nextest-rs/nextest/issues/16). + ### Testing multiple different executions Certain parts of the execution are picked randomly by Miri, such as the exact base address @@ -184,6 +216,7 @@ MIRIFLAGS="-Zmiri-many-seeds" cargo miri test # tries the seeds in 0..64 MIRIFLAGS="-Zmiri-many-seeds=0..16" cargo miri test ``` +Miri will test the given range of seeds with parallel interpreter instances. The default of 64 different seeds can be quite slow, so you often want to specify a smaller range. ### Running Miri on CI @@ -243,26 +276,6 @@ However, even for targets that we do support, the degree of support for accessin (such as the file system) differs between targets: generally, Linux targets have the best support, and macOS targets are usually on par. Windows is supported less well. -### Running tests in parallel - -Though it implements Rust threading, Miri itself is a single-threaded interpreter -(it works like a multi-threaded OS on a single-core CPU). -This means that when running `cargo miri test`, you will probably see a dramatic -increase in the amount of time it takes to run your whole test suite due to the -inherent interpreter slowdown and a loss of parallelism. - -You can get your test suite's parallelism back by running `cargo miri nextest run -jN` -(note that you will need [`cargo-nextest`](https://nexte.st) installed). -This works because `cargo-nextest` collects a list of all tests then launches a -separate `cargo miri run` for each test. For more information about nextest, see the -[`cargo-nextest` Miri documentation](https://nexte.st/book/miri.html). - -Note: This one-test-per-process model means that `cargo miri test` is able to detect data -races where two tests race on a shared resource, but `cargo miri nextest run` will not detect -such races. - -Note: `cargo-nextest` does not support doctests, see https://github.com/nextest-rs/nextest/issues/16 - ### Directly invoking the `miri` driver The recommended way to invoke Miri is via `cargo miri`. Directly invoking the underlying `miri` diff --git a/src/tools/miri/cargo-miri/src/phases.rs b/src/tools/miri/cargo-miri/src/phases.rs index f58cec827cf5d..caf6987291970 100644 --- a/src/tools/miri/cargo-miri/src/phases.rs +++ b/src/tools/miri/cargo-miri/src/phases.rs @@ -36,6 +36,9 @@ Examples: This will print the path to the generated sysroot (and nothing else) on stdout. stderr will still contain progress information about how the build is doing. +For documentation on `-Zmiri-...` flags, see Miri's README.md, available at: +- $(rustc --print sysroot)/share/doc/miri/README.md +- https://github.com/rust-lang/miri/blob/master/README.md "; fn show_help() { diff --git a/src/tools/miri/ci/ci.sh b/src/tools/miri/ci/ci.sh index f7c0b739c1dd4..42107adfe566a 100755 --- a/src/tools/miri/ci/ci.sh +++ b/src/tools/miri/ci/ci.sh @@ -168,6 +168,7 @@ case $HOST_TARGET in MANY_SEEDS=16 TEST_TARGET=x86_64-unknown-freebsd run_tests MANY_SEEDS=16 TEST_TARGET=i686-unknown-freebsd run_tests MANY_SEEDS=16 TEST_TARGET=x86_64-unknown-illumos run_tests + MANY_SEEDS=16 TEST_TARGET=x86_64-unknown-netbsd run_tests_minimal hello ;; armv7-unknown-linux-gnueabihf) # Host diff --git a/src/tools/miri/priroda/src/main.rs b/src/tools/miri/priroda/src/main.rs index 2739b041b0b48..fe67b21d2c134 100644 --- a/src/tools/miri/priroda/src/main.rs +++ b/src/tools/miri/priroda/src/main.rs @@ -6,6 +6,7 @@ extern crate rustc_data_structures; extern crate rustc_driver; extern crate rustc_hir; extern crate rustc_hir_analysis; +extern crate rustc_index; extern crate rustc_interface; extern crate rustc_log; extern crate rustc_middle; @@ -19,13 +20,15 @@ use std::path::PathBuf; use miri::*; use rustc_driver::Compilation; use rustc_hir::attrs::CrateType; +use rustc_index::IndexVec; use rustc_interface::interface; use rustc_middle::mir; +use rustc_middle::mir::{Local, VarDebugInfoContents}; use rustc_middle::ty::TyCtxt; use rustc_session::EarlyDiagCtxt; use rustc_session::config::ErrorOutputType; -use rustc_span::Span; use rustc_span::source_map::SourceMap; +use rustc_span::{Span, Symbol}; fn find_sysroot() -> String { std::env::var("MIRI_SYSROOT") @@ -129,6 +132,11 @@ struct PrirodaContext<'tcx> { last_location: Option, } +struct LocalDesc { + name: Option, + local: Local, + ty: String, +} /// Controls when execution returns to the frontend. enum ResumeMode { /// Stop at the next visible MIR instruction. @@ -336,15 +344,49 @@ impl<'tcx> PrirodaContext<'tcx> { } } - /// Returns the names of all user-visible locals in the innermost stack frame. + /// Returns structured descriptions for locals in the innermost stack frame. /// - /// Uses `var_debug_info` from the MIR body, which is the same source that - /// DWARF debug info is built from, so the names match what the user wrote. - fn list_locals(&self) -> Vec { + /// Starts from all MIR locals, then enriches them with source names from + /// `var_debug_info` when a debug entry maps directly to a whole local. + fn list_locals(&self) -> Vec { let Some(frame) = self.ecx.active_thread_stack().last() else { return Vec::new(); }; - frame.body().var_debug_info.iter().map(|info| info.name.to_string()).collect() + + self.local_desc_map(frame).into_iter().collect() + } + + fn local_desc_map( + &self, + frame: &Frame<'tcx, Provenance, FrameExtra<'tcx>>, + ) -> IndexVec { + // Initialize one description per MIR local so the table can be indexed by Local. + let mut locals: IndexVec = frame + .body() + .local_decls + .iter_enumerated() + .map(|(id, local_decl)| { + LocalDesc { name: None, local: id, ty: local_decl.ty.to_string() } + }) + .collect(); + + // FIXME: Some debug-info entries do not have a backing MIR local, for example + // because the source variable was optimized out or is represented as a + // projection. This local-indexed table cannot represent those entries yet; + // the final locals list should become a `Vec` with `id : Option`, `id` + // could be renamed to `local`. + + // Attach source names from debug info when the debug entry maps directly to a whole MIR local. + for var_debug_info in &frame.body().var_debug_info { + if let VarDebugInfoContents::Place(place) = var_debug_info.value + && let Some(local) = place.as_local() + && locals[local].name.is_none() + { + locals[local].name = Some(var_debug_info.name); + } + } + + locals } } @@ -366,7 +408,7 @@ enum BreakpointSetResult { enum CommandResult { ExecutionStopped(StepResult), BreakpointResult(BreakpointSetResult), - Locals(Vec), + Locals(Vec), // FIXME: distinguish terminating the debugger session from disconnecting a // frontend and terminating the interpreted program once multiple frontends exist. TerminateSession, @@ -403,12 +445,21 @@ impl Cli { BreakpointSetResult::Duplicate => println!("Duplicate breakpoint"), }, - CommandResult::Locals(names) => - if names.is_empty() { + CommandResult::Locals(locals_desc) => + if locals_desc.is_empty() { println!("no locals"); } else { - for name in &names { - println!("{name}"); + for local_desc in &locals_desc { + let mut name_str = "None".to_string(); + if let Some(name) = local_desc.name { + name_str = name.to_string(); + } + println!( + "Name: {}, Id: _{}, Ty: {}", + name_str, + local_desc.local.index(), + local_desc.ty, + ); } }, CommandResult::TerminateSession => { diff --git a/src/tools/miri/priroda/tests/ui/locals_in_function.stdout b/src/tools/miri/priroda/tests/ui/locals_in_function.stdout index ed5889f5836e7..4f2786d3e17b7 100644 --- a/src/tools/miri/priroda/tests/ui/locals_in_function.stdout +++ b/src/tools/miri/priroda/tests/ui/locals_in_function.stdout @@ -1,6 +1,10 @@ (priroda) breakpoint added: {MANIFEST_DIR}/tests/ui/locals_in_function.rs:5 (priroda) Hit breakpoint {MANIFEST_DIR}/tests/ui/locals_in_function.rs:5 -(priroda) x -y +(priroda) Name: None, Id: _0, Ty: () +Name: x, Id: _1, Ty: i32 +Name: y, Id: _2, Ty: bool +Name: None, Id: _3, Ty: (i32, bool) +Name: None, Id: _4, Ty: i32 +Name: None, Id: _5, Ty: bool (priroda) quitting diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 4a2bfdb2cd558..d7711535dbf29 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -01f54e80e888b66d6486a3a95d481b87353016df +16761606d606b6ec4d0c88fc9251670742ad9fd2 diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs index 296de803d5828..b58a7cfeba92e 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs @@ -574,7 +574,9 @@ impl<'tcx> Tree { // Don't check for protector if it is a Cell (see `unsafe_cell_deallocate` in `interior_mutability.rs`). // Related to https://github.com/rust-lang/rust/issues/55005. && !perm.permission.is_cell() - // Only trigger UB if the accessed bit is set, i.e. if the protector is actually protecting this offset. See #4579. + // Only trigger UB if the accessed bit is set, i.e. if the protector + // is actually protecting this offset. See #4579. Note that this + // takes into account the access we just did above! && perm.accessed { Err(TbError { diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 58fe7dc541fc3..f5761f4712470 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -129,7 +129,13 @@ pub fn iter_exported_symbols<'tcx>( || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER) || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) }; - if exported { + // FIXME: `#[no_mangle]` makes no sense on a generic item, but still causes it to be + // considered "extern". Remove this once `no_mangle_generic_items` is a hard error. + let exported_mono = exported && { + let generics = tcx.generics_of(def_id); + !generics.requires_monomorphization(tcx) + }; + if exported_mono { f(LOCAL_CRATE, def_id.into())?; } } diff --git a/src/tools/miri/src/shims/aarch64.rs b/src/tools/miri/src/shims/aarch64.rs index deab856b3e24f..3d8946fb36720 100644 --- a/src/tools/miri/src/shims/aarch64.rs +++ b/src/tools/miri/src/shims/aarch64.rs @@ -146,6 +146,45 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } + // Signed saturating doubling multiply returning the high half. + // + // Used by the `vqdmulh*` functions. + // + // This LLVM intrinsic multiplies the values of corresponding elements of the two source + // vector registers (which are signed integers), doubles the results, places the most significant half of the + // final results (using a saturating cast to fit the element type) into a vector, and writes the vector to the destination register. + // + // https://developer.arm.com/architectures/instruction-sets/intrinsics#f:@navigationhierarchiessimdisa=[Neon]&q=vqdmulh + name if name.starts_with("neon.sqdmulh.") => { + let [left, right] = + this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + + let (left, left_len) = this.project_to_simd(left)?; + let (right, right_len) = this.project_to_simd(right)?; + let (dest, dest_len) = this.project_to_simd(dest)?; + assert_eq!(left_len, right_len); + assert_eq!(left_len, dest_len); + + let elem_size = dest.layout.field(this, 0).size; + let bits = elem_size.bits(); + let min = elem_size.signed_int_min(); + let max = elem_size.signed_int_max(); + + for i in 0..dest_len { + let a = this.read_scalar(&this.project_index(&left, i)?)?.to_int(elem_size)?; + let b = this.read_scalar(&this.project_index(&right, i)?)?.to_int(elem_size)?; + + // Uses i128 arithmetic, which cannot overflow because the intrinsic takes at most i32. + let doubled = a.strict_mul(b).strict_mul(2); + let res = (doubled >> bits).clamp(min, max); + + this.write_scalar( + Scalar::from_int(res, elem_size), + &this.project_index(&dest, i)?, + )?; + } + } + // Vector table lookup: each index selects a byte from the 16-byte table, out-of-range -> 0. // Used to implement vtbl1_u8 function. // LLVM does not have a portable shuffle that takes non-const indices diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index c2ae65091ee2c..14eb7af4b52df 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -840,18 +840,46 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "mmap" => { - let [addr, length, prot, flags, fd, offset] = - this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + let [addr, length, prot, flags, fd, offset] = this.check_shim_sig( + shim_sig!(extern "C" fn(*mut _, usize, i32, i32, i32, libc::off_t) -> *mut _), + link_name, + abi, + args, + )?; let offset = this.read_scalar(offset)?.to_int(this.libc_ty_layout("off_t").size)?; let ptr = this.mmap(addr, length, prot, flags, fd, offset)?; this.write_scalar(ptr, dest)?; } "munmap" => { - let [addr, length] = - this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + let [addr, length] = this.check_shim_sig( + shim_sig!(extern "C" fn(*mut _, usize) -> i32), + link_name, + abi, + args, + )?; let result = this.munmap(addr, length)?; this.write_scalar(result, dest)?; } + "mprotect" => { + let [addr, length, prot] = this.check_shim_sig( + shim_sig!(extern "C" fn(*mut _, usize, i32) -> i32), + link_name, + abi, + args, + )?; + let result = this.mprotect(addr, length, prot)?; + this.write_scalar(result, dest)?; + } + "madvise" => { + let [addr, length, advice] = this.check_shim_sig( + shim_sig!(extern "C" fn(*mut _, usize, i32) -> i32), + link_name, + abi, + args, + )?; + let result = this.madvise(addr, length, advice)?; + this.write_scalar(result, dest)?; + } "reallocarray" => { // Currently this function does not exist on all Unixes, e.g. on macOS. @@ -1410,7 +1438,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let [_, _] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; this.write_null(dest)?; } - "sigaction" | "mprotect" if this.frame_in_std() => { + "sigaction" if this.frame_in_std() => { let [_, _, _] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; this.write_null(dest)?; } diff --git a/src/tools/miri/src/shims/unix/mem.rs b/src/tools/miri/src/shims/unix/mem.rs index c2ad7c0e9d0ab..50be3c7ae15e6 100644 --- a/src/tools/miri/src/shims/unix/mem.rs +++ b/src/tools/miri/src/shims/unix/mem.rs @@ -53,9 +53,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return interp_ok(Scalar::from_maybe_pointer(Pointer::without_provenance(addr), this)); } - let prot_read = this.eval_libc_i32("PROT_READ"); - let prot_write = this.eval_libc_i32("PROT_WRITE"); - // First, we do some basic argument validation as required by mmap if (flags & (map_private | map_shared)).count_ones() != 1 { this.set_last_error(LibcError("EINVAL"))?; @@ -80,13 +77,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ); } - // Miri doesn't support protections other than PROT_READ|PROT_WRITE. - if prot != prot_read | prot_write { - throw_unsup_format!( - "Miri does not support calls to mmap with protections other than \ - PROT_READ|PROT_WRITE", - ); - } + verify_prot(this, prot)?; // Miri does not support shared mappings, or any of the other extensions that for example // Linux has added to the flags arguments. @@ -103,14 +94,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } let align = this.machine.page_align(); - let Some(map_length) = length.checked_next_multiple_of(this.machine.page_size) else { + let Some(map_length) = round_up_to_page_size(this, length) else { this.set_last_error(LibcError("EINVAL"))?; return interp_ok(this.eval_libc("MAP_FAILED")); }; - if map_length > this.target_usize_max() { - this.set_last_error(LibcError("EINVAL"))?; - return interp_ok(this.eval_libc("MAP_FAILED")); - } let ptr = this.allocate_ptr( Size::from_bytes(map_length), @@ -135,13 +122,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")); } - let Some(length) = length.checked_next_multiple_of(this.machine.page_size) else { + let Some(length) = round_up_to_page_size(this, length) else { return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")); }; - if length > this.target_usize_max() { - this.set_last_error(LibcError("EINVAL"))?; - return interp_ok(this.eval_libc("MAP_FAILED")); - } let length = Size::from_bytes(length); this.deallocate_ptr( @@ -152,4 +135,106 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(Scalar::from_i32(0)) } + + fn mprotect( + &mut self, + addr: &OpTy<'tcx>, + length: &OpTy<'tcx>, + prot: &OpTy<'tcx>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let addr = this.read_pointer(addr)?; + let length = this.read_target_usize(length)?; + let prot = this.read_scalar(prot)?.to_i32()?; + + // addr must be a multiple of the page size. + if !addr.addr().bytes().is_multiple_of(this.machine.page_size) { + return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")); + } + + verify_prot(this, prot)?; + + // The pages from `[addr, addr + length)` must be mapped, so length definitely must not overflow. + let Some(length) = round_up_to_page_size(this, length) else { + return this.set_errno_and_return_neg1_i32(LibcError("ENOMEM")); + }; + // Ensure this is actually allocated memory we can access. + this.check_ptr_access(addr, Size::from_bytes(length), CheckInAllocMsg::MemoryAccess) + .map_err_kind(|_| err_ub_format!("`mprotect` called on out-of-bounds memory"))?; + + // If the memory comes from memory the Rust program has allocated with mmap, we only support + // `PROT_READ|PROT_WRITE`, so this `mprotect` is a no-op. If the memory was mmaped by the + // runtime (e.g. if it's the stack, executable memory, or static memory), POSIX also allows + // us to remap it. In those cases, such a call to `PROT_READ|PROT_WRITE` might actually change the permissions, + // but treating them as the new permissions is still UB. Therefore, we just pretend that we + // did the permission change by returning success, and will still reject if you try to use + // it with the "new" permissions. + interp_ok(Scalar::from_i32(0)) + } + + fn madvise( + &mut self, + addr: &OpTy<'tcx>, + length: &OpTy<'tcx>, + advice: &OpTy<'tcx>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let addr = this.read_pointer(addr)?; + let length = this.read_target_usize(length)?; + let advise = this.read_scalar(advice)?.to_i32()?; + + // addr must be a multiple of the page size. + if !addr.addr().bytes().is_multiple_of(this.machine.page_size) { + return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")); + } + + // advise must be supported. + let madv_normal = this.eval_libc_i32("MADV_NORMAL"); + let madv_random = this.eval_libc_i32("MADV_RANDOM"); + let madv_sequential = this.eval_libc_i32("MADV_SEQUENTIAL"); + let madv_willneed = this.eval_libc_i32("MADV_WILLNEED"); + if advise != madv_normal + && advise != madv_random + && advise != madv_sequential + && advise != madv_willneed + { + throw_unsup_format!( + "Miri does not support calls to madvise with advice other than MADV_NORMAL, MADV_RANDOM, MADV_SEQUENTIAL, or MADV_WILLNEED", + ); + } + + // The pages from `[addr, addr + length)` must be mapped, so length definitely must not overflow. + let Some(length) = round_up_to_page_size(this, length) else { + return this.set_errno_and_return_neg1_i32(LibcError("ENOMEM")); + }; + // Ensure this is actually allocated memory we can access. + this.check_ptr_access(addr, Size::from_bytes(length), CheckInAllocMsg::MemoryAccess) + .map_err_kind(|_| err_ub_format!("`madvise` called on out-of-bounds memory"))?; + + // All advises we support are no-ops. + interp_ok(Scalar::from_i32(0)) + } +} + +fn round_up_to_page_size(this: &MiriInterpCx<'_>, length: u64) -> Option { + length + .checked_next_multiple_of(this.machine.page_size) + .filter(|length| *length <= this.target_isize_max().try_into().unwrap()) +} + +fn verify_prot<'tcx>(this: &mut MiriInterpCx<'tcx>, prot: i32) -> InterpResult<'tcx> { + let prot_read = this.eval_libc_i32("PROT_READ"); + let prot_write = this.eval_libc_i32("PROT_WRITE"); + + // Miri doesn't support protections other than PROT_READ|PROT_WRITE. + if prot != prot_read | prot_write { + throw_unsup_format!( + "Miri does not support calls to mmap/mprotect with protections other than \ + PROT_READ|PROT_WRITE", + ); + } + + interp_ok(()) } diff --git a/src/tools/miri/src/shims/unix/socket.rs b/src/tools/miri/src/shims/unix/socket.rs index ae882f8ff3a40..72b614f58d0a4 100644 --- a/src/tools/miri/src/shims/unix/socket.rs +++ b/src/tools/miri/src/shims/unix/socket.rs @@ -1487,9 +1487,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Only shutting down the write end doesn't cause an EPOLLHUP event // and thus we won't set the `write_closed` readiness for it here. readiness.write_closed |= is_read_write_shutdown; - // The Linux kernel also sets EPOLLIN when both ends of a socket are closed: + // The Linux kernel also sets EPOLLIN when the read end of a socket is closed: // - readiness.readable |= is_read_write_shutdown; + readiness.readable |= is_read_shutdown || is_read_write_shutdown; drop(readiness); @@ -1697,12 +1697,16 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ) -> InterpResult<'tcx, Result> { let this = self.eval_context_mut(); - let SocketState::Connected(stream) = &mut *socket.state.borrow_mut() else { + let mut state = socket.state.borrow_mut(); + let SocketState::Connected(stream) = &mut *state else { panic!("try_non_block_send must only be called when the socket is connected") }; // This is a *non-blocking* write. let result = this.write_to_host(stream, length, buffer_ptr)?; + + drop(state); + match result { Err(IoError::HostError(e)) if matches!(e.kind(), io::ErrorKind::NotConnected | io::ErrorKind::WouldBlock) => @@ -1715,8 +1719,13 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // would be returned on UNIX-like systems. We thus remap this error to an EWOULDBLOCK. interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into()))) } - Ok(bytes_written) if bytes_written < length => { - // We had a short write. On Unix hosts using the `epoll` and `kqueue` backends, a + Ok(bytes_written) + if bytes_written < length && !socket.io_readiness.borrow().write_closed => + { + // We had a short write. (Note that we don't want to clear the writable readiness for + // sockets whose write end has already been closed as those never block a write, i.e., + // they are always write-ready.) + // On Unix hosts using the `epoll` and `kqueue` backends, a // short write means that the write buffer is full. We update the readiness // accordingly, which means that next time we see "writable" we will report an epoll // edge. Some applications (e.g. tokio) rely on this behavior; see @@ -1820,7 +1829,8 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ) -> InterpResult<'tcx, Result> { let this = self.eval_context_mut(); - let SocketState::Connected(stream) = &mut *socket.state.borrow_mut() else { + let mut state = socket.state.borrow_mut(); + let SocketState::Connected(stream) = &mut *state else { panic!("try_non_block_recv must only be called when the socket is connected") }; @@ -1832,6 +1842,9 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { length, buffer_ptr, )?; + + drop(state); + match result { Err(IoError::HostError(e)) if matches!(e.kind(), io::ErrorKind::NotConnected | io::ErrorKind::WouldBlock) => @@ -1844,9 +1857,16 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // would be returned on UNIX-like systems. We thus remap this error to an EWOULDBLOCK. interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into()))) } - Ok(bytes_read) if !should_peek && bytes_read < length && bytes_read > 0 => { + Ok(bytes_read) + if !should_peek + && bytes_read < length + && bytes_read > 0 + && !socket.io_readiness.borrow().read_closed => + { // We had a short read (and were not peeking). (Note that reading 0 bytes is guaranteed - // to indicate EOF, and can never happen spuriously, so we have to exclude that case.) + // to indicate EOF, and can never happen spuriously, so we have to exclude that case. + // We also don't want to clear the readable readiness for sockets whose read end has + // already been closed as those never block a read, i.e., they are always read-ready.) // On Unix hosts using the `epoll` and `kqueue` backends, a short read means that the // read buffer is empty. We update the readiness accordingly, which means that next time // we see "readable" we will report an epoll edge. Some applications (e.g. tokio) rely on diff --git a/src/tools/miri/src/shims/unix/sync.rs b/src/tools/miri/src/shims/unix/sync.rs index 4e351c1571218..a34314f3fbea6 100644 --- a/src/tools/miri/src/shims/unix/sync.rs +++ b/src/tools/miri/src/shims/unix/sync.rs @@ -35,7 +35,13 @@ const PTHREAD_INIT: u8 = 1; #[inline] fn mutexattr_kind_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> InterpResult<'tcx, u64> { interp_ok(match &ecx.tcx.sess.target.os { - Os::Linux | Os::Illumos | Os::Solaris | Os::MacOs | Os::FreeBsd | Os::Android => 0, + Os::Linux + | Os::Illumos + | Os::Solaris + | Os::MacOs + | Os::FreeBsd + | Os::Android + | Os::NetBsd => 0, os => throw_unsup_format!("`pthread_mutexattr` is not supported on {os}"), }) } @@ -135,8 +141,8 @@ impl SyncObj for PthreadMutex { fn mutex_init_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> InterpResult<'tcx, Size> { let offset = match &ecx.tcx.sess.target.os { Os::Linux | Os::Illumos | Os::Solaris | Os::FreeBsd | Os::Android => 0, - // macOS stores a signature in the first bytes, so we move to offset 4. - Os::MacOs => 4, + // macOS and NetBSD store a signature in the first bytes, so we move to offset 4. + Os::MacOs | Os::NetBsd => 4, os => throw_unsup_format!("`pthread_mutex` is not supported on {os}"), }; let offset = Size::from_bytes(offset); @@ -163,7 +169,7 @@ fn mutex_init_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> InterpResult<'tcx, Size> check_static_initializer("PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP"); check_static_initializer("PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP"); } - Os::Illumos | Os::Solaris | Os::MacOs | Os::FreeBsd | Os::Android => { + Os::Illumos | Os::Solaris | Os::MacOs | Os::FreeBsd | Os::Android | Os::NetBsd => { // No non-standard initializers. } os => throw_unsup_format!("`pthread_mutex` is not supported on {os}"), diff --git a/src/tools/miri/src/shims/x86/mod.rs b/src/tools/miri/src/shims/x86/mod.rs index 64b5b786c6c08..b3a8291b9361b 100644 --- a/src/tools/miri/src/shims/x86/mod.rs +++ b/src/tools/miri/src/shims/x86/mod.rs @@ -879,33 +879,39 @@ fn mpsadbw<'tcx>( assert_eq!(left.layout.size, dest.layout.size); let (num_chunks, op_items_per_chunk, left) = split_simd_to_128bit_chunks(ecx, left)?; + assert!(num_chunks <= 2); + let (_, _, right) = split_simd_to_128bit_chunks(ecx, right)?; let (_, dest_items_per_chunk, dest) = split_simd_to_128bit_chunks(ecx, dest)?; assert_eq!(op_items_per_chunk, dest_items_per_chunk.strict_mul(2)); let imm = ecx.read_scalar(imm)?.to_uint(imm.layout.size)?; - // Bit 2 of `imm` specifies the offset for indices of `left`. - // The offset is 0 when the bit is 0 or 4 when the bit is 1. - let left_offset = u64::try_from((imm >> 2) & 1).unwrap().strict_mul(4); - // Bits 0..=1 of `imm` specify the offset for indices of - // `right` in blocks of 4 elements. - let right_offset = u64::try_from(imm & 0b11).unwrap().strict_mul(4); for i in 0..num_chunks { let left = ecx.project_index(&left, i)?; let right = ecx.project_index(&right, i)?; let dest = ecx.project_index(&dest, i)?; + // The first 128-bit chunk uses the low 3 bits of IMM, the second chunk uses bits 3..6. + let lane_imm = imm.strict_shr(i.strict_mul(3).try_into().unwrap()); + + // Bit 2 of `lane_imm` specifies the offset for indices of `left`. + // The offset is 0 when the bit is 0 or 4 when the bit is 1. + let left_base = u64::try_from((lane_imm >> 2) & 1).unwrap().strict_mul(4); + // Bits 0..=1 of `lane_imm` specify the offset for indices of + // `right` in blocks of 4 elements. + let right_base = u64::try_from(lane_imm & 0b11).unwrap().strict_mul(4); + for j in 0..dest_items_per_chunk { - let left_offset = left_offset.strict_add(j); + let left_offset = left_base.strict_add(j); let mut res: u16 = 0; for k in 0..4 { let left = ecx .read_scalar(&ecx.project_index(&left, left_offset.strict_add(k))?)? .to_u8()?; let right = ecx - .read_scalar(&ecx.project_index(&right, right_offset.strict_add(k))?)? + .read_scalar(&ecx.project_index(&right, right_base.strict_add(k))?)? .to_u8()?; res = res.strict_add(left.abs_diff(right).into()); } diff --git a/src/tools/miri/test-cargo-miri/run-test.py b/src/tools/miri/test-cargo-miri/run-test.py index 60c7e192f4805..164b61f597065 100755 --- a/src/tools/miri/test-cargo-miri/run-test.py +++ b/src/tools/miri/test-cargo-miri/run-test.py @@ -170,9 +170,10 @@ def test_cargo_miri_test(): "test.empty.ref", env={'MIRIFLAGS': "-Zmiri-disable-isolation"}, ) - test("`cargo miri test` (proc-macro crate)", - cargo_miri("test") + ["-p", "proc_macro_crate"], - "test.proc-macro.stdout.ref", "test.proc-macro.stderr.ref", + test("`cargo miri test` (entire workspace, no isolation)", + cargo_miri("test") + ["--workspace"], + "test.workspace.stdout.ref", "test.workspace.stderr.ref", + env={'MIRIFLAGS': "-Zmiri-disable-isolation"}, ) test("`cargo miri test` (custom target dir)", cargo_miri("test") + ["--target-dir=custom-test"], diff --git a/src/tools/miri/test-cargo-miri/test.proc-macro.stdout.ref b/src/tools/miri/test-cargo-miri/test.proc-macro.stdout.ref deleted file mode 100644 index 7326c0a25a069..0000000000000 --- a/src/tools/miri/test-cargo-miri/test.proc-macro.stdout.ref +++ /dev/null @@ -1,5 +0,0 @@ - -running 0 tests - -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME - diff --git a/src/tools/miri/test-cargo-miri/test.proc-macro.stderr.ref b/src/tools/miri/test-cargo-miri/test.workspace.stderr.ref similarity index 100% rename from src/tools/miri/test-cargo-miri/test.proc-macro.stderr.ref rename to src/tools/miri/test-cargo-miri/test.workspace.stderr.ref diff --git a/src/tools/miri/test-cargo-miri/test.workspace.stdout.ref b/src/tools/miri/test-cargo-miri/test.workspace.stdout.ref new file mode 100644 index 0000000000000..a4e224a329797 --- /dev/null +++ b/src/tools/miri/test-cargo-miri/test.workspace.stdout.ref @@ -0,0 +1,103 @@ + +running 2 tests +.. +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + +imported main + +running 6 tests +...i.. +test result: ok. 5 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in $TIME + + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + +subcrate testing + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + +running 5 tests +..... +test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + +all doctests ran in $TIME; merged doctests compilation took $TIME + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + +running 1 test +. +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/src/tools/miri/tests/deps/Cargo.lock b/src/tools/miri/tests/deps/Cargo.lock index cbd2aa22b5236..4691588eefb40 100644 --- a/src/tools/miri/tests/deps/Cargo.lock +++ b/src/tools/miri/tests/deps/Cargo.lock @@ -290,7 +290,6 @@ dependencies = [ name = "miri-test-deps" version = "0.1.0" dependencies = [ - "cfg-if", "futures", "getrandom 0.1.16", "getrandom 0.2.17", diff --git a/src/tools/miri/tests/deps/Cargo.toml b/src/tools/miri/tests/deps/Cargo.toml index a4d06b628081c..bbbcc316f31c2 100644 --- a/src/tools/miri/tests/deps/Cargo.toml +++ b/src/tools/miri/tests/deps/Cargo.toml @@ -10,7 +10,6 @@ edition = "2021" # all dependencies (and their transitive ones) listed here can be used in `tests/*-dep`. libc = "0.2" num_cpus = "1.10.1" -cfg-if = "1" getrandom_01 = { package = "getrandom", version = "0.1" } getrandom_02 = { package = "getrandom", version = "0.2", features = ["js"] } diff --git a/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.rs b/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.rs index 98cc80b6b4ea2..d4d14d449aa68 100644 --- a/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.rs +++ b/src/tools/miri/tests/fail-dep/libc/eventfd_block_read_twice.rs @@ -4,6 +4,10 @@ use std::thread; +#[path = "../../utils/libc.rs"] +mod libc_utils; +use libc_utils::*; + // Test the behaviour of a thread being blocked on an eventfd read, get unblocked, and then // get blocked again. @@ -18,7 +22,7 @@ fn main() { // eventfd write will block when EFD_NONBLOCK flag is clear // and the addition caused counter to exceed u64::MAX - 1. let flags = libc::EFD_CLOEXEC; - let fd = unsafe { libc::eventfd(0, flags) }; + let fd = errno_result(unsafe { libc::eventfd(0, flags) }).unwrap(); let thread1 = thread::spawn(move || { let mut buf: [u8; 8] = [0; 8]; diff --git a/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.rs b/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.rs index 1a1d76eda2003..39d00a7522761 100644 --- a/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.rs +++ b/src/tools/miri/tests/fail-dep/libc/eventfd_block_write_twice.rs @@ -4,6 +4,10 @@ use std::thread; +#[path = "../../utils/libc.rs"] +mod libc_utils; +use libc_utils::*; + // Test the behaviour of a thread being blocked on an eventfd `write`, get unblocked, and then // get blocked again. @@ -17,7 +21,7 @@ fn main() { // eventfd write will block when EFD_NONBLOCK flag is clear // and the addition caused counter to exceed u64::MAX - 1. let flags = libc::EFD_CLOEXEC; - let fd = unsafe { libc::eventfd(0, flags) }; + let fd = errno_result(unsafe { libc::eventfd(0, flags) }).unwrap(); // Write u64 - 1, so the all subsequent write will block. let sized_8_data: [u8; 8] = (u64::MAX - 1).to_ne_bytes(); let res: i64 = unsafe { diff --git a/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs b/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs index eecf6abb9379f..6a6d363eea6bb 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs +++ b/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs @@ -16,8 +16,7 @@ use libc_utils::*; fn main() { // Create an epoll instance. - let epfd = unsafe { libc::epoll_create1(0) }; - assert_ne!(epfd, -1); + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); // Create two socketpair instances. let mut fds_a = [-1, -1]; diff --git a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs index 3eb79121a2f8d..22fe014d5831c 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs +++ b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs @@ -7,6 +7,7 @@ use std::thread; #[path = "../../utils/libc.rs"] mod libc_utils; use libc_utils::epoll::*; +use libc_utils::*; // Test if only one thread is unblocked if multiple threads blocked on same epfd. // Expected execution: @@ -16,14 +17,13 @@ use libc_utils::epoll::*; // 4. Thread 1 deadlocks. fn main() { // Create an epoll instance. - let epfd = unsafe { libc::epoll_create1(0) }; - assert_ne!(epfd, -1); + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); // Create an eventfd instance. let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC; - let fd1 = unsafe { libc::eventfd(0, flags) }; + let fd1 = errno_result(unsafe { libc::eventfd(0, flags) }).unwrap(); // Make a duplicate so that we have two file descriptors for the same file description. - let fd2 = unsafe { libc::dup(fd1) }; + let fd2 = errno_result(unsafe { libc::dup(fd1) }).unwrap(); // Register both with epoll. epoll_ctl_add(epfd, fd1, EPOLLIN | EPOLLOUT | EPOLLET).unwrap(); diff --git a/src/tools/miri/tests/fail-dep/libc/libc_epoll_unsupported_fd.rs b/src/tools/miri/tests/fail-dep/libc/libc_epoll_unsupported_fd.rs index 59cf0fc2ba026..d611feb30a703 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc_epoll_unsupported_fd.rs +++ b/src/tools/miri/tests/fail-dep/libc/libc_epoll_unsupported_fd.rs @@ -1,13 +1,15 @@ //@only-target: linux android illumos +#[path = "../../utils/libc.rs"] +mod libc_utils; +use libc_utils::*; + // This is a test for registering unsupported fd with epoll. // Register epoll fd with epoll is allowed in real system, but we do not support this. fn main() { // Create two epoll instance. - let epfd0 = unsafe { libc::epoll_create1(0) }; - assert_ne!(epfd0, -1); - let epfd1 = unsafe { libc::epoll_create1(0) }; - assert_ne!(epfd1, -1); + let epfd0 = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); + let epfd1 = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); // Register epoll with epoll. let mut ev = diff --git a/src/tools/miri/tests/fail-dep/libc/madvise_out_of_bounds.rs b/src/tools/miri/tests/fail-dep/libc/madvise_out_of_bounds.rs new file mode 100644 index 0000000000000..29d804fd0d99a --- /dev/null +++ b/src/tools/miri/tests/fail-dep/libc/madvise_out_of_bounds.rs @@ -0,0 +1,20 @@ +//@compile-flags: -Zmiri-disable-isolation +//@ignore-target: windows # No mmap on Windows +//@normalize-stderr-test: "only .*? bytes" -> "only SIZE bytes" + +fn main() { + unsafe { + let page_size = page_size::get(); + let ptr = libc::mmap( + std::ptr::null_mut(), + page_size, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0, + ); + assert!(!ptr.is_null()); + + libc::madvise(ptr, page_size + 1, libc::MADV_NORMAL); //~ ERROR: `madvise` called on out-of-bounds memory + } +} diff --git a/src/tools/miri/tests/fail-dep/libc/madvise_out_of_bounds.stderr b/src/tools/miri/tests/fail-dep/libc/madvise_out_of_bounds.stderr new file mode 100644 index 0000000000000..dbf38c7e4dd0f --- /dev/null +++ b/src/tools/miri/tests/fail-dep/libc/madvise_out_of_bounds.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: `madvise` called on out-of-bounds memory + --> tests/fail-dep/libc/madvise_out_of_bounds.rs:LL:CC + | +LL | libc::madvise(ptr, page_size + 1, libc::MADV_NORMAL); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail-dep/libc/mprotect_out_of_bounds.rs b/src/tools/miri/tests/fail-dep/libc/mprotect_out_of_bounds.rs new file mode 100644 index 0000000000000..1e6868b714a81 --- /dev/null +++ b/src/tools/miri/tests/fail-dep/libc/mprotect_out_of_bounds.rs @@ -0,0 +1,20 @@ +//@compile-flags: -Zmiri-disable-isolation +//@ignore-target: windows # No mmap on Windows +//@normalize-stderr-test: "only .*? bytes" -> "only SIZE bytes" + +fn main() { + unsafe { + let page_size = page_size::get(); + let ptr = libc::mmap( + std::ptr::null_mut(), + page_size, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0, + ); + assert!(!ptr.is_null()); + + libc::mprotect(ptr, page_size + 1, libc::PROT_READ | libc::PROT_WRITE); //~ ERROR: `mprotect` called on out-of-bounds memory + } +} diff --git a/src/tools/miri/tests/fail-dep/libc/mprotect_out_of_bounds.stderr b/src/tools/miri/tests/fail-dep/libc/mprotect_out_of_bounds.stderr new file mode 100644 index 0000000000000..ac48880150486 --- /dev/null +++ b/src/tools/miri/tests/fail-dep/libc/mprotect_out_of_bounds.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: `mprotect` called on out-of-bounds memory + --> tests/fail-dep/libc/mprotect_out_of_bounds.rs:LL:CC + | +LL | libc::mprotect(ptr, page_size + 1, libc::PROT_READ | libc::PROT_WRITE); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.rs b/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.rs index 37aa4590647b4..edf3f405056f4 100644 --- a/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.rs +++ b/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.rs @@ -1,5 +1,4 @@ //@ignore-target: windows # No libc socketpair on Windows -// test_race depends on a deterministic schedule. //@compile-flags: -Zmiri-deterministic-concurrency //@error-in-other-file: deadlock //@require-annotations-for-level: error diff --git a/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs b/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs index 2f8b5be0c0c57..d062aeaf34873 100644 --- a/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs +++ b/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs @@ -1,5 +1,4 @@ //@ignore-target: windows # No libc socketpair on Windows -// test_race depends on a deterministic schedule. //@compile-flags: -Zmiri-deterministic-concurrency //@error-in-other-file: deadlock //@require-annotations-for-level: error diff --git a/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.rs b/src/tools/miri/tests/fail/both_borrows/deallocate_against_protector1.rs similarity index 50% rename from src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.rs rename to src/tools/miri/tests/fail/both_borrows/deallocate_against_protector1.rs index a34df7c7fe3ae..211d74caadd8a 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.rs +++ b/src/tools/miri/tests/fail/both_borrows/deallocate_against_protector1.rs @@ -1,4 +1,8 @@ -//@error-in-other-file: /deallocating while item \[Unique for .*\] is strongly protected/ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +//@[stack]error-in-other-file: /deallocating while item \[Unique for .*\] is strongly protected/ +//@[tree]error-in-other-file: /deallocation through .* is forbidden/ fn inner(x: &mut i32, f: fn(&mut i32)) { // `f` may mutate, but it may not deallocate! diff --git a/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr b/src/tools/miri/tests/fail/both_borrows/deallocate_against_protector1.stack.stderr similarity index 76% rename from src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr rename to src/tools/miri/tests/fail/both_borrows/deallocate_against_protector1.stack.stderr index 9f0df14ee4ddd..b8a763a5fc3f4 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr +++ b/src/tools/miri/tests/fail/both_borrows/deallocate_against_protector1.stack.stderr @@ -14,13 +14,13 @@ LL | self.1.deallocate(From::from(ptr.cast()), layout); 2: std::mem::drop::> at RUSTLIB/core/src/mem/mod.rs:LL:CC 3: main::{closure#0} - at tests/fail/stacked_borrows/deallocate_against_protector1.rs:LL:CC - 4: <{closure@tests/fail/stacked_borrows/deallocate_against_protector1.rs:LL:CC} as std::ops::FnOnce<(&mut i32,)>>::call_once - shim + at tests/fail/both_borrows/deallocate_against_protector1.rs:LL:CC + 4: <{closure@tests/fail/both_borrows/deallocate_against_protector1.rs:LL:CC} as std::ops::FnOnce<(&mut i32,)>>::call_once - shim at RUSTLIB/core/src/ops/function.rs:LL:CC 5: inner - at tests/fail/stacked_borrows/deallocate_against_protector1.rs:LL:CC + at tests/fail/both_borrows/deallocate_against_protector1.rs:LL:CC 6: main - at tests/fail/stacked_borrows/deallocate_against_protector1.rs:LL:CC + at tests/fail/both_borrows/deallocate_against_protector1.rs:LL:CC note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace diff --git a/src/tools/miri/tests/fail/both_borrows/deallocate_against_protector1.tree.stderr b/src/tools/miri/tests/fail/both_borrows/deallocate_against_protector1.tree.stderr new file mode 100644 index 0000000000000..3e97a15333ac6 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/deallocate_against_protector1.tree.stderr @@ -0,0 +1,40 @@ +error: Undefined Behavior: deallocation through at ALLOC[0x0] is forbidden + --> RUSTLIB/alloc/src/boxed.rs:LL:CC + | +LL | self.1.deallocate(From::from(ptr.cast()), layout); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the allocation of the accessed tag also contains the strongly protected tag + = help: the strongly protected tag disallows deallocations +help: the accessed tag was created here + --> tests/fail/both_borrows/deallocate_against_protector1.rs:LL:CC + | +LL | drop(unsafe { Box::from_raw(raw) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: the strongly protected tag was created here, in the initial state Reserved + --> tests/fail/both_borrows/deallocate_against_protector1.rs:LL:CC + | +LL | inner(Box::leak(Box::new(0)), |x| { + | ^ + = note: stack backtrace: + 0: as std::ops::Drop>::drop + at RUSTLIB/alloc/src/boxed.rs:LL:CC + 1: std::ptr::drop_glue::> - shim(Some(std::boxed::Box)) + at RUSTLIB/core/src/ptr/mod.rs:LL:CC + 2: std::mem::drop::> + at RUSTLIB/core/src/mem/mod.rs:LL:CC + 3: main::{closure#0} + at tests/fail/both_borrows/deallocate_against_protector1.rs:LL:CC + 4: <{closure@tests/fail/both_borrows/deallocate_against_protector1.rs:LL:CC} as std::ops::FnOnce<(&mut i32,)>>::call_once - shim + at RUSTLIB/core/src/ops/function.rs:LL:CC + 5: inner + at tests/fail/both_borrows/deallocate_against_protector1.rs:LL:CC + 6: main + at tests/fail/both_borrows/deallocate_against_protector1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/deallocate_against_protector2.rs b/src/tools/miri/tests/fail/both_borrows/deallocate_against_protector2.rs new file mode 100644 index 0000000000000..4cac0cdd03f8b --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/deallocate_against_protector2.rs @@ -0,0 +1,23 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +// Ensure that even a ZST prevents the reference from being used for deallocation. +// The `nofree` attributes we add in LLVM IR rely on this. + +use std::alloc::Layout; + +fn inner(x: &mut (), f: fn(&mut ())) { + // `f` may mutate, but it may not deallocate! + f(x) +} + +fn main() { + let ptr = Box::leak(Box::new(0i32)) as *mut i32; + inner(unsafe { &mut *(ptr as *mut ()) }, |x| unsafe { + let raw = x as *mut _ as *mut i32; + // Avoid ever creating a `Box`, we don't want any implicit accesses. + std::alloc::dealloc(raw.cast(), Layout::new::()); + //~[tree]^ERROR: /deallocation through .* is forbidden/ + //~[stack]|ERROR: tag does not exist in the borrow stack for this location + }); +} diff --git a/src/tools/miri/tests/fail/both_borrows/deallocate_against_protector2.stack.stderr b/src/tools/miri/tests/fail/both_borrows/deallocate_against_protector2.stack.stderr new file mode 100644 index 0000000000000..20993ef5072b1 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/deallocate_against_protector2.stack.stderr @@ -0,0 +1,27 @@ +error: Undefined Behavior: attempting deallocation using at ALLOC, but that tag does not exist in the borrow stack for this location + --> tests/fail/both_borrows/deallocate_against_protector2.rs:LL:CC + | +LL | std::alloc::dealloc(raw.cast(), Layout::new::()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: would have been created here, but this is a zero-size retag ([0x0..0x0]) so the tag in question does not exist anywhere + --> tests/fail/both_borrows/deallocate_against_protector2.rs:LL:CC + | +LL | let raw = x as *mut _ as *mut i32; + | ^ + = note: stack backtrace: + 0: main::{closure#0} + at tests/fail/both_borrows/deallocate_against_protector2.rs:LL:CC + 1: <{closure@tests/fail/both_borrows/deallocate_against_protector2.rs:LL:CC} as std::ops::FnOnce<(&mut (),)>>::call_once - shim + at RUSTLIB/core/src/ops/function.rs:LL:CC + 2: inner + at tests/fail/both_borrows/deallocate_against_protector2.rs:LL:CC + 3: main + at tests/fail/both_borrows/deallocate_against_protector2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/deallocate_against_protector2.tree.stderr b/src/tools/miri/tests/fail/both_borrows/deallocate_against_protector2.tree.stderr new file mode 100644 index 0000000000000..7fae0b6a07773 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/deallocate_against_protector2.tree.stderr @@ -0,0 +1,29 @@ +error: Undefined Behavior: deallocation through at ALLOC[0x0] is forbidden + --> tests/fail/both_borrows/deallocate_against_protector2.rs:LL:CC + | +LL | std::alloc::dealloc(raw.cast(), Layout::new::()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the allocation of the accessed tag also contains the strongly protected tag + = help: the strongly protected tag disallows deallocations +help: the strongly protected tag was created here, in the initial state Reserved + --> tests/fail/both_borrows/deallocate_against_protector2.rs:LL:CC + | +LL | inner(unsafe { &mut *(ptr as *mut ()) }, |x| unsafe { + | ^ + = note: stack backtrace: + 0: main::{closure#0} + at tests/fail/both_borrows/deallocate_against_protector2.rs:LL:CC + 1: <{closure@tests/fail/both_borrows/deallocate_against_protector2.rs:LL:CC} as std::ops::FnOnce<(&mut (),)>>::call_once - shim + at RUSTLIB/core/src/ops/function.rs:LL:CC + 2: inner + at tests/fail/both_borrows/deallocate_against_protector2.rs:LL:CC + 3: main + at tests/fail/both_borrows/deallocate_against_protector2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs index 75eb06bc12cfb..90f70064932c1 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs @@ -146,7 +146,7 @@ fn test_epoll_ctl_del() { events: (EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO) as u32, u64: u64::try_from(fds[1]).unwrap(), }; - let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[1], &mut ev) }; + let res = unsafe { libc::epoll_ctl(epfd, EPOLL_CTL_ADD, fds[1], &mut ev) }; assert_eq!(res, 0); // Test EPOLL_CTL_DEL. @@ -158,10 +158,8 @@ fn test_epoll_ctl_del() { // This test is for one fd registered under two different epoll instance. fn test_two_epoll_instance() { // Create two epoll instance. - let epfd1 = unsafe { libc::epoll_create1(0) }; - assert_ne!(epfd1, -1); - let epfd2 = unsafe { libc::epoll_create1(0) }; - assert_ne!(epfd2, -1); + let epfd1 = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); + let epfd2 = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); // Create a socketpair instance. let mut fds = [-1, -1]; @@ -570,8 +568,7 @@ fn test_epoll_ctl_epfd_equal_fd() { // epfd that shouldn't receive a notification in edge-triggered mode. fn test_epoll_ctl_notification() { // Create an epoll instance. - let epfd0 = unsafe { libc::epoll_create1(0) }; - assert_ne!(epfd0, -1); + let epfd0 = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); // Create a socketpair instance. let mut fds = [-1, -1]; @@ -584,8 +581,7 @@ fn test_epoll_ctl_notification() { check_epoll_wait_noblock(epfd0, &[Ev { events: EPOLLOUT, data: fds[0] }]); // Create another epoll instance. - let epfd1 = unsafe { libc::epoll_create1(0) }; - assert_ne!(epfd1, -1); + let epfd1 = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); // Register the same file description for epfd1. epoll_ctl_add(epfd1, fds[0], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); @@ -692,8 +688,7 @@ fn test_issue_3858() { /// Ensure that if a socket becomes un-writable, we don't see it any more. fn test_issue_4374() { // Create an epoll instance. - let epfd0 = unsafe { libc::epoll_create1(0) }; - assert_ne!(epfd0, -1); + let epfd0 = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); // Create a socketpair instance, make it non-blocking. let mut fds = [-1, -1]; @@ -721,8 +716,7 @@ fn test_issue_4374() { /// Same as above, but for becoming un-readable. fn test_issue_4374_reads() { // Create an epoll instance. - let epfd0 = unsafe { libc::epoll_create1(0) }; - assert_ne!(epfd0, -1); + let epfd0 = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); // Create a socketpair instance, make it non-blocking. let mut fds = [-1, -1]; diff --git a/src/tools/miri/tests/pass-dep/libc/libc-eventfd.rs b/src/tools/miri/tests/pass-dep/libc/libc-eventfd.rs index e86c70b590b36..e753983344bd4 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-eventfd.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-eventfd.rs @@ -1,6 +1,7 @@ //@only-target: linux android illumos // test_race, test_blocking_read and test_blocking_write depend on a deterministic schedule. //@compile-flags: -Zmiri-deterministic-concurrency +//@run-native // FIXME(static_mut_refs): Do not allow `static_mut_refs` lint #![allow(static_mut_refs)] @@ -9,6 +10,7 @@ use std::{io, thread}; #[path = "../../utils/libc.rs"] mod libc_utils; +use libc_utils::*; fn main() { test_read_write(); @@ -35,8 +37,8 @@ fn write_bytes(fd: i32, data: [u8; N]) -> io::Result { } fn test_read_write() { - let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC; - let fd = unsafe { libc::eventfd(0, flags) }; + let fd = + errno_result(unsafe { libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC) }).unwrap(); let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes(); // Write 1 to the counter. let res = write_bytes(fd, sized_8_data).unwrap(); @@ -97,9 +99,15 @@ fn test_read_write() { fn test_race() { static mut VAL: u8 = 0; - let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC; - let fd = unsafe { libc::eventfd(0, flags) }; + + let fd = + errno_result(unsafe { libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC) }).unwrap(); let thread1 = thread::spawn(move || { + if !cfg!(miri) { + // Make sure the write goes first. + thread::sleep(std::time::Duration::from_millis(10)); + } + let mut buf: [u8; 8] = [0; 8]; let res = read_bytes(fd, &mut buf).unwrap(); // read returns number of bytes has been read, which is always 8. @@ -130,8 +138,7 @@ fn test_syscall() { // This test will block on eventfd read then get unblocked by `write`. fn test_blocking_read() { // eventfd read will block when EFD_NONBLOCK flag is clear and counter = 0. - let flags = libc::EFD_CLOEXEC; - let fd = unsafe { libc::eventfd(0, flags) }; + let fd = errno_result(unsafe { libc::eventfd(0, libc::EFD_CLOEXEC) }).unwrap(); let thread1 = thread::spawn(move || { let mut buf: [u8; 8] = [0; 8]; // This will block. @@ -154,8 +161,7 @@ fn test_blocking_read() { fn test_blocking_write() { // eventfd write will block when EFD_NONBLOCK flag is clear // and the addition caused counter to exceed u64::MAX - 1. - let flags = libc::EFD_CLOEXEC; - let fd = unsafe { libc::eventfd(0, flags) }; + let fd = errno_result(unsafe { libc::eventfd(0, libc::EFD_CLOEXEC) }).unwrap(); // Write u64 - 1, so the all subsequent write will block. let sized_8_data: [u8; 8] = (u64::MAX - 1).to_ne_bytes(); let res: i64 = unsafe { @@ -192,8 +198,7 @@ fn test_blocking_write() { fn test_two_threads_blocked_on_eventfd() { // eventfd write will block when EFD_NONBLOCK flag is clear // and the addition caused counter to exceed u64::MAX - 1. - let flags = libc::EFD_CLOEXEC; - let fd = unsafe { libc::eventfd(0, flags) }; + let fd = errno_result(unsafe { libc::eventfd(0, libc::EFD_CLOEXEC) }).unwrap(); // Write u64 - 1, so the all subsequent write will block. let sized_8_data: [u8; 8] = (u64::MAX - 1).to_ne_bytes(); let res: i64 = unsafe { diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index a131112ee258d..d9d00d7ba239b 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -272,7 +272,7 @@ fn test_dup() { let name = CString::new(path.into_os_string().into_encoded_bytes()).unwrap(); unsafe { - let fd = libc::open(name.as_ptr(), libc::O_RDONLY); + let fd = errno_result(libc::open(name.as_ptr(), libc::O_RDONLY)).unwrap(); let new_fd = libc::dup(fd); let new_fd2 = libc::dup2(fd, 8); @@ -856,8 +856,8 @@ fn test_readdir() { assert!(!dirp.is_null()); let mut entries = Vec::new(); loop { - cfg_if::cfg_if! { - if #[cfg(target_os = "macos")] { + cfg_select! { + target_os = "macos" => { // On macos we only support readdir_r as that's what std uses there. use std::mem::MaybeUninit; use libc::dirent; @@ -866,7 +866,8 @@ fn test_readdir() { let ret = libc::readdir_r(dirp, entry.as_mut_ptr(), &mut result); assert_eq!(ret, 0); let entry_ptr = result; - } else { + } + _ => { let entry_ptr = libc::readdir(dirp); } } diff --git a/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs b/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs index 98d7340fa9db3..12b5ae5666cfd 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs @@ -1,6 +1,7 @@ //@ignore-target: windows # No libc pipe on Windows // test_race depends on a deterministic schedule. //@compile-flags: -Zmiri-deterministic-concurrency +//@run-native use std::thread; #[path = "../../utils/libc.rs"] @@ -39,7 +40,7 @@ fn test_pipe() { let data = b"123"; write_all(fds[1], data).unwrap(); let mut buf4: [u8; 5] = [0; 5]; - let (part1, rest) = read_split_slice(fds[0], &mut buf4).unwrap(); + let (part1, rest) = read_partial(fds[0], &mut buf4).unwrap(); assert_eq!(part1[..], data[..part1.len()]); // Write 2 more bytes so we can exactly fill the `rest`. write_all(fds[1], b"34").unwrap(); diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs index 9ed0b9c735979..b7918581bc649 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs @@ -28,6 +28,8 @@ fn main() { test_readiness_after_short_read(); test_readiness_after_short_peek(); test_readiness_after_short_write(); + test_readable_after_read_shutdown_and_short_read(); + test_writable_after_write_shutdown_with_full_buffer(); } /// Test that connecting to a server socket works when the client @@ -359,7 +361,7 @@ fn test_shutdown_read_write() { let (server_sockfd, addr) = net::make_listener_ipv4().unwrap(); let client_sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - let epfd = unsafe { libc::epoll_create1(0) }; + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); // Spawn the server thread. let server_thread = thread::spawn(move || net::accept_ipv4(server_sockfd).unwrap()); @@ -387,7 +389,7 @@ fn test_shutdown_read() { let (server_sockfd, addr) = net::make_listener_ipv4().unwrap(); let client_sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - let epfd = unsafe { libc::epoll_create1(0) }; + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); // Spawn the server thread. let server_thread = thread::spawn(move || net::accept_ipv4(server_sockfd).unwrap()); @@ -411,7 +413,7 @@ fn test_shutdown_write() { let (server_sockfd, addr) = net::make_listener_ipv4().unwrap(); let client_sockfd = unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - let epfd = unsafe { libc::epoll_create1(0) }; + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); // Spawn the server thread. let server_thread = thread::spawn(move || { @@ -562,24 +564,14 @@ fn test_readiness_after_short_write() { unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); - // Spawn the server thread. - let server_thread = thread::spawn(move || { - let (peerfd, _) = net::accept_ipv4(server_sockfd).unwrap(); - // Return the peer socket file descriptor such that we can use - // it after joining the server thread. - peerfd - }); - net::connect_ipv4(client_sockfd, addr).unwrap(); + let (peerfd, _) = net::accept_ipv4(server_sockfd).unwrap(); unsafe { // Change client socket to be non-blocking. errno_check(libc::fcntl(client_sockfd, libc::F_SETFL, libc::O_NONBLOCK)); } - // The peer socket is a blocking socket. - let peerfd = server_thread.join().unwrap(); - // Add client socket with writable interest to epoll. epoll_ctl_add(epfd, client_sockfd, EPOLLET | EPOLLOUT).unwrap(); @@ -636,3 +628,153 @@ fn test_readiness_after_short_write() { // We should again be able to write into the socket. libc_utils::write_all(client_sockfd, &buffer).unwrap(); } + +/// Test that Miri correctly keeps the readable readiness when the read end of the client +/// socket has been closed -- even after a short read. +fn test_readable_after_read_shutdown_and_short_read() { + let (server_sockfd, addr) = net::make_listener_ipv4().unwrap(); + let client_sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); + + // Spawn the server thread. + let server_thread = thread::spawn(move || { + let (peerfd, _) = net::accept_ipv4(server_sockfd).unwrap(); + + // Write `TEST_BYTES` into the stream. + libc_utils::write_all(peerfd, TEST_BYTES).unwrap(); + + // Close the write end, so that the reader will get an EOF. + // (We could alternatively test this by closing the read end of the client socket, + // but Windows has some special behavior when closing a read end while there's still + // data coming in, so we avoid that.) + unsafe { errno_check(libc::shutdown(peerfd, libc::SHUT_WR)) }; + }); + + net::connect_ipv4(client_sockfd, addr).unwrap(); + + // Change client socket to be non-blocking. + unsafe { errno_check(libc::fcntl(client_sockfd, libc::F_SETFL, libc::O_NONBLOCK)) }; + + server_thread.join().unwrap(); + + // Add client socket with "read closed" and "readable" interest to epoll. + epoll_ctl_add(epfd, client_sockfd, EPOLLET | EPOLLIN | EPOLLRDHUP).unwrap(); + + // Ensure that the socket is readable and that its read end is closed. + check_epoll_wait(epfd, &[Ev { events: EPOLLIN | EPOLLRDHUP, data: client_sockfd }], -1); + + let mut buffer = [0u8; 1024]; + + // We want to read in chunks of 16 bytes. To ensure we get a short read, `TEST_BYTES.len()` + // must not be dividable by 16. + assert!(TEST_BYTES.len() % 16 != 0); + + let mut total_bytes_read = 0; + // Read everything from the socket until we get a short read. + // We don't want to provide `TEST_BYTES.len()` as `count` because then we won't trigger + // a short read. + loop { + let bytes_read = unsafe { + errno_result(libc::read( + client_sockfd, + buffer.as_mut_ptr().byte_add(total_bytes_read).cast(), + // Read a chunk of 16 bytes. + 16, + )) + .unwrap() + }; + + total_bytes_read += bytes_read as usize; + if bytes_read < 16 { + // We had a short read; we thus assume the read buffer is empty. + break; + } + } + assert_eq!(total_bytes_read, TEST_BYTES.len()); + + // We had a short read because `buffer` is bigger than `TEST_BYTES`. + // Because the read end of the socket is closed, we should still be able to + // read to detect EOFs. + + // Ensure that the "readable" and "read closed" readiness flags are still set. + assert_eq!( + current_epoll_readiness::<8>(client_sockfd, EPOLLIN | EPOLLET | EPOLLRDHUP), + EPOLLIN | EPOLLRDHUP + ); + + // A read should not block and return 0, indicating EOF. + let mut buffer = [1u8; 16]; + let bytes_read = unsafe { + errno_result(libc::read(client_sockfd, buffer.as_mut_ptr().cast(), buffer.len())).unwrap() + }; + assert_eq!(bytes_read, 0); +} + +/// Test that the writable readiness gets set when the write end of a socket +/// is closed -- even when the socket write buffer is full. +fn test_writable_after_write_shutdown_with_full_buffer() { + let (server_sockfd, addr) = net::make_listener_ipv4().unwrap(); + let client_sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); + + net::connect_ipv4(client_sockfd, addr).unwrap(); + net::accept_ipv4(server_sockfd).unwrap(); + + unsafe { + // Change client socket to be non-blocking. + errno_check(libc::fcntl(client_sockfd, libc::F_SETFL, libc::O_NONBLOCK)); + } + + // Add client socket with level-triggered "writable" and "write closed" interest to epoll. + epoll_ctl_add(epfd, client_sockfd, EPOLLOUT | EPOLLHUP).unwrap(); + + // Wait until the socket becomes writable. + check_epoll_wait(epfd, &[Ev { events: EPOLLOUT, data: client_sockfd }], -1); + + // We now want to fill the write buffer of the socket by repeatedly writing + // `buffer` into it. The last write should then be a short write. + // We assume/hope that the write buffer length is not divisible by 1039. + let buffer = [123u8; 1039]; + + loop { + let result = unsafe { + errno_result(libc::write(client_sockfd, buffer.as_ptr().cast(), buffer.len())) + }; + + match result { + Ok(bytes_written) => { + if (bytes_written as usize) < buffer.len() { + // We had a short write; we thus assume the write buffer is full. + break; + } + } + Err(err) if err.kind() == ErrorKind::WouldBlock => { + // Windows and Apple hosts behave weirdly when attempting to fill up the write buffer. + // Instead of doing a short write to completely fill the buffer, they can return an + // EWOULDBLOCK when the next write wouldn't fit into the buffer. + // When we get such an error, we also assume the write buffer is full. + break; + } + Err(err) => panic!("unexpected error whilst filling up buffer: {err}"), + } + } + + // The write buffer is full; because this is a level-triggered interest, + // a readiness of 0 means that the socket would now block on writes. + check_epoll_wait(epfd, &[], 0); + + // Close the socket write end. + unsafe { + errno_check(libc::shutdown(client_sockfd, libc::SHUT_WR)); + } + + // The socket should no longer block on writes after its write end is closed. + check_epoll_wait(epfd, &[Ev { events: EPOLLOUT, data: client_sockfd }], -1); + + // A write should not block and return an error. + let result = + unsafe { errno_result(libc::write(client_sockfd, buffer.as_ptr().cast(), buffer.len())) }; + assert_eq!(result.unwrap_err().kind(), ErrorKind::BrokenPipe); +} diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs b/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs index da521600d84a6..f994dc28a9349 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs @@ -1,6 +1,7 @@ //@ignore-target: windows # No libc socketpair on Windows // test_race depends on a deterministic schedule. //@compile-flags: -Zmiri-deterministic-concurrency +//@run-native // FIXME(static_mut_refs): Do not allow `static_mut_refs` lint #![allow(static_mut_refs)] @@ -34,7 +35,7 @@ fn test_socketpair() { let data = b"abc"; write_all(fds[0], data).unwrap(); let mut buf2: [u8; 5] = [0; 5]; - let (read, rest) = read_split_slice(fds[1], &mut buf2).unwrap(); + let (read, rest) = read_partial(fds[1], &mut buf2).unwrap(); assert_eq!(read[..], data[..read.len()]); // Write 2 more bytes so we can exactly fill the `rest`. write_all(fds[0], b"12").unwrap(); @@ -52,7 +53,7 @@ fn test_socketpair() { let data = b"abc"; write_all(fds[1], data).unwrap(); let mut buf4: [u8; 5] = [0; 5]; - let (read, rest) = read_split_slice(fds[0], &mut buf4).unwrap(); + let (read, rest) = read_partial(fds[0], &mut buf4).unwrap(); assert_eq!(read[..], data[..read.len()]); // Write 2 more bytes so we can exactly fill the `rest`. write_all(fds[1], b"12").unwrap(); @@ -64,9 +65,9 @@ fn test_socketpair() { errno_check(unsafe { libc::close(fds[0]) }); // Reading the other end should return that data, then EOF. let mut buf: [u8; 5] = [0; 5]; - let (read, _tail) = read_split_slice(fds[1], &mut buf).unwrap(); + let (read, _tail) = read_partial(fds[1], &mut buf).unwrap(); assert_eq!(read, data); - let (read, _tail) = read_split_slice(fds[1], &mut buf).unwrap(); + let (read, _tail) = read_partial(fds[1], &mut buf).unwrap(); assert_eq!(read, &[]); // Writing the other end should emit EPIPE. let err = write_all(fds[1], &mut buf).unwrap_err(); @@ -132,6 +133,11 @@ fn test_blocking_read() { // Test the behaviour of a socketpair getting blocked on write and subsequently unblocked. fn test_blocking_write() { + // The test uses Miri's exact buffer size. + if !cfg!(miri) { + return; + } + let mut fds = [-1, -1]; errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); let arr1: [u8; 0x34000] = [1; 0x34000]; diff --git a/src/tools/miri/tests/pass-dep/libc/libc-time.rs b/src/tools/miri/tests/pass-dep/libc/libc-time.rs index f315d2ab117f9..3bfcfab099d7b 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-time.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-time.rs @@ -1,5 +1,6 @@ //@ignore-target: windows # no libc time APIs on Windows //@compile-flags: -Zmiri-disable-isolation +//@run-native #[path = "../../utils/libc.rs"] mod libc_utils; @@ -9,6 +10,17 @@ use std::{env, mem, ptr}; use libc_utils::errno_check; +fn set_tz(name: &str) { + extern "C" { + fn tzset(); + } + + env::set_var("TZ", name); + if !cfg!(miri) { + unsafe { tzset() }; // re-read TZ env var (natively, it may be cached) + } +} + fn main() { test_clocks(); test_posix_gettimeofday(); @@ -66,11 +78,13 @@ fn test_posix_gettimeofday() { assert!(tv.tv_sec > 0); assert!(tv.tv_usec >= 0); // Theoretically this could be 0. - // Test that non-null tz returns an error (because we don't support it). - let mut tz = mem::MaybeUninit::::uninit(); - let tz_ptr = tz.as_mut_ptr(); - let is_error = unsafe { libc::gettimeofday(tp.as_mut_ptr(), tz_ptr.cast()) }; - assert_eq!(is_error, -1); + if cfg!(miri) { + // Test that non-null tz returns an error (because we don't support it). + let mut tz = mem::MaybeUninit::::uninit(); + let tz_ptr = tz.as_mut_ptr(); + let is_error = unsafe { libc::gettimeofday(tp.as_mut_ptr(), tz_ptr.cast()) }; + assert_eq!(is_error, -1); + } } /// Helper function to create an empty tm struct. @@ -104,9 +118,8 @@ fn create_empty_tm() -> libc::tm { /// Original GMT test fn test_localtime_r_gmt() { - // Set timezone to GMT. - let key = "TZ"; - env::set_var(key, "GMT"); + set_tz("GMT"); + const TIME_SINCE_EPOCH: libc::time_t = 1712475836; // 2024-04-07 07:43:56 GMT let custom_time_ptr = &TIME_SINCE_EPOCH; let mut tm = create_empty_tm(); @@ -120,7 +133,9 @@ fn test_localtime_r_gmt() { assert_eq!(tm.tm_year, 124); assert_eq!(tm.tm_wday, 0); assert_eq!(tm.tm_yday, 97); - assert_eq!(tm.tm_isdst, -1); + if cfg!(miri) { + assert_eq!(tm.tm_isdst, -1); + } #[cfg(any( target_os = "linux", target_os = "macos", @@ -130,21 +145,21 @@ fn test_localtime_r_gmt() { { assert_eq!(tm.tm_gmtoff, 0); unsafe { - assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00"); + assert_eq!( + std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), + if cfg!(miri) { "+00" } else { "GMT" } + ); } } // The returned value is the pointer passed in. assert!(ptr::eq(res, &mut tm)); - - // Remove timezone setting. - env::remove_var(key); } /// PST timezone test (testing different timezone handling). fn test_localtime_r_pst() { - let key = "TZ"; - env::set_var(key, "PST8PDT"); + set_tz("PST8PDT"); + const TIME_SINCE_EPOCH: libc::time_t = 1712475836; // 2024-04-07 07:43:56 GMT let custom_time_ptr = &TIME_SINCE_EPOCH; let mut tm = create_empty_tm(); @@ -159,7 +174,9 @@ fn test_localtime_r_pst() { assert_eq!(tm.tm_year, 124); assert_eq!(tm.tm_wday, 0); assert_eq!(tm.tm_yday, 97); - assert_eq!(tm.tm_isdst, -1); // DST information unavailable + if cfg!(miri) { + assert_eq!(tm.tm_isdst, -1); // DST information unavailable + } #[cfg(any( target_os = "linux", @@ -170,18 +187,20 @@ fn test_localtime_r_pst() { { assert_eq!(tm.tm_gmtoff, -7 * 3600); // -7 hours in seconds unsafe { - assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "-07"); + assert_eq!( + std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), + if cfg!(miri) { "-07" } else { "PDT" } + ); } } assert!(ptr::eq(res, &mut tm)); - env::remove_var(key); } /// Unix epoch test (edge case testing). fn test_localtime_r_epoch() { - let key = "TZ"; - env::set_var(key, "GMT"); + set_tz("GMT"); + const TIME_SINCE_EPOCH: libc::time_t = 0; // 1970-01-01 00:00:00 let custom_time_ptr = &TIME_SINCE_EPOCH; let mut tm = create_empty_tm(); @@ -196,7 +215,9 @@ fn test_localtime_r_epoch() { assert_eq!(tm.tm_year, 70); assert_eq!(tm.tm_wday, 4); // Thursday assert_eq!(tm.tm_yday, 0); - assert_eq!(tm.tm_isdst, -1); + if cfg!(miri) { + assert_eq!(tm.tm_isdst, -1); + } #[cfg(any( target_os = "linux", @@ -207,19 +228,20 @@ fn test_localtime_r_epoch() { { assert_eq!(tm.tm_gmtoff, 0); unsafe { - assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00"); + assert_eq!( + std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), + if cfg!(miri) { "+00" } else { "GMT" } + ); } } assert!(ptr::eq(res, &mut tm)); - env::remove_var(key); } /// Future date test (testing large values). #[cfg(target_pointer_width = "64")] fn test_localtime_r_future_64b() { - let key = "TZ"; - env::set_var(key, "GMT"); + set_tz("GMT"); // Using 2050-01-01 00:00:00 for 64-bit systems // value that's safe for 64-bit time_t @@ -237,7 +259,9 @@ fn test_localtime_r_future_64b() { assert_eq!(tm.tm_year, 150); // 2050 - 1900 assert_eq!(tm.tm_wday, 6); // Saturday assert_eq!(tm.tm_yday, 0); - assert_eq!(tm.tm_isdst, -1); + if cfg!(miri) { + assert_eq!(tm.tm_isdst, -1); + } #[cfg(any( target_os = "linux", @@ -248,19 +272,20 @@ fn test_localtime_r_future_64b() { { assert_eq!(tm.tm_gmtoff, 0); unsafe { - assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00"); + assert_eq!( + std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), + if cfg!(miri) { "+00" } else { "GMT" } + ); } } assert!(ptr::eq(res, &mut tm)); - env::remove_var(key); } /// Future date test (testing large values for 32b target). #[cfg(target_pointer_width = "32")] fn test_localtime_r_future_32b() { - let key = "TZ"; - env::set_var(key, "GMT"); + set_tz("GMT"); // Using 2030-01-01 00:00:00 for 32-bit systems // Safe value within i32 range @@ -279,7 +304,9 @@ fn test_localtime_r_future_32b() { assert_eq!(tm.tm_year, 130); // 2030 - 1900 assert_eq!(tm.tm_wday, 2); // Tuesday assert_eq!(tm.tm_yday, 0); - assert_eq!(tm.tm_isdst, -1); + if cfg!(miri) { + assert_eq!(tm.tm_isdst, -1); + } #[cfg(any( target_os = "linux", @@ -290,19 +317,20 @@ fn test_localtime_r_future_32b() { { assert_eq!(tm.tm_gmtoff, 0); unsafe { - assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00"); + assert_eq!( + std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), + if cfg!(miri) { "+00" } else { "GMT" } + ); } } assert!(ptr::eq(res, &mut tm)); - env::remove_var(key); } /// Tests the behavior of `localtime_r` with multiple calls to ensure deduplication of `tm_zone` pointers. #[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "android"))] fn test_localtime_r_multiple_calls_deduplication() { - let key = "TZ"; - env::set_var(key, "PST8PDT"); + set_tz("PST8PDT"); const TIME_SINCE_EPOCH_BASE: libc::time_t = 1712475836; // Base timestamp: 2024-04-07 07:43:56 GMT const NUM_CALLS: usize = 50; @@ -321,9 +349,11 @@ fn test_localtime_r_multiple_calls_deduplication() { let unique_count = unique_pointers.len(); + // Miri non-determinisitcally de-duplicates. Native always deduplicates. + let min = if cfg!(miri) { 2 } else { 1 }; assert!( - unique_count >= 2 && unique_count <= (NUM_CALLS - 1), - "Unexpected number of unique tm_zone pointers: {} (expected between 2 and {})", + unique_count >= min && unique_count <= (NUM_CALLS - 1), + "Unexpected number of unique tm_zone pointers: {} (expected between {min} and {})", unique_count, NUM_CALLS - 1 ); diff --git a/src/tools/miri/tests/pass-dep/libc/mmap.rs b/src/tools/miri/tests/pass-dep/libc/mmap.rs index bfd840d2fb89d..692b59d443ae8 100644 --- a/src/tools/miri/tests/pass-dep/libc/mmap.rs +++ b/src/tools/miri/tests/pass-dep/libc/mmap.rs @@ -94,6 +94,78 @@ fn test_mmap( assert_eq!(Error::last_os_error().raw_os_error().unwrap(), libc::EINVAL); } +fn test_mprotect() { + let page_size = page_size::get(); + let ptr = unsafe { + libc::mmap( + ptr::null_mut(), + 4 * page_size, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + Default::default(), + ) + }; + assert!(!ptr.is_null()); + + // Protect part of it redundantly. + let res = unsafe { + libc::mprotect(ptr.byte_add(2 * page_size), 42, libc::PROT_READ | libc::PROT_WRITE) + }; + assert_eq!(res, 0i32); + + // Protect everything redundantly. + let res = unsafe { libc::mprotect(ptr, 4 * page_size, libc::PROT_READ | libc::PROT_WRITE) }; + assert_eq!(res, 0i32); + + // We report an error when the address is not a multiple of the page size. + let res = + unsafe { libc::mprotect(ptr.byte_add(11), page_size, libc::PROT_READ | libc::PROT_WRITE) }; + assert_eq!(res, -1); + assert_eq!(Error::last_os_error().raw_os_error().unwrap(), libc::EINVAL); + + // We report an error if the length cannot be rounded up to a multiple of the page size. + let res = unsafe { libc::mprotect(ptr, usize::MAX - 1, libc::PROT_READ | libc::PROT_WRITE) }; + assert_eq!(res, -1); + assert_eq!(Error::last_os_error().raw_os_error().unwrap(), libc::ENOMEM); +} + +fn test_madvise() { + let page_size = page_size::get(); + let ptr = unsafe { + libc::mmap( + ptr::null_mut(), + 4 * page_size, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + Default::default(), + ) + }; + assert!(!ptr.is_null()); + + for advice in [libc::MADV_NORMAL, libc::MADV_RANDOM, libc::MADV_SEQUENTIAL, libc::MADV_WILLNEED] + { + // Advise part of it redundantly. + let res = unsafe { libc::madvise(ptr.byte_add(2 * page_size), 42, advice) }; + assert_eq!(res, 0i32); + + // Protect everything redundantly. + let res = unsafe { libc::madvise(ptr, 4 * page_size, advice) }; + assert_eq!(res, 0i32); + } + + // We report an error when the address is not a multiple of the page size. + let res = unsafe { libc::madvise(ptr.byte_add(11), page_size, libc::MADV_NORMAL) }; + assert_eq!(res, -1); + assert_eq!(Error::last_os_error().raw_os_error().unwrap(), libc::EINVAL); + + // We report an error if the length cannot be rounded up to a multiple of the page size. + let res = unsafe { libc::madvise(ptr, usize::MAX - 1, libc::MADV_NORMAL) }; + assert_eq!(res, -1); + assert_eq!(Error::last_os_error().raw_os_error().unwrap(), libc::ENOMEM); +} + #[cfg(target_os = "linux")] fn test_mremap() { let page_size = page_size::get(); @@ -145,6 +217,8 @@ fn main() { test_mmap(libc::mmap); #[cfg(target_os = "linux")] test_mmap(libc::mmap64); + test_mprotect(); + test_madvise(); #[cfg(target_os = "linux")] test_mremap(); } diff --git a/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs b/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs index cf7ea4c6abba1..6cba672c6569c 100644 --- a/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs +++ b/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs @@ -4,17 +4,21 @@ use std::ffi::{CStr, CString}; use std::thread; const MAX_THREAD_NAME_LEN: usize = { - cfg_if::cfg_if! { - if #[cfg(any(target_os = "linux"))] { + cfg_select! { + target_os = "linux" => { 16 - } else if #[cfg(any(target_os = "illumos", target_os = "solaris"))] { + } + any(target_os = "illumos", target_os = "solaris") => { 32 - } else if #[cfg(target_os = "macos")] { + } + target_os = "macos" => { libc::MAXTHREADNAMESIZE // 64, at the time of writing - } else if #[cfg(target_os = "freebsd")] { + } + target_os = "freebsd" => { usize::MAX // as far as I can tell - } else { - panic!() + } + _ => { + compile_error!("unsupported OS"); } } }; @@ -28,35 +32,38 @@ fn main() { .collect::(); fn set_thread_name(name: &CStr) -> i32 { - cfg_if::cfg_if! { - if #[cfg(any( + cfg_select! { + any( target_os = "linux", target_os = "freebsd", target_os = "illumos", target_os = "solaris" - ))] { + ) => { unsafe { libc::pthread_setname_np(libc::pthread_self(), name.as_ptr().cast()) } - } else if #[cfg(target_os = "macos")] { + } + target_os = "macos" => { unsafe { libc::pthread_setname_np(name.as_ptr().cast()) } - } else { + } + _ => { compile_error!("set_thread_name not supported for this OS") } } } fn get_thread_name(name: &mut [u8]) -> i32 { - cfg_if::cfg_if! { - if #[cfg(any( + cfg_select! { + any( target_os = "linux", target_os = "freebsd", target_os = "illumos", target_os = "solaris", target_os = "macos" - ))] { + ) => { unsafe { libc::pthread_getname_np(libc::pthread_self(), name.as_mut_ptr().cast(), name.len()) } - } else { + } + _ => { compile_error!("get_thread_name not supported for this OS") } } @@ -95,13 +102,14 @@ fn main() { // Test what happens when the buffer is shorter than 16, but still long enough. let res = get_thread_name(&mut buf[..15]); - cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { + cfg_select! { + target_os = "linux" => { // For glibc used by linux-gnu there should be a failue, // if a shorter than 16 bytes buffer is provided, even if that would be // large enough for the thread name. assert_eq!(res, libc::ERANGE); - } else { + } + _ => { // Everywhere else, this should work. assert_eq!(res, 0); // POSIX seems to promise at least 15 chars excluding a null terminator. @@ -112,15 +120,16 @@ fn main() { // Test what happens when the buffer is too short even for the short name. let res = get_thread_name(&mut buf[..4]); - cfg_if::cfg_if! { - if #[cfg(any(target_os = "freebsd", target_os = "macos"))] { + cfg_select! { + any(target_os = "freebsd", target_os = "macos") => { // On macOS and FreeBSD it's not an error for the buffer to be // too short for the thread name -- they truncate instead. assert_eq!(res, 0); let cstr = CStr::from_bytes_until_nul(&buf).unwrap(); assert_eq!(cstr.to_bytes_with_nul().len(), 4); assert!(short_name.as_bytes().starts_with(cstr.to_bytes())); - } else { + } + _ => { // The rest should give an error. assert_eq!(res, libc::ERANGE); } @@ -128,12 +137,13 @@ fn main() { // Test zero-sized buffer. let res = get_thread_name(&mut []); - cfg_if::cfg_if! { - if #[cfg(any(target_os = "freebsd", target_os = "macos"))] { + cfg_select! { + any(target_os = "freebsd", target_os = "macos") => { // On macOS and FreeBSD it's not an error for the buffer to be // too short for the thread name -- even with size 0. assert_eq!(res, 0); - } else { + } + _ => { // The rest should give an error. assert_eq!(res, libc::ERANGE); } @@ -149,16 +159,18 @@ fn main() { // Set full thread name. let cstr = CString::new(long_name.clone()).unwrap(); let res = set_thread_name(&cstr); - cfg_if::cfg_if! { - if #[cfg(target_os = "freebsd")] { + cfg_select! { + target_os = "freebsd" => { // Names of all size are supported. assert!(cstr.to_bytes_with_nul().len() <= MAX_THREAD_NAME_LEN); assert_eq!(res, 0); - } else if #[cfg(target_os = "macos")] { + } + target_os = "macos" => { // Name is too long. assert!(cstr.to_bytes_with_nul().len() > MAX_THREAD_NAME_LEN); assert_eq!(res, libc::ENAMETOOLONG); - } else { + } + _ => { // Name is too long. assert!(cstr.to_bytes_with_nul().len() > MAX_THREAD_NAME_LEN); assert_eq!(res, libc::ERANGE); @@ -179,14 +191,15 @@ fn main() { // Test what happens when our buffer is just one byte too small. let res = get_thread_name(&mut buf[..truncated_name.len()]); - cfg_if::cfg_if! { - if #[cfg(any(target_os = "freebsd", target_os = "macos"))] { + cfg_select! { + any(target_os = "freebsd", target_os = "macos") => { // On macOS and FreeBSD it's not an error for the buffer to be // too short for the thread name -- they truncate instead. assert_eq!(res, 0); let cstr = CStr::from_bytes_until_nul(&buf).unwrap(); assert_eq!(cstr.to_bytes(), &truncated_name.as_bytes()[..(truncated_name.len() - 1)]); - } else { + } + _ => { // The rest should give an error. assert_eq!(res, libc::ERANGE); } @@ -199,10 +212,11 @@ fn main() { // Now set the name for a non-existing thread and verify error codes. let invalid_thread = 0xdeadbeef; let error = { - cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { + cfg_select! { + target_os = "linux" => { libc::ENOENT - } else { + } + _ => { libc::ESRCH } } diff --git a/src/tools/miri/tests/pass-dep/shims/gettid.rs b/src/tools/miri/tests/pass-dep/shims/gettid.rs index 0dce7bdc0c52e..9b186699ce275 100644 --- a/src/tools/miri/tests/pass-dep/shims/gettid.rs +++ b/src/tools/miri/tests/pass-dep/shims/gettid.rs @@ -5,29 +5,37 @@ #![feature(linkage)] fn gettid() -> u64 { - cfg_if::cfg_if! { - if #[cfg(any(target_os = "android", target_os = "linux"))] { + cfg_select! { + any(target_os = "android", target_os = "linux") => { gettid_linux_like() - } else if #[cfg(target_os = "nto")] { + } + target_os = "nto" => { unsafe { libc::gettid() as u64 } - } else if #[cfg(target_os = "openbsd")] { + } + target_os = "openbsd" => { unsafe { libc::getthrid() as u64 } - } else if #[cfg(target_os = "freebsd")] { + } + target_os = "freebsd" => { unsafe { libc::pthread_getthreadid_np() as u64 } - } else if #[cfg(target_os = "netbsd")] { + } + target_os = "netbsd" => { unsafe { libc::_lwp_self() as u64 } - } else if #[cfg(any(target_os = "solaris", target_os = "illumos"))] { + } + any(target_os = "solaris", target_os = "illumos") => { // On Solaris and Illumos, the `pthread_t` is the OS TID. unsafe { libc::pthread_self() as u64 } - } else if #[cfg(target_vendor = "apple")] { + } + target_vendor = "apple" => { let mut id = 0u64; let status: libc::c_int = unsafe { libc::pthread_threadid_np(0, &mut id) }; assert_eq!(status, 0); id - } else if #[cfg(windows)] { + } + windows => { use windows_sys::Win32::System::Threading::GetCurrentThreadId; unsafe { GetCurrentThreadId() as u64 } - } else { + } + _ => { compile_error!("platform has no gettid") } } diff --git a/src/tools/miri/tests/pass/issues/issue-154385-no-mangle-generic.rs b/src/tools/miri/tests/pass/issues/issue-154385-no-mangle-generic.rs new file mode 100644 index 0000000000000..0989cdd86a362 --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-154385-no-mangle-generic.rs @@ -0,0 +1,9 @@ +fn main() { + foo(1234); +} + +#[allow(no_mangle_generic_items)] +#[unsafe(no_mangle)] +fn foo(value: T) { + println!("{value:?}"); +} diff --git a/src/tools/miri/tests/pass/issues/issue-154385-no-mangle-generic.stdout b/src/tools/miri/tests/pass/issues/issue-154385-no-mangle-generic.stdout new file mode 100644 index 0000000000000..81c545efebe5f --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-154385-no-mangle-generic.stdout @@ -0,0 +1 @@ +1234 diff --git a/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-aes.rs b/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-aes.rs index 1345924beecf0..c640dde5afaf5 100644 --- a/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-aes.rs +++ b/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-aes.rs @@ -1,6 +1,7 @@ // We're testing aarch64 AES target specific features. //@only-target: aarch64 //@compile-flags: -C target-feature=+neon,+aes +//@run-native use std::arch::aarch64::*; use std::arch::is_aarch64_feature_detected; diff --git a/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-crc32.rs b/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-crc32.rs index 849f99ee36cce..8d50d152f2ebe 100644 --- a/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-crc32.rs +++ b/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-crc32.rs @@ -1,6 +1,7 @@ // We're testing aarch64 CRC32 target specific features //@only-target: aarch64 //@compile-flags: -C target-feature=+crc +//@run-native use std::arch::aarch64::*; use std::arch::is_aarch64_feature_detected; diff --git a/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-neon.rs b/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-neon.rs index 884f8eff41bdb..fdd0ff6ca6dfb 100644 --- a/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-neon.rs +++ b/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-neon.rs @@ -1,6 +1,7 @@ // We're testing aarch64 target specific features //@only-target: aarch64 //@compile-flags: -C target-feature=+neon +//@run-native use std::arch::aarch64::*; use std::arch::is_aarch64_feature_detected; @@ -14,6 +15,7 @@ fn main() { test_tbl1_v16i8_basic(); test_vpadd(); test_vpaddl(); + test_vqdmulh(); } } @@ -157,3 +159,51 @@ unsafe fn test_vpaddl() { vst1q_u64(r.as_mut_ptr(), vpaddlq_u32(a)); assert_eq!(r, e); } + +#[target_feature(enable = "neon")] +unsafe fn test_vqdmulh() { + let a = vld1_s32([i32::MIN, i32::MAX].as_ptr()); + let r: [i32; 2] = transmute(vqdmulh_n_s32(a, i32::MIN)); + assert_eq!(r, [i32::MAX, -i32::MAX]); + + // This is the actual calculation that happens. + let product = i32::MIN as i128 * i32::MIN as i128 * 2; + assert_eq!(i32::MAX, (product >> 32).clamp(i32::MIN as i128, i32::MAX as i128) as i32); + + let product = i32::MAX as i128 * i32::MIN as i128 * 2; + assert_eq!(-i32::MAX, (product >> 32).clamp(i32::MIN as i128, i32::MAX as i128) as i32); + + let b = vld1_s32([123, i32::MIN].as_ptr()); + let r: [i32; 2] = transmute(vqdmulh_s32(a, b)); + assert_eq!(r, [-123, -i32::MAX]); + + // Wider 32-bit versions. + let a = vld1q_s32([0x4000_0000, -0x4000_0000, i32::MIN, i32::MAX].as_ptr()); + + let b = vld1q_s32([123, 456, 0x4000_0000, 789].as_ptr()); + let r: [i32; 4] = transmute(vqdmulhq_s32(a, b)); + assert_eq!(r, [61, -228, -1073741824, 788]); + + let r: [i32; 4] = transmute(vqdmulhq_n_s32(a, 0x4000_0000)); + assert_eq!(r, [536870912, -536870912, -1073741824, 1073741823]); + + // 16-bit versions. + + let a = vld1_s16([i16::MIN, i16::MAX, 0, 16384].as_ptr()); + let r: [i16; 4] = transmute(vqdmulh_n_s16(a, i16::MIN)); + assert_eq!(r, [i16::MAX, -i16::MAX, 0, -16384]); + + let b = vld1_s16([123, i16::MIN, 456, 789].as_ptr()); + let r: [i16; 4] = transmute(vqdmulh_s16(a, b)); + assert_eq!(r, [-123, -i16::MAX, 0, 394]); + + // Wider 16-bit versions. + + let a = vld1q_s16([i16::MIN, i16::MAX, 0, 16384, -16384, 8192, 1, -1].as_ptr()); + let b = vld1q_s16([123, 456, 789, i16::MIN, 1, 2, 3, 4].as_ptr()); + let r: [i16; 8] = transmute(vqdmulhq_s16(a, b)); + assert_eq!(r, [-123, 455, 0, -16384, -1, 0, 0, -1]); + + let r: [i16; 8] = transmute(vqdmulhq_n_s16(a, i16::MIN)); + assert_eq!(r, [32767, -32767, 0, -16384, 16384, -8192, -1, 1]); +} diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-sha.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-sha.rs index ae5731bc8a6a3..1e36867c4a0b6 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-sha.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-sha.rs @@ -1,6 +1,7 @@ // We're testing x86 target specific features //@only-target: x86_64 i686 //@compile-flags: -C target-feature=+sha,+sse2,+ssse3,+sse4.1 +//@run-native #[cfg(target_arch = "x86")] use std::arch::x86::*; diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-adx.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-adx.rs index baa984e68d83b..87b2f5d3d80b3 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-adx.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-adx.rs @@ -1,6 +1,7 @@ // We're testing x86 target specific features //@only-target: x86_64 i686 //@compile-flags: -C target-feature=+adx +//@run-native #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] mod x86 { diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-aes-vaes.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-aes-vaes.rs index 8936ae8e91268..ba51dcea35cc6 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-aes-vaes.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-aes-vaes.rs @@ -1,6 +1,7 @@ // We're testing x86 target specific features //@only-target: x86_64 i686 //@compile-flags: -C target-feature=+aes,+vaes,+avx512f +//@run-native use core::mem::transmute; #[cfg(target_arch = "x86")] @@ -11,7 +12,6 @@ use std::arch::x86_64::*; fn main() { assert!(is_x86_feature_detected!("aes")); assert!(is_x86_feature_detected!("vaes")); - assert!(is_x86_feature_detected!("avx512f")); unsafe { test_aes(); @@ -86,7 +86,7 @@ unsafe fn test_aes() { // be interpreted as integers; signedness does not make sense for them, but // __m128i happens to be defined in terms of signed integers. #[allow(overflowing_literals)] -#[target_feature(enable = "vaes,avx512f")] +#[target_feature(enable = "vaes")] unsafe fn test_vaes() { #[target_feature(enable = "avx")] unsafe fn get_a256() -> __m256i { @@ -177,6 +177,12 @@ unsafe fn test_vaes() { } test_mm256_aesenclast_epi128(); + // The tests below require avx512. + if !is_x86_feature_detected!("avx512f") { + println!("warning: skipping avx512 tests"); + return; + } + #[target_feature(enable = "avx512f")] unsafe fn get_a512() -> __m512i { // Constants are random diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx.rs index 9f7c12c4393b5..8709a17e8aeb4 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx.rs @@ -1,6 +1,7 @@ // We're testing x86 target specific features //@only-target: x86_64 i686 //@compile-flags: -C target-feature=+avx +//@run-native #[cfg(target_arch = "x86")] use std::arch::x86::*; diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx2.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx2.rs index de1abc818420c..7fe75254c2dd8 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx2.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx2.rs @@ -1,6 +1,7 @@ // We're testing x86 target specific features //@only-target: x86_64 i686 //@compile-flags: -C target-feature=+avx2 +//@run-native #[cfg(target_arch = "x86")] use std::arch::x86::*; @@ -1068,23 +1069,31 @@ unsafe fn test_avx2() { 18, 20, 22, 24, 26, 28, 30, ); - let r = _mm256_mpsadbw_epu8::<0b000>(a, a); + let r = _mm256_mpsadbw_epu8::<0b00000>(a, a); let e = _mm256_setr_epi16(0, 4, 8, 12, 16, 20, 24, 28, 0, 8, 16, 24, 32, 40, 48, 56); assert_eq_m256i(r, e); - let r = _mm256_mpsadbw_epu8::<0b001>(a, a); + let r = _mm256_mpsadbw_epu8::<0b001001>(a, a); let e = _mm256_setr_epi16(16, 12, 8, 4, 0, 4, 8, 12, 32, 24, 16, 8, 0, 8, 16, 24); assert_eq_m256i(r, e); - let r = _mm256_mpsadbw_epu8::<0b100>(a, a); + let r = _mm256_mpsadbw_epu8::<0b000001>(a, a); + let e = _mm256_setr_epi16(16, 12, 8, 4, 0, 4, 8, 12, 0, 8, 16, 24, 32, 40, 48, 56); + assert_eq_m256i(r, e); + + let r = _mm256_mpsadbw_epu8::<0b001000>(a, a); + let e = _mm256_setr_epi16(0, 4, 8, 12, 16, 20, 24, 28, 32, 24, 16, 8, 0, 8, 16, 24); + assert_eq_m256i(r, e); + + let r = _mm256_mpsadbw_epu8::<0b100100>(a, a); let e = _mm256_setr_epi16(16, 20, 24, 28, 32, 36, 40, 44, 32, 40, 48, 56, 64, 72, 80, 88); assert_eq_m256i(r, e); - let r = _mm256_mpsadbw_epu8::<0b101>(a, a); + let r = _mm256_mpsadbw_epu8::<0b101101>(a, a); let e = _mm256_setr_epi16(0, 4, 8, 12, 16, 20, 24, 28, 0, 8, 16, 24, 32, 40, 48, 56); assert_eq_m256i(r, e); - let r = _mm256_mpsadbw_epu8::<0b111>(a, a); + let r = _mm256_mpsadbw_epu8::<0b111111>(a, a); let e = _mm256_setr_epi16(32, 28, 24, 20, 16, 12, 8, 4, 64, 56, 48, 40, 32, 24, 16, 8); assert_eq_m256i(r, e); } diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs index 31f47b57fd285..0541e9a6cc6e0 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs @@ -1,6 +1,7 @@ // We're testing x86 target specific features //@only-target: x86_64 i686 //@compile-flags: -C target-feature=+avx512f,+avx512vl,+avx512bw,+avx512bitalg,+avx512vpopcntdq,+avx512vnni,+avx512vbmi +//@run-native #[cfg(target_arch = "x86")] use std::arch::x86::*; @@ -9,6 +10,13 @@ use std::arch::x86_64::*; use std::mem::transmute; fn main() { + if !is_x86_feature_detected!("avx512f") { + // GH runners don't have this, but we still want to run this natively if + // the machine happens to have gfni. So we bail out dynamically. + println!("warning: skipping AVX512 tests"); + return; + } + assert!(is_x86_feature_detected!("avx512f")); assert!(is_x86_feature_detected!("avx512vl")); assert!(is_x86_feature_detected!("avx512bw")); diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-bmi.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-bmi.rs index 030258f21fa51..d6c05b601f4eb 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-bmi.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-bmi.rs @@ -1,6 +1,7 @@ // We're testing x86 target specific features //@only-target: x86_64 i686 //@compile-flags: -C target-feature=+bmi1,+bmi2 +//@run-native #[cfg(target_arch = "x86")] use std::arch::x86::*; diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-gfni.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-gfni.rs index 48958ef581096..ef0fd62da5f3f 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-gfni.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-gfni.rs @@ -1,6 +1,7 @@ // We're testing x86 target specific features //@only-target: x86_64 i686 //@compile-flags: -C target-feature=+gfni,+avx512f +//@run-native // The constants in the tests below are just bit patterns. They should not // be interpreted as integers; signedness does not make sense for them, but @@ -20,8 +21,14 @@ const CONSTANT_BYTE: i32 = 0x63; fn main() { // Mostly copied from library/stdarch/crates/core_arch/src/x86/gfni.rs - assert!(is_x86_feature_detected!("avx512f")); - assert!(is_x86_feature_detected!("gfni")); + assert!(is_x86_feature_detected!("avx")); + + if !is_x86_feature_detected!("gfni") { + // GH runners don't have this, but we still want to run this natively if + // the machine happens to have gfni. So we bail out dynamically. + println!("warning: skipping gfni tests"); + return; + } unsafe { let byte_mul_test_data = generate_byte_mul_test_data(); @@ -29,15 +36,20 @@ fn main() { let affine_mul_test_data_constant = generate_affine_mul_test_data(CONSTANT_BYTE as u8); let inv_tests_data = generate_inv_tests_data(); - test_mm512_gf2p8mul_epi8(&byte_mul_test_data); test_mm256_gf2p8mul_epi8(&byte_mul_test_data); test_mm_gf2p8mul_epi8(&byte_mul_test_data); - test_mm512_gf2p8affine_epi64_epi8(&byte_mul_test_data, &affine_mul_test_data_identity); test_mm256_gf2p8affine_epi64_epi8(&byte_mul_test_data, &affine_mul_test_data_identity); test_mm_gf2p8affine_epi64_epi8(&byte_mul_test_data, &affine_mul_test_data_identity); - test_mm512_gf2p8affineinv_epi64_epi8(&inv_tests_data, &affine_mul_test_data_constant); test_mm256_gf2p8affineinv_epi64_epi8(&inv_tests_data, &affine_mul_test_data_constant); test_mm_gf2p8affineinv_epi64_epi8(&inv_tests_data, &affine_mul_test_data_constant); + + if is_x86_feature_detected!("avx512f") { + test_mm512_gf2p8mul_epi8(&byte_mul_test_data); + test_mm512_gf2p8affine_epi64_epi8(&byte_mul_test_data, &affine_mul_test_data_identity); + test_mm512_gf2p8affineinv_epi64_epi8(&inv_tests_data, &affine_mul_test_data_constant); + } else { + println!("warning: skipping avx512 tests"); + } } } diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-pclmulqdq.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-pclmulqdq.rs index 6051987f8d4c1..50ce9928ab7ed 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-pclmulqdq.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-pclmulqdq.rs @@ -1,6 +1,7 @@ // We're testing x86 target specific features //@only-target: x86_64 i686 //@compile-flags: -C target-feature=+pclmulqdq +//@run-native #[cfg(target_arch = "x86")] use std::arch::x86::*; diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse.rs index 9136b5eda3870..f42c6db676839 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse.rs @@ -1,5 +1,6 @@ // We're testing x86 target specific features //@only-target: x86_64 i686 +//@run-native #![allow(unnecessary_transmutes)] #[cfg(target_arch = "x86")] diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse2.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse2.rs index 570da30f0b622..8e4b284b1d150 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse2.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse2.rs @@ -1,5 +1,6 @@ // We're testing x86 target specific features //@only-target: x86_64 i686 +//@run-native #![allow(unnecessary_transmutes)] #[cfg(target_arch = "x86")] diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse3-ssse3.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse3-ssse3.rs index 10842160abdc6..e226f3512ac6e 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse3-ssse3.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse3-ssse3.rs @@ -2,6 +2,7 @@ //@only-target: x86_64 i686 // SSSE3 implicitly enables SSE3 //@compile-flags: -C target-feature=+ssse3 +//@run-native use core::mem::transmute; #[cfg(target_arch = "x86")] diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse41.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse41.rs index 7331c6ed0db33..48510b46ff327 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse41.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse41.rs @@ -1,6 +1,7 @@ // We're testing x86 target specific features //@only-target: x86_64 i686 //@compile-flags: -C target-feature=+sse4.1 +//@run-native #[cfg(target_arch = "x86")] use std::arch::x86::*; diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse42.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse42.rs index 30908baa6c15e..c998fe800df45 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse42.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-sse42.rs @@ -1,6 +1,7 @@ // We're testing x86 target specific features //@only-target: x86_64 i686 //@compile-flags: -C target-feature=+sse4.2 +//@run-native #[cfg(target_arch = "x86")] use std::arch::x86::*; diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-vpclmulqdq.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-vpclmulqdq.rs index e2a045bf81ff1..5ceaf405f4040 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-vpclmulqdq.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-vpclmulqdq.rs @@ -3,6 +3,7 @@ //@only-target: x86_64 i686 //@[avx512]compile-flags: -C target-feature=+vpclmulqdq,+avx512f //@[avx]compile-flags: -C target-feature=+vpclmulqdq,+avx2 +//@run-native // The constants in the tests below are just bit patterns. They should not // be interpreted as integers; signedness does not make sense for them, but diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86.rs index a18b6d01524e8..9745a437eccb6 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86.rs @@ -1,4 +1,6 @@ -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +//@only-target: x86_64 i686 +//@run-native + mod x86 { #[cfg(target_arch = "x86")] use core::arch::x86 as arch; @@ -84,7 +86,6 @@ mod x86_64 { } fn main() { - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] x86::main(); #[cfg(target_arch = "x86_64")] x86_64::main(); diff --git a/src/tools/miri/tests/utils/libc.rs b/src/tools/miri/tests/utils/libc.rs index f46e6aad01a5b..0f762ed916de5 100644 --- a/src/tools/miri/tests/utils/libc.rs +++ b/src/tools/miri/tests/utils/libc.rs @@ -86,7 +86,7 @@ pub fn read_exact_array(fd: libc::c_int) -> io::Result<[u8; N]> /// Do a single read from `fd` and return the part of the buffer that was written into, /// and the rest. #[track_caller] -pub fn read_split_slice(fd: libc::c_int, buf: &mut [u8]) -> io::Result<(&mut [u8], &mut [u8])> { +pub fn read_partial(fd: libc::c_int, buf: &mut [u8]) -> io::Result<(&mut [u8], &mut [u8])> { let res = errno_result(unsafe { libc::read(fd, buf.as_mut_ptr().cast(), buf.len()) })?; Ok(buf.split_at_mut(res as usize)) }