로제타 2는 기본적으로 x86 -> ARM 번역기입니다. 네... 물론 JIT도 있지만요. 그것보다 생각보다 왜 빠른가? 그리고 깊숙한 설명이 좀 많이 늦네요... 나온지 수년은 되었는데...
여튼 사람들은 로제타 2가 어케 작동하는지, 왜케 빨리 작동하는지(우린 다른 놈을 봐왔죠... 저 MS의 ARM 윈도라던가...)에 대해서 궁금해왔습니다.
그래서 IDA 같은걸로 마구 뜯어보고 아... 그러고 있죠. 그림은 IDA에서 본 코드입니다. 왼쪽은 로제타 2를 거친 코드, 오른쪽은 일반 맥의 X86 코드입니다. 보면, 로제타에서 parity64란 걸 호출합니다. 이건, x86의 8비트 패리티 플래그를 바로 ARM의 8비트 패리티에 박는 겁니다.
네 위에서 보다시피, 로제타 2는 바로 번역해버립니다. JIT 변환이 추가되어 있지만 잘 사용하진 않습니다. 먼저 다 번역해두고 돌리는거죠. 그러니까 각 x86 명령어는 저 AOT 바이너리내에서 하나 아님 그 이상의 ARM 명령어로 변환됩니다. NOP는 제외하고... 그리고 정확한 예외 처리, 디버거 연결 등을 하기 위해 맵핑된 주소를 모두 유지합니다.
이런 각각의 명령어를 다 일치시키면 에뮬레이트된 모든 레지스터 값을 호스트 레지스터에 보관하거나 특정 레지스터가 사용될 때마다 로드 또는 저장 명령이 필요할수 있습니다. ppc -> intel -> arm 처럼 가면 말이죠. 또한 명령의 최적화가 없습니다. 당연하게... 거진 대부분이 컴파일러가 최적화해주기 때문이죠. 그러나, 한번 번역하면 또 되풀이해서 써먹을수 있고, 정확한 예외를 보증하며, 디버거에 바로 연결과 더불어 최적화가 적어 번역속도가 빨라지게 됩니다. 그 밖에도 애플의 비밀 확장과 엄청나게 빠른 M 시리즈가 있지만요... 뭐 로제타(PPC -> Intel) 개발하면서 들어먹었던 쌍욕이 있기에(드럽게 느려먹은... 호환도 안되는...), 되면 또 이렇게 바꿀수 있겠죠.
대충 런치 서비스, 그리고 macOS 서브 시스템이 앱 구동을 할때는 먼저 Aot 바이너리를 요구합니다. 그리고 없다면 이걸 번역하고, oahd(로제타 2 데몬)이 찾았다! 하면 캐시된걸 쓰는거죠. 번역은 100kb가 넘는 바이너리의 경우, 0.0125초 정도입니다. 최소 242kb의 유니버설 바이너리에서 말이죠. 그러니까 뭐 금방이네요...
인텔 코드가 실행되면, 바이너리를 모두 램에 올리고 oahd가 열심히 번역해서 CPU에 박아넣어주는거죠... 목표는 사용자가 필요로 하기전에 작업을 끝내는 것입니다.
여튼 로제타를 사용하여 앱을 강제로 열수 있습니다 네... 당연히 아무리 잘나도 M은 인텔과 ARM 코드를 모두 실행하지 못합니다. 그렇기에 강제로 로제타로 번역시켜서 ARM 코드로 번역시키는 겁니다. 이건 뭐... PPC -> 인텔 때인 로제타도 그랬으니 PASS 하죠... PPC 코드와 인텔 코드가 인텔 CPU에서 짝짜꿍하고 돌아갈 가능성이 몇% 일까나요...
그리고 물론, 로제타 2를 사용하는 경우 더 개이익인 경우도 있습니다. macOS 커널인 Mach Absolute Time을 사용하게 되면, 로제타의 경우 1 나노초마다 틱을 받습니다만, 이상하게 M의 경우 41.67 나노초마다 틱이 증가합니다.
물론 로제타에서 번역못하는 것도 있습니다. 낡아 빠진 커널 확장이라던가, 가상화 환경, 그리고 인텔 벡터등 프로세서에 기반한 명령이 매우 필요한 코드등이죠... 그리고 로제타는 인텔 64비트만 먹습니다. 네 모하비등에서 돌아가는 32비트 앱은 안녕이에요
가상화 이야기를 했는데, 이상하게 로제타 2는 가상화 시스템에서 호스트 macOS 내에서와 정확히 동일하게 설치되고 사용됩니다. 리눅스에서 로제타 2를 사용할 수 있으려면 가상 장치에서 특수 공유 폴더를 만든 다음 리눅스 게스트에 마운트하면 됩니다. 참 쉽죠?
https://eclecticlight.co/2022/12/10/explainer-rosetta-2/
https://dougallj.wordpress.com/2022/11/09/why-is-rosetta-2-fast/
32비트 미지원~~ 하긴 이제 64 비트의 시대이기는 하죠.