JRISC-V is a pure Java RV64 user-mode emulator. Its long-term goal is to run every Linux and FreeBSD RISC-V 64-bit user-space program without a guest kernel.
Today it can already run real Linux RISC-V userland without a Linux kernel: Ubuntu Base binaries, interactive Bash, fastfetch, SQLite, CoreMark, Go programs, and RVV workloads. FreeBSD user-space support has also started, with static FreeBSD Go programs running through the FreeBSD syscall ABI.
- JDK 25
During development, run an ELF directly through Gradle:
./gradlew run --args="path/to/program.riscv64-musl"
Pass guest arguments after the ELF path:
./gradlew run --args="path/to/program.riscv64-musl alpha --beta"
You can also package it as a Shadow JAR:
./gradlew shadowJar
java -jar build/libs/riscv-1.0-SNAPSHOT-all.jar path/to/program.riscv64-musl
Usage:
jriscv [options] <program.elf> [program-args...]
jriscv [options] --guest-program <path> [program-args...]
Options:
--guest-program <path> Load the executable from an absolute guest path resolved through mounts.
--memory-base <address> Set the guest memory base address: auto, decimal, or 0x-prefixed hex.
Default: 0. auto derives the base from ELF load segments.
--memory-size <bytes> Set the guest virtual address window size.
--page-size <bytes> Set the guest base page size; must be a power of two >= 4096.
--max-committed-pages <n> Limit committed guest base pages; 0 disables the limit.
--huge-page-size <bytes> Set the guest HugeTLB page size.
--huge-pages <n> Set the number of guest HugeTLB pages.
--vector-vlen <bits> Set vector register length. Default: 128.
--max-instructions <count> Stop after this many guest instructions; 0 disables the limit.
--mount <spec> Add a guest filesystem mount:
type=bind|tar,src=<path>,dst=<guest>[,readonly|rw][,memory]
or type=tmpfs,dst=<guest>[,readonly|rw].
--network <none|host> Select the host socket backend. Default: none.
--use-host-tty Attach guest /dev/tty to the host controlling terminal when available.
--framebuffer <width>x<height>
Open a Swing framebuffer and expose it as guest /dev/fb0.
--framebuffer-scale <n> Set integer Swing framebuffer scale. Default: 3.
--root Use root identity; equivalent to --user root --uid 0 --gid 0 --groups 0.
--user <name> Set the guest login name exported in the default environment.
--uid <id> Set guest real, effective, and saved uid. Default: 1000.
--gid <id> Set guest real, effective, and saved gid. Default: 1000.
--groups <ids|none> Set comma-separated supplementary guest gids, or none.
--home <path> Set the guest home directory used by the default environment.
--shell <path> Set the guest shell path used by the default environment.
--env <name[=value]> Set a guest environment variable; without =, copy host value.
--debug-fixed-clock-nanos <nanos>
Use fixed epoch nanoseconds for deterministic guest time.
--debug-trace-compilation Accepted for compatibility; currently ignored.
--trace Print guest instruction traces.
-h, --help Print this help message.
<program.elf> is interpreted as a host path. Use --guest-program when the
executable must be loaded from a guest mount instead. --mount accepts
Docker-like key-value specs for bind mounts, tar archives, and tmpfs trees:
type=bind,src=./root,dst=/,readonly,
type=tar,src=ubuntu-base.tar,dst=/, or type=tmpfs,dst=/tmp. When type is
omitted, regular host files are inferred as tar mounts and other host paths are
inferred as bind mounts.
Tar mounts are lazy by default and read file payloads from the archive only when
opened. Add memory to load a tar archive into process memory; add rw as well
to allow guest writes that are discarded when the process exits. Tmpfs mounts
are empty writable in-memory trees by default. If no / mount is provided for a
host executable, the CLI mounts the executable's containing directory at /.
By default, guest /dev/tty is backed by the configured process streams; pass
--use-host-tty to try a real host controlling terminal when available. Pass
--framebuffer <width>x<height> to create a Swing-backed XRGB8888 framebuffer
that guest Linux programs can access through /dev/fb0. Use --env NAME=value
to append or override one guest environment entry; --env NAME copies the
current host value.
Guest host sockets are disabled by default. Pass --network host to map Linux
IPv4 and IPv6 TCP/UDP sockets, plus filesystem-backed Unix-domain stream
sockets, to host Java NIO sockets; NETLINK_ROUTE and network-interface ioctl
metadata remain synthetic. Swing/AWT programs running inside the guest can reach
an X11 server by bind-mounting the host socket directory, for example
--mount type=bind,src=/tmp/.X11-unix,dst=/tmp/.X11-unix,readonly, and passing
--env DISPLAY. Mount the host Xauthority file too when your X server requires
cookie authentication:
java -jar build/libs/riscv-1.0-SNAPSHOT-all.jar \
--mount src=./ubuntu-26.04-server-cloudimg-riscv64-root.tar,dst=/,memory \
--mount type=bind,src=/tmp/.X11-unix,dst=/tmp/.X11-unix,readonly \
--mount type=bind,src="${XAUTHORITY:-$HOME/.Xauthority}",dst=/root/.Xauthority,readonly \
--network host \
--root \
--env DISPLAY \
--env XAUTHORITY=/root/.Xauthority \
--guest-program /path/to/java -jar /path/to/app.jar
The loader accepts ELF64 little-endian RISC-V ET_EXEC and ET_DYN inputs with
System V, GNU/Linux, or FreeBSD OS ABI markers. It validates segment ranges,
alignment, permissions, overlap, entry-point placement, and dynamic interpreter
metadata before execution. Dynamically linked Linux programs are supported when
their interpreter and shared libraries are available through configured guest
mounts. FreeBSD ELF support currently selects the FreeBSD RISC-V syscall ABI for
static user-mode programs and a growing syscall subset.
The simulator implements enough Linux RISC-V user-mode behavior to run the
bundled static musl examples, the static Go example, CoreMark, RVV examples, and
common dynamically linked Ubuntu Base shell, coreutils, and findutils commands.
File access is sandboxed under configured --mount entries, with built-in
/proc and /dev virtual filesystems for process metadata and basic character
devices. Guest uid, gid, supplementary groups, login name, home, and shell can be
configured with CLI options. Host passthrough networking is opt-in with
--network host, which enables IPv4 and IPv6 TCP client/server sockets, UDP
datagrams, and bind-mounted Unix-domain stream sockets through the host network
stack. Local Unix-domain stream socketpair descriptors are supported even
without host networking. Synthetic netlink and interface ioctl metadata remain
available.
Each run...Example task builds the required RISC-V program before executing
it.
The decompressUbuntuBaseImage task downloads Ubuntu Base 26.04 for RISC-V and
produces a .tar root filesystem under downloads/ubuntu-base/26.04
without unpacking the tar entries.
The decompressFastfetchArchive task downloads fastfetch for Linux RISC-V from
GitHub releases and produces a .tar archive under
downloads/fastfetch. The default version is 2.62.1; override it with
-PfastfetchVersion=<version>. The runFastfetch, testFastfetchVersion, and
testFastfetch tasks also mount Ubuntu Base because the fastfetch release
binary is dynamically linked.
Run the Ubuntu Base dynamic-linking smoke tests:
./gradlew testUbuntuBaseTrue
./gradlew testUbuntuBaseBash
./gradlew testUbuntuBaseLs
./gradlew testUbuntuBaseCat
./gradlew testUbuntuBaseFind
The C and CoreMark examples use Zig CC. Gradle downloads the configured Zig
release when needed. To use an existing Zig executable command or path, set one
of ZIG_EXECUTABLE, zigExecutable, or jriscv.zigExecutable:
ZIG_EXECUTABLE=zig ./gradlew runLinuxStaticPrintfExample
ZIG_EXECUTABLE=/path/to/zig ./gradlew runLinuxStaticPrintfExample
./gradlew "-PzigExecutable=/path/to/zig" runLinuxStaticPrintfExample
The Go example uses the Go toolchain. Gradle downloads the configured Go
release when needed. To use an existing Go executable command or path, set one
of GO_EXECUTABLE, goExecutable, or jriscv.goExecutable:
GO_EXECUTABLE=go ./gradlew runGoHelloWorldExample
GO_EXECUTABLE=/path/to/go ./gradlew runGoHelloWorldExample
./gradlew "-PgoExecutable=/path/to/go" runGoHelloWorldExample
| Example | Purpose | Command |
|---|---|---|
| Freestanding Hello World | Run the smallest freestanding output example. | ./gradlew runHelloWorldExample |
| Freestanding hot loop | Run a small CPU hot-loop probe. | ./gradlew runHotLoopExample |
| Static Linux Go hello-world | Build and run a static linux/riscv64 Go program. |
./gradlew runGoHelloWorldExample |
| Static FreeBSD Go hello-world | Build and run a static freebsd/riscv64 Go program. |
./gradlew runFreeBsdGoHelloWorldExample |
| Static Go showcase | Run a larger Go standard-library workload covering JSON, sorting, compression, hashing, and goroutines. | ./gradlew runGoShowcaseExample |
| Static musl printf | Run a static musl printf hello-world program. |
./gradlew runLinuxStaticPrintfExample |
| Static musl framebuffer | Build and run a RISC-V Linux program that renders animation through /dev/fb0 in a Swing window. |
./gradlew runLinuxStaticFramebufferDemo |
| SQLite showcase | Download SQLite, build a static RISC-V file-database demo, and run transactions and queries. | ./gradlew runSQLiteShowcaseExample |
| fastfetch | Download the Linux RISC-V fastfetch release tar and run it. | ./gradlew runFastfetch |
| RVV vector add | Build and run the RVV vector-add example. | ./gradlew runRvvVectorAddExample |
| RVV matrix transpose | Build and run the RVV matrix-transpose smoke example. | ./gradlew runRvvMatrixTransposeExample |
| RVV reduction | Build and run the RVV reduction smoke example. | ./gradlew runRvvReductionExample |
| RVV softmax | Build and run the RVV softmax example. | ./gradlew runRvvSoftmaxExample |
| RVV polynomial basic | Build and run the RVV polynomial basic example. | ./gradlew runRvvPolynomialBasicExample |
| RVV micro-benchmark | Build and run the RVV micro-benchmark example. | ./gradlew runRvvUbenchExample |
| RVV CRC | Build and run the RVV CRC smoke example. | ./gradlew runRvvCrcExample |
| CoreMark | Build and run the downloaded CoreMark benchmark. | ./gradlew runCoreMarkExample |
Run the larger showcase workloads together:
./gradlew checkShowcaseExamples
Run the RV64GC baseline riscv-tests ISA acceptance tests through the
JRISC-V CLI:
./gradlew buildRiscVTests
./gradlew testRiscVTests
Run the repository-owned RVA22U64 extension acceptance tests:
./gradlew testRva22Acceptance
Run the repository-owned RVA23U64 extension acceptance tests:
./gradlew testRva23Acceptance
Use jriscv.riscvTestsFilter to run a subset by ELF filename regex, and
jriscv.riscvTestsMaxInstructions to override the per-ELF instruction
limit:
./gradlew "-Pjriscv.riscvTestsFilter=^rv64ui-p-.*[.]elf$" testRiscVTests
./gradlew "-Pjriscv.riscvTestsMaxInstructions=20000000" testRiscVTests
Generate the Linux Test Project syscall coverage guide:
./gradlew ltpSyscallCoverage
Run the LTP ABI-table driven syscall smoke test:
./gradlew testLtpSyscallSmoke
Run both checks:
./gradlew ltpSyscallCheck
The smoke test is generated from LTP's asm-generic 64-bit syscall table and
then built as a static riscv64-linux-musl ELF before running through the
JRISC-V CLI. It covers identity, process/resource, time, random, and
bind-mounted file-system syscall behavior.
Run package smoke tests that do not require Zig:
./gradlew packageSmokeTest
This generates a tiny RISC-V ELF fixture under build/fixtures/smoke, then runs
it through both the installDist launcher and the packaged Shadow JAR.
Run the local CI verification task:
./gradlew ciCheck
This compiles main and test sources, runs the unit tests, builds distribution artifacts, builds the Shadow JAR, and runs no-toolchain package smoke checks. When the managed Zig archive, extracted toolchain, or manual Zig executable configuration is already present, it also runs the static Linux C example checks and the RVA22U64/RVA23U64 acceptance suites.
Run the Zig-backed CI example checks explicitly:
./gradlew ciZigExampleCheck
Force ciCheck to download/use Zig and include those checks:
./gradlew -PjriscvCiIncludeZigExamples=true ciCheck
The build includes opt-in native-image packaging tasks for environments with a GraalVM Native Image toolchain:
./gradlew nativeCompile
./gradlew nativeImageSmokeTest
JVM mode is the primary path for current performance work.
CoreMark and the freestanding hot-loop example are useful probes for dispatch
and block-cache performance. The CLI still accepts --debug-trace-compilation
as a compatibility option for existing scripts; it is currently a no-op until
trace compiler diagnostics are added:
./gradlew runHotLoopCompilationTrace