Search Unity

Это второй пост из серии по IL2CPP. В этой статье мы будем исследовать C++ код, сгенерированный il2cpp.exe. Мы увидим, как представлены управляемые типы в машинном коде, посмотрим на проверки во время выполнения, используемые для поддержки виртуальной машины .NET, как генерируются циклы и многое другое!

Мы получим очень версия-зависимый код, который, безусловно, будет меняться в более поздних версиях Unity. Тем не менее, концепции останутся такими же.

Пример проекта

Для этого примера я буду использовать последнюю доступную версию Unity, 5.0.1p1. Как и в первом посте этой серии, я начну с пустого проекта и добавлю один скрипт со следующим содержанием:

[csharp]
using UnityEngine;

public class HelloWorld : MonoBehaviour {
private class Important {
public static int ClassIdentifier = 42;
public int InstanceIdentifier;
}

void Start () {
Debug.Log("Hello, IL2CPP!");

Debug.LogFormat("Static field: {0}", Important.ClassIdentifier);

var importantData = new [] {
new Important { InstanceIdentifier = 0 },
new Important { InstanceIdentifier = 1 } };

Debug.LogFormat("First value: {0}", importantData[0].InstanceIdentifier);
Debug.LogFormat("Second value: {0}", importantData[1].InstanceIdentifier);
try {
throw new InvalidOperationException("Don’t panic");
}
catch (InvalidOperationException e) {
Debug.Log(e.Message);
}

for (var i = 0; i < 3; ++i) {
Debug.LogFormat("Loop iteration: {0}", i);
}
}
}
[/csharp]

Я соберу этот проект для WebGL в редакторе Unity на Windows. Я выбрал опцию «Development Player» в Build Settings, так что мы можем получить относительно хорошие имена в сгенерированном коде C++. Я также установил опцию «Enable Exceptions» для WebGL в Player Settings в значение «Full».

Обзор сгенерированного кода

После завершения сборки под WebGL, сгенерированный C++ код доступен в директории Temp\StagingArea\Data\il2cppOutput в папке моего проекта. После закрытия редактора, эта директория будет удалена. Пока редактор открыт, этот каталог будет существовать, так что мы можем проверить его.

Утилита il2cpp.exe генерирует несколько файлов, даже для этого небольшого проекта. Я вижу 4625 файлов заголовков и 89 файлов исходного кода C++. Чтобы рассмотреть этот код, я хотел бы использовать текстовый редактор, который работает с Exuberant CTags. CTags обычно для такого кода быстро генерирует файл тегов, что позволяет легче в нём ориентироваться.

Вы можете увидеть, что многие из файлов C++ созданы не из простого кода скрипта, а содержат преобразованный код стандартных библиотек, таких как mscorlib.dll. Как уже упоминалось в первом посте в этой серии, скриптовый движок IL2CPP использует тот же код стандартных библиотек, что и Mono. Обратите внимание, что мы преобразовываем код mscorlib.dll и других стандартных библиотек при каждом запуске il2cpp.exe. Это может показаться ненужным, так как код не изменяется.

Тем не менее, IL2CPP всегда использует зачистку байт-кода, чтобы уменьшить размер исполняемого файла. Как следствие, даже небольшие изменения в коде скрипта могут вызвать много различных частей кода стандартной библиотеки, которые будут использоваться или нет, в зависимости от ситуации. Поэтому мы должны преобразовывать mscorlib.dll каждый раз. Мы ищем лучший способ сделать инкрементную сборку, но пока нам этого не удалось.

Как управляемый код отображается в сгенерированном C++ коде

Для каждого типа в управляемом коде il2cpp.exe будет генерировать один файл заголовка C++ для определения типа и другой файл заголовка для объявления метода для этого типа. Например, давайте посмотрим на сгенерированный код для типа UnityEngine.Vector3. Файл заголовка для этого типа имеет название UnityEngine_UnityEngine_Vector3.h. Имя создается на основе имени сборки, UnityEngine.dll, затем берется пространство имен и в конце имя типа. Код выглядит следующим образом:

[cpp]
// UnityEngine.Vector3
struct Vector3_t78
{
// System.Single UnityEngine.Vector3::x
float ___x_1;
// System.Single UnityEngine.Vector3::y
float ___y_2;
// System.Single UnityEngine.Vector3::z
float ___z_3;
};
[/cpp]

Утилита il2cpp.exe преобразует каждое из трех полей экземпляра, и делает небольшое изменение имени, используя добавление подчеркиваний в начале, чтобы избежать конфликтов с зарезервированными словами. Мы используем некоторые зарезервированные имена в C++, но пока не видели каких-либо конфликтов с кодом стандартных библиотек C++.

Файл UnityEngine_UnityEngine_Vector3MethodDeclarations.h содержит объявления для всех методов в Vector3. Например, Vector3 переопределяет метод Object.ToString:

[cpp]
// System.String UnityEngine.Vector3::ToString()
extern "C" String_t* Vector3_ToString_m2315 (Vector3_t78 * __this, MethodInfo* method) IL2CPP_METHOD_ATTR
[/cpp]

Обратите внимание на комментарий, в котором указан управляемый метод, представляющий это объявление. Это часто полезно для поиска файлов на выходе по имени управляемого метода в этом формате, особенно для методов с общими именами, такими как ToString.

Вот несколько интересных особенностей методов, созданных il2cpp.exe:

  • Это не функции-члены в C++. Все методы — свободные функции, где первый аргумент — указатель «this». Для статических функций в управляемом коде, IL2CPP всегда передаёт значение NULL для первого аргумента. Объявляя методы с указателем «this» в качестве первого аргумента, мы упрощаем способ генерации кода в il2cpp.exe и вызов методов через другие методы (например, делегаты) для сгенерированного кода.
  • Каждый метод имеет дополнительный аргумент типа MethodInfo*, который включает в себя метаданные о методе, которые используются для таких вещей, как вызов виртуального метода. Mono использует специфичные для платформы транспорты, чтобы передать эти метаданные. Для IL2CPP, мы решили избежать использования транспортов, чтобы улучшить переносимость
  • Все методы объявлены внешне, чтобы il2cpp.exe мог иногда обмануть компилятор C++ и рассматривать все методы, как если бы они имели такой же тип.
  • Имена типов содержат суффикс «_t». Имена методов — суффикс «_m». Конфликты имен решаются добавлением уникального номера для каждого имени. Эти цифры будут меняться, если что-либо изменить в коде пользовательского скрипта, так что вы не можете рассчитывать на них во время сборки.

Первые два пункта говорят, что каждый метод имеет по крайней мере два параметра, указатель «this» и указатель MethodInfo. Но добавляют ли эти параметры ненужные расходы? Мы не обнаружили того, чтобы эти дополнительные аргументы могли привести к снижению производительности. Хотя может показаться, что они будут, профилирование показало, что разница в производительности не поддается измерению.

Мы можем перейти к определению метода ToString, используя Ctags. Оно находится в файле Bulk_UnityEngine_0.cpp. Код в этом определении метода не выглядит похожим на C# код в методе Vector3::ToString(). Однако, если вы используете инструмент, такой как ILSpy, чтобы посмотреть код для метода Vector3::ToString(), вы увидите, что cгенерированный код C++ очень похож на IL код.

Почему il2cpp.exe не генерирует отдельный файл C++ для определения методов для каждого типа, как делает это для объявления методов? Bulk_UnityEngine_0.cpp файл на самом деле довольно большой, 20481 строка! Компиляторы C++, которые мы использовали, с трудом работали с большим количеством исходных файлов. Компиляция четырех тысяч .cpp файлов длилась дольше, чем компиляция того же кода в 80 .cpp файлах. Поэтому il2cpp.exe делит определения методов для типов на группы и генерирует один файл C++ для каждой группы.

Теперь вернемся к заголовочному файлу объявления методов и обратим внимание на строку в верхней части файла:

[cpp]
#include "codegen/il2cpp-codegen.h"
[/cpp]

Файл il2cpp-codegen.h содержит интерфейс, который использует сгенерированный код для доступа к libil2cpp во время выполнения. Мы обсудим несколько способов, которые во время выполнения используются сгенерированным кодом, позже.

Пролог метода

Давайте взглянем на определение метода Vector3::ToString(). В частности, он имеет общий пролог, который создается il2cpp.exe во всех методах.

[cpp]
StackTraceSentry _stackTraceSentry(&Vector3_ToString_m2315_MethodInfo);
static bool Vector3_ToString_m2315_init;
if (!Vector3_ToString_m2315_init)
{
ObjectU5BU5D_t4_il2cpp_TypeInfo_var = il2cpp_codegen_class_from_type(&ObjectU5BU5D_t4_0_0_0);
Vector3_ToString_m2315_init = true;
}
[/cpp]

В первой строке этого пролога создается локальная переменная типа StackTraceSentry. Эта переменная используется для отслеживания управляемых вызовов стека, так что IL2CPP можете сообщить об этом в вызовах, таких как Environment.StackTrace. Генерация этого кода на самом деле необязательна, и сработала в этом случае из-за передачи il2cpp.exe флага —enable-StackTrace (так как я установил для WebGL параметр «Enable Exceptions» в Player Settings в значение «Full»). Мы обнаружили, что для маленьких функций эта переменная добавляет расходы и оказывает негативное влияние на производительность. Таким образом, для iOS и других платформ, где можно получить информацию трассировки стека без этого кода, мы никогда не добавляем эту строку в генерируемый код. Для WebGL, мы не имеем поддержки трассировки стека, поэтому для правильной работы необходимо перехватывать исключения управляемого кода.

Вторая часть пролога делает «ленивую» инициализацию типа метаданных для любого массива или сгенерированных типов, используемых в теле метода. Так ObjectU5BU5D_t4 — это имя типа System.Object[]. Эта часть пролога выполняется только один раз, и часто не делает ничего, если тип был уже инициализирован, таким образом, мы не видели каких-либо неблагоприятных последствий для производительности от этого сгенерированного кода.

Этот код потокобезопасный? Что делать, если два потока вызывают Vector3::ToString () одновременно? На самом деле, этот код не создает проблем, так как весь код в libil2cpp используется для инициализации типа, безопасного для вызова из нескольких потоков. Возможно (даже вероятно), что функция il2cpp_codegen_class_from_type будет вызвана более чем один раз, но реально она сработает только один раз, в одном потоке. Выполнение метода не будет продолжаться, пока инициализация не завершена. Поэтому этот пролог метода является потокобезопасным.

Проверки во время выполнения

Следующая часть метода создает массив объектов, сохраняет значение поля Х для Vector3 в локальную переменную, затем создает локальную переменную “ящика” и добавляет его в массив с индексом ноль. Вот сгенерированный код C++ (с некоторыми аннотациями):

[cpp]
// Create a new single-dimension, zero-based object array
ObjectU5BU5D_t4* L_0 = ((ObjectU5BU5D_t4*)SZArrayNew(ObjectU5BU5D_t4_il2cpp_TypeInfo_var, 3));
// Store the Vector3::x field in a local
float L_1 = (__this->___x_1);
float L_2 = L_1;
// Box the float instance, since it is a value type.
Object_t * L_3 = Box(InitializedTypeInfo(&Single_t264_il2cpp_TypeInfo), &L_2);
// Here are three important runtime checks
NullCheck(L_0);
IL2CPP_ARRAY_BOUNDS_CHECK(L_0, 0);
ArrayElementTypeCheck (L_0, L_3);
// Store the boxed value in the array at index 0
*((Object_t **)(Object_t **)SZArrayLdElema(L_0, 0)) = (Object_t *)L_3;
[/cpp]

Три проверки отсутствуют в коде IL, но добавляются il2cpp.exe.

  • Проверка NullCheck бросит исключение NullReferenceException, если значение массива равно null.
  • IL2CPP_ARRAY_BOUNDS_CHECK бросит исключение IndexOutOfRangeException, если индекс массива не является правильным.
  • ArrayElementTypeCheck бросит исключение ArrayTypeMismatchException, если тип элемента, добавленного в массив, не является правильным.

Эти три проверки гарантируют правильность данных для виртуальной машины .NET. Вместо внедрения кода, Mono использует механизмы целевой платформы для обработки этих же проверок во время выполнения. Мы хотели, чтобы IL2CPP мог охватить больше платформ, включая такие как WebGL, где нет своего механизма проверок, поэтому il2cpp.exe вводит эти проверки.

Эти проверки создают проблемы с производительностью? В большинстве случаев, мы не видели какого-либо ухудшения производительности, к тому же они обеспечивают преимущества и безопасность, которые требуются в виртуальной машине .NET. Однако, в некоторых отдельных случаях мы заметили, что эти проверки привели к снижению производительности, особенно в труднодоступных циклах. Мы работаем над способом, который позволит управляемому коду удалить эти динамические проверки, когда il2cpp.exe генерирует C++ код. Следите за обновлениями.

Статические поля

Теперь, когда мы увидели, как выглядят поля экземпляра (на примере Vector3), давайте посмотрим, как преобразуются статические поля и как организован доступ к ним. Найдем определение метода HelloWorld_Start_m3, который находится в файле Bulk_Assembly-CSharp_0.cpp в моей сборке. Оттуда переходим к типу Important_t1 (в файле theAssemblyU2DCSharp_HelloWorld_Important.h):

[cpp]
struct Important_t1  : public Object_t
{
// System.Int32 HelloWorld/Important::InstanceIdentifier
int32_t ___InstanceIdentifier_1;
};
struct Important_t1_StaticFields
{
// System.Int32 HelloWorld/Important::ClassIdentifier
int32_t ___ClassIdentifier_0;
};
[/cpp]

Обратите внимание, что il2cpp.exe создала отдельную С++ структуру, чтобы предоставить статическое поле для этого типа, так как оно должно быть доступно всем экземплярам этого типа. Таким образом, во время выполнения, будет создан один экземпляр типа Important_t1_StaticFields, и все экземпляры типа Important_t1 будут использовать этот экземпляр как статическое поле. В сгенерированном коде доступ к статическому полю происходит следующим образом:

[cpp]
int32_t L_1 = (((Important_t1_StaticFields*)InitializedTypeInfo(&Important_t1_il2cpp_TypeInfo)->static_fields)->___ClassIdentifier_0);
[/cpp]

Метаданные типа для Important_t1 содержит указатель на один экземпляр типа Important_t1_StaticFields, и информацию о том, что этот экземпляр используется для получения значения статического поля.

Исключения

Управляемые исключения преобразуются il2cpp.exe в C++ исключения. Мы выбрали такой подход, чтобы избежать зависимости от платформы. Когда il2cpp.exe нужно создавать код, способный бросить управляемое исключение, он вызывает функцию il2cpp_codegen_raise_exception.

Блок try…catch для управляемых исключений в нашем методе HelloWorld_Start_m3 выглядит так:

[cpp]
try
{ // begin try (depth: 1)
InvalidOperationException_t7 * L_17 = (InvalidOperationException_t7 *)il2cpp_codegen_object_new (InitializedTypeInfo(&InvalidOperationException_t7_il2cpp_TypeInfo));
InvalidOperationException__ctor_m8(L_17, (String_t*) &_stringLiteral5, /*hidden argument*/&InvalidOperationException__ctor_m8_MethodInfo);
il2cpp_codegen_raise_exception(L_17);
// IL_0092: leave IL_00a8
goto IL_00a8;
} // end try (depth: 1)
catch(Il2CppExceptionWrapper& e)
{
__exception_local = (Exception_t8 *)e.ex;
if(il2cpp_codegen_class_is_assignable_from (&InvalidOperationException_t7_il2cpp_TypeInfo, e.ex->object.klass))
goto IL_0097;
throw e;
}
IL_0097:
{ // begin catch(System.InvalidOperationException)
V_1 = ((InvalidOperationException_t7 *)__exception_local);
NullCheck(V_1);
String_t* L_18 = (String_t*)VirtFuncInvoker0< String_t* >::Invoke(&Exception_get_Message_m9_MethodInfo, V_1);
Debug_Log_m6(NULL /*static, unused*/, L_18, /*hidden argument*/&Debug_Log_m6_MethodInfo);
// IL_00a3: leave IL_00a8
goto IL_00a8;
} // end catch (depth: 1)
[/cpp]

Все управляемые исключения заворачиваются в Il2CppExceptionWrapper. Когда сгенерированный код ловит исключение этого типа, он распаковывает C++ представление управляемого исключения (которое имеет тип Exception_t8). В данном случае, мы ищем только InvalidOperationException, поэтому если мы не найдем исключение этого типа, C++  снова бросит это исключение. Если же мы встречаем исключение правильного типа, то код переходит к реализации обработчика, и выводит сообщение исключения.

Goto!?!

Этот код вызывает интересный вопрос. Что эти ярлыки и goto там делают? Эти конструкции не являются необходимыми в структурном программировании! Тем не менее, IL не имеет концепции структурированного программирования, таких как циклы, конструкции if/then. Так как этот код низкоуровневый, il2cpp.exe придерживается концепции низкоуровневого программирования в сгенерированном коде.

Для примера, давайте посмотрим на цикл в методе HelloWorld_Start_m3:

[cpp]
IL_00a8:
{
V_2 = 0;
goto IL_00cc;
}
IL_00af:
{
ObjectU5BU5D_t4* L_19 = ((ObjectU5BU5D_t4*)SZArrayNew(ObjectU5BU5D_t4_il2cpp_TypeInfo_var, 1));
int32_t L_20 = V_2;
Object_t * L_21 =
Box(InitializedTypeInfo(&Int32_t5_il2cpp_TypeInfo), &L_20);
NullCheck(L_19);
IL2CPP_ARRAY_BOUNDS_CHECK(L_19, 0);
ArrayElementTypeCheck (L_19, L_21);
*((Object_t **)(Object_t **)SZArrayLdElema(L_19, 0)) = (Object_t *)L_21;
Debug_LogFormat_m7(NULL /*static, unused*/, (String_t*) &_stringLiteral6, L_19, /*hidden argument*/&Debug_LogFormat_m7_MethodInfo);
V_2 = ((int32_t)(V_2+1));
}
IL_00cc:
{
if ((((int32_t)V_2) < ((int32_t)3)))
{
goto IL_00af;
}
}
[/cpp]

Здесь переменная V_2 является индексом цикла. В начале она имеет значение 0, и увеличивается в нижней части цикла в этой строке:

[cpp]
V_2 = ((int32_t)(V_2+1));
[/cpp]

Условие окончания цикла проверяется здесь:

[cpp]
if ((((int32_t)V_2) < ((int32_t)3)))
[/cpp]

Пока V_2 меньше 3-х, управление переходит на метку IL_00af, которая является верхней частью тела цикла. Вы могли догадаться, что на данный момент il2cpp.exe генерирует C++ код непосредственно из IL, без использования промежуточного абстрактного представления синтаксического дерева. И это действительно так. Возможно, вы также заметили выше в разделе динамических проверок, что некоторые части из сгенерированного кода выглядят следующим образом:

[cpp]
float L_1 = (__this->___x_1);
float L_2 = L_1;
[/cpp]

Очевидно, что здесь не требуется переменная L_2. Большинство компиляторов C++ может оптимизировать эту задачу, но мы хотели бы вообще избежать ее создания. В настоящее время мы исследуем возможности использования AST, чтобы лучше понять код IL и генерировать лучший C++ код для случаев, использующих локальные переменные и циклов.

Вывод

Мы рассмотрели только малую часть C++ кода, сгенерированного IL2CPP для очень простого проекта. Если вы этого не сделали, я призываю вас погрузиться в сгенерированный код вашего проекта. Изучая его, имейте в виду, что генерируемый C++ код будет выглядеть по-другому в будущих версиях Unity, так как мы постоянно работаем над улучшением качества и производительности IL2CPP.

Путем преобразования IL кода в C++, мы добились хорошего баланса между портативностью и производительностью кода. Мы смогли получить много полезных для разработчиков функций управляемого кода, и в то же время по-прежнему пользуемся преимуществами машинного кода, которые обеспечивает компилятор С++ для различных платформ.

В будущих постах, мы будем изучать больше сгенерированного кода, в том числе вызовы методов, распределения реализаций методов и обертки для вызова нативных библиотек. Но в следующий раз мы будем отлаживать некоторую часть из сгенерированного кода для 64-битной сборки iOS, используя Xcode.

Комментарии закрыты.

  1. Just curious, why haven’t you used AST from the beginning for code generation but? With AST you do have a lot of info about the context.

    1. IL is easy enough to parse and understand that we we decided to avoid using an AST initially for bootstrapping the project and for speed of development iteration. We found that processing IL directly was good enough, and that decision helped us get the product released and in real use.

      As we look for more optimization opportunities though, we’re seeing that an AST can be beneficial.

  2. Looking at this code I’m wondering if this il2cpp is good enough that we don’t need plugins in C++ ?

    1. I don’t think that IL2CPP will replace native plugins completely. However, we can do much more in script code than is possible without IL2CPP.

      Native plugins will still be beneficial because they don’t have any of the checks required by the .NET VM (null checks, array bounds checks, etc). They also can access native APIs more easily in some cases.

      We hope that IL2CPP will give developers another tool to use so that they can make the best decisions for each project.

  3. Just FYI, a bug when initializing multiple lists (maybe other types as well?) of the same generic type: http://fogbugz.unity3d.com/default.asp?696840_1ukc8ppg8btktrh6

    1. Thanks for submitting this bug, we will investigate it.

      1. It’s been 2 weeks and no news so far… what kind of resources do you have at hand that you can’t investigate a well-defined and easily reproduced bug?

  4. Why don’t you pre-compile the .net core dlls in advance and then link them when compiling our custom C++ code? As I understand this could heavily reduce the compile time. You could at least do this as a fast-compile option so when we don’t care about the file size could test the game on the device quickly, and then when we want to release the game use the more optimized compilation path.

    1. This is certainly something we have considered. In the future we would like to enable a no-stripping mode for IL2CPP (just as we have for the Mono scripting backend on iOS). In this mode, we could certainly ship the converted standard library assemblies, or even build them once per project or machine.

      At this point we’re still working through binary size improvements, so we’re not ready to allow a no-stripping mode, but I suspect that we will get there.

  5. IL2CPP has a long way to go before it reaches maturity but it’s definitely many steps in the right direction. Given how much more work has to be done to get it there, I think it’s about time Unity seriously considered open sourcing the IL2CPP project. I trust that the guys at Unity Tech are very capable of pulling this off, but at this rate, it will be another year and a half (maybe 2) before we have a trusty IL2CPP.exe that can take just about any IL and spit out highly optimised, lean and error free C++ code. Besides I also think it’s a very inefficient use of your software engineering talent, who should be focusing their energies on game engine tech, not a general purpose IL to C++ transpiler. I appreciate all the work that is being done and the weekly patches that are fixing bugs as quickly as possible. But I can’t help but feel like it’s far too big a project for a team as small as yours and it brings back memories of all the man hours that were wasted porting the engine to flash!

    1. Thanks for your suggestion, we’ll definitely keep this in mind.

    2. Yes !

      Please Unity, Open source it, wait until it matures a few years, see how people you could benefit from the community…and in the meanwhile update Mono.

  6. Does/will il2cpp support all CLR 2.0 assemblies, including those compiled from C# 6.0 sources with all that fancy language features like async/await etc?

    1. Yes, we should be able to support and .NET 2.0 assembly. In practice, our initial focus is to support .NET 2.0 assemblies built with the Mono compiler that ships with Unity, so those will get highest priority. We have seen a few bugs related to .NET 2.0 assemblies built with MSVC (due to differing IL code), and we have fixed them, but there might be more out there.

      As far as C# 6.0 language features are concerned, if they will work with .NET 2.0 we should be fine, but this is not a case we are testing or optimizing for yet, so in practice they may not work. Our long-term plan is to support newer versions of .NET and C#, but we’re not there yet.

  7. You guys have built a Monster…kill it with fire !!!

  8. This was a nice read. Thanks for sharing it, much appreciated! Can hardly wait for the next post :)

    I wonder if all the C# optimization knowledge we built up over the years still applies when using Il2Cpp.

    For example, in C# it is often beneficial to use ValueType’s rather than ReferenceType’s, or to avoid using an enum to index into a dictionary, or the whole 2d arrays vs jagged arrays topic, or to cache anonymous delegates to avoid memory allocation each time the runtime comes across them and all these things we now do to squeeze out the last bit of performance.

    Can forget all these C# specific optimizations when targeting Il2Cpp?

    1. No, I think that most of these are probably still good optimizations. I say «probably» because I haven’t profiled them with IL2CPP yet, so it is not prudent for me to say with certainty. :)

      In general, I think that these optimizations still apply because much of the code is the same as the Mono scripting backend. Since IL2CPP uses the Mono C# compiler and the Mono standard libraries, any optimizations that lead to «better» IL code (for the definition of «better» you are after, maybe smaller or faster), will likely still apply with IL2CPP, since IL2CPP has to convert that IL code.

      An optimization specific to the Mono runtime might not be applicable with IL2CPP though. The best option is to measure things, of course.

  9. Nice post ! very informative :)

    From my experience (and also from looking at how the generated code looks like), it seems that in some cases, it could help to have a dedicated c++ implementation (not a generated one).

    You said that you guys didn’t want to implement all the Mono class libraries, but what about the engine itself? do you have the option to «plug in» a c++ implementation instead of generating code ?
    (I realize that probably most of the engine *is* native already, but for the managed parts, is this possible ?)

    1. Plugging in an implementation of the engine code in C++ is not something I recall us considering. At this point, I don’t think we would do it, since (as you mentioned) much of the engine code is already in native code. I suspect that the overhead of maintaining two copies of engine code (one in managed code for the Mono scripting backend and one in native code for the IL2CPP scripting backend) would be too difficult.

  10. I like the fact that you guys are working on implementing annonations to disable array bounds checks for specified places in the code. Was one of the first things that came to my mind the moment loops were mentioned in the intro of this post. Was glad to find out it was already being worked on.

    The tech looks promising, surely keeping my eyes open for more blog posts about this!

  11. This doesn’t seem to me c++, it is more c-style classes. I can understand the way because of simplicity. I wonder you found out generating more cpp files increases compile time drastic. This is in my experience the case related to the count of header include preprocessor directives per cpp file, because the compiler need to recompile for every cpp file. This could be solved with precompiled headers and is the preferred way.
    And I don’t really understand the double underscore, it’s really a no go by default.
    Very interesting!

    1. We have had discussions about whether we should output C code instead of C++. We’re sticking to C++ because we get cross-platform, no-cost exceptions support. We also use C++ templates in a few places to help with code generation.

      We actually do have an option in IL2CPP to generate a precompiled header, added for the same reasons you mentioned here. We found that it benefited compile-time performance for some projects, but it actually caused longer compile times for others. Since the benefits were not consistent and it caused some problems, we have now removed support for precompiled headers. We might bring it back later though.

      I’m not sure I understand your statement about the double underscore, can you clarify? Thanks.

      1. Thank you for your reply! By double underscore I mean your Name Generation Schema for variables like x is transformed to __x. You mentioned this is known to be reserved for c++ intern functions so it is not recommended to use. I think to use another prefix would be no Problem. But i remember other engines linke cryengine also do this, so I was a bit diappointed you do this either.

        1. It turns out to be surprisingly difficult (or at least, more difficult than we thought it would be) to avoid the reserved keywords in IL and C++, while also avoiding name conflicts with user script code. So we decided to try the leading underscore to isolate the generated code. So far we’ve built generated code with probably a half-dozen C++ compilers and we’ve been safe.

          The good news is that if we see a problem with a specific C++ compiler or toolchain, it is possible for il2cpp.exe to use a prefix, as you suggested. Until we run into that issue, we’ll likely keep the naming scheme as-is.