decaf-doc
  • Introduction
  • pa1a
    • 实验内容
    • lalr1使用指导
      • 编写lexer
      • impl块的可选属性
      • 产生式和语法动作
      • 解决冲突
      • 一个完整的例子
    • 抽象语法树
    • 框架中部分实现的解释
    • 文件结构
  • pa1b
    • 实验内容
    • lalr1使用指导
    • 错误恢复
    • 文件结构
  • pa2
    • 实验内容
    • 语义分析
    • 符号表
    • 语句的返回类型
    • visitor模式
    • 框架中部分实现的解释
  • pa3
    • 实验内容
    • 中间代码
    • 中间代码中的类型信息
    • 运行时存储布局
    • 面向对象机制
    • tacvm介绍
  • pa4
    • 实验内容
    • 基本块
    • 数据流分析概述
    • 数据流优化概述
    • 公共表达式提取
    • 复写传播
    • 常量传播
    • 死代码消除
  • pa5
    • 实验内容
    • 图着色基本原理
    • 改进干涉图节点
    • 着色算法
    • 预着色节点
    • 干涉图节点合并
    • 调用约定
Powered by GitBook
On this page

Was this helpful?

  1. pa4

数据流分析概述

Previous基本块Next数据流优化概述

Last updated 5 years ago

Was this helpful?

原理课上讲解了两种数据流分析的方法:前向的到达-定值分析和后向的活跃变量分析,公式分别如下:

到达-定值分析:

Out(B)=Gen(B)∪(In(B)−Kill(B))In(B)=∪B′ is predecessor of BOut(B′)Out(B) = Gen(B) \cup (In(B) - Kill(B))\\ In(B) = \cup_{B' \text{ is predecessor of } B} Out(B')\\Out(B)=Gen(B)∪(In(B)−Kill(B))In(B)=∪B′ is predecessor of B​Out(B′)

活跃变量分析:

LiveIn(B)=LiveUse(B)∪(LiveOut(B)−Def(B))LiveOut(B)=∪B′ is successor of BLiveIn(B′)LiveIn(B) = LiveUse(B) \cup (LiveOut(B) - Def(B))\\ LiveOut(B) = \cup_{B' \text{ is successor of } B} LiveIn(B')\\LiveIn(B)=LiveUse(B)∪(LiveOut(B)−Def(B))LiveOut(B)=∪B′ is successor of B​LiveIn(B′)

前向的数据流分析是从前向后流的,后向的数据流分析是从后向前流的。所谓的向前和向后指的是与控制流的方向相同和相反,而从前向后和从后向前指的是绝对方向。

大家也许注意到了,如果进行如下的替换:Out↔LiveInOut \leftrightarrow LiveInOut↔LiveIn,Gen↔LiveUseGen \leftrightarrow LiveUseGen↔LiveUse,In↔LiveOutIn \leftrightarrow LiveOutIn↔LiveOut,Kill↔DefKill \leftrightarrow DefKill↔Def,并且逻辑上将控制流图中的边反向,这两个公式就完全一样了。正因如此,虽然框架中的flow.rs内的计算逻辑是前向数据流,但是它也可以用于计算后向数据流,如活跃变量分析。

数据流分析并不只有到达-定值分析和活跃变量分析这两种,我们进行的四种分析/优化中,公共表达式提取,复写传播和常量传播都对应于自己的数据流分析方法,而死代码消除是基于活跃变量分析的。前面三个属于前向的数据流分析,后面一个属于后向的数据流分析。

数据流分析有自己的一套通用的框架:

(D,V,∧,F)(D, V, \land, F)(D,V,∧,F)

其中DDD表示数据流分析的方向,分为前向和后向两种;VVV表示数据流分析的值集,也就是在基本块间"流动"的东西,比如对于到达-定值分析是定值语句的集合,对于活跃变量分析是变量的集合;FFF是V→VV \rightarrow VV→V的函数集合,表示传递函数,也就是信息流过基本块后会发生什么变化,对应于上面给出的两个方程组中的第一行。

而∧\land∧表示交半格上的交汇运算符,对应于上面给出的两个方程组中的第二行。∧\land∧是一个V×V→VV \times V \rightarrow VV×V→V的函数,它刻画的是两个基本块的信息流入一个基本块时的交汇方式。到达-定值分析和活跃变量分析中,交汇方式都是对集合取并集(即∧=∪\land = \cup∧=∪),但也还有其它的交汇方式,比如对于公共表达式提取,复写传播和用到的数据流分析方法,采用的交汇方式都是集合取交集(即∧=∩\land = \cap∧=∩);对于常量传播用到的数据流分析,交汇运算符的定义还会更复杂一些。

关于半格的更深入的知识可以参考,关于传递函数自身的性质对于数据流分析的结果的影响可以参考龙书的对应章节,包括数据流方程的收敛性,收敛速度等。这些东西在编译原理这门课上不要求精确的理解,有个大概的印象即可。

对于前向的数据流,通用的公式为:

out(B)=fB(in(B))in(B)=∧B′ is predecessor of Bout(B′)out(B) = f_B(in(B))\\ in(B) = \land_{B' \text{ is predecessor of } B} out(B')\\out(B)=fB​(in(B))in(B)=∧B′ is predecessor of B​out(B′)

对于后向的数据流,通用的公式为:

in(B)=fB(out(B))out(B)=∧B′ is successor of Bin(B′)in(B) = f_B(out(B))\\ out(B) = \land_{B' \text{ is successor of } B} in(B')\\in(B)=fB​(out(B))out(B)=∧B′ is successor of B​in(B′)

其中fB∈Ff_B \in FfB​∈F。一般不会直接定义fBf_BfB​,而是定义fSf_SfS​,用于表示信息流过单条语句后会发生什么变化。设基本块BBB的语句依次为S1,S2,...,Sn−1,SnS_1, S_2, ..., S_{n - 1}, S_nS1​,S2​,...,Sn−1​,Sn​,对于前向数据流,显然有fB(x)=fSn(fSn−1(...fS2(fS1(x))))f_B(x) = f_{S_n}(f_{S_{n - 1}}(...f_{S_2}(f_{S_1}(x))))fB​(x)=fSn​​(fSn−1​​(...fS2​​(fS1​​(x)))),即fB=fSn∘fSn−1∘...∘fS2∘fS1f_B = f_{S_n} \circ f_{S_{n - 1}} \circ ... \circ f_{S_2} \circ f_{S_1}fB​=fSn​​∘fSn−1​​∘...∘fS2​​∘fS1​​,其中∘\circ∘是函数组合运算符。对于后向数据流结果是类似的,只是函数组合的顺序要颠倒过来。

在数据流分析相关的资料中一般只用ininin和outoutout,很少用LiveInLiveInLiveIn和LiveOutLiveOutLiveOut这一对名字,它们应该是特别为活跃变量分析特别起的名字。

为了保证灵活性,实验框架中没有对不同的数据流分析进行更高层次的抽象,只是提供了一些基础组件。flow.rs中提供了struct Flow,它负责用bitset的形式存储各个集合和迭代计算,这样计算的效率会很高,但是它也只能适用于各种运算都是基于集合运算的数据流分析。这个策略对于公共表达式提取,复写传播和死代码消除都适用,但是对于常量传播并不适用,它需要自己的求解方法,不过大致的思想是类似的。在填写了GenGenGen,KillKillKill(或者是LiveUseLiveUseLiveUse,DefDefDef)后,调用solve(graph)函数即可迭代解出数据流方程,其中graph是用来描述基本块间连接关系的迭代器。

flow.rs中提供了一个trait Meet,它表示交汇运算符,提供了Or和And两个实现,分别对应于集合(bitset)的并和交。

wiki