Compilação CIT JIT e .NET

Eu me tornei um pouco confuso sobre os detalhes de como o compilador JIT funciona. Eu sei que o C # compila até o IL. A primeira vez que é executado é JIT. Isso implica que ele seja traduzido em código nativo? O tempo de execução do .NET (como uma máquina virtual?) Interage com o código JIT? Eu sei que isso é ingênuo, mas eu realmente me confundi. Minha impressão sempre foi de que os assemblies não são interpretados pelo .NET Runtime, mas eu não entendo os detalhes da interação.

    Sim, o código JIT’ing IL envolve a tradução do IL em instruções de máquina nativas.

    Sim, o tempo de execução do .NET interage com o código de máquina nativo do JIT, no sentido de que o tempo de execução possui os blocos de memory ocupados pelo código de máquina nativo, as chamadas de tempo de execução no código de máquina nativo, etc.

    Você está correto que o tempo de execução do .NET não interpreta o código IL em seus assemblies.

    O que acontece é quando a execução atinge uma function ou um bloco de código (como uma cláusula else de um bloco if) que ainda não foi compilado pelo JIT no código de máquina nativo, o JIT é invocado para compilar esse bloco de IL no código de máquina nativo . Quando isso é feito, a execução do programa insere o código de máquina recém-emitido para executar sua lógica de programa. Se ao executar a execução de código de máquina nativa atingir uma chamada de function para uma function que ainda não tenha sido compilada para código de máquina, o JIT é chamado para compilar essa function “just in time”. E assim por diante.

    O JIT’r não necessariamente compila toda a lógica de um corpo de function em código de máquina de uma só vez. Se a function tiver instruções if, os blocos de instruções das cláusulas if ou else poderão não ser compilados pelo JIT até que a execução realmente passe por esse bloco. Os caminhos de código que não foram executados permanecem no formulário IL até que sejam executados.

    O código de máquina nativo compilado é mantido na memory para que possa ser usado novamente na próxima vez que a seção de código for executada. Na segunda vez que você chamar uma function, ela será executada mais rapidamente do que na primeira vez em que você a chamar, porque nenhuma etapa do JIT será necessária na segunda vez.

    Na área de trabalho .NET, o código de máquina nativo é mantido na memory pelo tempo de vida do appdomain. No .NET CF, o código de máquina nativo pode ser descartado se o aplicativo estiver com pouca memory. Será JIT compilado novamente a partir do código IL original na próxima vez que a execução passar por esse código.

    O código é “compilado” no Microsoft Intermediate Language, que é semelhante ao formato de assembly.

    Quando você clica duas vezes em um executável, o Windows carrega mscoree.dll que, em seguida, configura o ambiente CLR e inicia o código do programa. O compilador JIT começa a ler o código MSIL em seu programa e compila dinamicamente o código em instruções x86, que a CPU pode executar.

    O .NET usa um idioma intermediário chamado MSIL, às vezes abreviado como IL. O compilador lê seu código-fonte e produz o MSIL. Quando você executa o programa, o compilador .NET Just In Time (JIT) lê seu código MSIL e produz um aplicativo executável na memory. Você não verá nada disso acontecer, mas é uma boa ideia saber o que está acontecendo nos bastidores.

    Descreverei a compilation do código IL em instruções nativas da CPU pelo exemplo abaixo.

     public class Example { static void Main() { Console.WriteLine("Hey IL!!!"); } } 

    Primeiramente, o CLR conhece todos os detalhes sobre o tipo e o método que está sendo chamado desse tipo, devido a metadados.

    Quando o CLR começa a executar o IL na instrução da CPU nativa, esse tempo CLR aloca estruturas de dados internas para cada tipo referenciado pelo código do Main.

    No nosso caso, temos apenas um tipo de console, portanto, o CLR alocará uma estrutura de dados interna por meio dessa estrutura interna, e gerenciaremos o access aos tipos referenciados

    dentro dessa estrutura de dados, o CLR possui inputs sobre todos os methods definidos por esse tipo. Cada input contém o endereço onde a implementação do método pode ser encontrada.

    Ao inicializar esta estrutura, o CLR define cada input em FUNÇÃO não documentada contida dentro do próprio CLR. E como você pode imaginar, esta FUNÇÃO é o que chamamos de Compilador JIT.

    No geral, você poderia considerar o JIT Compiler como uma function CLR que compila o IL nas instruções da CPU nativa. Deixe-me mostrar em detalhes como esse processo será em nosso exemplo.

    1. Quando Main faz sua primeira chamada para WriteLine, a function JITCompiler é chamada.

    A function Compiler 2.JIT sabe qual método está sendo chamado e que tipo define este método.

    3.Em seguida, o Jit Compiler pesquisa o conjunto onde definiu esse tipo e obtém o código IL para o método definido por esse tipo em nosso caso, código IL do método WriteLine.

    O compilador 4.JIT aloca o bloco de memory DINÂMICO , depois que o JIT verifica e compila o código IL no código da CPU nativo e salva o código da CPU naquele bloco de memory.

    5.Em seguida, o compilador JIT volta para a input da estrutura de dados interna e substitui o endereço (que faz referência principalmente à implementação do código IL de WriteLine) com o novo bloco de memory criado dinamicamente que contém instruções da CPU nativas do WriteLine.

    6.Finally, a function JIT Compiler salta para o código no bloco de memory. Este código é a implementação do método WriteLine.

    7. Após a implementação do WriteLine, o código retorna ao código principal, que continua a execução como normal.