Skip to content

feat(riscv): RV32IMAC backend skeleton — encoder, ELF builder, PMP allocator#87

Open
avrabe wants to merge 1 commit intofix/synth-optimized-regalloc-paramsfrom
feat/riscv-skeleton
Open

feat(riscv): RV32IMAC backend skeleton — encoder, ELF builder, PMP allocator#87
avrabe wants to merge 1 commit intofix/synth-optimized-regalloc-paramsfrom
feat/riscv-skeleton

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented May 3, 2026

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

  • New crate crates/synth-backend-riscv/ — registered as a --backend riscv option in the CLI.
  • End-to-end pipeline working today: WASM `(add (param i32 i32) (result i32) local.get 0 local.get 1 i32.add)` → real RISC-V ELF (`file` reports "UCB RISC-V, RVC, soft-float ABI").

Modules

Module What it does
`register.rs` x0..x31 enum, ABI aliases (a0..a7, s0..s11, t0..t6), psABI callee-saved set
`riscv_op.rs` RV32I + Zicsr + M-extension instruction enum, Branch/Csr typed wrappers
`encoder.rs` R/I/S/B/U/J bit-packers, cross-checked against canonical reference encodings
`pmp.rs` Physical Memory Protection allocator (RISC-V analogue of MPU). NAPOT for power-of-2, TOR fallback
`elf_builder.rs` RV32 ELF emitter (EM_RISCV=0xF3, RVC e_flags), 2-pass label resolution
`selector.rs` Skeleton WASM-to-RV32 lowering (i32 arith, params, return). Full parity = B2
`backend.rs` `Backend` trait impl, registers as "riscv" with `produces_elf=true`

CLI integration

  • New `riscv` feature in `synth-cli` (default-enabled).
  • Compile path now dispatches to RISC-V ELF emission when `target_spec.family == ArchFamily::RiscV`, replacing the unconditional ARM ELF wrapper that was producing wrong-machine-type binaries.
  • `synth backends` now lists "riscv" alongside "arm".

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:

  • `addi a0, a0, 1` → `0x00150513`
  • `auipc ra, 0` → `0x00000097`
  • `csrrw zero, mtvec, a0` → `0x30551073`
  • `mul a0, a0, a1` → `0x02b50533`
  • `mret` → `0x30200073`
  • `fence iorw,iorw` → `0x0FF0000F`

Test summary

  • 60 new tests in synth-backend-riscv: encoder vectors, register file, PMP allocation, ELF round-trip, selector behavior, backend trait conformance.
  • All 600+ existing workspace tests still pass — zero regressions.
  • `cargo clippy --workspace --all-targets -- -D warnings` clean.
  • `cargo fmt --check` clean.

What's deliberately out of scope (Tracks B2/B3/B4)

  • Comparisons (lt_s/lt_u/gt_s/gt_u/le/ge/eq/ne)
  • Loads / stores (i32.load, i32.store, ...)
  • Division / remainder (encoded but not wired into the selector yet)
  • i64 lowering (RV32 register pairs)
  • Control flow (block / loop / if / br / br_table)
  • Cross-function calls (jal + linker relocations)
  • Trap handler / startup code (mtvec setup, mret)
  • Renode RV32 platform + integration test (B2)
  • Linker script generator (B3)
  • RISC-V Rocq proofs (later)

🤖 Generated with Claude Code

…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>
@temper-pulseengine
Copy link
Copy Markdown

Automated review for PR #87

pulseengine/synth:feat/riscv-skeleton → pulseengine/synth:fix/synth-optimized-regalloc-params

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):

  1. crates/synth-backend-riscv/Cargo.toml:1
    [dependencies]
    
    The RISC-V backend has its own dependencies.

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 510b193

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant