1. 程式人生 > >CoreCLR源碼探索(七) JIT的h5牛牛源碼出售工作原理(入門篇)

CoreCLR源碼探索(七) JIT的h5牛牛源碼出售工作原理(入門篇)

流程實例 算法和數據結構 www ... ext 結構 cal space net

很多C#的初學h5牛牛源碼出售Q1446595067官網:h5.haozibbs.com者都會有這麽一個疑問, .Net程序代碼是如何被機器加載執行的?
最簡單的解答是, C#會通過編譯器(CodeDom, Roslyn)編譯成IL代碼,
然後CLR(.Net Framework, .Net Core, Mono)會把這些IL代碼編譯成目標機器的機器代碼並執行.
相信大多數的C#的書籍都是這樣一筆帶過的.
這篇和下篇文章會深入講解JIT的具體工作流程,
和前面的GC篇一樣, 實現中的很多細節都是無標準文檔的, 用搜索引擎不會找到它們相關的資料.

因為內容相當多, 講解JIT的文章將會分為兩篇.
第一篇是入門篇, 看過這個系列之前的文章和CLR via C#, 了解一些編譯原理的都可以看的明白.
第二篇是詳解篇, 會分析JIT的具體實現流程, 算法和數據結構.

這篇的內容是基於CoreCLR 1.1.0分析的, 其他CLR中的實現不一定和這篇分析的實現完全一樣.
微軟最近提供了一篇JIT入門文檔,
盡管裏面寫的相當潦草但是仍有很大的參考價值, 推薦同時參考這個文檔.

JIT的作用介紹

相信很多C#程序員都知道, 我們編寫的C#代碼在經過編譯後得出的exe或dll裏面包含的並不是機器代碼,
而是一種中間代碼, 也稱為MSIL(簡稱IL).
MSIL可以在不同的系統或者平臺上執行, CLR中執行它們的模塊就是這篇要講的JIT.

如圖所示

技術分享圖片

CoreCLR中的JIT代號是RyuJIT, RyuJIT可以把MSIL翻譯為X86, X64或者ARM的機器代碼.

使用JIT的好處有

使用JIT的壞處有

為了解決這些壞處而出現的技術有NGEN, AOT, CoreRT等, 但是使用它們以後同時也就失去了使用JIT的好處.

JIT的流程總覽

以下的圖片來源於微軟提供的JIT入門文檔:

技術分享圖片

總體上來說RyuJIT可以分為兩個部分.
前端: 也就是圖上的第一行, 負責把MSIL轉換為JIT中的內部表現(IR)並且執行優化.
後端: 也就是圖上的第二行, 負責準備生產機器代碼, 分配寄存器等與平臺相關的處理.

具體的步驟可以分為:

技術分享圖片

前端的步驟有(導入MSIL和優化):


技術分享圖片

後端的步驟有(平臺相關的處理):

技術分享圖片

JIT的流程實例

只看上面的圖你可能會一頭霧水, 我們來看看實際的流程.
為了更容易理解這裏我使用的是Debug模式.
以下的內容來源於CoreCLR的輸出, 設置環境變量"COMPlus_JitDump=Main"並且使用Debug版的CoreCLR即可得到.

首先是C#代碼, 非常簡單的循環3次並且輸出到控制臺.

using System; using System.Runtime.InteropServices; namespace ConsoleApplication { public class Program { public static void Main(string[] args) { for (int x = 0; x < 3; ++x) { Console.WriteLine(x); } } } }

經過編譯後會生成以下的IL, 下面我標記了運行堆棧的狀態和簡單的註釋.

IL to import: IL_0000 00 nop IL_0001 16 ldc.i4.0 ; 運行堆棧 [ 0 ] IL_0002 0a stloc.0 ; 運行堆棧 [ ], 保存到本地變量0 (x = 0) IL_0003 2b 0d br.s 13 (IL_0012) ; 跳轉到IL_0012 IL_0005 00 nop IL_0006 06 ldloc.0 ; 運行堆棧 [ x ] IL_0007 28 0c 00 00 0a call 0xA00000C ; 運行堆棧 [ ], 調用Console.WriteLine, 這裏的0xA00000C是token IL_000c 00 nop IL_000d 00 nop IL_000e 06 ldloc.0 ; 運行堆棧 [ x ] IL_000f 17 ldc.i4.1 ; 運行堆棧 [ x, 1 ] IL_0010 58 add ; 運行堆棧 [ x+1 ] IL_0011 0a stloc.0 ; 運行堆棧 [ ], 保存到本地變量0 (x = x + 1) IL_0012 06 ldloc.0 ; 運行堆棧 [ x ] IL_0013 19 ldc.i4.3 ; 運行堆棧 [ x, 3 ] IL_0014 fe 04 clt ; 運行堆棧 [ x<3 ] IL_0016 0b stloc.1 ; 運行堆棧 [ ], 保存到本地變量1 (tmp = x < 3) IL_0017 07 ldloc.1 ; 運行堆棧 [ tmp ] IL_0018 2d eb brtrue.s -21 (IL_0005); 運行堆棧 [ ], 如果tmp為true則跳轉到IL_0005 IL_001a 2a ret ; 從函數返回

RyuJIT的前端會把IL導入為中間表現(IR), 如下

Importing BB02 (PC=000) of ‘ConsoleApplication.Program:Main(ref)‘ [ 0] 0 (0x000) nop [000004] ------------ * stmtExpr void (IL 0x000... ???) [000003] ------------ \--* no_op void [ 0] 1 (0x001) ldc.i4.0 0 [ 1] 2 (0x002) stloc.0 [000008] ------------ * stmtExpr void (IL 0x001... ???) [000005] ------------ | /--* const int 0 [000007] -A---------- \--* = int [000006] D------N---- \--* lclVar int V01 loc0 [ 0] 3 (0x003) br.s [000010] ------------ * stmtExpr void (IL 0x003... ???) [000009] ------------ \--* nop void Importing BB03 (PC=005) of ‘ConsoleApplication.Program:Main(ref)‘ [ 0] 5 (0x005) nop [000025] ------------ * stmtExpr void (IL 0x005... ???) [000024] ------------ \--* no_op void [ 0] 6 (0x006) ldloc.0 [ 1] 7 (0x007) call 0A00000C [000029] ------------ * stmtExpr void (IL 0x006... ???) [000027] --C-G------- \--* call void System.Console.WriteLine [000026] ------------ arg0 \--* lclVar int V01 loc0 [ 0] 12 (0x00c) nop [000031] ------------ * stmtExpr void (IL 0x00C... ???) [000030] ------------ \--* no_op void [ 0] 13 (0x00d) nop [000033] ------------ * stmtExpr void (IL 0x00D... ???) [000032] ------------ \--* no_op void [ 0] 14 (0x00e) ldloc.0 [ 1] 15 (0x00f) ldc.i4.1 1 [ 2] 16 (0x010) add [ 1] 17 (0x011) stloc.0 [000039] ------------ * stmtExpr void (IL 0x00E... ???) [000035] ------------ | /--* const int 1 [000036] ------------ | /--* + int [000034] ------------ | | \--* lclVar int V01 loc0 [000038] -A---------- \--* = int [000037] D------N---- \--* lclVar int V01 loc0 Importing BB04 (PC=018) of ‘ConsoleApplication.Program:Main(ref)‘ [ 0] 18 (0x012) ldloc.0 [ 1] 19 (0x013) ldc.i4.3 3 [ 2] 20 (0x014) clt [ 1] 22 (0x016) stloc.1 [000017] ------------ * stmtExpr void (IL 0x012... ???) [000013] ------------ | /--* const int 3 [000014] ------------ | /--* < int [000012] ------------ | | \--* lclVar int V01 loc0 [000016] -A---------- \--* = int [000015] D------N---- \--* lclVar int V02 loc1 [ 0] 23 (0x017) ldloc.1 [ 1] 24 (0x018) brtrue.s [000022] ------------ * stmtExpr void (IL 0x017... ???) [000021] ------------ \--* jmpTrue void [000019] ------------ | /--* const int 0 [000020] ------------ \--* != int [000018] ------------ \--* lclVar int V02 loc1 Importing BB05 (PC=026) of ‘ConsoleApplication.Program:Main(ref)‘ [ 0] 26 (0x01a) ret [000042] ------------ * stmtExpr void (IL 0x01A... ???) [000041] ------------ \--* return void

我們可以看到IL被分成了好幾組(BB02~BB05), 這裏的BB是BasicBlock的縮寫,
一個BasicBlock中有多個語句(Statement), 一個語句就是一棵樹(GenTree).

上面的文本對應了以下的結構(又稱HIR結構):

技術分享圖片

CoreCLR源碼探索(七) JIT的h5牛牛源碼出售工作原理(入門篇)