feat(riscv): RV32IMAC backend skeleton — encoder, ELF builder, PMP allocator#87
Open
avrabe wants to merge 1 commit intofix/synth-optimized-regalloc-paramsfrom
Open
feat(riscv): RV32IMAC backend skeleton — encoder, ELF builder, PMP allocator#87avrabe wants to merge 1 commit intofix/synth-optimized-regalloc-paramsfrom
avrabe wants to merge 1 commit intofix/synth-optimized-regalloc-paramsfrom
Conversation
…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>
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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