HTML5技术

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

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

CoreCLR源码探索(七) JIT的工作原理(入门篇) 很多C#的初学者都会有这么一个疑问, .Net程序代码是如何被机器加载执行的? 最简单的解答是, C#会通过编译器(CodeDom, Roslyn)编译成IL代码, 然后CLR(.Net Framework, .Net Core, Mono)会把这些IL代码编译成目标机

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

很多C#的初学者都会有这么一个疑问, .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结构):

 

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

网友点评
a