Ask HN: How does Rosetta 2 actually work?
Apple's docs say that Rosetta 2 works by translating x86 code into ARM64 code when an application starts up and then running the resulting ARM code natively. But that can't actually be true because if I use dlopen to load a dynamic library at runtime, the architecture of the library has to match the architecture of the executable that called dlopen or it results in an error (Error opening shared library foo.dylib : dlopen(foo.dylib, 0x000A): tried: 'foo.dylib' (mach-o file, but is an incompatible architecture (have 'arm64', need 'x86_64')
So at a minimum, dlopen can invoke Rosetta again in this case. But why thrown an error? Why not allow an x86 application to open an ARM library if it is actually ARM code at run-time?
The situation is actually even weirder than that because there is an application (Clozure Common Lisp) that produces x86 code at run time but does not go through dlopen, it just writes x86 opcodes directly into memory (AFAIK). And yet, it works when run under Rosetta. How is that possible?
All of the evidence indicates to me that Rosetta is actually an emulator and not a translator. But why would Apple misrepresent this? It makes no sense.
18 comments
[ 2.5 ms ] story [ 49.2 ms ] threadAnd why won't it let me load a dylib compiled for ARM into an x86 process?
Well, it would have to keep track of which regions of memory are code it has translated and which are data that was written out by the program it's executing.
It can check if a branch or jump instruction goes to data, and then jump to its emulator instead.
Because the Application Binary Interface (ABI) of ARM and x86-64 is different. Function calling conventions don't map 1:1, structure layouts may differ, exceptions can't be propagated properly, etc. etc. etc..
That's why Rosetta only allows you to mix code of a single architecture in a given address space, so that it can be emulated as a whole. That way all code that's being executed has the same ABI.
https://twitter.com/ErrataRob/status/1331735383193903104
If you mix arm and x86 code inside a process, you can't tell which ordering to use.
How does Rosettafied x86 code call system code then?
Even if translation is non-trivial, there are only a finite number of system calls (hundreds). It's not hard to provide bindings for those.
OK, that explains why I can't dlopen an ARM dylib. But then how does Rosettafied code call system library routines?
(I have no specific knowledge about how Rosetta works)
x86 code (translated or not) needs to call into system libraries with the x86 calling convention. The easiest way would be to have the x86 system libraries, and translate those too, although someone mentioned they're included as pre-translated cache files.
System calls (typically made from libc) in x86 will trap into the kernel, but I'd assume the CPU will run the kernel handler in ARM mode... probably? a different handler address for ARM and x86, but either way, some method to determine which type of cpu and use the appropriate syscall numbers and structures depending on the type. It's possible, but I'd think unlikely, that Apple made all the syscalls the same across both architectures, down to the width and alignment of all of the structs.
Lots of useful information in this reverse-engineering article: https://ffri.github.io/ProjectChampollion/part1/