很多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结构):
原文地址:https://www.cnblogs.com/uuuu01/p/8513561.html