少し間が空いてしまったが、決してサボっていたわけではない。「Apple M1」搭載のMacBook Airのレビュー記事を3回にわたり書いているし、これこのとおり、OS Xハッキング!に新ネタを...というわけで、今回は「Rosetta 2」。MacBook Airのレビューでサラリと触れたが(リンク)、より深く掘り下げてみたい。

Rosetta 2の基本

Finderを使いアプリの情報ウインドウを眺めてみると、Intel/Catalina環境では見かけなかった「Universal」や「Intel」といった文字列を確認できる。前者はUniversalアプリでx86_64バイナリとARM64バイナリの両方を、後者はx86_64バイナリのみを含むアプリという意味だ。もうひとつ「Appleシリコン」と表示されるアプリもあるが、こちらはARM64バイナリのみということになる。

Universalアプリの情報ウインドウには「Rosettaを使用して開く」というチェックボックスがあり、ここにチェックを入れてから(もちろんM1 Macで)起動すると、ARM64バイナリではなくx86_64バイナリが使用される。M1 Macでネイティブ動作するバイナリが含まれているのになぜ、と疑問に思うかもしれないが、おもに互換性確保を目的として用意されているものだ。

なお、以前のプラットフォーム移行期に使用されたUniversalバイナリ -- おもに「PowerPC+x86」と「x86+x86_64」の2種類 -- についても、M1 Macでも同様にUniversalバイナリと認識される。ただし、Rosetta 2にはx86_64からのトランスレート機能しか含まれていないため、「PowerPC+x86」なUniversalバイナリはM1 Macでは動作しない。

  • Universalバイナリには「Rosettaを使用して開く」というチェックボックスが表示される

  • UniversalバイナリであってもPowerPCの存在は無視される(SD Card FormatterはPowerPCバイナリも収録している)

Rosetta 2のありか

第267回に書いたとおり、初代Rosettaのトランスレータは「/usr/libexec/oah」ディレクトリ以下に置かれていた。カーネルが要変換と判断すると自動的に呼び出されるしくみのため、ユーザがコマンドラインで実行することはなく、その必要もなかったのだ。

Rosetta 2でも基本仕様に変わりはないはず、とTerminalで/usr/libexec/oahディレクトリを探してみるが見つからない。そこでx86_64バイナリのみ収録するEmacs -- Big Surから標準装備されなくなったこともありコンパイル済のものをインストールした -- を起動し、アクティビティモニタの情報ウインドウにある「開いているファイルとポート」を確認してみると、「/Library/Apple/usr/libexec/oah/runtime」というプロセスを発見。どうやら、Rosetta 2では/usrから/Library/Appleに引っ越ししたようだ。

その/Library/Apple/usr/libexec/oahディレクトリを表示してみると、ビンゴ。「translate_tool」などのRosetta 2関連と思しきコマンドをいくつか確認できた。

それらのコマンドを片っ端から「--help」や「--version」といった引数を与え実行しても何も起きないため、「runtime」コマンドをバイナリエディタで覗いてみると、「rosetta error:」などのキャラクタベースのリソースを確認できた。CLIで機能を呼び出す方法は不明だが、とりあえずRosetta 2に深く関わりのあるコマンドであることは間違いない。

次に気になるのは、初回実行時に作成された(ARM64アーキテクチャ用に変換されたバイナリ)の保存場所だが、これもアクティビティモニタの情報ウインドウを読み解くことで概要が判明した。「/var/db/oah/」ディレクトリ以下にキャッシュを作成し、それを「runtime」コマンド経由で呼び出しているようなのだ。

  • Rosetta 2のキャッシュは...

キャッシュサイズを調べると驚きの結果が

しかし、/var/db/oahディレクトリはSIP(System Integrity Protection)のために閲覧が許されないため、電源オフの後リカバリーモード -- M1 Macでは電源ボタン長押しでブートオプションを表示 -- に入り、SIPを無効化してから/var/db/oah以下を覗いてみたところ、おそらくランダムに生成されたサブディレクトリにバイナリが1つずつ格納されていた。

オリジナルのファイル名は暗号化されておらず、「Geekbench 5.aot」や「GoogleJapaneseInputRenderer.aot」などとオリジナルのファイル名の末尾に「.aot」をくわえたもの。実行形式のファイルだけでなくライブラリ(*.dylibなど)も見かけるほか、再起動してもキャッシュクリアされないようで、MacBook Airを使い始めてから起動したアプリのものがひと通り残っていた。

試しにCatalinaからコピーしてきたsleepコマンド(x86_64バイナリ/非ユニバーサル)を実行し、オリジナルと変換後のファイルサイズを比較してみたところ、面白い結果が。オリジナルは31328byteだが、変換後のキャッシュファイル(sleep.aot)は8979byteと、大幅に小さくなっていたのだ。他のアプリ/バイナリも傾向は同じで、かなりコンパクトになっている。

DTraceをかけても「DTrace cannot instrument translated processes」と表示されるばかりで、具体的な処理内容を追うことはできなかったが、実行速度と変換後のキャッシュサイズからすると、システムコールの置き換えという単純なレベルではなさそう。最適化を含め、かなり独特な処理を行っていることは間違いないだろう。

  • x86_64バイナリのsleepコマンドは、変換後3分の1ほどのキャッシュサイズに