Auto merge of #130979 - matthiaskrgr:rollup-u7ylca5, r=matthiaskrgr
Rollup of 5 pull requests Successful merges: - #128778 (atomics: allow atomic and non-atomic reads to race) - #130918 (simplify LLVM submodule handling) - #130960 (Only add an automatic SONAME for Rust dylibs) - #130973 (compiletest: rename "runtest/crash.rs" to "runtest/crashes.rs" to be in line with the test directory) - #130976 (remove couple redundant clones) r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
e6eb45143c
31 changed files with 422 additions and 347 deletions
|
@ -310,7 +310,7 @@ pub(crate) fn expand_deriving_smart_ptr(
|
|||
// Add the impl blocks for `DispatchFromDyn` and `CoerceUnsized`.
|
||||
let gen_args = vec![GenericArg::Type(alt_self_type.clone())];
|
||||
add_impl_block(impl_generics.clone(), sym::DispatchFromDyn, gen_args.clone());
|
||||
add_impl_block(impl_generics.clone(), sym::CoerceUnsized, gen_args.clone());
|
||||
add_impl_block(impl_generics.clone(), sym::CoerceUnsized, gen_args);
|
||||
}
|
||||
|
||||
fn contains_maybe_sized_bound_on_pointee(predicates: &[WherePredicate], pointee: Symbol) -> bool {
|
||||
|
|
|
@ -2490,7 +2490,7 @@ fn add_order_independent_options(
|
|||
}
|
||||
}
|
||||
|
||||
cmd.set_output_kind(link_output_kind, out_filename);
|
||||
cmd.set_output_kind(link_output_kind, crate_type, out_filename);
|
||||
|
||||
add_relro_args(cmd, sess);
|
||||
|
||||
|
|
|
@ -275,7 +275,12 @@ pub(crate) trait Linker {
|
|||
fn is_cc(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn set_output_kind(&mut self, output_kind: LinkOutputKind, out_filename: &Path);
|
||||
fn set_output_kind(
|
||||
&mut self,
|
||||
output_kind: LinkOutputKind,
|
||||
crate_type: CrateType,
|
||||
out_filename: &Path,
|
||||
);
|
||||
fn link_dylib_by_name(&mut self, _name: &str, _verbatim: bool, _as_needed: bool) {
|
||||
bug!("dylib linked with unsupported linker")
|
||||
}
|
||||
|
@ -396,7 +401,7 @@ impl<'a> GccLinker<'a> {
|
|||
]);
|
||||
}
|
||||
|
||||
fn build_dylib(&mut self, out_filename: &Path) {
|
||||
fn build_dylib(&mut self, crate_type: CrateType, out_filename: &Path) {
|
||||
// On mac we need to tell the linker to let this library be rpathed
|
||||
if self.sess.target.is_like_osx {
|
||||
if !self.is_ld {
|
||||
|
@ -427,7 +432,7 @@ impl<'a> GccLinker<'a> {
|
|||
let mut out_implib = OsString::from("--out-implib=");
|
||||
out_implib.push(out_filename.with_file_name(implib_name));
|
||||
self.link_arg(out_implib);
|
||||
} else {
|
||||
} else if crate_type == CrateType::Dylib {
|
||||
// When dylibs are linked by a full path this value will get into `DT_NEEDED`
|
||||
// instead of the full path, so the library can be later found in some other
|
||||
// location than that specific path.
|
||||
|
@ -474,7 +479,12 @@ impl<'a> Linker for GccLinker<'a> {
|
|||
!self.is_ld
|
||||
}
|
||||
|
||||
fn set_output_kind(&mut self, output_kind: LinkOutputKind, out_filename: &Path) {
|
||||
fn set_output_kind(
|
||||
&mut self,
|
||||
output_kind: LinkOutputKind,
|
||||
crate_type: CrateType,
|
||||
out_filename: &Path,
|
||||
) {
|
||||
match output_kind {
|
||||
LinkOutputKind::DynamicNoPicExe => {
|
||||
if !self.is_ld && self.is_gnu {
|
||||
|
@ -509,10 +519,10 @@ impl<'a> Linker for GccLinker<'a> {
|
|||
self.link_args(&["-static", "-pie", "--no-dynamic-linker", "-z", "text"]);
|
||||
}
|
||||
}
|
||||
LinkOutputKind::DynamicDylib => self.build_dylib(out_filename),
|
||||
LinkOutputKind::DynamicDylib => self.build_dylib(crate_type, out_filename),
|
||||
LinkOutputKind::StaticDylib => {
|
||||
self.link_or_cc_arg("-static");
|
||||
self.build_dylib(out_filename);
|
||||
self.build_dylib(crate_type, out_filename);
|
||||
}
|
||||
LinkOutputKind::WasiReactorExe => {
|
||||
self.link_args(&["--entry", "_initialize"]);
|
||||
|
@ -866,7 +876,12 @@ impl<'a> Linker for MsvcLinker<'a> {
|
|||
&mut self.cmd
|
||||
}
|
||||
|
||||
fn set_output_kind(&mut self, output_kind: LinkOutputKind, out_filename: &Path) {
|
||||
fn set_output_kind(
|
||||
&mut self,
|
||||
output_kind: LinkOutputKind,
|
||||
_crate_type: CrateType,
|
||||
out_filename: &Path,
|
||||
) {
|
||||
match output_kind {
|
||||
LinkOutputKind::DynamicNoPicExe
|
||||
| LinkOutputKind::DynamicPicExe
|
||||
|
@ -1124,7 +1139,13 @@ impl<'a> Linker for EmLinker<'a> {
|
|||
true
|
||||
}
|
||||
|
||||
fn set_output_kind(&mut self, _output_kind: LinkOutputKind, _out_filename: &Path) {}
|
||||
fn set_output_kind(
|
||||
&mut self,
|
||||
_output_kind: LinkOutputKind,
|
||||
_crate_type: CrateType,
|
||||
_out_filename: &Path,
|
||||
) {
|
||||
}
|
||||
|
||||
fn link_dylib_by_name(&mut self, name: &str, _verbatim: bool, _as_needed: bool) {
|
||||
// Emscripten always links statically
|
||||
|
@ -1273,7 +1294,12 @@ impl<'a> Linker for WasmLd<'a> {
|
|||
&mut self.cmd
|
||||
}
|
||||
|
||||
fn set_output_kind(&mut self, output_kind: LinkOutputKind, _out_filename: &Path) {
|
||||
fn set_output_kind(
|
||||
&mut self,
|
||||
output_kind: LinkOutputKind,
|
||||
_crate_type: CrateType,
|
||||
_out_filename: &Path,
|
||||
) {
|
||||
match output_kind {
|
||||
LinkOutputKind::DynamicNoPicExe
|
||||
| LinkOutputKind::DynamicPicExe
|
||||
|
@ -1422,7 +1448,13 @@ impl<'a> Linker for L4Bender<'a> {
|
|||
&mut self.cmd
|
||||
}
|
||||
|
||||
fn set_output_kind(&mut self, _output_kind: LinkOutputKind, _out_filename: &Path) {}
|
||||
fn set_output_kind(
|
||||
&mut self,
|
||||
_output_kind: LinkOutputKind,
|
||||
_crate_type: CrateType,
|
||||
_out_filename: &Path,
|
||||
) {
|
||||
}
|
||||
|
||||
fn link_staticlib_by_name(&mut self, name: &str, _verbatim: bool, whole_archive: bool) {
|
||||
self.hint_static();
|
||||
|
@ -1568,7 +1600,12 @@ impl<'a> Linker for AixLinker<'a> {
|
|||
&mut self.cmd
|
||||
}
|
||||
|
||||
fn set_output_kind(&mut self, output_kind: LinkOutputKind, out_filename: &Path) {
|
||||
fn set_output_kind(
|
||||
&mut self,
|
||||
output_kind: LinkOutputKind,
|
||||
_crate_type: CrateType,
|
||||
out_filename: &Path,
|
||||
) {
|
||||
match output_kind {
|
||||
LinkOutputKind::DynamicDylib => {
|
||||
self.hint_dynamic();
|
||||
|
@ -1775,7 +1812,13 @@ impl<'a> Linker for PtxLinker<'a> {
|
|||
&mut self.cmd
|
||||
}
|
||||
|
||||
fn set_output_kind(&mut self, _output_kind: LinkOutputKind, _out_filename: &Path) {}
|
||||
fn set_output_kind(
|
||||
&mut self,
|
||||
_output_kind: LinkOutputKind,
|
||||
_crate_type: CrateType,
|
||||
_out_filename: &Path,
|
||||
) {
|
||||
}
|
||||
|
||||
fn link_staticlib_by_name(&mut self, _name: &str, _verbatim: bool, _whole_archive: bool) {
|
||||
panic!("staticlibs not supported")
|
||||
|
@ -1841,7 +1884,13 @@ impl<'a> Linker for LlbcLinker<'a> {
|
|||
&mut self.cmd
|
||||
}
|
||||
|
||||
fn set_output_kind(&mut self, _output_kind: LinkOutputKind, _out_filename: &Path) {}
|
||||
fn set_output_kind(
|
||||
&mut self,
|
||||
_output_kind: LinkOutputKind,
|
||||
_crate_type: CrateType,
|
||||
_out_filename: &Path,
|
||||
) {
|
||||
}
|
||||
|
||||
fn link_staticlib_by_name(&mut self, _name: &str, _verbatim: bool, _whole_archive: bool) {
|
||||
panic!("staticlibs not supported")
|
||||
|
@ -1912,7 +1961,13 @@ impl<'a> Linker for BpfLinker<'a> {
|
|||
&mut self.cmd
|
||||
}
|
||||
|
||||
fn set_output_kind(&mut self, _output_kind: LinkOutputKind, _out_filename: &Path) {}
|
||||
fn set_output_kind(
|
||||
&mut self,
|
||||
_output_kind: LinkOutputKind,
|
||||
_crate_type: CrateType,
|
||||
_out_filename: &Path,
|
||||
) {
|
||||
}
|
||||
|
||||
fn link_staticlib_by_name(&mut self, _name: &str, _verbatim: bool, _whole_archive: bool) {
|
||||
panic!("staticlibs not supported")
|
||||
|
|
|
@ -1027,7 +1027,7 @@ impl<'p, 'tcx: 'p> PatCx for RustcPatCtxt<'p, 'tcx> {
|
|||
// Point at this range.
|
||||
first_range: thir_pat.span,
|
||||
// That's the gap that isn't covered.
|
||||
max: gap_as_pat.to_string(),
|
||||
max: gap_as_pat,
|
||||
// Suggest `lo..=max` instead.
|
||||
suggestion: suggested_range,
|
||||
},
|
||||
|
|
|
@ -1895,11 +1895,17 @@ impl<T: ?Sized + fmt::Display> fmt::Display for RefMut<'_, T> {
|
|||
/// uniqueness guarantee for mutable references is unaffected. There is *no* legal way to obtain
|
||||
/// aliasing `&mut`, not even with `UnsafeCell<T>`.
|
||||
///
|
||||
/// `UnsafeCell` does nothing to avoid data races; they are still undefined behavior. If multiple
|
||||
/// threads have access to the same `UnsafeCell`, they must follow the usual rules of the
|
||||
/// [concurrent memory model]: conflicting non-synchronized accesses must be done via the APIs in
|
||||
/// [`core::sync::atomic`].
|
||||
///
|
||||
/// The `UnsafeCell` API itself is technically very simple: [`.get()`] gives you a raw pointer
|
||||
/// `*mut T` to its contents. It is up to _you_ as the abstraction designer to use that raw pointer
|
||||
/// correctly.
|
||||
///
|
||||
/// [`.get()`]: `UnsafeCell::get`
|
||||
/// [concurrent memory model]: ../sync/atomic/index.html#memory-model-for-atomic-accesses
|
||||
///
|
||||
/// The precise Rust aliasing rules are somewhat in flux, but the main points are not contentious:
|
||||
///
|
||||
|
@ -1922,10 +1928,6 @@ impl<T: ?Sized + fmt::Display> fmt::Display for RefMut<'_, T> {
|
|||
/// live memory and the compiler is allowed to insert spurious reads if it can prove that this
|
||||
/// memory has not yet been deallocated.
|
||||
///
|
||||
/// - At all times, you must avoid data races. If multiple threads have access to
|
||||
/// the same `UnsafeCell`, then any writes must have a proper happens-before relation to all other
|
||||
/// accesses (or use atomics).
|
||||
///
|
||||
/// To assist with proper design, the following scenarios are explicitly declared legal
|
||||
/// for single-threaded code:
|
||||
///
|
||||
|
|
|
@ -24,25 +24,42 @@
|
|||
//!
|
||||
//! ## Memory model for atomic accesses
|
||||
//!
|
||||
//! Rust atomics currently follow the same rules as [C++20 atomics][cpp], specifically `atomic_ref`.
|
||||
//! Basically, creating a *shared reference* to one of the Rust atomic types corresponds to creating
|
||||
//! an `atomic_ref` in C++; the `atomic_ref` is destroyed when the lifetime of the shared reference
|
||||
//! ends. A Rust atomic type that is exclusively owned or behind a mutable reference does *not*
|
||||
//! correspond to an “atomic object” in C++, since the underlying primitive can be mutably accessed,
|
||||
//! for example with `get_mut`, to perform non-atomic operations.
|
||||
//! Rust atomics currently follow the same rules as [C++20 atomics][cpp], specifically the rules
|
||||
//! from the [`intro.races`][cpp-intro.races] section, without the "consume" memory ordering. Since
|
||||
//! C++ uses an object-based memory model whereas Rust is access-based, a bit of translation work
|
||||
//! has to be done to apply the C++ rules to Rust: whenever C++ talks about "the value of an
|
||||
//! object", we understand that to mean the resulting bytes obtained when doing a read. When the C++
|
||||
//! standard talks about "the value of an atomic object", this refers to the result of doing an
|
||||
//! atomic load (via the operations provided in this module). A "modification of an atomic object"
|
||||
//! refers to an atomic store.
|
||||
//!
|
||||
//! The end result is *almost* equivalent to saying that creating a *shared reference* to one of the
|
||||
//! Rust atomic types corresponds to creating an `atomic_ref` in C++, with the `atomic_ref` being
|
||||
//! destroyed when the lifetime of the shared reference ends. The main difference is that Rust
|
||||
//! permits concurrent atomic and non-atomic reads to the same memory as those cause no issue in the
|
||||
//! C++ memory model, they are just forbidden in C++ because memory is partitioned into "atomic
|
||||
//! objects" and "non-atomic objects" (with `atomic_ref` temporarily converting a non-atomic object
|
||||
//! into an atomic object).
|
||||
//!
|
||||
//! The most important aspect of this model is that *data races* are undefined behavior. A data race
|
||||
//! is defined as conflicting non-synchronized accesses where at least one of the accesses is
|
||||
//! non-atomic. Here, accesses are *conflicting* if they affect overlapping regions of memory and at
|
||||
//! least one of them is a write. They are *non-synchronized* if neither of them *happens-before*
|
||||
//! the other, according to the happens-before order of the memory model.
|
||||
//!
|
||||
//! The other possible cause of undefined behavior in the memory model are mixed-size accesses: Rust
|
||||
//! inherits the C++ limitation that non-synchronized conflicting atomic accesses may not partially
|
||||
//! overlap. In other words, every pair of non-synchronized atomic accesses must be either disjoint,
|
||||
//! access the exact same memory (including using the same access size), or both be reads.
|
||||
//!
|
||||
//! Each atomic access takes an [`Ordering`] which defines how the operation interacts with the
|
||||
//! happens-before order. These orderings behave the same as the corresponding [C++20 atomic
|
||||
//! orderings][cpp_memory_order]. For more information, see the [nomicon].
|
||||
//!
|
||||
//! [cpp]: https://en.cppreference.com/w/cpp/atomic
|
||||
//!
|
||||
//! Each method takes an [`Ordering`] which represents the strength of
|
||||
//! the memory barrier for that operation. These orderings are the
|
||||
//! same as the [C++20 atomic orderings][1]. For more information see the [nomicon][2].
|
||||
//!
|
||||
//! [1]: https://en.cppreference.com/w/cpp/atomic/memory_order
|
||||
//! [2]: ../../../nomicon/atomics.html
|
||||
//!
|
||||
//! Since C++ does not support mixing atomic and non-atomic accesses, or non-synchronized
|
||||
//! different-sized accesses to the same data, Rust does not support those operations either.
|
||||
//! Note that both of those restrictions only apply if the accesses are non-synchronized.
|
||||
//! [cpp-intro.races]: https://timsong-cpp.github.io/cppwp/n4868/intro.multithread#intro.races
|
||||
//! [cpp_memory_order]: https://en.cppreference.com/w/cpp/atomic/memory_order
|
||||
//! [nomicon]: ../../../nomicon/atomics.html
|
||||
//!
|
||||
//! ```rust,no_run undefined_behavior
|
||||
//! use std::sync::atomic::{AtomicU16, AtomicU8, Ordering};
|
||||
|
@ -52,27 +69,29 @@
|
|||
//! let atomic = AtomicU16::new(0);
|
||||
//!
|
||||
//! thread::scope(|s| {
|
||||
//! // This is UB: mixing atomic and non-atomic accesses
|
||||
//! s.spawn(|| atomic.store(1, Ordering::Relaxed));
|
||||
//! s.spawn(|| unsafe { atomic.as_ptr().write(2) });
|
||||
//! // This is UB: conflicting non-synchronized accesses, at least one of which is non-atomic.
|
||||
//! s.spawn(|| atomic.store(1, Ordering::Relaxed)); // atomic store
|
||||
//! s.spawn(|| unsafe { atomic.as_ptr().write(2) }); // non-atomic write
|
||||
//! });
|
||||
//!
|
||||
//! thread::scope(|s| {
|
||||
//! // This is UB: even reads are not allowed to be mixed
|
||||
//! s.spawn(|| atomic.load(Ordering::Relaxed));
|
||||
//! s.spawn(|| unsafe { atomic.as_ptr().read() });
|
||||
//! // This is fine: the accesses do not conflict (as none of them performs any modification).
|
||||
//! // In C++ this would be disallowed since creating an `atomic_ref` precludes
|
||||
//! // further non-atomic accesses, but Rust does not have that limitation.
|
||||
//! s.spawn(|| atomic.load(Ordering::Relaxed)); // atomic load
|
||||
//! s.spawn(|| unsafe { atomic.as_ptr().read() }); // non-atomic read
|
||||
//! });
|
||||
//!
|
||||
//! thread::scope(|s| {
|
||||
//! // This is fine, `join` synchronizes the code in a way such that atomic
|
||||
//! // and non-atomic accesses can't happen "at the same time"
|
||||
//! let handle = s.spawn(|| atomic.store(1, Ordering::Relaxed));
|
||||
//! handle.join().unwrap();
|
||||
//! s.spawn(|| unsafe { atomic.as_ptr().write(2) });
|
||||
//! // This is fine: `join` synchronizes the code in a way such that the atomic
|
||||
//! // store happens-before the non-atomic write.
|
||||
//! let handle = s.spawn(|| atomic.store(1, Ordering::Relaxed)); // atomic store
|
||||
//! handle.join().unwrap(); // synchronize
|
||||
//! s.spawn(|| unsafe { atomic.as_ptr().write(2) }); // non-atomic write
|
||||
//! });
|
||||
//!
|
||||
//! thread::scope(|s| {
|
||||
//! // This is UB: using different-sized atomic accesses to the same data
|
||||
//! // This is UB: non-synchronized conflicting differently-sized atomic accesses.
|
||||
//! s.spawn(|| atomic.store(1, Ordering::Relaxed));
|
||||
//! s.spawn(|| unsafe {
|
||||
//! let differently_sized = transmute::<&AtomicU16, &AtomicU8>(&atomic);
|
||||
|
@ -81,8 +100,8 @@
|
|||
//! });
|
||||
//!
|
||||
//! thread::scope(|s| {
|
||||
//! // This is fine, `join` synchronizes the code in a way such that
|
||||
//! // differently-sized accesses can't happen "at the same time"
|
||||
//! // This is fine: `join` synchronizes the code in a way such that
|
||||
//! // the 1-byte store happens-before the 2-byte store.
|
||||
//! let handle = s.spawn(|| atomic.store(1, Ordering::Relaxed));
|
||||
//! handle.join().unwrap();
|
||||
//! s.spawn(|| unsafe {
|
||||
|
@ -137,7 +156,7 @@
|
|||
//!
|
||||
//! # Atomic accesses to read-only memory
|
||||
//!
|
||||
//! In general, *all* atomic accesses on read-only memory are Undefined Behavior. For instance, attempting
|
||||
//! In general, *all* atomic accesses on read-only memory are undefined behavior. For instance, attempting
|
||||
//! to do a `compare_exchange` that will definitely fail (making it conceptually a read-only
|
||||
//! operation) can still cause a segmentation fault if the underlying memory page is mapped read-only. Since
|
||||
//! atomic `load`s might be implemented using compare-exchange operations, even a `load` can fault
|
||||
|
@ -153,7 +172,7 @@
|
|||
//!
|
||||
//! As an exception from the general rule stated above, "sufficiently small" atomic loads with
|
||||
//! `Ordering::Relaxed` are implemented in a way that works on read-only memory, and are hence not
|
||||
//! Undefined Behavior. The exact size limit for what makes a load "sufficiently small" varies
|
||||
//! undefined behavior. The exact size limit for what makes a load "sufficiently small" varies
|
||||
//! depending on the target:
|
||||
//!
|
||||
//! | `target_arch` | Size limit |
|
||||
|
|
|
@ -1205,7 +1205,8 @@ pub fn rustc_cargo_env(
|
|||
// busting caches (e.g. like #71152).
|
||||
if builder.config.llvm_enabled(target) {
|
||||
let building_is_expensive =
|
||||
crate::core::build_steps::llvm::prebuilt_llvm_config(builder, target).should_build();
|
||||
crate::core::build_steps::llvm::prebuilt_llvm_config(builder, target, false)
|
||||
.should_build();
|
||||
// `top_stage == stage` might be false for `check --stage 1`, if we are building the stage 1 compiler
|
||||
let can_skip_build = builder.kind == Kind::Check && builder.top_stage == stage;
|
||||
let should_skip_build = building_is_expensive && can_skip_build;
|
||||
|
|
|
@ -2036,7 +2036,7 @@ fn maybe_install_llvm(
|
|||
}
|
||||
!builder.config.dry_run()
|
||||
} else if let llvm::LlvmBuildStatus::AlreadyBuilt(llvm::LlvmResult { llvm_config, .. }) =
|
||||
llvm::prebuilt_llvm_config(builder, target)
|
||||
llvm::prebuilt_llvm_config(builder, target, true)
|
||||
{
|
||||
let mut cmd = command(llvm_config);
|
||||
cmd.arg("--libfiles");
|
||||
|
|
|
@ -87,10 +87,14 @@ impl LdFlags {
|
|||
///
|
||||
/// This will return the llvm-config if it can get it (but it will not build it
|
||||
/// if not).
|
||||
pub fn prebuilt_llvm_config(builder: &Builder<'_>, target: TargetSelection) -> LlvmBuildStatus {
|
||||
// If we have llvm submodule initialized already, sync it.
|
||||
builder.update_existing_submodule("src/llvm-project");
|
||||
|
||||
pub fn prebuilt_llvm_config(
|
||||
builder: &Builder<'_>,
|
||||
target: TargetSelection,
|
||||
// Certain commands (like `x test mir-opt --bless`) may call this function with different targets,
|
||||
// which could bypass the CI LLVM early-return even if `builder.config.llvm_from_ci` is true.
|
||||
// This flag should be `true` only if the caller needs the LLVM sources (e.g., if it will build LLVM).
|
||||
handle_submodule_when_needed: bool,
|
||||
) -> LlvmBuildStatus {
|
||||
builder.config.maybe_download_ci_llvm();
|
||||
|
||||
// If we're using a custom LLVM bail out here, but we can only use a
|
||||
|
@ -109,9 +113,10 @@ pub fn prebuilt_llvm_config(builder: &Builder<'_>, target: TargetSelection) -> L
|
|||
}
|
||||
}
|
||||
|
||||
// Initialize the llvm submodule if not initialized already.
|
||||
// If submodules are disabled, this does nothing.
|
||||
builder.config.update_submodule("src/llvm-project");
|
||||
if handle_submodule_when_needed {
|
||||
// If submodules are disabled, this does nothing.
|
||||
builder.config.update_submodule("src/llvm-project");
|
||||
}
|
||||
|
||||
let root = "src/llvm-project/llvm";
|
||||
let out_dir = builder.llvm_out(target);
|
||||
|
@ -284,7 +289,7 @@ impl Step for Llvm {
|
|||
};
|
||||
|
||||
// If LLVM has already been built or been downloaded through download-ci-llvm, we avoid building it again.
|
||||
let Meta { stamp, res, out_dir, root } = match prebuilt_llvm_config(builder, target) {
|
||||
let Meta { stamp, res, out_dir, root } = match prebuilt_llvm_config(builder, target, true) {
|
||||
LlvmBuildStatus::AlreadyBuilt(p) => return p,
|
||||
LlvmBuildStatus::ShouldBuild(m) => m,
|
||||
};
|
||||
|
|
|
@ -1537,7 +1537,9 @@ impl<'a> Builder<'a> {
|
|||
// rustc_llvm. But if LLVM is stale, that'll be a tiny amount
|
||||
// of work comparatively, and we'd likely need to rebuild it anyway,
|
||||
// so that's okay.
|
||||
if crate::core::build_steps::llvm::prebuilt_llvm_config(self, target).should_build() {
|
||||
if crate::core::build_steps::llvm::prebuilt_llvm_config(self, target, false)
|
||||
.should_build()
|
||||
{
|
||||
cargo.env("RUST_CHECK", "1");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ mod assembly;
|
|||
mod codegen;
|
||||
mod codegen_units;
|
||||
mod coverage;
|
||||
mod crash;
|
||||
mod crashes;
|
||||
mod debuginfo;
|
||||
mod incremental;
|
||||
mod js_doc;
|
||||
|
|
|
@ -191,7 +191,8 @@ struct AtomicMemoryCellClocks {
|
|||
/// The size of accesses to this atomic location.
|
||||
/// We use this to detect non-synchronized mixed-size accesses. Since all accesses must be
|
||||
/// aligned to their size, this is sufficient to detect imperfectly overlapping accesses.
|
||||
size: Size,
|
||||
/// `None` indicates that we saw multiple different sizes, which is okay as long as all accesses are reads.
|
||||
size: Option<Size>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
|
@ -265,6 +266,14 @@ impl AccessType {
|
|||
let mut msg = String::new();
|
||||
|
||||
if let Some(size) = size {
|
||||
if size == Size::ZERO {
|
||||
// In this case there were multiple read accesss with different sizes and then a write.
|
||||
// We will be reporting *one* of the other reads, but we don't have enough information
|
||||
// to determine which one had which size.
|
||||
assert!(self == AccessType::AtomicLoad);
|
||||
assert!(ty.is_none());
|
||||
return format!("multiple differently-sized atomic loads, including one load");
|
||||
}
|
||||
msg.push_str(&format!("{}-byte {}", size.bytes(), msg))
|
||||
}
|
||||
|
||||
|
@ -305,8 +314,7 @@ impl AccessType {
|
|||
}
|
||||
}
|
||||
|
||||
/// Memory Cell vector clock metadata
|
||||
/// for data-race detection.
|
||||
/// Per-byte vector clock metadata for data-race detection.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
struct MemoryCellClocks {
|
||||
/// The vector-clock timestamp and the thread that did the last non-atomic write. We don't need
|
||||
|
@ -325,8 +333,8 @@ struct MemoryCellClocks {
|
|||
read: VClock,
|
||||
|
||||
/// Atomic access, acquire, release sequence tracking clocks.
|
||||
/// For non-atomic memory in the common case this
|
||||
/// value is set to None.
|
||||
/// For non-atomic memory this value is set to None.
|
||||
/// For atomic memory, each byte carries this information.
|
||||
atomic_ops: Option<Box<AtomicMemoryCellClocks>>,
|
||||
}
|
||||
|
||||
|
@ -336,7 +344,7 @@ impl AtomicMemoryCellClocks {
|
|||
read_vector: Default::default(),
|
||||
write_vector: Default::default(),
|
||||
sync_vector: Default::default(),
|
||||
size,
|
||||
size: Some(size),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -383,17 +391,23 @@ impl MemoryCellClocks {
|
|||
&mut self,
|
||||
thread_clocks: &ThreadClockSet,
|
||||
size: Size,
|
||||
write: bool,
|
||||
) -> Result<&mut AtomicMemoryCellClocks, DataRace> {
|
||||
match self.atomic_ops {
|
||||
Some(ref mut atomic) => {
|
||||
// We are good if the size is the same or all atomic accesses are before our current time.
|
||||
if atomic.size == size {
|
||||
if atomic.size == Some(size) {
|
||||
Ok(atomic)
|
||||
} else if atomic.read_vector <= thread_clocks.clock
|
||||
&& atomic.write_vector <= thread_clocks.clock
|
||||
{
|
||||
// This is now the new size that must be used for accesses here.
|
||||
atomic.size = size;
|
||||
// We are fully ordered after all previous accesses, so we can change the size.
|
||||
atomic.size = Some(size);
|
||||
Ok(atomic)
|
||||
} else if !write && atomic.write_vector <= thread_clocks.clock {
|
||||
// This is a read, and it is ordered after the last write. It's okay for the
|
||||
// sizes to mismatch, as long as no writes with a different size occur later.
|
||||
atomic.size = None;
|
||||
Ok(atomic)
|
||||
} else {
|
||||
Err(DataRace)
|
||||
|
@ -499,7 +513,7 @@ impl MemoryCellClocks {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Detect data-races with an atomic read, caused by a non-atomic access that does
|
||||
/// Detect data-races with an atomic read, caused by a non-atomic write that does
|
||||
/// not happen-before the atomic-read.
|
||||
fn atomic_read_detect(
|
||||
&mut self,
|
||||
|
@ -508,14 +522,10 @@ impl MemoryCellClocks {
|
|||
access_size: Size,
|
||||
) -> Result<(), DataRace> {
|
||||
trace!("Atomic read with vectors: {:#?} :: {:#?}", self, thread_clocks);
|
||||
let atomic = self.atomic_access(thread_clocks, access_size)?;
|
||||
let atomic = self.atomic_access(thread_clocks, access_size, /*write*/ false)?;
|
||||
atomic.read_vector.set_at_index(&thread_clocks.clock, index);
|
||||
// Make sure the last non-atomic write and all non-atomic reads were before this access.
|
||||
if self.write_was_before(&thread_clocks.clock) && self.read <= thread_clocks.clock {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(DataRace)
|
||||
}
|
||||
// Make sure the last non-atomic write was before this access.
|
||||
if self.write_was_before(&thread_clocks.clock) { Ok(()) } else { Err(DataRace) }
|
||||
}
|
||||
|
||||
/// Detect data-races with an atomic write, either with a non-atomic read or with
|
||||
|
@ -527,7 +537,7 @@ impl MemoryCellClocks {
|
|||
access_size: Size,
|
||||
) -> Result<(), DataRace> {
|
||||
trace!("Atomic write with vectors: {:#?} :: {:#?}", self, thread_clocks);
|
||||
let atomic = self.atomic_access(thread_clocks, access_size)?;
|
||||
let atomic = self.atomic_access(thread_clocks, access_size, /*write*/ true)?;
|
||||
atomic.write_vector.set_at_index(&thread_clocks.clock, index);
|
||||
// Make sure the last non-atomic write and all non-atomic reads were before this access.
|
||||
if self.write_was_before(&thread_clocks.clock) && self.read <= thread_clocks.clock {
|
||||
|
@ -552,11 +562,9 @@ impl MemoryCellClocks {
|
|||
}
|
||||
thread_clocks.clock.index_mut(index).set_read_type(read_type);
|
||||
if self.write_was_before(&thread_clocks.clock) {
|
||||
// We must be ordered-after all atomic writes.
|
||||
let race_free = if let Some(atomic) = self.atomic() {
|
||||
// We must be ordered-after all atomic accesses, reads and writes.
|
||||
// This ensures we don't mix atomic and non-atomic accesses.
|
||||
atomic.write_vector <= thread_clocks.clock
|
||||
&& atomic.read_vector <= thread_clocks.clock
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
@ -957,9 +965,7 @@ impl VClockAlloc {
|
|||
let mut other_size = None; // if `Some`, this was a size-mismatch race
|
||||
let write_clock;
|
||||
let (other_access, other_thread, other_clock) =
|
||||
// First check the atomic-nonatomic cases. If it looks like multiple
|
||||
// cases apply, this one should take precedence, else it might look like
|
||||
// we are reporting races between two non-atomic reads.
|
||||
// First check the atomic-nonatomic cases.
|
||||
if !access.is_atomic() &&
|
||||
let Some(atomic) = mem_clocks.atomic() &&
|
||||
let Some(idx) = Self::find_gt_index(&atomic.write_vector, &active_clocks.clock)
|
||||
|
@ -977,10 +983,10 @@ impl VClockAlloc {
|
|||
} else if let Some(idx) = Self::find_gt_index(&mem_clocks.read, &active_clocks.clock) {
|
||||
(AccessType::NaRead(mem_clocks.read[idx].read_type()), idx, &mem_clocks.read)
|
||||
// Finally, mixed-size races.
|
||||
} else if access.is_atomic() && let Some(atomic) = mem_clocks.atomic() && atomic.size != access_size {
|
||||
} else if access.is_atomic() && let Some(atomic) = mem_clocks.atomic() && atomic.size != Some(access_size) {
|
||||
// This is only a race if we are not synchronized with all atomic accesses, so find
|
||||
// the one we are not synchronized with.
|
||||
other_size = Some(atomic.size);
|
||||
other_size = Some(atomic.size.unwrap_or(Size::ZERO));
|
||||
if let Some(idx) = Self::find_gt_index(&atomic.write_vector, &active_clocks.clock)
|
||||
{
|
||||
(AccessType::AtomicStore, idx, &atomic.write_vector)
|
||||
|
@ -1007,10 +1013,7 @@ impl VClockAlloc {
|
|||
assert!(!involves_non_atomic);
|
||||
Some("overlapping unsynchronized atomic accesses must use the same access size")
|
||||
} else if access.is_read() && other_access.is_read() {
|
||||
assert!(involves_non_atomic);
|
||||
Some(
|
||||
"overlapping atomic and non-atomic accesses must be synchronized, even if both are read-only",
|
||||
)
|
||||
panic!("there should be no same-size read-read races")
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
//@compile-flags: -Zmiri-preemption-rate=0.0 -Zmiri-disable-weak-memory-emulation
|
||||
// Avoid accidental synchronization via address reuse inside `thread::spawn`.
|
||||
//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0
|
||||
|
||||
use std::sync::atomic::{AtomicU8, AtomicU16, Ordering};
|
||||
use std::thread;
|
||||
|
||||
fn convert(a: &AtomicU16) -> &[AtomicU8; 2] {
|
||||
unsafe { std::mem::transmute(a) }
|
||||
}
|
||||
|
||||
// We can't allow mixed-size accesses; they are not possible in C++ and even
|
||||
// Intel says you shouldn't do it.
|
||||
fn main() {
|
||||
let a = AtomicU16::new(0);
|
||||
let a16 = &a;
|
||||
let a8 = convert(a16);
|
||||
|
||||
thread::scope(|s| {
|
||||
s.spawn(|| {
|
||||
a16.load(Ordering::SeqCst);
|
||||
});
|
||||
s.spawn(|| {
|
||||
a8[0].load(Ordering::SeqCst);
|
||||
//~^ ERROR: Race condition detected between (1) 2-byte atomic load on thread `unnamed-1` and (2) 1-byte atomic load on thread `unnamed-2`
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
error: Undefined Behavior: Race condition detected between (1) multiple differently-sized atomic loads, including one load on thread `unnamed-ID` and (2) 2-byte atomic store on thread `unnamed-ID` at ALLOC. (2) just happened here
|
||||
--> tests/fail/data_race/mixed_size_read_read_write.rs:LL:CC
|
||||
|
|
||||
LL | a16.store(0, Ordering::SeqCst);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Race condition detected between (1) multiple differently-sized atomic loads, including one load on thread `unnamed-ID` and (2) 2-byte atomic store on thread `unnamed-ID` at ALLOC. (2) just happened here
|
||||
|
|
||||
help: and (1) occurred earlier here
|
||||
--> tests/fail/data_race/mixed_size_read_read_write.rs:LL:CC
|
||||
|
|
||||
LL | a16.load(Ordering::SeqCst);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
= help: overlapping unsynchronized atomic accesses must use the same access size
|
||||
= help: see https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#memory-model-for-atomic-accesses for more information about the Rust memory model
|
||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
= note: BACKTRACE (of the first span) on thread `unnamed-ID`:
|
||||
= note: inside closure at tests/fail/data_race/mixed_size_read_read_write.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
error: Undefined Behavior: Race condition detected between (1) multiple differently-sized atomic loads, including one load on thread `unnamed-ID` and (2) 1-byte atomic store on thread `unnamed-ID` at ALLOC. (2) just happened here
|
||||
--> tests/fail/data_race/mixed_size_read_read_write.rs:LL:CC
|
||||
|
|
||||
LL | a8[0].store(0, Ordering::SeqCst);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Race condition detected between (1) multiple differently-sized atomic loads, including one load on thread `unnamed-ID` and (2) 1-byte atomic store on thread `unnamed-ID` at ALLOC. (2) just happened here
|
||||
|
|
||||
help: and (1) occurred earlier here
|
||||
--> tests/fail/data_race/mixed_size_read_read_write.rs:LL:CC
|
||||
|
|
||||
LL | a16.load(Ordering::SeqCst);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
= help: overlapping unsynchronized atomic accesses must use the same access size
|
||||
= help: see https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#memory-model-for-atomic-accesses for more information about the Rust memory model
|
||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
= note: BACKTRACE (of the first span) on thread `unnamed-ID`:
|
||||
= note: inside closure at tests/fail/data_race/mixed_size_read_read_write.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
//@compile-flags: -Zmiri-preemption-rate=0.0 -Zmiri-disable-weak-memory-emulation
|
||||
// Avoid accidental synchronization via address reuse inside `thread::spawn`.
|
||||
//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0
|
||||
// Two variants: the atomic store matches the size of the first or second atomic load.
|
||||
//@revisions: match_first_load match_second_load
|
||||
|
||||
use std::sync::atomic::{AtomicU16, AtomicU8, Ordering};
|
||||
use std::thread;
|
||||
|
||||
fn convert(a: &AtomicU16) -> &[AtomicU8; 2] {
|
||||
unsafe { std::mem::transmute(a) }
|
||||
}
|
||||
|
||||
// We can't allow mixed-size accesses; they are not possible in C++ and even
|
||||
// Intel says you shouldn't do it.
|
||||
fn main() {
|
||||
let a = AtomicU16::new(0);
|
||||
let a16 = &a;
|
||||
let a8 = convert(a16);
|
||||
|
||||
thread::scope(|s| {
|
||||
s.spawn(|| {
|
||||
a16.load(Ordering::SeqCst);
|
||||
});
|
||||
s.spawn(|| {
|
||||
a8[0].load(Ordering::SeqCst);
|
||||
});
|
||||
s.spawn(|| {
|
||||
thread::yield_now(); // make sure this happens last
|
||||
if cfg!(match_first_load) {
|
||||
a16.store(0, Ordering::SeqCst);
|
||||
//~[match_first_load]^ ERROR: Race condition detected between (1) multiple differently-sized atomic loads, including one load on thread `unnamed-1` and (2) 2-byte atomic store on thread `unnamed-3`
|
||||
} else {
|
||||
a8[0].store(0, Ordering::SeqCst);
|
||||
//~[match_second_load]^ ERROR: Race condition detected between (1) multiple differently-sized atomic loads, including one load on thread `unnamed-1` and (2) 1-byte atomic store on thread `unnamed-3`
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
error: Undefined Behavior: Race condition detected between (1) 1-byte atomic load on thread `unnamed-ID` and (2) 2-byte atomic store on thread `unnamed-ID` at ALLOC. (2) just happened here
|
||||
--> tests/fail/data_race/mixed_size_read_write.rs:LL:CC
|
||||
|
|
||||
LL | a16.store(1, Ordering::SeqCst);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Race condition detected between (1) 1-byte atomic load on thread `unnamed-ID` and (2) 2-byte atomic store on thread `unnamed-ID` at ALLOC. (2) just happened here
|
||||
|
|
||||
help: and (1) occurred earlier here
|
||||
--> tests/fail/data_race/mixed_size_read_write.rs:LL:CC
|
||||
|
|
||||
LL | a8[0].load(Ordering::SeqCst);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
= help: overlapping unsynchronized atomic accesses must use the same access size
|
||||
= help: see https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#memory-model-for-atomic-accesses for more information about the Rust memory model
|
||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
= note: BACKTRACE (of the first span) on thread `unnamed-ID`:
|
||||
= note: inside closure at tests/fail/data_race/mixed_size_read_write.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
39
src/tools/miri/tests/fail/data_race/mixed_size_read_write.rs
Normal file
39
src/tools/miri/tests/fail/data_race/mixed_size_read_write.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
//@compile-flags: -Zmiri-preemption-rate=0.0 -Zmiri-disable-weak-memory-emulation
|
||||
// Avoid accidental synchronization via address reuse inside `thread::spawn`.
|
||||
//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0
|
||||
// Two revisions, depending on which access goes first.
|
||||
//@revisions: read_write write_read
|
||||
|
||||
use std::sync::atomic::{AtomicU16, AtomicU8, Ordering};
|
||||
use std::thread;
|
||||
|
||||
fn convert(a: &AtomicU16) -> &[AtomicU8; 2] {
|
||||
unsafe { std::mem::transmute(a) }
|
||||
}
|
||||
|
||||
// We can't allow mixed-size accesses; they are not possible in C++ and even
|
||||
// Intel says you shouldn't do it.
|
||||
fn main() {
|
||||
let a = AtomicU16::new(0);
|
||||
let a16 = &a;
|
||||
let a8 = convert(a16);
|
||||
|
||||
thread::scope(|s| {
|
||||
s.spawn(|| {
|
||||
if cfg!(read_write) {
|
||||
// Let the other one go first.
|
||||
thread::yield_now();
|
||||
}
|
||||
a16.store(1, Ordering::SeqCst);
|
||||
//~[read_write]^ ERROR: Race condition detected between (1) 1-byte atomic load on thread `unnamed-2` and (2) 2-byte atomic store on thread `unnamed-1`
|
||||
});
|
||||
s.spawn(|| {
|
||||
if cfg!(write_read) {
|
||||
// Let the other one go first.
|
||||
thread::yield_now();
|
||||
}
|
||||
a8[0].load(Ordering::SeqCst);
|
||||
//~[write_read]^ ERROR: Race condition detected between (1) 2-byte atomic store on thread `unnamed-1` and (2) 1-byte atomic load on thread `unnamed-2`
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,20 +1,20 @@
|
|||
error: Undefined Behavior: Race condition detected between (1) 2-byte atomic load on thread `unnamed-ID` and (2) 1-byte atomic load on thread `unnamed-ID` at ALLOC. (2) just happened here
|
||||
--> tests/fail/data_race/mixed_size_read.rs:LL:CC
|
||||
error: Undefined Behavior: Race condition detected between (1) 2-byte atomic store on thread `unnamed-ID` and (2) 1-byte atomic load on thread `unnamed-ID` at ALLOC. (2) just happened here
|
||||
--> tests/fail/data_race/mixed_size_read_write.rs:LL:CC
|
||||
|
|
||||
LL | a8[0].load(Ordering::SeqCst);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Race condition detected between (1) 2-byte atomic load on thread `unnamed-ID` and (2) 1-byte atomic load on thread `unnamed-ID` at ALLOC. (2) just happened here
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Race condition detected between (1) 2-byte atomic store on thread `unnamed-ID` and (2) 1-byte atomic load on thread `unnamed-ID` at ALLOC. (2) just happened here
|
||||
|
|
||||
help: and (1) occurred earlier here
|
||||
--> tests/fail/data_race/mixed_size_read.rs:LL:CC
|
||||
--> tests/fail/data_race/mixed_size_read_write.rs:LL:CC
|
||||
|
|
||||
LL | a16.load(Ordering::SeqCst);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
LL | a16.store(1, Ordering::SeqCst);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
= help: overlapping unsynchronized atomic accesses must use the same access size
|
||||
= help: see https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#memory-model-for-atomic-accesses for more information about the Rust memory model
|
||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
= note: BACKTRACE (of the first span) on thread `unnamed-ID`:
|
||||
= note: inside closure at tests/fail/data_race/mixed_size_read.rs:LL:CC
|
||||
= note: inside closure at tests/fail/data_race/mixed_size_read_write.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
error: Undefined Behavior: Race condition detected between (1) 2-byte atomic store on thread `unnamed-ID` and (2) 1-byte atomic store on thread `unnamed-ID` at ALLOC. (2) just happened here
|
||||
--> tests/fail/data_race/mixed_size_write.rs:LL:CC
|
||||
--> tests/fail/data_race/mixed_size_write_write.rs:LL:CC
|
||||
|
|
||||
LL | a8[0].store(1, Ordering::SeqCst);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Race condition detected between (1) 2-byte atomic store on thread `unnamed-ID` and (2) 1-byte atomic store on thread `unnamed-ID` at ALLOC. (2) just happened here
|
||||
|
|
||||
help: and (1) occurred earlier here
|
||||
--> tests/fail/data_race/mixed_size_write.rs:LL:CC
|
||||
--> tests/fail/data_race/mixed_size_write_write.rs:LL:CC
|
||||
|
|
||||
LL | a16.store(1, Ordering::SeqCst);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -14,7 +14,7 @@ LL | a16.store(1, Ordering::SeqCst);
|
|||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
= note: BACKTRACE (of the first span) on thread `unnamed-ID`:
|
||||
= note: inside closure at tests/fail/data_race/mixed_size_write.rs:LL:CC
|
||||
= note: inside closure at tests/fail/data_race/mixed_size_write_write.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
//@compile-flags: -Zmiri-preemption-rate=0.0
|
||||
// Avoid accidental synchronization via address reuse inside `thread::spawn`.
|
||||
//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0
|
||||
|
||||
use std::sync::atomic::{AtomicU16, Ordering};
|
||||
use std::thread;
|
||||
|
||||
// Make sure races between atomic and non-atomic reads are detected.
|
||||
// This seems harmless but C++ does not allow them, so we can't allow them for now either.
|
||||
// This test coverse the case where the non-atomic access come first.
|
||||
fn main() {
|
||||
let a = AtomicU16::new(0);
|
||||
|
||||
thread::scope(|s| {
|
||||
s.spawn(|| {
|
||||
let ptr = &a as *const AtomicU16 as *mut u16;
|
||||
unsafe { ptr.read() };
|
||||
});
|
||||
s.spawn(|| {
|
||||
thread::yield_now();
|
||||
|
||||
// We also put a non-atomic access here, but that should *not* be reported.
|
||||
let ptr = &a as *const AtomicU16 as *mut u16;
|
||||
unsafe { ptr.read() };
|
||||
// Then do the atomic access.
|
||||
a.load(Ordering::SeqCst);
|
||||
//~^ ERROR: Data race detected between (1) non-atomic read on thread `unnamed-1` and (2) atomic load on thread `unnamed-2`
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
error: Undefined Behavior: Data race detected between (1) non-atomic read on thread `unnamed-ID` and (2) atomic load on thread `unnamed-ID` at ALLOC. (2) just happened here
|
||||
--> tests/fail/data_race/read_read_race1.rs:LL:CC
|
||||
|
|
||||
LL | a.load(Ordering::SeqCst);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between (1) non-atomic read on thread `unnamed-ID` and (2) atomic load on thread `unnamed-ID` at ALLOC. (2) just happened here
|
||||
|
|
||||
help: and (1) occurred earlier here
|
||||
--> tests/fail/data_race/read_read_race1.rs:LL:CC
|
||||
|
|
||||
LL | unsafe { ptr.read() };
|
||||
| ^^^^^^^^^^
|
||||
= help: overlapping atomic and non-atomic accesses must be synchronized, even if both are read-only
|
||||
= help: see https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#memory-model-for-atomic-accesses for more information about the Rust memory model
|
||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
= note: BACKTRACE (of the first span) on thread `unnamed-ID`:
|
||||
= note: inside closure at tests/fail/data_race/read_read_race1.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
//@compile-flags: -Zmiri-preemption-rate=0.0
|
||||
// Avoid accidental synchronization via address reuse inside `thread::spawn`.
|
||||
//@compile-flags: -Zmiri-address-reuse-cross-thread-rate=0
|
||||
|
||||
use std::sync::atomic::{AtomicU16, Ordering};
|
||||
use std::thread;
|
||||
|
||||
// Make sure races between atomic and non-atomic reads are detected.
|
||||
// This seems harmless but C++ does not allow them, so we can't allow them for now either.
|
||||
// This test coverse the case where the atomic access come first.
|
||||
fn main() {
|
||||
let a = AtomicU16::new(0);
|
||||
|
||||
thread::scope(|s| {
|
||||
s.spawn(|| {
|
||||
// We also put a non-atomic access here, but that should *not* be reported.
|
||||
let ptr = &a as *const AtomicU16 as *mut u16;
|
||||
unsafe { ptr.read() };
|
||||
// Then do the atomic access.
|
||||
a.load(Ordering::SeqCst);
|
||||
});
|
||||
s.spawn(|| {
|
||||
thread::yield_now();
|
||||
|
||||
let ptr = &a as *const AtomicU16 as *mut u16;
|
||||
unsafe { ptr.read() };
|
||||
//~^ ERROR: Data race detected between (1) atomic load on thread `unnamed-1` and (2) non-atomic read on thread `unnamed-2`
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
error: Undefined Behavior: Data race detected between (1) atomic load on thread `unnamed-ID` and (2) non-atomic read on thread `unnamed-ID` at ALLOC. (2) just happened here
|
||||
--> tests/fail/data_race/read_read_race2.rs:LL:CC
|
||||
|
|
||||
LL | unsafe { ptr.read() };
|
||||
| ^^^^^^^^^^ Data race detected between (1) atomic load on thread `unnamed-ID` and (2) non-atomic read on thread `unnamed-ID` at ALLOC. (2) just happened here
|
||||
|
|
||||
help: and (1) occurred earlier here
|
||||
--> tests/fail/data_race/read_read_race2.rs:LL:CC
|
||||
|
|
||||
LL | a.load(Ordering::SeqCst);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
= help: overlapping atomic and non-atomic accesses must be synchronized, even if both are read-only
|
||||
= help: see https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#memory-model-for-atomic-accesses for more information about the Rust memory model
|
||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
= note: BACKTRACE (of the first span) on thread `unnamed-ID`:
|
||||
= note: inside closure at tests/fail/data_race/read_read_race2.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
// We want to control preemption here.
|
||||
// Avoid accidental synchronization via address reuse.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-address-reuse-cross-thread-rate=0
|
||||
|
||||
#![feature(core_intrinsics)]
|
||||
|
||||
use std::ptr;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use std::sync::atomic::Ordering::*;
|
||||
use std::thread::spawn;
|
||||
|
||||
fn static_atomic_u32(val: u32) -> &'static AtomicU32 {
|
||||
let ret = Box::leak(Box::new(AtomicU32::new(val)));
|
||||
ret
|
||||
}
|
||||
|
||||
fn split_u32_ptr(dword: *const u32) -> *const [u16; 2] {
|
||||
unsafe { std::mem::transmute::<*const u32, *const [u16; 2]>(dword) }
|
||||
}
|
||||
|
||||
// Wine's SRWLock implementation does this, which is definitely undefined in C++ memory model
|
||||
// https://github.com/wine-mirror/wine/blob/303f8042f9db508adaca02ef21f8de4992cb9c03/dlls/ntdll/sync.c#L543-L566
|
||||
// It probably works just fine on x86, but Intel does document this as "don't do it!"
|
||||
pub fn main() {
|
||||
let x = static_atomic_u32(0);
|
||||
let j1 = spawn(move || {
|
||||
x.store(1, Relaxed);
|
||||
});
|
||||
|
||||
let j2 = spawn(move || {
|
||||
let x_ptr = x as *const AtomicU32 as *const u32;
|
||||
let x_split = split_u32_ptr(x_ptr);
|
||||
unsafe {
|
||||
let hi = ptr::addr_of!((*x_split)[0]);
|
||||
std::intrinsics::atomic_load_relaxed(hi); //~ ERROR: (1) 4-byte atomic store on thread `unnamed-1` and (2) 2-byte atomic load
|
||||
}
|
||||
});
|
||||
|
||||
j1.join().unwrap();
|
||||
j2.join().unwrap();
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
// We want to control preemption here.
|
||||
// Avoid accidental synchronization via address reuse.
|
||||
//@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-address-reuse-cross-thread-rate=0
|
||||
|
||||
use std::sync::atomic::Ordering::*;
|
||||
use std::sync::atomic::{AtomicU16, AtomicU32};
|
||||
use std::thread::spawn;
|
||||
|
||||
fn static_atomic(val: u32) -> &'static AtomicU32 {
|
||||
let ret = Box::leak(Box::new(AtomicU32::new(val)));
|
||||
ret
|
||||
}
|
||||
|
||||
fn split_u32_ptr(dword: *const u32) -> *const [u16; 2] {
|
||||
unsafe { std::mem::transmute::<*const u32, *const [u16; 2]>(dword) }
|
||||
}
|
||||
|
||||
// Racing mixed size reads may cause two loads to read-from
|
||||
// the same store but observe different values, which doesn't make
|
||||
// sense under the formal model so we forbid this.
|
||||
pub fn main() {
|
||||
let x = static_atomic(0);
|
||||
|
||||
let j1 = spawn(move || {
|
||||
x.load(Relaxed);
|
||||
});
|
||||
|
||||
let j2 = spawn(move || {
|
||||
let x_ptr = x as *const AtomicU32 as *const u32;
|
||||
let x_split = split_u32_ptr(x_ptr);
|
||||
unsafe {
|
||||
let hi = x_split as *const u16 as *const AtomicU16;
|
||||
(*hi).load(Relaxed); //~ ERROR: (1) 4-byte atomic load on thread `unnamed-1` and (2) 2-byte atomic load
|
||||
}
|
||||
});
|
||||
|
||||
j1.join().unwrap();
|
||||
j2.join().unwrap();
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
error: Undefined Behavior: Race condition detected between (1) 4-byte atomic load on thread `unnamed-ID` and (2) 2-byte atomic load on thread `unnamed-ID` at ALLOC. (2) just happened here
|
||||
--> tests/fail/weak_memory/racing_mixed_size_read.rs:LL:CC
|
||||
|
|
||||
LL | (*hi).load(Relaxed);
|
||||
| ^^^^^^^^^^^^^^^^^^^ Race condition detected between (1) 4-byte atomic load on thread `unnamed-ID` and (2) 2-byte atomic load on thread `unnamed-ID` at ALLOC. (2) just happened here
|
||||
|
|
||||
help: and (1) occurred earlier here
|
||||
--> tests/fail/weak_memory/racing_mixed_size_read.rs:LL:CC
|
||||
|
|
||||
LL | x.load(Relaxed);
|
||||
| ^^^^^^^^^^^^^^^
|
||||
= help: overlapping unsynchronized atomic accesses must use the same access size
|
||||
= help: see https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#memory-model-for-atomic-accesses for more information about the Rust memory model
|
||||
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||
= note: BACKTRACE (of the first span) on thread `unnamed-ID`:
|
||||
= note: inside closure at tests/fail/weak_memory/racing_mixed_size_read.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
//@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0
|
||||
|
||||
use std::sync::atomic::*;
|
||||
use std::thread::spawn;
|
||||
use std::thread::{self, spawn};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct EvilSend<T>(pub T);
|
||||
|
@ -143,10 +143,84 @@ fn test_local_variable_lazy_write() {
|
|||
assert_eq!(val, 127);
|
||||
}
|
||||
|
||||
// This test coverse the case where the non-atomic access come first.
|
||||
fn test_read_read_race1() {
|
||||
let a = AtomicU16::new(0);
|
||||
|
||||
thread::scope(|s| {
|
||||
s.spawn(|| {
|
||||
let ptr = &a as *const AtomicU16 as *mut u16;
|
||||
unsafe { ptr.read() };
|
||||
});
|
||||
s.spawn(|| {
|
||||
thread::yield_now();
|
||||
|
||||
a.load(Ordering::SeqCst);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// This test coverse the case where the atomic access come first.
|
||||
fn test_read_read_race2() {
|
||||
let a = AtomicU16::new(0);
|
||||
|
||||
thread::scope(|s| {
|
||||
s.spawn(|| {
|
||||
a.load(Ordering::SeqCst);
|
||||
});
|
||||
s.spawn(|| {
|
||||
thread::yield_now();
|
||||
|
||||
let ptr = &a as *const AtomicU16 as *mut u16;
|
||||
unsafe { ptr.read() };
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn mixed_size_read_read() {
|
||||
fn convert(a: &AtomicU16) -> &[AtomicU8; 2] {
|
||||
unsafe { std::mem::transmute(a) }
|
||||
}
|
||||
|
||||
let a = AtomicU16::new(0);
|
||||
let a16 = &a;
|
||||
let a8 = convert(a16);
|
||||
|
||||
// Just two different-sized atomic reads without any writes are fine.
|
||||
thread::scope(|s| {
|
||||
s.spawn(|| {
|
||||
a16.load(Ordering::SeqCst);
|
||||
});
|
||||
s.spawn(|| {
|
||||
a8[0].load(Ordering::SeqCst);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn failing_rmw_is_read() {
|
||||
let a = AtomicUsize::new(0);
|
||||
thread::scope(|s| {
|
||||
s.spawn(|| unsafe {
|
||||
// Non-atomic read.
|
||||
let _val = *(&a as *const AtomicUsize).cast::<usize>();
|
||||
});
|
||||
|
||||
s.spawn(|| {
|
||||
// RMW that will fail.
|
||||
// This is not considered a write, so there is no data race here.
|
||||
a.compare_exchange(1, 2, Ordering::SeqCst, Ordering::SeqCst).unwrap_err();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
test_fence_sync();
|
||||
test_multiple_reads();
|
||||
test_rmw_no_block();
|
||||
test_simple_release();
|
||||
test_local_variable_lazy_write();
|
||||
test_read_read_race1();
|
||||
test_read_read_race2();
|
||||
mixed_size_read_read();
|
||||
failing_rmw_is_read();
|
||||
}
|
||||
|
|
|
@ -7,12 +7,16 @@
|
|||
use run_make_support::{cmd, run_in_tmpdir, rustc};
|
||||
|
||||
fn main() {
|
||||
let check = |ty: &str| {
|
||||
rustc().crate_name("foo").crate_type(ty).input("foo.rs").run();
|
||||
cmd("readelf").arg("-d").arg("libfoo.so").run()
|
||||
};
|
||||
run_in_tmpdir(|| {
|
||||
rustc().crate_name("foo").crate_type("dylib").input("foo.rs").run();
|
||||
cmd("readelf")
|
||||
.arg("-d")
|
||||
.arg("libfoo.so")
|
||||
.run()
|
||||
.assert_stdout_contains("Library soname: [libfoo.so]");
|
||||
// Rust dylibs should get a relative SONAME
|
||||
check("dylib").assert_stdout_contains("Library soname: [libfoo.so]");
|
||||
});
|
||||
run_in_tmpdir(|| {
|
||||
// C dylibs should not implicitly get any SONAME
|
||||
check("cdylib").assert_stdout_not_contains("Library soname:");
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue