Kamil Kiełbasa
Kamil KiełbasaSoftware Engineer / Blogger / Trainer @ Exeno

Czym są CLR, JIT i IL w .NET

Dowiedz się, czym są Common Language Runtime (CLR), mechanizm Just-in-time compilation (JIT) oraz IL w .NET.
4.02.20215 min
Czym są CLR, JIT i IL w .NET

Zapytałem paru kolegów po fachu, o czym chcieliby poczytać na blogach IT. Jednym z tematów, który się przewinął, był Common Language Runtime, czyli w skrócie CLR. Wychodząc na przeciw oczekiwaniom, przedstawię trochę wiedzy z samym bebechów .NET-a.

Platforma .NET

Zacznijmy, jak zawsze, od podstaw. Na początku była pustka, potem wielki wybuch… ale do rzeczy. Czym w ogóle są CLR, IL, JIT? Jeżeli wpadła Ci w ręce książka dotycząca .NET (lub inne profesjonalne źródło wiedzy), pewnie natknąłeś/ęłaś się na poniższy diagram.


Platforma .NET wspiera pisanie programów w wielu językach. Oczywiście C# jest najpopularniejszy, ale musimy pamiętać o wsparciu F# i VB.NET. Za dawnych dziejów wspierane były również inne twory Microsoftu, takie jak np. JScript .NET aczkolwiek nie mam pojęcia, co się dzieje aktualnie ze wsparciem, jak i samym językiem. Jeżeli wiesz coś na ten temat, daj mi znać w komentarzu pod artykułem.

O wielojęzyczności platformy mogą też świadczyć opisy szablonów projektów dostarczonych przy instalacji SDK. Wystarczy wywołać komendę dotnet new -l , a na konsoli pokażą się wszystkie dostępne szablony. Wśród nich będzie kolumna dotycząca tego, jakie języki programowania wspiera dany szablon (z oznaczeniem języka domyślnego).

Na początku .NET Core'a, VB.NET nie dostał wsparcia dla nowo tworzącej się platformy, jednak wraz z wyjściem .NET 5.0 i on zyskał kolejną szanse na przebicie się do szerszego groma programistów.

Język pośredni

Skoro platforma .NET wspiera wiele języków, to w jaki sposób następuje kompilacja do kodu natywnego?

Wszystko dzieje się w kilku krokach. Omówimy je na przykładzie wyżej zamieszczonego diagramu. Otóż pierwszym krokiem, kiedy zdecydujemy się już nacisnąć przycisk Build w naszym IDE, jest kompilacja kodu źródłowego do tzw. kodu pośredniego (Common Intermediate Language). Inne nazwy, jakie możesz spotkać, to Microsoft Intermediate Language (MSIL) oraz Intermediate Language (IL). Możesz zadać sobie pytanie – czy zjedlibyśmy hotdoga ze stacji? No i ważniejsze – dlaczego nie następuje kompilacja bezpośrednio do kodu maszynowego? Otóż, kod maszynowy jest dość ciężki w utrzymaniu, różni się w zależności od architektury komputera, a nawet posiadanego procesora. Chcąc uniknąć możliwych problemów, powstaje kod, który da się odczytać samemu, może być uruchomiony na każdym systemie, który ma wsparcie dla Common Language Infrastructure (CLI).

Jeżeli zastanawiasz się, jak wygląda składnia CLI, to spieszę z przykładem. Dzięki możliwościom SharpLab można bez problemu na stronie internetowej zobaczyć, jaki zostanie wygenerowany kod CIL, a nawet Assembler z kodu C#. Przy pomocy nowej składni języka C# 9 możemy sprawdzić, co zostanie wygenerowane z najprostszego przykładu, jaki kiedykolwiek powstał

System.Console.WriteLine("Hello World!");


Tak, wraz z nową wersją tego języka tylko tyle potrzebujemy, aby napisać Hello World. Ten przykład skutecznie obrazuje, ile rzeczy robi za nas kompilator. Wygenerowany kod CIL będzie dużo bardziej rozbudowany.

.class private auto ansi ''
{
} // end of class 
.class private auto ansi abstract sealed beforefieldinit $Program
    extends [System.Private.CoreLib]System.Object
{
    .custom instance void [System.Private.CoreLib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
        01 00 00 00
    )
    // Methods
    .method private hidebysig static 
        void $Main (
            string[] args
        ) cil managed 
    {
        // Method begins at RVA 0x2050
        // Code size 11 (0xb)
        .maxstack 8
        .entrypoint
        IL_0000: ldstr "Hello World!"
        IL_0005: call void [System.Console]System.Console::WriteLine(string)
        IL_000a: ret
    } // end of method $Program::$Main
} // end of class $Program


Po samej długości kodu widać, że sporo się tutaj zadziało. Przede wszystkim kompilator od razu zinterpretował top level statement i za nas stworzył główną klasę $Program. Z racji, że to .NET, każda klasa musi dziedziczyć po System.Object co jest pokazane w linii 6.

Następnie mamy stworzony przez kompilator bezparametrowy konstruktor i wygenerowaną funkcję statyczną $Main. Dopiero w jej wnętrzu możemy zobaczyć, jak wygląda po kompilacji. W 22 linii możemy zauważyć wywołanie operacji ldstr, która wypchnie referencje do wcześniej zdefiniowanego stringa. Co ciekawe, już na tym etapie zobaczymy pewne optymalizacje wykonane przez kompilator. Dla przykładu, jeżeli spróbujemy wypisać stringa złożonego z innych stringów:

const string hello = "hello";
const string world = "world";
System.Console.WriteLine(hello + world + "!");


Otrzymamy ten sam kod CIL. Kompilator wykryje “rozwiązanie” tego wyrażenia już na etapie kompilacji i wstawi wynik w odpowiednie miejsce.

Ostatnią rzeczą, którą chciałbym opisać, jest kompilacja w trybie debug i w trybie release. Wszystkie powyższe przykłady były skompilowane w trybie release. Jeżeli skompilujemy ten sam kod w trybie debug, otrzymamy dodatkowo jedną małą rzecz:

IL_0000: ldstr "Hello World!"
IL_0005: call void [System.Console]System.Console::WriteLine(string)
IL_000a: nop
IL_000b: ret


Mistyczna instrukcja nop, jak sama dokumentacja wskazuje, jest to instrukcja, która nie robi nic ważnego. Jedynie może zmarnować cykle procesora.

CLR (Common Language Runtime) i JIT

W wolnym tłumaczeniu CLR oznacza Środowisko Uruchomieniowe Wspólnego Języka. Nazwa mówi chyba wszystko. Tutaj jest pobierany kod w języku pośrednim, następnie jest kompilowany i uruchamiany jako kod maszynowy. Kompilacja zachodzi przy wykorzystaniu mechanizmu JIT (Just in time compilation). Dzięki temu kod jest kompilowany dopiero przy okazji pierwszego dostępu do niego lub pierwszego wywołania danej funkcji. CLR jest odpowiedzialny również za wiele innych rzeczy, takich jak

  • Zarządzanie pamięcią
  • Czyszczenie pamięci (Garbage Collector)
  • Definicja podstawowych typów danych
  • Format Metadanych
  • Funkcje języka takie jak dziedziczenie, interfejsy, przeciążenia
  • … i wiele wiele innych rzeczy


Kusi Cię opcja dodania własnych języków jako możliwy do uruchomienia na CLR? Nic nie stoi na przeszkodzie. CLR będzie w stanie uruchomić każdy język pośredni, który implementuje specyfikację ECMA dotyczącą CLI (Common Language Infrastructure i tak, programiści mają za dużo podobnych skrótów). Przykładem jest właśnie kompilator C#.

W ten sposób omówiliśmy cały diagram. Oczywiście jest jeszcze ogrom wiedzy na temat CLR, w które można by się zgłębić. Osobiście polecam prezentacje Adama Furmanka dostępne na Youtube. Znacznie szerzej omawia zagadnienie, dość szczegółowo, jeśli interesuje Cię tematyka, to naprawdę warto.

Jak zwykle, mam nadzieje, że artykuł się podobał.
Do Następnego!

<p>Loading...</p>