Que recherchez-vous ?
Engine & platform

Introduction au fonctionnement interne de l'IL2CPP

JOSH PETERSON / UNITY TECHNOLOGIESSenior Software Engineer
May 6, 2015|10 Min
Introduction au fonctionnement interne de l'IL2CPP
Cette page a été traduite automatiquement pour faciliter votre expérience. Nous ne pouvons pas garantir l'exactitude ou la fiabilité du contenu traduit. Si vous avez des doutes quant à la qualité de cette traduction, reportez-vous à la version anglaise de la page web.

Il y a presque un an, nous avons commencé à parler de l'avenir des scripts dans Unity. Le nouveau backend de script IL2CPP promettait d'apporter à Unity une machine virtuelle très performante et très portable. En janvier, nous avons livré notre première plateforme utilisant IL2CPP, iOS 64 bits. La version 5 d'Unity a apporté une nouvelle plateforme, WebGL. Grâce à la contribution de notre formidable communauté d'utilisateurs, nous avons publié de nombreuses mises à jour de patchs pour IL2CPP, améliorant régulièrement son compilateur et son moteur d'exécution. Nous n'avons pas l'intention de cesser d'améliorer IL2CPP, mais nous avons pensé qu'il serait bon de prendre un peu de recul et de vous expliquer comment IL2CPP fonctionne de l'intérieur. Au cours des prochains mois, nous prévoyons d'écrire sur les sujets suivants (et peut-être d'autres) dans cette série d'articles sur les internes d'IL2CPP :

1. les bases - la chaîne d'outils et les arguments de la ligne de commande (ce billet)

2. Une visite du code généré

3. Conseils de débogage pour le code généré

4. Appels de méthodes (méthodes normales, méthodes virtuelles, etc.)

5. Mise en œuvre du partage générique

6. Enveloppements P/invoke pour les types et les méthodes

7. Intégration du collecteur de déchets

8. Cadres de test et utilisation

Afin de rendre cette série de billets possible, nous allons discuter de certains détails de l'implémentation d'IL2CPP qui changeront certainement à l'avenir. Nous espérons pouvoir continuer à fournir des informations utiles et intéressantes.

Qu'est-ce que l'IL2CPP ?

La technologie que nous appelons IL2CPP comporte deux parties distinctes.

  • Un compilateur en avance sur le temps (AOT)
  • Une bibliothèque d'exécution pour soutenir la machine virtuelle

Le compilateur AOT traduit le langage intermédiaire (IL), la sortie de bas niveau des compilateurs .NET, en code source C++. La bibliothèque d'exécution fournit des services et des abstractions tels qu'un ramasse-miettes, un accès indépendant de la plate-forme aux threads et aux fichiers, et des implémentations d'appels internes (code natif qui modifie directement les structures de données gérées).

Le compilateur AOT

Le compilateur IL2CPP AOT est nommé il2cpp.exe. Sous Windows, il se trouve dans le répertoire EditorDatail2cpp. Sous OSX, il se trouve dans le répertoire Contents/Frameworks/il2cpp/build de l'installation Unity.

L'utilitaire il2cpp.exe est un exécutable géré, écrit entièrement en C#. Nous l'avons compilé avec les compilateurs .NET et Mono au cours de notre développement d'IL2CPP. L'utilitaire il2cpp.exe accepte les assemblages gérés compilés avec le compilateur Mono fourni avec Unity et génère du code C++ que nous transmettons à un compilateur C++ spécifique à la plate-forme.

Vous pouvez considérer la chaîne d'outils IL2CPP de la manière suivante :

il2cpp toolchain plus petit
La bibliothèque d'exécution

L'autre partie de la technologie IL2CPP est une bibliothèque d'exécution qui prend en charge la machine virtuelle. Nous avons mis en œuvre cette bibliothèque en utilisant presque exclusivement du code C++ (il y a un peu de code assembleur spécifique à la plate-forme, mais cela reste entre nous). Nous appelons la bibliothèque d'exécution libil2cpp et elle est livrée en tant que bibliothèque statique liée à l'exécutable du lecteur. L'un des principaux avantages de la technologie IL2CPP est la simplicité et la portabilité de la bibliothèque d'exécution.

Vous pouvez trouver quelques indices sur la façon dont le code libil2cpp est organisé en regardant les fichiers d'en-tête pour libil2cpp que nous livrons avec Unity (vous les trouverez dans le répertoire Editor\Data\PlaybackEngines\webglsupport\BuildTools\Libraries\libil2cpp\include sur Windows, ou le répertoire Contents/Frameworks/il2cpp/libil2cpp sur OSX). Par exemple, l'interface entre le code C++ généré par il2cpp.exe et le moteur d'exécution libil2cpp se trouve dans le fichier d'en-tête codegen/il2cpp-codegen.h.

L'un des éléments clés de la durée d'exécution est le collecteur d'ordures. Unity 5 est livré avec libgc, le collecteur de déchets Boehm-Demers-Weiser. Cependant, libil2cpp a été conçu pour nous permettre d'utiliser d'autres ramasseurs d'ordures. Par exemple, nous étudions une intégration du GC de Microsoft qui a été mis en libre accès dans le cadre du CoreCLR. Nous en dirons plus à ce sujet dans notre article sur l'intégration du ramasse-miettes, plus loin dans la série.

Comment est exécuté il2cpp.exe ?

Prenons un exemple. J'utiliserai Unity 5.0.1 sur Windows, et je commencerai par un nouveau projet vide. Afin d'avoir au moins un script utilisateur à convertir, je vais ajouter ce simple composant MonoBehaviour à l'objet de jeu Main Camera :

Type de bloc inconnu "codeBlock", veuillez spécifier un sérialiseur pour ce type dans la propriété `serializers.types`.

Lorsque je construis pour la plateforme WebGL, je peux utiliser l'explorateur de processus pour voir la ligne de commande utilisée par Unity pour lancer il2cpp.exe :

Type de bloc inconnu "codeBlock", veuillez spécifier un sérialiseur pour ce type dans la propriété `serializers.types`.

Cette ligne de commande est assez longue et horrible, alors décortiquons-la. Tout d'abord, Unity exécute cet exécutable :

Type de bloc inconnu "codeBlock", veuillez spécifier un sérialiseur pour ce type dans la propriété `serializers.types`.

L'argument suivant sur la ligne de commande est l'utilitaire il2cpp.exe lui-même.

Type de bloc inconnu "codeBlock", veuillez spécifier un sérialiseur pour ce type dans la propriété `serializers.types`.

Les autres arguments de la ligne de commande sont transmis à il2cpp.exe, et non à mono.exe. Examinons-les. Tout d'abord, Unity transmet cinq drapeaux à il2cpp.exe :

  • --copy-level=None
  • Indiquer que il2cpp.exe ne doit pas effectuer de copies de fichiers spéciales du code C++ généré.
  • --enable-generic-sharing
  • Il s'agit d'une fonction de réduction de la taille du code et du binaire. IL2CPP partagera la mise en œuvre des méthodes génériques lorsqu'il le pourra.
  • --enable-unity-event-support
  • Prise en charge spéciale pour garantir que le code des événements Unity, auxquels on accède par réflexion, est correctement généré.
  • --output-format=Compact
  • Générer du code C++ dans un format qui nécessite moins de caractères pour les noms de types et de méthodes. Ce code est difficile à déboguer, car les noms dans le code IL ne sont pas préservés, mais il se compile souvent plus rapidement, car il y a moins de code à analyser pour le compilateur C++.
  • --extra-types.file="C:\Program Files\Unity\Editor\Data\il2cpp\il2cpp_default_extra_types.txt"
  • Utiliser le fichier de types supplémentaires par défaut (et vide). Ce fichier peut être ajouté dans un projet Unity pour indiquer à il2cpp.exe quels types de génériques ou de tableaux seront créés à l'exécution, mais ne sont pas présents dans le code IL.

Il est important de noter que ces arguments de ligne de commande peuvent être et seront modifiés dans les versions ultérieures. Nous ne disposons pas encore d'un ensemble stable et supporté d'arguments de ligne de commande pour il2cpp.exe. Enfin, nous avons une liste de deux fichiers et d'un répertoire sur la ligne de commande :

  • "C:\NUsers\NJosh Peterson\NDocuments\NIL2CPP Blog Example\NTemp\NStagingArea\NData\NManaged\NAssembly-CSharp.dll"
  • "C:\NUsers\NJosh Peterson\NDocuments\NIL2CPP Blog Example\NTemp\NStagingArea\NData\NManaged\NUnityEngine.UI.dll"
  • "C:\NUsers\NJosh Peterson\NDocuments\NIL2CPP Blog Example\NTempérature\NStagingArea\NData\Nil2cppOutput"

L'utilitaire il2cpp.exe accepte une liste de tous les assemblages IL qu'il doit convertir. Dans ce cas, il s'agit de l'assembly contenant mon MonoBehaviour simple, Assembly-CSharp.dll, et de l'assembly GUI, UnityEngine.UI.dll. Notez qu'il y a quelques assemblages manifestement manquants ici. Il est clair que mon script fait référence à UnityEngine.dll, qui fait référence à au moins mscorlib.dll, et peut-être à d'autres assemblages. Où sont-ils ? En fait, il2cpp.exe résout ces assemblages en interne. Ils peuvent être mentionnés sur la ligne de commande, mais ils ne sont pas nécessaires. Unity ne doit mentionner explicitement que les assemblages racines (ceux qui ne sont référencés par aucun autre assemblage).

Le dernier argument de la ligne de commande il2cpp.exe est le répertoire dans lequel les fichiers C++ de sortie doivent être créés. Si vous êtes curieux, jetez un coup d'œil aux fichiers générés dans ce répertoire, ils feront l'objet du prochain billet de cette série. Avant de le faire, vous pouvez choisir l'option "Development Player" dans les paramètres de construction de WebGL. Cela supprimera l'argument de ligne de commande --output-format=Compact et vous donnera de meilleurs noms de types et de méthodes dans le code C++ généré.

Essayez de modifier diverses options dans les paramètres WebGL ou iOS Player. Vous devriez pouvoir voir différentes options de ligne de commande passées à il2cpp.exe pour activer différentes étapes de génération de code. Par exemple, changer le paramètre "Activer les exceptions" dans les paramètres du lecteur WebGL pour une valeur de "Complet" ajoute les arguments --emit-null-checks, --enable-stacktrace, et --enable-array-bounds-check à la ligne de commande de il2cpp.exe.

Que ne fait pas l'IL2CPP ?

J'aimerais souligner l'un des défis que nous n'avons pas relevé avec l'IL2CPP, et nous ne pourrions pas être plus heureux de l'avoir ignoré. Nous n'avons pas tenté de réécrire la bibliothèque standard C# avec IL2CPP. Lorsque vous construisez un projet Unity qui utilise le backend de script IL2CPP, tout le code de la bibliothèque standard C# dans mscorlib.dll, System.dll, etc. est exactement le même code que celui utilisé pour le backend de script Mono.

Nous nous appuyons sur le code de la bibliothèque standard C# qui est déjà bien connu des utilisateurs et bien testé dans les projets Unity. Ainsi, lorsque nous enquêtons sur un bogue lié à IL2CPP, nous pouvons être sûrs que le bogue se trouve soit dans le compilateur AOT, soit dans la bibliothèque d'exécution, et nulle part ailleurs.

Comment nous développons, testons et expédions l'IL2CPP

Depuis la première version publique d'IL2CPP (4.6.1p5) en janvier, nous avons livré 6 versions complètes et 7 versions de correctifs (sur les versions 4.6 et 5.0 d'Unity). Nous avons corrigé plus de 100 bogues mentionnés dans les notes de version.

Afin de permettre cette amélioration continue, nous ne développons qu'une seule version du code IL2CPP en interne, qui se trouve à la limite de la branche trunk d'Unity utilisée pour les versions alpha et bêta. Juste avant chaque version, nous portons les changements d'IL2CPP sur la branche spécifique de la version, exécutons nos tests et vérifions que tous les bogues que nous avons corrigés ont été corrigés dans cette version. Nos équipes d'assurance qualité et d'ingénierie durable ont accompli un travail incroyable pour rendre possible ce rythme de livraison. Cela signifie que nos utilisateurs ne sont jamais à plus d'une semaine des derniers correctifs pour les bogues de l'IL2CPP.

Notre communauté d'utilisateurs s'est révélée inestimable en soumettant de nombreux rapports de bogues de grande qualité. Nous apprécions tous les commentaires de nos utilisateurs qui nous aident à améliorer continuellement l'IL2CPP, et nous attendons avec impatience d'en recevoir d'autres.

L'équipe de développement qui travaille sur IL2CPP a une forte mentalité de test d'abord. Nous employons souvent des pratiques de conception pilotée par les tests, et nous fusionnons rarement une demande d'extraction sans de bons tests. Cette stratégie fonctionne bien pour une technologie comme l'IL2CPP, pour laquelle les entrées et les sorties sont clairement définies. Cela signifie que la grande majorité des bogues que nous voyons ne sont pas des comportements inattendus, mais plutôt des cas inattendus (par exemple, il est possible d'utiliser un IntPtr 64 bits comme index de tableau 32 bits, ce qui fait échouer clang avec une erreur de compilateur C++, et le code réel fait réellement cela !) Cette différence nous permet de corriger rapidement les bogues avec un degré de confiance élevé.

Avec l'aide de notre communauté, nous travaillons dur pour rendre IL2CPP aussi stable et rapide que possible. D'ailleurs, si tout cela vous intéresse, sachez que nous embauchons (c'est le cas de le dire).

Plus d'informations à venir

Je crains d'avoir passé trop de temps à teaser de futurs articles de blog. Nous avons beaucoup de choses à dire, et tout cela ne tient pas dans un seul billet. La prochaine fois, nous examinerons le code généré par il2cpp.exe pour voir à quoi ressemble votre projet pour le compilateur C++.