[Disclaimer: I wrote Rosetta 2, so everything I say is biased by that.]
Contemporary out-of-order CPUs are incredibly over-provisioned; microarchitects will justify a new feature by another 0.1% gain on some benchmark. The end result is a CPU that's pretty decent at handling the sort of code bloat that comes from a binary translator.
There's also a big tradeoff in adding more optimizations to a binary translator. You would like to be able to precisely handle exceptions (especially ones caused by invalid memory accesses) while presenting a userspace exception handler with an architecturally valid state for the source program. There are some optimizations that would be easy to do in principle but are painful for maintaining this mapping between source program states and translated program states. The complexity burden combined with the difficulty of debugging that added complexity (or exhaustively verifying it up-front) shapes many of your decisions when writing a production binary translator. You should always have more Cool (tm) ideas than you actually use in practice.
Would Rosetta 2 have worked as well for x86 APX i.e. 32 GPRs instead of 16?
(16x x86 regs fit in 32x Arm regs, whereas 32x in 32x is... much less of a fit)
Yeah, modern CPUs are great at executing garbage code reasonably fast. Getting binary-translated code to come close to native performance is still difficult.
Apple obviously had an advantage as they also control the hardware (and Arm helped by adding some extensions to simplify translation); significantly easing two difficult parts of translating from x86: TSO and status flags. AVX is somewhat annoying (256-bit regs -> 128-bit regs, frequent merging of scalar values), but manageable.
> You would like to be able to precisely handle exceptions (especially ones caused by invalid memory accesses) while presenting a userspace exception handler with an architecturally valid state for the source program.
This is absolutely annoying and makes many optimizations much more difficult as a lot of additional state needs to be kept around, either for real or in metadata for reconstruction (including weird status flags, fun with partially written flags (inc/dec), maybe-written flags (shift/rotate), etc.). Does Rosetta 2 always have precise status flags (including PF/AF) at every possibly-faulting memory access? (This should be rarely needed in practice, so I've never implemented flag recovery in my own binary translators (primarily for research, Instrew but also non-public).)