> Everything runs on what is essentially a virtualization layer... the AS/400 instruction set (MI, or "machine interface") doesn't exist in any hardware, it's just run on top of a translation layer on top of (these days) POWER
To nitpick your nitpick – MI isn't interpreted, it is compiled to machine code. Not JIT, AOT: when you compile a program, it actually goes through two steps. First it compiles the source code to MI, and saves that in the executable (program object). Second, it compiles the MI to machine code, and saves that in the executable too, in a different section of it. At runtime, the MI is ignored and the machine code is run. There is even a command to delete the MI part of the executable (called deleting the "observability"), and your program will still work after the MI is deleting because it isn't actually used at runtime.
When they transitioned from custom CISC [0] to POWER, if you still had the MI in your executable, the system would automatically recompile it to POWER machine code. If you had deleted the observability, it wouldn't run, and you'd have to recompile it from source. Theoretically, the same would apply if IBM ever decided to move IBM i to some other platform than POWER (such as ARM or x86), although I doubt that is ever going to happen.
Also, there are actually two versions of MI, old MI and new MI. Old MI (OMI) was used with OPM (Original Program Model) executables. OMI was the original AS/400 machine interface, and is in fact descended from the System/38 machine interface. Around the same time they moved to RISC, they introduced a new program model (ILE, Integrated Language Environment) and new MI (NMI) to go with it. OPM programs still run, but now the system translates OMI to NMI, and then NMI to machine code. Now, the interesting thing about NMI – OMI was indigenous to the 38/400, whereas NMI was actually just the internal intermediate language used by IBM's compilers on other platforms (such as AIX and OS/2), known as "W-code", with some 400-specific extensions. So, in the contemporary ILE world, MI is really just a compiler intermediate language, like LLVM IR is, and so isn't really anything that different from what happens on AIX or Linux or Windows or z/OS – the history and the marketing make it sound a lot different.
Now, I agree that IBM i isn't necessarily that fast. It has a very complex software stack, and while I find that complexity fascinating, no doubt it imposes performance costs. But the "virtualisation" aspect in itself doesn't cost much at all. I think the widespread use of 128-bit pointers is likely a bigger issue – although, the 128-bit pointers don't get used directly, they get converted to 64-bit pointers before actually being used – still that has to have some performance cost which other systems don't have to deal with.
But you can opt out of that by running your code in a "teraspace", in which case you just use 64-bit pointers and the 128-bit pointers are not used. If you are running Java code or PASE code (AIX compatibility subsystem), both use teraspaces, and if you avoid invoking OS services (as in a compute-heavy workload), your performance should be close to that of AIX on the same hardware.
[0] the CISC instruction set, IMPI, was inspired by the 370 instruction set but not compatible with it. For AS/400, I don't believe it has ever been publicly documented, but I'm led to believe the 400 CISC instruction set was just an evolution of the 38 CISC instruction set, which was publicly documented in this manual: http://www.bitsavers.org/pdf/ibm/system38/SC21-9037-3_IBM_Sy...