Skip to content

Commit 56f24e0

Browse files
committed
Auto merge of rust-lang#150488 - JonathanBrouwer:rollup-9ugn7py, r=JonathanBrouwer
Rollup of 2 pull requests Successful merges: - rust-lang#150394 (Accommodate LLVM PassPlugin rename) - rust-lang#150483 (miri subtree update) r? `@ghost` `@rustbot` modify labels: rollup
2 parents 8cd58dd + 2c89d1c commit 56f24e0

File tree

13 files changed

+354
-19
lines changed

13 files changed

+354
-19
lines changed

compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@
2323
#include "llvm/MC/TargetRegistry.h"
2424
#include "llvm/Object/ObjectFile.h"
2525
#include "llvm/Passes/PassBuilder.h"
26+
#if LLVM_VERSION_GE(22, 0)
27+
#include "llvm/Plugins/PassPlugin.h"
28+
#else
2629
#include "llvm/Passes/PassPlugin.h"
30+
#endif
2731
#include "llvm/Passes/StandardInstrumentations.h"
2832
#include "llvm/Support/CBindingWrapping.h"
2933
#include "llvm/Support/FileSystem.h"

src/tools/miri/rust-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
cb79c42008b970269f6a06b257e5f04b93f24d03
1+
7fefa09b90ca57b8a0e0e4717d672d38a0ae58b5

src/tools/miri/src/bin/miri.rs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -710,20 +710,18 @@ fn main() {
710710
if !miri_config.native_lib.is_empty() && miri_config.provenance_mode == ProvenanceMode::Strict {
711711
fatal_error!("strict provenance is not compatible with calling native functions");
712712
}
713+
// Native calls and many-seeds are an "intersting" combination.
714+
if !miri_config.native_lib.is_empty() && many_seeds.is_some() {
715+
eprintln!(
716+
"warning: `-Zmiri-many-seeds` runs multiple instances of the program in the same address space, \
717+
so if the native library has global state, it will leak across execution bundaries"
718+
);
719+
}
713720
// You can set either one seed or many.
714721
if many_seeds.is_some() && miri_config.seed.is_some() {
715722
fatal_error!("Only one of `-Zmiri-seed` and `-Zmiri-many-seeds can be set");
716723
}
717-
718-
// Ensure we have parallelism for many-seeds mode.
719-
if many_seeds.is_some() && !rustc_args.iter().any(|arg| arg.starts_with("-Zthreads=")) {
720-
// Clamp to 20 threads; things get a less efficient beyond that due to lock contention.
721-
let threads = std::thread::available_parallelism().map_or(1, |n| n.get()).min(20);
722-
rustc_args.push(format!("-Zthreads={threads}"));
723-
}
724-
let many_seeds =
725-
many_seeds.map(|seeds| ManySeedsConfig { seeds, keep_going: many_seeds_keep_going });
726-
724+
// We cannot emulate weak memory without the data race detector.
727725
if miri_config.weak_memory_emulation && !miri_config.data_race_detector {
728726
fatal_error!(
729727
"Weak memory emulation cannot be enabled when the data race detector is disabled"
@@ -737,6 +735,15 @@ fn main() {
737735
fatal_error!("Invalid settings: {err}");
738736
}
739737

738+
// Ensure we have parallelism for many-seeds mode.
739+
if many_seeds.is_some() && !rustc_args.iter().any(|arg| arg.starts_with("-Zthreads=")) {
740+
// Clamp to 20 threads; things get a less efficient beyond that due to lock contention.
741+
let threads = std::thread::available_parallelism().map_or(1, |n| n.get()).min(20);
742+
rustc_args.push(format!("-Zthreads={threads}"));
743+
}
744+
let many_seeds =
745+
many_seeds.map(|seeds| ManySeedsConfig { seeds, keep_going: many_seeds_keep_going });
746+
740747
debug!("rustc arguments: {:?}", rustc_args);
741748
debug!("crate arguments: {:?}", miri_config.args);
742749
if !miri_config.native_lib.is_empty() && miri_config.native_lib_enable_tracing {

src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,7 @@ impl<'tcx> Tree {
661661
global,
662662
ChildrenVisitMode::VisitChildrenOfAccessed,
663663
&diagnostics,
664+
/* min_exposed_child */ None, // only matters for protector end access,
664665
)?;
665666
}
666667
interp_ok(())
@@ -686,6 +687,13 @@ impl<'tcx> Tree {
686687

687688
let source_idx = self.tag_mapping.get(&tag).unwrap();
688689

690+
let min_exposed_child = if self.roots.len() > 1 {
691+
LocationTree::get_min_exposed_child(source_idx, &self.nodes)
692+
} else {
693+
// There's no point in computing this when there is just one tree.
694+
None
695+
};
696+
689697
// This is a special access through the entire allocation.
690698
// It actually only affects `accessed` locations, so we need
691699
// to filter on those before initiating the traversal.
@@ -716,6 +724,7 @@ impl<'tcx> Tree {
716724
global,
717725
ChildrenVisitMode::SkipChildrenOfAccessed,
718726
&diagnostics,
727+
min_exposed_child,
719728
)?;
720729
}
721730
}
@@ -876,9 +885,36 @@ impl Tree {
876885
}
877886

878887
impl<'tcx> LocationTree {
888+
/// Returns the smallest exposed tag, if any, that is a transitive child of `root`.
889+
fn get_min_exposed_child(root: UniIndex, nodes: &UniValMap<Node>) -> Option<BorTag> {
890+
// We cannot use the wildcard datastructure to improve this lookup. This is because
891+
// the datastructure only tracks enabled nodes and we need to also consider disabled ones.
892+
let mut stack = vec![root];
893+
let mut min_tag = None;
894+
while let Some(idx) = stack.pop() {
895+
let node = nodes.get(idx).unwrap();
896+
if min_tag.is_some_and(|min| min < node.tag) {
897+
// The minimum we found before is bigger than this tag, and therefore
898+
// also bigger than all its children, so we can skip this subtree.
899+
continue;
900+
}
901+
stack.extend_from_slice(node.children.as_slice());
902+
if node.is_exposed {
903+
min_tag = match min_tag {
904+
Some(prev) if prev < node.tag => Some(prev),
905+
_ => Some(node.tag),
906+
};
907+
}
908+
}
909+
min_tag
910+
}
911+
879912
/// Performs an access on this location.
880913
/// * `access_source`: The index, if any, where the access came from.
881914
/// * `visit_children`: Whether to skip updating the children of `access_source`.
915+
/// * `min_exposed_child`: The tag of the smallest exposed (transitive) child of the accessed node.
916+
/// This is only used with `visit_children == SkipChildrenOfAccessed`, where we need to skip children
917+
/// of the accessed node.
882918
fn perform_access(
883919
&mut self,
884920
roots: impl Iterator<Item = UniIndex>,
@@ -888,6 +924,7 @@ impl<'tcx> LocationTree {
888924
global: &GlobalState,
889925
visit_children: ChildrenVisitMode,
890926
diagnostics: &DiagnosticInfo,
927+
min_exposed_child: Option<BorTag>,
891928
) -> InterpResult<'tcx> {
892929
let accessed_root = if let Some(idx) = access_source {
893930
Some(self.perform_normal_access(
@@ -906,11 +943,22 @@ impl<'tcx> LocationTree {
906943
};
907944

908945
let accessed_root_tag = accessed_root.map(|idx| nodes.get(idx).unwrap().tag);
909-
if matches!(visit_children, ChildrenVisitMode::SkipChildrenOfAccessed) {
910-
// FIXME: approximate which roots could be children of the accessed node and only skip them instead of all other trees.
911-
return interp_ok(());
912-
}
913946
for root in roots {
947+
let tag = nodes.get(root).unwrap().tag;
948+
// On a protector release access we have to skip the children of the accessed tag.
949+
// However, if the tag has exposed children then some of the wildcard subtrees could
950+
// also be children of the accessed node and would also need to be skipped. We can
951+
// narrow down which wildcard trees might be children by comparing their root tag to the
952+
// minimum exposed child of the accessed node. As the parent tag is always smaller
953+
// than the child tag this means we only need to skip subtrees with a root tag larger
954+
// than `min_exposed_child`. Once we find such a root, we can leave the loop because roots
955+
// are sorted by tag.
956+
if matches!(visit_children, ChildrenVisitMode::SkipChildrenOfAccessed)
957+
&& let Some(min_exposed_child) = min_exposed_child
958+
&& tag > min_exposed_child
959+
{
960+
break;
961+
}
914962
// We don't perform a wildcard access on the tree we already performed a
915963
// normal access on.
916964
if Some(root) == accessed_root {

src/tools/miri/src/concurrency/data_race.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,6 +1116,7 @@ impl VClockAlloc {
11161116
{
11171117
(AccessType::AtomicStore, idx, &atomic.write_vector)
11181118
} else if !access.is_atomic() &&
1119+
!access.is_read() &&
11191120
let Some(atomic) = mem_clocks.atomic() &&
11201121
let Some(idx) = Self::find_gt_index(&atomic.read_vector, &active_clocks.clock)
11211122
{
@@ -1124,7 +1125,7 @@ impl VClockAlloc {
11241125
} else if mem_clocks.write.1 > active_clocks.clock[mem_clocks.write.0] {
11251126
write_clock = mem_clocks.write();
11261127
(AccessType::NaWrite(mem_clocks.write_type), mem_clocks.write.0, &write_clock)
1127-
} else if let Some(idx) = Self::find_gt_index(&mem_clocks.read, &active_clocks.clock) {
1128+
} else if !access.is_read() && let Some(idx) = Self::find_gt_index(&mem_clocks.read, &active_clocks.clock) {
11281129
(AccessType::NaRead(mem_clocks.read[idx].read_type()), idx, &mem_clocks.read)
11291130
// Finally, mixed-size races.
11301131
} else if access.is_atomic() && let Some(atomic) = mem_clocks.atomic() && atomic.size != Some(access_size) {
@@ -1157,7 +1158,9 @@ impl VClockAlloc {
11571158
assert!(!involves_non_atomic);
11581159
Some("overlapping unsynchronized atomic accesses must use the same access size")
11591160
} else if access.is_read() && other_access.is_read() {
1160-
panic!("there should be no same-size read-read races")
1161+
panic!(
1162+
"there should be no same-size read-read races\naccess: {access:?}\nother_access: {other_access:?}"
1163+
)
11611164
} else {
11621165
None
11631166
};

src/tools/miri/src/concurrency/vector_clock.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,24 @@ const SMALL_VECTOR: usize = 4;
4848
/// The time-stamps recorded in the data-race detector consist of both
4949
/// a 32-bit unsigned integer which is the actual timestamp, and a `Span`
5050
/// so that diagnostics can report what code was responsible for an operation.
51-
#[derive(Clone, Copy, Debug)]
51+
#[derive(Clone, Copy)]
5252
pub(super) struct VTimestamp {
5353
/// The lowest bit indicates read type, the rest is the time.
5454
/// `1` indicates a retag read, `0` a regular read.
5555
time_and_read_type: u32,
5656
pub span: Span,
5757
}
5858

59+
impl Debug for VTimestamp {
60+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61+
f.debug_struct("VTimestamp")
62+
.field("time", &self.time())
63+
.field("read_type", &self.read_type())
64+
.field("span", &self.span)
65+
.finish()
66+
}
67+
}
68+
5969
impl VTimestamp {
6070
pub const ZERO: VTimestamp = VTimestamp::new(0, NaReadType::Read, DUMMY_SP);
6171

src/tools/miri/src/diagnostics.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ pub fn report_leaks<'tcx>(
548548
/// We want to present a multi-line span message for some errors. Diagnostics do not support this
549549
/// directly, so we pass the lines as a `Vec<String>` and display each line after the first with an
550550
/// additional `span_label` or `note` call.
551-
pub fn report_msg<'tcx>(
551+
fn report_msg<'tcx>(
552552
diag_level: DiagLevel,
553553
title: String,
554554
span_msg: Vec<String>,
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//@compile-flags: -Zmiri-deterministic-concurrency
2+
// A case that is not covered by `mixed_size_read_write`.
3+
#![feature(ptr_as_ref_unchecked)]
4+
5+
use std::sync::atomic::*;
6+
use std::thread;
7+
8+
fn main() {
9+
let data = AtomicI32::new(0);
10+
11+
thread::scope(|s| {
12+
s.spawn(|| unsafe {
13+
let _val = (&raw const data).read();
14+
let _val = (&raw const data).cast::<AtomicI8>().as_ref_unchecked().compare_exchange(
15+
0,
16+
1,
17+
Ordering::Relaxed,
18+
Ordering::Relaxed,
19+
);
20+
thread::yield_now();
21+
unreachable!();
22+
});
23+
s.spawn(|| {
24+
let _val = data.load(Ordering::Relaxed); //~ERROR: /Race condition detected between \(1\) 1-byte atomic store .* and \(2\) 4-byte atomic load/
25+
});
26+
});
27+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
error: Undefined Behavior: Race condition detected between (1) 1-byte atomic store on thread `unnamed-ID` and (2) 4-byte atomic load on thread `unnamed-ID` at ALLOC
2+
--> tests/fail/data_race/mixed_size_read_write_read.rs:LL:CC
3+
|
4+
LL | ... let _val = data.load(Ordering::Relaxed);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (2) just happened here
6+
|
7+
help: and (1) occurred earlier here
8+
--> tests/fail/data_race/mixed_size_read_write_read.rs:LL:CC
9+
|
10+
LL | let _val = (&raw const data).cast::<AtomicI8>().as_ref_unchecked().compare_exchange(
11+
| ________________________^
12+
LL | | 0,
13+
LL | | 1,
14+
LL | | Ordering::Relaxed,
15+
LL | | Ordering::Relaxed,
16+
LL | | );
17+
| |_____________^
18+
= help: overlapping unsynchronized atomic accesses must use the same access size
19+
= 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
20+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
21+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
22+
23+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
24+
25+
error: aborting due to 1 previous error
26+
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//@compile-flags: -Zmiri-tree-borrows -Zmiri-permissive-provenance
2+
use std::cell::UnsafeCell;
3+
4+
/// This is a variant of the test in `../protector-write-lazy.rs`, but with
5+
/// wildcard references.
6+
/// Checks that a protector release access correctly determines that certain tags
7+
/// cannot be children of the protected tag and that it updates them accordingly.
8+
///
9+
/// For this version we know the tag is not a child because its wildcard root has
10+
/// a smaller tag then the released reference.
11+
pub fn main() {
12+
// We need two locations so that we can create a new reference
13+
// that is foreign to an already active tag.
14+
let mut x: UnsafeCell<[u32; 2]> = UnsafeCell::new([32, 33]);
15+
let ref1 = &mut x;
16+
let cell_ptr = ref1.get() as *mut u32;
17+
18+
let int = ref1 as *mut UnsafeCell<[u32; 2]> as usize;
19+
let wild = int as *mut UnsafeCell<u32>;
20+
21+
let ref2 = unsafe { &mut *cell_ptr };
22+
23+
// `ref3` gets created before the protected ref `arg4`.
24+
let ref3 = unsafe { &mut *wild.wrapping_add(1) };
25+
26+
let protect = |arg4: &mut u32| {
27+
// Activates arg4. This would disable ref3 at [0] if it wasn't a cell.
28+
*arg4 = 41;
29+
30+
// Creates an exposed child of arg4.
31+
let ref5 = &mut *arg4;
32+
let _int = ref5 as *mut u32 as usize;
33+
34+
// This creates ref6 from ref3 at [1], so that it doesn't disable arg4 at [0].
35+
let ref6 = unsafe { &mut *ref3.get() };
36+
37+
// ┌───────────┐
38+
// │ ref1* │
39+
// │ Cel │ Cel │ *
40+
// └─────┬─────┘ │
41+
// │ │
42+
// │ │
43+
// ▼ ▼
44+
// ┌───────────┐ ┌───────────┐
45+
// │ ref2│ │ │ │ ref3│
46+
// │ Act │ Res │ │ Cel │ Cel │
47+
// └─────┬─────┘ └─────┬─────┘
48+
// │ │
49+
// │ │
50+
// ▼ ▼
51+
// ┌───────────┐ ┌───────────┐
52+
// │ arg4│ │ │ │ ref6│
53+
// │ Act │ Res │ │ Res │ Res │
54+
// └─────┬─────┘ └───────────┘
55+
// │
56+
// │
57+
// ▼
58+
// ┌───────────┐
59+
// │ref5*| │
60+
// │ Res │ Res │
61+
// └───────────┘
62+
63+
// Creates a pointer to [0] with the provenance of ref6.
64+
return (ref6 as *mut u32).wrapping_sub(1);
65+
66+
// Protector release on arg4 happens here.
67+
// This should cause a foreign write on all foreign nodes,
68+
// unless they could be a child of arg4.
69+
// arg4 has an exposed child, so some tags with a wildcard
70+
// ancestor could be its children.
71+
// However, the root of ref6 was created before arg4, so it
72+
// cannot be a child of it. So it should get disabled
73+
// (at location [0]).
74+
};
75+
// ptr has provenance of ref6
76+
let ptr = protect(ref2);
77+
// ref6 is disabled at [0].
78+
let _fail = unsafe { *ptr }; //~ ERROR: /read access through .* is forbidden/
79+
}

0 commit comments

Comments
 (0)