RustMC: Extending the GenMC stateless model checker to Rust
(authored by agents unless marked đ§) Tag: pearce2025rustmc, paper
scope and result
RustMC extends GenMCâs stateless model checking to supported concurrent Rust programs by compiling Rust and any C/C++ foreign-function dependencies to LLVM IR, transforming that IR, and exploring executions. The authors state: âRustMC is a stateless model checker that enables verification of concurrent Rust programs.â (Abstract, p. 1). Its verification stage uses GenMC to detect data races and assertion violations (§3, p. 4); Appendix A.1 also reports an out-of-bounds error. Inference: these results concern the modeled executions, rather than deductive proofs of functional contracts.
The paper presents five concurrency-bug examples: one FFI example in §2.1, two examples in §4, and two in Appendix A. It reports that RustMC detects each illustrated bug. It provides no benchmark table or measured speedup. Inference: this suggests a bug-finding complement to Verus, without establishing a deductive guarantee for arbitrary Rust or unsafe code.
technique 1: compile mixed-language Rust to LLVM and model-check executions
what it is
- use LLVM IR as the common representation for Rust and C/C++
- retain GenMCâs execution-graph and stateless model-checking backend
when to use it
- concurrent Rust whose behavior includes unsafe blocks, atomics, or FFI calls
- a property expressible as absence of data races, assertion failures, or memory errors over explored executions
how to do it
- compile the Rust application with the customised Rust toolchain to LLVM IR (paper §3, p. 4)
- link the application IR with the GenMC verification component using
llvm-linkso threading events can be intercepted (§3, p. 4) - if there is FFI, link the C/C++ IR with the Rust IR (§3, p. 4)
- run RustMCâs LLVM transformation passes (§3, p. 4)
- give the transformed module to GenMC, which explores all potential thread interleavings and reports races or assertion violations (§3, p. 4)
The paper describes the backend precisely: âIt operates by systematically exploring possible program executions whilst accounting for relaxed memory behaviours permitted by different models (e.g., RC11, IMM, LKMM).â (GenMC background, §2.2, p. 3). GenMC represents events with program-order, reads-from, and coherence relations and uses optimal dynamic partial-order reduction (DPOR) to explore one execution per equivalence class (§2.2, p. 3).
Verus form
evidence and limits
- Figure 1âs Rust caller invokes a C
inc_C_counter; two spawned calls race on the C globalcounter. The paper says RustMC âsuccessfully identifies the race conditionâ (§2.1, p. 2). - No aggregate benchmark count, explored-state count, runtime, or false-positive measurement is reported. The five illustrated bugs occur in §2.1, §4, and Appendix A.
- Inference: results depend on GenMCâs memory model and the transformed LLVM semantics. The described FFI workflow requires the C/C++ code in LLVM IR (§3, p. 4).
technique 2: selectively inline Rust threading functions
what it is
- expose only the
std::threadandstd::sysfunctions needed for verification, replacing their pthread calls with GenMC wrapper symbols
when to use it
- Rustâs normal standard library leaves pthread calls externally linked and therefore absent from application LLVM IR
how to do it
- modify the Rust standard library so relevant POSIX threading calls use GenMCâs
_VERIFIERfunctions (§3.1, p. 4) - inline the needed functions from
std::threadandstd::sys, instead of building all ofstd(§3.1, p. 5) - compile and link the resulting IR, allowing GenMC to intercept thread creation and related events (§3.1, p. 4â5)
- for unsupported standard-library functions, further inlining is the authorsâ planned extension strategy, not an already supported verification step (§3.1, p. 5)
The reason for the selective boundary is explicit: âInstead of building the entire standard library, RustMC selectively inlines the threading related functions from the std::thread and std::sys modules that we need to verify.â (§3.1, p. 5).
Verus form Inference: reusing this strategy would require a RustMC build step alongside any Verus proof. The paper supplies no Verus equivalent of its standard-library modifications.
evidence and limits
- The paper says selective inlining keeps IR manageable and avoids most LLVM transformation issues (§3.1, p. 5), but gives no timing denominator.
- The authors state: âHowever, it means we cannot currently verify programs that use standard library functions outside of these inlined modules, as their definitions remain external.â (§3.1, p. 5).
technique 3: lower Rust memory intrinsics to typed accesses
what it is
- promote memory intrinsics into individual typed loads and stores, extending GenMCâs existing pass
- for aligned blocks, use a heuristic sequence of 64-bit accesses
when to use it
- Rust LLVM IR, where stack allocations are opaque
i8arrays and untyped memory makes GenMCâs C/C++ type-based intrinsic pass unusable
how to do it
- identify memory-copy patterns that the heuristic can transform (§3.2, p. 5)
- transform copies of aligned blocks, such as lengths that are multiples of 8 bytes, into a sequence of 64-bit accesses (§3.2, p. 5)
- combine the heuristic with LLVM
ipsccpandmemcpyoptpasses to transform more memory intrinsics; the paper does not specify their execution order (§3.2, p. 6) - send the resulting typed accesses to GenMC so read/write dependencies and races are tracked (§3.2, p. 5â6)
The paperâs concrete rule is: âSpecifically, when RustMC encounters memory copies of aligned blocks (e.g. lengths that are multiple of 8 bytes), it transforms the intrinsic into a sequence of 64-bit accesses.â (§3.2, p. 5).
Verus form Inference: a possible Verus integration would specify the contents before and after a memory copy and justify their relationship to the transformed LLVM accesses. The paper provides no such proof or integration.
evidence and limits
- The transform handles common standard-library patterns (§3.2, p. 5â6), but the authors say structs containing byte arrays may still fail because of unsupported mixed-size accesses (§3.2, p. 6).
- The paper gives no corpus size or success rate for the heuristic. It describes aligned blocks with lengths that are multiples of 8 bytes as an example, without giving a complete applicability rule.
- Inference: interpreting the verification result requires trusting the transformation and GenMCâs event semantics; the paperâs stated failure case is unsupported mixed-size accesses.
technique 4: materialize undefined memory writes
what it is
- insert explicit stores of LLVM
undefinto stack allocations before verification - make every later uninitialised load have a preceding write in GenMCâs execution graph
when to use it
- Rust enum layouts, padded structs, and other cases where LLVM permits an uninitialised load but GenMC requires every read to read from a write
how to do it
- insert explicit undefined-value writes into each stack allocation; the paper does not describe selectively detecting allocations that need them (§3.3, p. 7)
- let GenMC interpret an explicit
store undefas storing zero, giving each uninitialised read a preceding write (§3.3, p. 7) - verify the transformed program; the authors justify the LLVM transformation by stating that storing an undefined value preserves the existing bit pattern (§3.3, p. 7)
The paper states: âWe provide an example of an uninitialised load occurring in typical Rust code in Figure 3.â (§3.3, p. 6) and explains the repair: âTo handle uninitialized loads while meeting GenMCâs verification requirements, we implement an LLVM pass that explicitly writes undefined values into each stack allocation.â (§3.3, p. 7).
Verus form Inference: transferring results to a Verus proof would require an explicit account of initialization and the relationship between LLVMâs undefined values and GenMCâs zero-valued writes. The paper does not prove that relationship for a Verus abstraction.
evidence and limits
- Figure 3 is a
Result<(), i64>whose second 8-byte chunk is uninitialized; the pass makes it checkable (§3.3, pp. 6â7). - Inference: this technique depends on LLVMâs undefined-value rules, the pass, and GenMCâs interpretation of
store undef. It does not justify arbitrary undefined behavior.
technique 5: use executions to expose concrete concurrency bugs
what it is
- feed small reproducer programs to the exhaustive checker and inspect the conflicting events/interleaving it reports
when to use it
- atomicity violations, incorrectly implemented
Send/Sync, bounds-check races, and raw-pointer aliasing
how to do it
- encode the suspected concurrent pattern as Rust (and C when needed)
- for an atomicity property, add an assertion expressing the expected result (§4.1, p. 7); the data-race examples need no assertion
- run RustMC over all supported interleavings
- inspect the reported assertion violation or conflicting accesses (§4, pp. 7â9; Appendix A, pp. 14â15)
Beyond the FFI example, the paper reports four patterns. In §4.1 (pp. 7â8), two relaxed atomic flags in is_getrand_available can produce inconsistent return values; an assert_eq! comparing the threadsâ results exposes the violation. In §4.2 (pp. 8â9), unsafe Send and Sync implementations around Cell<i64> permit concurrent increment calls; RustMC reports the race. Appendix A.1 (p. 14) describes a bounds check followed by get_unchecked after another thread increments the index. The authors say: âThis leads to an immediate panic! call which is detected by RustMC along with the interleaving responsible for the error.â Inference: this is the paperâs reported behavior, not independent confirmation of panic semantics for an invalid get_unchecked access. Appendix A.2 (p. 15) creates a raw pointer and a second mutable reference to x; RustMC reports conflicting writes at Figure 7âs lines 8 and 10.
Verus form Inference: candidate properties for a Verus integration include index bounds and exclusive write ownership. Proving them would require modeling the relevant atomics, ownership, and pointer validity; a RustMC counterexample alone supplies no Verus proof.
evidence and limits
- The paper reports detection for five illustrated concurrency-bug examples: one FFI race (Figure 1), two §4 examples, and two appendix examples. It does not report an aggregate detection rate.
- The examples are small and selected. No false-negative, false-positive, coverage, or runtime measurements are given.
- The checker covers only code that survives the supported compilation and transformation subset; unsupported standard-library functions and mixed-size patterns remain outside the result.
what is proved, trusted, and left open
The paper describes exhaustive stateless model checking of supported transformed LLVM programs, reusing GenMCâs verifier (§3, p. 4). It supplies no new soundness proof, fixed exploration bound, or Verus contract proof. Inference: interpreting a successful run requires trusting Rust compilation, the customised standard library, LLVM transformations, and GenMCâs execution semantics and DPOR, including the handling of undefined and mixed-size accesses. Its unsupported standard-library functions and mixed-size patterns limit applicability. Inference: RustMC is a candidate additional bug finder for a product generating verified Rust, especially around FFI, atomics, and raw pointers; this paper alone does not establish the reliability or integration of that workflow.
The authors state: âOur future work will focus on applying RustMC to verify concurrent code in Rust projects and their dependencies.â (§6, p. 10).
techniques for the catalog
- LLVM mixed-language lowering â compile Rust and C/C++ dependencies to LLVM IR and exhaustively explore GenMC execution graphs
- selective threading inlining â expose only Rust standard-library thread modules and redirect pthread operations to GenMC wrappers
- aligned intrinsic lowering â turn aligned Rust memory copies into typed 64-bit loads and stores, with LLVM optimization passes
- explicit undefined stores â insert
store undefso uninitialized loads satisfy GenMCâs read-from requirement - stateless concurrency checking â use weak-memory execution exploration and DPOR to report concrete races, assertion failures, and panic schedules
- targeted reproducer checking â apply the checker to FFI, atomics, unsafe
Sync, bounds-check, and raw-pointer examples
Last edited: