O que você está procurando?
Engine & platform

Uma introdução aos componentes internos do IL2CPP

JOSH PETERSON / UNITY TECHNOLOGIESSenior Software Engineer
May 6, 2015|10 Min
Uma introdução aos componentes internos do IL2CPP
Esta página da Web foi automaticamente traduzida para sua conveniência. Não podemos garantir a precisão ou a confiabilidade do conteúdo traduzido. Se tiver dúvidas sobre a precisão do conteúdo traduzido, consulte a versão oficial em inglês da página da Web.

Há quase um ano, começamos a falar sobre o futuro dos scripts no Unity. O novo backend de script IL2CPP prometia trazer uma máquina virtual de alto desempenho e altamente portátil para o Unity. Em janeiro, enviamos nossa primeira plataforma usando o IL2CPP, iOS de 64 bits. O lançamento do Unity 5 trouxe outra plataforma, o WebGL. Graças à contribuição de nossa enorme comunidade de usuários, enviamos muitas atualizações de versões de patches para o IL2CPP, aprimorando constantemente seu compilador e tempo de execução. Não temos planos de parar de aprimorar o IL2CPP, mas achamos que seria uma boa ideia dar um passo atrás e contar um pouco sobre como o IL2CPP funciona de dentro para fora. Nos próximos meses, planejamos escrever sobre os seguintes tópicos (e talvez outros) nesta série de postagens do IL2CPP Internals:

1. o básico - cadeia de ferramentas e argumentos da linha de comando (esta publicação)

2. Um tour pelo código gerado

3. Dicas de depuração para o código gerado

4. Chamadas de método (métodos normais, métodos virtuais, etc.)

5. Implementação de compartilhamento genérico

6. P/invoke wrappers para tipos e métodos

7. Integração do coletor de lixo

8. Teste de estruturas e uso

Para que esta série de postagens seja possível, discutiremos alguns detalhes sobre a implementação do IL2CPP que certamente mudarão no futuro. Esperamos que ainda possamos fornecer algumas informações úteis e interessantes.

O que é IL2CPP?

A tecnologia a que nos referimos como IL2CPP tem duas partes distintas.

  • Um compilador AOT (ahead-of-time)
  • Uma biblioteca de tempo de execução para dar suporte à máquina virtual

O compilador AOT traduz a linguagem intermediária (IL), a saída de baixo nível dos compiladores .NET, para o código-fonte C++. A biblioteca de tempo de execução fornece serviços e abstrações como um coletor de lixo, acesso independente de plataforma a threads e arquivos e implementações de chamadas internas (código nativo que modifica diretamente as estruturas de dados gerenciadas).

O compilador AOT

O compilador IL2CPP AOT tem o nome il2cpp.exe. No Windows, você pode encontrá-lo no diretório Editor\Data\il2cpp. No OSX, ele está no diretório Contents/Frameworks/il2cpp/build na instalação do Unity.

O utilitário il2cpp.exe é um executável gerenciado, escrito inteiramente em C#. Nós o compilamos com os compiladores .NET e Mono durante nosso desenvolvimento do IL2CPP. O utilitário il2cpp.exe aceita assemblies gerenciados compilados com o compilador Mono que acompanha o Unity e gera código C++ que passamos para um compilador C++ específico da plataforma.

Você pode pensar na cadeia de ferramentas IL2CPP da seguinte forma:

Cadeia de ferramentas il2cpp menor
A biblioteca de tempo de execução

A outra parte da tecnologia IL2CPP é uma biblioteca de tempo de execução para dar suporte à máquina virtual. Implementamos essa biblioteca usando quase inteiramente código C++ (ela tem um pouco de código assembly específico da plataforma, mas vamos manter isso entre nós dois). Chamamos a biblioteca de tempo de execução de libil2cpp, e ela é enviada como uma biblioteca estática vinculada ao executável do player. Um dos principais benefícios da tecnologia IL2CPP é essa biblioteca de tempo de execução simples e portátil.

Você pode encontrar algumas pistas sobre como o código libil2cpp está organizado observando os arquivos de cabeçalho do libil2cpp que enviamos com o Unity (você os encontrará no diretório Editor\Data\PlaybackEngines\webglsupport\BuildTools\Libraries\libil2cpp\include no Windows ou no diretório Contents/Frameworks/il2cpp/libil2cpp no OSX). Por exemplo, a interface entre o código C++ gerado pelo il2cpp.exe e o tempo de execução do libil2cpp está localizada no arquivo de cabeçalho codegen/il2cpp-codegen.h.

Uma parte importante do tempo de execução é o coletor de lixo. Estamos enviando o Unity 5 com libgc, o coletor de lixo Boehm-Demers-Weiser. No entanto, o libil2cpp foi projetado para permitir o uso de outros coletores de lixo. Por exemplo, estamos pesquisando uma integração do Microsoft GC, que teve seu código aberto como parte do CoreCLR. Teremos mais informações sobre isso em nossa postagem sobre a integração do coletor de lixo, mais adiante na série.

Como o il2cpp.exe é executado?

Vamos dar uma olhada em um exemplo. Usarei o Unity 5.0.1 no Windows e começarei com um projeto novo e vazio. Para que tenhamos pelo menos um script de usuário para converter, adicionarei esse componente MonoBehaviour simples ao objeto de jogo Main Camera:

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`.

Quando faço a compilação para a plataforma WebGL, posso usar o Process Explorer para ver a linha de comando do Unity usada para executar o il2cpp.exe:

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`.

Essa linha de comando é bem longa e horrível, portanto, vamos descompactá-la. Primeiro, o Unity está executando esse executável:

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`.

O próximo argumento na linha de comando é o próprio utilitário il2cpp.exe.

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`.

Os argumentos restantes da linha de comando são passados para o il2cpp.exe, não para o mono.exe. Vamos dar uma olhada neles. Primeiro, o Unity passa cinco sinalizadores para o il2cpp.exe:

  • --copy-level=None
  • Especifique que o il2cpp.exe não deve realizar cópias de arquivos especiais do código C++ gerado.
  • --enable-generic-sharing
  • Esse é um recurso de redução de tamanho de código e binário. O IL2CPP compartilhará a implementação de métodos genéricos quando for possível.
  • --enable-unity-event-support
  • Suporte especial para garantir que o código dos eventos do Unity, que são acessados por meio de reflexão, seja gerado corretamente.
  • --output-format=Compact
  • Gerar código C++ em um formato que requer menos caracteres para nomes de tipos e métodos. Esse código é difícil de depurar, pois os nomes no código IL não são preservados, mas geralmente compila mais rápido, pois há menos código para o compilador C++ analisar.
  • --extra-types.file="C:\Program Files\Unity\Editor\Data\il2cpp\il2cpp_default_extra_types.txt"
  • Use o arquivo padrão (e vazio) de tipos extras. Esse arquivo pode ser adicionado em um projeto Unity para permitir que o il2cpp.exe saiba quais tipos genéricos ou de matriz serão criados em tempo de execução, mas não estão presentes no código IL.

É importante observar que esses argumentos da linha de comando podem e serão alterados em versões posteriores. Ainda não chegamos a um ponto em que tenhamos um conjunto estável e compatível de argumentos de linha de comando para o il2cpp.exe. Por fim, temos uma lista de dois arquivos e um diretório na linha de comando:

  • "C:\Usuários\Josh Peterson\Documentos\IL2CPP Blog Example\Temp\StagingArea\Data\Managed\Assembly-CSharp.dll"
  • "C:\Usuários\Josh Peterson\Documentos\IL2CPP Blog Example\Temp\StagingArea\Data\Managed\UnityEngine.UI.dll"
  • "C:\Usuários\Josh Peterson\Documentos\IL2CPP Blog Example\Temp\StagingArea\Data\il2cppOutput"

O utilitário il2cpp.exe aceita uma lista de todos os assemblies IL que devem ser convertidos. Nesse caso, eles são o assembly que contém meu MonoBehaviour simples, Assembly-CSharp.dll, e o assembly da GUI, UnityEngine.UI.dll. Observe que há algumas montagens visivelmente ausentes aqui. Claramente, meu script faz referência ao UnityEngine.dll, que faz referência a pelo menos mscorlib.dll e talvez a outros assemblies. Onde eles estão? Na verdade, o il2cpp.exe resolve esses conjuntos internamente. Eles podem ser mencionados na linha de comando, mas não são necessários. O Unity só precisa mencionar explicitamente os assemblies raiz (aqueles que não são referenciados por nenhum outro assembly).

O último argumento na linha de comando do il2cpp.exe é o diretório em que os arquivos C++ de saída devem ser criados. Se estiver curioso, dê uma olhada nos arquivos gerados nesse diretório, pois eles serão o assunto da próxima postagem desta série. Antes de fazer isso, porém, talvez você queira escolher a opção "Development Player" nas configurações de compilação do WebGL. Isso removerá o argumento de linha de comando --output-format=Compact e lhe dará melhores nomes de tipos e métodos no código C++ gerado.

Tente alterar várias opções nas configurações do WebGL ou do iOS Player. Você poderá ver diferentes opções de linha de comando passadas para o il2cpp.exe para ativar diferentes etapas de geração de código. Por exemplo, alterar a configuração "Enable Exceptions" (Ativar exceções) nas configurações do WebGL Player para um valor de "Full" (Completo) adiciona os argumentos --emit-null-checks, --enable-stacktrace e --enable-array-bounds-check à linha de comando do il2cpp.exe.

O que o IL2CPP não faz?

Gostaria de destacar um dos desafios que não enfrentamos com o IL2CPP, e não poderíamos estar mais felizes por tê-lo ignorado. Não tentamos reescrever a biblioteca padrão do C# com o IL2CPP. Quando você cria um projeto Unity que usa o backend de script IL2CPP, todo o código da biblioteca padrão C# em mscorlib.dll, System.dll, etc. é exatamente o mesmo código usado para o backend de script Mono.

Contamos com o código da biblioteca padrão C# que já é bem conhecido pelos usuários e bem testado em projetos Unity. Portanto, quando investigamos um bug relacionado ao IL2CPP, podemos ter certeza de que o bug está no compilador AOT ou na biblioteca de tempo de execução, e em nenhum outro lugar.

Como desenvolvemos, testamos e enviamos o IL2CPP

Desde o lançamento público inicial do IL2CPP na versão 4.6.1p5 em janeiro, enviamos 6 lançamentos completos e 7 lançamentos de patches (nas versões 4.6 e 5.0 do Unity). Corrigimos mais de 100 bugs mencionados nas notas de versão.

Para que essa melhoria contínua ocorra, desenvolvemos internamente apenas uma versão do código do IL2CPP, que fica no limite do ramo do tronco do Unity usado para enviar versões alfa e beta. Pouco antes de cada lançamento, transferimos as alterações do IL2CPP para o ramo de lançamento específico, executamos nossos testes e verificamos se todos os erros que corrigimos foram corrigidos nessa versão. Nossas equipes de controle de qualidade e engenharia sustentada fizeram um trabalho incrível para tornar possível a entrega nesse ritmo. Isso significa que nossos usuários nunca ficam a mais de uma semana das últimas correções de bugs do IL2CPP.

Nossa comunidade de usuários provou ser inestimável ao enviar muitos relatórios de bugs de alta qualidade. Agradecemos todos os comentários de nossos usuários para ajudar a melhorar continuamente o IL2CPP e esperamos receber mais comentários.

A equipe de desenvolvimento que trabalha no IL2CPP tem uma forte mentalidade de testar primeiro. Geralmente, empregamos práticas de Test Driven Design e raramente mesclamos uma solicitação pull sem bons testes. Essa estratégia funciona bem para uma tecnologia como a IL2CPP, em que temos entradas e saídas claras. Isso significa que a grande maioria dos bugs que vemos não é um comportamento inesperado, mas sim casos inesperados (por exemplo, é possível usar um IntPtr de 64 bits como um índice de matriz de 32 bits, fazendo com que o clang falhe com um erro de compilador C++, e o código real realmente faz isso!) Essa diferença nos permite corrigir bugs rapidamente com um alto grau de confiança.

Com a ajuda da nossa comunidade, estamos trabalhando duro para tornar o IL2CPP o mais estável e rápido possível. A propósito, se isso o entusiasma, estamos contratando (só para dizer).

Mais informações em breve

Temo que eu tenha passado muito tempo aqui provocando futuras publicações no blog. Temos muito a dizer, e simplesmente não caberá tudo em uma única postagem. Na próxima vez, analisaremos o código gerado pelo il2cpp.exe para ver como o seu projeto realmente se parece com o compilador C++.