Compilation process của 1 chương trình .NET

·

6 min read

Keyword cần chú ý

runtime(n) là execution environment mà quản lý và thực thi “đoạn code đã được compile”. .NET framework sử dụng Common Language Runtime (CLR), .NET Core/6+ sử dụng CoreCLR
managed code(n) những đoạn code mà “việc thực thi chúng” được quản lý bởi runtime

Tổng quan về compilation process

  1. Lập trình viên viết 1 đoạn chương trình C#

  2. C# compiler kiểm tra cú pháp và phân tích source code

  3. Microsoft Intermediate Languages (MSIL) là kết quả của quá trình (2)

    • Nếu source code chứa hàm main() → compile thành file .exe

    • Nếu source code không chứa hàm main() → compile thành file .dll

  4. Common Language Runtime (CLR) được khởi tạo bên trong 1 process (tiến trình) và thực hiện chạy entry-point-method (trong trường hợp này là hàm Main)

  5. MSIL được chuyển thành native code bởi JIT compiler (Just-In-Time compiler)

CLR là gì

Theo định nghĩa của Microsoft

💡
Common language runtime (CLR) is an run-time environment that runs the code and provides services that make the development process easier.

Hãy thử xem 1 ví dụ về cách mà CLR hoạt động

💡
Ta viết 1 đoạn code, compile nó với C# compiler → cho ra kết quả là những file được viết bằng MSIL (.exe hoặc .dll). Bước tiếp theo CLR sẽ chịu trách nhiệm compile các file .exe hoặc .dll này thành các tập lệnh CPU (CPU instructions) và tạo ra môi trường (environment) để chạy các tập lệnh này

Như vậy, ta có thể viết 1 chương trình với bất kì ngôn ngữ lập trình nào, miễn là compiler (C++, C#, F#,..) ta dùng cho chúng nhắm tới runtime ta đang dùng. Ví dụ, VBC (VB Complier) và CSC (CSharp Compiler) đều sẽ compile VB.NET và C#.NET thành MSIL và chạy được với CLR (Common Language Runtime)

Compile source code

Ở bước (2), C# compiler sẽ dịch source code thành MSIL - là 1 bộ tập lệnh độc lập với CPU (CPU-independent set of instructions) mà có thể được chuyển thành native code 1 cách hiệu quả. Kết quả là 1 managed module - standard 32-bit Windows portable executable file hoặc 64-bit Windows portable executable file mà cần runtime để thực thi

Có gì bên trong managed module?

Ngoài phần managed code, 1 file PE sẽ có thêm phần metadata table - chứa thông tin về những thứ được định nghĩa trong module

Nếu coi PE giống như 1 cuốn sách công thức nấu ăn thì IL code sẽ là phần hướng dẫn nấu từng món và Metadata là danh sách nguyên liệu và thông tin của từng nguyên liệu (giá trị dinh dưỡng, khối lượng,…)

PE header:

  • Nếu header sử dụng PE32 format, file PE này có thể chạy trên phiên bản Windows 32-bit hoặc 64-bit.

  • Nếu header sử dụng PE32+ format, file PE này yêu cầu phiên bản Windows 64-bit để chạy. This header also indicates the type of file: , and contains a timestamp indicating when the file was built.

  • Header chứa 1 số thông tin như: loại file (GUI - Graphical User Interface, CUI - Console User Interface, or DLL - Dynamic Link Lybrary), ngày tạo,..

CLR header:

  • Cho biết thông tin như: phiên bản CLR cần dùng, entry point method (là hàm Main), vị trí/kích thước của module metadata,…

Metadata table

  • Describes the types in your code, including the definition of each type, the signatures of each type's members, the members that your code references, and other data that the runtime uses at execution time.

Managed code (MSIL)

  • Code the compiler produced as it compiled the source code

Loading CLR

Khi chạy 1 executable file, Windows sẽ kiểm tra header của file .exe này để xác định sẽ khởi tạo tiến trình 32-bit hoặc 64-bit. Sau đó, Windows kiểm tra thông tin về kiến trúc CPU (CPU architecture information) được nhúng (embedded) trong phần header và theo đó load MSCorEE.dll vào không gian địa của tiến trình

Tùy vào CPU type của máy tính mà Windows sẽ load phiên bản x86, x64 hoặc ARM của MSCorEE.dll

Thread chính của tiến trình sẽ gọi 1 method bên trong MSCorEE.dll mà sẽ: khởi tạo CLR, load EXE assembly, và gọi tới entry point method của nó (Main). Tại thời điểm đó, managed application sẽ bắt đầu chạy

JIT and execution

Tưởng tượng ta có 1 cuốn sách dạy nấu ăn (PE file) được viết ở 1 ngôn ngữ mà mình không biết hoàn toàn (MSIL). Và ta chỉ cần sử dụng 1 số công thức nhất định trong cuốn sách đó (chỉ thực thi 1 số methods)

  • Nếu không có JIT: Ta sẽ cần dịch toàn bộ cuốn sách sang ngôn ngữ mà ta biết (native code) trước khi bắt đầu nấu ăn. Việc này sẽ làm tốn rất nhiều thời gian, trong khi ta chỉ sử dụng 1 số công thức trong đó

  • Nếu có JIT: Ta chỉ dịch những công thức khi ta cần dùng đến. Và ta sẽ luôn giữ lại 1 bản dịch của công thức để khi ta cần dùng lại, ta không phải dịch nữa

Execution example

Once the Main method of Program.exe is called, and WriteLine is set to be executed next, the JITCompiler function searches assembly's (Console.dll) metadata for the called method's (WriteLine) MSIL. JITCompiler next verifies and compiles the MSIL into native code and saves it in a dynamically allocated block of memory.

Then, JIT replaces the reference of the called method (WriteLine) with an address of the block in memory containing the native code it previously compiled.

Finally, the JIT jumps to the code (this code is the actual implementation of WriteLine(string) method) in the memory block, executes it and returns to Main from where it continues execution as normal.

The second time WriteLine is executed, code for the WriteLine method has already been verified and compiled, meaning the call goes directly to the block of memory where native code is stored.

Subsequent calls to the JIT-compiled method go directly to the native code.

Verification

Trong khi compile MSIL sang native code, CLR thực hiện 1 tiến trình gọi là verification để đảm bảo những việc mà code thực hiện là an toàn. Ví dụ, verification kiểm tra mọi method có được gọi với số lượng parameters cần thiết hay không, mỗi parameter có được truyền vào method với type đúng hay không, mỗi method có return đúng với type được định nghĩa hay không,…

Tài liệu tham khảo