Skip to content

Commit 4e48c1a

Browse files
authored
Merge pull request #4614 from Patrick-6/miri-genmc-mutex
Implement std::sync::Mutex interception for GenMC mode.
2 parents 8fd150c + 8923247 commit 4e48c1a

27 files changed

+838
-122
lines changed

genmc-sys/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ fn compile_cpp_dependencies(genmc_path: &Path, always_configure: bool) {
233233
let cpp_files = [
234234
"MiriInterface/EventHandling.cpp",
235235
"MiriInterface/Exploration.cpp",
236+
"MiriInterface/Mutex.cpp",
236237
"MiriInterface/Setup.cpp",
237238
"MiriInterface/ThreadManagement.cpp",
238239
]

genmc-sys/cpp/include/MiriInterface.hpp

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
// GenMC headers:
1414
#include "ExecutionGraph/EventLabel.hpp"
15-
#include "Static/ModuleID.hpp"
1615
#include "Support/MemOrdering.hpp"
1716
#include "Support/RMWOps.hpp"
1817
#include "Verification/Config.hpp"
@@ -36,6 +35,7 @@ struct LoadResult;
3635
struct StoreResult;
3736
struct ReadModifyWriteResult;
3837
struct CompareExchangeResult;
38+
struct MutexLockResult;
3939

4040
// GenMC uses `int` for its thread IDs.
4141
using ThreadId = int;
@@ -136,10 +136,13 @@ struct MiriGenmcShim : private GenMCDriver {
136136

137137
/**** Blocking instructions ****/
138138
/// Inform GenMC that the thread should be blocked.
139-
/// Note: this function is currently hardcoded for `AssumeType::User`, corresponding to user
140-
/// supplied assume statements. This can become a parameter once more types of assumes are
141-
/// added.
142-
void handle_assume_block(ThreadId thread_id);
139+
void handle_assume_block(ThreadId thread_id, AssumeType assume_type);
140+
141+
/**** Mutex handling ****/
142+
auto handle_mutex_lock(ThreadId thread_id, uint64_t address, uint64_t size) -> MutexLockResult;
143+
auto handle_mutex_try_lock(ThreadId thread_id, uint64_t address, uint64_t size)
144+
-> MutexLockResult;
145+
auto handle_mutex_unlock(ThreadId thread_id, uint64_t address, uint64_t size) -> StoreResult;
143146

144147
/***** Exploration related functionality *****/
145148

@@ -358,4 +361,22 @@ inline CompareExchangeResult from_error(std::unique_ptr<std::string> error) {
358361
}
359362
} // namespace CompareExchangeResultExt
360363

364+
namespace MutexLockResultExt {
365+
inline MutexLockResult ok(bool is_lock_acquired) {
366+
return MutexLockResult { /* error: */ nullptr, /* is_reset: */ false, is_lock_acquired };
367+
}
368+
369+
inline MutexLockResult reset() {
370+
return MutexLockResult { /* error: */ nullptr,
371+
/* is_reset: */ true,
372+
/* is_lock_acquired: */ false };
373+
}
374+
375+
inline MutexLockResult from_error(std::unique_ptr<std::string> error) {
376+
return MutexLockResult { /* error: */ std::move(error),
377+
/* is_reset: */ false,
378+
/* is_lock_acquired: */ false };
379+
}
380+
} // namespace MutexLockResultExt
381+
361382
#endif /* GENMC_MIRI_INTERFACE_HPP */

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@
3232

3333
/**** Blocking instructions ****/
3434

35-
void MiriGenmcShim::handle_assume_block(ThreadId thread_id) {
36-
GenMCDriver::handleAssume(inc_pos(thread_id), AssumeType::User);
35+
void MiriGenmcShim::handle_assume_block(ThreadId thread_id, AssumeType assume_type) {
36+
BUG_ON(getExec().getGraph().isThreadBlocked(thread_id));
37+
GenMCDriver::handleAssume(inc_pos(thread_id), assume_type);
3738
}
3839

3940
/**** Memory access handling ****/
@@ -59,6 +60,7 @@ void MiriGenmcShim::handle_assume_block(ThreadId thread_id) {
5960
if (const auto* err = std::get_if<VerificationError>(&ret))
6061
return LoadResultExt::from_error(format_error(*err));
6162
const auto* ret_val = std::get_if<SVal>(&ret);
63+
// FIXME(genmc): handle `HandleResult::{Invalid, Reset}` return values.
6264
if (ret_val == nullptr)
6365
ERROR("Unimplemented: load returned unexpected result.");
6466
return LoadResultExt::from_value(*ret_val);
@@ -88,6 +90,7 @@ void MiriGenmcShim::handle_assume_block(ThreadId thread_id) {
8890
return StoreResultExt::from_error(format_error(*err));
8991

9092
const bool* is_coherence_order_maximal_write = std::get_if<bool>(&ret);
93+
// FIXME(genmc): handle `HandleResult::{Invalid, Reset}` return values.
9194
ERROR_ON(
9295
nullptr == is_coherence_order_maximal_write,
9396
"Unimplemented: Store returned unexpected result."
@@ -130,6 +133,7 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) {
130133
return ReadModifyWriteResultExt::from_error(format_error(*err));
131134

132135
const auto* ret_val = std::get_if<SVal>(&load_ret);
136+
// FIXME(genmc): handle `HandleResult::{Invalid, Reset}` return values.
133137
if (nullptr == ret_val) {
134138
ERROR("Unimplemented: read-modify-write returned unexpected result.");
135139
}
@@ -151,6 +155,7 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) {
151155
return ReadModifyWriteResultExt::from_error(format_error(*err));
152156

153157
const bool* is_coherence_order_maximal_write = std::get_if<bool>(&store_ret);
158+
// FIXME(genmc): handle `HandleResult::{Invalid, Reset}` return values.
154159
ERROR_ON(
155160
nullptr == is_coherence_order_maximal_write,
156161
"Unimplemented: RMW store returned unexpected result."
@@ -195,6 +200,7 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) {
195200
if (const auto* err = std::get_if<VerificationError>(&load_ret))
196201
return CompareExchangeResultExt::from_error(format_error(*err));
197202
const auto* ret_val = std::get_if<SVal>(&load_ret);
203+
// FIXME(genmc): handle `HandleResult::{Invalid, Reset}` return values.
198204
ERROR_ON(nullptr == ret_val, "Unimplemented: load returned unexpected result.");
199205
const auto read_old_val = *ret_val;
200206
if (read_old_val != expectedVal)
@@ -215,6 +221,7 @@ void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) {
215221
if (const auto* err = std::get_if<VerificationError>(&store_ret))
216222
return CompareExchangeResultExt::from_error(format_error(*err));
217223
const bool* is_coherence_order_maximal_write = std::get_if<bool>(&store_ret);
224+
// FIXME(genmc): handle `HandleResult::{Invalid, Reset}` return values.
218225
ERROR_ON(
219226
nullptr == is_coherence_order_maximal_write,
220227
"Unimplemented: compare-exchange store returned unexpected result."
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/** This file contains functionality related to handling mutexes. */
2+
3+
#include "MiriInterface.hpp"
4+
5+
// GenMC headers:
6+
#include "Static/ModuleID.hpp"
7+
8+
// CXX.rs generated headers:
9+
#include "genmc-sys/src/lib.rs.h"
10+
11+
#define MUTEX_UNLOCKED SVal(0)
12+
#define MUTEX_LOCKED SVal(1)
13+
14+
auto MiriGenmcShim::handle_mutex_lock(ThreadId thread_id, uint64_t address, uint64_t size)
15+
-> MutexLockResult {
16+
// This annotation informs GenMC about the condition required to make this lock call succeed.
17+
// It stands for `value_read_by_load != MUTEX_LOCKED`.
18+
const auto size_bits = size * 8;
19+
const auto annot = std::move(Annotation(
20+
AssumeType::Spinloop,
21+
Annotation::ExprVP(
22+
NeExpr<ModuleID::ID>::create(
23+
// `RegisterExpr` marks the value of the current expression, i.e., the loaded value.
24+
// The `id` is ignored by GenMC; it is only used by the LLI frontend to substitute
25+
// other variables from previous expressions that may be used here.
26+
RegisterExpr<ModuleID::ID>::create(size_bits, /* id */ 0),
27+
ConcreteExpr<ModuleID::ID>::create(size_bits, MUTEX_LOCKED)
28+
)
29+
.release()
30+
)
31+
));
32+
33+
// As usual, we need to tell GenMC which value was stored at this location before this atomic
34+
// access, if there previously was a non-atomic initializing access. We set the initial state of
35+
// a mutex to be "unlocked".
36+
const auto old_val = MUTEX_UNLOCKED;
37+
const auto load_ret = handle_load_reset_if_none<EventLabel::EventLabelKind::LockCasRead>(
38+
thread_id,
39+
old_val,
40+
address,
41+
size,
42+
annot,
43+
EventDeps()
44+
);
45+
if (const auto* err = std::get_if<VerificationError>(&load_ret))
46+
return MutexLockResultExt::from_error(format_error(*err));
47+
// If we get a `Reset`, GenMC decided that this lock operation should not yet run, since it
48+
// would not acquire the mutex. Like the handling of the case further down where we read a `1`
49+
// ("Mutex already locked"), Miri should call the handle function again once the current thread
50+
// is scheduled by GenMC the next time.
51+
if (std::holds_alternative<Reset>(load_ret))
52+
return MutexLockResultExt::reset();
53+
54+
const auto* ret_val = std::get_if<SVal>(&load_ret);
55+
ERROR_ON(!ret_val, "Unimplemented: mutex lock returned unexpected result.");
56+
ERROR_ON(
57+
*ret_val != MUTEX_UNLOCKED && *ret_val != MUTEX_LOCKED,
58+
"Mutex read value was neither 0 nor 1"
59+
);
60+
const bool is_lock_acquired = *ret_val == MUTEX_UNLOCKED;
61+
if (is_lock_acquired) {
62+
const auto store_ret = GenMCDriver::handleStore<EventLabel::EventLabelKind::LockCasWrite>(
63+
inc_pos(thread_id),
64+
old_val,
65+
address,
66+
size,
67+
EventDeps()
68+
);
69+
if (const auto* err = std::get_if<VerificationError>(&store_ret))
70+
return MutexLockResultExt::from_error(format_error(*err));
71+
// We don't update Miri's memory for this operation so we don't need to know if the store
72+
// was the co-maximal store, but we still check that we at least get a boolean as the result
73+
// of the store.
74+
const bool* is_coherence_order_maximal_write = std::get_if<bool>(&store_ret);
75+
ERROR_ON(
76+
nullptr == is_coherence_order_maximal_write,
77+
"Unimplemented: store part of mutex try_lock returned unexpected result."
78+
);
79+
} else {
80+
// We did not acquire the mutex, so we tell GenMC to block the thread until we can acquire
81+
// it. GenMC determines this based on the annotation we pass with the load further up in
82+
// this function, namely when that load will read a value other than `MUTEX_LOCKED`.
83+
this->handle_assume_block(thread_id, AssumeType::Spinloop);
84+
}
85+
return MutexLockResultExt::ok(is_lock_acquired);
86+
}
87+
88+
auto MiriGenmcShim::handle_mutex_try_lock(ThreadId thread_id, uint64_t address, uint64_t size)
89+
-> MutexLockResult {
90+
auto& currPos = threads_action_[thread_id].event;
91+
// As usual, we need to tell GenMC which value was stored at this location before this atomic
92+
// access, if there previously was a non-atomic initializing access. We set the initial state of
93+
// a mutex to be "unlocked".
94+
const auto old_val = MUTEX_UNLOCKED;
95+
const auto load_ret = GenMCDriver::handleLoad<EventLabel::EventLabelKind::TrylockCasRead>(
96+
++currPos,
97+
old_val,
98+
SAddr(address),
99+
ASize(size)
100+
);
101+
if (const auto* err = std::get_if<VerificationError>(&load_ret))
102+
return MutexLockResultExt::from_error(format_error(*err));
103+
const auto* ret_val = std::get_if<SVal>(&load_ret);
104+
if (nullptr == ret_val) {
105+
ERROR("Unimplemented: mutex trylock load returned unexpected result.");
106+
}
107+
108+
ERROR_ON(
109+
*ret_val != MUTEX_UNLOCKED && *ret_val != MUTEX_LOCKED,
110+
"Mutex read value was neither 0 nor 1"
111+
);
112+
const bool is_lock_acquired = *ret_val == MUTEX_UNLOCKED;
113+
if (!is_lock_acquired) {
114+
return MutexLockResultExt::ok(false); /* Lock already held. */
115+
}
116+
117+
const auto store_ret = GenMCDriver::handleStore<EventLabel::EventLabelKind::TrylockCasWrite>(
118+
++currPos,
119+
old_val,
120+
SAddr(address),
121+
ASize(size)
122+
);
123+
if (const auto* err = std::get_if<VerificationError>(&store_ret))
124+
return MutexLockResultExt::from_error(format_error(*err));
125+
// We don't update Miri's memory for this operation so we don't need to know if the store was
126+
// co-maximal, but we still check that we get a boolean result.
127+
const bool* is_coherence_order_maximal_write = std::get_if<bool>(&store_ret);
128+
ERROR_ON(
129+
nullptr == is_coherence_order_maximal_write,
130+
"Unimplemented: store part of mutex try_lock returned unexpected result."
131+
);
132+
return MutexLockResultExt::ok(true);
133+
}
134+
135+
auto MiriGenmcShim::handle_mutex_unlock(ThreadId thread_id, uint64_t address, uint64_t size)
136+
-> StoreResult {
137+
const auto pos = inc_pos(thread_id);
138+
const auto ret = GenMCDriver::handleStore<EventLabel::EventLabelKind::UnlockWrite>(
139+
pos,
140+
// As usual, we need to tell GenMC which value was stored at this location before this
141+
// atomic access, if there previously was a non-atomic initializing access. We set the
142+
// initial state of a mutex to be "unlocked".
143+
/* old_val */ MUTEX_UNLOCKED,
144+
MemOrdering::Release,
145+
SAddr(address),
146+
ASize(size),
147+
AType::Signed,
148+
/* store_value */ MUTEX_UNLOCKED,
149+
EventDeps()
150+
);
151+
if (const auto* err = std::get_if<VerificationError>(&ret))
152+
return StoreResultExt::from_error(format_error(*err));
153+
const bool* is_coherence_order_maximal_write = std::get_if<bool>(&ret);
154+
ERROR_ON(
155+
nullptr == is_coherence_order_maximal_write,
156+
"Unimplemented: store part of mutex unlock returned unexpected result."
157+
);
158+
return StoreResultExt::ok(*is_coherence_order_maximal_write);
159+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ void MiriGenmcShim::handle_thread_join(ThreadId thread_id, ThreadId child_id) {
3838
if (!std::holds_alternative<SVal>(ret)) {
3939
dec_pos(thread_id);
4040
}
41+
// FIXME(genmc): handle `HandleResult::{Invalid, Reset, VerificationError}` return values.
4142

4243
// NOTE: Thread return value is ignored, since Miri doesn't need it.
4344
}

genmc-sys/src/lib.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,17 @@ mod ffi {
254254
is_coherence_order_maximal_write: bool,
255255
}
256256

257+
#[must_use]
258+
#[derive(Debug)]
259+
struct MutexLockResult {
260+
/// If there was an error, it will be stored in `error`, otherwise it is `None`.
261+
error: UniquePtr<CxxString>,
262+
/// If true, GenMC determined that we should retry the mutex lock operation once the thread attempting to lock is scheduled again.
263+
is_reset: bool,
264+
/// Indicate whether the lock was acquired by this thread.
265+
is_lock_acquired: bool,
266+
}
267+
257268
/**** These are GenMC types that we have to copy-paste here since cxx does not support
258269
"importing" externally defined C++ types. ****/
259270

@@ -305,6 +316,13 @@ mod ffi {
305316
UMin = 10,
306317
}
307318

319+
#[derive(Debug)]
320+
enum AssumeType {
321+
User = 0,
322+
Barrier = 1,
323+
Spinloop = 2,
324+
}
325+
308326
// # Safety
309327
//
310328
// This block is unsafe to allow defining safe methods inside.
@@ -323,6 +341,7 @@ mod ffi {
323341
(This tells cxx that the enums defined above are already defined on the C++ side;
324342
it will emit assertions to ensure that the two definitions agree.) ****/
325343
type ActionKind;
344+
type AssumeType;
326345
type MemOrdering;
327346
type RMWBinOp;
328347
type SchedulePolicy;
@@ -430,7 +449,31 @@ mod ffi {
430449
/// Inform GenMC that the thread should be blocked.
431450
/// Note: this function is currently hardcoded for `AssumeType::User`, corresponding to user supplied assume statements.
432451
/// This can become a parameter once more types of assumes are added.
433-
fn handle_assume_block(self: Pin<&mut MiriGenmcShim>, thread_id: i32);
452+
fn handle_assume_block(
453+
self: Pin<&mut MiriGenmcShim>,
454+
thread_id: i32,
455+
assume_type: AssumeType,
456+
);
457+
458+
/**** Mutex handling ****/
459+
fn handle_mutex_lock(
460+
self: Pin<&mut MiriGenmcShim>,
461+
thread_id: i32,
462+
address: u64,
463+
size: u64,
464+
) -> MutexLockResult;
465+
fn handle_mutex_try_lock(
466+
self: Pin<&mut MiriGenmcShim>,
467+
thread_id: i32,
468+
address: u64,
469+
size: u64,
470+
) -> MutexLockResult;
471+
fn handle_mutex_unlock(
472+
self: Pin<&mut MiriGenmcShim>,
473+
thread_id: i32,
474+
address: u64,
475+
size: u64,
476+
) -> StoreResult;
434477

435478
/***** Exploration related functionality *****/
436479

src/concurrency/genmc/dummy.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ mod intercept {
4343

4444
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
4545
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
46+
fn genmc_intercept_function(
47+
&mut self,
48+
_instance: rustc_middle::ty::Instance<'tcx>,
49+
_args: &[rustc_const_eval::interpret::FnArg<'tcx, crate::Provenance>],
50+
_dest: &crate::PlaceTy<'tcx>,
51+
) -> InterpResult<'tcx, bool> {
52+
unreachable!()
53+
}
54+
4655
fn handle_genmc_verifier_assume(&mut self, _condition: &OpTy<'tcx>) -> InterpResult<'tcx> {
4756
unreachable!();
4857
}

0 commit comments

Comments
 (0)