'WizardStan' said:
The reality is that it's probably closer to 10:1 instead of 3:1. The best way to get an x86 game to run on an ARM processor is to get the source and then recompile it for the ARM.
An x86 interpreter, which is what you're describing, will not run in an average of 10:1 instructions on the very best of implementations. There's a ton more overhead than what you're describing, because you have to (for pure 386 emulation, nothing high level assumed, no MMU assistance on Linux):
- Fetch the first byte of the instruction, that's an ldr
- Increment the PC and check to see if it crossed a page boundary to see if it has to be reloaded from the page table/caused a page fault. When running on a 32bit OS it's entirely possible that the instruction stream won't be contiguous in physical memory. Of course, if you're emulating at a higher level like Windows user space then such an approach isn't necessary, but it'd be kind of strange to be using an interpreter with this level. Anyway, this is at least two instructions (inc, bcs)
- Switch to a handler for the byte, that's two instructions (ldr, bx)
- If this is a one byte instruction then you can finish what you're doing, but then you have to decrement a cycle counter and branch out if it's underflowed - you can merge the fetch of the next instruction and fall through for this, so it's one instruction (subs).
- Let's say the instruction is one byte - then you can start executing it now. You'll have to emulate flags though, which will be at least one more instruction. So for the simplest 1 byte instructions you might pull it off in 8 ARM instructions. But most instructions are not one byte or are more complicated in other ways - let's look at a still very simple instruction like add eax, ebx:
- Have to get another byte, the mod/rm byte. That's another three instructions.
- One low-instruction way to handle this is to have an 8bit jump table that handles every case for this byte. Unfortunately, this will have damaging effects on icache and wastes some dcache as well, but it might be your best bet - you might end up collapsing less common combinations to the same function and decoding those. Let's say you do have a function for register/register on eax, ebx. Then there is an ldr and a blx to get to the handler (this is assuming you actually have the base in a register, you'll be quickly running out of registers here), the function handler does two movs or two ldrs (will take same amount of cycles, starting to look like it's not worth it to try to keep any x86 registers in ARM registers), then a bx lr, so that's five instructions to load the operands.
- Now the add instruction: an adds for the add, then you have to compute flags - since x86 tends to stomp on all the flags a lot when it writes them, the best way to do this might be to use the mrc instruction on ARM to somewhere. Unfortunately, the carry flag is inverted on x86 from ARM, so you need another instruction to change it back. That's three instructions to perform the add.
So we have 17 instructions for add eax, ebx, and that's fairly best case-scenario.
The cycles ratio (depending on which x86 you're talking about) will be even worse, considering that you must take at least one indirect branch per instruction in an interpreter. An indirect branch on Cortex-A8 that isn't a function return (pulling off the stack) seems to be an automatic branch mispredict, which is 13 cycles all by itself. I would need to verify this though. The penalty is actually a lot worse than it'd be on an ARM9. Both icache and dcache misses will crop up much more than what you'd expect for an x86 with the same amount of icache/dcache .
A dynamic recompiler, on the other hand, could perform much much better, depending on what kind of code it's dealing with and how advanced it is.