Skip to content

Commit 7ef3cd9

Browse files
committed
Add GenMC estimation mode. Improve error handling and output printing.
1 parent e7da309 commit 7ef3cd9

Some content is hidden

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

64 files changed

+386
-170
lines changed

doc/genmc.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ 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-estimate`: This enables estimation of the concurrent execution space and verification time, before running the full verification. This should help users detect when their program is too complex to fully verify in a reasonable time. This will explore enough executions to make a good estimation, but at least 10 and at most `estimation-max` executions.
28+
- `-Zmiri-genmc-estimation-max={MAX_ITERATIONS}`: Set the maximum number of executions that will be explored during estimation (default: 1000).
2729
- `-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).
2830
- `-Zmiri-genmc-print-exec-graphs`: Shorthand for suffix `=explored`.
2931
- `-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.
@@ -36,6 +38,7 @@ Note that `cargo miri test` in GenMC mode is currently not supported.
3638
- `debug1`: Print revisits considered by GenMC.
3739
- `debug2`: Print the execution graph after every memory access.
3840
- `debug3`: Print reads-from values considered by GenMC.
41+
- `-Zmiri-genmc-verbose`: Show more information, such as estimated number of executions, and time taken for verification.
3942

4043
#### Regular Miri parameters useful for GenMC mode
4144

genmc-sys/cpp/include/MiriInterface.hpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ enum class LogLevel : std::uint8_t;
3131

3232
struct GenmcScalar;
3333
struct SchedulingResult;
34+
struct EstimationResult;
3435
struct LoadResult;
3536
struct StoreResult;
3637
struct ReadModifyWriteResult;
@@ -66,7 +67,7 @@ struct MiriGenmcShim : private GenMCDriver {
6667
/// `logLevel`, causing a data race. The safest way to use these functions is to call
6768
/// `set_log_level_raw` once, and only then start creating handles. There should not be any
6869
/// other (safe) way to create a `MiriGenmcShim`.
69-
/* unsafe */ static auto create_handle(const GenmcParams& params)
70+
/* unsafe */ static auto create_handle(const GenmcParams& params, bool estimation_mode)
7071
-> std::unique_ptr<MiriGenmcShim>;
7172

7273
virtual ~MiriGenmcShim() {}
@@ -183,6 +184,11 @@ struct MiriGenmcShim : private GenMCDriver {
183184
return nullptr;
184185
}
185186

187+
/**** Printing and estimation mode functionality. ****/
188+
189+
/// Get the results of a run in estimation mode.
190+
auto get_estimation_results() const -> EstimationResult;
191+
186192
private:
187193
/** Increment the event index in the given thread by 1 and return the new event. */
188194
[[nodiscard]] inline auto inc_pos(ThreadId tid) -> Event {

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
#include "Support/Verbosity.hpp"
1111

1212
// C++ headers:
13+
#include <cmath>
1314
#include <cstdint>
15+
#include <limits>
1416

1517
auto MiriGenmcShim::schedule_next(
1618
const int curr_thread_id,
@@ -40,3 +42,15 @@ auto MiriGenmcShim::handle_execution_end() -> std::unique_ptr<std::string> {
4042
GenMCDriver::handleExecutionEnd();
4143
return {};
4244
}
45+
46+
/**** Estimation mode result ****/
47+
48+
auto MiriGenmcShim::get_estimation_results() const -> EstimationResult {
49+
const auto& res = getResult();
50+
return EstimationResult {
51+
.mean = static_cast<double>(res.estimationMean),
52+
.sd = static_cast<double>(std::sqrt(res.estimationVariance)),
53+
.explored_execs = static_cast<uint64_t>(res.explored),
54+
.blocked_execs = static_cast<uint64_t>(res.exploredBlocked),
55+
};
56+
}

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

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ static auto to_genmc_verbosity_level(const LogLevel log_level) -> VerbosityLevel
5858
logLevel = to_genmc_verbosity_level(log_level);
5959
}
6060

61-
/* unsafe */ auto MiriGenmcShim::create_handle(const GenmcParams& params)
61+
/* unsafe */ auto MiriGenmcShim::create_handle(const GenmcParams& params, bool estimation_mode)
6262
-> std::unique_ptr<MiriGenmcShim> {
6363
auto conf = std::make_shared<Config>();
6464

@@ -82,7 +82,8 @@ static auto to_genmc_verbosity_level(const LogLevel log_level) -> VerbosityLevel
8282
// Miri.
8383
conf->warnOnGraphSize = 1024 * 1024;
8484

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

8889
// This prints the seed that GenMC picks for randomized scheduling during estimation mode.
@@ -119,12 +120,20 @@ static auto to_genmc_verbosity_level(const LogLevel log_level) -> VerbosityLevel
119120
);
120121
conf->symmetryReduction = params.do_symmetry_reduction;
121122

122-
// FIXME(genmc): expose this setting to Miri (useful for testing Miri-GenMC).
123-
conf->schedulePolicy = SchedulePolicy::WF;
124-
123+
// Set the scheduling policy. GenMC uses `WFR` for estimation mode.
124+
// For normal verification, `WF` has the best performance and is the GenMC default.
125+
// Other scheduling policies are used by GenMC for testing and for modes currently
126+
// unsupported with Miri such as bounding, which uses LTR.
127+
conf->schedulePolicy = estimation_mode ? SchedulePolicy::WFR : SchedulePolicy::WF;
128+
129+
// Set the min and max number of executions tested in estimation mode.
130+
conf->estimationMin = 10; // default taken from GenMC
131+
conf->estimationMax = params.estimation_max;
132+
// Deviation threshold % under which estimation is deemed good enough.
133+
conf->sdThreshold = 10; // default taken from GenMC
125134
// Set the mode used for this driver, either estimation or verification.
126-
// FIXME(genmc): implement estimation mode.
127-
const auto mode = GenMCDriver::Mode(GenMCDriver::VerificationMode {});
135+
const auto mode = estimation_mode ? GenMCDriver::Mode(GenMCDriver::EstimationMode {})
136+
: GenMCDriver::Mode(GenMCDriver::VerificationMode {});
128137

129138
// Running Miri-GenMC without race detection is not supported.
130139
// Disabling this option also changes the behavior of the replay scheduler to only schedule

genmc-sys/src/lib.rs

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ static GENMC_LOG_LEVEL: OnceLock<LogLevel> = OnceLock::new();
2727
pub fn create_genmc_driver_handle(
2828
params: &GenmcParams,
2929
genmc_log_level: LogLevel,
30+
do_estimation: bool,
3031
) -> UniquePtr<MiriGenmcShim> {
3132
// SAFETY: Only setting the GenMC log level once is guaranteed by the `OnceLock`.
3233
// No other place calls `set_log_level_raw`, so the `logLevel` value in GenMC will not change once we initialize it once.
@@ -40,7 +41,7 @@ pub fn create_genmc_driver_handle(
4041
}),
4142
"Attempt to change the GenMC log level after it was already set"
4243
);
43-
unsafe { MiriGenmcShim::create_handle(params) }
44+
unsafe { MiriGenmcShim::create_handle(params, do_estimation) }
4445
}
4546

4647
impl GenmcScalar {
@@ -54,6 +55,7 @@ impl GenmcScalar {
5455
impl Default for GenmcParams {
5556
fn default() -> Self {
5657
Self {
58+
estimation_max: 1000, // default taken from GenMC
5759
print_random_schedule_seed: false,
5860
do_symmetry_reduction: false,
5961
// GenMC graphs can be quite large since Miri produces a lot of (non-atomic) events.
@@ -70,6 +72,20 @@ impl Default for LogLevel {
7072
}
7173
}
7274

75+
impl FromStr for SchedulePolicy {
76+
type Err = &'static str;
77+
78+
fn from_str(s: &str) -> Result<Self, Self::Err> {
79+
Ok(match s {
80+
"wf" => SchedulePolicy::WF,
81+
"wfr" => SchedulePolicy::WFR,
82+
"arbitrary" | "random" => SchedulePolicy::Arbitrary,
83+
"ltr" => SchedulePolicy::LTR,
84+
_ => return Err("invalid scheduling policy"),
85+
})
86+
}
87+
}
88+
7389
impl FromStr for LogLevel {
7490
type Err = &'static str;
7591

@@ -92,9 +108,12 @@ mod ffi {
92108
/**** Types shared between Miri/Rust and Miri/C++ through cxx_bridge: ****/
93109

94110
/// Parameters that will be given to GenMC for setting up the model checker.
95-
/// (The fields of this struct are visible to both Rust and C++)
111+
/// The fields of this struct are visible to both Rust and C++.
112+
/// Note that this struct is #[repr(C)], so the order of fields matters.
96113
#[derive(Clone, Debug)]
97114
struct GenmcParams {
115+
/// Maximum number of executions explored in estimation mode.
116+
pub estimation_max: u32,
98117
pub print_random_schedule_seed: bool,
99118
pub do_symmetry_reduction: bool,
100119
pub print_execution_graphs: ExecutiongraphPrinting,
@@ -165,6 +184,19 @@ mod ffi {
165184
next_thread: i32,
166185
}
167186

187+
#[must_use]
188+
#[derive(Debug)]
189+
struct EstimationResult {
190+
/// Expected number of total executions.
191+
mean: f64,
192+
/// Standard deviation of the total executions estimate.
193+
sd: f64,
194+
/// Number of explored executions during the estimation.
195+
explored_execs: u64,
196+
/// Number of encounteded blocked executions during the estimation.
197+
blocked_execs: u64,
198+
}
199+
168200
#[must_use]
169201
#[derive(Debug)]
170202
struct LoadResult {
@@ -214,6 +246,14 @@ mod ffi {
214246
/**** These are GenMC types that we have to copy-paste here since cxx does not support
215247
"importing" externally defined C++ types. ****/
216248

249+
#[derive(Clone, Copy, Debug)]
250+
enum SchedulePolicy {
251+
LTR,
252+
WF,
253+
WFR,
254+
Arbitrary,
255+
}
256+
217257
#[derive(Debug)]
218258
/// Corresponds to GenMC's type with the same name.
219259
/// Should only be modified if changed by GenMC.
@@ -272,6 +312,7 @@ mod ffi {
272312
type ActionKind;
273313
type MemOrdering;
274314
type RMWBinOp;
315+
type SchedulePolicy;
275316

276317
/// Set the log level for GenMC.
277318
///
@@ -295,7 +336,10 @@ mod ffi {
295336
/// start creating handles.
296337
/// There should not be any other (safe) way to create a `MiriGenmcShim`.
297338
#[Self = "MiriGenmcShim"]
298-
unsafe fn create_handle(params: &GenmcParams) -> UniquePtr<MiriGenmcShim>;
339+
unsafe fn create_handle(
340+
params: &GenmcParams,
341+
estimation_mode: bool,
342+
) -> UniquePtr<MiriGenmcShim>;
299343
/// Get the bit mask that GenMC expects for global memory allocations.
300344
fn get_global_alloc_static_mask() -> u64;
301345

@@ -403,5 +447,10 @@ mod ffi {
403447
fn get_result_message(self: &MiriGenmcShim) -> UniquePtr<CxxString>;
404448
/// If an error occurred, return a string describing the error, otherwise, return `nullptr`.
405449
fn get_error_string(self: &MiriGenmcShim) -> UniquePtr<CxxString>;
450+
451+
/**** Printing functionality. ****/
452+
453+
/// Get the results of a run in estimation mode.
454+
fn get_estimation_results(self: &MiriGenmcShim) -> EstimationResult;
406455
}
407456
}

src/bin/miri.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,18 +186,17 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
186186
optimizations is usually marginal at best.");
187187
}
188188

189-
if let Some(_genmc_config) = &config.genmc_config {
189+
// Run in GenMC mode if enabled.
190+
if config.genmc_config.is_some() {
191+
// This is the entry point used in GenMC mode.
192+
// This closure will be called multiple times to explore the concurrent execution space of the program.
190193
let eval_entry_once = |genmc_ctx: Rc<GenmcCtx>| {
191194
miri::eval_entry(tcx, entry_def_id, entry_type, &config, Some(genmc_ctx))
192195
};
193-
194-
// FIXME(genmc): add estimation mode here.
195-
196196
let return_code = run_genmc_mode(&config, eval_entry_once, tcx).unwrap_or_else(|| {
197197
tcx.dcx().abort_if_errors();
198198
rustc_driver::EXIT_FAILURE
199199
});
200-
201200
exit(return_code);
202201
};
203202

src/concurrency/genmc/config.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@ use crate::{IsolatedOp, MiriConfig, RejectOpWith};
1010
pub struct GenmcConfig {
1111
/// Parameters sent to the C++ side to create a new handle to the GenMC model checker.
1212
pub(super) params: GenmcParams,
13+
pub(super) do_estimation: bool,
1314
/// Print the output message that GenMC generates when an error occurs.
1415
/// This error message is currently hard to use, since there is no clear mapping between the events that GenMC sees and the Rust code location where this event was produced.
1516
pub(super) print_genmc_output: bool,
1617
/// The log level for GenMC.
1718
pub(super) log_level: LogLevel,
19+
/// Enable more verbose output, such as number of executions estimate
20+
/// and time to completion of verification step.
21+
pub(super) verbose_output: bool,
1822
}
1923

2024
impl GenmcConfig {
@@ -57,8 +61,21 @@ impl GenmcConfig {
5761
"Invalid suffix to GenMC argument '-Zmiri-genmc-print-exec-graphs', expected '', '=none', '=explored', '=blocked' or '=all'"
5862
)),
5963
}
64+
} else if trimmed_arg == "estimate" {
65+
// FIXME(genmc): should this be on by default (like for GenMC)?
66+
// Enable estimating the execution space and require time before running the actual verification.
67+
genmc_config.do_estimation = true;
68+
} else if let Some(estimation_max_str) = trimmed_arg.strip_prefix("estimation-max=") {
69+
// Set the maximum number of executions to explore during estimation.
70+
genmc_config.params.estimation_max = estimation_max_str.parse().ok().filter(|estimation_max| *estimation_max > 0).ok_or_else(|| {
71+
format!(
72+
"'-Zmiri-genmc-estimation-max=...' expects a positive integer argument, but got '{estimation_max_str}'"
73+
)
74+
})?;
6075
} else if trimmed_arg == "print-genmc-output" {
6176
genmc_config.print_genmc_output = true;
77+
} else if trimmed_arg == "verbose" {
78+
genmc_config.verbose_output = true;
6279
} else {
6380
return Err(format!("Invalid GenMC argument: \"-Zmiri-genmc-{trimmed_arg}\""));
6481
}

src/concurrency/genmc/dummy.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,6 @@ mod run {
3939
impl GenmcCtx {
4040
// We don't provide the `new` function in the dummy module.
4141

42-
pub fn get_blocked_execution_count(&self) -> usize {
43-
unreachable!()
44-
}
45-
46-
pub fn get_explored_execution_count(&self) -> usize {
47-
unreachable!()
48-
}
49-
50-
pub fn is_exploration_done(&self) -> bool {
51-
unreachable!()
52-
}
53-
5442
/**** Memory access handling ****/
5543

5644
pub(super) fn set_ongoing_action_data_race_free(&self, _enable: bool) {

0 commit comments

Comments
 (0)