Skip to content

Commit 0d56e22

Browse files
authored
Merge pull request #4566 from Patrick-6/miri-genmc-rmw
Add more features for GenMC mode (RMW, fences, new printing options)
2 parents 6ab5a5d + 63b4257 commit 0d56e22

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1478
-207
lines changed

doc/genmc.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ Note that `cargo miri test` in GenMC mode is currently not supported.
2424
### Supported Parameters
2525

2626
- `-Zmiri-genmc`: Enable GenMC mode (not required if any other GenMC options are used).
27+
- `-Zmiri-genmc-print-exec-graphs={none,explored,blocked,all}`: Make GenMC print the execution graph of the program after every explored, every blocked, or after every execution (default: None).
28+
- `-Zmiri-genmc-print-exec-graphs`: Shorthand for suffix `=explored`.
29+
- `-Zmiri-genmc-print-genmc-output`: Print the output that GenMC provides. NOTE: this output is quite verbose and the events in the printed execution graph are hard to map back to the Rust code location they originate from.
2730
- `-Zmiri-genmc-log=LOG_LEVEL`: Change the log level for GenMC. Default: `warning`.
2831
- `quiet`: Disable logging.
2932
- `error`: Print errors.
@@ -34,7 +37,9 @@ Note that `cargo miri test` in GenMC mode is currently not supported.
3437
- `debug2`: Print the execution graph after every memory access.
3538
- `debug3`: Print reads-from values considered by GenMC.
3639

37-
<!-- FIXME(genmc): explain options. -->
40+
#### Regular Miri parameters useful for GenMC mode
41+
42+
- `-Zmiri-disable-weak-memory-emulation`: Disable any weak memory effects (effectively upgrading all atomic orderings in the program to `SeqCst`). This option may reduce the number of explored program executions, but any bugs related to weak memory effects will be missed. This option can help determine if an error is caused by weak memory effects (i.e., if it disappears with this option enabled).
3843

3944
<!-- FIXME(genmc): explain Miri-GenMC specific functions. -->
4045

genmc-sys/build.rs

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,23 @@ mod downloading {
3030
/// The GenMC commit we depend on. It must be available on the specified GenMC repository.
3131
pub(crate) const GENMC_COMMIT: &str = "af9cc9ccd5d412b16defc35dbf36571c63a19c76";
3232

33-
pub(crate) fn download_genmc() -> PathBuf {
33+
/// Ensure that a local GenMC repo is present and set to the correct commit.
34+
/// Return the path of the GenMC repo and whether the checked out commit was changed.
35+
pub(crate) fn download_genmc() -> (PathBuf, bool) {
3436
let Ok(genmc_download_path) = PathBuf::from_str(GENMC_DOWNLOAD_PATH);
3537
let commit_oid = Oid::from_str(GENMC_COMMIT).expect("Commit should be valid.");
3638

3739
match Repository::open(&genmc_download_path) {
3840
Ok(repo) => {
3941
assert_repo_unmodified(&repo);
42+
if let Ok(head) = repo.head()
43+
&& let Ok(head_commit) = head.peel_to_commit()
44+
&& head_commit.id() == commit_oid
45+
{
46+
// Fast path: The expected commit is already checked out.
47+
return (genmc_download_path, false);
48+
}
49+
// Check if the local repository already contains the commit we need, download it otherwise.
4050
let commit = update_local_repo(&repo, commit_oid);
4151
checkout_commit(&repo, &commit);
4252
}
@@ -51,7 +61,7 @@ mod downloading {
5161
}
5262
};
5363

54-
genmc_download_path
64+
(genmc_download_path, true)
5565
}
5666

5767
fn get_remote(repo: &Repository) -> Remote<'_> {
@@ -71,7 +81,8 @@ mod downloading {
7181

7282
// Update remote URL.
7383
println!(
74-
"cargo::warning=GenMC repository remote URL has changed from '{remote_url:?}' to '{GENMC_GITHUB_URL}'"
84+
"cargo::warning=GenMC repository remote URL has changed from '{}' to '{GENMC_GITHUB_URL}'",
85+
remote_url.unwrap_or_default()
7586
);
7687
repo.remote_set_url("origin", GENMC_GITHUB_URL)
7788
.expect("cannot rename url of remote 'origin'");
@@ -175,7 +186,7 @@ fn link_to_llvm(config_file: &Path) -> (String, String) {
175186
}
176187

177188
/// Build the GenMC model checker library and the Rust-C++ interop library with cxx.rs
178-
fn compile_cpp_dependencies(genmc_path: &Path) {
189+
fn compile_cpp_dependencies(genmc_path: &Path, always_configure: bool) {
179190
// Give each step a separate build directory to prevent interference.
180191
let out_dir = PathBuf::from(std::env::var("OUT_DIR").as_deref().unwrap());
181192
let genmc_build_dir = out_dir.join("genmc");
@@ -184,15 +195,13 @@ fn compile_cpp_dependencies(genmc_path: &Path) {
184195
// Part 1:
185196
// Compile the GenMC library using cmake.
186197

187-
let cmakelists_path = genmc_path.join("CMakeLists.txt");
188-
189198
// FIXME(genmc,cargo): Switch to using `CARGO_CFG_DEBUG_ASSERTIONS` once https://github.com/rust-lang/cargo/issues/15760 is completed.
190199
// Enable/disable additional debug checks, prints and options for GenMC, based on the Rust profile (debug/release)
191200
let enable_genmc_debug = matches!(std::env::var("PROFILE").as_deref().unwrap(), "debug");
192201

193-
let mut config = cmake::Config::new(cmakelists_path);
202+
let mut config = cmake::Config::new(genmc_path);
194203
config
195-
.always_configure(false) // We don't need to reconfigure on subsequent compilation runs.
204+
.always_configure(always_configure) // We force running the configure step when the GenMC commit changed.
196205
.out_dir(genmc_build_dir)
197206
.profile(GENMC_CMAKE_PROFILE)
198207
.define("GENMC_DEBUG", if enable_genmc_debug { "ON" } else { "OFF" });
@@ -251,7 +260,8 @@ fn compile_cpp_dependencies(genmc_path: &Path) {
251260

252261
fn main() {
253262
// Select which path to use for the GenMC repo:
254-
let genmc_path = if let Some(genmc_src_path) = option_env!("GENMC_SRC_PATH") {
263+
let (genmc_path, always_configure) = if let Some(genmc_src_path) = option_env!("GENMC_SRC_PATH")
264+
{
255265
let genmc_src_path =
256266
PathBuf::from_str(&genmc_src_path).expect("GENMC_SRC_PATH should contain a valid path");
257267
assert!(
@@ -261,13 +271,18 @@ fn main() {
261271
);
262272
// Rebuild files in the given path change.
263273
println!("cargo::rerun-if-changed={}", genmc_src_path.display());
264-
genmc_src_path
274+
// We disable `always_configure` when working with a local repository,
275+
// since it increases compile times when working on `genmc-sys`.
276+
(genmc_src_path, false)
265277
} else {
278+
// Download GenMC if required and ensure that the correct commit is checked out.
279+
// If anything changed in the downloaded repository (e.g., the commit),
280+
// we set `always_configure` to ensure there are no weird configs from previous builds.
266281
downloading::download_genmc()
267282
};
268283

269284
// Build all required components:
270-
compile_cpp_dependencies(&genmc_path);
285+
compile_cpp_dependencies(&genmc_path, always_configure);
271286

272287
// Only rebuild if anything changes:
273288
// Note that we don't add the downloaded GenMC repo, since that should never be modified

genmc-sys/cpp/include/MiriInterface.hpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ struct GenmcScalar;
3333
struct SchedulingResult;
3434
struct LoadResult;
3535
struct StoreResult;
36+
struct ReadModifyWriteResult;
3637

3738
// GenMC uses `int` for its thread IDs.
3839
using ThreadId = int;
@@ -90,6 +91,15 @@ struct MiriGenmcShim : private GenMCDriver {
9091
MemOrdering ord,
9192
GenmcScalar old_val
9293
);
94+
[[nodiscard]] ReadModifyWriteResult handle_read_modify_write(
95+
ThreadId thread_id,
96+
uint64_t address,
97+
uint64_t size,
98+
RMWBinOp rmw_op,
99+
MemOrdering ordering,
100+
GenmcScalar rhs_value,
101+
GenmcScalar old_val
102+
);
93103
[[nodiscard]] StoreResult handle_store(
94104
ThreadId thread_id,
95105
uint64_t address,
@@ -99,6 +109,8 @@ struct MiriGenmcShim : private GenMCDriver {
99109
MemOrdering ord
100110
);
101111

112+
void handle_fence(ThreadId thread_id, MemOrdering ord);
113+
102114
/**** Memory (de)allocation ****/
103115
auto handle_malloc(ThreadId thread_id, uint64_t size, uint64_t alignment) -> uint64_t;
104116
void handle_free(ThreadId thread_id, uint64_t address);
@@ -271,4 +283,21 @@ inline StoreResult from_error(std::unique_ptr<std::string> error) {
271283
}
272284
} // namespace StoreResultExt
273285

286+
namespace ReadModifyWriteResultExt {
287+
inline ReadModifyWriteResult
288+
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 };
293+
}
294+
295+
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 };
300+
}
301+
} // namespace ReadModifyWriteResultExt
302+
274303
#endif /* GENMC_MIRI_INTERFACE_HPP */

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

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,72 @@
8989
);
9090
}
9191

92+
void MiriGenmcShim::handle_fence(ThreadId thread_id, MemOrdering ord) {
93+
const auto pos = inc_pos(thread_id);
94+
GenMCDriver::handleFence(pos, ord, EventDeps());
95+
}
96+
97+
[[nodiscard]] auto MiriGenmcShim::handle_read_modify_write(
98+
ThreadId thread_id,
99+
uint64_t address,
100+
uint64_t size,
101+
RMWBinOp rmw_op,
102+
MemOrdering ordering,
103+
GenmcScalar rhs_value,
104+
GenmcScalar old_val
105+
) -> ReadModifyWriteResult {
106+
// NOTE: Both the store and load events should get the same `ordering`, it should not be split
107+
// into a load and a store component. This means we can have for example `AcqRel` loads and
108+
// stores, but this is intended for RMW operations.
109+
110+
// Somewhat confusingly, the GenMC term for RMW read/write labels is
111+
// `FaiRead` and `FaiWrite`.
112+
const auto load_ret = handle_load_reset_if_none<EventLabel::EventLabelKind::FaiRead>(
113+
thread_id,
114+
ordering,
115+
SAddr(address),
116+
ASize(size),
117+
AType::Unsigned, // The type is only used for printing.
118+
rmw_op,
119+
GenmcScalarExt::to_sval(rhs_value),
120+
EventDeps()
121+
);
122+
if (const auto* err = std::get_if<VerificationError>(&load_ret))
123+
return ReadModifyWriteResultExt::from_error(format_error(*err));
124+
125+
const auto* ret_val = std::get_if<SVal>(&load_ret);
126+
if (nullptr == ret_val) {
127+
ERROR("Unimplemented: read-modify-write returned unexpected result.");
128+
}
129+
const auto read_old_val = *ret_val;
130+
const auto new_value =
131+
executeRMWBinOp(read_old_val, GenmcScalarExt::to_sval(rhs_value), size, rmw_op);
132+
133+
const auto storePos = inc_pos(thread_id);
134+
const auto store_ret = GenMCDriver::handleStore<EventLabel::EventLabelKind::FaiWrite>(
135+
storePos,
136+
ordering,
137+
SAddr(address),
138+
ASize(size),
139+
AType::Unsigned, // The type is only used for printing.
140+
new_value
141+
);
142+
if (const auto* err = std::get_if<VerificationError>(&store_ret))
143+
return ReadModifyWriteResultExt::from_error(format_error(*err));
144+
145+
const auto* store_ret_val = std::get_if<std::monostate>(&store_ret);
146+
ERROR_ON(nullptr == store_ret_val, "Unimplemented: RMW store returned unexpected result.");
147+
148+
// FIXME(genmc,mixed-accesses): Use the value that GenMC returns from handleStore (once
149+
// available).
150+
const auto& g = getExec().getGraph();
151+
return ReadModifyWriteResultExt::ok(
152+
/* old_value: */ read_old_val,
153+
new_value,
154+
/* is_coherence_order_maximal_write */ g.co_max(SAddr(address))->getPos() == storePos
155+
);
156+
}
157+
92158
/**** Memory (de)allocation ****/
93159

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

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,12 @@ static auto to_genmc_verbosity_level(const LogLevel log_level) -> VerbosityLevel
6363
auto conf = std::make_shared<Config>();
6464

6565
// Set whether GenMC should print execution graphs after every explored/blocked execution.
66-
// FIXME(genmc): pass these settings from Miri.
67-
conf->printExecGraphs = false;
68-
conf->printBlockedExecs = false;
66+
conf->printExecGraphs =
67+
(params.print_execution_graphs == ExecutiongraphPrinting::Explored ||
68+
params.print_execution_graphs == ExecutiongraphPrinting::ExploredAndBlocked);
69+
conf->printBlockedExecs =
70+
(params.print_execution_graphs == ExecutiongraphPrinting::Blocked ||
71+
params.print_execution_graphs == ExecutiongraphPrinting::ExploredAndBlocked);
6972

7073
// `1024` is the default value that GenMC uses.
7174
// If any thread has at least this many events, a warning/tip will be printed.
@@ -79,8 +82,8 @@ static auto to_genmc_verbosity_level(const LogLevel log_level) -> VerbosityLevel
7982
// Miri.
8083
conf->warnOnGraphSize = 1024 * 1024;
8184

82-
// We only support the RC11 memory model for Rust.
83-
conf->model = ModelType::RC11;
85+
// We only support the `RC11` memory model for Rust, and `SC` when weak memory emulation is disabled.
86+
conf->model = params.disable_weak_memory_emulation ? ModelType::SC : ModelType::RC11;
8487

8588
// This prints the seed that GenMC picks for randomized scheduling during estimation mode.
8689
conf->printRandomScheduleSeed = params.print_random_schedule_seed;

0 commit comments

Comments
 (0)