blob: 6ff8e0656f36e49f0bdb7c0927c5d9f5a828c0d7 [file] [log] [blame]
use std::num::NonZeroI32;
use std::rc::Rc;
use std::sync::Arc;
use std::time::Instant;
use genmc_sys::EstimationResult;
use rustc_abi::Endian;
use rustc_log::tracing;
use rustc_middle::ty::TyCtxt;
use super::GlobalState;
use crate::concurrency::genmc::ExecutionEndResult;
use crate::rustc_const_eval::interpret::PointerArithmetic;
use crate::{GenmcConfig, GenmcCtx, MiriConfig};
#[derive(Clone, Copy, PartialEq, Eq)]
pub(super) enum GenmcMode {
Estimation,
Verification,
}
/// Do a complete run of the program in GenMC mode.
/// This will call `eval_entry` multiple times, until either:
/// - An error is detected (indicated by a `None` return value)
/// - All possible executions are explored.
///
/// Returns `None` is an error is detected, or `Some(return_value)` with the return value of the last run of the program.
pub fn run_genmc_mode<'tcx>(
tcx: TyCtxt<'tcx>,
config: &MiriConfig,
eval_entry: impl Fn(Rc<GenmcCtx>) -> Result<(), NonZeroI32>,
) -> Result<(), NonZeroI32> {
// Check for supported target: endianess and pointer size must match the host.
if tcx.data_layout.endian != Endian::Little || tcx.data_layout.pointer_size().bits() != 64 {
tcx.dcx().fatal("GenMC only supports 64bit little-endian targets");
}
let genmc_config = config.genmc_config.as_ref().unwrap();
// Run in Estimation mode if requested.
if genmc_config.do_estimation {
eprintln!("Estimating GenMC verification time...");
run_genmc_mode_impl(config, &eval_entry, tcx, GenmcMode::Estimation)?;
}
// Run in Verification mode.
eprintln!("Running GenMC Verification...");
run_genmc_mode_impl(config, &eval_entry, tcx, GenmcMode::Verification)
}
fn run_genmc_mode_impl<'tcx>(
config: &MiriConfig,
eval_entry: &impl Fn(Rc<GenmcCtx>) -> Result<(), NonZeroI32>,
tcx: TyCtxt<'tcx>,
mode: GenmcMode,
) -> Result<(), NonZeroI32> {
let time_start = Instant::now();
let genmc_config = config.genmc_config.as_ref().unwrap();
// There exists only one `global_state` per full run in GenMC mode.
// It is shared by all `GenmcCtx` in this run.
// FIXME(genmc): implement multithreading once GenMC supports it.
let global_state = Arc::new(GlobalState::new(tcx.target_usize_max()));
let genmc_ctx = Rc::new(GenmcCtx::new(config, global_state, mode));
// `rep` is used to report the progress, Miri will panic on wrap-around.
for rep in 0u64.. {
tracing::info!("Miri-GenMC loop {}", rep + 1);
// Prepare for the next execution and inform GenMC about it.
genmc_ctx.prepare_next_execution();
// Execute the program until completion to get the return value, or return if an error happens:
if let Err(err) = eval_entry(genmc_ctx.clone()) {
genmc_ctx.print_genmc_output(genmc_config, tcx);
return Err(err);
};
// We inform GenMC that the execution is complete.
// If there was an error, we print it.
match genmc_ctx.handle_execution_end() {
ExecutionEndResult::Continue => continue,
ExecutionEndResult::Stop => {
let elapsed_time_sec = Instant::now().duration_since(time_start).as_secs_f64();
// Print the output for the current mode.
if mode == GenmcMode::Estimation {
genmc_ctx.print_estimation_output(genmc_config, elapsed_time_sec);
} else {
genmc_ctx.print_verification_output(genmc_config, elapsed_time_sec);
}
// Return the return code of the last execution.
return Ok(());
}
ExecutionEndResult::Error(error) => {
// This can be reached for errors that affect the entire execution, not just a specific event.
// For instance, linearizability checking and liveness checking report their errors this way.
// Neither are supported by Miri-GenMC at the moment though.
// Since we don't have any span information for the error at this point,
// we just print GenMC's error string, and the full GenMC output if requested.
eprintln!("(GenMC) Error detected: {error}");
genmc_ctx.print_genmc_output(genmc_config, tcx);
return Err(NonZeroI32::new(rustc_driver::EXIT_FAILURE).unwrap());
}
}
}
unreachable!()
}
impl GenmcCtx {
/// Print the full output message produced by GenMC if requested, or a hint on how to enable it.
///
/// This message can be very verbose and is likely not useful for the average user.
/// This function should be called *after* Miri has printed all of its output.
fn print_genmc_output(&self, genmc_config: &GenmcConfig, tcx: TyCtxt<'_>) {
if genmc_config.print_genmc_output {
eprintln!("GenMC error report:");
eprintln!("{}", self.get_result_message());
} else {
tcx.dcx().note(
"add `-Zmiri-genmc-print-genmc-output` to MIRIFLAGS to see the detailed GenMC error report"
);
}
}
/// Given the time taken for the estimation mode run, print the expected time range for verification.
/// Verbose output also includes information about the expected number of executions and how many estimation rounds were explored or got blocked.
fn print_estimation_output(&self, genmc_config: &GenmcConfig, elapsed_time_sec: f64) {
let EstimationResult { mean, sd, explored_execs, blocked_execs } =
self.get_estimation_results();
#[allow(clippy::as_conversions)]
let time_per_exec_sec = elapsed_time_sec / (explored_execs as f64 + blocked_execs as f64);
let estimated_mean_sec = time_per_exec_sec * mean;
let estimated_sd_sec = time_per_exec_sec * sd;
if genmc_config.verbose_output {
eprintln!("Finished estimation in {elapsed_time_sec:.2?}s");
if blocked_execs != 0 {
eprintln!(" Explored executions: {explored_execs}");
eprintln!(" Blocked executions: {blocked_execs}");
}
eprintln!("Expected number of executions: {mean:.0} ± {sd:.0}");
}
// The estimation can be out-of-bounds of an `f64`.
if !(mean.is_finite() && mean >= 0.0 && sd.is_finite() && sd >= 0.0) {
eprintln!("WARNING: Estimation gave weird results, there may have been an overflow.");
}
eprintln!("Expected verification time: {estimated_mean_sec:.2}s ± {estimated_sd_sec:.2}s");
}
/// Given the time taken for the verification mode run, print the expected time range for verification.
/// Verbose output also includes information about the expected number of executions and how many estimation rounds were explored or got blocked.
fn print_verification_output(&self, genmc_config: &GenmcConfig, elapsed_time_sec: f64) {
let explored_execution_count = self.get_explored_execution_count();
let blocked_execution_count = self.get_blocked_execution_count();
eprintln!(
"Verification complete with {} executions. No errors found.",
explored_execution_count + blocked_execution_count
);
if genmc_config.verbose_output {
if blocked_execution_count > 0 {
eprintln!("Number of complete executions explored: {explored_execution_count}");
eprintln!("Number of blocked executions seen: {blocked_execution_count}");
}
eprintln!("Verification took {elapsed_time_sec:.2?}s.");
}
}
}