feat(riscv): RV32IMAC backend skeleton — encoder, ELF builder, PMP allocator#87
Conversation
Automated review for PR #87pulseengine/synth: Verdict: 💬 Comment Summary: The pull request is approved. The changes enhance the RISC-V backend by adding support for building relocatable ELF files and multi-function ELF files. This allows for more flexible integration of the backend into various bare-metal environments. Findings: 0 mechanical (rivet) · 1 from local AI model. Findings (1):
Generated by a local AI model and post-validated against a strict JSON contract. Each finding includes the verbatim line being criticised — verify by reading the file at the cited location. Reviewed at |
…ning (#86) Consolidated PR — supersedes #83 and #85. * fix(opt): regalloc clobbers parameter registers — AAPCS violation in i64 ops * fix(no-optimize): allocate stack frame + i64 local storage in select_with_stack * feat(cli): --relocatable flag forces ET_REL output * feat(m7): Cortex-M7 single-/double-FPU hardware profiles, 16-region MPU, Renode synth_cortex_m7.repl + cortex_m7_test.robot, M7 codegen smoke * fix: 8 clippy errors (Rust 1.95 lint refresh) codecov/patch fail noted — addressed as a first-class follow-up via the coverage-uplift task that lands ahead of #87.
510b193 to
0d46897
Compare
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
…locator
Adds a parallel RISC-V code-generation path alongside the ARM backend.
This is the Track B1 deliverable in the multi-track v0.1.0 plan: a working
end-to-end pipeline (WASM → RISC-V instructions → ELF) for a small WASM
subset, plus the infrastructure that the full instruction selector will
plug into.
* New crate `crates/synth-backend-riscv/`:
- `register.rs` — 32 GPR enum, ABI-name aliases (a0..a7, s0..s11, t0..t6),
callee-saved set per the RISC-V psABI, argument-register helper.
- `riscv_op.rs` — `RiscVOp` enum covering RV32I + Zicsr + M-extension,
plus `Branch`/`Csr` typed wrappers for trap-handler use.
- `encoder.rs` — bit-level R/I/S/B/U/J encoder. Cross-checked against
canonical reference encodings (`addi a0, a0, 1` → 0x00150513,
`auipc ra, 0` → 0x00000097, `csrrw zero, mtvec, a0` → 0x30551073, ...).
`Jal`/`Branch`/`Call` return `UnresolvedLabel` from the standalone path
and have explicit `encode_jal` / `encode_branch` helpers the ELF builder
calls once byte offsets are known.
- `pmp.rs` — Physical Memory Protection allocator (RISC-V analogue of
the ARM MPU). Picks NAPOT for power-of-two regions, falls back to TOR
(consumes 2 entries) for arbitrary sizes. Generates C init code that
writes `pmpaddrN` / `pmpcfgN` CSRs.
- `elf_builder.rs` — minimal RV32 ELF emitter. Writes ET_REL by default,
EM_RISCV (0xF3) machine type, RVC e_flags. Resolves `Jal`/`Branch`
labels in a 2-pass per-function assembler; emits `.text` + `.symtab`
+ `.strtab` + `.shstrtab`.
- `selector.rs` — skeleton WASM-to-RV32 instruction selector. Handles
i32 add/sub/mul/and/or/xor, i32.const, i32.eqz, local.get/set for
params 0..7 (mapped to a0..a7), and return. Constants > 12 bits go
through `lui + addi` with proper sign-extension carry handling.
The full lowering parity with the ARM selector is the B2 deliverable.
- `backend.rs` — `Backend` trait impl. Registered in the CLI behind
a `--features riscv` flag (on by default). Reports as "riscv" with
`produces_elf=true, supports_rule_verification=false`.
* CLI wiring:
- `synth-cli` adds `riscv` feature (default). `RiscVBackend::new()`
is registered alongside ARM/w2c2/awsm/wasker.
- Compile path now dispatches to `build_riscv_elf` /
`build_multi_func_riscv_elf` when `target_spec.family == RiscV`,
instead of producing an ARM ELF for RISC-V code bytes.
```
$ cat /tmp/rv_test.wat
(module
(func (export "add") (param i32 i32) (result i32)
local.get 0 local.get 1 i32.add)
(memory (export "memory") 1))
$ synth compile /tmp/rv_test.wat -o rv.elf --backend riscv --target riscv32imac
$ file rv.elf
rv.elf: ELF 32-bit LSB relocatable, UCB RISC-V, RVC, soft-float ABI
$ xxd -s 52 -l 20 rv.elf
00000034: 9302 0500 # addi t0, a0, 0 (LocalGet 0)
00000038: 1383 0500 # addi t1, a1, 0 (LocalGet 1)
0000003c: b382 6200 # add t0, t0, t1
00000040: 1385 0200 # addi a0, t0, 0 (move result to a0)
00000044: 6780 0000 # jalr zero, 0(ra) # ret
```
* Comparisons (lt_s/lt_u/gt_s/gt_u/le/ge/eq/ne)
* Loads / stores (i32.load, i32.store, …)
* Division / remainder (div_s/div_u/rem_s/rem_u — the M-extension is
encoded but not wired into the selector yet)
* i64 lowering (RV32 register pairs)
* Control flow (block / loop / if / br / br_table)
* Calls (cross-function jal + linker relocations)
* Trap-handler / startup code (mtvec setup, mret)
* Renode RV32 platform + integration test
* RISC-V Rocq proofs
* 60 new tests in `synth-backend-riscv` (encoder vector tests, PMP allocation,
ELF round-trip, selector behavior, backend trait conformance).
* All workspace tests still pass (≥600 tests, 0 regressions).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0d46897 to
8270e66
Compare
Track B1 deliverable — adds a parallel RISC-V code-generation path alongside the ARM backend. Stacked on top of #86 (M7 hardening + AAPCS fixes) so it carries those changes too. Once #86 merges, this rebases onto main as a clean diff of just the RISC-V additions.
What this PR adds
crates/synth-backend-riscv/— registered as a--backend riscvoption in the CLI.Modules
CLI integration
End-to-end demo
```
$ synth compile add.wat -o rv.elf --backend riscv --target riscv32imac
$ file rv.elf
rv.elf: ELF 32-bit LSB relocatable, UCB RISC-V, RVC, soft-float ABI
$ xxd -s 52 -l 20 rv.elf
9302 0500 # addi t0, a0, 0 (LocalGet 0)
1383 0500 # addi t1, a1, 0 (LocalGet 1)
b382 6200 # add t0, t0, t1
1385 0200 # addi a0, t0, 0
6780 0000 # ret (jalr zero, 0(ra))
```
Encoder cross-checks
The encoder tests use literal hex values that any toolchain (GCC, LLVM, official spec tables) must produce. Examples from the test suite:
Test summary
What's deliberately out of scope (Tracks B2/B3/B4)
🤖 Generated with Claude Code