CoreCLR源码探索(八) JIT的工作原理(详解篇)
在上一篇 我們對CoreCLR中的JIT有了一個基礎的了解,這一篇我們將更詳細分析JIT的實現.
JIT的實現代碼主要在https://github.com/dotnet/coreclr/tree/master/src/jit下,
要對一個的函數的JIT過程進行詳細分析, 最好的辦法是查看JitDump.
查看JitDump需要自己編譯一個Debug版本的CoreCLR, windows可以看這里, linux可以看這里,
編譯完以后定義環境變量COMPlus_JitDump=Main, Main可以換成其他函數的名稱, 然后使用該Debug版本的CoreCLR執行程序即可.
JitDump的例子可以看這里, 包含了Debug模式和Release模式的輸出.
接下來我們來結合代碼一步步的看JIT中的各個過程.
以下的代碼基于CoreCLR 1.1.0和x86/x64分析, 新版本可能會有變化.
(為什么是1.1.0? 因為JIT部分我看了半年時間, 開始看的時候2.0還未出來)
JIT的觸發
在上一篇中我提到了, 觸發JIT編譯會在第一次調用函數時, 會從樁(Stub)觸發:
這就是JIT Stub實際的樣子, 函數第一次調用前Fixup Precode的狀態:
Fixup Precode:(lldb) di --frame --bytes -> 0x7fff7c21f5a8: e8 2b 6c fe ff ? ? callq ?0x7fff7c2061d80x7fff7c21f5ad: 5e ? ? ? ? ? ? ? ? popq ? %rsi0x7fff7c21f5ae: 19 05 e8 23 6c fe ?sbbl ? %eax, -0x193dc18(%rip)0x7fff7c21f5b4: ff 5e a8 ? ? ? ? ? lcalll *-0x58(%rsi)0x7fff7c21f5b7: 04 e8 ? ? ? ? ? ? ?addb ? $-0x18, %al0x7fff7c21f5b9: 1b 6c fe ff ? ? ? ?sbbl ? -0x1(%rsi,%rdi,8), %ebp0x7fff7c21f5bd: 5e ? ? ? ? ? ? ? ? popq ? %rsi0x7fff7c21f5be: 00 03 ? ? ? ? ? ? ?addb ? %al, (%rbx)0x7fff7c21f5c0: e8 13 6c fe ff ? ? callq ?0x7fff7c2061d80x7fff7c21f5c5: 5e ? ? ? ? ? ? ? ? popq ? %rsi0x7fff7c21f5c6: b0 02 ? ? ? ? ? ? ?movb ? $0x2, %al (lldb) di --frame --bytes -> 0x7fff7c2061d8: e9 13 3f 9d 79 ? ? ? ? ? ? ? ? jmp ? ?0x7ffff5bda0f0 ? ? ? ? ? ?; PrecodeFixupThunk0x7fff7c2061dd: cc ? ? ? ? ? ? ? ? ? ? ? ? ? ? int3 ? 0x7fff7c2061de: cc ? ? ? ? ? ? ? ? ? ? ? ? ? ? int3 ? 0x7fff7c2061df: cc ? ? ? ? ? ? ? ? ? ? ? ? ? ? int3 ? 0x7fff7c2061e0: 49 ba 00 da d0 7b ff 7f 00 00 ?movabsq $0x7fff7bd0da00, %r100x7fff7c2061ea: 40 e9 e0 ff ff ff ? ? ? ? ? ? ?jmp ? ?0x7fff7c2061d0 這兩段代碼只有第一條指令是相關的, 注意callq后面的5e 19 05, 這些并不是匯編指令而是函數的信息, 下面會提到.
接下來跳轉到Fixup Precode Chunk, 從這里開始的代碼所有函數都會共用:
這段代碼的源代碼在vm\amd64\unixasmhelpers.S:
LEAF_ENTRY PrecodeFixupThunk, _TEXTpop ? ? rax ? ? ? ? // Pop the return address. It points right after the call instruction in the precode.// Inline computation done by FixupPrecode::GetMethodDesc()movzx ? r10,byte ptr [rax+2] ? ?// m_PrecodeChunkIndexmovzx ? r11,byte ptr [rax+1] ? ?// m_MethodDescChunkIndexmov ? ? rax,qword ptr [rax+r10*8+3]lea ? ? METHODDESC_REGISTER,[rax+r11*8]// Tail call to prestubjmp C_FUNC(ThePreStub)LEAF_END PrecodeFixupThunk, _TEXTpopq %rax后rax會指向剛才callq后面的地址, 再根據后面儲存的索引值可以得到編譯函數的MethodDesc, 接下來跳轉到The PreStub:
ThePreStub:(lldb) di --frame --bytes -> 0x7ffff5bda040 <ThePreStub>: 55 ? ? ? ? ? ? ? ? ? ? ? pushq ?%rbp0x7ffff5bda041 <ThePreStub+1>: 48 89 e5 ? ? ? ? ? ? ? ? movq ? %rsp, %rbp0x7ffff5bda044 <ThePreStub+4>: 53 ? ? ? ? ? ? ? ? ? ? ? pushq ?%rbx0x7ffff5bda045 <ThePreStub+5>: 41 57 ? ? ? ? ? ? ? ? ? ?pushq ?%r150x7ffff5bda047 <ThePreStub+7>: 41 56 ? ? ? ? ? ? ? ? ? ?pushq ?%r140x7ffff5bda049 <ThePreStub+9>: 41 55 ? ? ? ? ? ? ? ? ? ?pushq ?%r130x7ffff5bda04b <ThePreStub+11>: 41 54 ? ? ? ? ? ? ? ? ? ?pushq ?%r120x7ffff5bda04d <ThePreStub+13>: 41 51 ? ? ? ? ? ? ? ? ? ?pushq ?%r90x7ffff5bda04f <ThePreStub+15>: 41 50 ? ? ? ? ? ? ? ? ? ?pushq ?%r80x7ffff5bda051 <ThePreStub+17>: 51 ? ? ? ? ? ? ? ? ? ? ? pushq ?%rcx0x7ffff5bda052 <ThePreStub+18>: 52 ? ? ? ? ? ? ? ? ? ? ? pushq ?%rdx0x7ffff5bda053 <ThePreStub+19>: 56 ? ? ? ? ? ? ? ? ? ? ? pushq ?%rsi0x7ffff5bda054 <ThePreStub+20>: 57 ? ? ? ? ? ? ? ? ? ? ? pushq ?%rdi0x7ffff5bda055 <ThePreStub+21>: 48 8d a4 24 78 ff ff ff ?leaq ? -0x88(%rsp), %rsp ? ? ? ? ; allocate transition block0x7ffff5bda05d <ThePreStub+29>: 66 0f 7f 04 24 ? ? ? ? ? movdqa %xmm0, (%rsp) ? ? ? ? ? ? ; fill transition block0x7ffff5bda062 <ThePreStub+34>: 66 0f 7f 4c 24 10 ? ? ? ?movdqa %xmm1, 0x10(%rsp) ? ? ? ? ; fill transition block0x7ffff5bda068 <ThePreStub+40>: 66 0f 7f 54 24 20 ? ? ? ?movdqa %xmm2, 0x20(%rsp) ? ? ? ? ; fill transition block0x7ffff5bda06e <ThePreStub+46>: 66 0f 7f 5c 24 30 ? ? ? ?movdqa %xmm3, 0x30(%rsp) ? ? ? ? ; fill transition block0x7ffff5bda074 <ThePreStub+52>: 66 0f 7f 64 24 40 ? ? ? ?movdqa %xmm4, 0x40(%rsp) ? ? ? ? ; fill transition block0x7ffff5bda07a <ThePreStub+58>: 66 0f 7f 6c 24 50 ? ? ? ?movdqa %xmm5, 0x50(%rsp) ? ? ? ? ; fill transition block0x7ffff5bda080 <ThePreStub+64>: 66 0f 7f 74 24 60 ? ? ? ?movdqa %xmm6, 0x60(%rsp) ? ? ? ? ; fill transition block0x7ffff5bda086 <ThePreStub+70>: 66 0f 7f 7c 24 70 ? ? ? ?movdqa %xmm7, 0x70(%rsp) ? ? ? ? ; fill transition block0x7ffff5bda08c <ThePreStub+76>: 48 8d bc 24 88 00 00 00 ?leaq ? 0x88(%rsp), %rdi ? ? ? ? ?; arg 1 = transition block*0x7ffff5bda094 <ThePreStub+84>: 4c 89 d6 ? ? ? ? ? ? ? ? movq ? %r10, %rsi ? ? ? ? ? ? ? ?; arg 2 = methoddesc0x7ffff5bda097 <ThePreStub+87>: e8 44 7e 11 00 ? ? ? ? ? callq ?0x7ffff5cf1ee0 ? ? ? ? ? ?; PreStubWorker at prestub.cpp:9580x7ffff5bda09c <ThePreStub+92>: 66 0f 6f 04 24 ? ? ? ? ? movdqa (%rsp), %xmm00x7ffff5bda0a1 <ThePreStub+97>: 66 0f 6f 4c 24 10 ? ? ? ?movdqa 0x10(%rsp), %xmm10x7ffff5bda0a7 <ThePreStub+103>: 66 0f 6f 54 24 20 ? ? ? ?movdqa 0x20(%rsp), %xmm20x7ffff5bda0ad <ThePreStub+109>: 66 0f 6f 5c 24 30 ? ? ? ?movdqa 0x30(%rsp), %xmm30x7ffff5bda0b3 <ThePreStub+115>: 66 0f 6f 64 24 40 ? ? ? ?movdqa 0x40(%rsp), %xmm40x7ffff5bda0b9 <ThePreStub+121>: 66 0f 6f 6c 24 50 ? ? ? ?movdqa 0x50(%rsp), %xmm50x7ffff5bda0bf <ThePreStub+127>: 66 0f 6f 74 24 60 ? ? ? ?movdqa 0x60(%rsp), %xmm60x7ffff5bda0c5 <ThePreStub+133>: 66 0f 6f 7c 24 70 ? ? ? ?movdqa 0x70(%rsp), %xmm70x7ffff5bda0cb <ThePreStub+139>: 48 8d a4 24 88 00 00 00 ?leaq ? 0x88(%rsp), %rsp0x7ffff5bda0d3 <ThePreStub+147>: 5f ? ? ? ? ? ? ? ? ? ? ? popq ? %rdi0x7ffff5bda0d4 <ThePreStub+148>: 5e ? ? ? ? ? ? ? ? ? ? ? popq ? %rsi0x7ffff5bda0d5 <ThePreStub+149>: 5a ? ? ? ? ? ? ? ? ? ? ? popq ? %rdx0x7ffff5bda0d6 <ThePreStub+150>: 59 ? ? ? ? ? ? ? ? ? ? ? popq ? %rcx0x7ffff5bda0d7 <ThePreStub+151>: 41 58 ? ? ? ? ? ? ? ? ? ?popq ? %r80x7ffff5bda0d9 <ThePreStub+153>: 41 59 ? ? ? ? ? ? ? ? ? ?popq ? %r90x7ffff5bda0db <ThePreStub+155>: 41 5c ? ? ? ? ? ? ? ? ? ?popq ? %r120x7ffff5bda0dd <ThePreStub+157>: 41 5d ? ? ? ? ? ? ? ? ? ?popq ? %r130x7ffff5bda0df <ThePreStub+159>: 41 5e ? ? ? ? ? ? ? ? ? ?popq ? %r140x7ffff5bda0e1 <ThePreStub+161>: 41 5f ? ? ? ? ? ? ? ? ? ?popq ? %r150x7ffff5bda0e3 <ThePreStub+163>: 5b ? ? ? ? ? ? ? ? ? ? ? popq ? %rbx0x7ffff5bda0e4 <ThePreStub+164>: 5d ? ? ? ? ? ? ? ? ? ? ? popq ? %rbp0x7ffff5bda0e5 <ThePreStub+165>: 48 ff e0 ? ? ? ? ? ? ? ? jmpq ? *%rax%rax should be patched fixup precode = 0x7fff7c21f5a8(%rsp) should be the return address before calling "Fixup Precode"看上去相當長但做的事情很簡單, 它的源代碼在vm\amd64\theprestubamd64.S:
NESTED_ENTRY ThePreStub, _TEXT, NoHandlerPROLOG_WITH_TRANSITION_BLOCK 0, 0, 0, 0, 0//// call PreStubWorker//lea ? ? ? ? ? ? rdi, [rsp + __PWTB_TransitionBlock] ? ? // pTransitionBlock*mov ? ? ? ? ? ? rsi, METHODDESC_REGISTERcall ? ? ? ? ? ?C_FUNC(PreStubWorker)EPILOG_WITH_TRANSITION_BLOCK_TAILCALLTAILJMP_RAXNESTED_END ThePreStub, _TEXT 它會備份寄存器到棧, 然后調用PreStubWorker這個函數, 調用完畢以后恢復棧上的寄存器,
再跳轉到PreStubWorker的返回結果, 也就是打完補丁后的Fixup Precode的地址(0x7fff7c21f5a8).
PreStubWorker是C編寫的函數, 它會調用JIT的編譯函數, 然后對Fixup Precode打補丁.
打補丁時會讀取前面的5e,?5e代表precode的類型是PRECODE_FIXUP, 打補丁的函數是FixupPrecode::SetTargetInterlocked.
打完補丁以后的Fixup Precode如下:
下次再調用函數時就可以直接jmp到編譯結果了.
JIT Stub的實現可以讓運行時只編譯實際會運行的函數, 這樣可以大幅減少程序的啟動時間, 第二次調用時的消耗(1個jmp)也非常的小.
注意調用虛方法時的流程跟上面的流程有一點不同, 虛方法的地址會保存在函數表中,
打補丁時會對函數表而不是Precode打補丁, 下次調用時函數表中指向的地址是編譯后的地址, 有興趣可以自己試試分析.
接下來我們看看PreStubWorker的內部處理.這篇文章對CoreCLR中JIT的整個流程做出了更詳細的分析,但因為JIT中的代碼實在太多, 我無法像分析GC的時候一樣把代碼全部貼出來, 有很多細節也無法顧及.歡迎大家閱讀原文進行閱讀?
相關文章:
-
《代碼的未來》讀書筆記:內存管理與GC那點事兒
-
CoreCLR源碼探索(一) Object是什么
-
CoreCLR源碼探索(二) new是什么
-
CoreCLR源碼探索(三) GC內存分配器的內部實現
-
.NET跨平臺之旅:corehost 是如何加載 coreclr 的
-
.NET CoreCLR開發人員指南(上)
-
CoreCLR源碼探索(四) GC內存收集器的內部實現 分析篇
-
CoreCLR源碼探索(五) GC內存收集器的內部實現 調試篇
-
CoreCLR源碼探索(六) NullReferenceException是如何發生的
-
CoreCLR源碼探索(七) JIT的工作原理(入門篇)
原文地址:http://www.cnblogs.com/zkweb/p/7746222.html
.NET社區新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注
總結
以上是生活随笔為你收集整理的CoreCLR源码探索(八) JIT的工作原理(详解篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ABP从入门到精通(5):.扩展国际化语
- 下一篇: 学习Identity Server 4的