前言

C#这种语言之所以号称安全的,面向对象的语言。这个安全两个字可不是瞎叫的哦。因为JIT会检查任何可能超出分配范围的数值,以便使其保持在安全边界内。这里有两个概念,其一边界检查,其二IR解析。后者的生成是前者的功能的保证。啥叫IR,你以为的IL是中间语言,其实并不是,还有一层IR中间表象。.Net8的顶级技术之一(非专属),晓者寥寥。本篇来看看这两项技术。

概括

1.边界检查的缺陷

也叫循环提升,这里边界检查以数组的边界检查为例,看下C#代码

C# Code


using System.Runtime.CompilerServices;
class Program
{
static void Main()
{
int[] array = new int[10_000_000];
for (int i = 0; i < 1_000_000; i++)
{
Test(array);
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool Test(int[] array)
{
for (int i = 0; i < 0x12345; i++)
{
if (array[i] == 42)
{
return true;
}
}
return false;
}
}

JIT并不知道数组array[i]里面的i索引是否超过了array数组的长度。所以每次循环都会检查索引的大小,如果超过则报异常,不超过继续循环,这种功能就叫做边界检查。是.Net6 JIT自动加上去的,但是它有缺陷。

缺陷就在于,每次循环都检查,极大消耗了代码的运行效率。为了避免这种缺陷,是否可以在循环之前判断array数组的长度小于或者循环的最大值。通过这种一次性的判断,取代每次循环的判断,最大化提升代码运行效率。

在.Net8里面这种情况是可行的。

.Net8 JIT Machine Code


G_M000_IG01: ;; offset=0000H
4883EC28 sub rsp, 40
G_M000_IG02: ;; offset=0004H
33C0 xor eax, eax
4885C9 test rcx, rcx
7429 je SHORT G_M000_IG05
81790845230100 cmp dword ptr [rcx+08H], 0x12345
7C20 jl SHORT G_M000_IG05
0F1F40000F1F840000000000 align [12 bytes for IG03]
G_M000_IG03: ;; offset=0020H
8BD0 mov edx, eax
837C91102A cmp dword ptr [rcx+4*rdx+10H], 42
7429 je SHORT G_M000_IG08
FFC0 inc eax
3D45230100 cmp eax, 0x12345
7CEE jl SHORT G_M000_IG03
G_M000_IG04: ;; offset=0032H
EB17 jmp SHORT G_M000_IG06
G_M000_IG05: ;; offset=0034H
3B4108 cmp eax, dword ptr [rcx+08H]
7323 jae SHORT G_M000_IG10
8BD0 mov edx, eax
837C91102A cmp dword ptr [rcx+4*rdx+10H], 42
7410 je SHORT G_M000_IG08
FFC0 inc eax
3D45230100 cmp eax, 0x12345
7CE9 jl SHORT G_M000_IG05
G_M000_IG06: ;; offset=004BH
33C0 xor eax, eax
G_M000_IG07: ;; offset=004DH
4883C428 add rsp, 40
C3 ret
G_M00_IG08: ;; offset=0052H
B801000000 mov eax, 1
G_M000_IG09: ;; offset=0057H
4883C428 add rsp, 40
C3 ret
G_M000_IG10: ;; offset=005CH
E89F82C25F call CORINFO_HELP_RNGCHKFAIL
CC int3
; Total bytes of code 98

诚如上面所言,边界检查的判断放在了for循环的外面。if和else分成快速和慢速路径,前者进行了优化。逆向成C#代码如下

if(array!=null && array.length >=0x12345)//数组不能为空,且数组的长度不能小于循环的长度。否则可能边界溢出
{
for(int i=0;i<0x12345;i++)
{
if(array[i]==42)//这里不再检查边界
{
return true;
}
}
return false;
}
else
{
for(int i=0;i<0x2345;i++)
{
if(array[i]==42)//边界检查
return true;
}
return flase;
}

边界检查不是本节的重点,重点是这个边界检查是如何通过IR生成的,以及优化。因为IL代码里面并没有。

2.IR的生成

部分代码。常规的认为,C#的运行过程是:

C# Code->

IL ->

Machine Code

一般的认为,IL是中间语言,或者字节码。但是实际上还有一层在JIT里面。如下:

C# Code ->

IL ->

IR ->

Machine Code

这个IR是对IL进行各种骚操作。最重要的一点就是各种优化和变形。这里来看看IR是如何对IL进行边界检查优化的。

看下边界检查的核心IR代码:

***** BB02
STMT00002 ( 0x004[E-] ... 0x009 )
[000013] ---XG+----- * JTRUE void
[000012] N--XG+-N-U- \--* EQ int
[000034] ---XG+----- +--* COMMA int
[000026] ---X-+----- | +--* BOUNDS_CHECK_Rng void
[000008] -----+----- | | +--* LCL_VAR int V01 loc0
[000025] ---X-+----- | | \--* ARR_LENGTH int
[000007] -----+----- | | \--* LCL_VAR ref V00 arg0
[000035] n---G+----- | \--* IND int
[000033] -----+----- | \--* ARR_ADDR byref int[]
[000032] -----+----- | \--* ADD byref
[000023] -----+----- | +--* LCL_VAR ref V00 arg0
[000031] -----+----- | \--* ADD long
[000029] -----+----- | +--* LSH long
[000027] -----+---U- | | +--* CAST long <- uint
[000024] -----+----- | | | \--* LCL_VAR int V01 loc0
[000028] -----+-N--- | | \--* CNS_INT long 2
[000030] -----+----- | \--* CNS_INT long 16
[000011] -----+----- \--* CNS_INT int 42 ------------ BB03 [00D..019) -> BB02 (cond), preds={BB02} succs={BB04,BB02}

这种看着牛逼轰轰的代码,正是IR。从最里面看起,意思在注释里。

[000031] -----+-----                            |           \--*  ADD       long //把LSH计算的结果加上16,这个16就是下面的CNS_INT long 16的16.
[000029] -----+----- | +--* LSH long //LSH表示把数组索引左移2位。这个2就是下面的CNS_INT long 2里面的2
[000027] -----+---U- | | +--* CAST long <- uint//把数组索引的类型从uint转换转换成long类型
[000024] -----+----- | | | \--* LCL_VAR int V01 loc0 //读取本地变量V01,实际上就是数组arrar的索引。
[000028] -----+-N--- | | \--* CNS_INT long 2 //这个2是左移的位数
[000030] -----+----- | \--* CNS_INT long 16//被ADD相加的数值16

继续看

 |  \--*  IND       int
[000033] -----+----- | \--* ARR_ADDR byref int[]
[000032] -----+----- | \--* ADD byref //把前面计算的结果与array数组的地址相加。实际上就是 array + i*4+-x10。一个索引占4个字节,methodtable和array.length各占8字节,这个表达式的结果就是索引位i的array的值,也就是array[i]这个数值。
[000023] -----+----- | +--* LCL_VAR ref V00 arg0 //获取本地变量V00的地址,这个地址实际上就是数组array的地址。
[000031] -----+----- | \--* ADD long
[000029] -----+----- | +--* LSH long
[000027] -----+---U- | | +--* CAST long <- uint
[000024] -----+----- | | | \--* LCL_VAR int V01 loc0
[000028] -----+-N--- | | \--* CNS_INT long 2
[000030] -----+----- | \--* CNS_INT long 16

继续看

  [000013] ---XG+-----                         *  JTRUE     void //是或者否都进行相应的跳转
[000012] N--XG+-N-U- \--* EQ int //判断获取的array[i]是否等于42,这个42是CNS_INT int 42里的42
[000034] ---XG+----- +--* COMMA int //计算它的两个值,获取第二个值也就是array[i]
[000026] ---X-+----- | +--* BOUNDS_CHECK_Rng void
[000008] -----+----- | | +--* LCL_VAR int V01 loc0 //数组的索引i值
[000025] ---X-+----- | | \--* ARR_LENGTH int //获取数组长度
[000007] -----+----- | | \--* LCL_VAR ref V00 arg0 //数组的长度
[000035] n---G+----- | \--* IND int //获取array[i]的值
[000033] -----+----- | \--* ARR_ADDR byref int[] //获取刚刚array数组地址
//中间省略,上面已经写过了。
[000011] -----+----- \--* CNS_INT int 42

那么翻译成C# Code如下:

if(array[i]==42)
{
return true;
}
return false

这里还没有循环,因为循环在其它的Basic Block块,这里是BB02块。那么下面就是对着BB02进行优化变形,最终形成了如上边界检查去除所示的结果。关于这点,下篇再看。

结尾

作者:江湖评谈

原文:在此处

.Net8顶级技术:边界检查之IR解析(慎入)的更多相关文章

  1. 🏆【Java技术专区】「编译器专题」重塑认识Java编译器的执行过程(消除数组边界检查+公共子表达式)!

    前提概要 Java的class字节码并不是机器语言,要想让机器能够执行,还需要把字节码翻译成机器指令.这个过程是Java虚拟机做的,这个过程也叫编译.是更深层次的编译. 在编译原理中,把源代码翻译成机 ...

  2. UWP 手绘视频创作工具技术分享系列 - SVG 的解析和绘制

    本篇作为技术分享系列的第一篇,详细讲一下 SVG 的解析和绘制,这部分功能的研究和最终实现由团队的 @黄超超 同学负责,感谢提供技术文档和支持. 首先我们来看一下 SVG 的文件结构和组成 SVG ( ...

  3. 通信错误:(-1)[描述:无法解析路由器DDNS地址,请检查DDNS状态.] 解析办法

    EasyRadius提示:通信错误:(-1)[描述:无法解析路由器DDNS地址,请检查DDNS状态.] 出现以上问题,和easyradius没有直接的联系,主要产生原因有两种可能: 可能1:easyr ...

  4. [SAP ABAP开发技术总结]ABAP读写、解析XML文件

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  5. UWP 手绘视频创作工具技术分享系列 - 文字的解析和绘制

    本篇作为技术分享系列的第二篇,详细讲一下文字的解析和绘制,这部分功能的研究和最终实现由团队共同完成,目前还在寻找更理想的实现方式. 首先看一下文字绘制在手绘视频中的应用场景 文字是手绘视频中很重要的表 ...

  6. odoo 12企业版与免费社区版的区别,价格策略与技术支持指南的全面解析

    Odoo / Ps Cloud收费企业版是对社区版的极大增强,除了增加了很多功能外,最大的功能区别是企业版支持条码而社区版不支持,企业版对手机支持更好.有单独的APP,最重要区别的是企业版提供底层技术 ...

  7. 一文带你领略虚拟化领域顶级技术会议KVM Forum 2018

    KVM Forum是由Linux基金会组织的高端技术论坛会议,主要为社区各个维护者,开发人员,和用户提供一个讨论Linux虚拟化技术发展趋势以及挑战的交流场所.参会人员都集中在KVM虚拟化相关领域,是 ...

  8. 顶级技术盛会KubeCon 2020,网易轻舟布道多云环境云原生应用交付

    在日前的KubeCon 2020中国线上峰会上,VMware中国研发中心架构师.Harbor项目创始人和维护者张海宁,和网易数帆轻舟事业部架构师.Harbor维护者裴明明,共同分享了如何在多云和多集群 ...

  9. 11、NFC技术:NDEF Uri格式解析

    NDEF Uri格式规范 与NDEF文本格式一样,存储在NFC标签中的Uri也有一定的格式 http://www.nfc-forum.org/specs/spec_dashboard 编写可以解析Ur ...

  10. 9、NFC技术:NDEF文本格式解析

    NDEF文本格式规范     不管什么格式的数据本质上都是由一些字节组成的.对于NDEF文本格式来说.这些数据的第1个字节描述了数据的状态,然后若干个字节描述文本的语言编码,最后剩余字节表示文本数据. ...

随机推荐

  1. Spring--注解开发定义Bean

    注解开发 先看一看之前的bean的做法: 所谓注解开发,当然就要用到注解啊,就是在BookDao接口的实现类里面进行注解的定义 如图所示: 而在.xml文件里面,就需要进行这样一个操作: 注解当然不会 ...

  2. Nacos与OpenFeign开发

    目录 1.前言 2.生产者 3.消费者 4.扩展 1.前言 我的话是微服务B调用微服务A的controller层 2.生产者 微服务A请求接口如下: @GetMapping("/listUn ...

  3. Spark Catalyst 查询优化器原理

    这里我们讲解一下SparkSQL的优化器系统Catalyst,Catalyst本质就是一个SQL查询的优化器,而且和 大多数当前的大数据SQL处理引擎设计基本相同(Impala.Presto.Hive ...

  4. Defi开发简介

    Defi开发简介 介绍 Defi是去中心化金融的缩写, 是一项旨在利用区块链技术和智能合约创建更加开放,可访问和透明的金融体系的运动. 这与传统金融形成鲜明对比,传统金融通常由少数大型银行和金融机构控 ...

  5. React的行内样式与CSS

    如何为组件添加 CSS 的 class? 传递一个字符串作为 className 属性: render() { return <span className="menu navigat ...

  6. 淘宝/天猫获得淘宝商品评论 API 返回值说明

    item_review-获得淘宝商品评论 taobao.item_review 公共参数 API测试工具 名称 类型 必须 描述 key String 是 调用key(必须以GET方式拼接在URL中) ...

  7. [Python]Python安装教程

    anaconda Anaconda:python的一种软件发行版.Anaconda发行版会预装很多pydata生态圈里的软件,而Miniconda是最小的conda安装环境, 一个干净的conda环境 ...

  8. Redis缓冲区溢出及解决方案

    缓冲区(buffer),是内存空间的一部分.也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区. 一.Redis缓冲区溢出影响 在Redis ...

  9. 特性介绍 | MySQL 测试框架 MTR 系列教程(一):入门篇

    作者:卢文双 资深数据库内核研发 去年年底通过微信公众号[数据库内核]设定了一个目标--2023 年要写一系列 特性介绍+内核解析 的文章(现阶段还是以 MySQL 为主). 虽然关注者很少,但本着& ...

  10. Docker应用部署(Nginx、Tomcat)

    Docker hub官方链接: https://hub.docker.com 部署Nginx 官方已经给出了方法: https://hub.docker.com/_/nginx 运行容器 $ dock ...