HTML5技术

CoreCLR源码探索(七) JIT的工作原理(入门篇) - q303248153(5)

字号+ 作者:H5之家 来源:H5之家 2017-10-19 08:04 我要评论( )

目前的CoreCLR触发了JIT编译后, 会在当前线程中执行JIT编译. 如果多个线程同时调用了一个未JIT的函数, 其中一个线程会执行编译, 其他线程会等待编译完成. CoreCLR会对正在JIT编译的函数分配一个线程锁(ListLockEntr

目前的CoreCLR触发了JIT编译后, 会在当前线程中执行JIT编译.
如果多个线程同时调用了一个未JIT的函数, 其中一个线程会执行编译, 其他线程会等待编译完成.
CoreCLR会对正在JIT编译的函数分配一个线程锁(ListLockEntry)来实现这一点.

JIT会为准备的函数创建一个Compiler实例, Compiler实例储存了BasicBlock列表等编译时需要的信息.
一个正在编译的函数对应一个Compiler实例, 函数编译后Compiler实例会被销毁.

接下来我会对JIT的各项步骤进行一个简单的说明.

Frontend Importer

Importer负责读取和解析IL(byte array), 并根据IL生成JIT使用的内部表现IR(BasicBlock, Statement, GenTree).
BasicBlock会根据它们的跳转类型连接成一个图(graph).

第一个BasicBlock是内部使用的, 会添加一些函数进入的初始化处理(但不要和汇编中的prolog混淆).

下图是Importer的实例:

Inliner

如果函数符合内联的条件, 则Inliner会把函数的IR嵌入到它的调用端函数(callsite), 并且对本地变量和参数进行修整.
执行内联后接下来的步骤将在调用端函数中完成.

内联的条件有很多, 判断逻辑也相当的复杂, 这里我只列出一部分:

下图是Inliner的实例:

Morph

Morph会对Importer导入的HIR进行变形, 这个步骤包含了很多处理, 这里我只列出一部分:

  • 对各个节点进行修改
  • 标记节点是否需要检查null
  • 标记节点是否需要检查边界
  • 根据节点添加断言
  • 例如a = 5即可断言a等于5, b = new X()即可断言b != null
  • 经过Morph变形后的HIR将会包含更多信息, 对IL中隐式的处理(例如边界检查和溢出检查)也添加了显式的代码(GenTree).

    下图是Morph的实例:

    图中的comma表示的是逗号式, 例如(X(), 123)这个式会先评价X()然后结果使用123,
    上图中的comma会先把数组保存到一个临时变量, 执行边界检查, 然后再访问数组中的元素然后输出到控制台.

    Flowgraph Analysis

    Flowgraph Analysis会对BasicBlock进行流程分析,
    找出BasicBlock有哪些前任block(predecessor)和后继block(successor), 并且标记BasicBlock的引用次数.

    如果一个block是多个block的跳转目标, 则这个block有多个preds,
    如果一个block的跳转类型是jtrue(条件成立时跳转到目标block, 否则到下一个block), 则这个block有两个succs.

    并且计算DOM(dominator)树,
    例如出现 A -> B, A -> C, B -> D, C -> D, 则D的dominator不是B或C而是A, 表示执行D必须经过A,
    参考Wikipedia和论文.

    例如在这张图中:

    计算出来的DOM(dominator)树为:

    然后会根据流程分析的结果进行一些优化:

    优化 while 到 do while:
    优化前 jmp test; loop: ...; test: cond; jtrue loop;
    优化后 cond; jfalse done; loop: ...; test: cond; jtrue loop; done: ...;

    优化循环中数组的边界检查:
    优化前 for (var x = 0; x < a.Length; ++x) { b[x] = a[x]; },
    优化后

    if (x < a.Length) { if ((a != null && b != null) && (a.Length <= b.Length)) { do { var tmp = a[x]; // no bounds check b[x] = tmp; // no bounds check x = x + 1; } while (x < a.Length); } else { do { var tmp = a[x]; b[x] = tmp; x = x + 1; } while (x < a.Length); } }

    优化次数是常量的循环:
    优化前 for (var x = 0; x < 3; ++x) { DoSomething(); }
    优化后 DoSomething(); DoSomething(); DoSomething();
    注意循环次数过多或者循环中的代码过长则不会执行这项优化.

    LclVar sorting & Tree Ordering

    这个步骤会标记函数中本地变量的引用计数, 并且按引用计数排序本地变量表.
    然后会对tree的运行运行顺序执行标记, 例如 a() + b(), 会标记a()先于b()执行.
    (与C, C++不同, .Net中对操作参数的运行顺序有很严格的规定, 例如a+b和f(a, b)的运行顺序都是已规定的)

    经过运行顺序标记后其实就已经形成了LIR结构.
    LIR结构中无语句(Statement)节点, 语句节点经过在后面的Rationalization后会变为IL_OFFSET节点, 用于对应的IL偏移值,
    最终VisualStudio等IDE可以根据机器代码地址=>IL偏移值=>C#代码偏移值来下断点和调试.

    下图是Tree Ordering的实例, 红线表示连接下一个节点:

    Optimize SSA & VN

    RyuJIT为了实现更好的优化, 会对GenTree节点分配SSA序号和VN.

    要说明什么是SSA, 可以拿Wikipedia上的代码做例子:

    这里有4个BasicBlock和3个变量(x, y, w), 变量的值会随着执行而改变,
    我们很难确定两个时点的y是否同一个y, 这为代码优化带来了障碍.

     

    1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

    相关文章
    • C#使用Xamarin开发可移植移动应用(1.入门与Xamarin.Forms页面),附源码 - GuZhenYin

      C#使用Xamarin开发可移植移动应用(1.入门与Xamarin.Forms页面),附源

      2017-08-09 15:01

    • 【博客园皮肤】-超简洁美观-css源码分享 - Nirvana_zsy

      【博客园皮肤】-超简洁美观-css源码分享 - Nirvana_zsy

      2017-06-28 09:02

    • 每天4亿行SQLite订单大数据测试(源码) - 大石头

      每天4亿行SQLite订单大数据测试(源码) - 大石头

      2017-06-02 13:01

    • 使用sqlserver搭建高可用双机热备的Quartz集群部署【附源码】 - 一线码农

      使用sqlserver搭建高可用双机热备的Quartz集群部署【附源码】 - 一线

      2017-05-29 13:01

    网友点评
    e