Skip to content

Conversation

@marxin
Copy link
Contributor

@marxin marxin commented Aug 29, 2025

The PR adds the rudimentary support for RISC-V 64-bit target into the SinglePass compiler. Even though, the code still needs some polishing and various TODOs must be addressed, the compiler can pass all (104) the spec tests.

TODOs:

  • unwinding support
  • FP status register - save/restore
  • a general location_cmp + jmp_on_X needs polishing as RISC-V target does not provide a FLAGS register (Singlepass: refactor condition jumps to single fn (jmp_on_condition) #5750).
  • spec/address.wast is flaky (sometimes trap is not properly triggered)
  • basic testing - all compiler tests (including the WASI tests) are green now
  • include RISCV-V in CI by running the tests in qemu
  • more intensive testing needed (including real hardware + valgrind)
  • still some unimplemented entry points for the Machine trait - wipe unused entry points in the Trait

Minor improvements:

  • CLZ, CTZ and POPCOUNT have sub-optimal performance - could be implemented as builtins in the run-time (fn call overhead)

@marxin marxin requested a review from syrusakbary as a code owner August 29, 2025 17:23
theduke and others added 29 commits September 10, 2025 19:37
This is only the basic plumbing and architecture, the actual
implementation is to follow.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds experimental RISC-V 64-bit support to the Wasmer SinglePass compiler, enabling WebAssembly compilation for RISC-V architecture. The implementation includes core instruction encoding, trap handling, unwinding support, and comprehensive test coverage.

Key changes:

  • New RISC-V machine backend with full instruction set implementation (6219 lines)
  • Trap handling and unwinding support for RISC-V architecture
  • CI/CD integration with QEMU-based testing for RISC-V target
  • Test suite adaptations for RISC-V-specific limitations (jump instruction range)

Reviewed Changes

Copilot reviewed 23 out of 26 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
lib/compiler-singlepass/src/machine_riscv.rs Core RISC-V machine implementation with instruction encoding and code generation
lib/compiler-singlepass/src/riscv_decl.rs RISC-V register definitions and calling convention support
lib/compiler-singlepass/src/emitter_riscv.rs RISC-V instruction emitter (not shown in diff)
lib/vm/src/trap/traphandlers.rs Added RISC-V trap code extraction from illegal instructions
lib/compiler-singlepass/src/unwind.rs RISC-V DWARF unwinding support
tests/compilers/issues.rs Test adaptations for RISC-V limitations and Cranelift workaround
lib/compiler-singlepass/Cargo.toml Added tempfile dependency and riscv feature flag
.cargo/config.toml RISC-V cross-compilation and QEMU runner configuration
.github/workflows/build.yml CI integration for RISC-V WAST test suite
lib/compiler-singlepass/README.md Updated documentation to list riscv64 as experimental target

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@marxin
Copy link
Contributor Author

marxin commented Nov 24, 2025

The PR received a couple of fixed that very identified and reduced from running the RISC-V Singlepass compiler on tests for the following Rust crates:

  • aho-corasick
  • bstr
  • bytesize
  • glob
  • indexmap
  • itertools
  • itoa
  • memchr
  • quickcheck
  • rustc-demangle
  • rust-shlex
  • rust-url
  • ryu
  • strsim-rs
  • strum
  • time
  • tinyvec
  • unicode-ident
  • uuid

@wakabat
Copy link
Contributor

wakabat commented Dec 19, 2025

Hey so I was testing the latest commit from this branch, and I found an issue.

The quirky code can be found here: https://github.com/marxin/wasmer/blob/69720a4374dbeb56fac98431be56c5823ed70f70/lib/compiler-singlepass/src/codegen.rs#L2343-L2350

Per this definition, we know that signature index / id is actually 32 bit in size, not 64 bit.

We had a fix that solves the problem, however this fix is quite simple, so feel free to use any commit to include the fix.

@marxin
Copy link
Contributor Author

marxin commented Dec 22, 2025

Per this definition, we know that signature index / id is actually 32 bit in size, not 64 bit.

Thanks for it, picked the revision.

@wakabat
Copy link
Contributor

wakabat commented Jan 5, 2026

Hey so upon more testings, we have more to report here.

First, here are some test wasm files:
test-wasms.tar.gz

The first thing to mention is probably a bug: one of the wasm files attached above would fail the compilation process using the singlepass compiler, but works just fine using the LLVM compiler:

$ ./target/release/wasmer compile --target riscv64 --singlepass -o test-wasms/1_singlepass.wasmu test-wasms/1.wasm
Compiler: singlepass
Target: riscv64-unknown-unknown-elf
error: failed to compile `test-wasms/1.wasm`
╰─▶ 1: Compilation error: Assembler failed finalization with: ImpossibleRelocation(Dynamic(DynamicLabel(2)))
$ ./target/release/wasmer compile --target riscv64 --llvm -o test-wasms/1_llvm.wasmu test-wasms/1.wasm
Compiler: llvm-opta
Target: riscv64-unknown-unknown-elf
✔ File compiled successfully to `test-wasms/1_llvm.wasmu`.

I spent some time digging this, I think the issue is that singlepass compiler uses j when jumping to labels, but j only allows near jump(can be expressed in signed 20 bit integer), but cannot work with far jump. The included 1.wasm happens to include code that does a far jump. I have a temporary fix that would make compilation work, however I cannot be sure it is free from bug so feel free to fix the behavior in a more proper way.

Next, I want to discuss on generated code performance of the singlepass compiler.

It is worth noting that singlepass compiler generates much larger wasm files than LLVM compiler, in our tests, most singlepass binaries are 10x the size of LLVM binaries:

$ ls -lh test-wasms/
total 20M
-rw-rw-r-- 1 mint mint 258K Jan  5 16:11 0_llvm.wasmu
-rw-rw-r-- 1 mint mint 2.3M Jan  5 16:10 0_singlepass.wasmu
-rw-rw-r-- 1 mint mint  71K Jan  5 16:10 0.wasm
-rw-rw-r-- 1 mint mint 404K Jan  5 16:11 1_llvm.wasmu
-rw-rw-r-- 1 mint mint  82K Jan  5 16:10 1.wasm
-rw-rw-r-- 1 mint mint  56K Jan  5 16:11 2_llvm.wasmu
-rw-rw-r-- 1 mint mint 199K Jan  5 16:10 2_singlepass.wasmu
-rw-rw-r-- 1 mint mint  40K Jan  5 16:10 2.wasm
-rw-rw-r-- 1 mint mint 268K Jan  5 16:11 3_llvm.wasmu
-rw-rw-r-- 1 mint mint 2.6M Jan  5 16:10 3_singlepass.wasmu
-rw-rw-r-- 1 mint mint  59K Jan  5 16:10 3.wasm
-rw-rw-r-- 1 mint mint 189K Jan  5 16:11 4_llvm.wasmu
-rw-rw-r-- 1 mint mint 2.0M Jan  5 16:11 4_singlepass.wasmu
-rw-rw-r-- 1 mint mint  52K Jan  5 16:10 4.wasm
-rw-rw-r-- 1 mint mint 292K Jan  5 16:12 5_llvm.wasmu
-rw-rw-r-- 1 mint mint 3.1M Jan  5 16:11 5_singlepass.wasmu
-rw-rw-r-- 1 mint mint  58K Jan  5 16:10 5.wasm
-rw-rw-r-- 1 mint mint 268K Jan  5 16:12 6_llvm.wasmu
-rw-rw-r-- 1 mint mint 2.9M Jan  5 16:11 6_singlepass.wasmu
-rw-rw-r-- 1 mint mint  65K Jan  5 16:10 6.wasm
-rw-rw-r-- 1 mint mint 355K Jan  5 16:12 7_llvm.wasmu
-rw-rw-r-- 1 mint mint 3.6M Jan  5 16:12 7_singlepass.wasmu
-rw-rw-r-- 1 mint mint  67K Jan  5 16:10 7.wasm

It's worth mentioning that the runtime performance is much worse: we have a test that involves running all the wasm files above. For LLVM binaries, the test finished in about ~347 seconds, but for singlepass binaries, the test won't stop after running for 11 hours(and it is still running). All the other running environment in the test stays the same, it's just that in one test we use LLVM binaries from those wasm files, in another test we use singlepass binaries from those wasm files.

Even if we did the math now(note one test is still running after 5 hours), we are talking about ~100x runtime performance, and ~10x code size between LLVM compiler and singlepass compiler. I totally get that singlepass compiler will be slower, but is it expected that for larger programs, singlepass compiler can be this slow?

Is there any chance that some particular code sequences in those wasm files cause a regression somewhere? Can you help confirm if this matches your own testing between the 2 compilers? Or perhaps I made a mistake in my fix, and the code runs in an infinite loop?

As always, huge kudos for the work, and thanks a lot for the help!

@wakabat
Copy link
Contributor

wakabat commented Jan 5, 2026

Upon further checking, I think I misunderstood what SCRATCH_REG is used for. And my proposed fix is wrong. It is not a performance issue, but that my code is wrong, and the program somehow goes to an infinite loop.

So what really needs to do here, is to change this line to a jump pseudo-Instructions so we can do far-jump. But the problem is: jump requires a temporary register, and we don't really have a register to use in emit_j_label.

Let me know what you think might be a proper way to solve this. Thanks!

@wakabat
Copy link
Contributor

wakabat commented Jan 6, 2026

Okay so after some more tries, I think I've fixed the issues. A proposed fix can be found here: wakabat@e065880. This fix passes all our current tests. Feel free to review and incorporate it into this PR.

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.

4 participants