C# JIT compiling and .NET

I've become a bit confused about the details of how the JIT compiler works. I know that C# compiles down to IL. The first time it is run it is JIT'd. Does this involve it getting translated into native code? Is the .NET runtime (as a Virtual Machine?) interact with the JIT'd code? I know this is naive, but I've really confused myself. My impression has always been that the assemblies are not interpreted by the .NET Runtime but I don't understand the details of the interaction.


Solution 1:

Yes, JIT'ing IL code involves translating the IL into native machine instructions.

Yes, the .NET runtime interacts with the JIT'ed native machine code, in the sense that the runtime owns the memory blocks occupied by the native machine code, the runtime calls into the native machine code, etc.

You are correct that the .NET runtime does not interpret the IL code in your assemblies.

What happens is when execution reaches a function or code block (like, an else clause of an if block) that has not yet been JIT compiled into native machine code, the JIT'r is invoked to compile that block of IL into native machine code. When that's done, program execution enters the freshly emitted machine code to execute it's program logic. If while executing that native machine code execution reaches a function call to a function that has not yet been compiled to machine code, the JIT'r is invoked to compile that function "just in time". And so on.

The JIT'r doesn't necessarily compile all the logic of a function body into machine code at once. If the function has if statements, the statement blocks of the if or else clauses may not be JIT compiled until execution actually passes through that block. Code paths that have not executed remain in IL form until they do execute.

The compiled native machine code is kept in memory so that it can be used again the next time that section of code executes. The second time you call a function it will run faster than the first time you call it because no JIT step is necessary the second time around.

In desktop .NET, the native machine code is kept in memory for the lifetime of the appdomain. In .NET CF, the native machine code may be thrown away if the application is running low on memory. It will be JIT compiled again from the original IL code the next time execution passes through that code.

Solution 2:

Code is "compiled" into the Microsoft Intermediate Language, which is similar to assembly format.

When you double-click an executable, Windows loads mscoree.dll which then sets up the CLR environment and starts your program's code. The JIT compiler starts reading the MSIL code in your program and dynamically compiles the code into x86 instructions, which the CPU can execute.

Solution 3:

I will describe compilling IL code into native CPU instructions via example below.

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

Primarily CLR knows every details about type and what method being called from that type this is due to metadata .

When CLR starting to execute IL into native CPU instruction, that time CLR allocate internal data structures for every type referenced by Main's code.

In our case we have only one type Console, thus CLR will allocate one internal data structure. Via that internal structure, we will manage access to the referenced types.

Inside that data structure, CLR has entries about all methods defined by that type. Each entry holds the address where the method’s implementation can be found.

When initializing this structure, CLR sets each entry in undocumented FUNCTION contained inside in CLR itself. And as you can guess, this FUNCTION is what we call JIT Compiler.

Overall you could consider JIT Compiler as a CLR function, which compilling IL into native CPU instructions. Let me show you in details how this process will be in our example.

1.When Main's make his first call to WriteLine, the JITCompiler function is called.

2.JIT Compiler function knows what method is being called and what type defines this method.

3.Then Jit Compiler searches the assembly where defined that type and get IL code for the method defined by that type in our case IL code of WriteLine method.

4.JIT compiler allocate DYNAMIC memory block, after that JIT verify and compile IL code into native CPU code and save that CPU code in that memory block.

5.Then JIT compiler goes back to the internal data structure entry, and replaces the address(which primarily reference to the IL code implementation of WriteLine) with address new dynamically created memory block, which contain native CPU instructions of WriteLine.

6.Finally, the JIT Compiler function jumps to the code in the memory block and execute native code of writeline method.

7.After execution of the WriteLine, code returns to the Mains'code which continues execution as normal.

Solution 4:

.NET uses an intermediate language called MSIL, sometimes abbreviated as IL. The compiler reads your source code and produces MSIL. When you run the program, the .NET Just In Time (JIT) compiler reads your MSIL code and produces an executable application in memory. You won't see any of this happen, but it's a good idea to know what's going on behind the scenes.