And I appreciate you setting me straight about the extent of wasm's instruction proliferation.
But I find much to disagree with.
— ⁂ —
> There at least three small Wasm interpreters in Rust
None of those are small; the smallest one you found is 500 lines of code, and it's very incomplete, implementing only 11 of the 436 or however many wasm instructions there are, and even those it only implements partially. (Its only arithmetic is addition and subtraction, for example.) Even the 3kloc one says it doesn't pass the wasm testsuite; its pub enum Instruction has 174 items and most of those are implemented as follows:
Instruction::F64Store(_, _) => todo!(),
Instruction::I32Store8(_, _) => todo!(),
Instruction::I32Store16(_, _) => todo!(),
Instruction::I64Store8(_, _) => todo!(),
The 4kloc one (which I think is actually closer to 2.8kloc) doesn't include any of the SIMD ops, but it claims to implement the whole wasm spec as of 02019, and it's plausible that it actually does. A casual glance at the code doesn't reveal anything that contradicts that claim; it implements about 200 instructions.None of them include the peripherals, which are by definition excluded from wasm. But peripherals are usually the part of an emulator that requires the most effort, and they're usually a much bigger compatibility bitrot shitshow than your CPU is.
By contrast, the UM interpreter in the Cult of the Bound Variable paper was I think 55 lines of C (also, not including peripherals). My dumb Chifir interpreter was 75 lines of code; adding Yeso graphical output was another 30 lines https://gitlab.com/kragen/bubbleos/blob/master/yeso/chifir-y....
Uxn, despite its inefficiency, runs useful applications on the Nintendo DS today (in 5200 lines of pretty repetitive C, including the peripherals), and wasm doesn't and probably never will. And 365 teams in the ICFP programming contest independently implemented the UM successfully enough to run at least some existing applications; there will probably never be 300+ independent reimplementations of wasm. So in important ways they're already closer to the goal of eliminating bitrot than wasm ever will be. This is probably because eliminating bitrot isn't part of wasm's goals.
(I realize I forgot to mention the Infocom Z-machine, as well.)
Wasm has another deficiency other than complexity: as far as I know there's no standard way for a wasm program to generate some wasm and start running it, although of course the browser platform does provide that ability. This is essential if the thing you want to run under it is a virtual machine or other sort of emulator, because the only way to do an efficient emulator is to compile the code you want to interpret into the instruction set the emulator is running on. Something wasm-like can dramatically simplify this; if you're compiling to wasm, for example, you don't have to do instruction scheduling or register allocation, and you might not even have to do constant folding and function inlining.
— ⁂ —
One of the interesting ideas in the RISC-V ecosystem is that a Cray-style vector instruction set (RV64V) can give you SIMD-instruction-like performance without SIMD-instruction-like instruction set inflation. And, as the APL family shows, such vector instructions can include scalar math as a special case. I haven't been able to come up with a way to define such a vector instruction set that wouldn't be unacceptably bug-prone, though; https://dercuano.github.io/notes/vector-vm.html describes some of the things I tried that didn't work.
— ⁂ —
Why am I being so unreasonable about the amount of code? After all, a few hundred lines of C is something that you can write in an afternoon, right, so what's the big deal about 500 or 3000 lines of code for something you'll use for decades? And nobody has ever written an NES emulator in 500 or 3000 lines of code.
The problem is that, to parody Perlis's epigram, if your virtual machine definition has 500 lines of code, you probably forgot some. If a platform includes that much functionality, you have designed it so that that functionality has to live in the base platform rather than being implemented in programs that run on the platform. And that means that you will be strongly tempted to add stuff to the base platform, which is how you break programs that used to work.
In the case of MS-DOS or NES emulation this is limited by the fact that Nintendo couldn't go out and patch all the Famicoms and NESes in people's houses, so if they wanted to change things, well, too bad. NES emulator authors have very little incentive to add new functionality because the existing ROMs won't use it, and that's what they want to run.