RVVM – The RISC-V Virtual Machine
github.com
github.com
Of course, bot x86 and Arm started like that as well - but after 20+ years of evolution, they have to drag along a lot of history. (and one never really takes things away from an ISA, you only add new features, and at best deprecate old ones).
That said, with the extensibility of the ISA in mind as a design principle, I do have good hopes, RISC-V will stand the test of time reasonable well in that sense...
Patterson and Henessy comment as to how different (better) ARM64 is when compared to previous versions!
On the gripping hand, there are a number of cores that implement ONLY the 64 bit ISA, starting from ThunderX, to Apple's M1/M2, to the latest ARM cores found in for example the Snapdragon 8 Gen 1/2 phone SoCs -- from memory only one of the three core types in those SoCs can run 32 bit code. ARM has said all their future ARMv9 cores will not have 32 bit compatibility.
For MIT's 6.004 "Beta" processor loosely based on the DEC Alpha AXP, our test cases ran with a minimal kernel that would trap and emulate multiply, divide, etc. instructions using shifts and adds/subtracts, so we could implement more simple ALUs and still test the full instruction set.
In any case, particularly in the world of hypervisors, it doesn't seem too hard to deprecate an instruction and stop implementing it in hardware, and push that complexity into firmware. As long as the CPU covers the Popek and Goldberg virtualization requirements, hypervisors could be nested, and the firmware could implement a lowest level hypervisor that handles unimplemented instructions.
More generally, I wish ARM64, RISC-V, and other modern ISAs had taken DEC Alpha AXP's idea of restricting all of the privileged instructions to the firmware (PALCode in the Alpha's case) and basically implementing a single-tenant hypervisor in the firmware. The OS kernel always used an upcall instruction to the hypervisor/firmware to perform privileged operations. In other words, the OS kernel for Alpha was always paravirtualized. (UNSW's L4/Alpha microkernel was actually implemented in PALCode, so in that case, the L4 microkernel was the firmware-implemented hypervisor and the L4 syscalls were upcalls to the firmware.) As it stands, hypervisors need to both implement upcalls for efficiency and also implement trap-and-emulate functionality for OS kernels that aren't hypervisor-aware. The trap-and-emulate portions of the code are both lower performance and more complicated than the upcall handlers. Both hypervisors and OS kernels would be simpler if the platform guaranteed a hypervisor is always present.
Always having a firmware hypervisor also allows pushing even more complexity out of hardware into the firmware. The Alpha had a single privilege bit indicating if it was currently running in firmware/hypervisor (PALCode) mode, and the firmware could emulate an arbitrary number of privilege levels/rings. The Ultrix/Tru64 Unix/Linux firmware just emulated kernel and user modes, but the OpenVMS firmware emulated more levels/rings. x86's 5 rings (including "ring -1/hypervisor) could be efficiently emulated by hardware that only implements ring -1 (hypervisor) and ring 3 (user mode).
Edit: Taken to an extreme, you get something like Transmeta's Crusoe that pushed instruction decoding and scheduling into a firmware hypervisor JIT that works on the processor's microcode level. In retrospect, it seems that Crusoe went too far, at least as far as early 2000's technology could go. However, there's still plenty of optimization space in between the latest Intel processors on the extreme hardware complexity side and Transmeta's Crusoe on the extreme firmware complexity side.
Edit 2: In-order processors like (at least early) Intel Atom, P.A. Semi's PWRficient, and Transmeta's Crusoe tend to be more power-efficient. If the architecture designed for it, I could see a case for limited out-of-order hardware capability with hardware tracing and performance counters/reservoir sampling of instructions that caused pipeline stalls. The firmware could then use run-time information to JIT re-order the instruction streams in hotspots that weren't well-served by the hardware's limited out-of-order execution capacity. This might be a viable alternative to ARM's big.LITTLE, where the firmware (or kernel) kicks in to provide a performance boost to hotspots when plugged in, and executes as a simple in-order processor when lower power consumption is desired, without the extra complexity of separate pairs of cores for performance and efficiency. Hardware sampling of which speculations work out and which are wasted would presumably guide the firmware's attempts to efficiently re-optimize the hot spots.
This would make an excellent RISC-V proposal!
The nice thing is that RISC-V already has the concept of the HART, you could have a supervisor hart that manages many virtual harts.
I understand opensbi runs in M mode, taking on that role among others.
This is already possible on RISC-V to some extend, by trapping privileged instructions into upper privileged modes. Everything in the ISA is made so it may be achieved cleanly. It also does not allow to detect current privileged mode, so the kernel running in U-mode and trapped on each privileged instruction would never know it's actually not in S-mode.
There is even a software-based hypervisor extension emulator based on that, that brings KVM to non-hypervisor-capable HW: https://github.com/dramforever/opensbi-h
As it stands, the hypervisor needs to implement both trap-and-emulate and upcall handlers, and OSes need to implement both running on bare metal and (if they want to perform well on hypervisors) hypervisor upcalls.
If you want your hypervisor to support nested hypervisors, then I guess you'd still need to implement trap-and-emulate in the hypervisor to allow running a hypervisor on top. However, you at least remove the dual paths in the OS kernel if you just disallow the bare-metal case. This also allows a bit more flexibility in hardware implementation as you can change the hardware implementation and the instruction sequence in the hypervisor without needing to modify any legacy OS kernels.
Feature-wise, RISC-V is already about on par and has managed not to become a mess.
Furthermore, unlike x86, ARM has broken binary compatibility several times in the past, and yet failed to use the chance to do anything else than minor adjustments.
RISC-V is in a really good position.
The first was probably when they took the condition codes out of the hi bits of the PC into their own register, to allow 32 bit addressing instead of 26 bit addressing, thus nuking all the software that thought (as it was encouraged to!) that saving and restoring the PC also saved and restored condition codes.
After Thumb mode was introduced they've waffled over whether you must use only BX to change modes, or whether any instruction that writes to PC (mov, add, pop ...) is ok.
Thumb only CPUs such as Cortex M3/M4/M7 can't run code from e.g. ARM7TDMI. The Thumb mode stuff will work, but it's not a complete ISA. On ARM7TDMI you have to switch to ARM mode for many things, while on ARMv7 you need additional instructions that don't exist on ARM7TDMI. CM0 adds only the essential operations to Thumb1, while M3/M4/M7 add basically a complete re-encoding of ARM mode -- minus conditional execution on every instruction.
In Thumb mode on ARM7DTMI (and successors) a 4 byte `BL` instruction is actually two 2-byte instructions run one after the other. You can separate them with arbitrary other instructions between them, as long as LR is not touched. In ARMv7 `BL` looks the same, but you can no longer split the two 2-byte halves -- it is now actually a 4-byte instruction, and other 4-byte instructions exist using the same initial 2 bytes.
And of course Aarch64 is utterly incompatible with any of the 32 bit ISAs. Early 64 bit cores from ARM also ran 32 bit code. At first they could boot 32 bit OSes, then only 64 bit OSes but they could run 32 bit user code. The latest ARM cores run only 64 bit code, and ARM has said no future 64 bit cores will run 32 bit code at all. Other 64 bit only cores also exist, including the ThunderX (9 years ago!) and Apple's M1 and M2 as well as all iPhones starting from the iPhone 8.
https://github.com/cnlohr/mini-rv32ima/blob/master/mini-rv32...
A RISC-V emulator in one include file.
This is not commercial and the terms for the logo is: “(..) The RISC-V trade mark or the RISC-V logo may be freely used for non-commercial research and academic projects as long as the guidelines below are followed. Individual members may not use the RISC-V trademark for commercial purposes (..)”
So they will probably not remove it.
I think it's also easier to build as WASM and run in a browser.
I don't think 100% feature parity with QEMU is a goal.
Performance-wise:
- Instead of a static translate-and-run flow like in QEMU, RVVM has an interpret-trace-run execution loop which is remotely similar to JVM, and allows to collect some data like branch probabilities and hot loops, and optimize better
- Using a hardware host FPU instead of softfp emulation. This is like, 10x faster with some synthetic FPU benchmarks
- Conscious decisions for beneficial trade-offs, like fast-path JIT trace cache, JIT IR is more streamlined to "Big ISA Triad" (RISC-V, ARM64, x86-64), etc
Infrastructure-wise:
- A public library API for a lot of things: Machine management (Construct and run 'em in any program), device integration, registering new CPU instructions, userspace emulation
- Subjectively, a more lean and clean codebase, in places where it wasn't harmed by either 1) performance decisions to copy-paste or restructure things 2) complexity of related things like JIT backend arches
- Portability. RVVM officially runs in WASM, runs on Haiku, SerenityOS, KolibriOS, even DOS!
So yeah, while I can't say for sure it's the fastest RISC-V VM, but if we had a bunch more contributors and a bit more popularity it could definitely take a part of the QEMU crown (For things like distro building and god knows what else).
There are a few other differences here & there, but they aren't as much worth mentioning. Do you believe I should make some kind of QEMU/RVVM comparison anywhere in the repo? It seems kind of selfish to present it like that...