Skip to content

Commit 9a2ec69

Browse files
committed
Implement std::sync::Mutex interception
1 parent 0aefe34 commit 9a2ec69

File tree

16 files changed

+666
-6
lines changed

16 files changed

+666
-6
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: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,15 @@ struct LoadResult;
3636
struct StoreResult;
3737
struct ReadModifyWriteResult;
3838
struct CompareExchangeResult;
39+
struct MutexLockResult;
3940

4041
// GenMC uses `int` for its thread IDs.
4142
using ThreadId = int;
4243

44+
// Types used for GenMC annotations, e.g., in `handle_mutex_lock`.
45+
using AnnotID = ModuleID::ID;
46+
using AnnotT = SExpr<AnnotID>;
47+
4348
/// Set the log level for GenMC.
4449
///
4550
/// # Safety
@@ -141,6 +146,12 @@ struct MiriGenmcShim : private GenMCDriver {
141146
/// added.
142147
void handle_assume_block(ThreadId thread_id);
143148

149+
/**** Mutex handling ****/
150+
auto handle_mutex_lock(ThreadId thread_id, uint64_t address, uint64_t size) -> MutexLockResult;
151+
auto handle_mutex_try_lock(ThreadId thread_id, uint64_t address, uint64_t size)
152+
-> MutexLockResult;
153+
auto handle_mutex_unlock(ThreadId thread_id, uint64_t address, uint64_t size) -> StoreResult;
154+
144155
/***** Exploration related functionality *****/
145156

146157
/** Ask the GenMC scheduler for a new thread to schedule and return whether the execution is
@@ -234,6 +245,14 @@ struct MiriGenmcShim : private GenMCDriver {
234245
* indices, since GenMC expects us to do that.
235246
*/
236247
std::vector<Action> threads_action_;
248+
249+
/**
250+
* Map of already used annotation ids (e.g., for mutexes).
251+
* FIXME(GenMC): Ensure that these are consistent with how GenMC expects them once Miri supports
252+
* multithreading in GenMC mode.
253+
*/
254+
std::unordered_map<uint64_t, ModuleID::ID> annotation_id {};
255+
ModuleID::ID annotation_id_counter = 0;
237256
};
238257

239258
/// Get the bit mask that GenMC expects for global memory allocations.
@@ -358,4 +377,15 @@ inline CompareExchangeResult from_error(std::unique_ptr<std::string> error) {
358377
}
359378
} // namespace CompareExchangeResultExt
360379

380+
namespace MutexLockResultExt {
381+
inline MutexLockResult ok(bool is_lock_acquired) {
382+
return MutexLockResult { /* error: */ nullptr, is_lock_acquired };
383+
}
384+
385+
inline MutexLockResult from_error(std::unique_ptr<std::string> error) {
386+
return MutexLockResult { /* error: */ std::move(error),
387+
/* is_lock_acquired: */ false };
388+
}
389+
} // namespace MutexLockResultExt
390+
361391
#endif /* GENMC_MIRI_INTERFACE_HPP */
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/** This file contains functionality related to handling mutexes. */
2+
3+
#include "MiriInterface.hpp"
4+
5+
// CXX.rs generated headers:
6+
#include "genmc-sys/src/lib.rs.h"
7+
8+
auto MiriGenmcShim::handle_mutex_lock(ThreadId thread_id, uint64_t address, uint64_t size)
9+
-> MutexLockResult {
10+
// FIXME(genmc,multithreading): ensure this annotation id is unique even if Miri runs GenMC mode
11+
// multithreaded. We cannot use the address directly, since it is 64 bits, and `ID` is an
12+
// `unsigned int`.
13+
ModuleID::ID annot_id;
14+
auto [it, inserted] = annotation_id.try_emplace(address, annotation_id_counter);
15+
if (inserted) {
16+
annot_id = annotation_id_counter++;
17+
} else {
18+
annot_id = it->second;
19+
}
20+
const auto annot = std::move(Annotation(
21+
AssumeType::Spinloop,
22+
Annotation::ExprVP(NeExpr<AnnotID>::create(
23+
RegisterExpr<AnnotID>::create(ASize(size).getBits(), annot_id),
24+
ConcreteExpr<AnnotID>::create(ASize(size).getBits(), SVal(1))
25+
)
26+
.release())
27+
));
28+
29+
// Mutex starts out unlocked, so we always say the previous value is "unlocked".
30+
const auto old_val = SVal(0);
31+
const auto load_ret = handle_load_reset_if_none<EventLabel::EventLabelKind::LockCasRead>(
32+
thread_id,
33+
old_val,
34+
address,
35+
size,
36+
annot,
37+
EventDeps()
38+
);
39+
if (const auto* err = std::get_if<VerificationError>(&load_ret))
40+
return MutexLockResultExt::from_error(format_error(*err));
41+
// If we get a `Reset`, GenMC decided that this lock operation should not yet run, since it
42+
// would not acquire the mutex. Like the handling of the case further down where we read a `1`
43+
// ("Mutex already locked"), Miri should call the handle function again once the current thread
44+
// is scheduled by GenMC the next time.
45+
if (std::holds_alternative<Reset>(load_ret))
46+
return MutexLockResultExt::ok(false);
47+
48+
const auto* ret_val = std::get_if<SVal>(&load_ret);
49+
ERROR_ON(!ret_val, "Unimplemented: mutex lock returned unexpected result.");
50+
ERROR_ON(*ret_val != SVal(0) && *ret_val != SVal(1), "Mutex read value was neither 0 nor 1");
51+
const bool is_lock_acquired = *ret_val == SVal(0);
52+
if (is_lock_acquired) {
53+
const auto store_ret = GenMCDriver::handleStore<EventLabel::EventLabelKind::LockCasWrite>(
54+
inc_pos(thread_id),
55+
old_val,
56+
address,
57+
size,
58+
EventDeps()
59+
);
60+
if (const auto* err = std::get_if<VerificationError>(&store_ret))
61+
return MutexLockResultExt::from_error(format_error(*err));
62+
// We don't update Miri's memory for this operation so we don't need to know if the store
63+
// was the co-maximal store, but we still check that we at least get a boolean as the result
64+
// of the store.
65+
const bool* is_coherence_order_maximal_write = std::get_if<bool>(&store_ret);
66+
ERROR_ON(
67+
nullptr == is_coherence_order_maximal_write,
68+
"Unimplemented: store part of mutex try_lock returned unexpected result."
69+
);
70+
} else {
71+
// We did not acquire the mutex, so we tell GenMC to block the thread until we can acquire
72+
// it.
73+
GenMCDriver::handleAssume(inc_pos(thread_id), AssumeType::Spinloop);
74+
}
75+
return MutexLockResultExt::ok(is_lock_acquired);
76+
}
77+
78+
auto MiriGenmcShim::handle_mutex_try_lock(ThreadId thread_id, uint64_t address, uint64_t size)
79+
-> MutexLockResult {
80+
auto& currPos = threads_action_[thread_id].event;
81+
// Mutex starts out unlocked, so we always say the previous value is "unlocked".
82+
const auto old_val = SVal(0);
83+
const auto load_ret = GenMCDriver::handleLoad<EventLabel::EventLabelKind::TrylockCasRead>(
84+
++currPos,
85+
old_val,
86+
SAddr(address),
87+
ASize(size)
88+
);
89+
if (const auto* err = std::get_if<VerificationError>(&load_ret))
90+
return MutexLockResultExt::from_error(format_error(*err));
91+
const auto* ret_val = std::get_if<SVal>(&load_ret);
92+
if (nullptr == ret_val) {
93+
ERROR("Unimplemented: mutex trylock load returned unexpected result.");
94+
}
95+
96+
ERROR_ON(*ret_val != SVal(0) && *ret_val != SVal(1), "Mutex read value was neither 0 nor 1");
97+
const bool is_lock_acquired = *ret_val == SVal(0);
98+
if (!is_lock_acquired) {
99+
return MutexLockResultExt::ok(false); /* Lock already held. */
100+
}
101+
102+
const auto store_ret = GenMCDriver::handleStore<EventLabel::EventLabelKind::TrylockCasWrite>(
103+
++currPos,
104+
old_val,
105+
SAddr(address),
106+
ASize(size)
107+
);
108+
if (const auto* err = std::get_if<VerificationError>(&store_ret))
109+
return MutexLockResultExt::from_error(format_error(*err));
110+
// We don't update Miri's memory for this operation so we don't need to know if the store was
111+
// co-maximal, but we still check that we get a boolean result.
112+
const bool* is_coherence_order_maximal_write = std::get_if<bool>(&store_ret);
113+
ERROR_ON(
114+
nullptr == is_coherence_order_maximal_write,
115+
"Unimplemented: store part of mutex try_lock returned unexpected result."
116+
);
117+
return MutexLockResultExt::ok(true);
118+
}
119+
120+
auto MiriGenmcShim::handle_mutex_unlock(ThreadId thread_id, uint64_t address, uint64_t size)
121+
-> StoreResult {
122+
const auto pos = inc_pos(thread_id);
123+
const auto ret = GenMCDriver::handleStore<EventLabel::EventLabelKind::UnlockWrite>(
124+
pos,
125+
// Mutex starts out unlocked, so we always say the previous value is "unlocked".
126+
/* old_val */ SVal(0),
127+
MemOrdering::Release,
128+
SAddr(address),
129+
ASize(size),
130+
AType::Signed,
131+
SVal(0),
132+
EventDeps()
133+
);
134+
if (const auto* err = std::get_if<VerificationError>(&ret))
135+
return StoreResultExt::from_error(format_error(*err));
136+
const bool* is_coherence_order_maximal_write = std::get_if<bool>(&ret);
137+
ERROR_ON(
138+
nullptr == is_coherence_order_maximal_write,
139+
"Unimplemented: store part of mutex unlock returned unexpected result."
140+
);
141+
return StoreResultExt::ok(*is_coherence_order_maximal_write);
142+
}

genmc-sys/src/lib.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,15 @@ 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+
/// Indicate whether the lock was acquired by this thread.
263+
is_lock_acquired: bool,
264+
}
265+
257266
/**** These are GenMC types that we have to copy-paste here since cxx does not support
258267
"importing" externally defined C++ types. ****/
259268

@@ -432,6 +441,26 @@ mod ffi {
432441
/// This can become a parameter once more types of assumes are added.
433442
fn handle_assume_block(self: Pin<&mut MiriGenmcShim>, thread_id: i32);
434443

444+
/**** Mutex handling ****/
445+
fn handle_mutex_lock(
446+
self: Pin<&mut MiriGenmcShim>,
447+
thread_id: i32,
448+
address: u64,
449+
size: u64,
450+
) -> MutexLockResult;
451+
fn handle_mutex_try_lock(
452+
self: Pin<&mut MiriGenmcShim>,
453+
thread_id: i32,
454+
address: u64,
455+
size: u64,
456+
) -> MutexLockResult;
457+
fn handle_mutex_unlock(
458+
self: Pin<&mut MiriGenmcShim>,
459+
thread_id: i32,
460+
address: u64,
461+
size: u64,
462+
) -> StoreResult;
463+
435464
/***** Exploration related functionality *****/
436465

437466
/// Ask the GenMC scheduler for a new thread to schedule and

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 check_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)