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, 从这里开始的代码所有函数都会共用:

Fixup Precode Chunk:(lldb) di --frame --bytes
-> 0x7ffff5bda0f0 <PrecodeFixupThunk>: 58              popq   %rax                         ; rax = 0x7fff7c21f5ad0x7ffff5bda0f1 <PrecodeFixupThunk+1>: 4c 0f b6 50 02  movzbq 0x2(%rax), %r10            ; r10 = 0x05 (precode chunk index)0x7ffff5bda0f6 <PrecodeFixupThunk+6>: 4c 0f b6 58 01  movzbq 0x1(%rax), %r11            ; r11 = 0x19 (methoddesc chunk index)0x7ffff5bda0fb <PrecodeFixupThunk+11>: 4a 8b 44 d0 03  movq   0x3(%rax,%r10,8), %rax    ; rax = 0x7fff7bdd5040 (methoddesc chunk)0x7ffff5bda100 <PrecodeFixupThunk+16>: 4e 8d 14 d8     leaq   (%rax,%r11,8), %r10       ; r10 = 0x7fff7bdd5108 (methoddesc)0x7ffff5bda104 <PrecodeFixupThunk+20>: e9 37 ff ff ff  jmp    0x7ffff5bda040            ; ThePreStub

这段代码的源代码在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, _TEXT

popq %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打补丁.
打补丁时会读取前面的5e5e代表precode的类型是PRECODE_FIXUP, 打补丁的函数是FixupPrecode::SetTargetInterlocked.
打完补丁以后的Fixup Precode如下:

Fixup Precode:(lldb) di --bytes -s 0x7fff7c21f5a80x7fff7c21f5a8: e9 a3 87 3a 00     jmp    0x7fff7c5c7d500x7fff7c21f5ad: 5f                 popq   %rdi   0x7fff7c21f5ae: 19 05 e8 23 6c fe  sbbl   %eax, -0x193dc18(%rip)   0x7fff7c21f5b4: ff 5e a8           lcalll *-0x58(%rsi)   0x7fff7c21f5b7: 04 e8              addb   $-0x18, %al   0x7fff7c21f5b9: 1b 6c fe ff        sbbl   -0x1(%rsi,%rdi,8), %ebp   0x7fff7c21f5bd: 5e                 popq   %rsi   0x7fff7c21f5be: 00 03              addb   %al, (%rbx)   0x7fff7c21f5c0: e8 13 6c fe ff     callq  0x7fff7c2061d80x7fff7c21f5c5: 5e                 popq   %rsi   0x7fff7c21f5c6: b0 02              movb   $0x2, %al

下次再调用函数时就可以直接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跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/323107.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

P2607-[ZJOI2008]骑士【基环树,树形dp】

正题 题目大意 每个骑士有一个不可以同时上场的骑士&#xff0c;和一个战斗力。求最大战斗力。 解题思路 类似没有上司的舞会 其实就是在基环树森林&#xff0c;我们可以利用二次树形dp的方法。 先找到环&#xff0c;然后强行将环断开进行一次dp&#xff0c;然后强行连上进行…

【php】正则无法截取\反斜杠的解决方法

今天想将上次的图片上传功能优化一下&#xff0c;遇到一个问题&#xff0c;获取临时文件的名字时&#xff0c;由于名字中不能有\&#xff0c;所以必须对获取的文件名进行剪切&#xff0c;本人没有系统学习过php&#xff0c;所以有些知识点还是得重新学习。 获取到得字符串&…

Java GUI界面

package pac1;import java.awt.*; import java.awt.event.*; import javax.swing.*;public class Jlogin extends JFrame implements ActionListener{JButton oknew JButton("确定");JButton cancelnew JButton("取消");JTextField userNameTextnew JTextF…

HighChart模拟点击series的name显示隐藏

一、需求 HighChart模拟点击series的name显示隐藏&#xff0c;批量操作 二、代码实现 function toggleHighChart(show) {var charts Highcharts.charts;for(var i in charts){var chart charts[i];var series chart.options.series;for(var j in series){if(series[j].in){…

ZOJ1041-Transmitters【差积,计算几何】

正题 题目链接: http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId41 luogu也可以捞一把: https://www.luogu.org/problemnew/show/SP898 题目大意 一个雷达&#xff0c;可以扫半圆&#xff0c;给半径&#xff0c;可以转动雷达。给若干个点&#xff0c;求最多可以…

学习Identity Server 4的预备知识

我要使用asp.net core 2.0 web api 搭建一个基础框架并立即应用于一个实际的项目中去.这里需要使用identity server 4 做单点登陆.下面就简单学习一下相关的预备知识. 基于Token的安全验证体系 这个比较简单, 简单来说就是为了证明我们有访问权限, 我们首先需要获得一个token. …

【FTP】发布FTP服务器

FTP是用来传输文件的一种协议&#xff0c;类似于http 这次主要是用以下两个软件来完成局域网文件的传输 FileZillaServer.zip&#xff1a;开启FTP服务 FlashFXP54Setup.exe&#xff1a;更加便捷的传输上传文件 链接 链接&#xff1a;https://pan.baidu.com/s/1zTMXUO4EPD9l7_LH…

C/C++输入输出流

istream中的类&#xff08;如cin&#xff09;提供了一些面向行的类成员函数&#xff1a;getline()和get()。这两个函数都读取一行输入&#xff0c;直到达到换行符。不同的是&#xff0c;getline()将丢弃换行符&#xff0c;而get()将换行符保留在输入序列中。 目录 一、字符串…

帮你彻底搞懂JS中的prototype、__proto__与constructor(图解)

转载自 帮你彻底搞懂JS中的prototype、__proto__与constructor&#xff08;图解&#xff09;   作为一名前端工程师&#xff0c;必须搞懂JS中的prototype、__proto__与constructor属性&#xff0c;相信很多初学者对这些属性存在许多困惑&#xff0c;容易把它们混淆&#xff…

ssl1715-计算面积【差积】

正题 题目大意 给一个平行四边形的3个点&#xff0c;求最大面积。 解题思路 明显答案就是差积*2的绝对值。 codecodecode #include<cstdio> #include<algorithm> #include<cmath> #define db double using namespace std; int n; struct node{db x,y; }p[…

ASP.NET Core 企业级开发架构简介及框架汇总

一、 垂直方向架构 1. 多层架构 分层架构通过程序包或者程序的隔离构建松耦合的应用。我们以最近流行的洋葱架构模型进行分析&#xff0c;如图

【Php】最最简单的php环境搭建

Wamp是apachemysqlphp的集成环境&#xff0c;通过一键安装程序&#xff0c;可以完全免除配置的烦恼。 链接&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1717Mw66Z1wZ67ltLriUVew 提取码&#xff1a;7h5t 下载之后一直下一步&#xff0c;默认默认即可 如果输入local…

01)自学JavaScript

1. JavaScript 简介 1.1 JavaScript 是脚本语言 JavaScript 是一种轻量级的编程语言。 JavaScript 是可插入 HTML 页面的编程代码。 JavaScript 插入 HTML 页面后&#xff0c;可由所有的现代浏览器执行。 2. JavaScript 用法 HTML 中的脚本必须位于 <script> 与 &l…

ssl1213-多边形面积【差积,计算几何】

正题 题目大意 求一个多边形面积。 解题思路 随便定一个原点&#xff0c;然后答案就是相邻点的差积之和的绝对值。 要判断不能组成多边形的情况。 codecodecode #include<cstdio> #include<algorithm> #include<cmath> #define N 1010 #define db double …

Memcached总结

一、介绍 Memcached是一个自由开源的&#xff0c;高性能&#xff0c;分布式内存对象缓存系统。 Memcached是一种基于内存的key-value存储&#xff0c;用来存储小块的任意数据&#xff08;字符串、对象&#xff09;。 目的&#xff1a;通过缓存数据库查询结果&#xff0c;减少…

浅析C#中单点登录的原理和使用

是单点登录&#xff1f; 我想肯定有一部分人“望文生义”的认为单点登录就是一个用户只能在一处登录&#xff0c;其实这是错误的理解&#xff08;我记得我第一次也是这么理解的&#xff09;。 单点登录指的是多个子系统只需要登录一个&#xff0c;其他系统不需要登录了&#xf…

虚拟机和linux的安装

下载地址&#xff1a; 虚拟机14版本的&#xff1a; 链接:https://pan.baidu.com/s/1lxp62gerSI_29wQDuTEAJQ 提取码:53dn 乌班图 https://ubuntu.com/download

Mybatis入门程序增删改查操作

学习目标 了解Mybatis的基本知识熟悉Mybatis的工作原理掌握Mybatis入门程序的编写 文章目录 1.初始Mybatis 2.Mybatis入门程序 3.Mybatis操作总结 1.初始Mybatis MyBatis 是一款优秀的持久层框架&#xff0c;它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所…

Memcached:列出所有Key

翻译自 Memcached&#xff1a;列出所有Key 在一般情况下&#xff0c;有没有办法列出所有的Key&#xff0c;一个memcached的实例存储。但是&#xff0c;您可以列出类似于第一个1Meg键的内容&#xff0c;这在开发过程中通常就足够了。这是如何做&#xff1a; Telnet到您的服务…

jzoj5354-导弹拦截【dp,最大匹配,最少路径覆盖】

正题 解题思路 一个东西可以拦截导弹&#xff0c;每次只能打比上一次x,y,zx,y,zx,y,z都大的导弹。求一个最多可以拦截掉多少个导弹和至少要多少个才能拦截完。拦截导弹没有顺序要求。 解题思路 由于没有顺序要求所以我们可以直接定义一个比较&#xff0c;然后第一问做法和导弹…