Windows build support il2cpp что это

Введение в IL2CPP

Unity продолжают совершенствовать технологию IL2CPP, а мы публикуем перевод статьи о том, как она работает.

Windows build support il2cpp что это. Смотреть фото Windows build support il2cpp что это. Смотреть картинку Windows build support il2cpp что это. Картинка про Windows build support il2cpp что это. Фото Windows build support il2cpp что это

Около года назад мы писали о будущем скриптинга в Unity. Новая технология скриптинга IL2CPP должна была обеспечить движок высокопроизводительной портативной виртуальной машиной. В январе мы выпустили нашу первую платформу на 64-битной iOS, использующую IL2CPP. С выходом Unity 5 появилась еще одна платформа – WebGL. При поддержке огромного сообщества пользователей мы выпустили множество патчей и обновлений для IL2CPP, постепенно оптимизируя компилятор и повышая быстродействие среды.

А пока мы продолжаем совершенствовать технологию IL2CPP, было бы неплохо рассказать о том, как она работает. Мы планируем написать серию статей, посвященных таким темам:

1. Основы – набор инструментов и аргументы командной строки (эта статья).
2. Экскурсия по генерируемому коду.
3. Советы по отладке генерируемого кода.
4. Вызовы методов: обычные методы, виртуальные методы и другие.
5. Реализация общего обмена.
6. Обёртки P/Invoke для типов и методов.
7. Интеграция сборщика мусора.
8. Тестирование и применение фреймворков.

В этих статьях мы обсудим некоторые особенности реализации IL2CPP. Надеюсь, эта информация вам пригодится.

Технология IL2CPP состоит из двух частей:

• компилятор Ahead-of-time (AOT);
• исполняемая библиотека для поддержки виртуальной машины.

Эта утилита принимает управляемые сборки, скомпилированные Mono-компилятором, поставляемым с Unity, и генерирует код C++, который мы передаем в компилятор C++ для конкретной платформы.
Инструментарий IL2CPP можно схематически представить так:

Windows build support il2cpp что это. Смотреть фото Windows build support il2cpp что это. Смотреть картинку Windows build support il2cpp что это. Картинка про Windows build support il2cpp что это. Фото Windows build support il2cpp что это

Вторая часть технологии IL2CPP – это исполняемая библиотека для поддержки виртуальной машины. Мы написали эту библиотеку почти полностью на C++ (в ней есть немного платформенного кода, но пусть это останется между нами) и назвали ее libil2cpp. Она поставляется в виде статической библиотеки, связанной с исполняемым файлом плеера, а ее простота и портативность являются одними из ключевых преимуществ технологии IL2CPP.

Вы можете получить более четкое представление об организации кода libil2cpp, глядя на файлы заголовков, поставляемых с Unity (их можно найти в директории Editor\Data\PlaybackEngines\webglsupport\BuildTools\Libraries\libil2cpp\include в Windows, или в директории Contents/Frameworks/il2cpp/libil2cpp – в OS X). Например, интерфейс между кодом C++, генерируемым il2cpp.exe, и средой libil2cpp находится в файле заголовка codegen/il2cpp-codegen.h.

Один из ключевых элементов среды – сборщик мусора. В комплект Unity 5 входит libgc, сборщик мусора Boehm-Demers-Weiser. Однако libil2cpp поддерживает и другие сборщики. Например, мы рассматриваем возможность интеграции с Microsoft GC – сборщиком с открытым исходным кодом в комплекте CoreCLR. В одной из следующих статей мы расскажем о нём подробнее.

Как выполняется il2cpp.exe?

Давайте рассмотрим на примере. Для этого я создам новый пустой проект в Unity 5.0.1 на Windows. Чтобы у нас был хотя бы один пользовательский скрипт, я добавлю к главной камере простой компонент MonoBehaviour:

Windows build support il2cpp что это. Смотреть фото Windows build support il2cpp что это. Смотреть картинку Windows build support il2cpp что это. Картинка про Windows build support il2cpp что это. Фото Windows build support il2cpp что это

При сборке для платформы WebGL я могу использовать Process Explorer, чтобы увидеть команду для запуска il2cpp.exe:

Это достаточно длинная строка, поэтому давайте разберем ее по частям. Сперва Unity запускает этот исполняемый файл:

Следующий аргумент – сама утилита il2cpp.exe.

Остальные аргументы передаются il2cpp.exe, а не mono.exe. Давайте разберем и их. Во-первых, Unity передает 5 флагов il2cpp.exe:

Стоит отметить, что этот набор аргументов командной строки для il2cpp.exe всё еще нестабилен и, скорее всего, изменится в последующих версиях.

Итак, в командной строке указаны 2 файла и 1 директория:

• «C:\Users\Josh Peterson\Documents\IL2CPP Blog Example\Temp\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 и UnityEngine.UI.dll. Обратите внимание, что здесь явно отсутствует несколько сборок. Очевидно, что мой скрипт ссылается на UnityEngine.dll, а значит нужен хотя бы mscorlib.dll, и, возможно, другие сборки. Но где они? Дело в том, что разрешение сборок происходит внутри il2cpp.exe. Их можно упомянуть в командной строке, но это необязательно. Получается, Unity нужно всего лишь указать корневые сборки (на которые не ссылаются другие сборки).

Чего не делает IL2CPP?

Хотелось бы обратить внимание на одну сложность, которую мы решили обойти стороной и таким образом избежали многих проблем. Мы не пытались переписать стандартную библиотеку C# под IL2CPP. При создании проекта Unity с использованием IL2CPP весь код стандартных библиотек C# (mscorlib.dll, System.dll и т. д.) является точной копией кода Mono-скриптов.

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

Разработка, тестирование и выпуск IL2CPP

С момента первого официального релиза IL2CPP версии 4.6.1p5 в январе мы выпустили 6 полноценных обновлений и 7 патчей (для Unity 4.6 и 5.0), в которых исправили более ста ошибок.

Чтобы обеспечить непрерывную оптимизацию, мы разрабатываем обновления на базе одной версии кода. Перед каждым релизом мы передаем все изменения в конкретную релизную ветку, проводим тестирование и проверяем, все ли ошибки были успешно исправлены. Наши команды QA и Sustained Engineering прикладывают максимум усилий, чтобы разработка велась в таком темпе, и ошибки исправлялись каждую неделю.

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

Для разработчиков IL2CPP тестирование стоит на первом месте. Мы часто берем за основу технику разработки через тестирование и крайне редко отправляем запросы на включение, если код не был полностью протестирован. Эта стратегия хорошо работает для таких технологий, как IL2CPP, где есть четкие входы и выходы. Это означает, что подавляющее большинство ошибок, с которыми мы сталкиваемся, являются скорее частными случаями, чем неожиданным поведением системы (например, при использовании 64-битного IntPtr в качестве 32-битного индекса массива может произойти вылет с ошибкой компилятора С++). Таким образом, мы можем быстро определять и исправлять любые баги.
При поддержке нашего сообщества мы всё время делаем IL2CPP стабильнее и производительнее.

Источник

IL2CPP Overview

This type of compilation, in which Unity compiles code specifically for a target platform when it builds the native binary, is called ahead-of-time (AOT) compilation. The Mono backend compiles code at runtime, with a technique called just-in-time compilation (JIT).

IL2CPP can improve performance across a variety of platforms, but the need to include machine code in built applications increases both the build time and the size of the final built application. For more information, see How IL2CPP works and the blog series An introduction to IL2CPP internals.

IL2CPP supports the debugging of managed code in the same way as the Mono scripting backend. For more information, see Debugging C# code in Unity.

Building a project using IL2CPP

To build a project with IL2CPP, you need to have the backend installed in your Unity installation. You can select IL2CPP as an optional module when you first install a version of Unity, or add IL2CPP support to an existing installation through the Unity Hub. For more information, see Installing the Unity Hub and Adding modules to the Unity Editor.

You can change the scripting backend Unity uses to build your application in one of two ways:

Through the Player Settings Settings that let you set various player-specific options for the final game built by Unity. More info
See in Glossary
menu in the Editor. Perform the following steps to change the scripting backend through the Player Settings menu:

You can also open the Player Settings menu from inside the Build Settings menu; go to File > Build Settings and click on the Player Settings button.

Through the Editor scripting API. Use the PlayerSettings.SetScriptingBackend property to change the scripting backend that Unity uses.

Windows build support il2cpp что это. Смотреть фото Windows build support il2cpp что это. Смотреть картинку Windows build support il2cpp что это. Картинка про Windows build support il2cpp что это. Фото Windows build support il2cpp что этоThe Configuration section of the Player settings

How IL2CPP works

When you start a build using IL2CPP, Unity automatically performs the following steps:

IL2CPP enables Unity to pre-compile code for specific platforms. The binary file Unity produces at the end of this process already contains necessary machine code for the target platform, while Mono has to compile this machine code at runtime during execution. AOT compilation does increase build time, but it also improves compatibility with the target platform and can improve performance.

Both scripting backends require a new build for each platform you want to target. For example, to support both the Android and iOS Apple’s mobile operating system. More info
See in Glossary platforms, you need to build your application twice and produce two binary files.

The assembly stripping stage helps reduce the final binary size. Unity removes any bytecode that the final built application doesn’t use.

Optimizing IL2CPP build times

Project build times when using IL2CPP can be significantly longer than when using Mono. However, you can do several things to reduce build time.

Exclude your project from anti-malware software scans

You can exclude your Unity project folder and target build folders from anti-malware software scans before you build your project.

Store your project and target build folder on a solid-state drive (SSD)

Solid-state drives (SSDs) have faster read/write speeds than traditional hard disk drives (HDD). Converting IL code to C++ and compiling it involves a large number of read/write operations, so a faster storage device speeds up this process.

Enabling runtime checks using Il2CppSetOption

When you use the IL2CPP scripting backend, you can control how il2cpp.exe generates C++ code. You can use the Il2CppSetOption attribute to enable or disable the following runtime checks:

Property:Description:Default:
Null checksIf this option is enabled, the C++ code that IL2CPP generates contains null checks and throws managed NullReferenceException exceptions as necessary. If this option is disabled, IL2CPP doesn’t emit the null checks into the generated C++ code. For some projects, disabling this option might improve runtime performance.

When this setting is disabled, Unity doesn’t prevent attempts to access null values in the generated code, which might lead to incorrect behavior. Your application is likely to crash soon after it dereferences the null value. Unity recommends that you don’t disable this option.

Enabled
Array bounds checksIf this option is enabled, the C++ code that IL2CPP generates contains array bounds checks and throws managed IndexOutOfRangeException exceptions as necessary. If this option is disabled, IL2CPP doesn’t emit the array bounds checks into the generated C++ code.

For some projects, disabling this option might improve runtime performance. However, when this option is disabled, Unity doesn’t prevent attempts to access an array with invalid indices in the generated code, which might lead to incorrect behavior, including reading from or writing to arbitrary memory locations. In most cases, these memory accesses occur without any immediate side effects, and can corrupt the state of the application with no obvious warning signs. This can make debugging these errors extremely difficult. Unity recommends that you keep this option enabled.

Enabled
Divide by zero checksIf this option is enabled, C++ code generated by IL2CPP contains divide by zero checks for integer division and throw managed DivideByZeroException exceptions as necessary. If this option is disabled, IL2CPP doesn’t emit the divide by zero checks on integer division into the generated C++ code.

These checks have an impact on performance at runtime. You should only enable this option if you need to run divide by zero checks; otherwise, leave it disabled.

Disabled

To use the Il2CppSetOption attribute:

The below example describes how to use the Il2CppSetOption attribute:

You can apply Il2CppSetOption attribute to types, methods, and properties. Unity uses the attribute from the most local scope.

Источник

IL2CPP: экскурсия по генерируемому коду

Windows build support il2cpp что это. Смотреть фото Windows build support il2cpp что это. Смотреть картинку Windows build support il2cpp что это. Картинка про Windows build support il2cpp что это. Фото Windows build support il2cpp что это

Для этого мы будем использовать очень специфический код, который наверняка изменится в следующих версиях Unity. Но основные принципы останутся неизменными.

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

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

Обзор генерируемого кода

После завершения сборки сгенерированный код 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 генерирует 2 файла заголовков: для определения типа и объявления методов для этого типа. Например, давайте посмотрим на содержимое преобразованного типа UnityEngine.Vector3. Файл заголовка для этого типа называется UnityEngine_UnityEngine_Vector3.h. Имя создается на основе имени сборки (UnityEngine.dll), пространства имен и имени типа. Код выглядит следующим образом:

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

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

Обратите внимание на комментарий, в котором указан управляемый метод, представляющий исходное объявление. Это может пригодиться для поиска файлов на выходе по имени управляемого метода в данном формате, особенно для методов с общими именами, такими как ToString.
У методов, преобразованных il2cpp.exe, есть несколько интересных особенностей:

• Они не являются функциями-членами в C++, а представляют собой свободные функции с указателем this в качестве первого аргумента. Для первого аргумента статических функций в управляемом коде IL2CPP всегда передает значение NULL. Объявляя методы с указателем this в качестве первого аргумента, мы упрощаем генерацию кода в il2cpp.exe и вызов методов через другие методы (например делегаты) для генерируемого кода.

• Каждый метод имеет дополнительный аргумент типа MethodInfo*, содержащий метаданные о методе, которые могут использоваться, например, для вызова виртуального метода. Mono использует специфичные для платформы транспорты, чтобы передать эти метаданные. Но в случае IL2CPP мы решили не использовать их, чтобы улучшить переносимость.
• Все методы объявлены через extern «C», чтобы il2cpp.exe могла при необходимости обмануть компилятор C++ и рассматривать все методы, как если бы они имели один тип.

• Имена типов содержат суффикс «_t», имена методов — суффикс «_m». Конфликты имен решаются добавлением уникального номера для каждого имени. В случае любых изменений в коде пользовательского скрипта эти цифры тоже меняются, поэтому на них не стоит рассчитывать при переходе на новую сборку.

Первые 2 пункта подразумевают, что каждый метод имеет по крайней мере 2 параметра: указатель this и указатель MethodInfo. Добавляют ли эти параметры лишние затраты ресурсов? Да, добавляют, но это не влияет на производительность, как может показаться на первый взгляд. По крайней мере, так говорят результаты профилирования.

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

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

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

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

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

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

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

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

Il2cpp.exe добавляет 3 проверки, отсутствующие в коде IL:

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

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

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

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

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

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

Возникает интересный вопрос: что здесь делают метки и операторы goto? Эти конструкции необязательно использовать в структурном программировании. Дело в том, что в языке IL не используются принципы структурного программирования, такие как циклы и условные операторы. Это низкоуровневый язык, поэтому il2cpp.exe придерживается низкоуровневых концепций в генерируемом коде.

В качестве примера рассмотрим цикл for в методе HelloWorld_Start_m3:

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

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

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

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

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

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

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

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *