Skip to content

Commit 5a47c25

Browse files
committed
deduplicate warnings globally
1 parent 88057ce commit 5a47c25

File tree

11 files changed

+88
-149
lines changed

11 files changed

+88
-149
lines changed

src/alloc_addresses/mod.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use rustc_middle::ty::TyCtxt;
1313
pub use self::address_generator::AddressGenerator;
1414
use self::reuse_pool::ReusePool;
1515
use crate::concurrency::VClock;
16+
use crate::diagnostics::SpanDedupDiagnostic;
1617
use crate::*;
1718

1819
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -341,12 +342,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
341342
match global_state.provenance_mode {
342343
ProvenanceMode::Default => {
343344
// The first time this happens at a particular location, print a warning.
344-
let mut int2ptr_warned = this.machine.int2ptr_warned.borrow_mut();
345-
let first = int2ptr_warned.is_empty();
346-
if int2ptr_warned.insert(this.cur_span()) {
347-
// Newly inserted, so first time we see this span.
348-
this.emit_diagnostic(NonHaltingDiagnostic::Int2Ptr { details: first });
349-
}
345+
static DEDUP: SpanDedupDiagnostic = SpanDedupDiagnostic::new();
346+
this.dedup_diagnostic(&DEDUP, |first| {
347+
NonHaltingDiagnostic::Int2Ptr { details: first }
348+
});
350349
}
351350
ProvenanceMode::Strict => {
352351
throw_machine_stop!(TerminationInfo::Int2PtrWithStrictProvenance);

src/borrow_tracker/stacked_borrows/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ mod item;
66
mod stack;
77

88
use std::fmt::Write;
9+
use std::sync::atomic::AtomicBool;
910
use std::{cmp, mem};
1011

1112
use rustc_abi::{BackendRepr, Size};
@@ -822,7 +823,8 @@ trait EvalContextPrivExt<'tcx, 'ecx>: crate::MiriInterpCxExt<'tcx> {
822823
let size = match size {
823824
Some(size) => size,
824825
None => {
825-
if !this.machine.sb_extern_type_warned.replace(true) {
826+
static DEDUP: AtomicBool = AtomicBool::new(false);
827+
if !DEDUP.swap(true, std::sync::atomic::Ordering::Relaxed) {
826828
this.emit_diagnostic(NonHaltingDiagnostic::ExternTypeReborrow);
827829
}
828830
return interp_ok(place.clone());

src/concurrency/genmc/helper.rs

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,19 @@
1-
use std::sync::RwLock;
2-
31
use genmc_sys::{MemOrdering, RMWBinOp};
42
use rustc_abi::Size;
53
use rustc_const_eval::interpret::{InterpResult, interp_ok};
6-
use rustc_data_structures::fx::FxHashSet;
74
use rustc_middle::mir;
85
use rustc_middle::mir::interpret;
96
use rustc_middle::ty::ScalarInt;
10-
use rustc_span::Span;
117
use tracing::debug;
128

139
use super::GenmcScalar;
1410
use crate::alloc_addresses::EvalContextExt as _;
15-
use crate::diagnostics::EvalContextExt as _;
1611
use crate::intrinsics::AtomicRmwOp;
17-
use crate::{
18-
AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, BorTag, GenmcCtx, InterpCx,
19-
MiriInterpCx, MiriMachine, NonHaltingDiagnostic, Scalar, machine, throw_unsup_format,
20-
};
12+
use crate::*;
2113

2214
/// Maximum size memory access in bytes that GenMC supports.
2315
pub(super) const MAX_ACCESS_SIZE: u64 = 8;
2416

25-
/// Type for storing spans for already emitted warnings.
26-
pub(super) type WarningCache = RwLock<FxHashSet<Span>>;
27-
28-
#[derive(Default)]
29-
pub(super) struct Warnings {
30-
pub(super) compare_exchange_failure_ordering: WarningCache,
31-
pub(super) compare_exchange_weak: WarningCache,
32-
}
33-
34-
/// Emit a warning if it hasn't already been reported for current span.
35-
pub(super) fn emit_warning<'tcx>(
36-
ecx: &InterpCx<'tcx, MiriMachine<'tcx>>,
37-
cache: &WarningCache,
38-
diagnostic: impl FnOnce() -> NonHaltingDiagnostic,
39-
) {
40-
// FIXME: This is not the right span to use (it's always inside the local crates so if the same
41-
// operation is invoked from multiple places it will warn multiple times). `cur_span` is not
42-
// right either though (we should honor `#[track_caller]`). Ultimately what we want is "the
43-
// primary span the warning would point at".
44-
let span = ecx.machine.current_user_relevant_span();
45-
if cache.read().unwrap().contains(&span) {
46-
return;
47-
}
48-
// This span has not yet been reported, so we insert it into the cache and report it.
49-
let mut cache = cache.write().unwrap();
50-
if cache.insert(span) {
51-
// Some other thread may have added this span while we didn't hold the lock, so we only emit it if the insertions succeeded.
52-
ecx.emit_diagnostic(diagnostic());
53-
}
54-
}
55-
5617
/// This function is used to split up a large memory access into aligned, non-overlapping chunks of a limited size.
5718
/// Returns an iterator over the chunks, yielding `(base address, size)` of each chunk, ordered by address.
5819
pub fn split_access(address: Size, size: Size) -> impl Iterator<Item = (u64, u64)> {

src/concurrency/genmc/mod.rs

Lines changed: 24 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,15 @@ use tracing::{debug, info};
1414

1515
use self::global_allocations::{EvalContextExt as _, GlobalAllocationHandler};
1616
use self::helper::{
17-
MAX_ACCESS_SIZE, Warnings, emit_warning, genmc_scalar_to_scalar,
18-
maybe_upgrade_compare_exchange_success_orderings, scalar_to_genmc_scalar, to_genmc_rmw_op,
17+
MAX_ACCESS_SIZE, genmc_scalar_to_scalar, maybe_upgrade_compare_exchange_success_orderings,
18+
scalar_to_genmc_scalar, to_genmc_rmw_op,
1919
};
2020
use self::run::GenmcMode;
2121
use self::thread_id_map::ThreadIdMap;
2222
use crate::concurrency::genmc::helper::split_access;
23+
use crate::diagnostics::SpanDedupDiagnostic;
2324
use crate::intrinsics::AtomicRmwOp;
24-
use crate::{
25-
AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, MemoryKind, MiriConfig,
26-
MiriMachine, MiriMemoryKind, NonHaltingDiagnostic, Scalar, TerminationInfo, ThreadId,
27-
ThreadManager, VisitProvenance, VisitWith,
28-
};
25+
use crate::*;
2926

3027
mod config;
3128
mod global_allocations;
@@ -104,18 +101,11 @@ struct GlobalState {
104101
/// Keep track of global allocations, to ensure they keep the same address across different executions, even if the order of allocations changes.
105102
/// The `AllocId` for globals is stable across executions, so we can use it as an identifier.
106103
global_allocations: GlobalAllocationHandler,
107-
108-
/// Cache for which warnings have already been shown to the user.
109-
/// `None` if warnings are disabled.
110-
warning_cache: Option<Warnings>,
111104
}
112105

113106
impl GlobalState {
114-
fn new(target_usize_max: u64, print_warnings: bool) -> Self {
115-
Self {
116-
global_allocations: GlobalAllocationHandler::new(target_usize_max),
117-
warning_cache: print_warnings.then(Default::default),
118-
}
107+
fn new(target_usize_max: u64) -> Self {
108+
Self { global_allocations: GlobalAllocationHandler::new(target_usize_max) }
119109
}
120110
}
121111

@@ -412,27 +402,24 @@ impl GenmcCtx {
412402
let upgraded_success_ordering =
413403
maybe_upgrade_compare_exchange_success_orderings(success, fail);
414404

415-
if let Some(warning_cache) = &self.global_state.warning_cache {
416-
// FIXME(genmc): remove once GenMC supports failure memory ordering in `compare_exchange`.
417-
let (effective_failure_ordering, _) =
418-
upgraded_success_ordering.split_memory_orderings();
419-
// Return a warning if the actual orderings don't match the upgraded ones.
420-
if success != upgraded_success_ordering || effective_failure_ordering != fail {
421-
emit_warning(ecx, &warning_cache.compare_exchange_failure_ordering, || {
422-
NonHaltingDiagnostic::GenmcCompareExchangeOrderingMismatch {
423-
success_ordering: success,
424-
upgraded_success_ordering,
425-
failure_ordering: fail,
426-
effective_failure_ordering,
427-
}
428-
});
429-
}
430-
// FIXME(genmc): remove once GenMC implements spurious failures for `compare_exchange_weak`.
431-
if can_fail_spuriously {
432-
emit_warning(ecx, &warning_cache.compare_exchange_weak, || {
433-
NonHaltingDiagnostic::GenmcCompareExchangeWeak
434-
});
435-
}
405+
// FIXME(genmc): remove once GenMC supports failure memory ordering in `compare_exchange`.
406+
let (effective_failure_ordering, _) = upgraded_success_ordering.split_memory_orderings();
407+
// Return a warning if the actual orderings don't match the upgraded ones.
408+
if success != upgraded_success_ordering || effective_failure_ordering != fail {
409+
static DEDUP: SpanDedupDiagnostic = SpanDedupDiagnostic::new();
410+
ecx.dedup_diagnostic(&DEDUP, |_first| {
411+
NonHaltingDiagnostic::GenmcCompareExchangeOrderingMismatch {
412+
success_ordering: success,
413+
upgraded_success_ordering,
414+
failure_ordering: fail,
415+
effective_failure_ordering,
416+
}
417+
});
418+
}
419+
// FIXME(genmc): remove once GenMC implements spurious failures for `compare_exchange_weak`.
420+
if can_fail_spuriously {
421+
static DEDUP: SpanDedupDiagnostic = SpanDedupDiagnostic::new();
422+
ecx.dedup_diagnostic(&DEDUP, |_first| NonHaltingDiagnostic::GenmcCompareExchangeWeak);
436423
}
437424

438425
debug!(

src/concurrency/genmc/run.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,6 @@ pub(super) enum GenmcMode {
1616
Verification,
1717
}
1818

19-
impl GenmcMode {
20-
/// Return whether warnings on unsupported features should be printed in this mode.
21-
fn print_unsupported_warnings(self) -> bool {
22-
self == GenmcMode::Verification
23-
}
24-
}
25-
2619
/// Do a complete run of the program in GenMC mode.
2720
/// This will call `eval_entry` multiple times, until either:
2821
/// - An error is detected (indicated by a `None` return value)
@@ -57,8 +50,7 @@ fn run_genmc_mode_impl<'tcx>(
5750
// There exists only one `global_state` per full run in GenMC mode.
5851
// It is shared by all `GenmcCtx` in this run.
5952
// FIXME(genmc): implement multithreading once GenMC supports it.
60-
let global_state =
61-
Arc::new(GlobalState::new(tcx.target_usize_max(), mode.print_unsupported_warnings()));
53+
let global_state = Arc::new(GlobalState::new(tcx.target_usize_max()));
6254
let genmc_ctx = Rc::new(GenmcCtx::new(config, global_state, mode));
6355

6456
// `rep` is used to report the progress, Miri will panic on wrap-around.

src/diagnostics.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use std::fmt::{self, Write};
22
use std::num::NonZero;
3+
use std::sync::Mutex;
34

45
use rustc_abi::{Align, Size};
56
use rustc_errors::{Diag, DiagMessage, Level};
6-
use rustc_span::{DUMMY_SP, SpanData, Symbol};
7+
use rustc_hash::FxHashSet;
8+
use rustc_span::{DUMMY_SP, Span, SpanData, Symbol};
79

810
use crate::borrow_tracker::stacked_borrows::diagnostics::TagHistory;
911
use crate::borrow_tracker::tree_borrows::diagnostics as tree_diagnostics;
@@ -835,4 +837,45 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
835837
&this.machine,
836838
);
837839
}
840+
841+
/// Call `f` only if this is the first time we are seeing this span.
842+
/// The `first` parameter indicates whether this is the first time *ever* that this diagnostic
843+
/// is emitted.
844+
fn dedup_diagnostic(
845+
&self,
846+
dedup: &SpanDedupDiagnostic,
847+
f: impl FnOnce(/*first*/ bool) -> NonHaltingDiagnostic,
848+
) {
849+
let this = self.eval_context_ref();
850+
// We want to deduplicate both based on where the error seems to be located "from the user
851+
// perspective", and the location of the actual operation (to avoid warning about the same
852+
// operation called from different places in the local code).
853+
let span1 = this.machine.current_user_relevant_span();
854+
// For the "location of the operation", we still skip `track_caller` frames, to match the
855+
// span that the diagnostic will point at.
856+
let span2 = this
857+
.active_thread_stack()
858+
.iter()
859+
.rev()
860+
.find(|frame| !frame.instance().def.requires_caller_location(*this.tcx))
861+
.map(|frame| frame.current_span())
862+
.unwrap_or(span1);
863+
864+
let mut lock = dedup.0.lock().unwrap();
865+
let first = lock.is_empty();
866+
// Avoid mutating the hashset unless both spans are new.
867+
if !lock.contains(&span2) && lock.insert(span1) && (span1 == span2 || lock.insert(span2)) {
868+
// Both of the two spans were newly inserted.
869+
this.emit_diagnostic(f(first));
870+
}
871+
}
872+
}
873+
874+
/// Helps deduplicate a diagnostic to ensure it is only shown once per span.
875+
pub struct SpanDedupDiagnostic(Mutex<FxHashSet<Span>>);
876+
877+
impl SpanDedupDiagnostic {
878+
pub const fn new() -> Self {
879+
Self(Mutex::new(FxHashSet::with_hasher(rustc_hash::FxBuildHasher)))
880+
}
838881
}

src/helpers.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
use std::num::NonZero;
2+
use std::sync::Mutex;
23
use std::time::Duration;
34
use std::{cmp, iter};
45

56
use rand::RngCore;
67
use rustc_abi::{Align, ExternAbi, FieldIdx, FieldsShape, Size, Variants};
78
use rustc_apfloat::Float;
89
use rustc_apfloat::ieee::{Double, Half, Quad, Single};
10+
use rustc_hash::FxHashSet;
911
use rustc_hir::Safety;
1012
use rustc_hir::def::{DefKind, Namespace};
1113
use rustc_hir::def_id::{CRATE_DEF_INDEX, CrateNum, DefId, LOCAL_CRATE};
@@ -650,7 +652,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
650652
match reject_with {
651653
RejectOpWith::Abort => isolation_abort_error(op_name),
652654
RejectOpWith::WarningWithoutBacktrace => {
653-
let mut emitted_warnings = this.machine.reject_in_isolation_warned.borrow_mut();
655+
// Deduplicate these warnings *by shim* (not by span)
656+
static DEDUP: Mutex<FxHashSet<String>> =
657+
Mutex::new(FxHashSet::with_hasher(rustc_hash::FxBuildHasher));
658+
let mut emitted_warnings = DEDUP.lock().unwrap();
654659
if !emitted_warnings.contains(op_name) {
655660
// First time we are seeing this.
656661
emitted_warnings.insert(op_name.to_owned());

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ extern crate rustc_ast;
6060
extern crate rustc_const_eval;
6161
extern crate rustc_data_structures;
6262
extern crate rustc_errors;
63+
extern crate rustc_hash;
6364
extern crate rustc_hir;
6465
extern crate rustc_index;
6566
extern crate rustc_middle;

src/machine.rs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -649,16 +649,6 @@ pub struct MiriMachine<'tcx> {
649649
pub(crate) pthread_rwlock_sanity: Cell<bool>,
650650
pub(crate) pthread_condvar_sanity: Cell<bool>,
651651

652-
/// Remembers whether we already warned about an extern type with Stacked Borrows.
653-
pub(crate) sb_extern_type_warned: Cell<bool>,
654-
/// Remember whether we already warned about sharing memory with a native call.
655-
#[allow(unused)]
656-
pub(crate) native_call_mem_warned: Cell<bool>,
657-
/// Remembers which shims have already shown the warning about erroring in isolation.
658-
pub(crate) reject_in_isolation_warned: RefCell<FxHashSet<String>>,
659-
/// Remembers which int2ptr casts we have already warned about.
660-
pub(crate) int2ptr_warned: RefCell<FxHashSet<Span>>,
661-
662652
/// Cache for `mangle_internal_symbol`.
663653
pub(crate) mangle_internal_symbol_cache: FxHashMap<&'static str, String>,
664654

@@ -826,10 +816,6 @@ impl<'tcx> MiriMachine<'tcx> {
826816
pthread_mutex_sanity: Cell::new(false),
827817
pthread_rwlock_sanity: Cell::new(false),
828818
pthread_condvar_sanity: Cell::new(false),
829-
sb_extern_type_warned: Cell::new(false),
830-
native_call_mem_warned: Cell::new(false),
831-
reject_in_isolation_warned: Default::default(),
832-
int2ptr_warned: Default::default(),
833819
mangle_internal_symbol_cache: Default::default(),
834820
force_intrinsic_fallback: config.force_intrinsic_fallback,
835821
float_nondet: config.float_nondet,
@@ -1003,10 +989,6 @@ impl VisitProvenance for MiriMachine<'_> {
1003989
pthread_mutex_sanity: _,
1004990
pthread_rwlock_sanity: _,
1005991
pthread_condvar_sanity: _,
1006-
sb_extern_type_warned: _,
1007-
native_call_mem_warned: _,
1008-
reject_in_isolation_warned: _,
1009-
int2ptr_warned: _,
1010992
mangle_internal_symbol_cache: _,
1011993
force_intrinsic_fallback: _,
1012994
float_nondet: _,

src/shims/native_lib/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Implements calling functions from a native library.
22
33
use std::ops::Deref;
4+
use std::sync::atomic::AtomicBool;
45

56
use libffi::low::CodePtr;
67
use libffi::middle::Type as FfiType;
@@ -279,8 +280,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
279280

280281
// Helper to print a warning when a pointer is shared with the native code.
281282
let expose = |prov: Provenance| -> InterpResult<'tcx> {
282-
// The first time this happens, print a warning.
283-
if !this.machine.native_call_mem_warned.replace(true) {
283+
static DEDUP: AtomicBool = AtomicBool::new(false);
284+
if !DEDUP.swap(true, std::sync::atomic::Ordering::Relaxed) {
284285
// Newly set, so first time we get here.
285286
this.emit_diagnostic(NonHaltingDiagnostic::NativeCallSharedMem { tracing });
286287
}

0 commit comments

Comments
 (0)