| #ifndef GENMC_MIRI_INTERFACE_HPP | |
| #define GENMC_MIRI_INTERFACE_HPP | |
| // CXX.rs generated headers: | |
| #include "rust/cxx.h" | |
| // GenMC generated headers: | |
| #include "genmc/config.h" | |
| // Miri `genmc-sys/src_cpp` headers: | |
| #include "ResultHandling.hpp" | |
| // GenMC headers: | |
| #include "genmc/Execution/EventLabel.hpp" | |
| #include "genmc/Support/MemOrdering.hpp" | |
| #include "genmc/Support/RMWOps.hpp" | |
| #include "genmc/Verification/Config.hpp" | |
| #include "genmc/Verification/GenMCDriver.hpp" | |
| // C++ headers: | |
| #include <cstdint> | |
| #include <format> | |
| #include <iomanip> | |
| #include <memory> | |
| /**** Types available to both Rust and C++ ****/ | |
| struct GenmcParams; | |
| enum class LogLevel : std::uint8_t; | |
| struct GenmcScalar; | |
| struct SchedulingResult; | |
| struct EstimationResult; | |
| struct LoadResult; | |
| struct NonAtomicResult; | |
| struct StoreResult; | |
| struct ReadModifyWriteResult; | |
| struct CompareExchangeResult; | |
| struct MutexLockResult; | |
| struct MallocResult; | |
| struct FreeResult; | |
| // GenMC uses `int` for its thread IDs. | |
| using ThreadId = int; | |
| /// Set the log level for GenMC. | |
| /// | |
| /// # Safety | |
| /// | |
| /// This function is not thread safe, since it writes to the global, mutable, non-atomic `logLevel` | |
| /// variable. Any GenMC function may read from `logLevel` unsynchronized. | |
| /// The safest way to use this function is to set the log level exactly once before first calling | |
| /// `create_handle`. | |
| /// Never calling this function is safe, GenMC will fall back to its default log level. | |
| /* unsafe */ void set_log_level_raw(LogLevel log_level); | |
| struct MiriGenmcInterface : private GenMCDriver { | |
| public: | |
| MiriGenmcInterface(std::shared_ptr<const Config> conf, Mode mode /* = VerificationMode{} */) | |
| : GenMCDriver(std::move(conf), nullptr, mode) {} | |
| /// Create a new `MiriGenmcInterface`, which wraps a `GenMCDriver`. | |
| /// | |
| /// # Safety | |
| /// | |
| /// This function is marked as unsafe since the `logLevel` global variable is non-atomic. | |
| /// This function should not be called in an unsynchronized way with `set_log_level_raw`, | |
| /// since this function and any methods on the returned `MiriGenmcInterface` may read the | |
| /// `logLevel`, causing a data race. The safest way to use these functions is to call | |
| /// `set_log_level_raw` once, and only then start creating handles. There should not be any | |
| /// other (safe) way to create a `MiriGenmcInterface`. | |
| /* unsafe */ static auto create_handle(const GenmcParams& params, bool estimation_mode) | |
| -> std::unique_ptr<MiriGenmcInterface>; | |
| virtual ~MiriGenmcInterface() {} | |
| /**** Execution start/end handling ****/ | |
| // This function must be called at the start of any execution, before any events are | |
| // reported to GenMC. | |
| void handle_execution_start(); | |
| // This function must be called at the end of any execution, even if an error was found | |
| // during the execution. | |
| // Returns `null`, or a string containing an error message if an error occurred. | |
| std::unique_ptr<std::string> handle_execution_end(); | |
| /***** Functions for handling events encountered during program execution. *****/ | |
| /**** Memory access handling ****/ | |
| [[nodiscard]] LoadResult handle_atomic_load( | |
| ThreadId thread_id, | |
| uint64_t address, | |
| uint64_t size, | |
| MemOrdering ord, | |
| GenmcScalar old_val | |
| ); | |
| [[nodiscard]] NonAtomicResult | |
| handle_non_atomic_load(ThreadId thread_id, uint64_t address, uint64_t size); | |
| [[nodiscard]] ReadModifyWriteResult handle_read_modify_write( | |
| ThreadId thread_id, | |
| uint64_t address, | |
| uint64_t size, | |
| RMWBinOp rmw_op, | |
| MemOrdering ordering, | |
| GenmcScalar rhs_value, | |
| GenmcScalar old_val | |
| ); | |
| [[nodiscard]] CompareExchangeResult handle_compare_exchange( | |
| ThreadId thread_id, | |
| uint64_t address, | |
| uint64_t size, | |
| GenmcScalar expected_value, | |
| GenmcScalar new_value, | |
| GenmcScalar old_val, | |
| MemOrdering success_ordering, | |
| MemOrdering fail_load_ordering, | |
| bool can_fail_spuriously | |
| ); | |
| [[nodiscard]] StoreResult handle_atomic_store( | |
| ThreadId thread_id, | |
| uint64_t address, | |
| uint64_t size, | |
| GenmcScalar value, | |
| GenmcScalar old_val, | |
| MemOrdering ord | |
| ); | |
| [[nodiscard]] NonAtomicResult | |
| handle_non_atomic_store(ThreadId thread_id, uint64_t address, uint64_t size); | |
| void handle_fence(ThreadId thread_id, MemOrdering ord); | |
| /**** Memory (de)allocation ****/ | |
| auto handle_malloc(ThreadId thread_id, uint64_t size, uint64_t alignment) -> MallocResult; | |
| /** Returns null on success, or an error string if an error occurs. */ | |
| auto handle_free(ThreadId thread_id, uint64_t address) -> FreeResult; | |
| /**** Thread management ****/ | |
| void handle_thread_create(ThreadId thread_id, ThreadId parent_id); | |
| void handle_thread_join(ThreadId thread_id, ThreadId child_id); | |
| void handle_thread_finish(ThreadId thread_id, uint64_t ret_val); | |
| void handle_thread_kill(ThreadId thread_id); | |
| /**** Blocking instructions ****/ | |
| /// Inform GenMC that the thread should be blocked. | |
| void handle_assume_block(ThreadId thread_id, AssumeType assume_type); | |
| /**** Mutex handling ****/ | |
| auto handle_mutex_lock(ThreadId thread_id, uint64_t address, uint64_t size) -> MutexLockResult; | |
| auto handle_mutex_try_lock(ThreadId thread_id, uint64_t address, uint64_t size) | |
| -> MutexLockResult; | |
| auto handle_mutex_unlock(ThreadId thread_id, uint64_t address, uint64_t size) -> StoreResult; | |
| /***** Exploration related functionality *****/ | |
| /** Ask the GenMC scheduler for a new thread to schedule and return whether the execution is | |
| * finished, blocked, or can continue. | |
| * Updates the next instruction kind for the given thread id. */ | |
| auto schedule_next(const int curr_thread_id, const ActionKind curr_thread_next_instr_kind) | |
| -> SchedulingResult; | |
| /** | |
| * Check whether there are more executions to explore. | |
| * If there are more executions, this method prepares for the next execution and returns | |
| * `true`. Returns true if there are no more executions to explore. */ | |
| auto is_exploration_done() -> bool { | |
| return GenMCDriver::done(); | |
| } | |
| /**** Result querying functionality. ****/ | |
| // NOTE: We don't want to share the `VerificationResult` type with the Rust side, since it | |
| // is very large, uses features that CXX.rs doesn't support and may change as GenMC changes. | |
| // Instead, we only use the result on the C++ side, and only expose these getter function to | |
| // the Rust side. | |
| // Note that CXX.rs doesn't support returning a C++ string to Rust by value, | |
| // it must be behind an indirection like a `unique_ptr` (tested with CXX 1.0.170). | |
| /// Get the number of blocked executions encountered by GenMC (cast into a fixed with | |
| /// integer) | |
| auto get_blocked_execution_count() const -> uint64_t { | |
| return static_cast<uint64_t>(getResult().exploredBlocked); | |
| } | |
| /// Get the number of executions explored by GenMC (cast into a fixed with integer) | |
| auto get_explored_execution_count() const -> uint64_t { | |
| return static_cast<uint64_t>(getResult().explored); | |
| } | |
| /// Get all messages that GenMC produced (errors, warnings), combined into one string. | |
| auto get_result_message() const -> std::unique_ptr<std::string> { | |
| return std::make_unique<std::string>(getResult().message); | |
| } | |
| /// If an error occurred, return a string describing the error, otherwise, return `nullptr`. | |
| auto get_error_string() const -> std::unique_ptr<std::string> { | |
| const auto& result = GenMCDriver::getResult(); | |
| if (result.status.has_value()) | |
| return format_error(result.status.value()); | |
| return nullptr; | |
| } | |
| /**** Printing and estimation mode functionality. ****/ | |
| /// Get the results of a run in estimation mode. | |
| auto get_estimation_results() const -> EstimationResult; | |
| private: | |
| /** Returns the current event for a given thread. */ | |
| inline auto curr_pos(ThreadId tid) -> Event { | |
| ERROR_ON(tid >= threads_action_.size(), "ThreadId out of bounds"); | |
| return threads_action_[tid].event; | |
| } | |
| /** Increment the event index in the given thread by `count`. */ | |
| inline void inc_pos(ThreadId tid, unsigned int count) { | |
| ERROR_ON(tid >= threads_action_.size(), "ThreadId out of bounds"); | |
| threads_action_[tid].event.index += count; | |
| } | |
| /** | |
| * GenMC uses the term `Action` to refer to a struct of: | |
| * - `ActionKind`, storing whether the next instruction in a thread may be a load | |
| * - `Event`, storing the most recent event index added for a thread | |
| * | |
| * Here we store the "action" for each thread. In particular we use this to assign event | |
| * indices, since GenMC expects us to do that. | |
| */ | |
| std::vector<Action> threads_action_; | |
| }; | |
| /// Get the bit mask that GenMC expects for global memory allocations. | |
| /// FIXME(genmc): currently we use `get_global_alloc_static_mask()` to ensure the | |
| /// `SAddr::staticMask` constant is consistent between Miri and GenMC, but if | |
| /// https://github.com/dtolnay/cxx/issues/1051 is fixed we could share the constant | |
| /// directly. | |
| constexpr auto get_global_alloc_static_mask() -> uint64_t { | |
| return SAddr::staticMask; | |
| } | |
| // CXX.rs generated headers: | |
| // NOTE: this must be included *after* `MiriGenmcInterface` and all the other types we use are | |
| // defined, otherwise there will be compilation errors due to missing definitions. | |
| #include "genmc-sys/src/lib.rs.h" | |
| /**** Result handling ****/ | |
| // NOTE: these must come after the cxx_bridge generated code, since that contains the actual | |
| // definitions of these types. | |
| namespace GenmcScalarExt { | |
| inline GenmcScalar uninit() { | |
| return GenmcScalar { | |
| .value = 0, | |
| .provenance = 0, | |
| .is_init = false, | |
| }; | |
| } | |
| inline GenmcScalar from_sval(SVal sval) { | |
| return GenmcScalar { | |
| .value = sval.get(), | |
| .provenance = sval.getProvenance(), | |
| .is_init = true, | |
| }; | |
| } | |
| inline SVal to_sval(GenmcScalar scalar) { | |
| ERROR_ON(!scalar.is_init, "Cannot convert an uninitialized `GenmcScalar` into an `SVal`\n"); | |
| return SVal(scalar.value, scalar.provenance); | |
| } | |
| inline std::optional<SVal> try_to_sval(GenmcScalar scalar) { | |
| if (scalar.is_init) | |
| return { SVal(scalar.value, scalar.provenance) }; | |
| return std::nullopt; | |
| } | |
| } // namespace GenmcScalarExt | |
| namespace LoadResultExt { | |
| inline LoadResult from_value(SVal read_value) { | |
| return LoadResult { .status = OperationStatus::Ok, | |
| .error = nullptr, | |
| .read_value = GenmcScalarExt::from_sval(read_value) }; | |
| } | |
| inline LoadResult from_error(std::unique_ptr<std::string> error) { | |
| return LoadResult { .status = OperationStatus::Error, | |
| .error = std::move(error), | |
| .read_value = GenmcScalarExt::uninit() }; | |
| } | |
| inline LoadResult from_invalid() { | |
| return LoadResult { .status = OperationStatus::Invalid, | |
| .error = nullptr, | |
| .read_value = GenmcScalarExt::uninit() }; | |
| } | |
| } // namespace LoadResultExt | |
| namespace NonAtomicResultExt { | |
| inline NonAtomicResult ok() { | |
| return NonAtomicResult { .status = OperationStatus::Ok, .error = nullptr }; | |
| } | |
| inline NonAtomicResult from_error(std::unique_ptr<std::string> error) { | |
| return NonAtomicResult { .status = OperationStatus::Error, .error = std::move(error) }; | |
| } | |
| inline NonAtomicResult from_invalid() { | |
| return NonAtomicResult { .status = OperationStatus::Invalid, .error = nullptr }; | |
| } | |
| } // namespace NonAtomicResultExt | |
| namespace StoreResultExt { | |
| inline StoreResult ok(bool is_coherence_order_maximal_write) { | |
| return StoreResult { .status = OperationStatus::Ok, | |
| .error = nullptr, | |
| .is_coherence_order_maximal_write = is_coherence_order_maximal_write }; | |
| } | |
| inline StoreResult from_error(std::unique_ptr<std::string> error) { | |
| return StoreResult { .status = OperationStatus::Error, | |
| .error = std::move(error), | |
| .is_coherence_order_maximal_write = false }; | |
| } | |
| inline StoreResult from_invalid() { | |
| return StoreResult { .status = OperationStatus::Invalid, | |
| .error = nullptr, | |
| .is_coherence_order_maximal_write = false }; | |
| } | |
| } // namespace StoreResultExt | |
| namespace ReadModifyWriteResultExt { | |
| inline ReadModifyWriteResult | |
| ok(SVal old_value, SVal new_value, bool is_coherence_order_maximal_write) { | |
| return ReadModifyWriteResult { .status = OperationStatus::Ok, | |
| .error = nullptr, | |
| .old_value = GenmcScalarExt::from_sval(old_value), | |
| .new_value = GenmcScalarExt::from_sval(new_value), | |
| .is_coherence_order_maximal_write = | |
| is_coherence_order_maximal_write }; | |
| } | |
| inline ReadModifyWriteResult from_error(std::unique_ptr<std::string> error) { | |
| return ReadModifyWriteResult { .status = OperationStatus::Error, | |
| .error = std::move(error), | |
| .old_value = GenmcScalarExt::uninit(), | |
| .new_value = GenmcScalarExt::uninit(), | |
| .is_coherence_order_maximal_write = false }; | |
| } | |
| inline ReadModifyWriteResult from_invalid() { | |
| return ReadModifyWriteResult { .status = OperationStatus::Invalid, | |
| .error = nullptr, | |
| .old_value = GenmcScalarExt::uninit(), | |
| .new_value = GenmcScalarExt::uninit(), | |
| .is_coherence_order_maximal_write = false }; | |
| } | |
| } // namespace ReadModifyWriteResultExt | |
| namespace CompareExchangeResultExt { | |
| inline CompareExchangeResult success(SVal old_value, bool is_coherence_order_maximal_write) { | |
| return CompareExchangeResult { .status = CasStatus::Succeeded, | |
| .error = nullptr, | |
| .old_value = GenmcScalarExt::from_sval(old_value), | |
| .is_coherence_order_maximal_write = | |
| is_coherence_order_maximal_write }; | |
| } | |
| inline CompareExchangeResult failure(SVal old_value) { | |
| return CompareExchangeResult { .status = CasStatus::Failed, | |
| .error = nullptr, | |
| .old_value = GenmcScalarExt::from_sval(old_value), | |
| .is_coherence_order_maximal_write = false }; | |
| } | |
| inline CompareExchangeResult from_error(std::unique_ptr<std::string> error) { | |
| return CompareExchangeResult { .status = CasStatus::Error, | |
| .error = std::move(error), | |
| .old_value = GenmcScalarExt::uninit(), | |
| .is_coherence_order_maximal_write = false }; | |
| } | |
| inline CompareExchangeResult from_invalid() { | |
| return CompareExchangeResult { .status = CasStatus::Invalid, | |
| .error = nullptr, | |
| .old_value = GenmcScalarExt::uninit(), | |
| .is_coherence_order_maximal_write = false }; | |
| } | |
| } // namespace CompareExchangeResultExt | |
| namespace MutexLockResultExt { | |
| inline MutexLockResult acquired() { | |
| return MutexLockResult { .status = MutexLockStatus::Acquired, .error = nullptr }; | |
| } | |
| inline MutexLockResult not_acquired() { | |
| return MutexLockResult { .status = MutexLockStatus::NotAcquired, .error = nullptr }; | |
| } | |
| inline MutexLockResult reset() { | |
| return MutexLockResult { .status = MutexLockStatus::Reset, .error = nullptr }; | |
| } | |
| inline MutexLockResult from_error(std::unique_ptr<std::string> error) { | |
| return MutexLockResult { .status = MutexLockStatus::Error, .error = std::move(error) }; | |
| } | |
| inline MutexLockResult from_invalid() { | |
| return MutexLockResult { .status = MutexLockStatus::Invalid, .error = nullptr }; | |
| } | |
| } // namespace MutexLockResultExt | |
| namespace MallocResultExt { | |
| inline MallocResult ok(SVal addr) { | |
| return MallocResult { .status = OperationStatus::Ok, .error = nullptr, .address = addr.get() }; | |
| } | |
| inline MallocResult from_error(std::unique_ptr<std::string> error) { | |
| return MallocResult { .status = OperationStatus::Error, | |
| .error = std::move(error), | |
| .address = 0UL }; | |
| } | |
| inline MallocResult from_invalid() { | |
| return MallocResult { .status = OperationStatus::Invalid, .error = nullptr, .address = 0UL }; | |
| } | |
| } // namespace MallocResultExt | |
| namespace FreeResultExt { | |
| inline FreeResult ok() { | |
| return FreeResult { .status = OperationStatus::Ok, .error = nullptr }; | |
| } | |
| inline FreeResult from_error(std::unique_ptr<std::string> error) { | |
| return FreeResult { .status = OperationStatus::Error, .error = std::move(error) }; | |
| } | |
| inline FreeResult from_invalid() { | |
| return FreeResult { .status = OperationStatus::Invalid, .error = nullptr }; | |
| } | |
| } // namespace FreeResultExt | |
| #endif /* GENMC_MIRI_INTERFACE_HPP */ |