Skip to content

Commit e7da309

Browse files
authored
Merge pull request #4578 from Patrick-6/miri-genmc-cas
Add compare_exchange support for GenMC mode
2 parents 71c8206 + a00b17a commit e7da309

27 files changed

+850
-54
lines changed

genmc-sys/cpp/include/MiriInterface.hpp

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ struct SchedulingResult;
3434
struct LoadResult;
3535
struct StoreResult;
3636
struct ReadModifyWriteResult;
37+
struct CompareExchangeResult;
3738

3839
// GenMC uses `int` for its thread IDs.
3940
using ThreadId = int;
@@ -100,6 +101,17 @@ struct MiriGenmcShim : private GenMCDriver {
100101
GenmcScalar rhs_value,
101102
GenmcScalar old_val
102103
);
104+
[[nodiscard]] CompareExchangeResult handle_compare_exchange(
105+
ThreadId thread_id,
106+
uint64_t address,
107+
uint64_t size,
108+
GenmcScalar expected_value,
109+
GenmcScalar new_value,
110+
GenmcScalar old_val,
111+
MemOrdering success_ordering,
112+
MemOrdering fail_load_ordering,
113+
bool can_fail_spuriously
114+
);
103115
[[nodiscard]] StoreResult handle_store(
104116
ThreadId thread_id,
105117
uint64_t address,
@@ -231,15 +243,15 @@ constexpr auto get_global_alloc_static_mask() -> uint64_t {
231243
namespace GenmcScalarExt {
232244
inline GenmcScalar uninit() {
233245
return GenmcScalar {
234-
/* value: */ 0,
235-
/* is_init: */ false,
246+
.value = 0,
247+
.is_init = false,
236248
};
237249
}
238250

239251
inline GenmcScalar from_sval(SVal sval) {
240252
return GenmcScalar {
241-
/* value: */ sval.get(),
242-
/* is_init: */ true,
253+
.value = sval.get(),
254+
.is_init = true,
243255
};
244256
}
245257

@@ -252,22 +264,22 @@ inline SVal to_sval(GenmcScalar scalar) {
252264
namespace LoadResultExt {
253265
inline LoadResult no_value() {
254266
return LoadResult {
255-
/* error: */ std::unique_ptr<std::string>(nullptr),
256-
/* has_value: */ false,
257-
/* read_value: */ GenmcScalarExt::uninit(),
267+
.error = std::unique_ptr<std::string>(nullptr),
268+
.has_value = false,
269+
.read_value = GenmcScalarExt::uninit(),
258270
};
259271
}
260272

261273
inline LoadResult from_value(SVal read_value) {
262-
return LoadResult { /* error: */ std::unique_ptr<std::string>(nullptr),
263-
/* has_value: */ true,
264-
/* read_value: */ GenmcScalarExt::from_sval(read_value) };
274+
return LoadResult { .error = std::unique_ptr<std::string>(nullptr),
275+
.has_value = true,
276+
.read_value = GenmcScalarExt::from_sval(read_value) };
265277
}
266278

267279
inline LoadResult from_error(std::unique_ptr<std::string> error) {
268-
return LoadResult { /* error: */ std::move(error),
269-
/* has_value: */ false,
270-
/* read_value: */ GenmcScalarExt::uninit() };
280+
return LoadResult { .error = std::move(error),
281+
.has_value = false,
282+
.read_value = GenmcScalarExt::uninit() };
271283
}
272284
} // namespace LoadResultExt
273285

@@ -278,26 +290,50 @@ inline StoreResult ok(bool is_coherence_order_maximal_write) {
278290
}
279291

280292
inline StoreResult from_error(std::unique_ptr<std::string> error) {
281-
return StoreResult { /* error: */ std::move(error),
282-
/* is_coherence_order_maximal_write: */ false };
293+
return StoreResult { .error = std::move(error), .is_coherence_order_maximal_write = false };
283294
}
284295
} // namespace StoreResultExt
285296

286297
namespace ReadModifyWriteResultExt {
287298
inline ReadModifyWriteResult
288299
ok(SVal old_value, SVal new_value, bool is_coherence_order_maximal_write) {
289-
return ReadModifyWriteResult { /* error: */ std::unique_ptr<std::string>(nullptr),
290-
/* old_value: */ GenmcScalarExt::from_sval(old_value),
291-
/* new_value: */ GenmcScalarExt::from_sval(new_value),
292-
is_coherence_order_maximal_write };
300+
return ReadModifyWriteResult { .error = std::unique_ptr<std::string>(nullptr),
301+
.old_value = GenmcScalarExt::from_sval(old_value),
302+
.new_value = GenmcScalarExt::from_sval(new_value),
303+
.is_coherence_order_maximal_write =
304+
is_coherence_order_maximal_write };
293305
}
294306

295307
inline ReadModifyWriteResult from_error(std::unique_ptr<std::string> error) {
296-
return ReadModifyWriteResult { /* error: */ std::move(error),
297-
/* old_value: */ GenmcScalarExt::uninit(),
298-
/* new_value: */ GenmcScalarExt::uninit(),
299-
/* is_coherence_order_maximal_write: */ false };
308+
return ReadModifyWriteResult { .error = std::move(error),
309+
.old_value = GenmcScalarExt::uninit(),
310+
.new_value = GenmcScalarExt::uninit(),
311+
.is_coherence_order_maximal_write = false };
300312
}
301313
} // namespace ReadModifyWriteResultExt
302314

315+
namespace CompareExchangeResultExt {
316+
inline CompareExchangeResult success(SVal old_value, bool is_coherence_order_maximal_write) {
317+
return CompareExchangeResult { .error = nullptr,
318+
.old_value = GenmcScalarExt::from_sval(old_value),
319+
.is_success = true,
320+
.is_coherence_order_maximal_write =
321+
is_coherence_order_maximal_write };
322+
}
323+
324+
inline CompareExchangeResult failure(SVal old_value) {
325+
return CompareExchangeResult { .error = nullptr,
326+
.old_value = GenmcScalarExt::from_sval(old_value),
327+
.is_success = false,
328+
.is_coherence_order_maximal_write = false };
329+
}
330+
331+
inline CompareExchangeResult from_error(std::unique_ptr<std::string> error) {
332+
return CompareExchangeResult { .error = std::move(error),
333+
.old_value = GenmcScalarExt::uninit(),
334+
.is_success = false,
335+
.is_coherence_order_maximal_write = false };
336+
}
337+
} // namespace CompareExchangeResultExt
338+
303339
#endif /* GENMC_MIRI_INTERFACE_HPP */

genmc-sys/cpp/src/MiriInterface/EventHandling.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,71 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) {
155155
);
156156
}
157157

158+
[[nodiscard]] auto MiriGenmcShim::handle_compare_exchange(
159+
ThreadId thread_id,
160+
uint64_t address,
161+
uint64_t size,
162+
GenmcScalar expected_value,
163+
GenmcScalar new_value,
164+
GenmcScalar old_val,
165+
MemOrdering success_ordering,
166+
MemOrdering fail_load_ordering,
167+
bool can_fail_spuriously
168+
) -> CompareExchangeResult {
169+
// NOTE: Both the store and load events should get the same `ordering`, it should not be split
170+
// into a load and a store component. This means we can have for example `AcqRel` loads and
171+
// stores, but this is intended for CAS operations.
172+
173+
// FIXME(GenMC): properly handle failure memory ordering.
174+
175+
auto expectedVal = GenmcScalarExt::to_sval(expected_value);
176+
auto new_val = GenmcScalarExt::to_sval(new_value);
177+
178+
const auto load_ret = handle_load_reset_if_none<EventLabel::EventLabelKind::CasRead>(
179+
thread_id,
180+
success_ordering,
181+
SAddr(address),
182+
ASize(size),
183+
AType::Unsigned, // The type is only used for printing.
184+
expectedVal,
185+
new_val
186+
);
187+
if (const auto* err = std::get_if<VerificationError>(&load_ret))
188+
return CompareExchangeResultExt::from_error(format_error(*err));
189+
const auto* ret_val = std::get_if<SVal>(&load_ret);
190+
ERROR_ON(nullptr == ret_val, "Unimplemented: load returned unexpected result.");
191+
const auto read_old_val = *ret_val;
192+
if (read_old_val != expectedVal)
193+
return CompareExchangeResultExt::failure(read_old_val);
194+
195+
// FIXME(GenMC): Add support for modelling spurious failures.
196+
197+
const auto storePos = inc_pos(thread_id);
198+
const auto store_ret = GenMCDriver::handleStore<EventLabel::EventLabelKind::CasWrite>(
199+
storePos,
200+
success_ordering,
201+
SAddr(address),
202+
ASize(size),
203+
AType::Unsigned, // The type is only used for printing.
204+
new_val
205+
);
206+
if (const auto* err = std::get_if<VerificationError>(&store_ret))
207+
return CompareExchangeResultExt::from_error(format_error(*err));
208+
const auto* store_ret_val = std::get_if<std::monostate>(&store_ret);
209+
ERROR_ON(
210+
nullptr == store_ret_val,
211+
"Unimplemented: compare-exchange store returned unexpected result."
212+
);
213+
214+
// FIXME(genmc,mixed-accesses): Use the value that GenMC returns from handleStore (once
215+
// available).
216+
const auto& g = getExec().getGraph();
217+
return CompareExchangeResultExt::success(
218+
read_old_val,
219+
/* is_coherence_order_maximal_write */ g.co_max(SAddr(address))->getPos() == storePos
220+
);
221+
}
222+
158223
/**** Memory (de)allocation ****/
159224

160225
auto MiriGenmcShim::handle_malloc(ThreadId thread_id, uint64_t size, uint64_t alignment)

genmc-sys/cpp/src/MiriInterface/Setup.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,10 @@ static auto to_genmc_verbosity_level(const LogLevel log_level) -> VerbosityLevel
152152
// Miri already ensures that memory accesses are valid, so this check doesn't matter.
153153
// We check that the address is static, but skip checking if it is part of an actual
154154
// allocation.
155-
/* isStaticallyAllocated: */ [](SAddr addr) { return addr.isStatic(); },
155+
.isStaticallyAllocated = [](SAddr addr) { return addr.isStatic(); },
156156
// FIXME(genmc,error reporting): Once a proper a proper API for passing such information is
157157
// implemented in GenMC, Miri should use it to improve the produced error messages.
158-
/* getStaticName: */ [](SAddr addr) { return "[UNKNOWN STATIC]"; },
158+
.getStaticName = [](SAddr addr) { return "[UNKNOWN STATIC]"; },
159159
// This function is called to get the initial value stored at the given address.
160160
//
161161
// From a Miri perspective, this API doesn't work very well: most memory starts out
@@ -177,10 +177,10 @@ static auto to_genmc_verbosity_level(const LogLevel log_level) -> VerbosityLevel
177177
// FIXME(genmc): implement proper support for uninitialized memory in GenMC. Ideally, the
178178
// initial value getter would return an `optional<SVal>`, since the memory location may be
179179
// uninitialized.
180-
/* initValGetter: */ [](const AAccess& a) { return SVal(0xDEAD); },
180+
.initValGetter = [](const AAccess& a) { return SVal(0xDEAD); },
181181
// Miri serves non-atomic loads from its own memory and these GenMC checks are wrong in
182182
// that case. This should no longer be required with proper mixed-size access support.
183-
/* skipUninitLoadChecks: */ [](MemOrdering ord) { return ord == MemOrdering::NotAtomic; },
183+
.skipUninitLoadChecks = [](MemOrdering ord) { return ord == MemOrdering::NotAtomic; },
184184
};
185185
driver->setInterpCallbacks(std::move(interpreter_callbacks));
186186

genmc-sys/src/lib.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,19 @@ mod ffi {
198198
is_coherence_order_maximal_write: bool,
199199
}
200200

201+
#[must_use]
202+
#[derive(Debug)]
203+
struct CompareExchangeResult {
204+
/// If there was an error, it will be stored in `error`, otherwise it is `None`.
205+
error: UniquePtr<CxxString>,
206+
/// The value that was read by the compare-exchange.
207+
old_value: GenmcScalar,
208+
/// `true` if compare_exchange op was successful.
209+
is_success: bool,
210+
/// `true` if the write should also be reflected in Miri's memory representation.
211+
is_coherence_order_maximal_write: bool,
212+
}
213+
201214
/**** These are GenMC types that we have to copy-paste here since cxx does not support
202215
"importing" externally defined C++ types. ****/
203216

@@ -313,6 +326,18 @@ mod ffi {
313326
rhs_value: GenmcScalar,
314327
old_value: GenmcScalar,
315328
) -> ReadModifyWriteResult;
329+
fn handle_compare_exchange(
330+
self: Pin<&mut MiriGenmcShim>,
331+
thread_id: i32,
332+
address: u64,
333+
size: u64,
334+
expected_value: GenmcScalar,
335+
new_value: GenmcScalar,
336+
old_value: GenmcScalar,
337+
success_ordering: MemOrdering,
338+
fail_load_ordering: MemOrdering,
339+
can_fail_spuriously: bool,
340+
) -> CompareExchangeResult;
316341
fn handle_store(
317342
self: Pin<&mut MiriGenmcShim>,
318343
thread_id: i32,

src/concurrency/genmc/helper.rs

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,52 @@
1+
use std::sync::RwLock;
2+
13
use genmc_sys::{MemOrdering, RMWBinOp};
24
use rustc_abi::Size;
35
use rustc_const_eval::interpret::{InterpResult, interp_ok};
6+
use rustc_data_structures::fx::FxHashSet;
47
use rustc_middle::mir;
58
use rustc_middle::ty::ScalarInt;
9+
use rustc_span::Span;
610
use tracing::debug;
711

812
use super::GenmcScalar;
13+
use crate::diagnostics::EvalContextExt;
914
use crate::intrinsics::AtomicRmwOp;
1015
use crate::{
11-
AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, MiriInterpCx, Scalar,
12-
throw_unsup_format,
16+
AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, InterpCx, MiriInterpCx,
17+
MiriMachine, NonHaltingDiagnostic, Scalar, throw_unsup_format,
1318
};
1419

1520
/// Maximum size memory access in bytes that GenMC supports.
1621
pub(super) const MAX_ACCESS_SIZE: u64 = 8;
1722

23+
/// Type for storing spans for already emitted warnings.
24+
pub(super) type WarningCache = RwLock<FxHashSet<Span>>;
25+
26+
#[derive(Default)]
27+
pub(super) struct Warnings {
28+
pub(super) compare_exchange_failure_ordering: WarningCache,
29+
pub(super) compare_exchange_weak: WarningCache,
30+
}
31+
32+
/// Emit a warning if it hasn't already been reported for current span.
33+
pub(super) fn emit_warning<'tcx>(
34+
ecx: &InterpCx<'tcx, MiriMachine<'tcx>>,
35+
cache: &WarningCache,
36+
diagnostic: impl FnOnce() -> NonHaltingDiagnostic,
37+
) {
38+
let span = ecx.machine.current_span();
39+
if cache.read().unwrap().contains(&span) {
40+
return;
41+
}
42+
// This span has not yet been reported, so we insert it into the cache and report it.
43+
let mut cache = cache.write().unwrap();
44+
if cache.insert(span) {
45+
// Some other thread may have added this span while we didn't hold the lock, so we only emit it if the insertions succeeded.
46+
ecx.emit_diagnostic(diagnostic());
47+
}
48+
}
49+
1850
/// This function is used to split up a large memory access into aligned, non-overlapping chunks of a limited size.
1951
/// Returns an iterator over the chunks, yielding `(base address, size)` of each chunk, ordered by address.
2052
pub fn split_access(address: Size, size: Size) -> impl Iterator<Item = (u64, u64)> {
@@ -112,7 +144,53 @@ impl AtomicFenceOrd {
112144
}
113145
}
114146

147+
/// Since GenMC ignores the failure memory ordering and Miri should not detect bugs that don't actually exist, we upgrade the success ordering if required.
148+
/// This means that Miri running in GenMC mode will not explore all possible executions allowed under the RC11 memory model.
149+
/// FIXME(genmc): remove this once GenMC properly supports the failure memory ordering.
150+
pub(super) fn maybe_upgrade_compare_exchange_success_orderings(
151+
success: AtomicRwOrd,
152+
failure: AtomicReadOrd,
153+
) -> AtomicRwOrd {
154+
use AtomicReadOrd::*;
155+
let (success_read, success_write) = success.split_memory_orderings();
156+
let upgraded_success_read = match (success_read, failure) {
157+
(_, SeqCst) | (SeqCst, _) => SeqCst,
158+
(Acquire, _) | (_, Acquire) => Acquire,
159+
(Relaxed, Relaxed) => Relaxed,
160+
};
161+
AtomicRwOrd::from_split_memory_orderings(upgraded_success_read, success_write)
162+
}
163+
115164
impl AtomicRwOrd {
165+
/// Split up an atomic read-write memory ordering into a separate read and write ordering.
166+
pub(super) fn split_memory_orderings(self) -> (AtomicReadOrd, AtomicWriteOrd) {
167+
match self {
168+
AtomicRwOrd::Relaxed => (AtomicReadOrd::Relaxed, AtomicWriteOrd::Relaxed),
169+
AtomicRwOrd::Acquire => (AtomicReadOrd::Acquire, AtomicWriteOrd::Relaxed),
170+
AtomicRwOrd::Release => (AtomicReadOrd::Relaxed, AtomicWriteOrd::Release),
171+
AtomicRwOrd::AcqRel => (AtomicReadOrd::Acquire, AtomicWriteOrd::Release),
172+
AtomicRwOrd::SeqCst => (AtomicReadOrd::SeqCst, AtomicWriteOrd::SeqCst),
173+
}
174+
}
175+
176+
/// Split up an atomic read-write memory ordering into a separate read and write ordering.
177+
fn from_split_memory_orderings(
178+
read_ordering: AtomicReadOrd,
179+
write_ordering: AtomicWriteOrd,
180+
) -> Self {
181+
match (read_ordering, write_ordering) {
182+
(AtomicReadOrd::Relaxed, AtomicWriteOrd::Relaxed) => AtomicRwOrd::Relaxed,
183+
(AtomicReadOrd::Acquire, AtomicWriteOrd::Relaxed) => AtomicRwOrd::Acquire,
184+
(AtomicReadOrd::Relaxed, AtomicWriteOrd::Release) => AtomicRwOrd::Release,
185+
(AtomicReadOrd::Acquire, AtomicWriteOrd::Release) => AtomicRwOrd::AcqRel,
186+
(AtomicReadOrd::SeqCst, AtomicWriteOrd::SeqCst) => AtomicRwOrd::SeqCst,
187+
_ =>
188+
panic!(
189+
"Unsupported memory ordering combination ({read_ordering:?}, {write_ordering:?})"
190+
),
191+
}
192+
}
193+
116194
pub(super) fn to_genmc(self) -> MemOrdering {
117195
match self {
118196
AtomicRwOrd::Relaxed => MemOrdering::Relaxed,

0 commit comments

Comments
 (0)