| /** This file contains functionality related to creation of the GenMCDriver, |
| * including translating settings set by Miri. */ |
| |
| #include "MiriInterface.hpp" |
| |
| // CXX.rs generated headers: |
| #include "genmc-sys/src/lib.rs.h" |
| |
| // GenMC headers: |
| #include "Support/Error.hpp" |
| #include "Support/Verbosity.hpp" |
| #include "Verification/InterpreterCallbacks.hpp" |
| |
| // C++ headers: |
| #include <cstdint> |
| |
| /** |
| * Translate the Miri-GenMC `LogLevel` to the GenMC `VerbosityLevel`. |
| * Downgrade any debug options to `Tip` if `ENABLE_GENMC_DEBUG` is not enabled. |
| */ |
| static auto to_genmc_verbosity_level(const LogLevel log_level) -> VerbosityLevel { |
| switch (log_level) { |
| case LogLevel::Quiet: |
| return VerbosityLevel::Quiet; |
| case LogLevel::Error: |
| return VerbosityLevel::Error; |
| case LogLevel::Warning: |
| return VerbosityLevel::Warning; |
| case LogLevel::Tip: |
| return VerbosityLevel::Tip; |
| #ifdef ENABLE_GENMC_DEBUG |
| case LogLevel::Debug1Revisits: |
| return VerbosityLevel::Debug1; |
| case LogLevel::Debug2MemoryAccesses: |
| return VerbosityLevel::Debug2; |
| case LogLevel::Debug3ReadsFrom: |
| return VerbosityLevel::Debug3; |
| #else |
| // Downgrade to `Tip` if the debug levels are not available. |
| case LogLevel::Debug1Revisits: |
| case LogLevel::Debug2MemoryAccesses: |
| case LogLevel::Debug3ReadsFrom: |
| return VerbosityLevel::Tip; |
| #endif |
| default: |
| WARN_ONCE( |
| "unknown-log-level", |
| "Unknown `LogLevel`, defaulting to `VerbosityLevel::Tip`." |
| ); |
| return VerbosityLevel::Tip; |
| } |
| } |
| |
| /* unsafe */ void set_log_level_raw(LogLevel log_level) { |
| // The `logLevel` is a static, non-atomic variable. |
| // It should never be changed if `MiriGenmcShim` still exists, since any of its methods may read |
| // the `logLevel`, otherwise it may cause data races. |
| logLevel = to_genmc_verbosity_level(log_level); |
| } |
| |
| /* unsafe */ auto MiriGenmcShim::create_handle(const GenmcParams& params, bool estimation_mode) |
| -> std::unique_ptr<MiriGenmcShim> { |
| auto conf = std::make_shared<Config>(); |
| |
| // Set whether GenMC should print execution graphs after every explored/blocked execution. |
| conf->printExecGraphs = |
| (params.print_execution_graphs == ExecutiongraphPrinting::Explored || |
| params.print_execution_graphs == ExecutiongraphPrinting::ExploredAndBlocked); |
| conf->printBlockedExecs = |
| (params.print_execution_graphs == ExecutiongraphPrinting::Blocked || |
| params.print_execution_graphs == ExecutiongraphPrinting::ExploredAndBlocked); |
| |
| // `1024` is the default value that GenMC uses. |
| // If any thread has at least this many events, a warning/tip will be printed. |
| // |
| // Miri produces a lot more events than GenMC, so the graph size warning triggers on almost |
| // all programs. The current value is large enough so the warning is not be triggered by any |
| // reasonable programs. |
| // FIXME(genmc): The emitted warning mentions features not supported by Miri ('--unroll' |
| // parameter). |
| // FIXME(genmc): A more appropriate limit should be chosen once the warning is useful for |
| // Miri. |
| conf->warnOnGraphSize = 1024 * 1024; |
| |
| // We only support the `RC11` memory model for Rust, and `SC` when weak memory emulation is |
| // disabled. |
| conf->model = params.disable_weak_memory_emulation ? ModelType::SC : ModelType::RC11; |
| |
| // This prints the seed that GenMC picks for randomized scheduling during estimation mode. |
| conf->printRandomScheduleSeed = params.print_random_schedule_seed; |
| |
| // FIXME(genmc): supporting IPR requires annotations for `assume` and `spinloops`. |
| conf->ipr = false; |
| // FIXME(genmc): supporting BAM requires `Barrier` support + detecting whether return value |
| // of barriers are used. |
| conf->disableBAM = true; |
| |
| // Instruction caching could help speed up verification by filling the graph from cache, if |
| // the list of values read by all load events in a thread have been seen before. Combined |
| // with not replaying completed threads, this can also reducing the amount of Mir |
| // interpretation required by Miri. With the current setup, this would be incorrect, since |
| // Miri doesn't give GenMC the actual values read by non-atomic reads. |
| conf->instructionCaching = false; |
| // Many of Miri's checks work under the assumption that threads are only executed in an |
| // order that could actually happen during a normal execution. Formally, this means that |
| // replaying an execution needs to respect the po-rf-relation of the executiongraph (po == |
| // program-order, rf == reads-from). This means, any event in the graph, when replayed, must |
| // happen after any events that happen before it in the same graph according to the program |
| // code, and all (non-atomic) reads must happen after the write event they read from. |
| // |
| // Not replaying completed threads means any read event from that thread never happens in |
| // Miri's memory, so this would only work if there are never any non-atomic reads from any |
| // value written by the skipped thread. |
| conf->replayCompletedThreads = true; |
| |
| // FIXME(genmc): implement symmetry reduction. |
| ERROR_ON( |
| params.do_symmetry_reduction, |
| "Symmetry reduction is currently unsupported in GenMC mode." |
| ); |
| conf->symmetryReduction = params.do_symmetry_reduction; |
| |
| // Set the scheduling policy. GenMC uses `WFR` for estimation mode. |
| // For normal verification, `WF` has the best performance and is the GenMC default. |
| // Other scheduling policies are used by GenMC for testing and for modes currently |
| // unsupported with Miri such as bounding, which uses LTR. |
| conf->schedulePolicy = estimation_mode ? SchedulePolicy::WFR : SchedulePolicy::WF; |
| |
| // Set the min and max number of executions tested in estimation mode. |
| conf->estimationMin = 10; // default taken from GenMC |
| conf->estimationMax = params.estimation_max; |
| // Deviation threshold % under which estimation is deemed good enough. |
| conf->sdThreshold = 10; // default taken from GenMC |
| // Set the mode used for this driver, either estimation or verification. |
| const auto mode = estimation_mode ? GenMCDriver::Mode(GenMCDriver::EstimationMode {}) |
| : GenMCDriver::Mode(GenMCDriver::VerificationMode {}); |
| |
| // Running Miri-GenMC without race detection is not supported. |
| // Disabling this option also changes the behavior of the replay scheduler to only schedule |
| // at atomic operations, which is required with Miri. This happens because Miri can generate |
| // multiple GenMC events for a single MIR terminator. Without this option, the scheduler |
| // might incorrectly schedule an atomic MIR terminator because the first event it creates is |
| // a non-atomic (e.g., `StorageLive`). |
| conf->disableRaceDetection = false; |
| |
| // Miri can already check for unfreed memory. Also, GenMC cannot distinguish between memory |
| // that is allowed to leak and memory that is not. |
| conf->warnUnfreedMemory = false; |
| |
| // Validate the config and exit if there are any errors |
| std::vector<std::string> warnings; |
| auto config_valid = conf->validate(warnings); |
| for (const auto& w : warnings) |
| WARN("{}", w); |
| if (auto* errors = std::get_if<ConfigErrorList>(&config_valid); errors) { |
| for (const auto& e : *errors) |
| LOG(VerbosityLevel::Error, "{}", e); |
| exit(EUSER); |
| } |
| |
| // Create the actual driver and Miri-GenMC communication shim. |
| auto driver = std::make_unique<MiriGenmcShim>(std::move(conf), mode); |
| |
| // FIXME(genmc,HACK): Until a proper solution is implemented in GenMC, these callbacks will |
| // allow Miri to return information about global allocations and override uninitialized memory |
| // checks for non-atomic loads (Miri handles those without GenMC, so the error would be wrong). |
| auto interpreter_callbacks = InterpreterCallbacks { |
| // Miri already ensures that memory accesses are valid, so this check doesn't matter. |
| // We check that the address is static, but skip checking if it is part of an actual |
| // allocation. |
| .isStaticallyAllocated = [](SAddr addr) { return addr.isStatic(); }, |
| // FIXME(genmc,error reporting): Once a proper a proper API for passing such information is |
| // implemented in GenMC, Miri should use it to improve the produced error messages. |
| .getStaticName = [](SAddr addr) { return "[UNKNOWN STATIC]"; }, |
| // This function is called to get the initial value stored at the given address. |
| // |
| // From a Miri perspective, this API doesn't work very well: most memory starts out |
| // "uninitialized"; |
| // only statics have an initial value. And their initial value is just a sequence of bytes, |
| // but GenMC expect this to be already split into separate atomic variables. So we return a |
| // dummy value. |
| // This value should never be visible to the interpreted program. |
| // GenMC does not understand uninitialized memory the same way Miri does, which may cause |
| // this function to be called. The returned value can be visible to Miri or the user: |
| // - Printing the execution graph may contain this value in place of uninitialized values. |
| // FIXME(genmc): NOTE: printing the execution graph is not yet implemented. |
| // - Non-atomic loads may return this value, but Miri ignores values of non-atomic loads. |
| // - Atomic loads will *not* see this value once mixed atomic-non-atomic support is added. |
| // Currently, atomic loads can see this value, unless initialized by an *atomic* store. |
| // FIXME(genmc): update this comment once mixed atomic-non-atomic support is added. |
| // |
| // FIXME(genmc): implement proper support for uninitialized memory in GenMC. |
| // Ideally, the initial value getter would return an `optional<SVal>`, since the memory |
| // location may be uninitialized. |
| .initValGetter = [](const AAccess& a) { return SVal(0xDEAD); }, |
| // Miri serves non-atomic loads from its own memory and these GenMC checks are wrong in that |
| // case. This should no longer be required with proper mixed-size access support. |
| .skipUninitLoadChecks = [](const MemAccessLabel* access_label |
| ) { return access_label->getOrdering() == MemOrdering::NotAtomic; }, |
| }; |
| driver->setInterpCallbacks(std::move(interpreter_callbacks)); |
| |
| return driver; |
| } |