Autonomous C/C++ → Rust code migration system.
AutoReCoder takes a C codebase, produces a provably-correct (but fully unsafe) Rust translation
via c2rust, then runs an agent loop that incrementally
removes unsafe blocks — one function at a time — without ever breaking correctness.
The key insight: instead of translating C to Rust in one shot (which fails for large, complex codebases), start from a working but fully-unsafe baseline and make it safe incrementally.
C source
│
▼ bootstrap.py (runs once)
workspace/src/ ← c2rust translation [100% unsafe, but correct]
workspace/corpus/ ← locked fuzzing corpus
│
▼ oracle.py (locked ground truth)
├── pass_rate = Rust output matches C output on every corpus input
└── unsafe_count = number of `unsafe` blocks remaining
│
▼ agent loop via program.md (runs until unsafe_count == 0)
├── pass_rate == 1.0 AND unsafe_count ↓ → keep commit, log, update patterns
└── otherwise → git reset --hard HEAD~1, try again
Two metrics drive everything:
pass_rate— hard constraint, must stay at 1.000. The Rust binary must produce identical output to the original C binary on every corpus input.unsafe_count— optimization target. Minimize whilepass_ratestays at 1.000.
System dependencies (macOS):
brew install llvm@16 cmake bear radamsac2rust requires LLVM 16 specifically — LLVM 17+ has breaking API changes in its AST exporter.
bearis needed to extractcompile_commands.jsonfrom Makefile-based projects.radamsais optional but improves corpus quality.
Rust toolchain:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup install nightly
rustup install nightly-2022-08-08 # required by c2rust's build scripts
rustup component add miri --toolchain nightlyPython package manager:
curl -LsSf https://astral.sh/uv/install.sh | sh
uv syncgit clone --recurse-submodules https://github.com/isztld/AutoReCoder.git
cd AutoReCoderThe --recurse-submodules flag is required to pull in vendor/c2rust.
cd vendor/c2rust
LLVM_CONFIG_PATH=/opt/homebrew/opt/llvm@16/bin/llvm-config \
cargo +nightly-2022-08-08 build --release -p c2rust
cd ../..The
-p c2rustflag skipsc2rust-refactor, which requires additional Python 2 code generation scripts and is not needed for transpilation.
Copy your C/C++ source into workspace/original/. You must also write a driver — a small
program that reads from stdin, calls your library's main entry point, and writes to stdout.
This is what the oracle fuzzes and diffs against the Rust translation.
workspace/original/ contains a ready-to-use example: miniz
(a single-file zlib implementation) with a compress/decompress round-trip driver.
uv run bootstrap/bootstrap.pyThis detects your build system, runs c2rust, verifies the baseline passes at pass_rate: 1.000,
and generates a locked corpus of 500 test inputs. It prints progress as it goes:
[OK] Build system: make
[OK] compile_commands.json generated
[OK] c2rust translation complete → workspace/src/ (1,247 unsafe blocks)
[OK] Baseline pass_rate: 1.000
[OK] Corpus generated: 500 inputs → workspace/corpus/
[OK] Bootstrap complete. Starting unsafe_count: 1247
After bootstrap, commit the generated baseline and tag it:
git add workspace/src/ workspace/callgraph.json
git commit -m "bootstrap: c2rust baseline"
git tag baseline
uv run oracle.py # verify: pass_rate: 1.000
workspace/corpus/is in.gitignoreby design — corpus inputs are binary files that bloat the repository. They live only on the machine running the migration.
Open the repo in Claude Code and use this prompt to start:
Read program.md and run the migration agent loop. Start from setup verification,
then run experiments continuously. Log every result to results/results.tsv.
Claude Code automatically loads CLAUDE.md as its system context, so all constraints
and output formats are already in place. The agent will:
- Call
uv run oracle.py --list-targetsto rank unsafe functions - Edit
workspace/src/— one function per experiment - Commit, run
uv run oracle.py, check pass_rate and unsafe_count - Keep the commit if both metrics are satisfied; otherwise
git reset --hard HEAD~1 - Log every attempt to
results/results.tsvand updatepatterns/patterns.md - Repeat until
unsafe_count == 0or you halt it
To watch progress in real time:
tail -f results/results.tsvYou can also run experiments manually — the oracle and git loop work the same way.
uv run oracle.py # full evaluation (build, diff test, unsafe count)
uv run oracle.py --unsafe-only # count unsafe blocks only (fast, no compile)
uv run oracle.py --list-targets # ranked JSON list of functions to migrate nextOutput format (stable — the agent parses this):
pass_rate: 1.000
unsafe_count: 247
unsafe_fns: ["parse_header", "alloc_node", "compress_block"]
compile_status: ok
miri_errors: 0
peak_memory_mb: 142
patterns/patterns.md is a growing library of safe Rust equivalents for common C idioms,
maintained by the agent across experiments. Each entry tracks success rate so the agent
can prioritise approaches that have worked before.
Patterns included by default:
redundant-inner-unsafe— c2rust blanket-wraps function bodies inunsafe {}; if the body contains no unsafe operations (pure arithmetic, comparisons, constant ops), just remove the blockraw-pointer-array-access—ptr[i]→ slice indexing viafrom_raw_partsmalloc-free-pair→Box::new()/Vecmanual-string-buffer→format!()/String::with_capacity()linked-list-node→Option<Box<Node>>global-mutable-state→AtomicI32/OnceLock<Mutex<T>>transmute-cast→f32::to_bits(),from_be_bytes()arena-allocator→bumpaloor index-based approach
| Build System | Detection file | How compile_commands.json is extracted |
|---|---|---|
| CMake | CMakeLists.txt |
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON |
| Make | Makefile |
bear -- make |
| Autoconf | configure |
./configure && bear -- make |
| Meson | meson.build |
meson setup (native support) |
CLAUDE.md— agent instructions (constraints, experiment loop, done conditions)ARCHITECTURE.md— technical design of each componentpatterns/patterns.md— C→Rust migration pattern libraryprogram.md— agent loop directives (customise per migration session)
MIT
