With RISC-V being as slow as it is, where is the use beyond proof of concept?
Impressive. I would imagine it is very difficult to get native performance without ISA extensions like Apple had to do for TSO and other awkward x86isms.
I would guess someone will make a RISC-V extension for that stuff eventually, though I haven’t seen anyone propose one yet.
That was a fascinating read. Thank you. I suppose it is possible that there could be a RISC-V extension for this.
As the article states though, this is an ancient x86 artifact not often used in modern x86-64 software. If the code generated by GCC and Clang does not create such code, it may not exist in Linux binaries in practice. Or perhaps the decider is if such code is found inside Glibc or MUSL.
As this is a JIT and not an AOT compiler, you cannot optimize away unused flags. I expect the default behaviour is going to be not to set these flags on the assumption that modern software will not use them. If you just skip these flags, the generated RISC-V code stays fast.
You could have a command-line switch that enables this flag behaviour for software that needs it (with a big performance hit). This switch could take advantage of the RISC-V extension, if it exists, to speed things up.
Outside of this niche functionally though, it seems that the x86-64 instructions are mapping to RISC-V well. The extensions actually needed for good performance are things like the vector extension and binary bit manipulation.
Linux benefits from a different kind of integration. The article states that Apple is able to pull-off this optimization because they create both the translation software and the silicon. But the reason they need to worry about these obscure instructions is because the x86-64 software targeting macOS arrives as compiled binaries built using who knows what technology or toolchain. The application bundle model for macOS applications encourages ISVs to bundle in whatever crazy dependencies they like. This could include hand-rolled assembler you wrote decades ago. To achieve reliable results, Apple has to emulate these corner cases.
On Linux, the model is that everybody uses the same small subset of compilers to dynamically link against the same c runtimes and supporting libraries (things like OpenSSL or FreeTyoe). Even though we distribute Linux binaries, they are typically built from source that is portable across multiple architectures.
If GCC and Glibc or Clang and MUSL do not output certain instructions, a Linux x86-64 emulator can assume a happy path that does not bother emulating them either.
Ironically, a weakness in my assumptions here could be games. What happens when the x86-64 code we want to emulate is actually Windows code running on Linux. Now we are back to not knowing what crazy toolchain was used to generate the x86-64 and what instructions and behaviour it may depend on.
Another awesome stopgap measure like Proton before the Linux and RISC-V standards take over the market!
This is a Linux x86-64 to Linux RISC-V emulator. It will not execute non-Linux code or execute code outside Linux.
The Linux system call interface is the same on both sides so, when it encounters a Linux system call in the x86-64 code, it makes that call directly into the RISC-V host kernel. It is only emulating the user space. This makes it faster.
So, when I think “emulation” I usually consider it to be software emulating a hardware device (e.g. the original Gameboy, audio cards for legacy programmes that required audio cards, etc.). What they’re describing in the article is what has been described to me as being an abstraction/compatibility layer. So my questions are: 1.a. Is that really what this is or is it actually an emulator? b. If the latter, what makes it an emulator rather than a compat layer? 2. In general, how much do the two concepts interact? E.g. separate concepts entirely, ends of some continuum, etc.
The hardware being emulated here is the CPU.
To me it sounds like what Java or .NET JIT does. I doubt it falls strictly into emulation 🤷♂️
In Java or .NET, the JIT is still going from a higher level abstraction to a lower one. You JIT from CIL (common intermediate language) or Java Bytecode down to native machine code.
When you convert from a high level language to a low level language, we call it compiling.
Here, you are translating the native machine code of one architecture to the native machine code of another (x86-64 to RISC-V).
When you run code designed for one platform on another platform, we call it emulation.
JIT means Just-in-Time which just means it happens when you “execute” the code instead of Ahead-of-Time.
In .NET, you have a JIT compiler. Here, you have a JIT emulator.
A JIT is faster than an interpreter. Modern web browsers JIT JavaScript to make it faster.