a risc-v 64 interpreter and JIT-to-amd64 for Go

220 views
Skip to first unread message

Jason E. Aten

unread,
May 29, 2026, 3:26:25 AMMay 29
to golang-nuts
Inspired by the C++ sandbox 


I wrote

https://github.com/glycerine/riscv-emu-golang 

which is a RISC-V cpu emulator in pure Go. 

With it you can run RV64 ELF binaries (or just snippets of RV64 assembly) fully inside your Go host program.

It includes a JIT compiler to translate RISC-V to amd64 at 
runtime for a nice 8x speedup. With that JIT-ing we are only
about 6x slower than native C compiled to amd64. 
Not bad for a fun side project.

Enjoy,
Jason


  JIT COMPARISON (rv8 vs abjit) — 2026-05-29 04:16  [macos]
  cpu: Intel(R) Core(TM) i7-1068NG7 CPU @ 2.30GHz

  Strategy                                     MIPS
  ──────────
────────────────────────────────────────
  Go JIT — rv8 Fixed Static Mapping (native): 3367 MIPS
  Go JIT — abjit (native):                    3396 MIPS
  Go interpreter (no JIT):                     432 MIPS
  libriscv — JIT (TCC):                       3517 MIPS
  libriscv — interpreter (no JIT):             846 MIPS
  libriscv — full binary translation - N/A, claims 50% native
  native x86-64 (-O3 -march=native):         16833 MIPS (150ms)
  wazero wasm aot-and-run                     8555 MIPS (but linux is near native)

Specific ISA: RV64IMAFDC with Zicsr, Zifencei, Zicond, and bitmanip extensions Zba/Zbb/Zbc/Zbs. As a single hart, atomics are processed but ignored as no-ops.

Jason E. Aten

unread,
Jun 16, 2026, 7:11:43 AM (8 days ago) Jun 16
to golang-nuts
The latest version of https://github.com/glycerine/riscv-emu-golang just shipped some big features:

* supports JIT to arm64 now too (in addition to amd64). Hopefully this
makes it work on modern Apple hardware, but I only have x86_64 and so I had to
use QEMU (software arm64 emulation) to test. If you have Apple M-? chip and can run "make test" and
let me know if it passes on actual hardware, that would be great. 

* provides a minimal OS: a small but deterministic implementation of Linux OS system calls. 
This means I can run actual standard Go toolchain compiled RV64 ELF binaries on it now.

plus: emux is a stand-alone command line to launch an emulation run. 

I think of emux as a miniature "Antithesis". It is a tiny DST tester in a small 8MB box.
It is a simple command line tool. Since the emulator is single threaded, reproducible determinism is
now viable for testing your Go programs. Of course we are about 400x slower than actual
native code on actual hardware at this point, but we still have some ideas for how to speed it up
emulation.

Enjoy,
Jason

Jason E. Aten

unread,
Jun 18, 2026, 11:57:32 AM (5 days ago) Jun 18
to golang-nuts
Update: the emulator now boots a regular Linux kernel in about 10 seconds. I shortened
the name from emux to emu, making it easier to type. And then I couldn't help but think of the flightless 
Australian bird, the emu.

small_emu_net.png

Recent improvements: I added host filesystem passthrough and automatic Tailscale network attachment
for live networking. If you start more than one emu process on the same machine,
the first emu will act as leader/gateway and proxy all the other emu's network requests
to the tailscale daemon. Since the followers automatically form a mesh net, I re-christened it "emu_net".

It's starting to shape up to be the docker/container runtime I always wished for.

Enjoy,
Jason
Reply all
Reply to author
Forward
0 new messages