﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-&lt;table border="0" cellspacing="0" cellpadding="0" style="margin-left:5%;display:inline;height:30px;"&gt;&lt;tr&gt;&lt;td style="font-weight:bolder; font-size:16px; line-height:30px;"&gt;一年十二月&amp;nbsp&amp;nbsp谁主春秋&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style="font-size:14px; line-height:30px;"&gt;关注：基础系统工程 密码学 人工智能&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;-随笔分类-Compiler</title><link>http://www.cppblog.com/qinqing1984/category/21446.html</link><description /><language>zh-cn</language><lastBuildDate>Tue, 12 Dec 2023 10:36:22 GMT</lastBuildDate><pubDate>Tue, 12 Dec 2023 10:36:22 GMT</pubDate><ttl>60</ttl><item><title>浅谈体系结构与内联优化</title><link>http://www.cppblog.com/qinqing1984/archive/2023/11/16/230184.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Thu, 16 Nov 2023 15:32:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/11/16/230184.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230184.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/11/16/230184.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230184.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230184.html</trackback:ping><description><![CDATA[周知内联是为了消除函数调用的代价，即四大指令序列：调用前序列、被调者起始序列、被调者收尾序列、返回后序列。它们通常对应到体系结构调用者保存/恢复寄存器集合与被调者保存/恢复寄存器集合之约束。这个本质也是内联的前提。试问如果有某体系结构比如S，它任意深度的函数调用代价几乎为零，那么显然内联是没意义没必要的。但是S可能存在吗？我认为不太可能。因为机器的资源比如寄存器集数量与堆栈空间是有限的，且调用需要知晓上下文，所以不能够支持任意深度的调用，但是可以支持有限深度比如4层调用，这4层调用代价几乎为零，假设再来一层，那么第5层调用代价就不为零了，这时如果内联第5层就变成4层调用，代价又几乎为零。综上所述，内联无论在何种体系结构，即使在一定深度内没意义也不会破坏性能。<br /><br />
体系结构直接影响程序性能。主要体现在指令集、寄存器、cache三块。它们对于编译器实现代码优化必须都考虑，尤其cache比如内联优化、循环展开、基本块布局、函数重排，如果不是因为有cache这玩意，内联优化的复杂性会大为降低，因为不用考虑代码膨胀引起的副作用即cache缺失，只要评估函数的指令数与动态执行消耗的关系，指令数很少但执行耗费很多时钟周期的，则不宜内联，尤其函数为非叶子结点；指令数很多但执行耗费较少的，则可仅内联其中的快速路径代码。因现实存在cache这玩意，就必须权衡代码膨胀带来的副作用，是否能接受一定的膨胀，需要精确评估，构建函数调用频率与其静态调用位置的矩阵，计算收益比如平均执行一次的耗时是否减少，若收益为正且明显则可内联，否则不宜内联。<br /><br />有些编译器为了简单处理，不会内联带静态变量的函数哪怕指令数很少，或者内联不太正确比如<span style="color: #ff00ff;">LLVM</span>（详见下文）。其实单从技术上可以做到，不过要复杂些，复杂在于链接器的协作。为了保证函数级静态变量的语义，编译时要预留全局唯一标志与构造函数的占位符，在调用者体内插入对全局唯一标志的（位）判断（标志字的一位对应一个静态变量，表明是否已构造或初始化赋值）、构造函数调用/初始化赋值、置位标志，而链接时要确定全局唯一标志及构造函数的地址。静态变量、全局唯一标志放于可执行文件的数据区，全局唯一构造/初始化及析构函数放于代码区，具体布局位置可以灵活，比如. data. static_obj，. text. obj. ctor/dtor。如果这种函数性能影响较大需要内联优化，而编译器不支持，有个替代的办法是用全局变量或文件/类级别的静态变量，辅以对应标志处理一次性构造或初始化赋值（必要时将这处理封装为一个函数以确保目标函数被内联），可达到同样效果不足之处是作用域扩大了。<br /><br /><strong>关于LLVM对于带静态变量的函数之内联的测验结果</strong><br /><img src="http://www.cppblog.com/images/cppblog_com/qinqing1984/llvm-inline-test-all-c-codes.png" width="912" height="955" alt="" /><br /><br /><img src="http://www.cppblog.com/images/cppblog_com/qinqing1984/llvm-inline-test-compile-out-disa.PNG" width="913" height="381" alt="" /><br /><br /><img src="http://www.cppblog.com/images/cppblog_com/qinqing1984/llvm-inline-test-test_1_disa.PNG" width="880" height="267" alt="" /><br /><br /><img src="http://www.cppblog.com/images/cppblog_com/qinqing1984/llvm-inline-test-test_2_disa.PNG" width="876" height="147" alt="" /><br /><br /><img src="http://www.cppblog.com/images/cppblog_com/qinqing1984/llvm-inline-test-main_disa.PNG" width="770" height="101" alt="" /><br /><br /><img src="http://www.cppblog.com/images/cppblog_com/qinqing1984/llvm-inline-test-test_1_ir.PNG" width="917" height="598" alt="" /><br /><br /><img src="http://www.cppblog.com/images/cppblog_com/qinqing1984/llvm-inline-test-test_2_ir.png" width="918" height="599" alt="" /><img src ="http://www.cppblog.com/qinqing1984/aggbug/230184.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-11-16 23:32 <a href="http://www.cppblog.com/qinqing1984/archive/2023/11/16/230184.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>浅谈图论在寄存器分配中的应用</title><link>http://www.cppblog.com/qinqing1984/archive/2023/10/04/230129.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 04 Oct 2023 05:08:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/10/04/230129.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230129.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/10/04/230129.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230129.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230129.html</trackback:ping><description><![CDATA[<div><strong>&#8203;1. 区间图</strong>：用于局部寄存器分配，基本块内的每个活跃范围看作一个区间（最早定义位置+最新使用位置），所有活跃范围构成区间图。区间图是一种不精确的冲突图（因为高估了活跃范围的范围而导致伪冲突，比如认为一个复制操作连接的或两个源相同目标不同的复制操作产生重叠的两个活跃范围冲突，但实际没有冲突），优势在于着色是P（复杂度O(|V|)或O(|E|)）而非NP问题。llvm早期的线性扫描分配器是基于区间图在全局的扩展，比较适用于JIT编译（减少编译时间）</div>
<div><strong>&#8203;2. 一般图</strong>：用于全局寄存器分配，是一种精确的冲突图（由一组定义与一组使用构成的网络）。优势在于努力最小化溢出活跃范围而生成高效执行的代码，但会牺牲编译时间。llvm的greedy寄存器分配是基于一般图的代表。编译器使用的冲突图可能会将机器约束条件比如多寄存器值/调用约定编码进去而存在重复边，导致不满足图论中的<span style="color: #ff00ff;">简单图</span>定义，故这里采用一般图</div>
<div><strong>&#8203;3. 弦图</strong>：定义详见<a href="https://oi-wiki.org/graph/chord/" target="_blank">https://oi-wiki.org/graph/chord</a>。基于静态单赋值形式名建立的冲突图是弦图。优势在于可以做到最佳着色（复杂度O(|V|+|E|)）而非启发式（基于一般图的全局寄存器分配使用启发式），利于减少寄存器压力。劣势在于必须将指派寄存器后的仍然为静态单赋值代码转换为机器码，而这种转换可能增加寄存器压力，以及插入一些可能非必要的复制操作，若复制操作实现的数据流与ssa phi函数对应，则分配器无法合并这种复制，这将破坏弦图的性质</div>
<div><strong>&#8203;4. 冲突图拆分</strong>：查找其中的团分割即<span style="color: #ff00ff;">连通子图</span>，移除它划分得到不相交的一些子图，这样一来，各子图可独立着色（有点类似活跃范围拆分）而利于减少寄存器压力，另外实现上还能节省下三角布尔矩阵（用于快速判断两结点是否冲突）的规模</div>
<div>&#8203;#############################</div>
<div>寄存器分配与图论的染色理论相关。其它的比如常量传播与格代数及不动点相关，循环优化与多面体、矩阵相关。这三方面是我目前看到的编译器所用数学理论</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230129.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-10-04 13:08 <a href="http://www.cppblog.com/qinqing1984/archive/2023/10/04/230129.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>忙表达式数据流分析示例</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/30/230118.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Sat, 30 Sep 2023 00:47:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/30/230118.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230118.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/30/230118.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230118.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230118.html</trackback:ping><description><![CDATA[<div style="text-align: left;"><img src="http://www.cppblog.com/images/cppblog_com/qinqing1984/busy_expr_data_flow_analysis_1_1.png" width="994" height="1387" alt="" /></div>
<br /><div style="text-align: left;"><img src="http://www.cppblog.com/images/cppblog_com/qinqing1984/busy_expr_data_flow_analysis_2_2.png" width="994" height="1388" alt="" /></div><br /><div style="text-align: left;"><img src="http://www.cppblog.com/images/cppblog_com/qinqing1984/busy_expr_data_flow_analysis_3_3.png" width="994" height="1411" alt="" /></div>
<br /><div style="text-align: left;"><img src="http://www.cppblog.com/images/cppblog_com/qinqing1984/busy_expr_data_flow_analysis_4.png" width="994" height="1446" alt="" /></div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230118.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-30 08:47 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/30/230118.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>基于矩阵法分析改进指令调度</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/23/230098.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Sat, 23 Sep 2023 04:14:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/23/230098.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230098.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/23/230098.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230098.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230098.html</trackback:ping><description><![CDATA[<div><img src="http://www.cppblog.com/images/cppblog_com/qinqing1984/bb-list-schedule-algorithm.png" width="644" height="406" alt="" /><br />
&nbsp; 周知编译原理龙书阐述的基本块指令调度算法，它所使用的空的资源预约表RTD与每个指令的资源预约表RT，可以看作二维矩阵，行表示时钟周期、列表示cpu资源，其定位的元素值1表示占用/预约，0表示空闲/非预约。前者是随周期递增而动态扩大的矩阵，后者是固定尺寸(维数)的矩阵（指令花费周期与每周期预约资源皆已知）。在调度时，按带优先级比如关键路径的拓扑排序基本块内的指令，顺序选取一条指令Inst，计算每前驱发射周期加延迟的结果tmp，取所有tmp的最大值tmax作为Inst的发射周期，再判断处理器资源是否可用，即RTD和RT作<strong>与运算</strong>，得到一个新矩阵RTN，若RTN为<span style="color: #ff00ff;">全零矩阵</span>则tmax为Inst的最终发射周期，否则递增tmax再做矩阵与运算，直至得到全零矩阵。最后更新RTD，即RTD与RT作<strong>或运算</strong>结果存于RTD。重复上述过程直到基本块末尾。</div>
<div>综上&#8203;不难看出，如果一个基本块很大比如有1000条指令，平均每指令花2个周期，则RTD需要2000个条目，若一条目即矩阵每行占用32字节（256种资源数），则总量约64k。当然这对于现代内存体量来说不算什么，但可以有更好的节省内存的做法：RTD尺寸其实可以相对固定，其上限为基本块中耗费周期最多指令的周期的一个大于1常数因子倍（为兼顾指令并行性），这样一来就要增加当指令完成时（当前指令发射周期大于前一条的终止周期时复位前一条指令的RTD）从发射周期处复位RTD即作一个矩阵<strong>反运算</strong>的操作，其它步骤对应的矩阵与、矩阵或运算的操作保留不变。另由于RTD固定了尺寸，因此发射周期递增后要取模</div>
<div>【备注】以上是我针对简单机器模型（每种资源数量仅一个，比如整数运算单元1个，内存访问单元1个，浮点运算单元1个）用布尔矩阵作的优化。如果是复杂的超标量机器即每种资源数有多个，那么只需修改如下：布尔矩阵换成整数矩阵；新增一个机器资源可用总数整数矩阵RDA（单列资源数同值），布尔矩阵与运算换成<strong>加法</strong>并与RDA<strong>比较</strong>，若<strong>大于</strong>RDA则递增tmax；布尔矩阵或运算换成加法；布尔矩阵反运算换成<strong>减法</strong>，RTD减RT存于RTD</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230098.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-23 12:14 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/23/230098.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于程序编译中的图论问题思考</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/07/230068.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 06 Sep 2023 22:50:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/07/230068.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230068.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/07/230068.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230068.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230068.html</trackback:ping><description><![CDATA[<div>设程序片段S=if C then S1 else S2，S1和S2可以是由赋值、条件、循环构成的复杂语句，S不为当前程序最后语句或某个循环主体最后语句，则S对应的流图生成的深度优先生成树T有3条树边，有S1的出口数+S2出口数-1条交叉边。为什么是3条树边？C到S1、C到S2、S1或S2到S3（S3是S后继结点，下同）。为什么交叉边数是S1出口数+S2出口数-1？因为流图中S1出口及S2出口到S3的边，在生成T的过程中，只有1个出口比S3先访问，这对应形成1条树边，访问S3后再访问其它出口，这对应形成其它出口到S3的交叉边，注意这里没有前向边，因为较晚访问的出口在T中不可能是S3的祖先。如果把S改为if C then S1，情况会怎样？结果取决于生成T的过程中先访问S3还是S1。若先访问S3，则有树边2条：C到S3、C到S1，交叉边数等于S1的出口数：S1的每个出口到S3各一条边，没有前向边。若先访问S2，则有树边1条：C到S2，前向边1条：C到S3，交叉边数等于S1出口数-1。</div><div>总结此类问题分析的基本思路是对程序控制结构先构建流图，再构建深度优先生成树，辨别其中的前向边、交叉边、后退边</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230068.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-07 06:50 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/07/230068.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>函数式语言编译优化</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/07/230067.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 06 Sep 2023 22:47:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/07/230067.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230067.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/07/230067.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230067.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230067.html</trackback:ping><description><![CDATA[<div><strong>1.闭包记录分配</strong>：若逃逸分析能识别哪些闭包记录在创建它们的函数中是出口不活跃的，则这些闭包记录可分配在栈帧中（不再是堆中）</div><div><strong>2.内联扩展</strong>：由于小函数较多，因此内联可以免去调用开销而提高性能，对于递归函数，需先用循环前置头转换再内联，如果是尾递归函数，可先使用尾调用优化删除递归。如果一个函数的所有调用都被内联扩展，并且该函数没有作为参数传递或其它方式被引用，那么可以删除这个函数本身即函数定义。内联扩展可以继续作用于扩展后的函数体，只要存在函数调用，这也叫层叠式内联</div><div><strong>3.循环不变量参数外提</strong>：递归函数经过循环前置头转换后，若每次递归调用头函数，传入的某些参数值总不变，则可以将它们从函数参数中删除，函数体中的每次使用出现用序曲函数对应的参数名替换</div><div><strong>4.解开嵌套的let</strong>：将嵌套的多层let中的代码合并为一个let中的代码，in中的代码不变</div><div><strong>5.避免代码膨胀</strong>：由于内联复制函数体，通常使程序体积变大，且层叠式内联可无限扩展下去，因此为避免代码膨胀，有如下启发式策略对内联进行控制</div><div>&nbsp; a) 只内联执行很频繁的函数调用，可根据静态估计比如循环嵌套深度、迭代次数，或根据执行剖面分析反馈，计算函数的执行频率</div><div>&nbsp; b) 内联很小的函数，其函数体不会比直接调用多出较多指令</div><div>&nbsp; c) 内联只调用一次的函数，然后删除原来的函数定义</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230067.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-07 06:47 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/07/230067.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>动态二进制优化与静态编译优化的区别</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230064.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 06 Sep 2023 15:44:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230064.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230064.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230064.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230064.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230064.html</trackback:ping><description><![CDATA[<div><strong>1. 输入</strong>：前者是二进制可执行程序，后者是高级语言源程序</div><div><strong>2. 优化对象</strong>：前者主要是直线型代码区域比如踪迹或超块（热点路径代码），超块类似后者中的扩展块；后者是控制流图，即所有代码块，不限于热点路径代码。超块构造类似后者中的基本块放置和过程放置</div><div><strong>3. 优化方法</strong>：前者要运行时采集剖析数据比如结点剖析和边剖析，再基于剖析数据形成有利于指令cache局部性的超块，然后在超块上作常量传播、常量折叠、强度削弱、复写传播、死代码消除、公共表达式消除等基本优化，也会作指令重排，但考虑到陷阱处理要恢复精确的客户进程状态，因此比较受限，没有后者中的指令重排自由。后者如果基于剖析作优化，那么效果和前者差不多</div><div><strong>4. 寄存器分配</strong>：都是基于活跃范围的冲突图着色算法，但前者考虑到陷阱处理会延长相关寄存器的活跃范围，而后者不用</div><div>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;</div><div>总结：二进制优化所用的技术和编译优化其实相同，不同的是为陷阱处理所作的改变调整，以及运用在热点代码块而非所有块</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230064.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-06 23:44 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/06/230064.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>NFA、DFA、正则表达式的互转复杂度总结</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230063.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 06 Sep 2023 15:42:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230063.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230063.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230063.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230063.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230063.html</trackback:ping><description><![CDATA[<div><strong>1. NFA到DFA</strong>：设NFA的状态数为n，根据子集构造法，则至多有2^n个状态转移，对每个状态转移，其状态分量至多有n个状态，每个状态计算它的可达状态集合耗时为O(n^2)，另可达状态集合的并耗时为O(n^2)，故一个转移耗时为n*O(n^2)=O(n^3)，则所有转移总耗时为O(n^3*2^n)。由于实际产生的状态数远小于2^n（通常为n），因此耗时为O(n^3*s)，s为DFA实际具有的状态数</div>
<div><strong>2. DFA到NFA</strong>：转化方法是修改转移表，对每个状态转移的目标状态加上集合括号（因NFA对特定输入可能有多个目标状态，故为集合），若转为&#163;-DFA，则还需对每个状态增加对&#163;的转移为空集。该方法耗时为O(n)，n为DFA的状态数</div><br />
<div><strong>3. DFA到正则表达式</strong>：设DFA状态数为n，根据递推公式R(i,j,k)=R(i,j,k-1)+R(i,k,k-1)R(k,k,k-1)^*R(k,j,k-1)（1&lt;=i&lt;=j&lt;=n，0&lt;=k&lt;=n）来逐步构造表达式，最终的表达式就是所有R(1,j,n)的并，其中j为可接受状态。该过程会产生总共n^3+n^2个表达式，每次k递增导致表达式长度增为4倍，故总耗时为O(n^3*4^n)。另一种更快的方法是消除所有除初始和接受状态外的中间状态，每次消除一个，就合并其前驱经过它到其后继的正则表达式和前驱直接到后继的正则表达式，因前驱或后继至多n-2个，则共有(n-2)^2个前驱到后继的直通边，且中间状态至多n-2个，故耗时为O(n^3)；最后合并各接受状态的正则表达式，因接受状态至多n-1个，故耗时为O(n)。故总耗时为O(n^3)</div>
<div><strong>4. 正则表达式到&#163;-NFA</strong>：作词法分析，对每个终结符号构建状态结点及转移边，即子&#163;-NFA，特定符号对应用并、连接、闭包、结合之一联合已构建的子&#163;-NFA，耗时为O(n)，n为正则表达式的长度</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230063.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-06 23:42 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/06/230063.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于格的基本定理简要总结</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230062.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 06 Sep 2023 15:39:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230062.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230062.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230062.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230062.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230062.html</trackback:ping><description><![CDATA[<div><strong>1. 三条定律</strong>：交换律、结合律、吸收律（对于半格是幂等律），吸收律包含了幂等律</div>
<div><strong>2. 上下界</strong>：交半格每对元素都有唯一最大下界，并半格每对元素都有唯一最小上界，格每对元素都有唯一最大下界和唯一最小上界</div>
<br />
<div><strong>3. 格定义一个偏序</strong>，偏序有三个性质：自反性、反对称性、传递性</div>
<div><strong>4. 格与偏序的关系</strong>：每个格对应一个偏序，但不是所有偏序都对应一个格，要满足每对元素都有唯一最小上界和（或，对于半格）唯一最大下界。如果集合中的任何一个子集（包括空集）均存在最小上界和最大下界，那么对应一个完备格</div>
<br />
<div>5. 任何元素有限的格都是完备格，格中的交运算和并运算对于其定义的偏序来说是单调的</div>
<div>6. 格的乘积、和、提升、映射仍然是格，利用这个性质，可以在已有格的基础上增量地构造描述能力更丰富的格，这种技术称为论域精化，是提高程序静态分析精度的重要指导思想之一</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230062.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-06 23:39 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/06/230062.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>浅谈重命名</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230061.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 06 Sep 2023 15:35:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230061.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230061.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230061.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230061.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230061.html</trackback:ping><description><![CDATA[<div>周知cpu为方便乱序执行，内部会使用重命名寄存器技术消除数据依赖（war和waw）。编译器在如下场景也会用到重命名</div><br />
<div><strong>&#8203;1. 静态单赋值</strong>。过程内的每个变量唯一定义一次，原有相同的则会重命名，包括phi结点的定值</div>
<div><strong>&#8203;2. bb表调度</strong>。为消除反相关依赖即war，可以重命名读操作使用或写操作定义的值，这样能调度产生总时钟周期更少的指令序列，但可能增加寄存器压力导致溢出而新增了长延迟操作（内存加载/存储）并迫使另一轮调度</div>
<div><strong>&#8203;3. ebb表调度</strong>。对于某一ebb的一条路径p，p存在过早退出路径pe，p和pe的公共前缀是基本块b，当调度p时，如果某个操作i向后移动到b，且i定义的值杀死了pe上的同名值，那么需要重命名i的定值。若i的定值被重命名，且其在p的出口处是活跃的，则调度器需要在出口处复制回原来的名字</div>
<div><strong>&#8203;4. trace表调度</strong>。踪迹不同于ebb路径，它允许中间存在多个前驱即入口的基本块，而后者不能。当调度存在多入口的块b的某踪迹t时，t上的某操作i可能前向移动跨越b（t外的代码路径需作补偿），若i杀死了一个活跃范围跨越b的值，则需要重命名i的定值；同理，若i向后移动跨越b且杀死了t上的某值，则需重命名i的定值，这时t外的代码路径补偿可以使用同一名字</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230061.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-06 23:35 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/06/230061.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>不可达代码与死代码的区别及删除</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230060.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 06 Sep 2023 15:33:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230060.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230060.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230060.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230060.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230060.html</trackback:ping><description><![CDATA[<div>1. 不可达代码是指无论输入什么都不会执行的代码，对过程而言，即是从入口基本块到不了（没有路径可达）的那些基本块；死代码是指可达但计算了后面任何可执行路径都不会使用其计算结果的代码，比如死变量和死指令</div><br />
<div>2. 不可达代码的识别本质是有向图的可达性判定与传递闭包计算问题，一般用DFS法处理。先找到从入口基本块不可达的基本块，再删除（同时改变其前驱和后继基本块的指向），直到找不到为止。死代码的识别可用活跃分析或必要指令标记法，对于活跃分析，删除基本块出口不活跃的变量定值，以及它所使用不活跃操作数的定值；对于标记法，从必要指令出发，根据def-use链和use-def链，不断标记对其操作数有贡献的指令，最后删除没被标记的那些指令</div><br />
<div>3. 不可达代码和死代码可能来源于程序员，更可能源于编译器的其它一些优化产生，删除优化它们能显著减小代码体积，对执行速度有间接的影响，因为可能改善指令高速缓层的利用率</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230060.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-06 23:33 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/06/230060.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>循环不变代码外提</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230059.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 06 Sep 2023 15:30:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230059.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230059.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230059.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230059.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230059.html</trackback:ping><description><![CDATA[<div>1. 目的是识别循环中那种在每个迭代都产生相同值的计算，并将它们移到循环之外。注意，如果一个计算出现在嵌套循环内，对外循环的特定迭代而言，内循环的每个迭代都产生相同的值，但外循环的不同迭代产生不同的值，那么这种计算将移到内循环外，而非外循环外</div><br />
<div>2. 识别循环不变量可以基于数据流分析求得的use-def链，一条指令是循环不变的，当它的每个操作数满足以下条件之一</div>
<div>a）该操作数是常数</div>
<div>b）该操作数的所有到达定值在循环之外。因为若有一个在循环内，则该指令就可能是循环变化的，除非那个定值是循环不变量</div>
<div>c）该操作数只存在一个为循环不变量的到达定值，且该指令之前没有对其左部变量（若有）的使用。因为若有多个这样的定值，则该指令就可能是循环变化的，除非多个定值结果都一样；因为若前面有对其左部变量的使用，则该指令的赋值就杀死了左部变量的初值，这样外提后左部变量第一次迭代就会使用错误的定值</div><br />
<div>3. 由于以上条件没考虑到控制流分析，不能保证循环不变量在每个迭代中执行，以及循环不变量之左部变量的所有使用都是相同的值。因此为了保证外提后的代码行为正确，还需要满足条件：循环不变量所在基本块必须是循环中所有使用了其左部变量的基本块和所有出口基本块的必经结点。当外提循环不变量后，考虑到循环有可能执行0次即一开始就不满足循环进入条件，可以用是否进入循环的测试条件来保护前置块，即识别终止条件是否一开始就为false，来保护它。这种方法总是安全的，但增加了代码体积。不过若终止条件恒为true或false，则常数传播分析会删除这个冗余测试，如果为false，那么还会删除前置块</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230059.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-06 23:30 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/06/230059.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>过程内优化</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230058.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 06 Sep 2023 15:23:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230058.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230058.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230058.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230058.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230058.html</trackback:ping><description><![CDATA[<div><strong style="font-size: 11pt;">叶调用优化与收缩包装</strong></div>
<div>1. 叶调用优化适用于被调者是不调用任何过程的过程之场景，这种过程叫叶过程</div>
<div>2. 有几种可能的优化</div>
<div>a）如果过程的实现使用display数组来寻址非局部变量，那么叶过程可避免在起始代码序列中更新display数组</div>
<div>b）如果叶过程内不使用由被调者保存的寄存器（寄存器分配器应设法优先使用由调用者保存的寄存器），那么可避免起始代码序列中保存代码和收尾代码序列中恢复代码。很小的叶过程很可能不使用到被调者保存的寄存器，只使用部分调用者保存的寄存器的叶过程，那么调用者也可以避免一部分寄存器保存与恢复代码</div>
<div>c）如果调用者有很多次调用叶过程，而且两者代码同时可见，那么叶过程不必自己分配栈帧，由调用者一次性分配好</div>
<div>3. 收缩包装是叶调用优化的一种推广，目的是尽可能去掉过程起始代码序列和收尾代码序列中实际没用的寄存器保存恢复代码。可以先用数据流分析来计算每个基本块的保存寄存器集合（基本块入口可预见但其前驱不可预见且入口不可达的那些寄存器）与恢复寄存器集合（基本块出口可达但其后继不可达且出口不可预见的那些寄存器），再在保存寄存器集合非空的基本块入口处插入save指令（插入点已是最早的合适的位置），恢复寄存器集合非空的基本块出口处插入restore指令（插入点已是最晚的合适的位置）</div>
<div></div>
<br />
<div><strong style="font-size: 11pt;">尾调用优化与尾递归删除</strong></div>
<div>1. 尾调用优化的条件是两个（不同）过程编译时同时可见，比如处于同一编译单元，或调用者有足够多的、使得优化可能发生的关于被调用者的信息</div>
<div>2. 尾调用优化的实现，因为被调者返回后代码序列到调用者收尾代码序列之间不存在有用计算，所以原来标准链接处理要保存的那些寄存器不可能活跃，首先要裁剪调用前代码序列即不保存由调用者保存的寄存器和不压栈返回地址，以及裁剪被调过程的起始代码序列即不保存由被调者保存的寄存器和不分配新栈帧（借用调用者的栈帧，若被调者的栈帧比调用者的大，则需按两者之差扩展栈帧），然后转移到被调者裁剪过的起始代码序列，最后修改被调过程的收尾代码序列：正确释放栈帧，比如用帧指针赋给栈指针，使之直接返回到调用者的调用者（比如o调用p，p调用q，q是尾调用，那么优化后q实际返回到o）。综上可得，尾调用优化减免了压栈返回地址与保存寄存器的开销</div>
<div>3. 尾递归删除是尾调用优化的一种特例，由于调用者和被调者是同一过程，因此不存在扩展栈帧和额外释放栈帧，只须改变参数及跳转到过程入口处即可</div>
<div></div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230058.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-06 23:23 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/06/230058.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>数据流分析之迭代算法和区域分析算法</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230057.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 06 Sep 2023 15:18:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230057.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230057.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230057.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230057.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230057.html</trackback:ping><description><![CDATA[<div><strong>1. 数学基础</strong>：两者的共同点是都基于数据流值的半格和对组合运算封闭的传递函数，不同点是区域分析算法还要求传递函数是一个半格，不仅支持组合运算，而且支持交汇运算和闭包运算，交汇运算用于把有相同后继的不同执行路径组合起来，闭包运算用于环上（比如循环）执行零到多次的效果</div><br />
<div><strong>2. 流程</strong>：迭代算法由初始化和循环求不动解组成，以前向数据流为例，其中初始化包括初始化入口基本块的out集合为合适值，其它基本块的out集合为半格的顶元素；循环求不动解遍历除入口外（因为入口的out不会变）的每个基本块，计算其out集合，直至所有基本块的out不再改变。区域分析算法由计算层次区域序列、构造区域传递函数和计算各区域入口值组成，计算层次区域序列自底向上，基本块为叶子区域，自然循环分为循环体区域和循环区域，都是内部区域，不是自然循环的整个流图为根区域；区域传递函数有2个，一是R区域入口到其直接子区域S的入口的数据流值传递，记作Fin(R,S)，另一是R区域入口到其直接子区域出口基本块B（可能有多个）出口处的数据流值传递，记作Fout(R,B)，区域传递函数的计算自底向上，对于叶子区域，Fin是恒等函数，Fout和迭代算法的传递函数一样，取决于具体数据流问题；对于更大的区域（非叶子区域），遍历每个子区域，Fin由所有Fout(R,B)交汇而成，B为S在R中的前驱，若R为循环区域，则再求Fout的闭包，遍历S的每个出口基本块B，Fout由Fout(S,B)和Fin(R,S)组合而成。计算各区域入口值自顶向下，根区域的In值等于流图入口的In值，其它区域S的In值等于Fin(R,S)，R为父区域，所有Fin在前一环节已构造好</div><br />
<div><strong>3.</strong> <strong>结果</strong>：对同一数据流问题比如到达定值，两种算法求得的数据流值是一样的。为什么区域分析算法是正确的？因为它实际是按照程序控制流来构造传递函数的，包含了所有可能执行路径数据流值传递的效果，这相当于迭代算法求不动解的过程，所以最后只要一个流图的入口值，就能算出各区域的入口值。为什么迭代算法是收敛的？因为半格是单调的且高度有穷。收敛速度取决于遍历基本块的顺序，如果按基本块深度优先排序（逆后序）遍历，那么迭代轮数不超过流图的深度（各条无环路径后退边的最大数）加2</div><br />
<div><strong>4. 区别</strong>：迭代算法用于可归约流图和不可归约流图，区域分析算法仅能用于可归约流图</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230057.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-06 23:18 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/06/230057.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>缓存优化之过程内代码置放</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230056.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 06 Sep 2023 15:15:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230056.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230056.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230056.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230056.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230056.html</trackback:ping><description><![CDATA[<div>1. 作为指令高速缓层优化的一种重要技术，它根据CFG流图边的执行频率将频繁执行的基本块排列在一起，并布局那些基本块在下降分支路径，而不在一起的也就是很少执行的基本块布局在转移分支路径。这样做一来可以使取到I-cache中的指令实际被执行的比例较高，二来对于某些体系结构上转移和下降路径延迟不等的分支指令，可以降低跳转延迟</div><br />
<div>2. 实现过程内代码置放有以下几个环节：</div>
<div>a）获取剖析数据：编译器可以先在基本块出口处插入代码以统计到其后继基本块的执行次数，作为CFG流图边的权重，然后编译生成可执行文件，输入代表性数据运行它，结果输出一个数据文件，用于第二次编译，这次编译实施过程内代码置放优化</div>
<div>b）以链的形式构建热路径：热路径是CFG路径的一个集合，其中包括频繁执行的那些边，每条路径是一个或多个基本块按边的方向构成的链，每个链关联一个优先级，用于布局代码的先后顺序。初始时，每个基本块构成一个只有它本身的链，其优先级为CFG流图边的数量或者更大值；接下来，在CFG中按权重降序遍历每条边&lt;x,y&gt;（x不等于y），若x是某个链a的尾结点且y是某链b的头结点，则把b合并到a后面，更新a的优先级为a原来优先级、b优先级、P三者的最小值，同时递增P，其中P为链合并操作的计数器，用于决定链的相对次序由低到高排列，初值为0。当遍历结束时，所有热路径构建完成</div>
<div>c）进行代码布局：经过前一环节，就得到了链的集合。首先从链集合找出含有入口基本块的链t，将t加入工作表WL；然后从WL移出一个优先级最低的链c，按序（构建链时加入基本块的顺序）遍历c的每个基本块x，把x放在过程可执行代码体的末端，对于边&lt;x,y&gt;，将包含y的链t加入WL（若t不在WL中），重复该过程直至WL为空</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230056.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-06 23:15 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/06/230056.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>前期优化之全局复写传播</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230055.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 06 Sep 2023 15:13:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230055.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230055.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230055.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230055.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230055.html</trackback:ping><description><![CDATA[<div><strong>【输入输出】</strong></div>
<div>一个过程的所有基本块，除entry和exit外的每个基本块包含指令序列</div><br />
<div><strong>【流程】</strong></div>
<div>由前向数据流分析、局部复写传播和遍历基本块构成</div>
<div>1. 前向数据流分析：目标是计算出每个基本块入口处有效的复写赋值集合，这里定义为CPin(i)，i为基本块，其元素为表示复写赋值的四元组&lt;u,v,blk,pos&gt;，u为左变量，v为右变量，blk为基本块，pos为在blk中的位置。另外定义COPY(i)为基本块i中出现且到达了出口的那些复写赋值集合，即u和v在i中pos后没被赋值；KILL(i)为被基本块i杀死的那些赋值集合，即i中存在对其它基本块复写赋值右变量的赋值；CPout(i)为基本块i出口处有效的复写赋值集合。以上四种集合的数据流方程为：CPin(i)等于i的每个前驱p的CPout的交集，CPout(i)等于COPY(i)与CPin(i)减去KILL(i)之差的并集。CPout(entry)初值为空集，其它基本块的CPout初值为全集U，U为过程所有复写赋值的集合即所有基本块的COPY之并集，根据迭代求不动点法可算出每个基本块最终的CPin值</div>
<div>2. 局部复写传播：作为被全局复写传播调用的例程，有两个参数，一个为输入输出参数单个基本块，另一个为输入参数CPin，即前向数据流分析求得的结果。该例程内部维护一个有效复写赋值的表，称作ACP，其元素为二元组&lt;u,v&gt;，u是复写赋值的左变量，v是右变量。首先初始化ACP即将CPin中的复写赋值加入到ACP，再遍历基本块的每条指令，针对指令类别做对应的处理，有以下几种情况</div>
<div>a）对于一元/二元表达式及过程调用，将表达式的操作数或调用参数替换为ACP中对应元组的第二分量，若不存在这样的元组则不用替换</div>
<div>b）对于赋值语句（包括复写赋值），从ACP中删除第一或第二分量为赋值语句左变量的元组，这是为了删除被杀死的复写赋值</div>
<div>c）对于复写赋值且左变量u不等于右变量v，将元组&lt;u,v&gt;加入到ACP</div>
<div>当遍历结束后，局部复写传播就完成了</div>
<div>3. 遍历基本块：对每个基本块调用局部复写传播，当遍历结束后，全局复写传播就完成了</div><br />
<div><strong>【分析】</strong></div>
<div>数据流分析的复杂度取决于基本块总数及指令总数，局部复写传播的复杂度取决于基本块的指令总数，遍历基本块复杂度取决于基本块数量。全局复写传播会造成无用的赋值指令，但是这正给死代码删除和强度削减（比如两个相同的整型变量加法用移位代替）提供了机会</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230055.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-06 23:13 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/06/230055.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>基于SSA的稀有条件常数传播</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230054.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 06 Sep 2023 15:10:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230054.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230054.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230054.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230054.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230054.html</trackback:ping><description><![CDATA[<div><strong>【输入】</strong></div>
<div>ssa控制流图。结点为一个phi函数或一条运算指令，边包含控制流边和ssa边</div><br />
<div><strong>【输出】</strong></div>
<div>所有ssa变量的最终LatCell（常量半格值）</div><br />
<div><strong>【流程】</strong></div>
<div>1. 算法维护两个工作表，一是流图边FlowWL，用于跟踪控制流的执行，二是ssa边SSAWL，用于单赋值变量的传播。还有一个ExecFlag映射，用于确保仅有控制流边导向的运算结点最多执行一次，多次执行是没必要的，因为运算涉及的分量不会变（没有ssa前驱边），ExecFlag(a,b)为true表示边a-&gt;b导向的结点b已执行，否则未执行</div>
<div>2. 两种结点的分析：</div>
<div>a) 对于phi结点，不管被哪种边导向，都先计算其LatCell（phi结果与各个phi参数的交），若与旧值不同，则将它的ssa后继边加入SSAWL，若控制流后继边尚未执行即对应ExecFlag为false，则将它的控制流后继边加入FlowWL</div>
<div>b) 对于运算结点，若是控制流边导向且未被执行过（到结点的所有边的ExecFlag为false）或ssa边导向且以前执行过（存在至少一条边的ExecFlag为true），则执行其运算，计算左值变量的LatCell（解释执行整数运算），若与旧值不同，则将ssa后继边加入SSAWL，若LatCell是常量且为条件运算，则将满足条件的Y或N边加入FlowWL，否则将所有控制流后继边加入FlowWL</div>
<div>3. 算法初始时，设置所有控制流边的ExecFlag为false，设置所有ssa变量的LatCell为未知（半格顶元素），将流图入口到第1个结点的边加入FlowWL。然后进行主循环，先从FlowWL移出一条边，若边的ExecFlag为false则设为true，判断尾结点类型，若为phi则转到上述2-a处理，若为运算则转到2-b处理；再从SSAWL移出一条边，若边尾结点为phi类型则转到2-a处理，否则为运算类型转到2-b处理，以上过程直至FlowWL和SSAWL皆为空</div><br />
<div><strong>【分析】</strong></div>
<div>该算法思想是符号执行，对于运算x=y或x=y+z（这里+泛指对整型有意义的操作），在常量半格中，x、y、z初值为未知，y和z单调降低，导致x也单调降低，它们最多降低2次，故当格值不变后，SSAWL终为空，另外由于ExecFlag的作用导致所有仅控制流边导向的结点最多执行一次，因此FlowWL终为空，算法是收敛的，复杂度取决于控制流边和ssa边的总数</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230054.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-06 23:10 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/06/230054.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>程序控制依赖图的构建</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230053.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 06 Sep 2023 15:07:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230053.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230053.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230053.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230053.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230053.html</trackback:ping><description><![CDATA[<div><strong>【输入】</strong></div>
<div>程序控制流图CFG</div><br />
<div><strong>【输出】</strong></div>
<div>带区域结点的控制依赖图CDG</div><br />
<div><strong>【流程】</strong></div>
<div>1. 为CFG添加一个虚构谓词结点start，它的Y边指向入口结点entry，出边指向出口结点exit，得到CFG+。添加start是因为entry到第1个基本块没有条件判断</div>
<div>2. 为CFG+构建后必经结点树PDOMTree，将CFG+中所有n不是m的后必经结点的边m-&gt;n加入集合S，边的标号来自CFG为Y或N</div>
<div>3. 遍历S，对每条边m-&gt;n，先在PDOMTree中找到最低公共祖先p（如果m为根结点则为m，否则为m的父结点），再将PDOMTree中p到n路径上每个结点（p和entry除外）x加入CDG，并添加边m-&gt;x，其边标号同m-&gt;n</div>
<div>4. 对CDG的每个内部结点，若存在Y边，则新建一个区域结点，连接所有Y边对应的子结点；若存在N结点，则新建一个区域结点，连接所有N边对应的子结点</div><br />
<div><strong>【应用】</strong></div>
<div>对于控制依赖于同一结点的所有结点，只要它们之间没有数据依赖关系，就可以并行执行</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230053.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-06 23:07 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/06/230053.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>过程间分析之调用图构建算法</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230052.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 06 Sep 2023 15:04:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230052.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230052.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230052.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230052.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230052.html</trackback:ping><description><![CDATA[<div><strong>【输入】</strong></div>
<div>根过程，及每个过程（含根过程）的指令序列</div><br />
<div><strong>【输出】</strong></div>
<div>调用图，由过程点集和调用边（形如&lt;p,i,q&gt;，p在位置i调用q）集构成</div><br />
<div><strong>【全局结构】</strong></div>
<div>PVVs：过程值变量集合</div>
<div>PVVals：过程值变量到过程常数集合的映射</div>
<div>PVBinds：过程值变量到过程值变量集合的映射</div>
<div>PVCalls：调用边的集合</div><br />
<div><strong>【流程核心】</strong></div>
<div>1. 分析过程p内指令，要处理调用指令和赋值指令两种类型。对于调用指令，若被调过程q是过程常数，则将q和&lt;p,i,q&gt;加入调用图，先解析q的过程值形参与传入实参的关系，有4种情况</div>
<div>a）过程常数cp传入过程值形参fp，将偶对&lt;fp,cp&gt;加入PVVals，fp加入PVVs</div>
<div>b）过程值变量vp传入过程值形参fp，将&lt;fp,vp&gt;加入PVBinds，fp和vp加入PVVs</div>
<div>c）过程值形参fp传出过程值变量vp，将&lt;vp,fp&gt;加入PVBinds，vp和fp加入PVVs</div>
<div>d）过程值形参fp传出过程常数cp，将&lt;fp,cp&gt;加入PVVals，fp加入PVVs</div>
<div>若q不是常数而是过程值变量，则将q加入PVVs，&lt;p,i,q&gt;加入PVCalls。再解析q的返回与p的关系，有2种情况</div>
<div>e）返回一个过程值变量vp1赋给另一过程值变量vp2，将&lt;vp2,vp1&gt;加入PVBinds，vp2和vp1加入PVVs</div>
<div>f）返回一个过程常数cp赋给一个过程值变量vp，将&lt;vp,cp&gt;加入PVVals，vp加入PVVs</div>
<div>对于赋值指令，其实情况和上述返回赋值一样</div>
<div>----------------------------------------------------------------</div>
<div>2. 遍历PVVs，传播各过程值变量的PVBinds，直至不再改变（迭代求不动解），本质是计算过程值变量的传递闭包</div>
<div>3. 遍历PVCalls，对每个&lt;p,i,q&gt;，先遍历它的每个PVVals u，将u和&lt;p,i,u&gt;加入调用图；再遍历它的每个PVBinds u及u的每个PVVals v，将v和&lt;p,i,v&gt;加入调用图</div>
<div>----------------------------------------------------------------</div>
<div>以上三环节可使用工作表w来驱动，w初始只有根过程，不断从w移出一个过程p、分析p，每当在环节1或环节3发现一个新过程（过程常数）就加入w，直至w为空，这时所有过程都已分析，调用图构建完成</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230052.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-06 23:04 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/06/230052.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>过程间分析之常量传播算法</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230051.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 06 Sep 2023 15:02:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230051.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230051.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230051.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230051.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230051.html</trackback:ping><description><![CDATA[<div><strong>【输入】</strong></div>
<div>调用图，其顶端是根过程</div><br />
<div><strong>【输出】</strong></div>
<div>每个过程每个参数的icp值</div><br />
<div><strong>【算法步骤】</strong></div>
<div>1. 将根过程加入工作表，遍历调用图，构建每个过程的形参集合，初始化每个形参的icp值为未知（icp格的顶元素）</div>
<div>2. 从工作表移出一个过程p，若工作表为空则终止</div>
<div>3. 遍历p的指令序列，对每个调用点遍历被调过程q的形参，对每个形参x，若对应的传入实参是p的一个形参，则计算x的icp值（等于x旧值和传入实参的icp值之交）</div>
<div>4. 若x的icp值比旧值小，则将q加入工作表，转到步骤2继续</div><br />
<div><strong>【算法分析】</strong></div>
<div>数学基础是icp半格，高度为3，所以必定收敛（因为半格是单调偏序的，icp最多变小2次：未知-&gt;常量，常量-&gt;非常量）。步骤1复杂度取决于过程数及其参数数量，步骤2～4之外循环次数取决于调用图的深度，内循环取决于调用点数、被调过程的参数数量。该算法是位置无关的，不能处理特定调用点的特定过程之常量传播，另外过程的形参集合不能有交集</div><br />
<div><strong>【应用】</strong></div>
<div>可以计算出每个过程入口形参对应的常量实参集合，进而可以运用全局常数传播使结果更精确。如果确定了一个过程的哪些参数是常量，那么可以克隆出一个副本，对副本进行优化，比如裁剪调用和起始代码序列，使之不传递常数参数，再运用过程内优化</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230051.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-06 23:02 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/06/230051.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>自然循环构建</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230050.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 06 Sep 2023 14:59:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230050.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230050.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230050.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230050.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230050.html</trackback:ping><description><![CDATA[<div><strong>【输入】</strong>控制流图&lt;N, E&gt; G，回边m&#8212;&gt;n</div>
<div><strong>【输出】</strong>循环子图&lt;N, E&gt; loop</div><br />
<div><strong>【流程】</strong></div>
<div>1. 将m、n加入loop的结点集合，及m&#8212;&gt;n加入loop的边集合，若m不等于n即不为自环，则加入m到queue（先进先出队列）</div>
<div>2. 若queue非空，则其从头出队得结点q；否则结束</div>
<div>3. 在G中遍历q的每一个前驱结点p，将p加入queue尾，若p不在loop结点集合中，则加入到loop结点集合，及边p&#8212;&gt;q加入loop的边集合。转到步骤2继续</div>
<br />
<div><strong>【分析】</strong></div>
<div>正确性：检验最终loop中的结点集合是否满足自然循环的定义，注意到输入指定了回边，这说明n是m的支配结点，当为自环时只有一个结点而满足支配自反性，当不为自环时，加入的结点是m的所有直接与间接前驱，所以n也是它们的支配结点（假设不是，则必有m的一个前驱p，从入口结点经过p到m但不经过n，这与n是m的支配结点矛盾），且回边已在第1步加入loop，故满足了自然循环的定义。由于m在loop中的前驱数量是有限的，因此算法必然终止</div>
<div>复杂度：第3步判断p是否在loop结点集合中，取决于图的具体结构，设n为循环子图的结点数，若是邻接矩阵，则只需O(1)时间检测边是否存在，因此总耗时为O(n)。若为邻接表，检测边是否存在与结点数成正比，则总耗时为O(n^2)</div>
<div>其它算法：从m开始，标记n为visited，在G的反向流图中深度优先搜索，将访问到的结点及边加入loop，遇到n就回溯</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230050.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-06 22:59 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/06/230050.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>总结支配结点相关结论的证明</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230049.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 06 Sep 2023 14:57:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230049.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230049.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230049.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230049.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230049.html</trackback:ping><description><![CDATA[<div><strong>【命题1】</strong>控制流图G中若a dom n，且b dom n，则a dom b 或b dom a</div>
<div><strong>【证明】</strong>设G入口为s，假设结论不成立，即a 不dom b且b 不dom a，或a dom b且b dom a。根据支配结点定义，如果是前者，则从s有全部路径经a（或b）到n但不经过b（或a），这与题设b（或a）dom n矛盾；如果是后者，则从s有全部路径经a，然后经b，再经a，构成了无限循环a-&gt;b-&gt;a-&gt;&#8226;&#8226;&#8226;，永远到不了n，这也与题设矛盾。故结论成立</div>
<div></div><br />
<div><strong>【命题2】</strong>控制流图G中若m idom n，则m是唯一的，若d &#8800; n 且d dom n ，则d dom m</div>
<div><strong>【证明】</strong>设G入口为s，假设不唯一，G中有另一个结点m'且m' idom n，根据支配结点定义，从s经m到n的路径上必有m' dom m，从s经m'到n的路径上必有m dom m'，根据支配关系的反对称性，有m'=m，故唯一。假设d 不dom m，则从s到m的路径上不必然经过d，又m是n的唯一直接支配结点，则从s到n的路径上不必然经过d，即d 不dom n，这与题设矛盾，故d dom m。可以看到用反证法证明后一个结论时，直接支配结点的唯一性很关键</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230049.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-06 22:57 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/06/230049.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>总结数据流分析的几个问题</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230048.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 06 Sep 2023 14:53:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230048.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230048.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230048.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230048.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230048.html</trackback:ping><description><![CDATA[<div><span style="color: #ff6600;"><strong>1. 迭代算法在什么情况下是正确的</strong></span></div>
<div>数据流值满足半格的定义，以及数据流方程中的传递函数满足单调性</div><br />
<div><span style="color: #ff6600;"><strong>2. 迭代算法在什么情况下必定收敛</strong></span></div>
<div>在满足正确性的前提下，当数据流值对应的半格高度有限时，必定收敛。以最小元为初值的迭代收敛于最小不动点，以最大元为初值的迭代收敛于最大不动点</div><br />
<div><span style="color: #ff6600;"><strong>3. IDEAL、MOP、MFP三种解的意义与关系</strong></span></div>
<div>IDEAL是理想解即最精确的解，它将程序入口entry到某点p所有可达路径（可执行路径）的尾端的数据流值做聚合操作，区分来自不同路径的数据流值，若聚合操作是交运算，则最大下界为其值，任何大于IDEAL的解都是错误的，而小于IDEAL的解是保守的；若聚合操作是并运算，则最小上界为其值，任何小于IDEAL的解都是错误的，而大于IDEAL的解是保守的。MOP是全路径聚合解，它将entry到p所有流图路径（不一定可执行）的尾端的数据流值做聚合操作，区分来自不同路径的数据流值，若包含了不可执行路径，则会丢失精确性，否则等于IDEAL；MFP是基于数据流方程与迭代算法求得的最大或最小不动点解，它在每个控制流图的汇合节点做聚合操作而非路径尾端，不区分来自不同路径的数据流值，若传递函数不满足分配律，则会丢失精确性，否则等于MOP。故精确性关系为MFP&lt;=MOP&lt;=IDEAL，可知MFP解是安全的，基于MFP作的优化是正确的</div><br />
<div><span style="color: #ff6600;"><strong>4. 为什么不采用IDEAL和MOP解</strong></span></div>
<div>因为一般程序路径数可能无限，所以没有求MOP的有效算法，且不可达路径是一个不可判定问题，所以没有求IDEAL的有效算法</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230048.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-06 22:53 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/06/230048.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>加宽算子和变窄算子的用途</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230047.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 06 Sep 2023 14:45:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230047.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230047.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230047.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230047.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230047.html</trackback:ping><description><![CDATA[<div><span style="color: #ff6600;"><strong>为什么要加宽算子？</strong></span><br />因为当格的偏序集合L不满足升链条件，从最小元迭代计算最小不动点的过程是不收敛的，即迭代序列(f&#8319;(&#8869;))&#8345;不保证最终稳定，且其最小上界不保证等于最小不动点，因此需要一种近似lfp(f)的方法。引入加宽算子fw:L&#215;L&#8212;&gt;L, fw(x)=x&#9661;f(x)，可以将L上的一个序列转为收敛的升链，从L的最小元开始迭代不断上升，直至lfp(f)的一个上近似即fw的最小不动点lfp(fw)，关系式为lfp(f)&lt;=f(lfp(fw))&lt;=fw(lfp(fw))=lfp(fw)。对上式反复应用f单调得到：lfp(f)&lt;=f&#8319;&#8314;&#185;(lfp(fw))&lt;=f&#8319;(lfp(fw))&lt;=&#8230;&lt;=f(lfp(fw))&lt;=lfp(fw)，这表明对lfp(fw)使用f迭代可获得更精确的上近似，其过程可看成沿一个递降链进一步逼近lfp(f)，但L不一定满足降链条件而导致上述过程不收敛，故需要引入变窄算子fn:L&#215;L&#8212;&gt;L, fn(x)=x&#9651;f(x)，将L的一个序列转为收敛的降链，从lfp(fw)开始迭代，不断下降直至fn的一个不动点fp(fn)，则有关系式：lfp(f)&lt;=fp(fn)&lt;=lfp(fw)。注意，这里根据加宽算子的定义可知fw是单调的，但根据变窄算子的定义不确定fn是否单调，故从lfp(fw)迭代求得的fp(fn)，不确定是最小还是最大不动点，只能说是一个不动点，这也反映了变窄算子不需要满足单调性，就可以更加逼近lfp(f)</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230047.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-06 22:45 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/06/230047.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>伽罗瓦连接</title><link>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230046.html</link><dc:creator>春秋十二月</dc:creator><author>春秋十二月</author><pubDate>Wed, 06 Sep 2023 14:42:00 GMT</pubDate><guid>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230046.html</guid><wfw:comment>http://www.cppblog.com/qinqing1984/comments/230046.html</wfw:comment><comments>http://www.cppblog.com/qinqing1984/archive/2023/09/06/230046.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/qinqing1984/comments/commentRss/230046.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/qinqing1984/services/trackbacks/230046.html</trackback:ping><description><![CDATA[<div><strong style="font-size: 12pt;">【性质】</strong></div>
<div>1. 判定两个完全格L和M能否构成伽罗瓦连接，即抽象化函数&#945;: L&#8212;&gt;M是否完全加性的，或具体化函数&#947;: M&#8212;&gt;L是否完全乘性的</div>
<div>2. 构造抽象化函数和具体化函数，即对于一个Galois连接（L, &#945;, &#947;, M），给定&#945;可通过&#947;(m) = &#8852;{l | &#945;(l) &#8849; m}确定&#947;，这对于所有m成立，且由于&#945;是确定的，因此&#947;是唯一确定的。取最小上界是为了保证m描述的L中元素对于所有安全地描述了M中&#945;(l)的l是安全的；给定&#947;可通过 &#945;(l) = &#8851; {m | l &#8849; &#947;(m) } 确定&#945;，其唯一性和取最大下界的原因类似前面</div>
<div>3. 帮助定义分析具体格源值到抽象格属性的正确性关系与表示函数。设有Galois连接（L, &#945;, &#947;, M），R: V&#215;L &#8212;&gt;{true, false}为正确性关系，由表示函数&#946;:V&#8212;&gt;L生成，定义S: V&#215;M &#8212;&gt;{true, false}，则有v S m &#8660; v R (&#947;(m)) &#8660; &#946;(v ) &#8849; &#947;(m) &#8660; (&#945;&#9702;&#946;)(v) &#8849; m，即S为正确性关系，由表示函数&#945;&#9702;&#946;: V&#8212;&gt;M生成</div>
<div>4. 抽象化上迭代多次具体化+抽象化，结果等于一次抽象化，即&#945;&#9702;&#947;&#9702;&#945; = &#945;；具体化上迭代多次抽象化+具体化，结果等于一次具体化，即&#947;&#9702;&#945;&#9702; &#947; = &#947;。这个性质被用于基于约化算子构造的伽罗瓦插入（特殊的伽罗瓦连接：具体化为单射，抽象化为满射）的证明</div>
<div></div>
<br />
<div><strong style="font-size: 12pt;">【组合】</strong></div>
<div>分三大类，即顺序组合、并行组合和函数空间。为简化描述，下文简称Galois为G</div>
<div>1. 顺序组合：取第一个G连接的具体格，最后一个G连接的抽象格，从第一个G连接到最后一个G连接组合各抽象化函数，从最后一个G连接到第一个G连接组合各具体化函数。例如，设（L&#8320;, &#945;&#8321;, &#947;&#8321;, L&#8321;）和（L&#8321;, &#945;&#8322;, &#947;&#8322;, L&#8322;）都是G连接，则（L&#8320;, &#945;&#8322;&#9702;&#945;&#8321;, &#947;&#8322;&#9702;&#947;&#8321;, L&#8322;）也是一个G连接</div>
<div>2. 并行组合：有六种方法，即独立特征、相关性、直积、直张量积、约化积、约化张量积，前两种用于组合分别针对不同结构多个分析的多个G连接为一个G连接。中间两种组合针对同一结构多个分析的多个G连接为一个G连接，后两种组合针对同一结构多个分析的多个G连接为一个G插入。独立特征、直积、约化积与其它方法的区别是两对抽象化函数与具体化函数之间没有相互作用，会损失分析结果精度，本质就是P(A)&#215;P(B)和P(A&#215;B)的差别（P为幂集，A、B为集合）；独立特征与直积、约化积的区别是具体化函数定义不同（抽象化函数相同），前者是两个具体化函数的二元组即&#947;(m&#8321;, m&#8322;)=(&#947;&#8321;(m&#8321;), &#947;&#8322;(m&#8322;))，后者则是最大下界即&#947;(m)=&#947;&#8321;(m&#8321;)&#8743;&#947;&#8322;(m&#8322;)</div>
<div>3. 函数空间：分为总函数空间和单调函数空间。对于前者，设（L, &#945;, &#947;, M）为一个G连接，S为一个集合，f为S到L的函数，g为S到M的函数，因L和M为完全格，故由f或g构成的函数集合为总函数空间，则得到一个G连接（S&#8212;&gt;L, &#945;', &#947;', S&#8212;&gt;M），其中&#945;'(f)=&#945;&#9702;f, &#947;'(g)=&#947;&#9702;g。对于后者，设（L&#8321;, &#945;&#8321;, &#947;&#8321;, M&#8321;）和（L&#8322;, &#945;&#8322;, &#947;&#8322;, M&#8322;）为G连接，f为L&#8321;到L&#8322;的函数，g为M&#8321;到M&#8322;的函数，因每个L及M为完全格，故由f或g构成的函数集合为单调函数空间，则得到一个G连接（L&#8321;&#8212;&gt;L&#8322;, &#945;, &#947;, M&#8321;&#8212;&gt;M&#8322;），其中&#945;(f)=&#945;&#8322; &#9702;f &#9702;&#947;&#8321;，&#947;(g)=&#947;&#8322;&#9702; g&#9702; &#945;&#8321;</div><br />
<div><span style="font-size: 12pt;"><strong>【应用】</strong></span></div>
<div>当要做数据流分析的一个完全格L不满足升链条件时，除了直接对L运用加宽算子及变窄算子外，还怎么去计算近似它的最小不动点？这时伽罗瓦连接就派上用场了，先将L对应到另一个完全格M，即构造一个Galois连接或插入（L, &#945;, &#947;, M），设A是L上的广义单调框架（不要求L满足升链条件，指定传递函数集合F为L到L的单调函数空间，即F本身也是完全格），其中f是L到L的单调函数，B是M上的广义单调框架，其中g是M到M的单调函数，保证g是由f衍生的函数的上近似即&#945;&#9702;f&#9702;&#947; &#8849; g，及M满足升链条件。到了这里可以证明两个结论：</div>
<div>➀ lfp(f) &#8849; &#947;(lfp(g)) 和 &#945;(lfp(f)) &#8849;&nbsp; lfp(g)</div>
<div>➁B的约束解(B&#8321;, B&#8322;)蕴含A的约束解(A&#8321;, A&#8322;)=(&#947;&#9702;B&#8321;, &#947;&#9702;B&#8322;)，下标1、2表示流图结点的入口、出口。接下来有两种方法可以计算近似L的最小不动点</div>
<div>1. 直接计算M上的最小不动点，然后应用上述结论➀，取lfp(f) = &#947;(lfp(g))</div>
<div>2. 构造M的上界算子（针对Galois连接）或加宽算子（针对Galois插入），满足 l&#8321; &#8711;&#8343; l&#8322; = &#947;(&#945;(l&#8321;) &#8711;&#8344; &#945;(l&#8322;))，可以证明左式为L上的一个加宽算子，取其lfp&#8711;&#8343; (f)。如果前面构造的是Galois插入，那么可以证明L和M两者的加宽算子精度是一样的，即lfp&#8711;&#8343; (f) = &#947;(lfp&#8711;&#8344;(&#945;&#9702;f&#9702;&#947; ))</div><img src ="http://www.cppblog.com/qinqing1984/aggbug/230046.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/qinqing1984/" target="_blank">春秋十二月</a> 2023-09-06 22:42 <a href="http://www.cppblog.com/qinqing1984/archive/2023/09/06/230046.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>