Loupe

[Unity] A la découverte de IL2CPP

Il y a un an, Unity Technologies nous annonçait le développement d’un nouveau Runtime .NET sous le nom “IL2CPP”. Un an plus tard, la version 5 de l’éditeur inclus cette technologie pour les projets iOS 64bits et WebGL (preview).

Qu’est ce que c’est ?

Concrètement cela consiste en un compilateur AOT (Ahead of Time) et une machine virtuelle qui correspondent à une implémentation de la CLI (Common Language Infrastructure). Le programme transforme du code IL en C++ pour qu’il puisse ensuite être réinterprété selon les besoins.

Comment ça fonctionne ?

Au niveau du scripting, cela se traduit par quelques changements complètement transparent pour le développeur dans la chaine de traitement. Voici les différentes étapes de transformation du code :

  • Récupération des scripts C#, UnityScript
  • Transformation en code IL via les compilateurs (via Mono pour le C# par exemple)
  • Conversion du code IL en C++ via il2cpp.exe
  • Compilation du C++ suivant la plateforme ciblé (Emscripten, XCode, …)

il2cpp-toolchain-smaller

 

L’exécutable il2cpp.exe est disponible dans le dossier Editor\Data\il2cpp.

L’autre partie importante, libil2cpp, n’est autre que la librairie du runtime qui sera utilisée par la machine virtuelle. Entièrement écrite en C++, ce qui a l’avantage d’être facilement portable, elle sert en autre à décrire le Garbage Collector. Les fichiers sont disponibles dans le dossier Editor\Data\PlaybackEngines\webglsupport\BuildTools\Libraries\libil2cpp\include.

Du C# au C++

Pour comprendre un peu mieux comment sont générés fichiers C++, on va créer un nouveau projet WebGL (Preview) et y ajouter un script C# attaché à la caméra :

   1: using UnityEngine;
   2:  
   3: public class MonScript : MonoBehaviour
   4: {
   5:     void Start()
   6:     {
   7:         transform.position = Vector3.zero;
   8:     }
   9: }

Une fois la compilation effectuée, direction le dossier Temp\StagingArea\Data\il2cppOutput pour retrouver l’ensemble du code C++ générés. Ce qui frappe tout de suite c’est le nombre de fichiers présent : plus de 4000 ! On va s’intéresser plus spécifiquement aux fichiers définissant notre script :

AssemblyU2DCSharp_MonScript.h

Notre classe MonScript est transformée en structure héritant de la classe MonoBehaviour.

   1: #pragma once
   2: #include <stdint.h>
   3: // UnityEngine.MonoBehaviour
   4: #include "UnityEngine_UnityEngine_MonoBehaviour.h"
   5: // MonScript
   6: struct MonScript_t1  : public MonoBehaviour_t2
   7: {
   8: };

AssemblyU2DCSharp_MonScriptMethodDeclarations.h

On retrouve deux déclarations de méthodes :

  • Le constructeur : MonScript__ctor_m0
  • La fonction Start : MonScript_Start_m1

On remarque que les noms des méthodes sont suffixés par un “_m(index)” pour garantir l’unicité.

   1: #pragma once
   2: #include <stdint.h>
   3: #include <assert.h>
   4: #include <exception>
   5: #include "codegen/il2cpp-codegen.h"
   6:  
   7: // MonScript
   8: struct MonScript_t1;
   9:  
  10: // System.Void MonScript::.ctor()
  11:  void MonScript__ctor_m0 (MonScript_t1 * __this, MethodInfo* method) IL2CPP_METHOD_ATTR;
  12: // System.Void MonScript::Start()
  13:  void MonScript_Start_m1 (MonScript_t1 * __this, MethodInfo* method) IL2CPP_METHOD_ATTR;

Bulk_Assembly-CSharp_0.cpp

La définition de l’intégralité des scripts se trouve dans ce fichier, c’est donc normal qu’il soit plutôt volumineux. Dans notre cas, voici à quoi ressemble ce fichier (que j’ai volontairement simplifié) :

   1: // System.Void MonScript::.ctor()
   2: extern MethodInfo MonScript__ctor_m0_MethodInfo;
   3:  void MonScript__ctor_m0 (MonScript_t1 * __this, MethodInfo* method){
   4:     {
   5:         MonoBehaviour__ctor_m2(__this, /*hidden argument*/&MonoBehaviour__ctor_m2_MethodInfo);
   6:         return;
   7:     }
   8: }
   9:  
  10: // System.Void MonScript::Start()
  11: extern MethodInfo MonScript_Start_m1_MethodInfo;
  12:  void MonScript_Start_m1 (MonScript_t1 * __this, MethodInfo* method){
  13:     {
  14:         Transform_t3 * L_0 = Component_get_transform_m3(__this, /*hidden argument*/&Component_get_transform_m3_MethodInfo);
  15:         Vector3_t4  L_1 = Vector3_get_zero_m4(NULL /*static, unused*/, /*hidden argument*/&Vector3_get_zero_m4_MethodInfo);
  16:         Transform_set_position_m5(L_0, L_1, /*hidden argument*/&Transform_set_position_m5_MethodInfo);
  17:         return;
  18:     }
  19: }

On retrouve bien dans ces 3 lignes la modification de la position de notre GameObject.

Si on s’attarde sur le code “Vector.zero” (C#) qui correspond à la méthode “Vector3_get_zero_m4” (C++), on retrouve la définition de la fonction statique dans le fichier UnityEngine_UnityEngine_Vector3MethodDeclarations.h :

   1: // UnityEngine.Vector3 UnityEngine.Vector3::get_zero()
   2:  Vector3_t4  Vector3_get_zero_m4 (Object_t18 * __this/* static, unused */, MethodInfo* method) IL2CPP_METHOD_ATTR;

N’hésitez pas à fouiller dans les dossiers de Unity, on y trouve de nombreux fichiers intéressants afin de mieux comprendre l’architecture d’IL2CPP.

Le futur ?

IL2CPP est annoncé par Unity comme l’outil qui les gouvernera tous. Pour le moment seulement utilisé sur iOS et WebGL, les équipes semblent satisfaites du travail accomplis et du gain de temps pour qu’il soit généralisé sur l’ensemble des plateformes à moyen terme. Pour gagner encore un peu de temps, rendre le projet open-source pourrait être une alternative séduisante.

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus