Rollup merge of #154582 - RalfJung:miri, r=RalfJung

miri subtree update

Subtree update of `miri` to https://github.com/rust-lang/miri/commit/76621481ee89df1ba53dc32da539c0f31df202ef.

Created using https://github.com/rust-lang/josh-sync.

r? @ghost
This commit is contained in:
Trevor Gross
2026-03-31 05:08:22 -04:00
committed by GitHub
46 changed files with 908 additions and 252 deletions
+41 -12
View File
@@ -3421,14 +3421,14 @@ dependencies = [
[[package]]
name = "rustc-build-sysroot"
version = "0.5.11"
version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b881c015c729b43105bbd3702a9bdecee28fafaa21126d1d62e454ec011a4b7"
checksum = "eec3905e8201688412f6f4b1f6c86d38b3ee6578f59ba85f41330a3af61e8365"
dependencies = [
"anyhow",
"rustc_version",
"tempfile",
"toml 0.8.23",
"toml 1.1.0+spec-1.1.0",
"walkdir",
]
@@ -5195,9 +5195,9 @@ dependencies = [
[[package]]
name = "serde_spanned"
version = "1.0.3"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392"
checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98"
dependencies = [
"serde_core",
]
@@ -5693,7 +5693,6 @@ version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"indexmap",
"serde",
"serde_spanned 0.6.9",
"toml_datetime 0.6.11",
@@ -5708,13 +5707,28 @@ checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8"
dependencies = [
"indexmap",
"serde_core",
"serde_spanned 1.0.3",
"serde_spanned 1.1.0",
"toml_datetime 0.7.3",
"toml_parser",
"toml_writer",
"winnow 0.7.13",
]
[[package]]
name = "toml"
version = "1.1.0+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8195ca05e4eb728f4ba94f3e3291661320af739c4e43779cbdfae82ab239fcc"
dependencies = [
"indexmap",
"serde_core",
"serde_spanned 1.1.0",
"toml_datetime 1.1.0+spec-1.1.0",
"toml_parser",
"toml_writer",
"winnow 1.0.0",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
@@ -5733,6 +5747,15 @@ dependencies = [
"serde_core",
]
[[package]]
name = "toml_datetime"
version = "1.1.0+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_edit"
version = "0.19.15"
@@ -5762,11 +5785,11 @@ dependencies = [
[[package]]
name = "toml_parser"
version = "1.0.4"
version = "1.1.0+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011"
dependencies = [
"winnow 0.7.13",
"winnow 1.0.0",
]
[[package]]
@@ -5777,9 +5800,9 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
name = "toml_writer"
version = "1.0.4"
version = "1.1.0+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed"
[[package]]
name = "tracing"
@@ -6765,6 +6788,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "winnow"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8"
[[package]]
name = "winsplit"
version = "0.1.0"
+5 -5
View File
@@ -58,7 +58,7 @@ jobs:
env:
HOST_TARGET: ${{ matrix.host_target }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: install multiarch
if: ${{ matrix.multiarch != '' }}
run: |
@@ -105,7 +105,7 @@ jobs:
name: style checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: ./.github/workflows/setup
- name: rustfmt
@@ -121,7 +121,7 @@ jobs:
name: bootstrap build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
# Deliberately skipping `./.github/workflows/setup` as we do our own setup
- name: Add cache for cargo
id: cache
@@ -156,7 +156,7 @@ jobs:
name: coverage report
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: ./.github/workflows/setup
- name: coverage
run: ./miri test --coverage
@@ -191,7 +191,7 @@ jobs:
pull-requests: write
if: ${{ github.event_name == 'schedule' }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 256 # get a bit more of the history
- name: install josh-sync
+29 -2
View File
@@ -171,8 +171,8 @@ MIRI_LOG=rustc_mir::interpret=info,miri::stacked_borrows ./miri run tests/pass/v
```
Note that you will only get `info`, `warn` or `error` messages if you use a prebuilt compiler.
In order to get `debug` and `trace` level messages, you need to build miri with a locally built
compiler that has `debug=true` set in `bootstrap.toml`.
In order to get `debug` and `trace` level messages, you need to build miri with a [locally built
compiler](#advanced-topic-building-miri-against-a-locally-compiled-rustc) that has `debug=true` set in `bootstrap.toml`.
#### Debugging error messages
@@ -320,6 +320,33 @@ You can also directly run Miri on a Rust source file:
./x.py run miri --stage 1 --args src/tools/miri/tests/pass/hello.rs
```
## Advanced topic: Building Miri against a locally compiled rustc
Very rarely, it can be necessary to work with an out-of-tree Miri but build it against a rustc that
was locally compiled. (Usually, you should instead work on the Miri that's in the Rust tree, as
described in the previous subsection.)
This requires a fully bootstrapped build:
```sh
# Build rustc, then build rustc with that rustc. This can take a while.
./x build library --stage 3
```
You also need to set up a linked toolchain with rustup:
```sh
rustup toolchain link stage2 build/host/stage2
```
Then in the Miri folder, you can set this as the current toolchain and build against it:
```sh
rustup override set stage2
# Prevent `./miri` from reseting the toolchain.
export MIRI_AUTO_OPS=no
```
## Advanced topic: Syncing with the rustc repo
We use the [`josh-sync`](https://github.com/rust-lang/josh-sync) tool to transmit changes between the
+33 -39
View File
@@ -230,9 +230,9 @@ dependencies = [
[[package]]
name = "rustc-build-sysroot"
version = "0.5.11"
version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b881c015c729b43105bbd3702a9bdecee28fafaa21126d1d62e454ec011a4b7"
checksum = "eec3905e8201688412f6f4b1f6c86d38b3ee6578f59ba85f41330a3af61e8365"
dependencies = [
"anyhow",
"rustc_version",
@@ -339,11 +339,11 @@ dependencies = [
[[package]]
name = "serde_spanned"
version = "0.6.9"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98"
dependencies = [
"serde",
"serde_core",
]
[[package]]
@@ -392,45 +392,42 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.23"
version = "1.1.0+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
checksum = "f8195ca05e4eb728f4ba94f3e3291661320af739c4e43779cbdfae82ab239fcc"
dependencies = [
"indexmap",
"serde",
"serde_core",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"toml_write",
"toml_parser",
"toml_writer",
"winnow",
]
[[package]]
name = "toml_write"
version = "0.1.2"
name = "toml_datetime"
version = "1.1.0+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_parser"
version = "1.1.0+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011"
dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
version = "1.1.0+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed"
[[package]]
name = "unicode-ident"
@@ -489,12 +486,9 @@ dependencies = [
[[package]]
name = "winnow"
version = "0.7.13"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
dependencies = [
"memchr",
]
checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8"
[[package]]
name = "wit-bindgen"
+1 -1
View File
@@ -18,7 +18,7 @@ directories = "6"
rustc_version = "0.4"
serde_json = "1.0.40"
cargo_metadata = "0.23"
rustc-build-sysroot = "0.5.10"
rustc-build-sysroot = "0.5.12"
# Enable some feature flags that dev-dependencies need but dependencies
# do not. This makes `./miri install` after `./miri build` faster.
+1
View File
@@ -28,6 +28,7 @@ begingroup "Building Miri"
export RUSTFLAGS="-D warnings"
export CARGO_INCREMENTAL=0
export CARGO_EXTRA_FLAGS="--locked"
export CARGO_UNSTABLE_BUILD_DIR_NEW_LAYOUT=true
# Determine configuration for installed build (used by test-cargo-miri and `./miri bench`).
# We use the default set of features for this.
+7 -4
View File
@@ -80,7 +80,7 @@ fn auto_actions() -> Result<()> {
// `toolchain` goes first as it could affect the others
if auto_toolchain {
Self::toolchain(vec![])?;
Self::toolchain(None, vec![])?;
}
if auto_fmt {
Self::fmt(vec![])?;
@@ -121,15 +121,18 @@ pub fn exec(self) -> Result<()> {
Command::Clippy { features, flags } => Self::clippy(features, flags),
Command::Bench { target, no_install, save_baseline, load_baseline, benches } =>
Self::bench(target, no_install, save_baseline, load_baseline, benches),
Command::Toolchain { flags } => Self::toolchain(flags),
Command::Toolchain { commit, flags } => Self::toolchain(commit, flags),
Command::Squash => Self::squash(),
}
}
fn toolchain(flags: Vec<String>) -> Result<()> {
fn toolchain(new_commit: Option<String>, flags: Vec<String>) -> Result<()> {
let sh = Shell::new()?;
sh.change_dir(miri_dir()?);
let new_commit = sh.read_file("rust-version")?.trim().to_owned();
let new_commit = match new_commit {
Some(c) => c,
None => sh.read_file("rust-version")?.trim().to_owned(),
};
let current_commit = {
let rustc_info = cmd!(sh, "rustc +miri --version -v").read();
if let Ok(rustc_info) = rustc_info {
+5 -2
View File
@@ -138,6 +138,9 @@ pub enum Command {
/// The `rust-version` file is used to determine the commit that will be intsalled.
/// `rustup-toolchain-install-master` must be installed for this to work.
Toolchain {
/// Overwrite the commit to install.
#[arg(long)]
commit: Option<String>,
/// Flags that are passed through to `rustup-toolchain-install-master`.
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
flags: Vec<String>,
@@ -157,8 +160,8 @@ fn add_remainder(&mut self, remainder: Vec<String>) -> Result<()> {
| Self::Build { flags, .. }
| Self::Check { flags, .. }
| Self::Doc { flags, .. }
| Self::Fmt { flags }
| Self::Toolchain { flags }
| Self::Fmt { flags, .. }
| Self::Toolchain { flags, .. }
| Self::Clippy { flags, .. }
| Self::Run { flags, .. }
| Self::Test { flags, .. } => {
+1 -1
View File
@@ -1 +1 @@
fd0c901b00ee1e08a250039cdb90258603497e20
116458d0a5ae01cd517cabd2d1aee7f5457018ab
+66 -79
View File
@@ -656,64 +656,10 @@ fn yield_active_thread(&mut self) {
// We should only switch stacks between steps.
self.yield_active_thread = true;
}
/// Get the wait time for the next timeout, or `None` if no timeout is pending.
fn next_callback_wait_time(&self, clock: &MonotonicClock) -> Option<Duration> {
self.threads
.iter()
.filter_map(|t| {
match &t.state {
ThreadState::Blocked { timeout: Some(timeout), .. } =>
Some(timeout.get_wait_time(clock)),
_ => None,
}
})
.min()
}
}
impl<'tcx> EvalContextPrivExt<'tcx> for MiriInterpCx<'tcx> {}
trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> {
/// Execute a timeout callback on the callback's thread.
#[inline]
fn run_timeout_callback(&mut self) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let mut found_callback = None;
// Find a blocked thread that has timed out.
for (id, thread) in this.machine.threads.threads.iter_enumerated_mut() {
match &thread.state {
ThreadState::Blocked { timeout: Some(timeout), .. }
if timeout.get_wait_time(&this.machine.monotonic_clock) == Duration::ZERO =>
{
let old_state = mem::replace(&mut thread.state, ThreadState::Enabled);
let ThreadState::Blocked { callback, .. } = old_state else { unreachable!() };
found_callback = Some((id, callback));
// Run the fallback (after the loop because borrow-checking).
break;
}
_ => {}
}
}
if let Some((thread, callback)) = found_callback {
// This back-and-forth with `set_active_thread` is here because of two
// design decisions:
// 1. Make the caller and not the callback responsible for changing
// thread.
// 2. Make the scheduler the only place that can change the active
// thread.
let old_thread = this.machine.threads.set_active_thread_id(thread);
callback.call(this, UnblockKind::TimedOut)?;
this.machine.threads.set_active_thread_id(old_thread);
}
// found_callback can remain None if the computer's clock
// was shifted after calling the scheduler and before the call
// to get_ready_callback (see issue
// https://github.com/rust-lang/miri/issues/1763). In this case,
// just do nothing, which effectively just returns to the
// scheduler.
interp_ok(())
}
#[inline]
fn run_on_stack_empty(&mut self) -> InterpResult<'tcx, Poll<()>> {
let this = self.eval_context_mut();
@@ -790,19 +736,12 @@ fn schedule(&mut self) -> InterpResult<'tcx, SchedulingAction> {
this.poll_and_unblock(Some(Duration::ZERO))?;
}
let thread_manager = &this.machine.threads;
let clock = &this.machine.monotonic_clock;
// We also check timeouts before running any other thread, to ensure that timeouts
// "in the past" fire before any other thread can take an action. This ensures that for
// `pthread_cond_timedwait`, "an error is returned if [...] the absolute time specified by
// abstime has already been passed at the time of the call".
// <https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_timedwait.html>
let potential_sleep_time = thread_manager.next_callback_wait_time(clock);
if potential_sleep_time == Some(Duration::ZERO) {
// The timeout exceeded for some thread so we unblock the thread and execute its timeout callback.
this.run_timeout_callback()?;
}
let potential_sleep_time = this.unblock_expired_timeouts()?;
let thread_manager = &mut this.machine.threads;
let rng = this.machine.rng.get_mut();
@@ -868,6 +807,71 @@ fn schedule(&mut self) -> InterpResult<'tcx, SchedulingAction> {
throw_machine_stop!(TerminationInfo::GlobalDeadlock);
}
}
/// Poll for I/O events until either an I/O event happened or the timeout expired.
/// The different timeout values are described in [`BlockingIoManager::poll`].
fn poll_and_unblock(&mut self, timeout: Option<Duration>) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let ready = match this.machine.blocking_io.poll(timeout) {
Ok(ready) => ready,
// We can ignore errors originating from interrupts; that's just a spurious wakeup.
Err(e) if e.kind() == io::ErrorKind::Interrupted => return interp_ok(()),
// For other errors we panic. On Linux and BSD hosts this should only be
// reachable when a system resource error (e.g. ENOMEM or ENOSPC) occurred.
Err(e) => panic!("unexpected error while polling: {e}"),
};
ready.into_iter().try_for_each(|thread_id| this.unblock_thread(thread_id, BlockReason::IO))
}
/// Find all threads with expired timeouts, unblock them and execute their timeout callbacks.
///
/// This method returns the minimum duration until the next thread timeout expires.
/// If all ready threads have no timeout set, [`None`] is returned.
fn unblock_expired_timeouts(&mut self) -> InterpResult<'tcx, Option<Duration>> {
let this = self.eval_context_mut();
let clock = &this.machine.monotonic_clock;
let mut min_wait_time = Option::<Duration>::None;
let mut callbacks = Vec::new();
for (id, thread) in this.machine.threads.threads.iter_enumerated_mut() {
match &thread.state {
ThreadState::Blocked { timeout: Some(timeout), .. } => {
let wait_time = timeout.get_wait_time(clock);
if wait_time.is_zero() {
// The timeout expired for this thread.
let old_state = mem::replace(&mut thread.state, ThreadState::Enabled);
let ThreadState::Blocked { callback, .. } = old_state else {
unreachable!()
};
// Add callback to list to be run after this loop because of borrow-checking.
callbacks.push((id, callback));
} else {
// Update `min_wait_time` to contain the smallest duration until
// the next timeout expires.
min_wait_time = Some(wait_time.min(min_wait_time.unwrap_or(Duration::MAX)));
}
}
_ => {}
}
}
for (thread, callback) in callbacks {
// This back-and-forth with `set_active_thread` is here because of two
// design decisions:
// 1. Make the caller and not the callback responsible for changing
// thread.
// 2. Make the scheduler the only place that can change the active
// thread.
let old_thread = this.machine.threads.set_active_thread_id(thread);
callback.call(this, UnblockKind::TimedOut)?;
this.machine.threads.set_active_thread_id(old_thread);
}
interp_ok(min_wait_time)
}
}
// Public interface to thread management.
@@ -1348,21 +1352,4 @@ fn run_threads(&mut self) -> InterpResult<'tcx, !> {
}
}
}
/// Poll for I/O events until either an I/O event happened or the timeout expired.
/// The different timeout values are described in [`BlockingIoManager::poll`].
fn poll_and_unblock(&mut self, timeout: Option<Duration>) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let ready = match this.machine.blocking_io.poll(timeout) {
Ok(ready) => ready,
// We can ignore errors originating from interrupts; that's just a spurious wakeup.
Err(e) if e.kind() == io::ErrorKind::Interrupted => return interp_ok(()),
// For other errors we panic. On Linux and BSD hosts this should only be
// reachable when a system resource error (e.g. ENOMEM or ENOSPC) occurred.
Err(e) => panic!("{e}"),
};
ready.into_iter().try_for_each(|thread_id| this.unblock_thread(thread_id, BlockReason::IO))
}
}
+1 -1
View File
@@ -569,7 +569,7 @@ pub struct MiriMachine<'tcx> {
pub(crate) user_relevant_crates: Vec<CrateNum>,
/// Mapping extern static names to their pointer.
extern_statics: FxHashMap<Symbol, StrictPointer>,
pub(crate) extern_statics: FxHashMap<Symbol, StrictPointer>,
/// The random number generator used for resolving non-determinism.
/// Needs to be queried by ptr_to_int, hence needs interior mutability.
+129
View File
@@ -4,6 +4,7 @@
use rustc_span::Symbol;
use rustc_target::callconv::FnAbi;
use crate::shims::math::compute_crc32;
use crate::*;
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
@@ -58,6 +59,93 @@ fn emulate_aarch64_intrinsic(
this.write_immediate(*res_lane, &dest)?;
}
}
// Wrapping pairwise addition.
//
// Concatenates the two input vectors and adds adjacent elements. For input vectors `v`
// and `w` this computes `[v0 + v1, v2 + v3, ..., w0 + w1, w2 + w3, ...]`, using
// wrapping addition for `+`.
//
// Used by `vpadd_{s8, u8, s16, u16, s32, u32}`.
name if name.starts_with("neon.addp.") => {
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);
assert_eq!(left.layout, right.layout);
assert_eq!(left.layout, dest.layout);
assert!(dest_len.is_multiple_of(2));
let half_len = dest_len.strict_div(2);
for lane_idx in 0..dest_len {
// The left and right vectors are concatenated.
let (src, src_pair_idx) = if lane_idx < half_len {
(&left, lane_idx)
} else {
(&right, lane_idx.strict_sub(half_len))
};
// Convert "pair index" into "index of first element of the pair".
let i = src_pair_idx.strict_mul(2);
let lhs = this.read_immediate(&this.project_index(src, i)?)?;
let rhs = this.read_immediate(&this.project_index(src, i.strict_add(1))?)?;
// Wrapping addition on the element type.
let sum = this.binary_op(BinOp::Add, &lhs, &rhs)?;
let dst_lane = this.project_index(&dest, lane_idx)?;
this.write_immediate(*sum, &dst_lane)?;
}
}
// Widening pairwise addition.
//
// Takes a single input vector, and an output vector with half as many lanes and double
// the element width. Takes adjacent pairs of elements, widens both, and then adds them
// together.
//
// Used by `vpaddl_{u8, u16, u32}` and `vpaddlq_{u8, u16, u32}`.
name if name.starts_with("neon.uaddlp.") => {
let [src] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let (src, src_len) = this.project_to_simd(src)?;
let (dest, dest_len) = this.project_to_simd(dest)?;
// Operates pairwise, so src has twice as many lanes.
assert_eq!(src_len, dest_len.strict_mul(2));
let src_elem_size = src.layout.field(this, 0).size;
let dest_elem_size = dest.layout.field(this, 0).size;
// Widens, so dest elements must be exactly twice as wide.
assert_eq!(dest_elem_size.bytes(), src_elem_size.bytes().strict_mul(2));
for dest_idx in 0..dest_len {
let src_idx = dest_idx.strict_mul(2);
let a_scalar = this.read_scalar(&this.project_index(&src, src_idx)?)?;
let b_scalar =
this.read_scalar(&this.project_index(&src, src_idx.strict_add(1))?)?;
let a_val = a_scalar.to_uint(src_elem_size)?;
let b_val = b_scalar.to_uint(src_elem_size)?;
// Use addition on u128 to simulate widening addition for the destination type.
// This cannot wrap since the element type is at most u64.
let sum = a_val.strict_add(b_val);
let dst_lane = this.project_index(&dest, dest_idx)?;
this.write_scalar(Scalar::from_uint(sum, dest_elem_size), &dst_lane)?;
}
}
// 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
@@ -85,6 +173,47 @@ fn emulate_aarch64_intrinsic(
this.write_scalar(val, &this.project_index(&dest, i)?)?;
}
}
// Used to implement the __crc32{b,h,w,x} and __crc32c{b,h,w,x} functions.
// Polynomial 0x04C11DB7 (standard CRC-32):
// https://developer.arm.com/documentation/ddi0602/latest/Base-Instructions/CRC32B--CRC32H--CRC32W--CRC32X--CRC32-checksum-
// Polynomial 0x1EDC6F41 (CRC-32C / Castagnoli):
// https://developer.arm.com/documentation/ddi0602/latest/Base-Instructions/CRC32CB--CRC32CH--CRC32CW--CRC32CX--CRC32C-checksum-
"crc32b" | "crc32h" | "crc32w" | "crc32x" | "crc32cb" | "crc32ch" | "crc32cw"
| "crc32cx" => {
this.expect_target_feature_for_intrinsic(link_name, "crc")?;
// The polynomial constants below include the leading 1 bit
// (e.g. 0x104C11DB7 instead of 0x04C11DB7) which the ARM docs
// omit but the polynomial division algorithm requires.
let (bit_size, polynomial): (u32, u128) = match unprefixed_name {
"crc32b" => (8, 0x104C11DB7),
"crc32h" => (16, 0x104C11DB7),
"crc32w" => (32, 0x104C11DB7),
"crc32x" => (64, 0x104C11DB7),
"crc32cb" => (8, 0x11EDC6F41),
"crc32ch" => (16, 0x11EDC6F41),
"crc32cw" => (32, 0x11EDC6F41),
"crc32cx" => (64, 0x11EDC6F41),
_ => unreachable!(),
};
let [left, right] =
this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
let left = this.read_scalar(left)?;
let right = this.read_scalar(right)?;
// The CRC accumulator is always u32. The data argument is u32 for
// b/h/w variants and u64 for the x variant, per the LLVM intrinsic
// definitions (all b/h/w take i32, only x takes i64).
// https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/IR/IntrinsicsAArch64.td
// If the higher bits are non-zero, `compute_crc32` will panic. We should probably
// raise a proper error instead, but outside stdarch nobody can trigger this anyway.
let crc = left.to_u32()?;
let data =
if bit_size == 64 { right.to_u64()? } else { u64::from(right.to_u32()?) };
let result = compute_crc32(crc, data, bit_size, polynomial);
this.write_scalar(Scalar::from_u32(result), dest)?;
}
_ => return interp_ok(EmulateItemResult::NotSupported),
}
interp_ok(EmulateItemResult::NeedsReturn)
+48
View File
@@ -245,3 +245,51 @@ fn emulate_foreign_item_inner(
interp_ok(EmulateItemResult::NeedsReturn)
}
}
/// Compute a CRC32 checksum using the given polynomial.
///
/// `bit_size` is the number of relevant data bits (8, 16, 32, or 64).
/// Only the low `bit_size` bits of `data` are used; higher bits must be zero.
/// `polynomial` includes the leading 1 bit (e.g. `0x11EDC6F41` for CRC32C).
///
/// Following hardware CRC conventions, `crc` and `data` bits are assumed to be reversed,
/// and output bits will be equally reversed.
pub(crate) fn compute_crc32(crc: u32, data: u64, bit_size: u32, polynomial: u128) -> u32 {
assert!(
bit_size == 64 || data < 1u64.strict_shl(bit_size),
"crc32: `data` is larger than {bit_size} bits"
);
// Bit-reverse inputs to match hardware CRC conventions.
let crc = u128::from(crc.reverse_bits());
// Reverse all 64 bits of `data`, then shift right by `64 - bit_size`. This
// discards the (now-reversed) higher bits, leaving only the reversed low
// `bit_size` bits in the lowest positions (with zeros above).
let v = u128::from(data.reverse_bits() >> (64u32.strict_sub(bit_size)));
// Perform polynomial division modulo 2.
// The algorithm for the division is an adapted version of the
// schoolbook division algorithm used for normal integer or polynomial
// division. In this context, the quotient is not calculated, since
// only the remainder is needed.
//
// The algorithm works as follows:
// 1. Pull down digits until division can be performed. In the context of division
// modulo 2 it means locating the most significant digit of the dividend and shifting
// the divisor such that the position of the divisors most significand digit and the
// dividends most significand digit match.
// 2. Perform a division and determine the remainder. Since it is arithmetic modulo 2,
// this operation is a simple bitwise exclusive or.
// 3. Repeat steps 1. and 2. until the full remainder is calculated. This is the case
// once the degree of the remainder polynomial is smaller than the degree of the
// divisor polynomial. In other words, the number of leading zeros of the remainder
// is larger than the number of leading zeros of the divisor. It is important to
// note that standard arithmetic comparison is not applicable here:
// 0b10011 / 0b11111 = 0b01100 is a valid division, even though the dividend is
// smaller than the divisor.
let mut dividend = (crc << bit_size) ^ (v << 32);
while dividend.leading_zeros() <= polynomial.leading_zeros() {
dividend ^= (polynomial << polynomial.leading_zeros()) >> dividend.leading_zeros();
}
u32::try_from(dividend).unwrap().reverse_bits()
}
+3 -2
View File
@@ -396,8 +396,9 @@ fn ty_to_ffitype(&self, layout: TyAndLayout<'tcx>) -> Result<FfiType, Ty<'tcx>>
// matches what codegen does. This does mean that we support some types whose ABI is not
// stable, but that's fine -- we are anyway quite conservative in native-lib mode.
if let BackendRepr::Scalar(s) = layout.backend_repr {
// Simple sanity-check: this cannot be `repr(C)`.
assert!(!layout.ty.ty_adt_def().is_some_and(|adt| adt.repr().c()));
// Simple sanity-check: this cannot be a `repr(C)` struct or union. (It could be a
// repr(C) enum. Those indeed behave like integers in the ABI.)
assert!(!layout.ty.ty_adt_def().is_some_and(|adt| !adt.is_enum() && adt.repr().c()));
return Ok(match s.primitive() {
Primitive::Int(Integer::I8, /* signed */ true) => FfiType::i8(),
Primitive::Int(Integer::I16, /* signed */ true) => FfiType::i16(),
+16 -3
View File
@@ -633,6 +633,16 @@ fn emulate_foreign_item_inner(
let result = this.getsockname(socket, address, address_len)?;
this.write_scalar(result, dest)?;
}
"getpeername" => {
let [socket, address, address_len] = this.check_shim_sig(
shim_sig!(extern "C" fn(i32, *mut _, *mut _) -> i32),
link_name,
abi,
args,
)?;
let result = this.getpeername(socket, address, address_len)?;
this.write_scalar(result, dest)?;
}
// Time
"gettimeofday" => {
@@ -727,11 +737,14 @@ fn emulate_foreign_item_inner(
this.read_target_usize(handle)?;
let symbol = this.read_pointer(symbol)?;
let name = this.read_c_str(symbol)?;
if let Ok(name) = str::from_utf8(name)
&& is_dyn_sym(name, &this.tcx.sess.target.os)
{
let Ok(name) = str::from_utf8(name) else {
throw_unsup_format!("dlsym: non UTF-8 symbol name not supported")
};
if is_dyn_sym(name, &this.tcx.sess.target.os) {
let ptr = this.fn_ptr(FnVal::Other(DynSym::from_str(name)));
this.write_pointer(ptr, dest)?;
} else if let Some(&ptr) = this.machine.extern_statics.get(&Symbol::intern(name)) {
this.write_pointer(ptr, dest)?;
} else {
this.write_null(dest)?;
}
+11 -2
View File
@@ -215,8 +215,17 @@ fn read_umtx_time(&mut self, ut: &MPlaceTy<'tcx>) -> InterpResult<'tcx, Option<U
let Some(duration) = this.read_timespec(&timespec_place)? else { return interp_ok(None) };
let flags_place = this.project_field(ut, FieldIdx::from_u32(1))?;
let flags = this.read_scalar(&flags_place)?.to_u32()?;
let abs_time_flag = flags == abs_time;
let mut flags = this.read_scalar(&flags_place)?.to_u32()?;
let abs_time_flag = if flags & abs_time != 0 {
flags &= !abs_time;
true
} else {
false
};
if flags != 0 {
throw_unsup_format!("unsupported `_umtx_time` flags: {:#x}", flags);
}
let clock_id_place = this.project_field(ut, FieldIdx::from_u32(2))?;
let clock_id = this.read_scalar(&clock_id_place)?;
+13 -15
View File
@@ -364,6 +364,9 @@ fn open(
this.machine.emit_diagnostic(NonHaltingDiagnostic::FileInProcOpened);
}
// We will "subtract" supported flags from this and at the end check that no bits are left.
let mut flag = flag;
let mut options = OpenOptions::new();
let o_rdonly = this.eval_libc_i32("O_RDONLY");
@@ -379,6 +382,7 @@ fn open(
// Now we check the access mode
let access_mode = flag & 0b11;
flag &= !access_mode;
if access_mode == o_rdonly {
writable = false;
@@ -390,23 +394,20 @@ fn open(
} else {
throw_unsup_format!("unsupported access mode {:#x}", access_mode);
}
// We need to check that there aren't unsupported options in `flag`. For this we try to
// reproduce the content of `flag` in the `mirror` variable using only the supported
// options.
let mut mirror = access_mode;
let o_append = this.eval_libc_i32("O_APPEND");
if flag & o_append == o_append {
flag &= !o_append;
options.append(true);
mirror |= o_append;
}
let o_trunc = this.eval_libc_i32("O_TRUNC");
if flag & o_trunc == o_trunc {
flag &= !o_trunc;
options.truncate(true);
mirror |= o_trunc;
}
let o_creat = this.eval_libc_i32("O_CREAT");
if flag & o_creat == o_creat {
flag &= !o_creat;
// Get the mode. On macOS, the argument type `mode_t` is actually `u16`, but
// C integer promotion rules mean that on the ABI level, it gets passed as `u32`
// (see https://github.com/rust-lang/rust/issues/71915).
@@ -430,11 +431,9 @@ fn open(
}
}
mirror |= o_creat;
let o_excl = this.eval_libc_i32("O_EXCL");
if flag & o_excl == o_excl {
mirror |= o_excl;
flag &= !o_excl;
options.create_new(true);
} else {
options.create(true);
@@ -442,9 +441,9 @@ fn open(
}
let o_cloexec = this.eval_libc_i32("O_CLOEXEC");
if flag & o_cloexec == o_cloexec {
flag &= !o_cloexec;
// We do not need to do anything for this flag because `std` already sets it.
// (Technically we do not support *not* setting this flag, but we ignore that.)
mirror |= o_cloexec;
}
if this.tcx.sess.target.os == Os::Linux {
let o_tmpfile = this.eval_libc_i32("O_TMPFILE");
@@ -456,6 +455,7 @@ fn open(
let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
if flag & o_nofollow == o_nofollow {
flag &= !o_nofollow;
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
@@ -472,13 +472,11 @@ fn open(
return this.set_last_error_and_return_i32(LibcError("ELOOP"));
}
}
mirror |= o_nofollow;
}
// If `flag` is not equal to `mirror`, there is an unsupported option enabled in `flag`,
// then we throw an error.
if flag != mirror {
throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
// If `flag` has any bits left set, those are not supported.
if flag != 0 {
throw_unsup_format!("unsupported flags {:#x}", flag);
}
// Reject if isolation is enabled.
+42
View File
@@ -620,6 +620,48 @@ fn getsockname(
Err(e) => this.set_last_error_and_return_i32(e),
}
}
fn getpeername(
&mut self,
socket: &OpTy<'tcx>,
address: &OpTy<'tcx>,
address_len: &OpTy<'tcx>,
) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let socket = this.read_scalar(socket)?.to_i32()?;
let address_ptr = this.read_pointer(address)?;
let address_len_ptr = this.read_pointer(address_len)?;
// Get the file handle
let Some(fd) = this.machine.fds.get(socket) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
};
let Some(socket) = fd.downcast::<Socket>() else {
// Man page specifies to return ENOTSOCK if `fd` is not a socket.
return this.set_last_error_and_return_i32(LibcError("ENOTSOCK"));
};
assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
let state = socket.state.borrow();
let SocketState::Connected(stream) = &*state else {
// We can only read the peer address of connected sockets.
return this.set_last_error_and_return_i32(LibcError("ENOTCONN"));
};
let address = match stream.peer_addr() {
Ok(address) => address,
Err(e) => return this.set_last_error_and_return_i32(e),
};
match this.write_socket_address(&address, address_ptr, address_len_ptr, "getpeername")? {
Ok(_) => interp_ok(Scalar::from_i32(0)),
Err(e) => this.set_last_error_and_return_i32(e),
}
}
}
impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
@@ -451,6 +451,16 @@ fn emulate_foreign_item_inner(
this.SetFilePointerEx(file, distance_to_move, new_file_pointer, move_method)?;
this.write_scalar(res, dest)?;
}
"MoveFileExW" => {
let [existing_name, new_name, flags] = this.check_shim_sig(
shim_sig!(extern "system" fn(*const _, *const _, u32) -> winapi::BOOL),
link_name,
abi,
args,
)?;
let res = this.MoveFileExW(existing_name, new_name, flags)?;
this.write_scalar(res, dest)?;
}
// Allocation
"HeapAlloc" => {
+30
View File
@@ -490,6 +490,36 @@ fn FlushFileBuffers(
}
}
fn MoveFileExW(
&mut self,
existing_name: &OpTy<'tcx>,
new_name: &OpTy<'tcx>,
flags: &OpTy<'tcx>,
) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let existing_name = this.read_path_from_wide_str(this.read_pointer(existing_name)?)?;
let new_name = this.read_path_from_wide_str(this.read_pointer(new_name)?)?;
let flags = this.read_scalar(flags)?.to_u32()?;
// Flag to indicate whether we should replace an existing file.
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefileexw
let movefile_replace_existing = this.eval_windows_u32("c", "MOVEFILE_REPLACE_EXISTING");
if flags != movefile_replace_existing {
throw_unsup_format!("MoveFileExW: Unsupported `dwFlags` value {}", flags);
}
match std::fs::rename(existing_name, new_name) {
Ok(_) => interp_ok(this.eval_windows("c", "TRUE")),
Err(e) => {
this.set_last_error(e)?;
interp_ok(this.eval_windows("c", "FALSE"))
}
}
}
fn DeleteFileW(
&mut self,
file_name: &OpTy<'tcx>, // LPCWSTR
+9 -35
View File
@@ -5,6 +5,7 @@
use rustc_target::callconv::FnAbi;
use rustc_target::spec::Arch;
use crate::shims::math::compute_crc32;
use crate::*;
/// A bitmask constant for scrutinizing the immediate byte provided
@@ -445,46 +446,19 @@ fn emulate_x86_sse42_intrinsic(
// The 64-bit version will only consider the lower 32 bits,
// while the upper 32 bits get discarded.
#[expect(clippy::as_conversions)]
u128::from((left.to_u64()? as u32).reverse_bits())
(left.to_u64()? as u32)
} else {
u128::from(left.to_u32()?.reverse_bits())
left.to_u32()?
};
let v = match bit_size {
8 => u128::from(right.to_u8()?.reverse_bits()),
16 => u128::from(right.to_u16()?.reverse_bits()),
32 => u128::from(right.to_u32()?.reverse_bits()),
64 => u128::from(right.to_u64()?.reverse_bits()),
let data = match bit_size {
8 => u64::from(right.to_u8()?),
16 => u64::from(right.to_u16()?),
32 => u64::from(right.to_u32()?),
64 => right.to_u64()?,
_ => unreachable!(),
};
// Perform polynomial division modulo 2.
// The algorithm for the division is an adapted version of the
// schoolbook division algorithm used for normal integer or polynomial
// division. In this context, the quotient is not calculated, since
// only the remainder is needed.
//
// The algorithm works as follows:
// 1. Pull down digits until division can be performed. In the context of division
// modulo 2 it means locating the most significant digit of the dividend and shifting
// the divisor such that the position of the divisors most significand digit and the
// dividends most significand digit match.
// 2. Perform a division and determine the remainder. Since it is arithmetic modulo 2,
// this operation is a simple bitwise exclusive or.
// 3. Repeat steps 1. and 2. until the full remainder is calculated. This is the case
// once the degree of the remainder polynomial is smaller than the degree of the
// divisor polynomial. In other words, the number of leading zeros of the remainder
// is larger than the number of leading zeros of the divisor. It is important to
// note that standard arithmetic comparison is not applicable here:
// 0b10011 / 0b11111 = 0b01100 is a valid division, even though the dividend is
// smaller than the divisor.
let mut dividend = (crc << bit_size) ^ (v << 32);
const POLYNOMIAL: u128 = 0x11EDC6F41;
while dividend.leading_zeros() <= POLYNOMIAL.leading_zeros() {
dividend ^=
(POLYNOMIAL << POLYNOMIAL.leading_zeros()) >> dividend.leading_zeros();
}
let result = u32::try_from(dividend).unwrap().reverse_bits();
let result = compute_crc32(crc, data, bit_size, 0x11EDC6F41);
let result = if bit_size == 64 {
Scalar::from_u64(u64::from(result))
} else {
@@ -7,7 +7,7 @@ fn main() {
let t = thread::spawn(|| unsafe {
// Access the environment in another thread without taking the env lock.
// This represents some C code that queries the environment.
libc::getenv(b"TZ\0".as_ptr().cast()); //~ERROR: Data race detected
libc::getenv(c"TZ".as_ptr()); //~ERROR: Data race detected
});
// Meanwhile, the main thread uses the "safe" Rust env accessor.
env::set_var("MY_RUST_VAR", "Ferris");
@@ -1,8 +1,8 @@
error: Undefined Behavior: Data race detected between (1) non-atomic write on thread `main` and (2) non-atomic read on thread `unnamed-ID` at ALLOC
--> tests/fail-dep/libc/env-set_var-data-race.rs:LL:CC
|
LL | libc::getenv(b"TZ/0".as_ptr().cast());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (2) just happened here
LL | libc::getenv(c"TZ".as_ptr());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (2) just happened here
|
help: and (1) occurred earlier here
--> tests/fail-dep/libc/env-set_var-data-race.rs:LL:CC
@@ -19,7 +19,7 @@ LL | let t = thread::spawn(|| unsafe {
| _____________^
LL | | // Access the environment in another thread without taking the env lock.
LL | | // This represents some C code that queries the environment.
LL | | libc::getenv(b"TZ/0".as_ptr().cast());
LL | | libc::getenv(c"TZ".as_ptr());
LL | | });
| |______^
@@ -6,6 +6,6 @@ fn main() {
}
fn test_mkstemp_immutable_arg() {
let s: *mut libc::c_char = b"fooXXXXXX\0" as *const _ as *mut _;
let s: *mut libc::c_char = c"fooXXXXXX".as_ptr().cast_mut();
let _fd = unsafe { libc::mkstemp(s) }; //~ ERROR: Undefined Behavior: writing to alloc1 which is read-only
}
@@ -6,7 +6,6 @@ fn main() {
}
fn test_file_open_missing_needed_mode() {
let name = b"missing_arg.txt\0";
let name_ptr = name.as_ptr().cast::<libc::c_char>();
let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT) }; //~ ERROR: Undefined Behavior: not enough variadic arguments
let name = c"missing_arg.txt".as_ptr();
let _fd = unsafe { libc::open(name, libc::O_CREAT) }; //~ ERROR: Undefined Behavior: not enough variadic arguments
}
@@ -1,8 +1,8 @@
error: Undefined Behavior: not enough variadic arguments for `open(pathname, O_CREAT, ...)`: got 0, expected at least 1
--> tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs:LL:CC
|
LL | let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
LL | let _fd = unsafe { libc::open(name, libc::O_CREAT) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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
@@ -1,5 +1,6 @@
fn main() {
fn f() -> u32 { //~ ERROR: type u32 passing return place of type ()
fn f() -> u32 {
//~^ERROR: type u32 passing return place of type ()
42
}
@@ -5,7 +5,8 @@
use std::num::NonZero;
use std::ptr;
fn f(c: u32) { //~ERROR: expected something greater or equal to 1
fn f(c: u32) {
//~^ERROR: expected something greater or equal to 1
println!("{c}");
}
@@ -176,7 +176,7 @@ struct Triple {
}
extern "C" {
fn swap_ptr_triple_dangling(t_ptr: *const Triple);
fn swap_ptr_triple_dangling(t_ptr: *mut Triple);
}
let x = 101;
@@ -184,9 +184,9 @@ struct Triple {
let ptr = Box::as_ptr(&b);
drop(b);
let z = 121;
let triple = Triple { ptr0: &raw const x, ptr1: ptr, ptr2: &raw const z };
let mut triple = Triple { ptr0: &raw const x, ptr1: ptr, ptr2: &raw const z };
unsafe { swap_ptr_triple_dangling(&triple) }
unsafe { swap_ptr_triple_dangling(&mut triple) }
assert_eq!(unsafe { *triple.ptr2 }, x);
}
@@ -1,3 +1,10 @@
#[allow(unused)]
#[repr(C)]
enum CEnum {
A,
B,
}
extern "C" {
fn add_one_int(x: i32) -> i32;
fn add_int16(x: i16) -> i16;
@@ -19,6 +26,7 @@ fn test_stack_spill(
fn get_unsigned_int() -> u32;
fn add_float(x: f32) -> f32;
fn printer();
fn scalar_enum(e: CEnum) -> u8;
}
fn main() {
@@ -43,5 +51,8 @@ fn main() {
// test void function that prints from C
printer();
// test passing enums with scalar layout
assert_eq!(scalar_enum(CEnum::B), 1);
}
}
@@ -4,6 +4,11 @@
// See comments in build_native_lib()
#define EXPORT __attribute__((visibility("default")))
enum cenum {
cenum_a,
cenum_b,
};
EXPORT int32_t add_one_int(int32_t x) {
return 2 + x;
}
@@ -38,6 +43,10 @@ EXPORT uint8_t u8_id(uint8_t x) {
return x;
}
EXPORT uint8_t scalar_enum(enum cenum e) {
return (uint8_t)e;
}
// To test that functions not marked with EXPORT cannot be called by Miri.
int32_t not_exported(void) {
return 0;
@@ -8,7 +8,7 @@ fn main() {
unsafe {
thread::spawn(|| {
// Access the environment in another thread without taking the env lock
let s = libc::getenv("MIRI_ENV_VAR_TEST\0".as_ptr().cast());
let s = libc::getenv(c"MIRI_ENV_VAR_TEST".as_ptr());
if s.is_null() {
panic!("null");
}
@@ -7,8 +7,8 @@
type GetEntropyFn = unsafe extern "C" fn(*mut u8, libc::size_t) -> libc::c_int;
fn main() {
let name = "getentropy\0";
let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr() as *const _) as usize };
let name = c"getentropy";
let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr()) as usize };
// If the GC does not account for the extra_fn_ptr entry that this dlsym just added, this GC
// run will delete our entry for the base addr of the function pointer we will transmute to,
// and the call through the function pointer will report UB.
+12 -22
View File
@@ -54,29 +54,23 @@ fn main() {
fn test_file_open_unix_allow_two_args() {
let path = utils::prepare_with_content("test_file_open_unix_allow_two_args.txt", &[]);
let name = CString::new(path.into_os_string().into_encoded_bytes()).unwrap();
let mut name = path.into_os_string();
name.push("\0");
let name_ptr = name.as_bytes().as_ptr().cast::<libc::c_char>();
let _fd = unsafe { libc::open(name_ptr, libc::O_RDONLY) };
let _fd = unsafe { libc::open(name.as_ptr(), libc::O_RDONLY) };
}
fn test_file_open_unix_needs_three_args() {
let path = utils::prepare_with_content("test_file_open_unix_needs_three_args.txt", &[]);
let name = CString::new(path.into_os_string().into_encoded_bytes()).unwrap();
let mut name = path.into_os_string();
name.push("\0");
let name_ptr = name.as_bytes().as_ptr().cast::<libc::c_char>();
let _fd = unsafe { libc::open(name_ptr, libc::O_CREAT, 0o666) };
let _fd = unsafe { libc::open(name.as_ptr(), libc::O_CREAT, 0o666) };
}
fn test_file_open_unix_extra_third_arg() {
let path = utils::prepare_with_content("test_file_open_unix_extra_third_arg.txt", &[]);
let name = CString::new(path.into_os_string().into_encoded_bytes()).unwrap();
let mut name = path.into_os_string();
name.push("\0");
let name_ptr = name.as_bytes().as_ptr().cast::<libc::c_char>();
let _fd = unsafe { libc::open(name_ptr, libc::O_RDONLY, 42) };
let _fd = unsafe { libc::open(name.as_ptr(), libc::O_RDONLY, 42) };
}
fn test_dup_stdout_stderr() {
@@ -92,12 +86,10 @@ fn test_dup_stdout_stderr() {
fn test_dup() {
let bytes = b"dup and dup2";
let path = utils::prepare_with_content("miri_test_libc_dup.txt", bytes);
let name = CString::new(path.into_os_string().into_encoded_bytes()).unwrap();
let mut name = path.into_os_string();
name.push("\0");
let name_ptr = name.as_bytes().as_ptr().cast::<libc::c_char>();
unsafe {
let fd = libc::open(name_ptr, libc::O_RDONLY);
let fd = libc::open(name.as_ptr(), libc::O_RDONLY);
let new_fd = libc::dup(fd);
let new_fd2 = libc::dup2(fd, 8);
@@ -519,7 +511,7 @@ fn test_read_and_uninit() {
{
// We test that libc::read initializes its buffer.
let path = utils::prepare_with_content("pass-libc-read-and-uninit.txt", &[1u8, 2, 3]);
let cpath = CString::new(path.clone().into_os_string().into_encoded_bytes()).unwrap();
let cpath = CString::new(path.into_os_string().into_encoded_bytes()).unwrap();
unsafe {
let fd = libc::open(cpath.as_ptr(), libc::O_RDONLY);
assert_ne!(fd, -1);
@@ -528,8 +520,8 @@ fn test_read_and_uninit() {
let buf = buf.assume_init();
assert_eq!(buf, 1);
assert_eq!(libc::close(fd), 0);
assert_eq!(libc::unlink(cpath.as_ptr()), 0);
}
remove_file(&path).unwrap();
}
{
// We test that if we requested to read 4 bytes, but actually read 3 bytes, then
@@ -567,17 +559,15 @@ fn test_nofollow_not_symlink() {
#[cfg(target_os = "macos")]
fn test_ioctl() {
let path = utils::prepare_with_content("miri_test_libc_ioctl.txt", &[]);
let name = CString::new(path.into_os_string().into_encoded_bytes()).unwrap();
let mut name = path.into_os_string();
name.push("\0");
let name_ptr = name.as_bytes().as_ptr().cast::<libc::c_char>();
unsafe {
// 100 surely is an invalid FD.
assert_eq!(libc::ioctl(100, libc::FIOCLEX), -1);
let errno = std::io::Error::last_os_error().raw_os_error().unwrap();
assert_eq!(errno, libc::EBADF);
let fd = libc::open(name_ptr, libc::O_RDONLY);
let fd = libc::open(name.as_ptr(), libc::O_RDONLY);
assert_eq!(libc::ioctl(fd, libc::FIOCLEX), 0);
}
}
@@ -63,15 +63,22 @@ fn test_sigrt() {
}
fn test_dlsym() {
let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, b"notasymbol\0".as_ptr().cast()) };
let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, c"notasymbol".as_ptr()) };
assert!(addr as usize == 0);
let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, b"isatty\0".as_ptr().cast()) };
let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, c"isatty".as_ptr()) };
assert!(addr as usize != 0);
let isatty: extern "C" fn(i32) -> i32 = unsafe { transmute(addr) };
assert_eq!(isatty(999), 0);
let errno = std::io::Error::last_os_error().raw_os_error().unwrap();
assert_eq!(errno, libc::EBADF);
let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, c"environ".as_ptr()) };
assert!(addr as usize != 0);
extern "C" {
static mut environ: *const *const u8;
}
assert!(addr as usize == &raw const environ as usize);
}
fn test_getuid() {
@@ -38,6 +38,9 @@ fn main() {
test_getsockname_ipv4_random_port();
test_getsockname_ipv4_unbound();
test_getsockname_ipv6();
test_getpeername_ipv4();
test_getpeername_ipv6();
}
fn test_socket_close() {
@@ -183,7 +186,6 @@ fn test_listen() {
/// - Connecting when the server is already accepting
/// - Accepting when there is already an incoming connection
fn test_accept_connect() {
// Create a new non-blocking server socket.
let server_sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
let client_sockfd =
@@ -379,6 +381,132 @@ fn test_getsockname_ipv6() {
assert_eq!(addr.sin6_addr.s6_addr, sock_addr.sin6_addr.s6_addr);
}
/// Test the `getpeername` syscall on an IPv4 socket.
/// For a connected socket, the `getpeername` syscall should
/// return the same address as the socket was connected to.
fn test_getpeername_ipv4() {
let server_sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
let client_sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() };
let addr = net::ipv4_sock_addr(net::IPV4_LOCALHOST, 0);
unsafe {
errno_check(libc::bind(
server_sockfd,
(&addr as *const libc::sockaddr_in).cast::<libc::sockaddr>(),
size_of::<libc::sockaddr_in>() as libc::socklen_t,
));
}
unsafe {
errno_check(libc::listen(server_sockfd, 16));
}
// Retrieve actual listener address because we used a randomized port.
let (_, server_addr) =
sockname(|storage, len| unsafe { libc::getsockname(server_sockfd, storage, len) }).unwrap();
let LibcSocketAddr::V4(addr) = server_addr else {
// We bound an IPv4 address so we also expect
// an IPv4 address to be returned.
panic!()
};
// Spawn the server thread.
let server_thread = thread::spawn(move || {
let (_peerfd, _peer_addr) =
sockname(|storage, len| unsafe { libc::accept(server_sockfd, storage, len) }).unwrap();
});
// Test connecting to an already accepting server.
unsafe {
errno_check(libc::connect(
client_sockfd,
(&addr as *const libc::sockaddr_in).cast::<libc::sockaddr>(),
size_of::<libc::sockaddr_in>() as libc::socklen_t,
));
}
let (_, peer_addr) =
sockname(|storage, len| unsafe { libc::getpeername(client_sockfd, storage, len) }).unwrap();
let LibcSocketAddr::V4(peer_addr) = peer_addr else {
// We connected to an IPv4 address so we also expect
// an IPv4 address to be returned.
panic!()
};
assert_eq!(addr.sin_family, peer_addr.sin_family);
assert_eq!(addr.sin_port, peer_addr.sin_port);
assert_eq!(addr.sin_addr.s_addr, peer_addr.sin_addr.s_addr);
server_thread.join().unwrap();
}
/// Test the `getpeername` syscall on an IPv6 socket.
/// For a connected socket, the `getpeername` syscall should
/// return the same address as the socket was connected to.
fn test_getpeername_ipv6() {
let server_sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET6, libc::SOCK_STREAM, 0)).unwrap() };
let client_sockfd =
unsafe { errno_result(libc::socket(libc::AF_INET6, libc::SOCK_STREAM, 0)).unwrap() };
let addr = net::ipv6_sock_addr(net::IPV6_LOCALHOST, 0);
unsafe {
errno_check(libc::bind(
server_sockfd,
(&addr as *const libc::sockaddr_in6).cast::<libc::sockaddr>(),
size_of::<libc::sockaddr_in6>() as libc::socklen_t,
));
}
unsafe {
errno_check(libc::listen(server_sockfd, 16));
}
// Retrieve actual listener address because we used a randomized port.
let (_, server_addr) =
sockname(|storage, len| unsafe { libc::getsockname(server_sockfd, storage, len) }).unwrap();
let LibcSocketAddr::V6(addr) = server_addr else {
// We bound an IPv6 address so we also expect
// an IPv6 address to be returned.
panic!()
};
// Spawn the server thread.
let server_thread = thread::spawn(move || {
let (_peerfd, _peer_addr) =
sockname(|storage, len| unsafe { libc::accept(server_sockfd, storage, len) }).unwrap();
});
// Test connecting to an already accepting server.
unsafe {
errno_check(libc::connect(
client_sockfd,
(&addr as *const libc::sockaddr_in6).cast::<libc::sockaddr>(),
size_of::<libc::sockaddr_in6>() as libc::socklen_t,
));
}
let (_, peer_addr) =
sockname(|storage, len| unsafe { libc::getpeername(client_sockfd, storage, len) }).unwrap();
let LibcSocketAddr::V6(peer_addr) = peer_addr else {
// We connected to an IPv6 address so we also expect
// an IPv6 address to be returned.
panic!()
};
assert_eq!(addr.sin6_family, peer_addr.sin6_family);
assert_eq!(addr.sin6_port, peer_addr.sin6_port);
assert_eq!(addr.sin6_flowinfo, peer_addr.sin6_flowinfo);
assert_eq!(addr.sin6_scope_id, peer_addr.sin6_scope_id);
assert_eq!(addr.sin6_addr.s6_addr, peer_addr.sin6_addr.s6_addr);
server_thread.join().unwrap();
}
/// Set a socket option. It's the caller's responsibility to ensure that `T` is
/// associated with the given socket option.
///
@@ -22,7 +22,7 @@
FILE_ALLOCATION_INFO, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_NORMAL, FILE_BEGIN,
FILE_CURRENT, FILE_END_OF_FILE_INFO, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT,
FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, FileAllocationInfo, FileEndOfFileInfo,
FlushFileBuffers, GetFileInformationByHandle, OPEN_ALWAYS, OPEN_EXISTING,
FlushFileBuffers, GetFileInformationByHandle, MoveFileExW, OPEN_ALWAYS, OPEN_EXISTING,
SetFileInformationByHandle, SetFilePointerEx,
};
use windows_sys::Win32::System::IO::IO_STATUS_BLOCK;
@@ -42,6 +42,7 @@ fn main() {
test_set_file_info();
test_dup_handle();
test_flush_buffers();
test_move_file();
}
}
@@ -376,6 +377,23 @@ unsafe fn test_flush_buffers() {
}
}
unsafe fn test_move_file() {
let tmp_dir = utils::tmp();
let temp = tmp_dir.join("test_move_file.txt");
let temp_new = tmp_dir.join("test_move_file_new.txt");
let mut file = fs::File::options().create(true).write(true).open(&temp).unwrap();
file.write_all(b"Hello, World!\n").unwrap();
let from = to_wide_cstr(&temp);
let to = to_wide_cstr(&temp_new);
if MoveFileExW(from.as_ptr(), to.as_ptr(), 1) == 0 {
panic!("Failed to rename file from {} to {}", temp.display(), temp_new.display());
}
assert_eq!(fs::read_to_string(temp_new).unwrap(), "Hello, World!\n");
}
fn to_wide_cstr(path: &Path) -> Vec<u16> {
let mut raw_path = path.as_os_str().encode_wide().collect::<Vec<_>>();
raw_path.extend([0, 0]);
@@ -134,13 +134,19 @@ macro_rules! assert_eq {
assert_eq!(simd_reduce_min(b), -4.0f16);
assert_eq!(
simd_maximum_number_nsz(f16x2::from_array([0.0, f16::NAN]), f16x2::from_array([f16::NAN, 0.0])),
simd_maximum_number_nsz(
f16x2::from_array([0.0, f16::NAN]),
f16x2::from_array([f16::NAN, 0.0])
),
f16x2::from_array([0.0, 0.0])
);
assert_eq!(simd_reduce_max(f16x2::from_array([0.0, f16::NAN])), 0.0f16);
assert_eq!(simd_reduce_max(f16x2::from_array([f16::NAN, 0.0])), 0.0f16);
assert_eq!(
simd_minimum_number_nsz(f16x2::from_array([0.0, f16::NAN]), f16x2::from_array([f16::NAN, 0.0])),
simd_minimum_number_nsz(
f16x2::from_array([0.0, f16::NAN]),
f16x2::from_array([f16::NAN, 0.0])
),
f16x2::from_array([0.0, 0.0])
);
assert_eq!(simd_reduce_min(f16x2::from_array([0.0, f16::NAN])), 0.0f16);
@@ -348,13 +354,19 @@ macro_rules! assert_eq {
assert_eq!(simd_reduce_min(b), -4.0f128);
assert_eq!(
simd_maximum_number_nsz(f128x2::from_array([0.0, f128::NAN]), f128x2::from_array([f128::NAN, 0.0])),
simd_maximum_number_nsz(
f128x2::from_array([0.0, f128::NAN]),
f128x2::from_array([f128::NAN, 0.0])
),
f128x2::from_array([0.0, 0.0])
);
assert_eq!(simd_reduce_max(f128x2::from_array([0.0, f128::NAN])), 0.0f128);
assert_eq!(simd_reduce_max(f128x2::from_array([f128::NAN, 0.0])), 0.0f128);
assert_eq!(
simd_minimum_number_nsz(f128x2::from_array([0.0, f128::NAN]), f128x2::from_array([f128::NAN, 0.0])),
simd_minimum_number_nsz(
f128x2::from_array([0.0, f128::NAN]),
f128x2::from_array([f128::NAN, 0.0])
),
f128x2::from_array([0.0, 0.0])
);
assert_eq!(simd_reduce_min(f128x2::from_array([0.0, f128::NAN])), 0.0f128);
@@ -0,0 +1,8 @@
#![no_main]
#[no_mangle]
fn miri_start(_argc: isize, _argv: *const *const u8) -> isize {
let _b = Box::new(0);
println!("hello, world!");
0
}
@@ -0,0 +1 @@
hello, world!
@@ -0,0 +1,61 @@
// We're testing aarch64 CRC32 target specific features
//@only-target: aarch64
//@compile-flags: -C target-feature=+crc
use std::arch::aarch64::*;
use std::arch::is_aarch64_feature_detected;
fn main() {
assert!(is_aarch64_feature_detected!("crc"));
unsafe {
test_crc32_standard();
test_crc32c_castagnoli();
}
}
#[target_feature(enable = "crc")]
unsafe fn test_crc32_standard() {
// __crc32b: 8-bit input
assert_eq!(__crc32b(0x00000000, 0x01), 0x77073096);
assert_eq!(__crc32b(0xffffffff, 0x61), 0x174841bc);
assert_eq!(__crc32b(0x2aa1e72b, 0x2a), 0x772d9171);
// __crc32h: 16-bit input
assert_eq!(__crc32h(0x00000000, 0x0001), 0x191b3141);
assert_eq!(__crc32h(0xffffffff, 0x1234), 0xf6b56fbf);
assert_eq!(__crc32h(0x8ecec3b5, 0x022b), 0x03a1db7c);
// __crc32w: 32-bit input
assert_eq!(__crc32w(0x00000000, 0x00000001), 0xb8bc6765);
assert_eq!(__crc32w(0xffffffff, 0x12345678), 0x5092782d);
assert_eq!(__crc32w(0xae2912c8, 0x00845fed), 0xc5690dd4);
// __crc32d: 64-bit input
assert_eq!(__crc32d(0x00000000, 0x0000000000000001), 0xccaa009e);
assert_eq!(__crc32d(0xffffffff, 0x123456789abcdef0), 0xe6ddf8b5);
assert_eq!(__crc32d(0x0badeafe, 0xc0febeefdadafefe), 0x61a45fba);
}
#[target_feature(enable = "crc")]
unsafe fn test_crc32c_castagnoli() {
// __crc32cb: 8-bit input
assert_eq!(__crc32cb(0x00000000, 0x01), 0xf26b8303);
assert_eq!(__crc32cb(0xffffffff, 0x61), 0x3e2fbccf);
assert_eq!(__crc32cb(0x2aa1e72b, 0x2a), 0xf24122e4);
// __crc32ch: 16-bit input
assert_eq!(__crc32ch(0x00000000, 0x0001), 0x13a29877);
assert_eq!(__crc32ch(0xffffffff, 0x1234), 0xf13f4cea);
assert_eq!(__crc32ch(0x8ecec3b5, 0x022b), 0x013bb2fb);
// __crc32cw: 32-bit input
assert_eq!(__crc32cw(0x00000000, 0x00000001), 0xdd45aab8);
assert_eq!(__crc32cw(0xffffffff, 0x12345678), 0x4dece20c);
assert_eq!(__crc32cw(0xae2912c8, 0x00845fed), 0xffae2ed1);
// __crc32cd: 64-bit input
assert_eq!(__crc32cd(0x00000000, 0x0000000000000001), 0x493c7d27);
assert_eq!(__crc32cd(0xffffffff, 0x123456789abcdef0), 0xd95b664b);
assert_eq!(__crc32cd(0x0badeafe, 0xc0febeefdadafefe), 0x5b44f54f);
}
@@ -12,6 +12,8 @@ fn main() {
unsafe {
test_vpmaxq_u8();
test_tbl1_v16i8_basic();
test_vpadd();
test_vpaddl();
}
}
@@ -65,3 +67,93 @@ fn test_tbl1_v16i8_basic() {
assert_eq!(&got2_arr[3..16], &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12][..]);
}
}
#[target_feature(enable = "neon")]
unsafe fn test_vpadd() {
let a = vld1_s8([1, 2, 3, 4, 5, 6, 7, 8].as_ptr());
let b = vld1_s8([9, 10, -1, 2, i8::MIN, i8::MIN, i8::MAX, i8::MAX].as_ptr());
let e =
[3i8, 7, 11, 15, 19, -1 + 2, i8::MIN.wrapping_add(i8::MIN), i8::MAX.wrapping_add(i8::MAX)];
let mut r = [0i8; 8];
vst1_s8(r.as_mut_ptr(), vpadd_s8(a, b));
assert_eq!(r, e);
let a = vld1_s16([1, 2, 3, 4].as_ptr());
let b = vld1_s16([-1, 2, i16::MAX, i16::MAX].as_ptr());
let e = [3i16, 7, -1 + 2, i16::MAX.wrapping_add(i16::MAX)];
let mut r = [0i16; 4];
vst1_s16(r.as_mut_ptr(), vpadd_s16(a, b));
assert_eq!(r, e);
let a = vld1_s32([1, 2].as_ptr());
let b = vld1_s32([i32::MAX, i32::MAX].as_ptr());
let e = [3i32, i32::MAX.wrapping_add(i32::MAX)];
let mut r = [0i32; 2];
vst1_s32(r.as_mut_ptr(), vpadd_s32(a, b));
assert_eq!(r, e);
let a = vld1_u8([1, 2, 3, 4, 5, 6, 7, 8].as_ptr());
let b = vld1_u8([9, 10, 11, 12, 13, 14, u8::MAX, u8::MAX].as_ptr());
let e = [3u8, 7, 11, 15, 19, 23, 27, 254];
let mut r = [0u8; 8];
vst1_u8(r.as_mut_ptr(), vpadd_u8(a, b));
assert_eq!(r, e);
let a = vld1_u16([1, 2, 3, 4].as_ptr());
let b = vld1_u16([5, 6, u16::MAX, u16::MAX].as_ptr());
let e = [3u16, 7, 11, 65534];
let mut r = [0u16; 4];
vst1_u16(r.as_mut_ptr(), vpadd_u16(a, b));
assert_eq!(r, e);
let a = vld1_u32([1, 2].as_ptr());
let b = vld1_u32([u32::MAX, u32::MAX].as_ptr());
let e = [3u32, u32::MAX.wrapping_add(u32::MAX)];
let mut r = [0u32; 2];
vst1_u32(r.as_mut_ptr(), vpadd_u32(a, b));
assert_eq!(r, e);
}
#[target_feature(enable = "neon")]
unsafe fn test_vpaddl() {
let a = vld1_u8([1, 2, 3, 4, 5, 6, u8::MAX, u8::MAX].as_ptr());
let e = [3u16, 7, 11, 510];
let mut r = [0u16; 4];
vst1_u16(r.as_mut_ptr(), vpaddl_u8(a));
assert_eq!(r, e);
let a = vld1q_u8([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, u8::MAX, u8::MAX].as_ptr());
let e = [3u16, 7, 11, 15, 19, 23, 27, 510];
let mut r = [0u16; 8];
vst1q_u16(r.as_mut_ptr(), vpaddlq_u8(a));
assert_eq!(r, e);
let a = vld1_u16([1, 2, u16::MAX, u16::MAX].as_ptr());
let e = [3u32, 131070];
let mut r = [0u32; 2];
vst1_u32(r.as_mut_ptr(), vpaddl_u16(a));
assert_eq!(r, e);
let a = vld1q_u16([1, 2, 3, 4, 5, 6, u16::MAX, u16::MAX].as_ptr());
let e = [3u32, 7, 11, 131070];
let mut r = [0u32; 4];
vst1q_u32(r.as_mut_ptr(), vpaddlq_u16(a));
assert_eq!(r, e);
let a = vld1_u32([1, 2].as_ptr());
let e = [3u64];
let mut r = [0u64; 1];
vst1_u64(r.as_mut_ptr(), vpaddl_u32(a));
assert_eq!(r, e);
let a = vld1_u32([u32::MAX, u32::MAX].as_ptr());
let e = [8589934590];
let mut r = [0u64; 1];
vst1_u64(r.as_mut_ptr(), vpaddl_u32(a));
assert_eq!(r, e);
let a = vld1q_u32([1, 2, u32::MAX, u32::MAX].as_ptr());
let e = [3u64, 8589934590];
let mut r = [0u64; 2];
vst1q_u64(r.as_mut_ptr(), vpaddlq_u32(a));
assert_eq!(r, e);
}
+1 -1
View File
@@ -30,9 +30,9 @@ fn main() {
test_file_clone();
test_file_set_len();
test_file_sync();
test_rename();
// Windows file handling is very incomplete.
if cfg!(not(windows)) {
test_rename();
test_directory();
test_canonicalize();
#[cfg(unix)]
+20
View File
@@ -8,6 +8,7 @@ fn main() {
test_create_ipv4_listener();
test_create_ipv6_listener();
test_accept_and_connect();
test_peer_addr();
}
fn test_create_ipv4_listener() {
@@ -34,3 +35,22 @@ fn test_accept_and_connect() {
handle.join().unwrap();
}
/// Test whether the [`TcpStream::peer_addr`] of a connected socket
/// is the same address as the one the stream was connected to.
fn test_peer_addr() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
// Get local address with randomized port to know where
// we need to connect to.
let address = listener.local_addr().unwrap();
let handle = thread::spawn(move || {
let (_stream, _addr) = listener.accept().unwrap();
});
let stream = TcpStream::connect(address).unwrap();
let peer_addr = stream.peer_addr().unwrap();
assert_eq!(address, peer_addr);
handle.join().unwrap();
}