发明内容
本发明的目的是提供一种能够减少访存次数和访存总带宽的方法,以减轻嵌入式处理器和通用处理器上的访存瓶颈问题,从而有效提高应用程序的性能。
为了实现上述目的,本发明提供了一种基于数据流分析的访存合并优化方法,包括以下步骤:
1)、利用编译器收集整个程序的访存操作信息,将所得信息存入一个映射表;
2)、由编译器对程序中的所有基本块构造支配图和后支配图;
3)、对程序中的所有读操作做反向数据流分析,更新基本块的输入数据集和输出数据集;
4)、对程序中的所有写操作,进行正向数据流分析,更新基本块的输入数据集和输出数据集;
5)、根据步骤3)和步骤4)所得到的基本块的输入数据集和输出数据集,对每一个访存操作,判断该访存操作所在基本块的输入数据集合中的访存操作是否可与该访存操作合并,并为输入数据集中所有可以与当前访存操作做合并的访存操作建立一个集合:
6)、判断用于保存可合并的访存操作的集合是否都为空,若都为空,则跳转到步骤14),否则,执行下一步;
7)、对步骤5)所得集合中的所有元素,分别计算各个元素对应的访存操作与当前访存操作之间的合并密度;
8)、从步骤7)所得到的结果中,选择合并密度最大的访存操作,将该访存操作与当前访存操作进行合并,生成新的访存操作;
9)、判断所合并的访存操作的类型,若访存操作为读操作,则将访存操作所在基本块的读操作替换为对临时寄存器的读操作,并跳转到步骤13),若访存操作为写操作,执行下一步;
10)、访存操作为写操作,将访存操作所在基本块的写操作替换为对源操作数寄存器的写;
11)、在写操作合并时,如果两个写操作的数据位置相邻,则到步骤13);否则,执行下一步;
12)、两个写操作的数据位置不相邻,则遍历支配图中一个写操作原来所在基本块的所有祖先节点,寻找位于这些节点中的一个读操作,使得该读操作读取中间间隔部分的数据,如果找到该读操作,则在合并的写操作之前插入指令,将中间间隔部分的数据写入源操作数寄存器,如果找不到这样的读操作,则在支配图上该写操作原来所在基本块的父节点对应的基本块处,插入这样的读操作;
13)、转步骤3);
14)、结束。
上述技术方案中,所述步骤3)的对读操作做反向数据流分析的具体实现步骤如下:
3-1、将所有基本块的输入输出数据集初始化为空;
3-2、对每一个基本块,如果一个基本块有多个后继块,则将这些后继块的输出数据集合并为该基本块的输入数据集;否则,该基本块的输入数据集等于它的直接后继块的输出数据集;
3-3、对每一个基本块,如果该基本块中是读操作,则将该读操作加入到数据集中;
3-4、对每一个基本块,如果该基本块中是写操作,则将数据集中与该写操作相关的数据位都置为无效;
3-5、对每一个基本块,如果新产生的数据集不等于该基本块原来的输出数据集,则将新产生的数据集设为该基本块的输出数据集,
3-6、判断程序中是否还有未被处理的基本块,若有,转步骤3-2,否则,转下一步;
3-7、判断程序中所有基本块的输出数据集与上一次计算相比是否都没有变化,若是,结束反向数据流分析过程,否则,转步骤3-2。
上述技术方案中,所述步骤4)的对写操作做正向数据流分析的具体实现步骤如下:
4-1、所有基本块的输入输出数据集初始化为空;
4-2、对每一个基本块,如果一个基本块有多个前驱块,则将这些前驱块的输出数据集合并为该基本块的输入数据集;否则,该基本块的输入数据集等于它的直接前驱块的输出数据集;
4-3、对每一个基本块,如果该基本块中是读操作,则将数据集中与该读操作相关的数据位都置为无效;
4-4、对每一个基本块,如果该基本块中是写操作,则将该写操作加入数据集中,并将数据集中与该写操作相关的数据位都置为无效;
4-5、对每一个基本块,如果新产生的数据集不等于该基本块原来的输出数据集,则将新产生的数据集设为该基本块的输出数据集;
4-6、判断程序中是否还有未被处理的基本块,若有,转步骤4-2,否则,转下一步;
4-7、判断程序中所有基本块的输出数据集与上一次计算相比是否都没有变化,若是,结束正向数据流分析过程,否则,转步骤4-2。
所述的数据集合并的具体实现步骤包括:
如果所要合并的两个数据集中有相同的元素,即该元素表示的访存操作所在的基本块相同,则对这两个元素代表的访存操作的数据位进行“与”操作,所得结果存入合并后的新的数据集中;
如果两个集合中有不相同的元素,则直接将这些元素存入合并后的新的数据集中。
所述的将与写操作相关的数据位置为无效的方法是将写操作的有效数据位先取反,然后再与数据集中所有操作的数据位进行“与”操作,其中有效数据用1表示,无效数据用0表示。
所述的将与读操作相关的数据位都置为无效的方法是将该读操作的有效数据位先取反,然后再与数据集中所有操作的数据位进行“与”操作,其中有效数据用1表示,无效数据用0表示。
上述技术方案中,所述步骤5)中,所述合并的判断条件根据访存操作的类型而异:
读操作合并必须满足2个条件:一个读操作在被合并读操作所在基本块处必须要活跃;被合并读操作所在基本块必须支配所述读操作原来所在的基本块;
写操作合并必须要满足2个条件:一个写操作在被合并写操作所在基本块处必须要活跃;被合并写操作所在基本块必须与所述写操作原来所在的基本块控制等价,即所述写操作原来所在的基本块支配被合并写操作所在基本块,并且被合并写操作所在基本块后支配所述写操作原来所在的基本块。
所述的活跃的判断方法为:如果该访存操作在该基本块处的相对偏移量和数据宽度与在它所属的基本块处的相对偏移量和数据宽度都相等,则这个访存操作在该基本块处是活跃的。
上述技术方案中,所述步骤7)中计算各个元素对应的访存操作与当前访存操作之间的合并密度的方法是:将第一个访存操作的数据宽度加上第二个访存操作的数据宽度,然后减去合并后的访存操作的数据宽度;其中,所述的合并后的访存操作的数据宽度的计算方法是:首先得到所要合并的第一个访存操作的起始偏移量加上第一个访存操作的数据宽度的结果,和第二访存操作的起始偏移量加上第二个访存操作的数据宽度的结果,然后将两个结果做比较,取其中值较大的结果,最后用值较大的结果减去第一个访存操作和第二个访存操作中起始偏移量较小的起始偏移量。
上述技术方案中,所述步骤8)中,所述的选择合并密度最大的访存操作,将该访存操作与当前访存操作进行合并是:
合并后生成的新的访存操作所在的基本块号与所要合并的当前访存操作的基本块号相同,合并后的访存操作相对于开始地址的偏移量是从两个合并的访存操作中取偏移量较小的偏移量,合并后的访存操作的数据宽度的计算方法是首先得到第一个访存操作的起始偏移量加上第一个访存操作的数据宽度的结果,和第二访存操作的起始偏移量加上第二个访存操作的数据宽度的结果,然后将两个结果做比较,取其中值较大的结果,最后用值较大的结果减去合并后的访存操作相对于开始地址的偏移量。
本发明的优点在于:
1.本发明的基于数据流分析的访存合并技术精确的描述了程序中的访存操作的位信息,能够准确有效的优化程序,同时具有普遍性与通用性,适用于不同的硬件体系结构和应用程序。
2.本发明的基于数据流分析的访存合并技术有较好的优化性能,用软件的方法减少了程序对储存器带宽的要求。
3.本发明的基于数据流分析的访存合并技术的复杂度较低,易于实现。
具体实施方式
下面结合附图和具体实施方式对本发明所述方法进行详细说明。
本发明的基于数据流分析的访存合并方法通过对全局访存指令的访存信息进行数据流分析,将地址相邻或者相近的两个或多个访存指令合并为一个多字节的访存指令。例如,在程序中有两条读操作指令,第一条读操作指令实现对一基本块中数据相对于开始地址的偏移量为20,数据长度为8位的数据的读操作,第二条读操作指令实现对同一基本块中,数据相对于开始地址的偏移量为28,数据长度为8位的数据的读操作。通过本发明的访存合并方法,可将上述两个读操作指令合并为一个读操作指令,该读操作指令实现对数据相对于开始地址的偏移量为20,数据长度为16位的数据的读操作。
本发明的基于数据流分析的访存合并优化方法的具体实现步骤如下。
步骤10、遍历程序,将所有访存操作的信息(bb,offset,size)记录到映射表mem_access_map中。其中,bb是访存操作所在的基本块编号,offset是所访问的数据相对于开始地址的偏移,size是访问数据的大小。利用现有的编译器,本步骤所要完成的工作即可实现。本发明是在编译器的后端的代码生成器(codegenerator)中实现的,但也可以在其它阶段实现。
步骤20、计算程序中所有基本块的支配(dominance)与后支配(post-dominance)信息,构造支配图与后支配图。在支配图与后支配图中,反映了基本块之间的控制依赖关系。
例如,对于2个基本块而言,如果从程序入口到第2个基本块的所有路径都经过第1个基本块,则第1个基本块支配第2个基本块。如果不存在第3个基本块,满足第1个基本块支配第3个基本块而且第3个基本块支配第2个基本块,则第1个基本块直接支配第2个基本块。把支配的基本块作为父节点,被它直接支配的基本块作为它的子节点,这样就构成了一个树(实际上是以程序入口基本块为根的一棵树)。支配图一般也称为支配树(dominate tree)。在这棵树上,一个节点被它的所有祖先节点支配,也支配它所有的子孙节点。类似的,如果从第2个基本块到程序出口的所有路径都经过第1个基本块,那么就说第1个基本块后支配第2个基本块,同样可以构造后支配树。对支配图与后支配图的构造由编译器实现,支配图和后支配图的构造有专用的算法,在本发明中采用“支配节点计算算法”,关于该算法的详细说明请参见参考文献3:A.V.Aho,R.Sethi and J.D.Ullman,“Compilerprinciples,techniques,and tools.”Addison Wesley Press,1986,pp.670.
步骤30、对程序中的所有读操作,进行反向数据流分析。在反向数据流中,基本块的输入集位于基本块的出口,基本块的输出集位于基本块的入口。反向数据流将某基本块的后继块的输出集合并,合并后的结果作为该基本块的输入集。如图2所示,反向数据流分析的具体实现过程如下。
步骤31、初始化基本块的输入集in和输出集out;
步骤32、判断变量changed的值,若该变量的值为假(false),转步骤310,若该变量的值为真(true),执行步骤33;
步骤33、将变量changed的值置为false;
步骤34、取当前基本块的后继块,将所有后继块的输出数据集合并为当前基本块的输入数据集。下面结合图4,在例1中说明数据集是如何合并的。
例1:假定每个基本块最多只含有一个读操作或者写操作,即最多只存在一次访存操作。设Out(x),Out(y)分别为基本块i的后继块x和后继块y的输出数据集,In(i)为基本块i的输入数据集。在后继块x的输出数据集Out(x)中有两个访存操作,分别为r(l,20,8)和r(m,32,4),在后继块y的输出数据集Out(y)中有两个访存操作,分别为r(l,16,8)和r(q,64,32),其中,r(l,20,8)与r(l,16,8)表示两个在基本块l中实现的读操作,由于基本块l只能有一个访存操作,所以这两个读操作必然是同一个读操作,是相同元素。因此,对两个操作的数据区域进行“与”操作。在读操作r(l,20,8)中,读取基本块l中从20~28位的数据,在读操作r(l,16,8)中,读取基本块l中从16~24位的数据。通过“与”操作,得到两个操作中的共同数据区20~24位,因此,“与”操作的结果为r(l,20,4),并放入输入集In(i)中。这里所做的“与操作”的目的是做安全性检查,保证数据的安全性。读操作r(m,32,4)与r(q,64,32)是在不同的基本块中实现的,在两个集合中没有相同元素,因此直接加入到In(i)中。通过上述合并操作,基本块i的输入数据集In(i)中有三个访存操作,分别为r(l,20,4),r(m,32,4)与r(q,64,32)。如果基本块i还有其它后继块,则继续将In(i)与其他后继的输出数据集合并,直到所有的数据集都已合并。
在本实施例中,假定基本块最多只有一个访存操作,实际使用中,可能一个基本块含有多个访存操作,此时只需要对基本块做进一步划分使每个基本块只有一个访存操作以满足上述假设。
步骤35、判断当前基本块中是否有读或写的访存操作,如果没有,执行步骤37,否则,执行下一步;
步骤36、判断访存操作是否为读操作,如果是,将该读操作加入到当前基本块的输入数据集中,否则,执行下一步;
步骤37、访存操作为写操作,将基本块输入数据集中与该写操作相关的数据位设为无效。下面结合附图5,在例2中说明是如何将与写操作相关的数据位设为无效;
例2:设基本块i中含有写操作w(i,16,8),基本块i的输入数据集为{r(l,8,16),r(m,20,24)},由于该写操作重新定值了16~24位的数据,所以要把输入数据集中所有相关的数据位设为无效,r(l,8,16)中的数据位是从8~24位,r(m,20,24)中的数据位是从20~44位,写操作重新定值了16~24位的数据,因此,r(l,8,16)中数据的有效位是从8~16位,r(m,20,24)中数据的有效位是从24~44位,改变后的输入数据集最后为{r(l,8,8),r(m,24,20)}。将与写操作相关的数据位设为无效的具体实现方法是将写操作的有效数据位先取反,然后再与数据集中所有操作的数据位进行“与”操作,其中有效数据用1表示,无效数据用0表示。
步骤38、将步骤36得到的输入数据集与该基本块原来的输出数据集做比较,如果两者不相等,置变量changed为真,并将该输入数据集置为该基本块的输出数据集,然后执行下一步;如果两者相等,不做任何操作,直接执行下一步;
步骤39、判断程序中是否还有基本块没有被处理,如果还有基本块没有被处理,则取下一个未处理的基本块作为当前基本块,并跳转到步骤34,否则,跳转到步骤32;
步骤310、、结束反向数据流分析。
步骤40、对程序中的所有写操作,进行正向数据流分析。在正向数据流中,基本块的输入集位于基本块的入口,基本块的输出集位于基本块的出口。正向数据流将某基本块的前驱基本块的输出集合并,合并后的结果作为该基本块的输入集。如图3所示,对写操作的正向数据流分析具体包括以下步骤。
步骤41、初始化基本块的输入集in和输出集out;
步骤42、判断变量changed的值,若该变量的值为假(false),转步骤410,若该变量的值为真(true),执行步骤43;
步骤43、将变量changed的值置为false;
步骤44、取当前基本块的前驱块,将所有前驱块的输出数据集合并为当前基本块的输入数据集;
步骤45、判断当前基本块中是否有读或写的访存操作,如果没有,执行步骤48,否则,执行下一步;
步骤46、判断访存操作是否为写操作,如果是,将该写操作加入到基本块的输入数据集中,并将该输入数据集中与该写操作相关的数据位设为无效,否则,执行下一步;
步骤47、访存操作为读操作,则将该输入数据集中与该读操作相关的数据位设为无效;在步骤46和47中,对数据位设为无效的实现过程可参考实施例2。
步骤48、将该基本块的输入数据集与该基本块原来的输出数据集做比较,如果两者不相等,则将变量changed的值置为true,并将该输入数据集置为该基本块的输出数据集,然后执行下一步,如果两者相等,不做任何操作,直接执行下一步;
步骤49、判断程序中是否还有基本块没有被处理,如果还有基本块没有被处理,则取下一个未处理的基本块作为当前基本块,并跳转到步骤44,否则,跳转到步骤42;
步骤410、结束正向数据流分析。
步骤50、对每一个访存操作,判断该访存操作所在基本块的输入数据集合中的访存操作是否可与该访存操作合并。访存操作的合并需要一定的条件,且不同的访存操作做合并时所需要的条件不同。读操作合并必须满足2个条件:a.一个读操作在被合并读操作所在基本块处必须要活跃;b.该基本块必须支配所述操作原来所在的基本块。写操作合并也要满足2个条件:a.一个写操作在被合并写操作所在基本块处必须要活跃;b.该基本块必须与所述操作原来所在的基本块控制等价,即所述操作原来所在的基本块支配该基本块,并且该基本块后支配所述操作原来所在的基本块。上述合并条件中,所述的支配与后支配关系可根据步骤20得到的支配图和后支配图得到。所述活跃的条件是:如果该访存操作在某一个基本块处的相对偏移量和数据宽度与在它所属的基本块处的相对偏移量和数据宽度都相等,则这个访存操作在所在基本块处是活跃的。为输入数据集中所有可以与当前访存操作做合并的活跃的访存操作建立一个集合,各个可合并的访存操作就是集合中的元素。下面结合例3,说明如何进行访存操作活跃性的判断。
例3:对位于基本块i处的读操作r(i,x,s),设任一读操作r(j,y,t)位于基本块j,如果在基本块i的输入集in(i)中存在r(j,y’,t’)并且y=y’,t=t’,那么r(j,y,t)在基本块i处是活跃的,它可以与r(i,x,s)合并。构造一个集合avai_set(i),所有可以与r(i,x,s)合并的读操作都是该集合中的元素。对于位于基本块i处的写操作也可以做同样的处理。
步骤60、判断用于保存活跃的访存操作的集合是否为空,若为空,则跳转到步骤140,否则,执行下一步;
步骤70、对步骤50所得集合中的所有元素,分别计算各个元素对应的访存操作与当前访存操作之间的合并密度。所述的合并密度的计算方法是将第一个访存操作的数据宽度加上第二个访存操作的数据宽度,然后减去合并后的访存操作的数据宽度。而合并后的访存操作的数据宽度的计算方法是首先得到第一个访存操作的起始偏移量加上第一个访存操作的数据宽度的结果,和第二访存操作的起始偏移量加上第二个访存操作的数据宽度的结果,然后将两个结果做比较,取其中值较大的结果,然后用值较大的结果减去第一个访存操作和第二个访存操作中起始偏移量较小的值。
例如:任取集合avai_set(i)中的元素r/w(j,y,t),计算它与读(写)操作r/w(i,x,s)的合并密度:comb_dense=s+t-(Max(x+s,y+t)-Min(x,y))。
步骤80、从步骤70所得到的结果中,选择合并密度最大的元素,将该元素与当前访存操作进行合并,生成新的访存操作。新的访存操作的所在的基本块号与当前访存操作的基本块号相同,新的访存操作相对于开始地址的偏移量是从两个合并的访存操作中取偏移量较小的值,新的访存操作的数据宽度的计算方法是首先得到第一个访存操作的起始偏移量加上第一个访存操作的数据宽度的结果,和第二访存操作的起始偏移量加上第二个访存操作的数据宽度的结果,然后将两个结果做比较,取其中值较大的结果,然后用值较大的结果减去新的访存操作的偏移量。例如:将合并密度最大的元素记为r/w(j,y,t),当前访存操作为r/w(i,x,s),则合并后的新的访存操作为r/w(i,p,v),其中p=Min(x,y),v=Max(x+s,y+t)-p。
步骤90、判断所合并的访存操作的类型,若访存操作为读操作,则将对访存操作所在基本块的读操作替换为对临时寄存器的读操作,并跳转到步骤130;若访存操作为写操作,执行下一步。例如:将基本块j中的读操作替换为读取r(i,p,v)的临时寄存器。
步骤100、访存操作为写操作,将对访存操作所在基本块的写操作替换为对源操作数寄存器的写。例如:将基本块j中的写操作替换为写w(i,p,v)的源操作数寄存器。
步骤110、在写操作合并时,如果两个写操作的数据位置相邻,则到步骤130;否则,执行下一步。
步骤120、两个写操作的数据位置不相邻,则遍历支配图中该写操作所在基本块的所有祖先节点,寻找位于这些节点中的一个读操作,使得该读操作读取中间间隔部分的数据。如果找到该读操作,则在合并的写操作之前插入指令,将中间间隔部分的数据写入w(i,p,v)的源操作数寄存器。如果找不到这样的读操作,则在支配图上该写操作所在基本块的父节点对应的基本块处,插入这样的读操作。
步骤130、转步骤30。
步骤140、结束。
下面结合附图,举例说明本发明所述的步骤。
图6(a)表示了进行访存合并前的一个程序片断,其中在程序中存在2个读操作和2个写操作,在基本块1中包含有一个读操作read{64,8,s},在基本块2中有一个写操作write{64,8,x},在基本块3中有一个读操作read{80,16,t},在基本块4中有一个写操作write{80,16,y}。其中s和t分别为2个读操作的源操作数寄存器,x和y分别为2个写操作的目的操作数寄存器。括号中的第一个数字代表数据相对于开始地址的偏移,括号中的第二个数字是访问数据的大小。
对程序做遍历,遍历后程序所有的访存操作的信息记录在mem_access_map映射表中,如表1所示。
表1
基本块(BB) |
类型 |
访存信息(bb,offset,size) |
1 |
R |
(1,64,8) |
2 |
W |
(2,64,8) |
3 |
R |
(3,80,16) |
4 |
W |
(4,80,16) |
首先对各个基本块的数据集做初始化,初始化后的数据集全部为空集。然后对程序中的各个读操作做反向数据流分析。在反向数据流分析中,将基本块的后继块的输出集合并,合并后的结果作为基本块的输入集,在反向数据流中,基本块的输入集位于基本块的出口,基本块的输出集位于基本块的入口。
在本实施例中,程序中有两个读操作,在第一次反向数据流分析中,基本块1的输入集为空,输出集为其本身所包含的读操作(1,64,8),基本块3的输入集为空,输出集为其本身所包含的读操作(3,80,16)。基本块2和基本块4的输出集和输入集都为空。第一次反向数据流分析的结果请参见表2。
表2
基本块(BB) |
输入集 |
输出集 |
1 |
{} |
{(1,64,8)} |
2 |
{} |
{} |
3 |
{} |
{(3,80,16)} |
4 |
{} |
{} |
在第二次反向数据流分析中,基本块2是基本块1的后继块,基本块2的输出集为空,因此,基本块1的输入集和输出集不发生改变。基本块3和基本块4都是基本块2的后继块,在反向数据流分析中,要将基本块的后继块的输出集合并,合并后的结果作为基本块的输入集,因此基本块3的输出集中的读操作(3,80,16)添加到基本块2的输入集中,并相应改变基本块2的输出集。基本块4是基本块3的后继块,由于基本块4的输出集是空集,因此基本块3的输入集和输出集都不发生变化。基本块4没有后继块,它的输入集和输出集都不发生变化。第二次反向数据流分析的结果请参见表3。
表3
基本块(BB) |
输入集 |
输出集 |
1 |
{} |
{(1,64,8)} |
2 |
{(3,80,16)} |
{(3,80,16)} |
3 |
{} |
{(3,80,16)} |
4 |
{} |
{} |
在第三次反向数据流分析中,基本块2是基本块1的后继块,基本块2的输出集中包含有读操作(3,80,16),将该读操作加入到基本块1的输入集和输出集中。基本块2的后继块是基本块3和基本块4,基本块2的输入集和输出集已经包含了基本块3和基本块4中的所有操作,因此,基本块2的输入集和输出集都不发生变化。同样的,基本块3和基本块4的输入集和输出集也不发生改变。第三次反向数据流分析的结果请参见表4。
表4
基本块(BB) |
输入集 |
输出集 |
1 |
{(3,80,16)} |
{(1,64,8),(3,80,16)} |
2 |
{(3,80,16)} |
{(3,80,16)} |
3 |
{} |
{(3,80,16)} |
4 |
{} |
{} |
在第四次反向数据流分析中,变量changed的值为false,终止数据流分析的过程,第四次反向数据流分析的结果与第三次相同,如表5所示。
表5
基本块(BB) |
输入集 |
输出集 |
1 |
{(3,80,16)} |
{(1,64,8),(3,80,16)} |
2 |
{(3,80,16)} |
{(3,80,16)} |
3 |
{} |
{(3,80,16)} |
4 |
{} |
{} |
同样的,还要对程序中的写操作做正向数据流分析,正向数据流分析中将基本块的前驱基本块的输出集合并,合并后的结果作为该基本块的输入集。基本块的输入集位于基本块的入口,基本块的输出集位于基本块的出口。在第一次正向数据流分析中,基本块1的输入集和输出集都为空,基本块2的输入集为空,基本块2中有写操作(2,64,8),因此基本块2的输出集中包含该写操作。基本块3的输入集和输出集都为空,基本块4的输入集为空,输出集包含该基本块自身的写操作(4,80,16)。对写操作的第一次正向数据流分析结果请参见表6。
表6
基本块(BB) |
输入集 |
输出集 |
1 |
{} |
{} |
2 |
{} |
{(2,64,8)} |
3 |
{} |
{} |
4 |
{} |
{(4,80,16)} |
在第二次正向数据流分析中,基本块1没有前驱块,它的输入集和输出集都为空,基本块1是基本块2的前驱块,由于基本块1的输出集为空,基本块2的输入集不变,也为空。基本块2的输出集中只包含该基本块的写操作(2,64,8)。基本块2是基本块3的唯一的前驱块,将基本块2的输出集中的写操作存入基本块3的输入集中,基本块3本身没有写操作,输出集与输入集一样。基本块2还是基本块4的前驱块,将基本块2的输出集中的写操作存入基本块4的输入集中,基本块4本身还有写操作(4,80,16),输出集包含两个写操作(2,64,8)和(4,80,16)。第二次正向数据流分析结果请参见表7。
表7
基本块(BB) |
输入集 |
输出集 |
1 |
{} |
{} |
2 |
{} |
{(2,64,8)} |
3 |
{(2,64,8)} |
{(2,64,8)} |
4 |
{(2,64,8)} |
{(2,64,8)(4,80,16)} |
在第三次正向数据流分析中,变量changed的值为false,终止数据流分析的过程,第三次正向数据流分析的结果与第二次相同,第三次正向数据流分析结果请参见表8。
表8
基本块(BB) |
输入集 |
输出集 |
1 |
{} |
{} |
2 |
{} |
{(2,64,8)} |
3 |
{(2,64,8)} |
{(2,64,8)} |
4 |
{(2,64,8)} |
{(2,64,8)(4,80,16)} |
完成数据流分析后,实现对读操作和写操作的合并。
对于读操作,只有基本块1和基本块3中存在读操作。由于基本块1的输入集中的读操作是(3,80,16),而与基本块3中的原操作(3,80,16)相比,位信息没有变化,所以(3,80,16)在基本块1处是活跃的。而在支配图中,基本块1支配基本块3。因此,(3,80,16)可以被合并到BB1的读操作(1,64,8)上。它的合并密度comb_den=8+16-32=-8。由于基本块3的输入集为空,因此不能合并。所以最终选择(3,80,16)与(1,64,8)合并,合并后的操作为r(64,32,u),该操作表示从偏移64处读取32位数据,u为临时目的寄存器。同时原来BB3处的读操作被替换为直接读取临时寄存器u。读操作合并后的结果如图6(b)所示。
再合并写操作。先看基本块2:由于输入集为空,因此不能合并。再看基本块4:输入集只有一个元素(2,64,8),合并密度=8+16-32=-8。因此可以合并,合并后的写操作为w(64,32,v),v为临时的源寄存器。同时,原来基本块2的写操作(2,64,8)被替换为对临时寄存器v的写。但由于这两个写操作不相邻,所以需要沿着控制流图向上寻找一个读操作,使得该读操作可以读取中间间隔部分(即(72,80)部分)。在更新后的控制流图上,可以看到BB1的读操作已经变为(1,64,32),其中包含间隔部分。因此在合并后的写操作之前插入指令,将间隔部分的数据填入临时寄存器v。如图6(b)所示。由于所有可能的操作都已经合并,因此算法结束。