mirror of
https://github.com/rust-lang/rust.git
synced 2026-05-29 20:46:07 +03:00
Auto merge of #148993 - RalfJung:miri, r=RalfJung
miri subtree update Subtree update of `miri` to https://github.com/rust-lang/miri/commit/5774cefbfcdc58f5042205571d65d7b4d317f546. Created using https://github.com/rust-lang/josh-sync. r? `@ghost`
This commit is contained in:
+4
-1
@@ -73,7 +73,10 @@ jobs:
|
||||
sudo bash -c "echo 'https://ports.ubuntu.com/ priority:4' >> /etc/apt/apt-mirrors.txt"
|
||||
# Add architecture
|
||||
sudo dpkg --add-architecture ${{ matrix.multiarch }}
|
||||
sudo apt update
|
||||
# Ubuntu Ports often has outdated mirrors so try a few times to get the apt repo
|
||||
for TRY in $(seq 3); do
|
||||
{ sudo apt update && break; } || sleep 30
|
||||
done
|
||||
# Install needed packages
|
||||
sudo apt install $(echo "libatomic1: zlib1g-dev:" | sed 's/:/:${{ matrix.multiarch }}/g')
|
||||
- uses: ./.github/workflows/setup
|
||||
|
||||
@@ -1519,9 +1519,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tikv-jemalloc-sys"
|
||||
version = "0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7"
|
||||
version = "0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd3c60906412afa9c2b5b5a48ca6a5abe5736aec9eb48ad05037a677e52e4e2d"
|
||||
checksum = "cd8aa5b2ab86a2cefa406d889139c162cbb230092f7d1d7cbc1716405d852a3b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
||||
@@ -464,11 +464,6 @@ to Miri failing to detect cases of undefined behavior in a program.
|
||||
errors and warnings.
|
||||
* `-Zmiri-recursive-validation` is a *highly experimental* flag that makes validity checking
|
||||
recurse below references.
|
||||
* `-Zmiri-retag-fields[=<all|none|scalar>]` controls when Stacked Borrows retagging recurses into
|
||||
fields. `all` means it always recurses (the default, and equivalent to `-Zmiri-retag-fields`
|
||||
without an explicit value), `none` means it never recurses, `scalar` means it only recurses for
|
||||
types where we would also emit `noalias` annotations in the generated LLVM IR (types passed as
|
||||
individual scalars or pairs of scalars). Setting this to `none` is **unsound**.
|
||||
* `-Zmiri-preemption-rate` configures the probability that at the end of a basic block, the active
|
||||
thread will be preempted. The default is `0.01` (i.e., 1%). Setting this to `0` disables
|
||||
preemption. Note that even without preemption, the schedule is still non-deterministic:
|
||||
|
||||
@@ -1 +1 @@
|
||||
8401398e1f14a24670ee1a3203713dc2f0f8b3a8
|
||||
7a72c5459dd58f81b0e1a0e5436d145485889375
|
||||
|
||||
@@ -23,18 +23,18 @@
|
||||
mod log;
|
||||
|
||||
use std::env;
|
||||
use std::num::NonZero;
|
||||
use std::num::{NonZero, NonZeroI32};
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicI32, AtomicU32, Ordering};
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
use miri::{
|
||||
BacktraceStyle, BorrowTrackerMethod, GenmcConfig, GenmcCtx, MiriConfig, MiriEntryFnType,
|
||||
ProvenanceMode, RetagFields, TreeBorrowsParams, ValidationMode, run_genmc_mode,
|
||||
ProvenanceMode, TreeBorrowsParams, ValidationMode, run_genmc_mode,
|
||||
};
|
||||
use rustc_abi::ExternAbi;
|
||||
use rustc_data_structures::sync;
|
||||
use rustc_data_structures::sync::{self, DynSync};
|
||||
use rustc_driver::Compilation;
|
||||
use rustc_hir::def_id::LOCAL_CRATE;
|
||||
use rustc_hir::{self as hir, Node};
|
||||
@@ -120,15 +120,47 @@ fn miri_start(argc: isize, argv: *const *const u8) -> isize {\
|
||||
}
|
||||
}
|
||||
|
||||
fn run_many_seeds(
|
||||
many_seeds: ManySeedsConfig,
|
||||
eval_entry_once: impl Fn(u64) -> Result<(), NonZeroI32> + DynSync,
|
||||
) -> Result<(), NonZeroI32> {
|
||||
let exit_code =
|
||||
sync::IntoDynSyncSend(AtomicU32::new(rustc_driver::EXIT_SUCCESS.cast_unsigned()));
|
||||
let num_failed = sync::IntoDynSyncSend(AtomicU32::new(0));
|
||||
sync::par_for_each_in(many_seeds.seeds.clone(), |&seed| {
|
||||
if let Err(return_code) = eval_entry_once(seed.into()) {
|
||||
eprintln!("FAILING SEED: {seed}");
|
||||
if !many_seeds.keep_going {
|
||||
// `abort_if_errors` would unwind but would not actually stop miri, since
|
||||
// `par_for_each` waits for the rest of the threads to finish.
|
||||
exit(return_code.get());
|
||||
}
|
||||
// Preserve the "maximum" return code (when interpreted as `u32`), to make
|
||||
// the result order-independent and to make it 0 only if all executions were 0.
|
||||
exit_code.fetch_max(return_code.get().cast_unsigned(), Ordering::Relaxed);
|
||||
num_failed.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
});
|
||||
let num_failed = num_failed.0.into_inner();
|
||||
let exit_code = exit_code.0.into_inner().cast_signed();
|
||||
if num_failed > 0 {
|
||||
eprintln!("{num_failed}/{total} SEEDS FAILED", total = many_seeds.seeds.count());
|
||||
Err(NonZeroI32::new(exit_code).unwrap())
|
||||
} else {
|
||||
assert!(exit_code == 0);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl rustc_driver::Callbacks for MiriCompilerCalls {
|
||||
fn after_analysis<'tcx>(
|
||||
&mut self,
|
||||
_: &rustc_interface::interface::Compiler,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
) -> Compilation {
|
||||
if tcx.sess.dcx().has_errors_or_delayed_bugs().is_some() {
|
||||
tcx.dcx().fatal("miri cannot be run on programs that fail compilation");
|
||||
}
|
||||
tcx.dcx().abort_if_errors();
|
||||
tcx.dcx().flush_delayed();
|
||||
|
||||
if !tcx.crate_types().contains(&CrateType::Executable) {
|
||||
tcx.dcx().fatal("miri only makes sense on bin crates");
|
||||
}
|
||||
@@ -161,64 +193,28 @@ fn after_analysis<'tcx>(
|
||||
optimizations is usually marginal at best.");
|
||||
}
|
||||
|
||||
// Run in GenMC mode if enabled.
|
||||
if config.genmc_config.is_some() {
|
||||
// Validate GenMC settings.
|
||||
if let Err(err) = GenmcConfig::validate(&mut config, tcx) {
|
||||
fatal_error!("Invalid settings: {err}");
|
||||
}
|
||||
|
||||
// This is the entry point used in GenMC mode.
|
||||
// This closure will be called multiple times to explore the concurrent execution space of the program.
|
||||
let eval_entry_once = |genmc_ctx: Rc<GenmcCtx>| {
|
||||
let res = if config.genmc_config.is_some() {
|
||||
assert!(self.many_seeds.is_none());
|
||||
run_genmc_mode(tcx, &config, |genmc_ctx: Rc<GenmcCtx>| {
|
||||
miri::eval_entry(tcx, entry_def_id, entry_type, &config, Some(genmc_ctx))
|
||||
};
|
||||
let return_code = run_genmc_mode(&config, eval_entry_once, tcx).unwrap_or_else(|| {
|
||||
tcx.dcx().abort_if_errors();
|
||||
rustc_driver::EXIT_FAILURE
|
||||
});
|
||||
exit(return_code);
|
||||
})
|
||||
} else if let Some(many_seeds) = self.many_seeds.take() {
|
||||
assert!(config.seed.is_none());
|
||||
run_many_seeds(many_seeds, |seed| {
|
||||
let mut config = config.clone();
|
||||
config.seed = Some(seed);
|
||||
eprintln!("Trying seed: {seed}");
|
||||
miri::eval_entry(tcx, entry_def_id, entry_type, &config, /* genmc_ctx */ None)
|
||||
})
|
||||
} else {
|
||||
miri::eval_entry(tcx, entry_def_id, entry_type, &config, None)
|
||||
};
|
||||
|
||||
if let Some(many_seeds) = self.many_seeds.take() {
|
||||
assert!(config.seed.is_none());
|
||||
let exit_code = sync::IntoDynSyncSend(AtomicI32::new(rustc_driver::EXIT_SUCCESS));
|
||||
let num_failed = sync::IntoDynSyncSend(AtomicU32::new(0));
|
||||
sync::par_for_each_in(many_seeds.seeds.clone(), |seed| {
|
||||
let mut config = config.clone();
|
||||
config.seed = Some((*seed).into());
|
||||
eprintln!("Trying seed: {seed}");
|
||||
let return_code = miri::eval_entry(
|
||||
tcx,
|
||||
entry_def_id,
|
||||
entry_type,
|
||||
&config,
|
||||
/* genmc_ctx */ None,
|
||||
)
|
||||
.unwrap_or(rustc_driver::EXIT_FAILURE);
|
||||
if return_code != rustc_driver::EXIT_SUCCESS {
|
||||
eprintln!("FAILING SEED: {seed}");
|
||||
if !many_seeds.keep_going {
|
||||
// `abort_if_errors` would actually not stop, since `par_for_each` waits for the
|
||||
// rest of the to finish, so we just exit immediately.
|
||||
exit(return_code);
|
||||
}
|
||||
exit_code.store(return_code, Ordering::Relaxed);
|
||||
num_failed.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
});
|
||||
let num_failed = num_failed.0.into_inner();
|
||||
if num_failed > 0 {
|
||||
eprintln!("{num_failed}/{total} SEEDS FAILED", total = many_seeds.seeds.count());
|
||||
}
|
||||
exit(exit_code.0.into_inner());
|
||||
if let Err(return_code) = res {
|
||||
tcx.dcx().abort_if_errors();
|
||||
exit(return_code.get());
|
||||
} else {
|
||||
let return_code = miri::eval_entry(tcx, entry_def_id, entry_type, &config, None)
|
||||
.unwrap_or_else(|| {
|
||||
tcx.dcx().abort_if_errors();
|
||||
rustc_driver::EXIT_FAILURE
|
||||
});
|
||||
exit(return_code);
|
||||
exit(rustc_driver::EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
// Unreachable.
|
||||
@@ -571,7 +567,10 @@ fn main() {
|
||||
} else if arg == "-Zmiri-mute-stdout-stderr" {
|
||||
miri_config.mute_stdout_stderr = true;
|
||||
} else if arg == "-Zmiri-retag-fields" {
|
||||
miri_config.retag_fields = RetagFields::Yes;
|
||||
eprintln!(
|
||||
"warning: `-Zmiri-retag-fields` is a NOP and will be removed in a future version of Miri.\n\
|
||||
Field retagging has been on-by-default for a long time."
|
||||
);
|
||||
} else if arg == "-Zmiri-fixed-schedule" {
|
||||
miri_config.fixed_scheduling = true;
|
||||
} else if arg == "-Zmiri-deterministic-concurrency" {
|
||||
@@ -579,13 +578,6 @@ fn main() {
|
||||
miri_config.address_reuse_cross_thread_rate = 0.0;
|
||||
miri_config.cmpxchg_weak_failure_rate = 0.0;
|
||||
miri_config.weak_memory_emulation = false;
|
||||
} else if let Some(retag_fields) = arg.strip_prefix("-Zmiri-retag-fields=") {
|
||||
miri_config.retag_fields = match retag_fields {
|
||||
"all" => RetagFields::Yes,
|
||||
"none" => RetagFields::No,
|
||||
"scalar" => RetagFields::OnlyScalar,
|
||||
_ => fatal_error!("`-Zmiri-retag-fields` can only be `all`, `none`, or `scalar`"),
|
||||
};
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-seed=") {
|
||||
let seed = param.parse::<u64>().unwrap_or_else(|_| {
|
||||
fatal_error!("-Zmiri-seed must be an integer that fits into u64")
|
||||
@@ -747,6 +739,13 @@ fn main() {
|
||||
);
|
||||
};
|
||||
|
||||
// Validate GenMC settings.
|
||||
if miri_config.genmc_config.is_some()
|
||||
&& let Err(err) = GenmcConfig::validate(&mut miri_config)
|
||||
{
|
||||
fatal_error!("Invalid settings: {err}");
|
||||
}
|
||||
|
||||
debug!("rustc arguments: {:?}", rustc_args);
|
||||
debug!("crate arguments: {:?}", miri_config.args);
|
||||
if !miri_config.native_lib.is_empty() && miri_config.native_lib_enable_tracing {
|
||||
|
||||
@@ -116,8 +116,6 @@ pub struct GlobalStateInner {
|
||||
protected_tags: FxHashMap<BorTag, ProtectorKind>,
|
||||
/// The pointer ids to trace
|
||||
tracked_pointer_tags: FxHashSet<BorTag>,
|
||||
/// Whether to recurse into datatypes when searching for pointers to retag.
|
||||
retag_fields: RetagFields,
|
||||
}
|
||||
|
||||
impl VisitProvenance for GlobalStateInner {
|
||||
@@ -131,18 +129,6 @@ fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
|
||||
/// We need interior mutable access to the global state.
|
||||
pub type GlobalState = RefCell<GlobalStateInner>;
|
||||
|
||||
/// Policy on whether to recurse into fields to retag
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum RetagFields {
|
||||
/// Don't retag any fields.
|
||||
No,
|
||||
/// Retag all fields.
|
||||
Yes,
|
||||
/// Only retag fields of types with Scalar and ScalarPair layout,
|
||||
/// to match the LLVM `noalias` we generate.
|
||||
OnlyScalar,
|
||||
}
|
||||
|
||||
/// The flavor of the protector.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ProtectorKind {
|
||||
@@ -168,7 +154,6 @@ impl GlobalStateInner {
|
||||
pub fn new(
|
||||
borrow_tracker_method: BorrowTrackerMethod,
|
||||
tracked_pointer_tags: FxHashSet<BorTag>,
|
||||
retag_fields: RetagFields,
|
||||
) -> Self {
|
||||
GlobalStateInner {
|
||||
borrow_tracker_method,
|
||||
@@ -176,7 +161,6 @@ pub fn new(
|
||||
root_ptr_tags: FxHashMap::default(),
|
||||
protected_tags: FxHashMap::default(),
|
||||
tracked_pointer_tags,
|
||||
retag_fields,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,11 +228,7 @@ pub struct TreeBorrowsParams {
|
||||
|
||||
impl BorrowTrackerMethod {
|
||||
pub fn instantiate_global_state(self, config: &MiriConfig) -> GlobalState {
|
||||
RefCell::new(GlobalStateInner::new(
|
||||
self,
|
||||
config.tracked_pointer_tags.clone(),
|
||||
config.retag_fields,
|
||||
))
|
||||
RefCell::new(GlobalStateInner::new(self, config.tracked_pointer_tags.clone()))
|
||||
}
|
||||
|
||||
pub fn get_tree_borrows_params(self) -> TreeBorrowsParams {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::{cmp, mem};
|
||||
|
||||
use rustc_abi::{BackendRepr, Size};
|
||||
use rustc_abi::Size;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_middle::mir::{Mutability, RetagKind};
|
||||
use rustc_middle::ty::layout::HasTypingEnv;
|
||||
@@ -887,14 +887,12 @@ fn sb_retag_place_contents(
|
||||
place: &PlaceTy<'tcx>,
|
||||
) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
let retag_fields = this.machine.borrow_tracker.as_mut().unwrap().get_mut().retag_fields;
|
||||
let retag_cause = match kind {
|
||||
RetagKind::TwoPhase => unreachable!(), // can only happen in `retag_ptr_value`
|
||||
RetagKind::FnEntry => RetagCause::FnEntry,
|
||||
RetagKind::Default | RetagKind::Raw => RetagCause::Normal,
|
||||
};
|
||||
let mut visitor =
|
||||
RetagVisitor { ecx: this, kind, retag_cause, retag_fields, in_field: false };
|
||||
let mut visitor = RetagVisitor { ecx: this, kind, retag_cause, in_field: false };
|
||||
return visitor.visit_value(place);
|
||||
|
||||
// The actual visitor.
|
||||
@@ -902,7 +900,6 @@ struct RetagVisitor<'ecx, 'tcx> {
|
||||
ecx: &'ecx mut MiriInterpCx<'tcx>,
|
||||
kind: RetagKind,
|
||||
retag_cause: RetagCause,
|
||||
retag_fields: RetagFields,
|
||||
in_field: bool,
|
||||
}
|
||||
impl<'ecx, 'tcx> RetagVisitor<'ecx, 'tcx> {
|
||||
@@ -967,24 +964,10 @@ fn visit_value(&mut self, place: &PlaceTy<'tcx>) -> InterpResult<'tcx> {
|
||||
self.walk_value(place)?;
|
||||
}
|
||||
_ => {
|
||||
// Not a reference/pointer/box. Only recurse if configured appropriately.
|
||||
let recurse = match self.retag_fields {
|
||||
RetagFields::No => false,
|
||||
RetagFields::Yes => true,
|
||||
RetagFields::OnlyScalar => {
|
||||
// Matching `ArgAbi::new` at the time of writing, only fields of
|
||||
// `Scalar` and `ScalarPair` ABI are considered.
|
||||
matches!(
|
||||
place.layout.backend_repr,
|
||||
BackendRepr::Scalar(..) | BackendRepr::ScalarPair(..)
|
||||
)
|
||||
}
|
||||
};
|
||||
if recurse {
|
||||
let in_field = mem::replace(&mut self.in_field, true); // remember and restore old value
|
||||
self.walk_value(place)?;
|
||||
self.in_field = in_field;
|
||||
}
|
||||
// Not a reference/pointer/box. Recurse.
|
||||
let in_field = mem::replace(&mut self.in_field, true); // remember and restore old value
|
||||
self.walk_value(place)?;
|
||||
self.in_field = in_field;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustc_abi::{BackendRepr, Size};
|
||||
use rustc_abi::Size;
|
||||
use rustc_middle::mir::{Mutability, RetagKind};
|
||||
use rustc_middle::ty::layout::HasTypingEnv;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
@@ -468,16 +468,13 @@ fn tb_retag_place_contents(
|
||||
place: &PlaceTy<'tcx>,
|
||||
) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
let options = this.machine.borrow_tracker.as_mut().unwrap().get_mut();
|
||||
let retag_fields = options.retag_fields;
|
||||
let mut visitor = RetagVisitor { ecx: this, kind, retag_fields };
|
||||
let mut visitor = RetagVisitor { ecx: this, kind };
|
||||
return visitor.visit_value(place);
|
||||
|
||||
// The actual visitor.
|
||||
struct RetagVisitor<'ecx, 'tcx> {
|
||||
ecx: &'ecx mut MiriInterpCx<'tcx>,
|
||||
kind: RetagKind,
|
||||
retag_fields: RetagFields,
|
||||
}
|
||||
impl<'ecx, 'tcx> RetagVisitor<'ecx, 'tcx> {
|
||||
#[inline(always)] // yes this helps in our benchmarks
|
||||
@@ -545,22 +542,8 @@ fn visit_value(&mut self, place: &PlaceTy<'tcx>) -> InterpResult<'tcx> {
|
||||
self.walk_value(place)?;
|
||||
}
|
||||
_ => {
|
||||
// Not a reference/pointer/box. Only recurse if configured appropriately.
|
||||
let recurse = match self.retag_fields {
|
||||
RetagFields::No => false,
|
||||
RetagFields::Yes => true,
|
||||
RetagFields::OnlyScalar => {
|
||||
// Matching `ArgAbi::new` at the time of writing, only fields of
|
||||
// `Scalar` and `ScalarPair` ABI are considered.
|
||||
matches!(
|
||||
place.layout.backend_repr,
|
||||
BackendRepr::Scalar(..) | BackendRepr::ScalarPair(..)
|
||||
)
|
||||
}
|
||||
};
|
||||
if recurse {
|
||||
self.walk_value(place)?;
|
||||
}
|
||||
// Not a reference/pointer/box. Recurse.
|
||||
self.walk_value(place)?;
|
||||
}
|
||||
}
|
||||
interp_ok(())
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use genmc_sys::LogLevel;
|
||||
use rustc_abi::Endian;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
|
||||
use super::GenmcParams;
|
||||
use crate::{IsolatedOp, MiriConfig, RejectOpWith};
|
||||
@@ -86,16 +84,11 @@ pub fn parse_arg(
|
||||
///
|
||||
/// Unsupported configurations return an error.
|
||||
/// Adjusts Miri settings where required, printing a warnings if the change might be unexpected for the user.
|
||||
pub fn validate(miri_config: &mut MiriConfig, tcx: TyCtxt<'_>) -> Result<(), &'static str> {
|
||||
pub fn validate(miri_config: &mut MiriConfig) -> Result<(), &'static str> {
|
||||
let Some(genmc_config) = miri_config.genmc_config.as_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Check for supported target.
|
||||
if tcx.data_layout.endian != Endian::Little || tcx.data_layout.pointer_size().bits() != 64 {
|
||||
return Err("GenMC only supports 64bit little-endian targets");
|
||||
}
|
||||
|
||||
// Check for disallowed configurations.
|
||||
if !miri_config.data_race_detector {
|
||||
return Err("Cannot disable data race detection in GenMC mode");
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use rustc_abi::{Align, Size};
|
||||
use rustc_const_eval::interpret::{AllocId, InterpCx, InterpResult};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
|
||||
pub use self::intercept::EvalContextExt as GenmcEvalContextExt;
|
||||
pub use self::run::run_genmc_mode;
|
||||
@@ -23,6 +22,7 @@ pub struct GenmcCtx {}
|
||||
pub struct GenmcConfig {}
|
||||
|
||||
mod run {
|
||||
use std::num::NonZeroI32;
|
||||
use std::rc::Rc;
|
||||
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
@@ -30,10 +30,10 @@ mod run {
|
||||
use crate::{GenmcCtx, MiriConfig};
|
||||
|
||||
pub fn run_genmc_mode<'tcx>(
|
||||
_config: &MiriConfig,
|
||||
_eval_entry: impl Fn(Rc<GenmcCtx>) -> Option<i32>,
|
||||
_tcx: TyCtxt<'tcx>,
|
||||
) -> Option<i32> {
|
||||
_config: &MiriConfig,
|
||||
_eval_entry: impl Fn(Rc<GenmcCtx>) -> Result<(), NonZeroI32>,
|
||||
) -> Result<(), NonZeroI32> {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
@@ -240,10 +240,7 @@ pub fn parse_arg(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate(
|
||||
_miri_config: &mut crate::MiriConfig,
|
||||
_tcx: TyCtxt<'_>,
|
||||
) -> Result<(), &'static str> {
|
||||
pub fn validate(_miri_config: &mut crate::MiriConfig) -> Result<(), &'static str> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
use rustc_abi::{Align, Size};
|
||||
use rustc_const_eval::interpret::{AllocId, InterpCx, InterpResult, interp_ok};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_middle::{throw_machine_stop, throw_ub_format, throw_unsup_format};
|
||||
use rustc_middle::{throw_ub_format, throw_unsup_format};
|
||||
// FIXME(genmc,tracing): Implement some work-around for enabling debug/trace level logging (currently disabled statically in rustc).
|
||||
use tracing::{debug, info};
|
||||
use tracing::debug;
|
||||
|
||||
use self::global_allocations::{EvalContextExt as _, GlobalAllocationHandler};
|
||||
use self::helper::{
|
||||
@@ -63,12 +63,6 @@ struct ExitStatus {
|
||||
exit_type: ExitType,
|
||||
}
|
||||
|
||||
impl ExitStatus {
|
||||
fn do_leak_check(self) -> bool {
|
||||
matches!(self.exit_type, ExitType::MainThreadFinish)
|
||||
}
|
||||
}
|
||||
|
||||
/// State that is reset at the start of every execution.
|
||||
#[derive(Debug, Default)]
|
||||
struct PerExecutionState {
|
||||
@@ -223,8 +217,6 @@ fn prepare_next_execution(&self) {
|
||||
|
||||
/// Inform GenMC that the program's execution has ended.
|
||||
///
|
||||
/// This function must be called even when the execution is blocked
|
||||
/// (i.e., it returned a `InterpErrorKind::MachineStop` with error kind `TerminationInfo::GenmcBlockedExecution`).
|
||||
/// Don't call this function if an error was found.
|
||||
///
|
||||
/// GenMC detects certain errors only when the execution ends.
|
||||
@@ -694,39 +686,37 @@ pub(crate) fn handle_thread_finish<'tcx>(&self, threads: &ThreadManager<'tcx>) {
|
||||
}
|
||||
|
||||
/// Handle a call to `libc::exit` or the exit of the main thread.
|
||||
/// Unless an error is returned, the program should continue executing (in a different thread, chosen by the next scheduling call).
|
||||
/// Unless an error is returned, the program should continue executing (in a different thread,
|
||||
/// chosen by the next scheduling call).
|
||||
pub(crate) fn handle_exit<'tcx>(
|
||||
&self,
|
||||
thread: ThreadId,
|
||||
exit_code: i32,
|
||||
exit_type: ExitType,
|
||||
) -> InterpResult<'tcx> {
|
||||
// Calling `libc::exit` doesn't do cleanup, so we skip the leak check in that case.
|
||||
let exit_status = ExitStatus { exit_code, exit_type };
|
||||
|
||||
if let Some(old_exit_status) = self.exec_state.exit_status.get() {
|
||||
throw_ub_format!(
|
||||
"`exit` called twice, first with status {old_exit_status:?}, now with status {exit_status:?}",
|
||||
"`exit` called twice, first with exit code {old_exit_code}, now with status {exit_code}",
|
||||
old_exit_code = old_exit_status.exit_code,
|
||||
);
|
||||
}
|
||||
|
||||
// FIXME(genmc): Add a flag to continue exploration even when the program exits with a non-zero exit code.
|
||||
if exit_code != 0 {
|
||||
info!("GenMC: 'exit' called with non-zero argument, aborting execution.");
|
||||
let leak_check = exit_status.do_leak_check();
|
||||
throw_machine_stop!(TerminationInfo::Exit { code: exit_code, leak_check });
|
||||
match exit_type {
|
||||
ExitType::ExitCalled => {
|
||||
// `exit` kills the current thread; we have to tell GenMC about this.
|
||||
let thread_infos = self.exec_state.thread_id_manager.borrow();
|
||||
let genmc_tid = thread_infos.get_genmc_tid(thread);
|
||||
self.handle.borrow_mut().pin_mut().handle_thread_kill(genmc_tid);
|
||||
}
|
||||
ExitType::MainThreadFinish => {
|
||||
// The main thread has already exited so we don't call `handle_thread_kill` again.
|
||||
assert_eq!(thread, ThreadId::MAIN_THREAD);
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(exit_type, ExitType::ExitCalled) {
|
||||
let thread_infos = self.exec_state.thread_id_manager.borrow();
|
||||
let genmc_tid = thread_infos.get_genmc_tid(thread);
|
||||
|
||||
self.handle.borrow_mut().pin_mut().handle_thread_kill(genmc_tid);
|
||||
} else {
|
||||
assert_eq!(thread, ThreadId::MAIN_THREAD);
|
||||
}
|
||||
// We continue executing now, so we store the exit status.
|
||||
self.exec_state.exit_status.set(Some(exit_status));
|
||||
// To cover all possible behaviors, we have to continue execution the other threads:
|
||||
// whatever they do next could also have happened before the `exit` call. So we just
|
||||
// remember the exit status and use it when the other threads are done.
|
||||
self.exec_state.exit_status.set(Some(ExitStatus { exit_code, exit_type }));
|
||||
interp_ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use std::num::NonZeroI32;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
use genmc_sys::EstimationResult;
|
||||
use rustc_abi::Endian;
|
||||
use rustc_log::tracing;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
|
||||
@@ -24,10 +26,15 @@ pub(super) enum GenmcMode {
|
||||
///
|
||||
/// Returns `None` is an error is detected, or `Some(return_value)` with the return value of the last run of the program.
|
||||
pub fn run_genmc_mode<'tcx>(
|
||||
config: &MiriConfig,
|
||||
eval_entry: impl Fn(Rc<GenmcCtx>) -> Option<i32>,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
) -> Option<i32> {
|
||||
config: &MiriConfig,
|
||||
eval_entry: impl Fn(Rc<GenmcCtx>) -> Result<(), NonZeroI32>,
|
||||
) -> Result<(), NonZeroI32> {
|
||||
// Check for supported target.
|
||||
if tcx.data_layout.endian != Endian::Little || tcx.data_layout.pointer_size().bits() != 64 {
|
||||
tcx.dcx().fatal("GenMC only supports 64bit little-endian targets");
|
||||
}
|
||||
|
||||
let genmc_config = config.genmc_config.as_ref().unwrap();
|
||||
// Run in Estimation mode if requested.
|
||||
if genmc_config.do_estimation {
|
||||
@@ -41,10 +48,10 @@ pub fn run_genmc_mode<'tcx>(
|
||||
|
||||
fn run_genmc_mode_impl<'tcx>(
|
||||
config: &MiriConfig,
|
||||
eval_entry: &impl Fn(Rc<GenmcCtx>) -> Option<i32>,
|
||||
eval_entry: &impl Fn(Rc<GenmcCtx>) -> Result<(), NonZeroI32>,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
mode: GenmcMode,
|
||||
) -> Option<i32> {
|
||||
) -> Result<(), NonZeroI32> {
|
||||
let time_start = Instant::now();
|
||||
let genmc_config = config.genmc_config.as_ref().unwrap();
|
||||
|
||||
@@ -62,9 +69,9 @@ fn run_genmc_mode_impl<'tcx>(
|
||||
genmc_ctx.prepare_next_execution();
|
||||
|
||||
// Execute the program until completion to get the return value, or return if an error happens:
|
||||
let Some(return_code) = eval_entry(genmc_ctx.clone()) else {
|
||||
if let Err(err) = eval_entry(genmc_ctx.clone()) {
|
||||
genmc_ctx.print_genmc_output(genmc_config, tcx);
|
||||
return None;
|
||||
return Err(err);
|
||||
};
|
||||
|
||||
// We inform GenMC that the execution is complete.
|
||||
@@ -80,18 +87,17 @@ fn run_genmc_mode_impl<'tcx>(
|
||||
genmc_ctx.print_verification_output(genmc_config, elapsed_time_sec);
|
||||
}
|
||||
// Return the return code of the last execution.
|
||||
return Some(return_code);
|
||||
return Ok(());
|
||||
}
|
||||
ExecutionEndResult::Error(error) => {
|
||||
// This can be reached for errors that affect the entire execution, not just a specific event.
|
||||
// For instance, linearizability checking and liveness checking report their errors this way.
|
||||
// Neither are supported by Miri-GenMC at the moment though. However, GenMC also
|
||||
// treats races on deallocation as global errors, so this code path is still reachable.
|
||||
// Neither are supported by Miri-GenMC at the moment though.
|
||||
// Since we don't have any span information for the error at this point,
|
||||
// we just print GenMC's error string, and the full GenMC output if requested.
|
||||
eprintln!("(GenMC) Error detected: {error}");
|
||||
genmc_ctx.print_genmc_output(genmc_config, tcx);
|
||||
return None;
|
||||
return Err(NonZeroI32::new(rustc_driver::EXIT_FAILURE).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,14 +118,21 @@ pub(crate) fn schedule_thread<'tcx>(
|
||||
// Depending on the exec_state, we either schedule the given thread, or we are finished with this execution.
|
||||
match result.exec_state {
|
||||
ExecutionState::Ok => interp_ok(Some(thread_infos.get_miri_tid(result.next_thread))),
|
||||
ExecutionState::Blocked => throw_machine_stop!(TerminationInfo::GenmcBlockedExecution),
|
||||
ExecutionState::Blocked => {
|
||||
// This execution doesn't need further exploration. We treat this as "success, no
|
||||
// leak check needed", which makes it a NOP in the big outer loop.
|
||||
throw_machine_stop!(TerminationInfo::Exit {
|
||||
code: 0, // success
|
||||
leak_check: false,
|
||||
});
|
||||
}
|
||||
ExecutionState::Finished => {
|
||||
let exit_status = self.exec_state.exit_status.get().expect(
|
||||
"If the execution is finished, we should have a return value from the program.",
|
||||
);
|
||||
throw_machine_stop!(TerminationInfo::Exit {
|
||||
code: exit_status.exit_code,
|
||||
leak_check: exit_status.do_leak_check()
|
||||
leak_check: matches!(exit_status.exit_type, super::ExitType::MainThreadFinish),
|
||||
});
|
||||
}
|
||||
ExecutionState::Error => {
|
||||
|
||||
@@ -515,10 +515,13 @@ pub fn active_thread_stack_mut(
|
||||
&mut self.threads[self.active_thread].stack
|
||||
}
|
||||
|
||||
pub fn all_stacks(
|
||||
pub fn all_blocked_stacks(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (ThreadId, &[Frame<'tcx, Provenance, FrameExtra<'tcx>>])> {
|
||||
self.threads.iter_enumerated().map(|(id, t)| (id, &t.stack[..]))
|
||||
self.threads
|
||||
.iter_enumerated()
|
||||
.filter(|(_id, t)| matches!(t.state, ThreadState::Blocked { .. }))
|
||||
.map(|(id, t)| (id, &t.stack[..]))
|
||||
}
|
||||
|
||||
/// Create a new thread and returns its id.
|
||||
|
||||
@@ -33,9 +33,6 @@ pub enum TerminationInfo {
|
||||
},
|
||||
Int2PtrWithStrictProvenance,
|
||||
Deadlock,
|
||||
/// In GenMC mode, executions can get blocked, which stops the current execution without running any cleanup.
|
||||
/// No leak checks should be performed if this happens, since they would give false positives.
|
||||
GenmcBlockedExecution,
|
||||
MultipleSymbolDefinitions {
|
||||
link_name: Symbol,
|
||||
first: SpanData,
|
||||
@@ -80,8 +77,6 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
StackedBorrowsUb { msg, .. } => write!(f, "{msg}"),
|
||||
TreeBorrowsUb { title, .. } => write!(f, "{title}"),
|
||||
Deadlock => write!(f, "the evaluated program deadlocked"),
|
||||
GenmcBlockedExecution =>
|
||||
write!(f, "GenMC determined that the execution got blocked (this is not an error)"),
|
||||
MultipleSymbolDefinitions { link_name, .. } =>
|
||||
write!(f, "multiple definitions of symbol `{link_name}`"),
|
||||
SymbolShimClashing { link_name, .. } =>
|
||||
@@ -226,19 +221,20 @@ pub fn prune_stacktrace<'tcx>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit a custom diagnostic without going through the miri-engine machinery.
|
||||
/// Report the result of a Miri execution.
|
||||
///
|
||||
/// Returns `Some` if this was regular program termination with a given exit code and a `bool` indicating whether a leak check should happen; `None` otherwise.
|
||||
pub fn report_error<'tcx>(
|
||||
/// Returns `Some` if this was regular program termination with a given exit code and a `bool`
|
||||
/// indicating whether a leak check should happen; `None` otherwise.
|
||||
pub fn report_result<'tcx>(
|
||||
ecx: &InterpCx<'tcx, MiriMachine<'tcx>>,
|
||||
e: InterpErrorInfo<'tcx>,
|
||||
res: InterpErrorInfo<'tcx>,
|
||||
) -> Option<(i32, bool)> {
|
||||
use InterpErrorKind::*;
|
||||
use UndefinedBehaviorInfo::*;
|
||||
|
||||
let mut labels = vec![];
|
||||
|
||||
let (title, helps) = if let MachineStop(info) = e.kind() {
|
||||
let (title, helps) = if let MachineStop(info) = res.kind() {
|
||||
let info = info.downcast_ref::<TerminationInfo>().expect("invalid MachineStop payload");
|
||||
use TerminationInfo::*;
|
||||
let title = match info {
|
||||
@@ -253,13 +249,6 @@ pub fn report_error<'tcx>(
|
||||
labels.push(format!("this thread got stuck here"));
|
||||
None
|
||||
}
|
||||
GenmcBlockedExecution => {
|
||||
// This case should only happen in GenMC mode.
|
||||
assert!(ecx.machine.data_race.as_genmc_ref().is_some());
|
||||
// The program got blocked by GenMC without finishing the execution.
|
||||
// No cleanup code was executed, so we don't do any leak checks.
|
||||
return Some((0, false));
|
||||
}
|
||||
MultipleSymbolDefinitions { .. } | SymbolShimClashing { .. } => None,
|
||||
};
|
||||
#[rustfmt::skip]
|
||||
@@ -334,7 +323,7 @@ pub fn report_error<'tcx>(
|
||||
};
|
||||
(title, helps)
|
||||
} else {
|
||||
let title = match e.kind() {
|
||||
let title = match res.kind() {
|
||||
UndefinedBehavior(ValidationError(validation_err))
|
||||
if matches!(
|
||||
validation_err.kind,
|
||||
@@ -344,7 +333,7 @@ pub fn report_error<'tcx>(
|
||||
ecx.handle_ice(); // print interpreter backtrace (this is outside the eval `catch_unwind`)
|
||||
bug!(
|
||||
"This validation error should be impossible in Miri: {}",
|
||||
format_interp_error(ecx.tcx.dcx(), e)
|
||||
format_interp_error(ecx.tcx.dcx(), res)
|
||||
);
|
||||
}
|
||||
UndefinedBehavior(_) => "Undefined Behavior",
|
||||
@@ -363,12 +352,12 @@ pub fn report_error<'tcx>(
|
||||
ecx.handle_ice(); // print interpreter backtrace (this is outside the eval `catch_unwind`)
|
||||
bug!(
|
||||
"This error should be impossible in Miri: {}",
|
||||
format_interp_error(ecx.tcx.dcx(), e)
|
||||
format_interp_error(ecx.tcx.dcx(), res)
|
||||
);
|
||||
}
|
||||
};
|
||||
#[rustfmt::skip]
|
||||
let helps = match e.kind() {
|
||||
let helps = match res.kind() {
|
||||
Unsupported(_) =>
|
||||
vec![
|
||||
note!("this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support"),
|
||||
@@ -422,7 +411,7 @@ pub fn report_error<'tcx>(
|
||||
// We want to dump the allocation if this is `InvalidUninitBytes`.
|
||||
// Since `format_interp_error` consumes `e`, we compute the outut early.
|
||||
let mut extra = String::new();
|
||||
match e.kind() {
|
||||
match res.kind() {
|
||||
UndefinedBehavior(InvalidUninitBytes(Some((alloc_id, access)))) => {
|
||||
writeln!(
|
||||
extra,
|
||||
@@ -448,7 +437,7 @@ pub fn report_error<'tcx>(
|
||||
if let Some(title) = title {
|
||||
write!(primary_msg, "{title}: ").unwrap();
|
||||
}
|
||||
write!(primary_msg, "{}", format_interp_error(ecx.tcx.dcx(), e)).unwrap();
|
||||
write!(primary_msg, "{}", format_interp_error(ecx.tcx.dcx(), res)).unwrap();
|
||||
|
||||
if labels.is_empty() {
|
||||
labels.push(format!("{} occurred here", title.unwrap_or("error")));
|
||||
@@ -468,7 +457,7 @@ pub fn report_error<'tcx>(
|
||||
eprint!("{extra}"); // newlines are already in the string
|
||||
|
||||
if show_all_threads {
|
||||
for (thread, stack) in ecx.machine.threads.all_stacks() {
|
||||
for (thread, stack) in ecx.machine.threads.all_blocked_stacks() {
|
||||
if thread != ecx.active_thread() {
|
||||
let stacktrace = Frame::generate_stacktrace_from_stack(stack);
|
||||
let (stacktrace, was_pruned) = prune_stacktrace(stacktrace, &ecx.machine);
|
||||
|
||||
+42
-29
@@ -1,6 +1,7 @@
|
||||
//! Main evaluator loop and setting up the initial stack frame.
|
||||
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::num::NonZeroI32;
|
||||
use std::panic::{self, AssertUnwindSafe};
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
@@ -88,8 +89,6 @@ pub struct MiriConfig {
|
||||
pub preemption_rate: f64,
|
||||
/// Report the current instruction being executed every N basic blocks.
|
||||
pub report_progress: Option<u32>,
|
||||
/// Whether Stacked Borrows and Tree Borrows retagging should recurse into fields of datatypes.
|
||||
pub retag_fields: RetagFields,
|
||||
/// The location of the shared object files to load when calling external functions
|
||||
pub native_lib: Vec<PathBuf>,
|
||||
/// Whether to enable the new native lib tracing system.
|
||||
@@ -147,7 +146,6 @@ fn default() -> MiriConfig {
|
||||
mute_stdout_stderr: false,
|
||||
preemption_rate: 0.01, // 1%
|
||||
report_progress: None,
|
||||
retag_fields: RetagFields::Yes,
|
||||
native_lib: vec![],
|
||||
native_lib_enable_tracing: false,
|
||||
gc_interval: 10_000,
|
||||
@@ -462,7 +460,7 @@ pub fn eval_entry<'tcx>(
|
||||
entry_type: MiriEntryFnType,
|
||||
config: &MiriConfig,
|
||||
genmc_ctx: Option<Rc<GenmcCtx>>,
|
||||
) -> Option<i32> {
|
||||
) -> Result<(), NonZeroI32> {
|
||||
// Copy setting before we move `config`.
|
||||
let ignore_leaks = config.ignore_leaks;
|
||||
|
||||
@@ -482,35 +480,50 @@ pub fn eval_entry<'tcx>(
|
||||
ecx.handle_ice();
|
||||
panic::resume_unwind(panic_payload)
|
||||
});
|
||||
// `Ok` can never happen; the interpreter loop always exits with an "error"
|
||||
// (but that "error" might be just "regular program termination").
|
||||
let Err(err) = res.report_err();
|
||||
// Obtain the result of the execution. This is always an `Err`, but that doesn't necessarily
|
||||
// indicate an error.
|
||||
let Err(res) = res.report_err();
|
||||
|
||||
// Show diagnostic, if any.
|
||||
let (return_code, leak_check) = report_error(&ecx, err)?;
|
||||
// Error reporting: if we survive all checks, we return the exit code the program gave us.
|
||||
'miri_error: {
|
||||
// Show diagnostic, if any.
|
||||
let Some((return_code, leak_check)) = report_result(&ecx, res) else {
|
||||
break 'miri_error;
|
||||
};
|
||||
|
||||
// If we get here there was no fatal error.
|
||||
|
||||
// Possibly check for memory leaks.
|
||||
if leak_check && !ignore_leaks {
|
||||
// Check for thread leaks.
|
||||
if !ecx.have_all_terminated() {
|
||||
tcx.dcx().err("the main thread terminated without waiting for all remaining threads");
|
||||
tcx.dcx().note("set `MIRIFLAGS=-Zmiri-ignore-leaks` to disable this check");
|
||||
return None;
|
||||
}
|
||||
// Check for memory leaks.
|
||||
info!("Additional static roots: {:?}", ecx.machine.static_roots);
|
||||
let leaks = ecx.take_leaked_allocations(|ecx| &ecx.machine.static_roots);
|
||||
if !leaks.is_empty() {
|
||||
report_leaks(&ecx, leaks);
|
||||
tcx.dcx().note("set `MIRIFLAGS=-Zmiri-ignore-leaks` to disable this check");
|
||||
// Ignore the provided return code - let the reported error
|
||||
// determine the return code.
|
||||
return None;
|
||||
// If we get here there was no fatal error -- yet.
|
||||
// Possibly check for memory leaks.
|
||||
if leak_check && !ignore_leaks {
|
||||
// Check for thread leaks.
|
||||
if !ecx.have_all_terminated() {
|
||||
tcx.dcx()
|
||||
.err("the main thread terminated without waiting for all remaining threads");
|
||||
tcx.dcx().note("set `MIRIFLAGS=-Zmiri-ignore-leaks` to disable this check");
|
||||
break 'miri_error;
|
||||
}
|
||||
// Check for memory leaks.
|
||||
info!("Additional static roots: {:?}", ecx.machine.static_roots);
|
||||
let leaks = ecx.take_leaked_allocations(|ecx| &ecx.machine.static_roots);
|
||||
if !leaks.is_empty() {
|
||||
report_leaks(&ecx, leaks);
|
||||
tcx.dcx().note("set `MIRIFLAGS=-Zmiri-ignore-leaks` to disable this check");
|
||||
// Ignore the provided return code - let the reported error
|
||||
// determine the return code.
|
||||
break 'miri_error;
|
||||
}
|
||||
}
|
||||
|
||||
// The interpreter has not reported an error.
|
||||
// (There could still be errors in the session if there are other interpreters.)
|
||||
return match NonZeroI32::new(return_code) {
|
||||
None => Ok(()),
|
||||
Some(return_code) => Err(return_code),
|
||||
};
|
||||
}
|
||||
Some(return_code)
|
||||
|
||||
// The interpreter reported an error.
|
||||
assert!(tcx.dcx().has_errors().is_some());
|
||||
Err(NonZeroI32::new(rustc_driver::EXIT_FAILURE).unwrap())
|
||||
}
|
||||
|
||||
/// Turns an array of arguments into a Windows command line string.
|
||||
|
||||
@@ -119,7 +119,7 @@ pub mod native_lib {
|
||||
};
|
||||
pub use crate::borrow_tracker::tree_borrows::{EvalContextExt as _, Tree};
|
||||
pub use crate::borrow_tracker::{
|
||||
BorTag, BorrowTrackerMethod, EvalContextExt as _, RetagFields, TreeBorrowsParams,
|
||||
BorTag, BorrowTrackerMethod, EvalContextExt as _, TreeBorrowsParams,
|
||||
};
|
||||
pub use crate::clock::{Instant, MonotonicClock};
|
||||
pub use crate::concurrency::cpu_affinity::MAX_CPUS;
|
||||
@@ -136,7 +136,7 @@ pub mod native_lib {
|
||||
pub use crate::data_structures::dedup_range_map::DedupRangeMap;
|
||||
pub use crate::data_structures::mono_hash_map::MonoHashMap;
|
||||
pub use crate::diagnostics::{
|
||||
EvalContextExt as _, NonHaltingDiagnostic, TerminationInfo, report_error,
|
||||
EvalContextExt as _, NonHaltingDiagnostic, TerminationInfo, report_result,
|
||||
};
|
||||
pub use crate::eval::{MiriConfig, MiriEntryFnType, create_ecx, eval_entry};
|
||||
pub use crate::helpers::{EvalContextExt as _, ToU64 as _, ToUsize as _};
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
use rustc_session::config::OomStrategy;
|
||||
use rustc_span::Symbol;
|
||||
use rustc_target::callconv::FnAbi;
|
||||
use rustc_target::spec::{Os, Arch};
|
||||
use rustc_target::spec::{Arch, Os};
|
||||
|
||||
use super::alloc::EvalContextExt as _;
|
||||
use super::backtrace::EvalContextExt as _;
|
||||
|
||||
@@ -500,9 +500,8 @@ fn handle_segfault(
|
||||
capstone_disassemble(&instr, addr, cs, acc_events).expect("Failed to disassemble instruction");
|
||||
|
||||
// Move the instr ptr into the deprotection code.
|
||||
#[allow(unknown_lints)]
|
||||
#[expect(clippy::as_conversions, function_casts_as_integer)]
|
||||
new_regs.set_ip(mempr_off as usize);
|
||||
#[expect(clippy::as_conversions)]
|
||||
new_regs.set_ip(mempr_off as *const () as usize);
|
||||
// Don't mess up the stack by accident!
|
||||
new_regs.set_sp(stack_ptr);
|
||||
|
||||
@@ -553,9 +552,8 @@ fn handle_segfault(
|
||||
new_regs = regs_bak;
|
||||
|
||||
// Reprotect everything and continue.
|
||||
#[allow(unknown_lints)]
|
||||
#[expect(clippy::as_conversions, function_casts_as_integer)]
|
||||
new_regs.set_ip(mempr_on as usize);
|
||||
#[expect(clippy::as_conversions)]
|
||||
new_regs.set_ip(mempr_on as *const () as usize);
|
||||
new_regs.set_sp(stack_ptr);
|
||||
ptrace::setregs(pid, new_regs).unwrap();
|
||||
wait_for_signal(Some(pid), signal::SIGSTOP, InitialCont::Yes)?;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
use crate::shims::files::FileDescription;
|
||||
use crate::shims::sig::check_min_vararg_count;
|
||||
use crate::shims::unix::linux_like::epoll::EpollReadyEvents;
|
||||
use crate::shims::unix::linux_like::epoll::EpollEvents;
|
||||
use crate::shims::unix::*;
|
||||
use crate::*;
|
||||
|
||||
@@ -62,8 +62,8 @@ fn flock<'tcx>(
|
||||
throw_unsup_format!("cannot flock {}", self.name());
|
||||
}
|
||||
|
||||
/// Check the readiness of file description.
|
||||
fn get_epoll_ready_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadyEvents> {
|
||||
/// Return which epoll events are currently active.
|
||||
fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollEvents> {
|
||||
throw_unsup_format!("{}: epoll does not support this file description", self.name());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,14 +27,15 @@ pub fn is_dyn_sym(name: &str, target_os: &Os) -> bool {
|
||||
// needed at least on macOS to avoid file-based fallback in getrandom
|
||||
"getentropy" | "getrandom" => true,
|
||||
// Give specific OSes a chance to allow their symbols.
|
||||
_ => match *target_os {
|
||||
Os::Android => android::is_dyn_sym(name),
|
||||
Os::FreeBsd => freebsd::is_dyn_sym(name),
|
||||
Os::Linux => linux::is_dyn_sym(name),
|
||||
Os::MacOs => macos::is_dyn_sym(name),
|
||||
Os::Solaris | Os::Illumos => solarish::is_dyn_sym(name),
|
||||
_ => false,
|
||||
},
|
||||
_ =>
|
||||
match *target_os {
|
||||
Os::Android => android::is_dyn_sym(name),
|
||||
Os::FreeBsd => freebsd::is_dyn_sym(name),
|
||||
Os::Linux => linux::is_dyn_sym(name),
|
||||
Os::MacOs => macos::is_dyn_sym(name),
|
||||
Os::Solaris | Os::Illumos => solarish::is_dyn_sym(name),
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,7 +531,10 @@ fn emulate_foreign_item_inner(
|
||||
}
|
||||
"pipe2" => {
|
||||
// Currently this function does not exist on all Unixes, e.g. on macOS.
|
||||
this.check_target_os(&[Os::Linux, Os::FreeBsd, Os::Solaris, Os::Illumos], link_name)?;
|
||||
this.check_target_os(
|
||||
&[Os::Linux, Os::FreeBsd, Os::Solaris, Os::Illumos],
|
||||
link_name,
|
||||
)?;
|
||||
let [pipefd, flags] = this.check_shim_sig(
|
||||
shim_sig!(extern "C" fn(*mut _, i32) -> i32),
|
||||
link_name,
|
||||
|
||||
@@ -530,7 +530,8 @@ fn macos_fbsd_solarish_stat(
|
||||
) -> InterpResult<'tcx, Scalar> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
if !matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos) {
|
||||
if !matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos)
|
||||
{
|
||||
panic!("`macos_fbsd_solaris_stat` should not be called on {}", this.tcx.sess.target.os);
|
||||
}
|
||||
|
||||
@@ -560,7 +561,8 @@ fn macos_fbsd_solarish_lstat(
|
||||
) -> InterpResult<'tcx, Scalar> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
if !matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos) {
|
||||
if !matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos)
|
||||
{
|
||||
panic!(
|
||||
"`macos_fbsd_solaris_lstat` should not be called on {}",
|
||||
this.tcx.sess.target.os
|
||||
@@ -591,7 +593,8 @@ fn macos_fbsd_solarish_fstat(
|
||||
) -> InterpResult<'tcx, Scalar> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
if !matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos) {
|
||||
if !matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos)
|
||||
{
|
||||
panic!(
|
||||
"`macos_fbsd_solaris_fstat` should not be called on {}",
|
||||
this.tcx.sess.target.os
|
||||
@@ -904,7 +907,8 @@ fn opendir(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
|
||||
fn readdir64(&mut self, dirent_type: &str, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
if !matches!(&this.tcx.sess.target.os, Os::Linux | Os::Solaris | Os::Illumos | Os::FreeBsd) {
|
||||
if !matches!(&this.tcx.sess.target.os, Os::Linux | Os::Solaris | Os::Illumos | Os::FreeBsd)
|
||||
{
|
||||
panic!("`linux_solaris_readdir64` should not be called on {}", this.tcx.sess.target.os);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{BTreeMap, btree_map};
|
||||
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||
use std::io;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -17,19 +17,14 @@
|
||||
/// An `Epoll` file descriptor connects file handles and epoll events
|
||||
#[derive(Debug, Default)]
|
||||
struct Epoll {
|
||||
/// A map of EpollEventInterests registered under this epoll instance.
|
||||
/// Each entry is differentiated using FdId and file descriptor value.
|
||||
/// A map of EpollEventInterests registered under this epoll instance. Each entry is
|
||||
/// differentiated using FdId and file descriptor value.
|
||||
interest_list: RefCell<BTreeMap<EpollEventKey, EpollEventInterest>>,
|
||||
/// A map of EpollEventInstance that will be returned when `epoll_wait` is called.
|
||||
/// Similar to interest_list, the entry is also differentiated using FdId
|
||||
/// and file descriptor value.
|
||||
/// We keep this separate from `interest_list` for two reasons: there might be many
|
||||
/// interests but only a few of them ready (so with a separate list it is more efficient
|
||||
/// to find a ready event), and having separate `RefCell` lets us mutate the `interest_list`
|
||||
/// while unblocking threads which might mutate the `ready_list`.
|
||||
ready_list: RefCell<BTreeMap<EpollEventKey, EpollEventInstance>>,
|
||||
/// A list of thread ids blocked on this epoll instance.
|
||||
blocked_tid: RefCell<Vec<ThreadId>>,
|
||||
/// The subset of interests that is currently considered "ready". Stored separately so we
|
||||
/// can access it more efficiently.
|
||||
ready_set: RefCell<BTreeSet<EpollEventKey>>,
|
||||
/// The queue of threads blocked on this epoll instance.
|
||||
queue: RefCell<VecDeque<ThreadId>>,
|
||||
}
|
||||
|
||||
impl VisitProvenance for Epoll {
|
||||
@@ -43,33 +38,16 @@ fn range_for_id(id: FdId) -> std::ops::RangeInclusive<EpollEventKey> {
|
||||
(id, 0)..=(id, i32::MAX)
|
||||
}
|
||||
|
||||
/// EpollEventInstance contains information that will be returned by epoll_wait.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct EpollEventInstance {
|
||||
/// Bitmask of event types that happened to the file description.
|
||||
events: u32,
|
||||
/// User-defined data associated with the interest that triggered this instance.
|
||||
data: u64,
|
||||
/// The release clock associated with this event.
|
||||
clock: VClock,
|
||||
}
|
||||
|
||||
/// EpollEventInterest registers the file description information to an epoll
|
||||
/// instance during a successful `epoll_ctl` call. It also stores additional
|
||||
/// information needed to check and update readiness state for `epoll_wait`.
|
||||
///
|
||||
/// `events` and `data` field matches the `epoll_event` struct defined
|
||||
/// by the epoll_ctl man page. For more information
|
||||
/// see the man page:
|
||||
///
|
||||
/// <https://man7.org/linux/man-pages/man2/epoll_ctl.2.html>
|
||||
/// Tracks the events that this epoll is interested in for a given file descriptor.
|
||||
#[derive(Debug)]
|
||||
pub struct EpollEventInterest {
|
||||
/// The events bitmask retrieved from `epoll_event`.
|
||||
events: u32,
|
||||
/// The way the events looked last time we checked (for edge trigger / ET detection).
|
||||
prev_events: u32,
|
||||
/// The data retrieved from `epoll_event`.
|
||||
/// The events bitmask the epoll is interested in.
|
||||
relevant_events: u32,
|
||||
/// The currently active events for this file descriptor.
|
||||
active_events: u32,
|
||||
/// The vector clock for wakeups.
|
||||
clock: VClock,
|
||||
/// User-defined data associated with this interest.
|
||||
/// libc's data field in epoll_event can store integer or pointer,
|
||||
/// but only u64 is supported for now.
|
||||
/// <https://man7.org/linux/man-pages/man3/epoll_event.3type.html>
|
||||
@@ -78,7 +56,7 @@ pub struct EpollEventInterest {
|
||||
|
||||
/// EpollReadyEvents reflects the readiness of a file description.
|
||||
#[derive(Debug)]
|
||||
pub struct EpollReadyEvents {
|
||||
pub struct EpollEvents {
|
||||
/// The associated file is available for read(2) operations, in the sense that a read will not block.
|
||||
/// (I.e., returning EOF is considered "ready".)
|
||||
pub epollin: bool,
|
||||
@@ -97,9 +75,9 @@ pub struct EpollReadyEvents {
|
||||
pub epollerr: bool,
|
||||
}
|
||||
|
||||
impl EpollReadyEvents {
|
||||
impl EpollEvents {
|
||||
pub fn new() -> Self {
|
||||
EpollReadyEvents {
|
||||
EpollEvents {
|
||||
epollin: false,
|
||||
epollout: false,
|
||||
epollrdhup: false,
|
||||
@@ -197,18 +175,17 @@ pub fn remove_epolls(&mut self, id: FdId) {
|
||||
if let Some(epolls) = self.0.remove(&id) {
|
||||
for epoll in epolls.iter().filter_map(|(_id, epoll)| epoll.upgrade()) {
|
||||
// This is a still-live epoll with interest in this FD. Remove all
|
||||
// relevent interests.
|
||||
// relevent interests (including from the ready set).
|
||||
epoll
|
||||
.interest_list
|
||||
.borrow_mut()
|
||||
.extract_if(range_for_id(id), |_, _| true)
|
||||
// Consume the iterator.
|
||||
.for_each(|_| ());
|
||||
// Also remove all events from the ready list that refer to this FD.
|
||||
epoll
|
||||
.ready_list
|
||||
.ready_set
|
||||
.borrow_mut()
|
||||
.extract_if(range_for_id(id), |_, _| true)
|
||||
.extract_if(range_for_id(id), |_| true)
|
||||
// Consume the iterator.
|
||||
.for_each(|_| ());
|
||||
}
|
||||
@@ -344,57 +321,60 @@ fn epoll_ctl(
|
||||
// Add new interest to list. Experiments show that we need to reset all state
|
||||
// on `EPOLL_CTL_MOD`, including the edge tracking.
|
||||
let epoll_key = (id, fd);
|
||||
let new_interest = EpollEventInterest { events, data, prev_events: 0 };
|
||||
let new_interest = if op == epoll_ctl_add {
|
||||
if op == epoll_ctl_add {
|
||||
if interest_list.range(range_for_id(id)).next().is_none() {
|
||||
// This is the first time this FD got added to this epoll.
|
||||
// Remember that in the global list so we get notified about FD events.
|
||||
this.machine.epoll_interests.insert(id, &epfd);
|
||||
}
|
||||
match interest_list.entry(epoll_key) {
|
||||
btree_map::Entry::Occupied(_) => {
|
||||
// We already had interest in this.
|
||||
return this.set_last_error_and_return_i32(LibcError("EEXIST"));
|
||||
}
|
||||
btree_map::Entry::Vacant(e) => e.insert(new_interest),
|
||||
let new_interest = EpollEventInterest {
|
||||
relevant_events: events,
|
||||
data,
|
||||
active_events: 0,
|
||||
clock: VClock::default(),
|
||||
};
|
||||
if interest_list.try_insert(epoll_key, new_interest).is_err() {
|
||||
// We already had interest in this.
|
||||
return this.set_last_error_and_return_i32(LibcError("EEXIST"));
|
||||
}
|
||||
} else {
|
||||
// Modify the existing interest.
|
||||
let Some(interest) = interest_list.get_mut(&epoll_key) else {
|
||||
return this.set_last_error_and_return_i32(LibcError("ENOENT"));
|
||||
};
|
||||
*interest = new_interest;
|
||||
interest
|
||||
};
|
||||
interest.relevant_events = events;
|
||||
interest.data = data;
|
||||
}
|
||||
|
||||
// Deliver events for the new interest.
|
||||
let force_edge = true; // makes no difference since we reset `prev_events`
|
||||
send_ready_events_to_interests(
|
||||
update_readiness(
|
||||
this,
|
||||
&epfd,
|
||||
fd_ref.as_unix(this).get_epoll_ready_events()?.get_event_bitmask(this),
|
||||
force_edge,
|
||||
std::iter::once((&epoll_key, new_interest)),
|
||||
fd_ref.as_unix(this).epoll_active_events()?.get_event_bitmask(this),
|
||||
/* force_edge */ true,
|
||||
move |callback| {
|
||||
// Need to release the RefCell when this closure returns, so we have to move
|
||||
// it into the closure, so we have to do a re-lookup here.
|
||||
callback(epoll_key, interest_list.get_mut(&epoll_key).unwrap())
|
||||
},
|
||||
)?;
|
||||
|
||||
interp_ok(Scalar::from_i32(0))
|
||||
} else if op == epoll_ctl_del {
|
||||
let epoll_key = (id, fd);
|
||||
|
||||
// Remove epoll_event_interest from interest_list.
|
||||
// Remove epoll_event_interest from interest_list and ready_set.
|
||||
if interest_list.remove(&epoll_key).is_none() {
|
||||
// We did not have interest in this.
|
||||
return this.set_last_error_and_return_i32(LibcError("ENOENT"));
|
||||
};
|
||||
epfd.ready_set.borrow_mut().remove(&epoll_key);
|
||||
// If this was the last interest in this FD, remove us from the global list
|
||||
// of who is interested in this FD.
|
||||
if interest_list.range(range_for_id(id)).next().is_none() {
|
||||
this.machine.epoll_interests.remove(id, epfd.id());
|
||||
}
|
||||
|
||||
// Remove related event instance from ready list.
|
||||
epfd.ready_list.borrow_mut().remove(&epoll_key);
|
||||
|
||||
interp_ok(Scalar::from_i32(0))
|
||||
} else {
|
||||
throw_unsup_format!("unsupported epoll_ctl operation: {op}");
|
||||
@@ -466,10 +446,8 @@ fn epoll_wait(
|
||||
return this.set_last_error_and_return(LibcError("EBADF"), dest);
|
||||
};
|
||||
|
||||
// We just need to know if the ready list is empty and borrow the thread_ids out.
|
||||
let ready_list_empty = epfd.ready_list.borrow().is_empty();
|
||||
if timeout == 0 || !ready_list_empty {
|
||||
// If the ready list is not empty, or the timeout is 0, we can return immediately.
|
||||
if timeout == 0 || !epfd.ready_set.borrow().is_empty() {
|
||||
// If the timeout is 0 or there is a ready event, we can return immediately.
|
||||
return_ready_list(&epfd, dest, &event, this)?;
|
||||
} else {
|
||||
// Blocking
|
||||
@@ -486,7 +464,7 @@ fn epoll_wait(
|
||||
}
|
||||
};
|
||||
// Record this thread as blocked.
|
||||
epfd.blocked_tid.borrow_mut().push(this.active_thread());
|
||||
epfd.queue.borrow_mut().push_back(this.active_thread());
|
||||
// And block it.
|
||||
let dest = dest.clone();
|
||||
// We keep a strong ref to the underlying `Epoll` to make sure it sticks around.
|
||||
@@ -504,13 +482,14 @@ fn epoll_wait(
|
||||
|this, unblock: UnblockKind| {
|
||||
match unblock {
|
||||
UnblockKind::Ready => {
|
||||
return_ready_list(&epfd, &dest, &event, this)?;
|
||||
let events = return_ready_list(&epfd, &dest, &event, this)?;
|
||||
assert!(events > 0, "we got woken up with no events to deliver");
|
||||
interp_ok(())
|
||||
},
|
||||
UnblockKind::TimedOut => {
|
||||
// Remove the current active thread_id from the blocked thread_id list.
|
||||
epfd
|
||||
.blocked_tid.borrow_mut()
|
||||
.queue.borrow_mut()
|
||||
.retain(|&id| id != this.active_thread());
|
||||
this.write_int(0, &dest)?;
|
||||
interp_ok(())
|
||||
@@ -523,13 +502,13 @@ fn epoll_wait(
|
||||
interp_ok(())
|
||||
}
|
||||
|
||||
/// For a specific file description, get its ready events and send it to everyone who registered
|
||||
/// interest in this FD. This function should be called whenever the result of
|
||||
/// `get_epoll_ready_events` would change.
|
||||
/// For a specific file description, get its currently active events and send it to everyone who
|
||||
/// registered interest in this FD. This function must be called whenever the result of
|
||||
/// `epoll_active_events` might change.
|
||||
///
|
||||
/// If `force_edge` is set, edge-triggered interests will be triggered even if the set of
|
||||
/// ready events did not change. This can lead to spurious wakeups. Use with caution!
|
||||
fn epoll_send_fd_ready_events(
|
||||
fn update_epoll_active_events(
|
||||
&mut self,
|
||||
fd_ref: DynFileDescriptionRef,
|
||||
force_edge: bool,
|
||||
@@ -537,7 +516,7 @@ fn epoll_send_fd_ready_events(
|
||||
let this = self.eval_context_mut();
|
||||
let id = fd_ref.id();
|
||||
// Figure out who is interested in this. We need to clone this list since we can't prove
|
||||
// that `send_ready_events_to_interest` won't mutate it.
|
||||
// that `send_active_events_to_interest` won't mutate it.
|
||||
let Some(epolls) = this.machine.epoll_interests.get_epolls(id) else {
|
||||
return interp_ok(());
|
||||
};
|
||||
@@ -547,72 +526,61 @@ fn epoll_send_fd_ready_events(
|
||||
.expect("someone forgot to remove the garbage from `machine.epoll_interests`")
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let event_bitmask = fd_ref.as_unix(this).get_epoll_ready_events()?.get_event_bitmask(this);
|
||||
let active_events = fd_ref.as_unix(this).epoll_active_events()?.get_event_bitmask(this);
|
||||
for epoll in epolls {
|
||||
send_ready_events_to_interests(
|
||||
this,
|
||||
&epoll,
|
||||
event_bitmask,
|
||||
force_edge,
|
||||
epoll.interest_list.borrow_mut().range_mut(range_for_id(id)),
|
||||
)?;
|
||||
update_readiness(this, &epoll, active_events, force_edge, |callback| {
|
||||
for (&key, interest) in epoll.interest_list.borrow_mut().range_mut(range_for_id(id))
|
||||
{
|
||||
callback(key, interest)?;
|
||||
}
|
||||
interp_ok(())
|
||||
})?;
|
||||
}
|
||||
|
||||
interp_ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Send the latest ready events for one particular FD (identified by `event_key`) to everyone in
|
||||
/// the `interests` list, if they are interested in this kind of event.
|
||||
fn send_ready_events_to_interests<'tcx, 'a>(
|
||||
/// Call this when the interests denoted by `for_each_interest` have their active event set changed
|
||||
/// to `active_events`. The list is provided indirectly via the `for_each_interest` closure, which
|
||||
/// will call its argument closure for each relevant interest.
|
||||
///
|
||||
/// Any `RefCell` should be released by the time `for_each_interest` returns since we will then
|
||||
/// be waking up threads which might require access to those `RefCell`.
|
||||
fn update_readiness<'tcx>(
|
||||
ecx: &mut MiriInterpCx<'tcx>,
|
||||
epoll: &Epoll,
|
||||
event_bitmask: u32,
|
||||
active_events: u32,
|
||||
force_edge: bool,
|
||||
interests: impl Iterator<Item = (&'a EpollEventKey, &'a mut EpollEventInterest)>,
|
||||
for_each_interest: impl FnOnce(
|
||||
&mut dyn FnMut(EpollEventKey, &mut EpollEventInterest) -> InterpResult<'tcx>,
|
||||
) -> InterpResult<'tcx>,
|
||||
) -> InterpResult<'tcx> {
|
||||
let mut wakeup = false;
|
||||
for (&event_key, interest) in interests {
|
||||
let mut ready_list = epoll.ready_list.borrow_mut();
|
||||
// This checks if any of the events specified in epoll_event_interest.events
|
||||
// match those in ready_events.
|
||||
let flags = interest.events & event_bitmask;
|
||||
let prev = std::mem::replace(&mut interest.prev_events, flags);
|
||||
if flags == 0 {
|
||||
// Make sure we *remove* any previous item from the ready list, since this
|
||||
// is not ready any more.
|
||||
ready_list.remove(&event_key);
|
||||
continue;
|
||||
}
|
||||
// Generate new instance, or update existing one. It is crucial that whe we are done,
|
||||
// if an interest exists in the ready list, then it matches the latest events and data!
|
||||
let instance = match ready_list.entry(event_key) {
|
||||
btree_map::Entry::Occupied(e) => e.into_mut(),
|
||||
btree_map::Entry::Vacant(e) => {
|
||||
if !force_edge && flags == prev & flags {
|
||||
// Every bit in `flags` was already set in `prev`, and there's currently
|
||||
// no entry in the ready list for this. So there is nothing new and no
|
||||
// prior entry to update; just skip it.
|
||||
continue;
|
||||
}
|
||||
e.insert(EpollEventInstance::default())
|
||||
}
|
||||
};
|
||||
instance.events = flags;
|
||||
instance.data = interest.data;
|
||||
ecx.release_clock(|clock| {
|
||||
instance.clock.join(clock);
|
||||
})?;
|
||||
wakeup = true;
|
||||
}
|
||||
if wakeup {
|
||||
// Wake up threads that may have been waiting for events on this epoll.
|
||||
// Do this only once for all the interests.
|
||||
// Edge-triggered notification only notify one thread even if there are
|
||||
// multiple threads blocked on the same epoll.
|
||||
if let Some(thread_id) = epoll.blocked_tid.borrow_mut().pop() {
|
||||
ecx.unblock_thread(thread_id, BlockReason::Epoll)?;
|
||||
let mut ready_set = epoll.ready_set.borrow_mut();
|
||||
for_each_interest(&mut |key, interest| {
|
||||
// Update the ready events tracked in this interest.
|
||||
let new_readiness = interest.relevant_events & active_events;
|
||||
let prev_readiness = std::mem::replace(&mut interest.active_events, new_readiness);
|
||||
if new_readiness == 0 {
|
||||
// Un-trigger this, there's nothing left to report here.
|
||||
ready_set.remove(&key);
|
||||
} else if force_edge || new_readiness != prev_readiness & new_readiness {
|
||||
// Either we force an "edge" to be detected, or there's a bit set in `new`
|
||||
// that was not set in `prev`. In both cases, this is ready now.
|
||||
ready_set.insert(key);
|
||||
ecx.release_clock(|clock| {
|
||||
interest.clock.join(clock);
|
||||
})?;
|
||||
}
|
||||
interp_ok(())
|
||||
})?;
|
||||
// While there are events ready to be delivered, wake up a thread to receive them.
|
||||
while !ready_set.is_empty()
|
||||
&& let Some(thread_id) = epoll.queue.borrow_mut().pop_front()
|
||||
{
|
||||
drop(ready_set); // release the "lock" so the unblocked thread can have it
|
||||
ecx.unblock_thread(thread_id, BlockReason::Epoll)?;
|
||||
ready_set = epoll.ready_set.borrow_mut();
|
||||
}
|
||||
|
||||
interp_ok(())
|
||||
@@ -625,28 +593,40 @@ fn return_ready_list<'tcx>(
|
||||
dest: &MPlaceTy<'tcx>,
|
||||
events: &MPlaceTy<'tcx>,
|
||||
ecx: &mut MiriInterpCx<'tcx>,
|
||||
) -> InterpResult<'tcx> {
|
||||
let mut ready_list = epfd.ready_list.borrow_mut();
|
||||
) -> InterpResult<'tcx, i32> {
|
||||
let mut interest_list = epfd.interest_list.borrow_mut();
|
||||
let mut ready_set = epfd.ready_set.borrow_mut();
|
||||
let mut num_of_events: i32 = 0;
|
||||
let mut array_iter = ecx.project_array_fields(events)?;
|
||||
|
||||
while let Some(des) = array_iter.next(ecx)? {
|
||||
if let Some((_, epoll_event_instance)) = ready_list.pop_first() {
|
||||
ecx.write_int_fields_named(
|
||||
&[
|
||||
("events", epoll_event_instance.events.into()),
|
||||
("u64", epoll_event_instance.data.into()),
|
||||
],
|
||||
&des.1,
|
||||
)?;
|
||||
// Synchronize waking thread with the event of interest.
|
||||
ecx.acquire_clock(&epoll_event_instance.clock)?;
|
||||
|
||||
num_of_events = num_of_events.strict_add(1);
|
||||
} else {
|
||||
break;
|
||||
// Sanity-check to ensure that all event info is up-to-date.
|
||||
if cfg!(debug_assertions) {
|
||||
for (key, interest) in interest_list.iter() {
|
||||
// Ensure this matches the latest readiness of this FD.
|
||||
// We have to do an FD lookup by ID for this. The FdNum might be already closed.
|
||||
let fd = &ecx.machine.fds.fds.values().find(|fd| fd.id() == key.0).unwrap();
|
||||
let current_active = fd.as_unix(ecx).epoll_active_events()?.get_event_bitmask(ecx);
|
||||
assert_eq!(interest.active_events, current_active & interest.relevant_events);
|
||||
}
|
||||
}
|
||||
|
||||
// While there is a slot to store another event, and an event to store, deliver that event.
|
||||
while let Some(slot) = array_iter.next(ecx)?
|
||||
&& let Some(&key) = ready_set.first()
|
||||
{
|
||||
let interest = interest_list.get_mut(&key).expect("non-existent event in ready set");
|
||||
// Deliver event to caller.
|
||||
ecx.write_int_fields_named(
|
||||
&[("events", interest.active_events.into()), ("u64", interest.data.into())],
|
||||
&slot.1,
|
||||
)?;
|
||||
num_of_events = num_of_events.strict_add(1);
|
||||
// Synchronize receiving thread with the event of interest.
|
||||
ecx.acquire_clock(&interest.clock)?;
|
||||
// Since currently, all events are edge-triggered, we remove them from the ready set when
|
||||
// they get delivered.
|
||||
ready_set.remove(&key);
|
||||
}
|
||||
ecx.write_int(num_of_events, dest)?;
|
||||
interp_ok(())
|
||||
interp_ok(num_of_events)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
use crate::concurrency::VClock;
|
||||
use crate::shims::files::{FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef};
|
||||
use crate::shims::unix::UnixFileDescription;
|
||||
use crate::shims::unix::linux_like::epoll::{EpollReadyEvents, EvalContextExt as _};
|
||||
use crate::shims::unix::linux_like::epoll::{EpollEvents, EvalContextExt as _};
|
||||
use crate::*;
|
||||
|
||||
/// Maximum value that the eventfd counter can hold.
|
||||
@@ -107,14 +107,14 @@ fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
|
||||
}
|
||||
|
||||
impl UnixFileDescription for EventFd {
|
||||
fn get_epoll_ready_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadyEvents> {
|
||||
fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollEvents> {
|
||||
// We only check the status of EPOLLIN and EPOLLOUT flags for eventfd. If other event flags
|
||||
// need to be supported in the future, the check should be added here.
|
||||
|
||||
interp_ok(EpollReadyEvents {
|
||||
interp_ok(EpollEvents {
|
||||
epollin: self.counter.get() != 0,
|
||||
epollout: self.counter.get() != MAX_COUNTER,
|
||||
..EpollReadyEvents::new()
|
||||
..EpollEvents::new()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -220,7 +220,7 @@ fn eventfd_write<'tcx>(
|
||||
// Linux seems to cause spurious wakeups here, and Tokio seems to rely on that
|
||||
// (see <https://github.com/rust-lang/miri/pull/4676#discussion_r2510528994>
|
||||
// and also <https://www.illumos.org/issues/16700>).
|
||||
ecx.epoll_send_fd_ready_events(eventfd, /* force_edge */ true)?;
|
||||
ecx.update_epoll_active_events(eventfd, /* force_edge */ true)?;
|
||||
|
||||
// Return how many bytes we consumed from the user-provided buffer.
|
||||
return finish.call(ecx, Ok(buf_place.layout.size.bytes_usize()));
|
||||
@@ -316,7 +316,7 @@ fn eventfd_read<'tcx>(
|
||||
// The state changed; we check and update the status of all supported event
|
||||
// types for current file description.
|
||||
// Linux seems to always emit do notifications here, even if we were already writable.
|
||||
ecx.epoll_send_fd_ready_events(eventfd, /* force_edge */ true)?;
|
||||
ecx.update_epoll_active_events(eventfd, /* force_edge */ true)?;
|
||||
|
||||
// Tell userspace how many bytes we put into the buffer.
|
||||
return finish.call(ecx, Ok(buf_place.layout.size.bytes_usize()));
|
||||
|
||||
@@ -20,8 +20,14 @@
|
||||
|
||||
#[derive(Clone)]
|
||||
enum MacOsUnfairLock {
|
||||
Active { mutex_ref: MutexRef },
|
||||
PermanentlyLocked,
|
||||
Active {
|
||||
mutex_ref: MutexRef,
|
||||
},
|
||||
/// If a lock gets copied while being held, we put it in this state.
|
||||
/// It seems like in the real implementation, the lock actually remembers who held it,
|
||||
/// and still behaves as-if it was held by that thread in the new location. In Miri, we don't
|
||||
/// know who actually owns this lock at the moment.
|
||||
PermanentlyLockedByUnknown,
|
||||
}
|
||||
|
||||
impl SyncObj for MacOsUnfairLock {
|
||||
@@ -93,10 +99,12 @@ fn os_unfair_lock_get_data<'a>(
|
||||
// locks when they get released, so it got copied while locked. Unfortunately
|
||||
// that is something `std` needs to support (the guard could have been leaked).
|
||||
// On the plus side, we know nobody was queued for the lock while it got copied;
|
||||
// that would have been rejected by our `on_access`. So we behave like a
|
||||
// futex-based lock would in this case: any attempt to acquire the lock will
|
||||
// just wait forever, since there's nobody to wake us up.
|
||||
interp_ok(MacOsUnfairLock::PermanentlyLocked)
|
||||
// that would have been rejected by our `on_access`.
|
||||
// The real implementation would apparently remember who held the old lock, and
|
||||
// consider them to hold the copy as well -- but our copies don't preserve sync
|
||||
// object metadata so we instead move the lock into a "permanently locked"
|
||||
// state.
|
||||
interp_ok(MacOsUnfairLock::PermanentlyLockedByUnknown)
|
||||
} else {
|
||||
throw_ub_format!("`os_unfair_lock` was not properly initialized at this location, or it got overwritten");
|
||||
}
|
||||
@@ -303,18 +311,12 @@ fn os_unfair_lock_lock(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
let MacOsUnfairLock::Active { mutex_ref } = this.os_unfair_lock_get_data(lock_op)? else {
|
||||
// Trying to get a poisoned lock. Just block forever...
|
||||
this.block_thread(
|
||||
BlockReason::Sleep,
|
||||
None,
|
||||
callback!(
|
||||
@capture<'tcx> {}
|
||||
|_this, _unblock: UnblockKind| {
|
||||
panic!("we shouldn't wake up ever")
|
||||
}
|
||||
),
|
||||
// Trying to lock a perma-locked lock. On macOS this would block or abort depending
|
||||
// on whether the current thread is considered to be the one holding this lock. We
|
||||
// don't know who is considered to be holding the lock so we don't know what to do.
|
||||
throw_unsup_format!(
|
||||
"attempted to lock an os_unfair_lock that was copied while being locked"
|
||||
);
|
||||
return interp_ok(());
|
||||
};
|
||||
let mutex_ref = mutex_ref.clone();
|
||||
|
||||
@@ -342,15 +344,15 @@ fn os_unfair_lock_trylock(
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
let MacOsUnfairLock::Active { mutex_ref } = this.os_unfair_lock_get_data(lock_op)? else {
|
||||
// Trying to get a poisoned lock. That never works.
|
||||
// Trying to lock a perma-locked lock. That behaves the same no matter who the owner is
|
||||
// so we can implement the real behavior here.
|
||||
this.write_scalar(Scalar::from_bool(false), dest)?;
|
||||
return interp_ok(());
|
||||
};
|
||||
let mutex_ref = mutex_ref.clone();
|
||||
|
||||
if mutex_ref.owner().is_some() {
|
||||
// Contrary to the blocking lock function, this does not check for
|
||||
// reentrancy.
|
||||
// Contrary to the blocking lock function, this does not check for reentrancy.
|
||||
this.write_scalar(Scalar::from_bool(false), dest)?;
|
||||
} else {
|
||||
this.mutex_lock(&mutex_ref)?;
|
||||
@@ -364,10 +366,10 @@ fn os_unfair_lock_unlock(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<'tcx>
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
let MacOsUnfairLock::Active { mutex_ref } = this.os_unfair_lock_get_data(lock_op)? else {
|
||||
// A perma-locked lock is definitely not held by us.
|
||||
throw_machine_stop!(TerminationInfo::Abort(
|
||||
"attempted to unlock an os_unfair_lock not owned by the current thread".to_owned()
|
||||
));
|
||||
// We don't know who the owner is so we cannot proceed.
|
||||
throw_unsup_format!(
|
||||
"attempted to unlock an os_unfair_lock that was copied while being locked"
|
||||
);
|
||||
};
|
||||
let mutex_ref = mutex_ref.clone();
|
||||
|
||||
@@ -393,10 +395,10 @@ fn os_unfair_lock_assert_owner(&mut self, lock_op: &OpTy<'tcx>) -> InterpResult<
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
let MacOsUnfairLock::Active { mutex_ref } = this.os_unfair_lock_get_data(lock_op)? else {
|
||||
// A perma-locked lock is definitely not held by us.
|
||||
throw_machine_stop!(TerminationInfo::Abort(
|
||||
"called os_unfair_lock_assert_owner on an os_unfair_lock not owned by the current thread".to_owned()
|
||||
));
|
||||
// We don't know who the owner is so we cannot proceed.
|
||||
throw_unsup_format!(
|
||||
"attempted to assert the owner of an os_unfair_lock that was copied while being locked"
|
||||
);
|
||||
};
|
||||
let mutex_ref = mutex_ref.clone();
|
||||
|
||||
@@ -415,8 +417,10 @@ fn os_unfair_lock_assert_not_owner(&mut self, lock_op: &OpTy<'tcx>) -> InterpRes
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
let MacOsUnfairLock::Active { mutex_ref } = this.os_unfair_lock_get_data(lock_op)? else {
|
||||
// A perma-locked lock is definitely not held by us.
|
||||
return interp_ok(());
|
||||
// We don't know who the owner is so we cannot proceed.
|
||||
throw_unsup_format!(
|
||||
"attempted to assert the owner of an os_unfair_lock that was copied while being locked"
|
||||
);
|
||||
};
|
||||
let mutex_ref = mutex_ref.clone();
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
EvalContextExt as _, FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef,
|
||||
};
|
||||
use crate::shims::unix::UnixFileDescription;
|
||||
use crate::shims::unix::linux_like::epoll::{EpollReadyEvents, EvalContextExt as _};
|
||||
use crate::shims::unix::linux_like::epoll::{EpollEvents, EvalContextExt as _};
|
||||
use crate::*;
|
||||
|
||||
/// The maximum capacity of the socketpair buffer in bytes.
|
||||
@@ -99,7 +99,7 @@ fn destroy<'tcx>(
|
||||
}
|
||||
}
|
||||
// Notify peer fd that close has happened, since that can unblock reads and writes.
|
||||
ecx.epoll_send_fd_ready_events(peer_fd, /* force_edge */ false)?;
|
||||
ecx.update_epoll_active_events(peer_fd, /* force_edge */ false)?;
|
||||
}
|
||||
interp_ok(Ok(()))
|
||||
}
|
||||
@@ -280,8 +280,8 @@ fn anonsocket_write<'tcx>(
|
||||
// Notify epoll waiters: we might be no longer writable, peer might now be readable.
|
||||
// The notification to the peer seems to be always sent on Linux, even if the
|
||||
// FD was readable before.
|
||||
ecx.epoll_send_fd_ready_events(self_ref, /* force_edge */ false)?;
|
||||
ecx.epoll_send_fd_ready_events(peer_fd, /* force_edge */ true)?;
|
||||
ecx.update_epoll_active_events(self_ref, /* force_edge */ false)?;
|
||||
ecx.update_epoll_active_events(peer_fd, /* force_edge */ true)?;
|
||||
|
||||
return finish.call(ecx, Ok(write_size));
|
||||
}
|
||||
@@ -378,10 +378,10 @@ fn anonsocket_read<'tcx>(
|
||||
// Linux seems to always notify the peer if the read buffer is now empty.
|
||||
// (Linux also does that if this was a "big" read, but to avoid some arbitrary
|
||||
// threshold, we do not match that.)
|
||||
ecx.epoll_send_fd_ready_events(peer_fd, /* force_edge */ readbuf_now_empty)?;
|
||||
ecx.update_epoll_active_events(peer_fd, /* force_edge */ readbuf_now_empty)?;
|
||||
};
|
||||
// Notify epoll waiters: we might be no longer readable.
|
||||
ecx.epoll_send_fd_ready_events(self_ref, /* force_edge */ false)?;
|
||||
ecx.update_epoll_active_events(self_ref, /* force_edge */ false)?;
|
||||
|
||||
return finish.call(ecx, Ok(read_size));
|
||||
}
|
||||
@@ -389,11 +389,11 @@ fn anonsocket_read<'tcx>(
|
||||
}
|
||||
|
||||
impl UnixFileDescription for AnonSocket {
|
||||
fn get_epoll_ready_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadyEvents> {
|
||||
fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollEvents> {
|
||||
// We only check the status of EPOLLIN, EPOLLOUT, EPOLLHUP and EPOLLRDHUP flags.
|
||||
// If other event flags need to be supported in the future, the check should be added here.
|
||||
|
||||
let mut epoll_ready_events = EpollReadyEvents::new();
|
||||
let mut epoll_ready_events = EpollEvents::new();
|
||||
|
||||
// Check if it is readable.
|
||||
if let Some(readbuf) = &self.readbuf {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
use super::{
|
||||
ShiftOp, horizontal_bin_op, mask_load, mask_store, mpsadbw, packssdw, packsswb, packusdw,
|
||||
packuswb, pmulhrsw, psign, shift_simd_by_scalar, shift_simd_by_simd,
|
||||
packuswb, pmulhrsw, psadbw, psign, shift_simd_by_scalar, shift_simd_by_simd,
|
||||
};
|
||||
use crate::*;
|
||||
|
||||
@@ -241,41 +241,11 @@ fn emulate_x86_avx2_intrinsic(
|
||||
}
|
||||
}
|
||||
// Used to implement the _mm256_sad_epu8 function.
|
||||
// Compute the absolute differences of packed unsigned 8-bit integers
|
||||
// in `left` and `right`, then horizontally sum each consecutive 8
|
||||
// differences to produce four unsigned 16-bit integers, and pack
|
||||
// these unsigned 16-bit integers in the low 16 bits of 64-bit elements
|
||||
// in `dest`.
|
||||
// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_sad_epu8
|
||||
"psad.bw" => {
|
||||
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.strict_mul(8));
|
||||
|
||||
for i in 0..dest_len {
|
||||
let dest = this.project_index(&dest, i)?;
|
||||
|
||||
let mut acc: u16 = 0;
|
||||
for j in 0..8 {
|
||||
let src_index = i.strict_mul(8).strict_add(j);
|
||||
|
||||
let left = this.project_index(&left, src_index)?;
|
||||
let left = this.read_scalar(&left)?.to_u8()?;
|
||||
|
||||
let right = this.project_index(&right, src_index)?;
|
||||
let right = this.read_scalar(&right)?.to_u8()?;
|
||||
|
||||
acc = acc.strict_add(left.abs_diff(right).into());
|
||||
}
|
||||
|
||||
this.write_scalar(Scalar::from_u64(acc.into()), &dest)?;
|
||||
}
|
||||
psadbw(this, left, right, dest)?
|
||||
}
|
||||
// Used to implement the _mm256_shuffle_epi8 intrinsic.
|
||||
// Shuffles bytes from `left` using `right` as pattern.
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use rustc_span::Symbol;
|
||||
use rustc_target::callconv::FnAbi;
|
||||
|
||||
use super::psadbw;
|
||||
use crate::*;
|
||||
|
||||
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
|
||||
@@ -78,6 +79,15 @@ fn emulate_x86_avx512_intrinsic(
|
||||
this.write_scalar(Scalar::from_u32(r), &d_lane)?;
|
||||
}
|
||||
}
|
||||
// Used to implement the _mm512_sad_epu8 function.
|
||||
"psad.bw.512" => {
|
||||
this.expect_target_feature_for_intrinsic(link_name, "avx512bw")?;
|
||||
|
||||
let [left, right] =
|
||||
this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
|
||||
|
||||
psadbw(this, left, right, dest)?
|
||||
}
|
||||
_ => return interp_ok(EmulateItemResult::NotSupported),
|
||||
}
|
||||
interp_ok(EmulateItemResult::NeedsReturn)
|
||||
|
||||
@@ -1038,6 +1038,54 @@ fn mpsadbw<'tcx>(
|
||||
interp_ok(())
|
||||
}
|
||||
|
||||
/// Compute the absolute differences of packed unsigned 8-bit integers
|
||||
/// in `left` and `right`, then horizontally sum each consecutive 8
|
||||
/// differences to produce unsigned 16-bit integers, and pack
|
||||
/// these unsigned 16-bit integers in the low 16 bits of 64-bit elements
|
||||
/// in `dest`.
|
||||
///
|
||||
/// <https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sad_epu8>
|
||||
/// <https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_sad_epu8>
|
||||
/// <https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm512_sad_epu8>
|
||||
fn psadbw<'tcx>(
|
||||
ecx: &mut crate::MiriInterpCx<'tcx>,
|
||||
left: &OpTy<'tcx>,
|
||||
right: &OpTy<'tcx>,
|
||||
dest: &MPlaceTy<'tcx>,
|
||||
) -> InterpResult<'tcx, ()> {
|
||||
let (left, left_len) = ecx.project_to_simd(left)?;
|
||||
let (right, right_len) = ecx.project_to_simd(right)?;
|
||||
let (dest, dest_len) = ecx.project_to_simd(dest)?;
|
||||
|
||||
// fn psadbw(a: u8x16, b: u8x16) -> u64x2;
|
||||
// fn psadbw(a: u8x32, b: u8x32) -> u64x4;
|
||||
// fn vpsadbw(a: u8x64, b: u8x64) -> u64x8;
|
||||
assert_eq!(left_len, right_len);
|
||||
assert_eq!(left_len, left.layout.layout.size().bytes());
|
||||
assert_eq!(dest_len, left_len.strict_div(8));
|
||||
|
||||
for i in 0..dest_len {
|
||||
let dest = ecx.project_index(&dest, i)?;
|
||||
|
||||
let mut acc: u16 = 0;
|
||||
for j in 0..8 {
|
||||
let src_index = i.strict_mul(8).strict_add(j);
|
||||
|
||||
let left = ecx.project_index(&left, src_index)?;
|
||||
let left = ecx.read_scalar(&left)?.to_u8()?;
|
||||
|
||||
let right = ecx.project_index(&right, src_index)?;
|
||||
let right = ecx.read_scalar(&right)?.to_u8()?;
|
||||
|
||||
acc = acc.strict_add(left.abs_diff(right).into());
|
||||
}
|
||||
|
||||
ecx.write_scalar(Scalar::from_u64(acc.into()), &dest)?;
|
||||
}
|
||||
|
||||
interp_ok(())
|
||||
}
|
||||
|
||||
/// Multiplies packed 16-bit signed integer values, truncates the 32-bit
|
||||
/// product to the 18 most significant bits by right-shifting, and then
|
||||
/// divides the 18-bit value by 2 (rounding to nearest) by first adding
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
use super::{
|
||||
FloatBinOp, ShiftOp, bin_op_simd_float_all, bin_op_simd_float_first, convert_float_to_int,
|
||||
packssdw, packsswb, packuswb, shift_simd_by_scalar,
|
||||
packssdw, packsswb, packuswb, psadbw, shift_simd_by_scalar,
|
||||
};
|
||||
use crate::*;
|
||||
|
||||
@@ -37,41 +37,11 @@ fn emulate_x86_sse2_intrinsic(
|
||||
// vectors.
|
||||
match unprefixed_name {
|
||||
// Used to implement the _mm_sad_epu8 function.
|
||||
// Computes the absolute differences of packed unsigned 8-bit integers in `a`
|
||||
// and `b`, then horizontally sum each consecutive 8 differences to produce
|
||||
// two unsigned 16-bit integers, and pack these unsigned 16-bit integers in
|
||||
// the low 16 bits of 64-bit elements returned.
|
||||
//
|
||||
// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sad_epu8
|
||||
"psad.bw" => {
|
||||
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)?;
|
||||
|
||||
// left and right are u8x16, dest is u64x2
|
||||
assert_eq!(left_len, right_len);
|
||||
assert_eq!(left_len, 16);
|
||||
assert_eq!(dest_len, 2);
|
||||
|
||||
for i in 0..dest_len {
|
||||
let dest = this.project_index(&dest, i)?;
|
||||
|
||||
let mut res: u16 = 0;
|
||||
let n = left_len.strict_div(dest_len);
|
||||
for j in 0..n {
|
||||
let op_i = j.strict_add(i.strict_mul(n));
|
||||
let left = this.read_scalar(&this.project_index(&left, op_i)?)?.to_u8()?;
|
||||
let right =
|
||||
this.read_scalar(&this.project_index(&right, op_i)?)?.to_u8()?;
|
||||
|
||||
res = res.strict_add(left.abs_diff(right).into());
|
||||
}
|
||||
|
||||
this.write_scalar(Scalar::from_u64(res.into()), &dest)?;
|
||||
}
|
||||
psadbw(this, left, right, dest)?
|
||||
}
|
||||
// Used to implement the _mm_{sll,srl,sra}_epi{16,32,64} functions
|
||||
// (except _mm_sra_epi64, which is not available in SSE2).
|
||||
|
||||
@@ -9,5 +9,5 @@ fn main() {
|
||||
let lock = lock;
|
||||
// This needs to either error or deadlock.
|
||||
unsafe { libc::os_unfair_lock_lock(lock.get()) };
|
||||
//~^ error: deadlock
|
||||
//~^ error: lock an os_unfair_lock that was copied while being locked
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
error: the evaluated program deadlocked
|
||||
error: unsupported operation: attempted to lock an os_unfair_lock that was copied while being locked
|
||||
--> tests/fail-dep/concurrency/apple_os_unfair_lock_move_deadlock.rs:LL:CC
|
||||
|
|
||||
LL | unsafe { libc::os_unfair_lock_lock(lock.get()) };
|
||||
| ^ this thread got stuck here
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unsupported operation occurred here
|
||||
|
|
||||
= help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
//@only-target: linux android illumos
|
||||
//~^ERROR: deadlocked
|
||||
//~^^ERROR: deadlocked
|
||||
//@compile-flags: -Zmiri-deterministic-concurrency
|
||||
//@error-in-other-file: deadlock
|
||||
|
||||
|
||||
@@ -14,24 +14,13 @@ note: inside `main`
|
||||
LL | thread2.join().unwrap();
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: the evaluated program deadlocked
|
||||
|
|
||||
= note: this thread got stuck here
|
||||
= note: (no span available)
|
||||
|
||||
error: the evaluated program deadlocked
|
||||
--> tests/fail-dep/libc/eventfd_block_read_twice.rs:LL:CC
|
||||
|
|
||||
LL | let res: i64 = unsafe { libc::read(fd, buf.as_mut_ptr().cast(), 8).try_into().unwrap() };
|
||||
| ^ this thread got stuck here
|
||||
|
||||
error: the evaluated program deadlocked
|
||||
|
|
||||
= note: this thread got stuck here
|
||||
= note: (no span available)
|
||||
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
//@only-target: linux android illumos
|
||||
//~^ERROR: deadlocked
|
||||
//~^^ERROR: deadlocked
|
||||
//@compile-flags: -Zmiri-deterministic-concurrency
|
||||
//@error-in-other-file: deadlock
|
||||
|
||||
@@ -38,7 +36,7 @@ fn main() {
|
||||
|
||||
let thread2 = thread::spawn(move || {
|
||||
let sized_8_data = (u64::MAX - 1).to_ne_bytes();
|
||||
// Write u64::MAX - 1, so the all subsequent write will block.
|
||||
// Write u64::MAX - 1, so that all subsequent writes will block.
|
||||
let res: i64 = unsafe {
|
||||
// This `write` will initially blocked, then get unblocked by thread3, then get blocked again
|
||||
// because the `write` in thread1 executes first.
|
||||
|
||||
@@ -14,24 +14,13 @@ note: inside `main`
|
||||
LL | thread2.join().unwrap();
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: the evaluated program deadlocked
|
||||
|
|
||||
= note: this thread got stuck here
|
||||
= note: (no span available)
|
||||
|
||||
error: the evaluated program deadlocked
|
||||
--> tests/fail-dep/libc/eventfd_block_write_twice.rs:LL:CC
|
||||
|
|
||||
LL | libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap()
|
||||
| ^ this thread got stuck here
|
||||
|
||||
error: the evaluated program deadlocked
|
||||
|
|
||||
= note: this thread got stuck here
|
||||
= note: (no span available)
|
||||
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
//@compile-flags: -Zmiri-deterministic-concurrency
|
||||
//~^ERROR: deadlocked
|
||||
//~^^ERROR: deadlocked
|
||||
//@only-target: linux android illumos
|
||||
//@error-in-other-file: deadlock
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::thread::spawn;
|
||||
use std::thread;
|
||||
|
||||
#[path = "../../utils/libc.rs"]
|
||||
mod libc_utils;
|
||||
|
||||
// Using `as` cast since `EPOLLET` wraps around
|
||||
const EPOLL_IN_OUT_ET: u32 = (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET) as _;
|
||||
@@ -49,39 +50,37 @@ fn main() {
|
||||
let epfd = unsafe { libc::epoll_create1(0) };
|
||||
assert_ne!(epfd, -1);
|
||||
|
||||
// Create a socketpair instance.
|
||||
let mut fds = [-1, -1];
|
||||
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
|
||||
// Create an eventfd instance.
|
||||
let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC;
|
||||
let fd1 = unsafe { libc::eventfd(0, flags) };
|
||||
// Make a duplicate so that we have two file descriptors for the same file description.
|
||||
let fd2 = unsafe { libc::dup(fd1) };
|
||||
|
||||
// Register both with epoll.
|
||||
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fd1 as u64 };
|
||||
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd1, &mut ev) };
|
||||
assert_eq!(res, 0);
|
||||
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fd2 as u64 };
|
||||
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd2, &mut ev) };
|
||||
assert_eq!(res, 0);
|
||||
|
||||
// Register one side of the socketpair with epoll.
|
||||
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fds[0] as u64 };
|
||||
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[0], &mut ev) };
|
||||
assert_eq!(res, 0);
|
||||
// Consume the initial events.
|
||||
let expected = [(libc::EPOLLOUT as u32, fd1 as u64), (libc::EPOLLOUT as u32, fd2 as u64)];
|
||||
check_epoll_wait::<8>(epfd, &expected, -1);
|
||||
|
||||
// epoll_wait to clear notification.
|
||||
let expected_event = u32::try_from(libc::EPOLLOUT).unwrap();
|
||||
let expected_value = fds[0] as u64;
|
||||
check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)], 0);
|
||||
|
||||
let expected_event = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap();
|
||||
let expected_value = fds[0] as u64;
|
||||
let thread1 = spawn(move || {
|
||||
check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)], -1);
|
||||
let thread1 = thread::spawn(move || {
|
||||
check_epoll_wait::<2>(epfd, &expected, -1);
|
||||
});
|
||||
let thread2 = thread::spawn(move || {
|
||||
check_epoll_wait::<2>(epfd, &expected, -1);
|
||||
//~^ERROR: deadlocked
|
||||
});
|
||||
let thread2 = spawn(move || {
|
||||
check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)], -1);
|
||||
});
|
||||
// Yield so the threads are both blocked.
|
||||
thread::yield_now();
|
||||
|
||||
let thread3 = spawn(move || {
|
||||
// Just a single write, so we only wake up one of them.
|
||||
let data = "abcde".as_bytes().as_ptr();
|
||||
let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
|
||||
assert!(res > 0 && res <= 5);
|
||||
});
|
||||
// Create two events at once.
|
||||
libc_utils::write_all_from_slice(fd1, &0_u64.to_ne_bytes()).unwrap();
|
||||
|
||||
thread1.join().unwrap();
|
||||
thread2.join().unwrap();
|
||||
thread3.join().unwrap();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
error: the evaluated program deadlocked
|
||||
|
|
||||
= note: this thread got stuck here
|
||||
= note: (no span available)
|
||||
|
||||
error: the evaluated program deadlocked
|
||||
--> RUSTLIB/std/src/sys/thread/PLATFORM.rs:LL:CC
|
||||
|
|
||||
@@ -16,22 +11,16 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) };
|
||||
note: inside `main`
|
||||
--> tests/fail-dep/libc/libc_epoll_block_two_thread.rs:LL:CC
|
||||
|
|
||||
LL | thread1.join().unwrap();
|
||||
LL | thread2.join().unwrap();
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: the evaluated program deadlocked
|
||||
--> tests/fail-dep/libc/libc_epoll_block_two_thread.rs:LL:CC
|
||||
|
|
||||
LL | check_epoll_wait::<TAG>(epfd, &[(expected_event, expected_value)], -1);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this thread got stuck here
|
||||
|
||||
error: the evaluated program deadlocked
|
||||
|
|
||||
= note: this thread got stuck here
|
||||
= note: (no span available)
|
||||
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
|
||||
LL | check_epoll_wait::<TAG>(epfd, &expected, -1);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this thread got stuck here
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//! This is a regression test for <https://github.com/rust-lang/miri/issues/3947>: we had some
|
||||
//! faulty logic around `release_clock` that led to this code not reporting a data race.
|
||||
//~^^ERROR: deadlock
|
||||
//@ignore-target: windows # no libc socketpair on Windows
|
||||
//@compile-flags: -Zmiri-deterministic-concurrency
|
||||
//@error-in-other-file: deadlock
|
||||
|
||||
@@ -20,12 +20,7 @@ error: the evaluated program deadlocked
|
||||
LL | libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
|
||||
| ^ this thread got stuck here
|
||||
|
||||
error: the evaluated program deadlocked
|
||||
|
|
||||
= note: this thread got stuck here
|
||||
= note: (no span available)
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
//@ignore-target: windows # No libc socketpair on Windows
|
||||
//~^ERROR: deadlocked
|
||||
//~^^ERROR: deadlocked
|
||||
// test_race depends on a deterministic schedule.
|
||||
//@compile-flags: -Zmiri-deterministic-concurrency
|
||||
//@error-in-other-file: deadlock
|
||||
|
||||
@@ -14,24 +14,13 @@ note: inside `main`
|
||||
LL | thread2.join().unwrap();
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: the evaluated program deadlocked
|
||||
|
|
||||
= note: this thread got stuck here
|
||||
= note: (no span available)
|
||||
|
||||
error: the evaluated program deadlocked
|
||||
--> tests/fail-dep/libc/socketpair_block_read_twice.rs:LL:CC
|
||||
|
|
||||
LL | libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
|
||||
| ^ this thread got stuck here
|
||||
|
||||
error: the evaluated program deadlocked
|
||||
|
|
||||
= note: this thread got stuck here
|
||||
= note: (no span available)
|
||||
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
//@ignore-target: windows # No libc socketpair on Windows
|
||||
//~^ERROR: deadlocked
|
||||
//~^^ERROR: deadlocked
|
||||
// test_race depends on a deterministic schedule.
|
||||
//@compile-flags: -Zmiri-deterministic-concurrency
|
||||
//@error-in-other-file: deadlock
|
||||
|
||||
@@ -14,24 +14,13 @@ note: inside `main`
|
||||
LL | thread2.join().unwrap();
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: the evaluated program deadlocked
|
||||
|
|
||||
= note: this thread got stuck here
|
||||
= note: (no span available)
|
||||
|
||||
error: the evaluated program deadlocked
|
||||
--> tests/fail-dep/libc/socketpair_block_write_twice.rs:LL:CC
|
||||
|
|
||||
LL | let res = unsafe { libc::write(fds[0], data.as_ptr() as *const libc::c_void, data.len()) };
|
||||
| ^ this thread got stuck here
|
||||
|
||||
error: the evaluated program deadlocked
|
||||
|
|
||||
= note: this thread got stuck here
|
||||
= note: (no span available)
|
||||
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//@error-in-other-file: miri cannot be run on programs that fail compilation
|
||||
|
||||
#![deny(warnings, unused)]
|
||||
|
||||
struct Foo;
|
||||
|
||||
@@ -11,7 +11,5 @@ LL | #![deny(warnings, unused)]
|
||||
| ^^^^^^
|
||||
= note: `#[deny(dead_code)]` implied by `#[deny(unused)]`
|
||||
|
||||
error: miri cannot be run on programs that fail compilation
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::thread;
|
||||
use std::thread::spawn;
|
||||
|
||||
#[path = "../../utils/libc.rs"]
|
||||
mod libc_utils;
|
||||
@@ -17,6 +16,7 @@ fn main() {
|
||||
test_notification_after_timeout();
|
||||
test_epoll_race();
|
||||
wakeup_on_new_interest();
|
||||
multiple_events_wake_multiple_threads();
|
||||
}
|
||||
|
||||
// Using `as` cast since `EPOLLET` wraps around
|
||||
@@ -90,7 +90,7 @@ fn test_epoll_block_then_unblock() {
|
||||
// epoll_wait before triggering notification so it will block then get unblocked before timeout.
|
||||
let expected_event = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap();
|
||||
let expected_value = fds[0] as u64;
|
||||
let thread1 = spawn(move || {
|
||||
let thread1 = thread::spawn(move || {
|
||||
thread::yield_now();
|
||||
let data = "abcde".as_bytes().as_ptr();
|
||||
let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
|
||||
@@ -210,3 +210,54 @@ fn wakeup_on_new_interest() {
|
||||
// This should wake up the thread.
|
||||
t.join().unwrap();
|
||||
}
|
||||
|
||||
/// Ensure that if a single operation triggers multiple events, we wake up enough threads
|
||||
/// to consume them all.
|
||||
fn multiple_events_wake_multiple_threads() {
|
||||
// Create an epoll instance.
|
||||
let epfd = unsafe { libc::epoll_create1(0) };
|
||||
assert_ne!(epfd, -1);
|
||||
|
||||
// Create an eventfd instance.
|
||||
let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC;
|
||||
let fd1 = unsafe { libc::eventfd(0, flags) };
|
||||
// Make a duplicate so that we have two file descriptors for the same file description.
|
||||
let fd2 = unsafe { libc::dup(fd1) };
|
||||
|
||||
// Register both with epoll.
|
||||
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fd1 as u64 };
|
||||
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd1, &mut ev) };
|
||||
assert_eq!(res, 0);
|
||||
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fd2 as u64 };
|
||||
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd2, &mut ev) };
|
||||
assert_eq!(res, 0);
|
||||
|
||||
// Consume the initial events.
|
||||
let expected = [(libc::EPOLLOUT as u32, fd1 as u64), (libc::EPOLLOUT as u32, fd2 as u64)];
|
||||
check_epoll_wait::<8>(epfd, &expected, -1);
|
||||
|
||||
// Block two threads on the epoll, both wanting to get just one event.
|
||||
let t1 = thread::spawn(move || {
|
||||
let mut e = libc::epoll_event { events: 0, u64: 0 };
|
||||
let res = unsafe { libc::epoll_wait(epfd, &raw mut e, 1, -1) };
|
||||
assert!(res == 1);
|
||||
(e.events, e.u64)
|
||||
});
|
||||
let t2 = thread::spawn(move || {
|
||||
let mut e = libc::epoll_event { events: 0, u64: 0 };
|
||||
let res = unsafe { libc::epoll_wait(epfd, &raw mut e, 1, -1) };
|
||||
assert!(res == 1);
|
||||
(e.events, e.u64)
|
||||
});
|
||||
// Yield so both threads are waiting now.
|
||||
thread::yield_now();
|
||||
|
||||
// Trigger the eventfd. This triggers two events at once!
|
||||
libc_utils::write_all_from_slice(fd1, &0_u64.to_ne_bytes()).unwrap();
|
||||
|
||||
// Both threads should have been woken up so that both events can be consumed.
|
||||
let e1 = t1.join().unwrap();
|
||||
let e2 = t2.join().unwrap();
|
||||
// Ensure that across the two threads we got both events.
|
||||
assert!(expected == [e1, e2] || expected == [e2, e1]);
|
||||
}
|
||||
|
||||
@@ -262,10 +262,8 @@ fn test_epoll_eventfd() {
|
||||
let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC;
|
||||
let fd = unsafe { libc::eventfd(0, flags) };
|
||||
|
||||
// Write to the eventfd instance.
|
||||
let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
|
||||
let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
|
||||
assert_eq!(res, 8);
|
||||
// Write 1 to the eventfd instance.
|
||||
libc_utils::write_all_from_slice(fd, &1_u64.to_ne_bytes()).unwrap();
|
||||
|
||||
// Create an epoll instance.
|
||||
let epfd = unsafe { libc::epoll_create1(0) };
|
||||
@@ -281,18 +279,15 @@ fn test_epoll_eventfd() {
|
||||
let expected_value = u64::try_from(fd).unwrap();
|
||||
check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);
|
||||
|
||||
// Write to the eventfd again.
|
||||
let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
|
||||
assert_eq!(res, 8);
|
||||
// Write 0 to the eventfd.
|
||||
libc_utils::write_all_from_slice(fd, &0_u64.to_ne_bytes()).unwrap();
|
||||
|
||||
// This does not change the status, so we should get no event.
|
||||
// However, Linux performs a spurious wakeup.
|
||||
check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);
|
||||
|
||||
// Read from the eventfd.
|
||||
let mut buf = [0u8; 8];
|
||||
let res = unsafe { libc_utils::read_all(fd, buf.as_mut_ptr().cast(), 8) };
|
||||
assert_eq!(res, 8);
|
||||
libc_utils::read_all_into_array::<8>(fd).unwrap();
|
||||
|
||||
// This consumes the event, so the read status is gone. However, deactivation
|
||||
// does not trigger an event.
|
||||
@@ -355,6 +350,7 @@ fn test_epoll_socketpair_both_sides() {
|
||||
|
||||
// The state of fds[1] does not change (was writable, is writable).
|
||||
// However, we force a spurious wakeup as the read buffer just got emptied.
|
||||
// fds[0] lost its readability, but becoming less active is not considered an "edge".
|
||||
check_epoll_wait::<8>(epfd, &[(expected_event1, expected_value1)]);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
//@normalize-stderr-test: "::<.*>" -> ""
|
||||
|
||||
#![allow(function_casts_as_integer)]
|
||||
|
||||
#[inline(never)]
|
||||
fn func_a() -> Box<[*mut ()]> {
|
||||
func_b::<u8>()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
tests/pass/backtrace/backtrace-api-v1.rs:29:9 (func_d)
|
||||
tests/pass/backtrace/backtrace-api-v1.rs:16:9 (func_c)
|
||||
tests/pass/backtrace/backtrace-api-v1.rs:11:5 (func_b::<u8>)
|
||||
tests/pass/backtrace/backtrace-api-v1.rs:7:5 (func_a)
|
||||
tests/pass/backtrace/backtrace-api-v1.rs:36:18 (main)
|
||||
tests/pass/backtrace/backtrace-api-v1.rs:27:9 (func_d)
|
||||
tests/pass/backtrace/backtrace-api-v1.rs:14:9 (func_c)
|
||||
tests/pass/backtrace/backtrace-api-v1.rs:9:5 (func_b::<u8>)
|
||||
tests/pass/backtrace/backtrace-api-v1.rs:5:5 (func_a)
|
||||
tests/pass/backtrace/backtrace-api-v1.rs:34:18 (main)
|
||||
|
||||
@@ -15,12 +15,48 @@ fn main() {
|
||||
assert!(is_x86_feature_detected!("avx512vpopcntdq"));
|
||||
|
||||
unsafe {
|
||||
test_avx512();
|
||||
test_avx512bitalg();
|
||||
test_avx512vpopcntdq();
|
||||
test_avx512ternarylogic();
|
||||
}
|
||||
}
|
||||
|
||||
#[target_feature(enable = "avx512bw")]
|
||||
unsafe fn test_avx512() {
|
||||
#[target_feature(enable = "avx512bw")]
|
||||
unsafe fn test_mm512_sad_epu8() {
|
||||
let a = _mm512_set_epi8(
|
||||
71, 70, 69, 68, 67, 66, 65, 64, //
|
||||
55, 54, 53, 52, 51, 50, 49, 48, //
|
||||
47, 46, 45, 44, 43, 42, 41, 40, //
|
||||
39, 38, 37, 36, 35, 34, 33, 32, //
|
||||
31, 30, 29, 28, 27, 26, 25, 24, //
|
||||
23, 22, 21, 20, 19, 18, 17, 16, //
|
||||
15, 14, 13, 12, 11, 10, 9, 8, //
|
||||
7, 6, 5, 4, 3, 2, 1, 0, //
|
||||
);
|
||||
|
||||
// `d` is the absolute difference with the corresponding row in `a`.
|
||||
let b = _mm512_set_epi8(
|
||||
63, 62, 61, 60, 59, 58, 57, 56, // lane 7 (d = 8)
|
||||
62, 61, 60, 59, 58, 57, 56, 55, // lane 6 (d = 7)
|
||||
53, 52, 51, 50, 49, 48, 47, 46, // lane 5 (d = 6)
|
||||
44, 43, 42, 41, 40, 39, 38, 37, // lane 4 (d = 5)
|
||||
35, 34, 33, 32, 31, 30, 29, 28, // lane 3 (d = 4)
|
||||
26, 25, 24, 23, 22, 21, 20, 19, // lane 2 (d = 3)
|
||||
17, 16, 15, 14, 13, 12, 11, 10, // lane 1 (d = 2)
|
||||
8, 7, 6, 5, 4, 3, 2, 1, // lane 0 (d = 1)
|
||||
);
|
||||
|
||||
let r = _mm512_sad_epu8(a, b);
|
||||
let e = _mm512_set_epi64(64, 56, 48, 40, 32, 24, 16, 8);
|
||||
|
||||
assert_eq_m512i(r, e);
|
||||
}
|
||||
test_mm512_sad_epu8();
|
||||
}
|
||||
|
||||
// Some of 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
|
||||
// __mXXXi happens to be defined in terms of signed integers.
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
//@compile-flags: -Zmiri-retag-fields=none
|
||||
|
||||
struct Newtype<'a>(#[allow(dead_code)] &'a mut i32);
|
||||
|
||||
fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) {
|
||||
dealloc();
|
||||
}
|
||||
|
||||
// Make sure that we do *not* retag the fields of `Newtype`.
|
||||
fn main() {
|
||||
let ptr = Box::into_raw(Box::new(0i32));
|
||||
#[rustfmt::skip] // I like my newlines
|
||||
unsafe {
|
||||
dealloc_while_running(
|
||||
Newtype(&mut *ptr),
|
||||
|| drop(Box::from_raw(ptr)),
|
||||
)
|
||||
};
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
//@compile-flags: -Zmiri-retag-fields=scalar
|
||||
|
||||
struct Newtype<'a>(
|
||||
#[allow(dead_code)] &'a mut i32,
|
||||
#[allow(dead_code)] i32,
|
||||
#[allow(dead_code)] i32,
|
||||
);
|
||||
|
||||
fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) {
|
||||
dealloc();
|
||||
}
|
||||
|
||||
// Make sure that with -Zmiri-retag-fields=scalar, we do *not* retag the fields of `Newtype`.
|
||||
fn main() {
|
||||
let ptr = Box::into_raw(Box::new(0i32));
|
||||
#[rustfmt::skip] // I like my newlines
|
||||
unsafe {
|
||||
dealloc_while_running(
|
||||
Newtype(&mut *ptr, 0, 0),
|
||||
|| drop(Box::from_raw(ptr)),
|
||||
)
|
||||
};
|
||||
}
|
||||
@@ -22,6 +22,18 @@ pub unsafe fn read_all(
|
||||
return read_so_far as libc::ssize_t;
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn read_all_into_array<const N: usize>(fd: libc::c_int) -> Result<[u8; N], libc::ssize_t> {
|
||||
let mut buf = [0; N];
|
||||
let res = unsafe { read_all(fd, buf.as_mut_ptr().cast(), buf.len()) };
|
||||
if res >= 0 {
|
||||
assert_eq!(res as usize, buf.len());
|
||||
Ok(buf)
|
||||
} else {
|
||||
Err(res)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn write_all(
|
||||
fd: libc::c_int,
|
||||
buf: *const libc::c_void,
|
||||
@@ -39,3 +51,14 @@ pub unsafe fn write_all(
|
||||
}
|
||||
return written_so_far as libc::ssize_t;
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn write_all_from_slice(fd: libc::c_int, buf: &[u8]) -> Result<(), libc::ssize_t> {
|
||||
let res = unsafe { write_all(fd, buf.as_ptr().cast(), buf.len()) };
|
||||
if res >= 0 {
|
||||
assert_eq!(res as usize, buf.len());
|
||||
Ok(())
|
||||
} else {
|
||||
Err(res)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user