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 concurrent Rust by compiling Rust and any C/C++ foreign-function dependencies to LLVM IR, transforming that IR, and exhaustively exploring executions. The paperâs central claim is a tool capability, not a proof of functional contracts: its checks report races, assertion violations, memory errors, and panic-producing interleavings. The authors state: âRustMC is a stateless model checker that enables verification of concurrent Rust programs.â (Abstract, p. 1). They also state: âThis tool paper presents the key challenges we addressed to extend GenMC.â (Abstract, p. 1).
The paperâs working evidence is three main examples and two appendix examples, all adapted from real bugs or patterns, rather than a benchmark table with a denominator or measured speedup. It reports that RustMC identifies each described bad interleaving. Inference: this is useful as a bug-finding complement to Verus, while it does not establish 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 Inference: Verus can express a desired safety property as requires/ensures contracts and prove sequential or proof-oriented concurrent abstractions, but it does not provide RustMCâs automatic enumeration of concrete weak-memory interleavings. A VL pipeline could run RustMC on the executable concurrent harness, while using Verus contracts and state-machine proofs for the abstraction. The LLVM compilation, GenMC execution graph, and DPOR backend do not carry over as Verus source constructs.
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 count of programs, explored states, runtime, or false positives is reported; the evidence denominator is the five presented examples (three in §4 and two in Appendix A), all successfully detected according to the prose.
- GenMCâs weak-memory model and the transformed LLVM semantics are trusted for this result. FFI behavior must be available in LLVM IR; Rustâs borrow checker cannot check the cross-language global.
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)
- add more standard-library modules incrementally when a program needs them (§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: there is no direct Verus equivalent. In a Verus project, thread creation would be represented by a supported proof-oriented API or transition-system model, while the concrete library implementation remains executable code whose contracts must be supplied. The selective inlining strategy is a RustMC build transformation, not a Verus annotation.
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.
- A stated limitation is that programs using standard-library functions outside the inlined modules cannot currently be verified (§3.1, p. 5). External definitions therefore remain an assumption or unsupported boundary.
technique 3: lower Rust memory intrinsics to typed accesses
what it is
- transform
memcpy,memmove, andmemsetcalls into individual loads and stores that GenMC can track as events - 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 intrinsics and their source/destination alignment and length (§3.2, p. 5)
- for aligned lengths that are multiples of 8 bytes, replace the copy with sequential 64-bit loads and stores (§3.2, p. 5)
- run LLVM
ipsccpandmemcpyoptpasses to expose additional transformable copies (§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 Verus proof should reason about the abstract sequence or mathematical contents of a copy through a specification function and loop invariant; it cannot reuse RustMCâs LLVM pass. A low-level implementation may remain external_body only for an explicitly trusted primitive, but the verified wrapper must state and prove the copy contract using constructs documented for Verus.
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. The only quantitative condition is the heuristicâs 8-byte alignment/multiple rule, not an evaluation denominator.
- Mixed-size semantics and GenMCâs event decomposition are trusted; a heuristic can reject a valid program or fail to model an edge case.
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
- detect stack allocations that can be read before an ordinary store (§3.3, p. 6â7)
- insert an explicit
store undeffor each allocation (§3.3, p. 7) - let GenMC interpret that explicit write as storing zero, giving the load a predecessor (§3.3, p. 7)
- verify the transformed program; the inserted write is intended to preserve LLVMâs 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: Verus specifications should model only initialized logical values or explicitly include an initialization invariant; there is no Verus construct that makes LLVM undef a proof fact. Treating the inserted zero-like event as a semantic model requires a trusted executable boundary and a proof that the abstraction preserves the intended behavior.
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). - LLVMâs undefined-value rules, the pass, and GenMCâs interpretation of
store undefare trusted. The technique does not prove that arbitrary undefined behavior is benign.
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)
- include an assertion or unsafe operation whose failure exposes the bug
- run RustMC over all supported interleavings
- use the reported conflicting reads/writes or panic-producing schedule to repair synchronization or unsafe trait assumptions
The paper reports four concrete patterns. In §4.1, two relaxed atomic flags in an is_getrand_available function can return different results; RustMC finds the atomicity violation. In §4.2, an unsafe Sync implementation around Cell<i64> permits two increment calls; RustMC reports the race. Appendix A.1 checks a bounds test followed by get_unchecked after another thread increments the index, and reports the resulting out-of-bounds panic. Appendix A.2 creates two mutable raw-pointer references to x; RustMC reports conflicting writes at lines 8 and 10.
Verus form Inference: Verus can state an invariant that an index remains below vector length, or a state-machine invariant that only one writer owns a cell, but proving these examples requires modeling atomics, thread ownership, and unsafe pointer validity. RustMCâs concrete counterexample schedule is useful evidence for debugging; it is not itself a Verus proof.
evidence and limits
- The denominator is five examples in the paper: one FFI race (Figure 1), two §4 examples, and two appendix examples; the paper says each was detected, so the reported detection fraction is 5/5 presented examples, not a general benchmark 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
RustMC proves a bounded model-checking result under GenMCâs memory models for the transformed LLVM module: if it finds no reported error in the explored state space, that is evidence for the modeled executions. It does not prove Rustâs full type/ownership semantics, FFI correctness, arbitrary library behavior, or a Verus contract. Trusted components include rustc and its LLVM lowering, the customised standard library, LLVM transformation passes, GenMCâs execution semantics and DPOR, and the mapping of undef and mixed-size accesses. Inference: a product that generates agent-written verified Rust should use RustMC as an additional concurrency and unsafe-code bug finder, especially around FFI, atomics, and raw pointers; it is a moderate threat to product claims if the product promises concurrency safety without this dynamic/exhaustive check, but a smaller threat to deductive Verus contract proving because the tools target different obligations.
The paper says its intended future work is to apply RustMC to real projects and dependencies (§6, p. 10). What VL should not redo: the paper already supplies the LLVM/GenMC bridge, selective threading inlining, aligned-copy lowering, and explicit-undef workaround. A new VL contribution would need to connect these checks to Verus specifications, contracts, and proof-maintenance workflows, or to evaluate larger real-world Rust/Verus projects with a stated denominator.
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
Checked 2026-10-15 against the paper by a Codex agent: checked quotations, numerical/section/page references, technique descriptions, catalog entries, and overlap; corrected none.
Last edited: