Imported from xen0n/larva (
AGENTS.md). Install upstream withnpx skills add xen0n/larva. Copyright stays with the author.
AGENTS.md
This document guides AI agents working on LARVa — a proof-of-concept RISC-V to LoongArch binary translator. Use this as the maintainer-preferred guide for automated changes.
Key expectations
- Check TODO.md first — Before starting work, read
TODO.mdto see what's planned and claim tasks via the mutex workflow - Keep changes minimal and scoped.
- One logical change per commit (no unrelated edits in the same commit).
- Prefer safe, idiomatic Rust; avoid
unsafeunless absolutely necessary. - Avoid reformatting unrelated files.
- Review diffs for unrelated changes before finalizing.
- Update this AGENTS.md when architectural changes occur.
Project overview
- Language: Rust (Edition 2021)
- Goal: Near-native RISC-V (RV64GC) emulation on LoongArch via binary translation
- License: GPL-3.0-or-later
Platform requirements
- Host: x86_64 for development; designed to be architecture-agnostic
- Floating-point: IEEE 754-2008 with nan2008 NaN semantics required
- Standard on modern systems (Loongson 3A4000+, x86_64, ARM)
- Legacy MIPS may differ in NaN signaling behavior
High-level layout:
src/rv/: RISC-V instruction definitions, decoding, disassemblysrc/exec/: Execution engine (interpreter, memory management)src/bin/: Executable tools (larva-disas,larva-test)docs/: Design documentation and instruction correspondence tables
Common commands
# Build
cargo build
# Build release
cargo build --release
# Run tests
cargo test
# Run the hello-world demo
cargo run --bin larva-test
# Disassemble a RISC-V binary
cargo run --bin larva-disas -- <file>
# Check formatting
cargo fmt -- --check
# Run clippy
cargo clippy -- -D warnings
Run the minimal set needed for the touched area.
Architecture notes
Instruction handling
src/rv/insn.rs: CentralRvInsnenum with all instruction variantssrc/rv/args.rs: Instruction argument types (R-type, I-type, etc.)src/rv/disas_helper.rs: Disassembly formatting helpers- Adding new instructions: update enum, decoder, and interpreter
Execution flow
- Decode:
RvDecoderconverts bytes toRvInsn - Interpret:
RvInterpreterExecutorexecutes one instruction at a time - Memory:
GuestMmuhandles guest→host address translation - Syscalls: Minimal linux-user emulation in
src/exec/interp/syscall.rs
StopReason handling
The interpreter returns StopReason after each instruction:
Next: Continue to next PCContinueAt(addr): Jump to addrBreak,Segv,ReservedInsn: Stop execution
Code style and conventions
- Follow
rustfmtdefaults; runcargo fmtbefore committing - Clippy-clean code; no warnings in CI
- Prefer explicit error handling over
unwrap()/expect() - Use
todo!()for unimplemented cases (mark with issue reference if known) - Keep
unsafeblocks minimal and documented with safety comments
Large-scale changes
When making large-scale changes (e.g., fixing clippy warnings, refactoring, formatting), split into logical, atomic commits rather than one big commit:
- One logical change per commit — if the change touches multiple independent aspects, split them
- Group by concern — e.g., one commit per lint type, one commit per module refactor
- Avoid mixing — don't combine formatting fixes with logic changes
Examples:
# Good: fixing multiple clippy warnings
# Commit 1: fix precedence warnings in mem.rs
# Commit 2: fix redundant-field-names in mem.rs
# Commit 3: fix uninlined-format-args across all files
# Good: refactoring
# Commit 1: extract helper functions in module A
# Commit 2: extract helper functions in module B
# Commit 3: update call sites
This makes reviews easier, history more meaningful, and allows selective reverts.
Commit message style
Follow Conventional Commits:
<type>(<scope>): <summary>
Types:
feat: New feature or instruction implementationfix: Bug fixrefactor: Code restructuring without behavior changedocs: Documentation onlytest: Adding or fixing testsbuild: Build system or dependencies
Scopes:
rv: RISC-V decoding/instructionsexec: Execution engineinterp: Interpreter specificallymmu: Memory managementsyscall: System call handlingdisas: Disassemblertest: Test utilities
Guidelines:
- Imperative, present-tense summary (no trailing period)
- ~50-72 characters for summary
- One logical change per commit
- Include body explaining motivation for non-trivial changes
- For LLM-generated commits:
- Add
Original prompt:in body with blockquote - Add
Co-authored-by: <agent model name>trailer
- Add
Examples:
feat(rv): implement remaining RV64A atomic operations
Implement lr.d, sc.d, and all AMO instructions for RV64A.
Only single-threaded execution is supported; atomics
behave sequentially consistent.
Original prompt:
> Implement the remaining atomic operations in the interpreter.
Co-authored-by: Kimi k2.5
Debugging techniques
When debugging RISC-V emulation issues, use these techniques to isolate problems:
QEMU user-mode comparison
Use QEMU's user-mode emulator as a reference to understand expected behavior:
# Run with strace to see system calls
QEMU_STRACE=1 qemu-riscv64 ./program arg1 arg2
# Trace memory operations with detailed output
qemu-riscv64 -d trace:target_mmap,trace:target_mmap_complete,trace:target_mprotect,trace:target_munmap ./program
# See all trace options
qemu-riscv64 -d help
Using strace with QEMU
Trace host-level system calls to understand guest behavior:
# Trace memory-related syscalls
strace -e trace=memory,mmap,munmap,mprotect qemu-riscv64 ./program
# Full syscall trace with timing
strace -tt -T qemu-riscv64 ./program 2>&1 | head -100
Interpreter debug mode
Enable instruction-level tracing in the interpreter:
// In larva-run or test code:
let mut executor = RvInterpreterExecutor::new(64, &mut state, &mut mmu);
executor.debug(true); // Prints each instruction before execution
This outputs:
pc = 00000000000101a0
decoded 4b: Auipc(UJTypeArgs { rd: 3, imm: 32768 })
Syscall debugging
Add syscall tracing to identify unimplemented or misbehaving syscalls:
// In syscall.rs, the debug flag already prints:
syscall: 64 (0x1, 0x7fffffff0020, 0xc, 0x0, 0x0, 0x0)
// nr (arg0, arg1, arg2, ...)
Memory layout debugging
For MMU issues, add debug prints to trace mappings:
eprintln!("mmap_fixed: addr={:#x} len={:#x}", addr.0, len);
eprintln!("checking {} existing regions", regions.len());
Guest binary analysis
Inspect RISC-V binaries to understand their layout:
# View program headers (loads, permissions)
readelf -l ./program
# View dynamic symbols (if any)
readelf -s ./program | head -30
# Check for static vs dynamic linking
file ./program
# Disassemble entry point
riscv64-linux-gnu-objdump -d ./program | head -50
Stack layout debugging
Compare stack layouts between working (QEMU) and non-working runs:
# In GDB with QEMU
qemu-riscv64 -g 1234 ./program &
gdb-multiarch -ex "target remote :1234" -ex "x/20xg \$sp" ./program
Environment variables for debugging
Consider adding envvar-guarded debug output:
if std::env::var("LARVA_DEBUG").is_ok() {
eprintln!("MMU: mapping {:#x} len={:#x}", addr, len);
}
Common patterns:
LARVA_DEBUG=1— general debugLARVA_DEBUG_SYSCALLS=1— syscall tracingLARVA_DEBUG_MMU=1— memory operations
Comparing with native execution
For static binaries, compare register states at key points:
- Run under QEMU with GDB, break at
_start - Record register values and memory contents
- Compare with LARVa at same PC
- Divergence indicates the bug location
Testing strategy
Test types
| Test | Command | Purpose | Architecture |
|---|---|---|---|
| Unit tests | cargo test --lib |
Test individual functions and modules | Architecture-agnostic (Rust) |
| Integration | cargo run --bin larva-test |
Run hardcoded RISC-V hello-world | Runs on host, interprets RISC-V |
| Disassembler | cargo run --bin larva-disas <file> |
Disassemble RISC-V binaries | N/A (pure decode) |
Test architecture notes
- Interpreter tests (
larva-test): Architecture-agnostic — the interpreter emulates RISC-V on any host architecture (currently tested on x86_64, but should work on LoongArch, ARM, etc.) - Binary translation tests: Will be architecture-specific when implemented (require LoongArch host)
- CI runs: All tests run on GitHub-hosted
ubuntu-latest(x86_64)
Adding tests
- Add unit tests in
#[cfg(test)]modules within source files - For MMU, memory, and core interpreter: test with
cargo test --lib - For end-to-end: extend
larva-testor add new test binaries - The
larva-testbinary must output "hello world" — this is the smoke test
Test requirements for PRs
feat and fix commits must include tests. This means:
- New features: add unit tests demonstrating the feature works
- Bug fixes: add a test that would have caught the bug
- Refactors: ensure existing tests still pass
Tests can be simple — the goal is verification, not exhaustive coverage. Example:
#[test]
fn test_new_feature() {
let result = my_new_function(42);
assert_eq!(result, expected_value);
}
Testing against real RISC-V binaries
Compile test programs with:
riscv64-linux-gnu-gcc -static -o test_prog test.c
Then run through the interpreter (when syscall coverage is sufficient).
Roadmap priorities
See README.md for full roadmap. Current focus areas:
- Syscalls: Expand beyond
write/exit_groupto support real programs - Floating point: Implement RVF/RVD (currently mostly
todo!()) - Atomics: Implement RV32A/RV64A for multi-threaded guests
- Translation: Begin LoongArch code generation (the core BT engine)
TODO.md workflow
Use TODO.md to coordinate work between agents and prevent duplicate effort.
Proper mutex workflow
The correct workflow is:
- Open an "acquire" PR first — This PR only updates TODO.md to claim the task in the Mutex table
- Work on implementation — Push commits to the same branch
- Final commit drops the mutex — Last commit removes your entry from Mutex (or marks task Done)
- Merge — The single PR contains both the claim and the implementation
Example workflow
Step 1: Open PR to claim task
git checkout -b feat/rv64a-atomics
# Edit TODO.md: add entry to Mutex table
git commit -m "docs(todo): claim RV64A atomics task"
git push && gh pr create
Step 2-4: Implement and finalize
# ... implement features ...
git commit -m "feat(interp): implement LR/SC instructions"
git commit -m "feat(interp): implement AMO operations"
# Edit TODO.md: remove from Mutex, add to Done
git commit -m "docs(todo): mark RV64A atomics complete"
git push
Mutex dropping as HEAD commit
Important: The mutex-dropping commit must be the last commit (HEAD) on the branch. This ensures:
- If the PR needs reverts, the mutex claim is automatically reverted too
- Clear separation between implementation and administrative changes
- Easy review — the final commit shows task completion
Wrong (mutex dropped in middle):
abc123 docs(todo): drop mutex ← not HEAD
def456 feat: implement X
Right (mutex dropped as HEAD):
abc123 feat: implement X
def456 docs(todo): drop mutex ← HEAD
Claiming a task (Mutex table format)
| Task | Assigned To | PR/Branch | Started |
|------|-------------|-----------|---------|
| RV64A atomics | @agent-name | #7 / feat/rv64a | 2026-02-08 |
Rules
- Acquire mutex FIRST — Open the claim PR before starting implementation
- One task at a time per agent in Mutex
- Drop mutex in final commit — The PR that implements the work also removes the claim
- Don't claim without a PR — The PR proves you're actually working on it
- Small, focused PRs — If a task is large, consider splitting into sub-tasks
Documentation style
When editing Markdown files:
- Blank line after headings — always add a blank line after any heading (
#,##,###, etc.) - Blank line before code blocks — always add a blank line before fenced code blocks (```)
Good:
## Section Title
Text here.
### Subsection
More text.
```rust
fn main() {}
Bad:
```markdown
## Section Title
Text here.
### Subsection
More text.
```rust
fn main() {}
## Pre-commit checklist
Before committing, always run these checks:
```bash
# 1. Format check
cargo fmt -- --check
# 2. Lint check
cargo clippy -- -D warnings
# 3. Build check
cargo build
# 4. Test check (if applicable)
cargo test --lib
# 5. Run hello-world (for interpreter changes)
cargo run --bin larva-test
Fix issues before committing. Do not commit code that fails any of these checks.
Validation checklist (for PRs)
-
cargo buildcompiles without errors -
cargo clippy -- -D warningsis clean -
cargo fmt -- --checkpasses -
cargo test --libpasses (if tests added/modified) -
cargo run --bin larva-testoutputs "hello world" - New instructions tested with hand-crafted or compiled RISC-V code
