数据流分析之活跃分析
引言1 相关概念2 算法2.1 算法规则2.2 算法流程2.3 算法优化 3 举例引言
在函数中,每个临时变量(或虚拟寄存器)都是有生命周期。在给其分配真实寄存器的时候,需要通过活跃分析去知道两个变量(或虚拟寄存器)是否可以共用同一个真实寄存器;此外,还可以用于llvm IR的死store指令删除。
注:本文后术寄存器没有特别说明是虚拟寄存器或真实寄存器,均为虚拟寄存器
1 相关概念
入口活跃列表
指每条指令执行前的存活变量。指令n的入口活跃集合表示为in[n],n为指令序号、元素为寄存器;
出口活跃列表
指每条指令执行后的存活变量。指令n的出口活跃集合表示为out[n],n为指令序号、元素为寄存器;
引用
指令操作中,对寄存器的非修改访问称为一次引用。指令n的引用集合表示为use[n], n为指令序号、元素为寄存器;
定值
指令操作中,对寄存器的修改称为一次定值。指令n的定值集合表示为def[n], n为指令序号、元素为寄存器(一般有0个或1个元素);
注:活跃分析就是分析每条指令执行前后的入口活跃列表和出口活跃列表
2 算法
2.1 算法规则
整个算法其核心思想是如下三条规则:
若变量属于use[n],必属于in[n]。即用每个指令结点的use初始化其in;若变量属于in[n],必属于n的前驱结点b的out[b]。即沿着有向图的反方向求并集传播;若变量属于out[n]且不属于def[n],必属于in[n]。即def会阻止反方向传播;
2.2 算法流程
首先,将函数的指令按执行顺序(包括跳转顺序)串成一个有向图,每个结点对应一条指令;将每个指令结点n的 use[n] 添加到 in[n] 中(添加前 in[n] 为空集合);直到所有结点的入口活跃列表和出口活跃列表没有变化前,反复执行如下操作:将每个结点n的 in[n] 添加到其前驱结点的 out[n] 中;
将每个结点n的 out[n] 中、且不在 def[n] 中的变量添加到in[n]中;
2.3 算法优化
我们对一个函数的每个结点生成入口活跃列表和出口活跃列表是比较耗时的,但是,很多时候我们只需要求出每个基本块的入口活跃列表和出口活跃列表。具体如下:
通过如下方式求出每个基本块的use和def:
变量属于基本块的use,当且仅当该基本块有一条指令引用该变量、且在这条指令前没有对该变量定值的指令;
变量属于基本块的def,当且仅当该基本块至少有一条指令对该变量定值;按照前面的算法,将每个基本块视作一个有向图结点,求其入口活跃列表和出口活跃列表;
注:这种优化,只求出了跨基本块的入口活跃列表和出口活跃列表的变量;例如对于在基本块内部先def、再use且没有在其他基本块引用的变量不会出现在两个列表里面。所以还需要对每个基本块内部按指令进行展开操作,具体过程:
将同时在基本块的in和ou中的变量添加到基本块每条指令的in和out中;将存在基本块in中、但不存在out中的变量,添加到该基本块最后引用该变量的指令和该指令之前的指令对应in中;将存在基本块out中、但不存在in中的变量,添加到该基本块第一条对该变量定值的指令和该指令之后的指令对应out中;若基本块中有指令引用了即不在基本块的in、又不在基本块的out中,那么需要添加到第一个对该变量def的指令、最后对该变量use的指令以及它们之间的指令中。特别地,不会出现第一个对变量def的指令之前还有对变量的use指令。