.Net8顶级技术:IR边界检查之IR解析(二)
前言
IR技术应用在各个编程语言当中,它属于JIT的核心部分,确实有点点麻烦。但部分基本明了。本篇通过小例子了解下。前情提要,看这一篇之前建议看看前一篇:点击此处,以便于理解。
概括
1.前奏
先上C#代码:
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool Test(int[] array)
{
for (int i = 0; i < 0x12345; i++)
{
if (array[i] == 42)
{
return true;
}
}
return false;
}
Test函数经过Roslyn编译成IL代码之后,会被JIT导入及操作变成IR。
BBnum BBid ref try hnd preds weight lp [IL range] [jump] [EH region] [flags]
-----------------------------------------------------------------------------------------------------------------------------------------
BB01 [0007] 1 1 [???..???)-> BB04 ( cond ) internal
BB02 [0001] 2 BB01,BB03 4 0 [004..00B)-> BB05 ( cond ) i Loop idxlen bwd bwd-target align
BB03 [0003] 1 BB02 4 0 [00D..019)-> BB02 ( cond ) i bwd
BB04 [0005] 2 BB01,BB03 0.50 [019..01B) (return) i
BB05 [0002] 1 BB02 0.50 [00B..00D) (return) i
-----------------------------------------------------------------------------------------------------------------------------------------
可以看到IL被分割成了五个BB(basic block).注意表格的BBnum和jump列。BB01的BBnum就是BB01,它的jump是BB04。为啥是BB04?因为BB01的IR表示的是如果(i>=0x12345),则跳转到BB04,也就是BB01的正常逻辑。下面看下这个五个BB.
------------ BB01 [???..???) -> BB04 (cond), preds={} succs={BB02,BB04}
***** BB01
STMT00006 ( 0x011[E-] ... ??? )
( 7, 9) [000038] ----------- * JTRUE void
( 5, 7) [000039] J------N--- \--* GE int
( 3, 2) [000040] ----------- +--* LCL_VAR int V01 loc0
( 1, 4) [000041] ----------- \--* CNS_INT int 0x12345
------------ BB02 [004..00B) -> BB05 (cond), preds={BB01,BB03} succs={BB03,BB05}
***** 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}
***** BB03
STMT00003 ( 0x00D[E-] ... 0x010 )
[000018] -A---+----- * ASG int
[000017] D----+-N--- +--* LCL_VAR int V01 loc0
[000016] -----+----- \--* ADD int
[000014] -----+----- +--* LCL_VAR int V01 loc0
[000015] -----+----- \--* CNS_INT int 1
***** BB03
STMT00001 ( 0x011[E-] ... 0x017 )
( 7, 9) [000006] ----------- * JTRUE void
( 5, 7) [000005] J------N--- \--* LT int
( 3, 2) [000003] ----------- +--* LCL_VAR int V01 loc0
( 1, 4) [000004] ----------- \--* CNS_INT int 0x12345
------------ BB04 [019..01B) (return), preds={BB01,BB03} succs={}
***** BB04
STMT00005 ( 0x019[E-] ... 0x01A )
[000022] -----+----- * RETURN int
[000037] -----+----- \--* CNS_INT int 0
------------ BB05 [00B..00D) (return), preds={BB02} succs={}
***** BB05
STMT00004 ( 0x00B[E-] ... 0x00C )
[000020] -----+----- * RETURN int
[000036] -----+----- \--* CNS_INT int 1
preds表示能在逻辑上执行到当前块的所有快,succs表示当前语句逻辑能达到的BB块。举个例子:比如BB01,首先看下这条IR表示的如果(i>=0x12345),则跳转到BB04,也就是直接返回0。因为逻辑是索引大于了循环的最大次数,是不合理的。如果(i<0x12345),则跳转到BB02,也就是判断(array[i]是否等于42)。上面BB01的predes为空,则表示没有逻辑能达到这条语句。它的succs为BB02和BB04,跟上面的推测吻合。其它依次类推。
2.BB的IR表示
通过上面的BB01到BB05的观察,得知它们分别表示如下:
一:BB01
if(i>=0x12345)
二:BB02
if(array[i]==42)
三:BB03
i=i+1;
if(i<0x12345)
四:BB04
return 0
五:BB05
return 1
以上循环被分割成了五个BB。它的实际逻辑如下:
if(i>=0x12345)
{
return flase;
}
else
{
for(i<0x12345;i++)
{
if(array[i]==42)
{
return true;
}
}
return flase;
}
所以呢,实际是示例的for循环,被分解成了上面的代码。但是还没完,为了确保这个array[i]不会出现内存访问的错误,BB02里面有个BOUNDS_CHECK_Rng的边界检查技术,它会判断array[i]里的i索引是否查过array.length的长度,因为在for循环里面,所以每次都会判断,会增加相应的开销。为了达到最优的效果,.Net8会去掉这开销。那么应该怎么做呢?继续看。
JIT先增加BB06,BB07,BB08,BB09四个块,然后把BOUNDS_CHECK_Rng给去掉。
去掉前后对比如下。
去掉前:
[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
去掉后:
[000013] ----G+----- * JTRUE void
[000012] N---G+-N-U- \--* EQ int
[000034] ----G+-N--- +--* COMMA int
[000026] -----+----- | +--* NOP void
[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
然后再新增BB10,BB11,BB12,BB13四个BB块。这些BB块如下所示:
------------ BB01 [???..???) -> BB12 (cond), preds={} succs={BB02,BB12}
***** BB01
STMT00006 ( 0x011[E-] ... ??? )
( 7, 9) [000038] ----------- * JTRUE void
( 5, 7) [000039] J------N--- \--* GE int
( 3, 2) [000040] ----------- +--* LCL_VAR int V01 loc0
( 1, 4) [000041] ----------- \--* CNS_INT int 0x12345
------------ BB02 [???..???), preds={BB01} succs={BB03}
------------ BB03 [???..???) -> BB09 (cond), preds={BB02} succs={BB04,BB09}
***** BB03
STMT00010 ( ??? ... ??? )
( 7, 6) [000072] ----------- * JTRUE void
( 5, 4) [000071] J------N--- \--* EQ int
( 3, 2) [000069] ----------- +--* LCL_VAR ref V00 arg0
( 1, 1) [000070] ----------- \--* CNS_INT ref null
------------ BB04 [???..???) -> BB09 (cond), preds={BB03} succs={BB05,BB09}
***** BB04
STMT00011 ( ??? ... ??? )
( 7, 6) [000076] ----------- * JTRUE void
( 5, 4) [000075] J------N--- \--* LT int
( 3, 2) [000073] ----------- +--* LCL_VAR int V01 loc0
( 1, 1) [000074] ----------- \--* CNS_INT int 0
------------ BB05 [???..???) -> BB09 (cond), preds={BB04} succs={BB06,BB09}
***** BB05
STMT00012 ( ??? ... ??? )
( 9, 11) [000081] ---X------- * JTRUE void
( 7, 9) [000080] J--X---N--- \--* LT int
( 5, 4) [000079] ---X------- +--* ARR_LENGTH int
( 3, 2) [000078] ----------- | \--* LCL_VAR ref V00 arg0
( 1, 4) [000077] ----------- \--* CNS_INT int 0x12345
------------ BB06 [004..00B) -> BB13 (cond), preds={BB05,BB07} succs={BB07,BB13}
***** BB06
STMT00002 ( 0x004[E-] ... 0x009 )
[000013] ----G+----- * JTRUE void
[000012] N---G+-N-U- \--* EQ int
[000034] ----G+-N--- +--* COMMA int
[000026] -----+----- | +--* NOP void
[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
------------ BB07 [00D..019) -> BB06 (cond), preds={BB06} succs={BB08,BB06}
***** BB07
STMT00003 ( 0x00D[E-] ... 0x010 )
[000018] -A---+----- * ASG int
[000017] D----+-N--- +--* LCL_VAR int V01 loc0
[000016] -----+----- \--* ADD int
[000014] -----+----- +--* LCL_VAR int V01 loc0
[000015] -----+----- \--* CNS_INT int 1
***** BB07
STMT00001 ( 0x011[E-] ... 0x017 )
( 7, 9) [000006] ----------- * JTRUE void
( 5, 7) [000005] J------N--- \--* LT int
( 3, 2) [000003] ----------- +--* LCL_VAR int V01 loc0
( 1, 4) [000004] ----------- \--* CNS_INT int 0x12345
------------ BB08 [???..???) -> BB12 (always), preds={BB07} succs={BB12}
------------ BB09 [???..???), preds={BB03,BB04,BB05} succs={BB10}
------------ BB10 [004..00B) -> BB13 (cond), preds={BB09,BB11} succs={BB11,BB13}
***** BB10
STMT00007 ( 0x004[E-] ... ??? )
[000042] ---XGO----- * JTRUE void
[000043] N--XGO-N-U- \--* EQ int
[000044] ---XGO----- +--* COMMA int
[000045] ---X-O----- | +--* BOUNDS_CHECK_Rng void
[000046] ----------- | | +--* LCL_VAR int V01 loc0
[000047] ---X------- | | \--* ARR_LENGTH int
[000048] ----------- | | \--* LCL_VAR ref V00 arg0
[000049] n---GO----- | \--* IND int
[000050] -----O----- | \--* ARR_ADDR byref int[]
[000051] ----------- | \--* ADD byref
[000052] ----------- | +--* LCL_VAR ref V00 arg0
[000053] ----------- | \--* ADD long
[000054] ----------- | +--* LSH long
[000055] ---------U- | | +--* CAST long <- uint
[000056] ----------- | | | \--* LCL_VAR int V01 loc0
[000057] -------N--- | | \--* CNS_INT long 2
[000058] ----------- | \--* CNS_INT long 16
[000059] ----------- \--* CNS_INT int 42
------------ BB11 [00D..019) -> BB10 (cond), preds={BB10} succs={BB12,BB10}
***** BB11
STMT00008 ( 0x00D[E-] ... ??? )
[000060] -A--------- * ASG int
[000061] D------N--- +--* LCL_VAR int V01 loc0
[000062] ----------- \--* ADD int
[000063] ----------- +--* LCL_VAR int V01 loc0
[000064] ----------- \--* CNS_INT int 1
***** BB11
STMT00009 ( 0x011[E-] ... ??? )
( 7, 9) [000065] ----------- * JTRUE void
( 5, 7) [000066] J------N--- \--* LT int
( 3, 2) [000067] ----------- +--* LCL_VAR int V01 loc0
( 1, 4) [000068] ----------- \--* CNS_INT int 0x12345
------------ BB12 [019..01B) (return), preds={BB01,BB08,BB11} succs={}
***** BB12
STMT00005 ( 0x019[E-] ... 0x01A )
[000022] -----+----- * RETURN int
[000037] -----+----- \--* CNS_INT int 0
------------ BB13 [00B..00D) (return), preds={BB06,BB10} succs={}
***** BB13
STMT00004 ( 0x00B[E-] ... 0x00C )
[000020] -----+----- * RETURN int
[000036] -----+----- \--* CNS_INT int 1
-------------------------------------------------------------------------------------------------------------------
3.BB块分析
通过去掉的边界检查,进行的优化之后。新增了7个BB块,总共有13个BB块。那么这些BB干嘛的呢?实际上就是为了去掉边界检查(因为在for循环里,每次都要判断),而确保内存array[i]在正确内存范围内。逐一来看下:
BB01:
if(i>=0x12345)判断索引是否大于循环最大值
BB02
无
BB03
if(array==null) //这里是判断数组的地址是否等于0
BB04
if(i<0)判断索引是否小于0
BB05
if(array.length<0x12345)判断数组长度是否小于循环最大数0x12345
BB06
if(array[i]==42)
BB07
i=i+1索引自增
BB08
无
BB09
无
BB10
if(i<array.length) //这里跟上面的BB06一样,但是多了边界检查。BB06去掉,这里没去掉。是因为这里需要边界检查,而BB06不需要。一个快速路径,一个慢速路径。
if(array[i]==42)
BB11
i=i+1
BB12
return 0
BB02
return 1
它实际逻辑是:
if(i<0x12345 && array!= null && i>0 && array.Length >= 0x12345 )//再去掉边界检查之后的优化里,这进行大量的检查,确保array[i],在正确内存范围内。
{
for (int i = 0; i < 0x12345; i++)
{
if (array[i] == 42) 不检查边界,因为上面的if检查过了
{
return true;
}
}
}
else //如果上面的if有一个条件不符合,则进行边界检查。优化不成功
{
for (int i = 0; i < 0x12345; i++)
{
if (array[i] == 42) 这里需要边界检查也就是BOUNDS_CHECK_Rng
{
return true;
}
}
}
结尾
作者:江湖评谈
欢迎关注公众号,第一时间首发分享技术文章。
.Net8顶级技术:IR边界检查之IR解析(二)的更多相关文章
- 🏆【Java技术专区】「编译器专题」重塑认识Java编译器的执行过程(消除数组边界检查+公共子表达式)!
前提概要 Java的class字节码并不是机器语言,要想让机器能够执行,还需要把字节码翻译成机器指令.这个过程是Java虚拟机做的,这个过程也叫编译.是更深层次的编译. 在编译原理中,把源代码翻译成机 ...
- php大力力 [025节] 来不及学习和分类的,大力力认为有价值的一些技术文章合集(大力力二叔公)(2015-08-27)
php大力力 [025节] 来不及学习和分类的,大力力认为有价值的一些技术文章合集(大力力二叔公)(2015-08-27) 比较好的模版 免费模板网,提供大量DIV+CSS布局网页模板下载及后台管理 ...
- [SAP ABAP开发技术总结]权限对象检查
声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...
- AOI自动光学检测机技术在电路板检查中的应用
1.简述 AOI技术在许多不同的制造业领域使用,自从电子影像技术开始发展,就被各种人利用在不同的应用领域.大家最熟悉的数字相机.数字摄影机是大家生活中最常用到的器材之一,而工业产品的生产也大量使用这些 ...
- 一文带你领略虚拟化领域顶级技术会议KVM Forum 2018
KVM Forum是由Linux基金会组织的高端技术论坛会议,主要为社区各个维护者,开发人员,和用户提供一个讨论Linux虚拟化技术发展趋势以及挑战的交流场所.参会人员都集中在KVM虚拟化相关领域,是 ...
- 顶级技术盛会KubeCon 2020,网易轻舟布道多云环境云原生应用交付
在日前的KubeCon 2020中国线上峰会上,VMware中国研发中心架构师.Harbor项目创始人和维护者张海宁,和网易数帆轻舟事业部架构师.Harbor维护者裴明明,共同分享了如何在多云和多集群 ...
- 大数据技术之_19_Spark学习_01_Spark 基础解析 + Spark 概述 + Spark 集群安装 + 执行 Spark 程序
第1章 Spark 概述1.1 什么是 Spark1.2 Spark 特点1.3 Spark 的用户和用途第2章 Spark 集群安装2.1 集群角色2.2 机器准备2.3 下载 Spark 安装包2 ...
- JVM CPU Profiler技术原理及源码深度解析
研发人员在遇到线上报警或需要优化系统性能时,常常需要分析程序运行行为和性能瓶颈.Profiling技术是一种在应用运行时收集程序相关信息的动态分析手段,常用的JVM Profiler可以从多个方面对程 ...
- 基于HTML5技术的电力3D监控应用(二)
上篇介绍了我们电力项目的基本情况,我们选用HTML5技术还是顶着很大压力,毕竟HTML5技术性能行不行,浏览器兼容性会不会有问题,这些在项目选型阶段还是充满疑惑,项目做到现在终于快收尾了我们才敢松口气 ...
- 通信错误:(-1)[描述:无法解析路由器DDNS地址,请检查DDNS状态.] 解析办法
EasyRadius提示:通信错误:(-1)[描述:无法解析路由器DDNS地址,请检查DDNS状态.] 出现以上问题,和easyradius没有直接的联系,主要产生原因有两种可能: 可能1:easyr ...
随机推荐
- Java+Redis 通过Lua 完成库存扣减,创建消息队列,异步处理消息--实战
需要完成功能 借助redis Stream 数据结构实现消息队列,异步完成订单创建,其中涉及到了缓存(击穿,穿透,雪崩),锁(Redisson),并发处理,异步处理,Lua脚本 IDE:IDEA 20 ...
- Promise的使用及原理
此文章主要讲解核心思想和基本用法,想要了解更多细节全面的使用方式,请阅读官方API 这篇文章假定你具备最基本的异步编程知识,例如知道什么是回调,知道什么是链式调用,同时具备最基本的单词量,例如page ...
- 如何理解redis两种不同的持久化方式
其实redis就是一种高级的以键值对形式存储数据的数据库,而它的好处就是他可以支持数据的持久化,其实redis之所以会有这样的优点,主要是因为,redis的数据都是存放在内存中的,如果不配置持久化,那 ...
- Mac连接Win的方法
前言 我们都知道,Mac和Win还是非常不一样的,作为Macdows双修选手,我今天给大家介绍一些从Mac连接Win的方法. Win的RDP 由于Win默认未安装ssh,我们最常使用的连接方式则是使用 ...
- 提供一款局域网聊天小工具,对接了chatGPT3.5
账号.密码随便填,只要自己能记住就可以,网络地址如效果图(本机IP地址+8080端口),效果图如下 服务地址用本机IP,端口可以随便用一个,如8888,填完记得点击按钮开启: 通讯地址如果你有其他人的 ...
- 游戏模拟——Position based dynamics
目录 Verlet积分 基本积分方法 Verlet 算位置 Verlet 算速度 PBD 基于力的方法解碰撞 过冲问题 基于位置的方法解碰撞 算法流程 求解器借用的思想 关于动量守恒 约束投影 简单约 ...
- 关于取消DevTools listening on ws://127.0.0.1…提示的方法
Python代码写好之后,通过任务计划程序定期执行.py文件,但总会有命令窗口,虽然不影响程序执行,但每次需要手动叉掉比较烦.于是我网上搜索了一些方法. 网上的方法并没有直接解决我的问题,但我借助搜索 ...
- Mysql关联删除CSV中的相关数据
问题描述:提供一个csv文件,记录的是一些不同数据库的不同表中的共同字段account_id数据,需要在A库的account表中做关联删除 解决思路:csv文件中储存的都是account_id,六位纯 ...
- C#模拟C++模板特化对类型的值的支持
概述 C++的模板相比于C#,有很多地方都更加的灵活(虽然代价是降低了编译速度),比如C++支持变长参数模板.支持枚举.int等类型的值作为模板参数. C++支持枚举.int等类型的值作为模板参数,为 ...
- TiDB在科捷物流神州金库核心系统的应用与实践
导读:在经过了近半年的测试验证和迁移准备之后,神州金库3.0核心系统 WMS 正式从 MySQL 迁移到了分布式 HTAP 数据库 TiDB,上线后不久即经历了第一次双11的考验,TiDB的性能和稳定 ...