.Net的技术体系为啥经常变化?

前言

其实可以通过JIT看下微软为啥经常频繁更新和变化自己的技术,JIT Compile是.Net从IL代码到机器码的关键技术,总体来说JIT是.Net代码生成的最后一步。本篇来看下它的大致框架。.

概括

一:前奏

1.Roslyn
首先就是Roslyn编译器,它主要负责的是把C#源代码编译成MSIL代码。MSIL是微软的一套中间代码的规范,它继承自传统的WIN PE技术,在原来的COM的基础上取代COM在PE的槽位置,存储的是.Net Header(.Net MetaData Directory RVA)。
2.CLR
当Roslyn把源码编译成MSIL,下面就是CLR接管了。CLR主要做一些JIT前期的工作,及后期工作。比如管控和分派内存模型,加载配置文件(runtimeconfig.json),GC垃圾回收,以及调用JIT。
3.JIT
当CLR做完以上工作之后,就会调用JIT,把IL代码编译经过机器繁琐复杂的过程,编译成某个平台的机器码。

二:JIT过程

以上可以看到,前端基本上不可变,也就是MSIL这套东西,因为它变了,后端以及C#源码这些东西都会要相应的变化,代价太大。CLR可变嘛?CLR是可变的,但是它跟C#源码,IL代码,机器码的关系并不大,所以它经常变更,这点参考github和微软官方说明。JIT可变吗?JIT也是可变的,它主要是负责中间表现(IR)的优化工作,也就是把IL代码进行各种变形,各种赋予实际上意义,各种性能优化,各种状态的生成。JIT的变化是最大的,只要IL代码的存在依据,它可以随时或者下一个版本进行一个极大的变化。

从.Net6到7的这个版本中间的一些性能优化,都是在JIT的IR层面进行的优化。它主要是更智能化的处理这些需要优化的IR代码。然后跟优化的结果来进行一个机器码的生成。总之JIT的IR变形的本质只有一个,那就是生成一个性能最大化,但是功能俱全的IL代码的机器码。

三:具体的表现

JIT的变化具体表现在,比如快速JIT,快速循环JIT,消除边界检查,去虚拟保护,这些PGO形式的IR变形。大部分都是在.Net7实现的。

要了解这些变化,需要了解从IL到IR,JIT是如何变形促使优化最大化的。这中间极其复杂,尤其是原理方面。

四:例子

看一段简单的Machine Code generate

000001C8B5A9B891 89 55 DD FF  mov  dword ptr [rbp-23h],edx

如果.Net8的JIT要生成以上代码,该如何做呢?
首先看这个机器码89,它是mov Ev Gv。E代表内存/寄存器,G通用寄存器,V则表示word,Dword,Qword。那么89表示从寄存器里面读取数据到内存。
继看55,55的二进制:0101 0101。拆分成2-3-3模式:01 010 101。010表示寄存器序号,10b表示edx寄存器,101b表示rbp寄存器。
那么,这个rbp-23h的23h是哪里来的呢?注意看在89 55的后面还有一个字节的DD FF.
-23h十六进制是:FFDD
所以整个的机器码:89 55 DD FF。它表示的就是ASM代码:mov dword ptr [rbp-23h],edx 。
这是JIT生成的最后一步。