IL2CPP内部の紹介

もう1年近く前になりますが、私たちはUnityにおけるスクリプティングの将来について話し始めました。新しいスクリプトバックエンド IL2CPP は、Unity に高性能で移植性の高いバーチャルマシンを確実に提供してくれます。1月には、IL2CPPを使用した最初のプラットフォーム、iOS 64ビットを出荷した。Unity 5のリリースは、WebGLという別のプラットフォームをもたらした。IL2CPPは、多くのユーザー・コミュニティからのフィードバックにより、多くのパッチ・リリース・アップデートを出荷し、コンパイラとランタイムを着実に改善してきました。IL2CPPの改良を止めるつもりはないが、一歩引いて、IL2CPPが内部からどのように機能するかについて少しお話しするのは良い考えだと思った。今後数ヶ月間、このIL2CPP Internalsシリーズで以下のトピック(そして多分他のトピックも)について書く予定です:
1.基本 - ツールチェインとコマンドライン引数(この記事です)
4.メソッド呼び出し(通常のメソッド、仮想メソッドなど)
8.テストフレームワークと使用法
この連載を可能にするために、将来必ず変わるであろうIL2CPPの実装についての詳細を説明する。まだ有益で興味深い情報を提供できることを願っている。
私たちがIL2CPPと呼ぶ技術には、2つの異なる部分がある。
- AOTコンパイラ
- 仮想マシンをサポートするランタイム・ライブラリ
AOTコンパイラーは、.NETコンパイラーの低レベル出力である中間言語(IL)をC++ソースコードに変換する。ランタイム・ライブラリは、ガベージ・コレクタ、スレッドやファイルへのプラットフォームに依存しないアクセス、内部呼び出し(マネージド・データ構造を直接変更するネイティブ・コード)の実装などのサービスや抽象化を提供する。
IL2CPP AOTコンパイラはil2cpp.exeという名前です。Windowsでは、EditorDatail2cppディレクトリにある。OSXでは、Unityインストール内のContents/Frameworks/il2cpp/buildディレクトリにあります。
il2cpp.exeユーティリティは、すべてC#で書かれたマネージド実行ファイルです。IL2CPPの開発では、.NETとMonoの両方のコンパイラでコンパイルしている。il2cpp.exeユーティリティは、Unityに同梱されているMonoコンパイラでコンパイルされたマネージド・アセンブリを受け取り、プラットフォーム固有のC++コンパイラに渡すC++コードを生成します。
IL2CPPツールチェーンについて、次のように考えることができる:

IL2CPP技術のもう1つの部分は、仮想マシンをサポートするランタイム・ライブラリである。このライブラリは、ほとんどC++のコードだけで実装されている(プラットフォーム固有のアセンブリ・コードも少しあるが、それはここだけの話にしておこう)。ランタイム・ライブラリをlibil2cppと呼び、プレーヤーの実行ファイルにリンクされたスタティック・ライブラリとして出荷される。IL2CPP技術の主な利点のひとつは、このシンプルでポータブルなランタイム・ライブラリである。
Unity に同梱されている libil2cpp のヘッダファイルを見れば、libil2cpp のコードがどのように構成されているか、いくつかのヒントを見つけることができます(Windows では EditorDataPlaybackEngineswebglsupportBuildToolsLibraries ディレクトリに、OSX では Contents/Frameworks/il2cpp/libil2cpp ディレクトリにあります)。例えば、il2cpp.exeによって生成されたC++コードとlibil2cppランタイムの間のインターフェイスは、codegen/il2cpp-codegen.hヘッダーファイルにあります。
ランタイムの重要な部分のひとつがガベージ・コレクターだ。私たちはUnity 5にBoehm-Demers-Weiserガベージコレクタであるlibgcを搭載して出荷しています。しかし、libil2cppは他のガベージコレクタを使えるように設計されている。例えば、CoreCLRの一部としてオープンソース化されたマイクロソフトGCの統合を研究している。これについては、この連載の後半にあるガベージ・コレクタの統合についての記事で詳しく説明する。
例を見てみよう。WindowsのUnity 5.0.1を使い、新しい空のプロジェクトから始めます。少なくとも1つのユーザースクリプトを変換できるように、このシンプルなMonoBehaviourコンポーネントをMain Cameraゲームオブジェクトに追加します:
serializers.types`プロップでシリアライザーを指定してください。
WebGLプラットフォーム用にビルドすると、プロセス・エクスプローラーを使って、Unityがil2cpp.exeを実行するのに使ったコマンドラインを見ることができる:
serializers.types`プロップでシリアライザーを指定してください。
このコマンドラインはかなり長くて恐ろしいので、紐解いてみよう。まず、Unityはこの実行ファイルを実行している:
serializers.types`プロップでシリアライザーを指定してください。
コマンドラインの次の引数は、il2cpp.exeユーティリティそのものである。
serializers.types`プロップでシリアライザーを指定してください。
残りのコマンドライン引数は、mono.exeではなく、il2cpp.exeに渡される。見てみよう。まず、Unityはil2cpp.exeに5つのフラグを渡す:
- --copy-level=None
- il2cpp.exeが、生成されたC++コードの特別なファイルコピーを行わないように指定する。
- --enable-generic-sharing
- これはコードとバイナリのサイズを小さくする機能である。IL2CPPは、できる限りジェネリックメソッドの実装を共有する。
- --enable-unity-event-support
- リフレクションによってアクセスされるUnityイベント用のコードが正しく生成されるようにするための特別なサポート。
- --output-format=Compact
- 型名やメソッド名に必要な文字数が少ないフォーマットでC++コードを生成する。このコードは、ILコード中の名前が保存されないため、デバッグが難しいが、C++コンパイラーが解析するコードが少ないため、コンパイルが速くなることが多い。
- --extra-types.file="C:\Program Files\Unity\Editor\Data\il2cpp\il2cpp_default_extra_types.txt"
- デフォルトの(そして空の)追加タイプファイルを使用する。このファイルをUnityプロジェクトに追加すると、il2cpp.exeに、実行時に作成されるがILコードには存在しないジェネリック型や配列型を知らせることができます。
これらのコマンドライン引数は、後のリリースで変更される可能性があることに注意することが重要である。まだ、il2cpp.exeのコマンドライン引数が安定してサポートされている段階ではない。最後に、コマンドラインに2つのファイルと1つのディレクトリのリストができた:
- "C:㊟UsersJosh Peterson㊟Documents㊟IL2CPP Blog ExampleTemp㊟StagingArea㊟Data㊟Managed㊟Assembly-CSharp.dll"
- "C:¥Users¥Josh Peterson¥Documents¥IL2CPP Blog Example¥Temp¥StagingArea¥Data¥Managed¥UnityEngine.UI.dll"
- "C:\Users\Josh Peterson\Documents\IL2CPP Blog Example\Temp\StagingArea\Data\il2cppOutput"
il2cpp.exeユーティリティは、変換するすべてのILアセンブリのリストを受け付けます。この場合、単純なMonoBehaviourを含むアセンブリであるAssembly-CSharp.dllと、GUIアセンブリであるUnityEngine.UI.dllです。なお、ここではいくつかのアセンブルの欠落が目立つ。明らかに、私のスクリプトはUnityEngine.dllを参照しており、それは少なくともmscorlib.dll、そしておそらく他のアセンブリを参照しています。彼らはどこにいるのか?実際には、il2cpp.exeは内部的にこれらのアセンブリを解決する。コマンドラインに記載することもできるが、必要ではない。Unityは、ルートアセンブリ(他のアセンブリから参照されないアセンブリ)のみを明示的に言及する必要があります。
il2cpp.exeコマンドラインの最後の引数は、出力C++ファイルを作成するディレクトリです。もし興味があれば、そのディレクトリにある生成されたファイルを見てほしい。しかし、その前に、WebGLビルド設定で "Development Player "オプションを選択することをお勧めします。これにより、--output-format=Compactコマンドライン引数が削除され、生成されるC++コードの型名とメソッド名が改善される。
WebGLまたはiOSプレーヤーの設定で様々なオプションを変更してみてください。il2cpp.exeに渡されるコマンドラインオプションで、異なるコード生成ステップを有効にすることができるはずだ。例えば、WebGL Player Settingsの "Enable exceptions "設定を "Full "に変更すると、--emit-null-checks、--enable-stacktrace、--enable-array-bounds-checkの引数がil2cpp.exeのコマンドラインに追加される。
私たちがIL2CPPで挑戦しなかった課題のひとつを指摘しておきたい。IL2CPPでC#標準ライブラリを書き直そうとはしなかった。IL2CPPスクリプトバックエンドを使用するUnityプロジェクトをビルドすると、mscorlib.dll、System.dllなどのC#標準ライブラリコードはすべて、Monoスクリプトバックエンドに使用されるコードとまったく同じです。
私たちは、すでにユーザーによく知られ、Unityプロジェクトで十分にテストされているC#標準ライブラリコードに依存しています。そのため、IL2CPPに関連するバグを調査する場合、そのバグはAOTコンパイラーかランタイム・ライブラリーのどちらかにあり、他のどこにもないと確信できる。
1月にバージョン4.6.1p5でIL2CPPの最初のパブリックリリースを行って以来、私たちは6つのフルリリースと7つのパッチリリース(Unityのバージョン4.6と5.0にわたって)を出荷してきました。リリースノートに記載されている100以上のバグを修正しました。
この継続的な改善を実現するために、私たちは内部でIL2CPPのコードの1つのバージョンに対してのみ開発をしており、それはアルファとベータリリースを出荷するために使用されるUnityのトランクブランチのブリーディングエッジに位置しています。各リリースの直前に、私たちはIL2CPPの変更を特定のリリースブランチに移植し、テストを実行し、私たちが修正したすべてのバグがそのバージョンで修正されていることを確認します。私たちのQAチームとサステイナブル・エンジニアリングチームは、この速度での配信を可能にするために素晴らしい仕事をしてくれました。つまり、IL2CPPのバグが修正されるまで、1週間以上かかることはないのです。
私たちのユーザー・コミュニティは、多くの質の高いバグ・レポートを提出することで、その貴重さを証明してきました。IL2CPPの継続的な改善のため、ユーザーの皆様からのフィードバックに感謝しています。
IL2CPPの開発チームは、テスト第一主義を徹底している。私たちはテスト駆動設計のプラクティスをよく採用しており、良いテストがないプルリクエストをマージすることはめったにない。この戦略は、インプットとアウトプットが明確なIL2CPPのような技術には有効だ。つまり、私たちが目にするバグの大部分は予期せぬ動作ではなく、予期せぬケースなのです(例えば、64ビットのIntPtrを32ビットの配列インデックスとして使用することが可能で、clangがC++コンパイラエラーで失敗する原因になります。)この違いが、高い信頼性をもってバグを素早く修正することを可能にしている。
私たちのコミュニティの助けを借りて、IL2CPPを可能な限り安定させ、速くするために努力しています。ちなみに、もしあなたがこの中のどれかに興味を持たれたなら、私たちは採用中です。
ここで将来のブログ記事を予告することに時間を費やしすぎたのではないかと心配している。私たちには言いたいことがたくさんあり、とても1つの記事には収まりきらない。次回は、il2cpp.exeによって生成されたコードを掘り下げて、C++コンパイラーがあなたのプロジェクトを実際にどのように見ているかを見てみよう。
