『compiler-7』optimization

代码优化

一、基本块与流图

  • 基本块:一段顺序执行的代码块,满足以下三条性质:

    • 基本块中的代码是连续的语句序列
    • 程序的执行只能从基本块的第一条语句进入
    • 程序的执行只能从基本块的最后一条语句离开

    注意:每条中间代码属于且仅属于一个基本块

  • 流图:节点基本块有向边表示基本块之间的前驱后继关系(转移、条件假转移、紧随块)


  • 基本块划分算法:输入中间代码语句序列,输出基本块序列

    1. 确定入口语句

      • 整个语句序列第一条语句是入口语句
      • 由转移(有条件or无条件)语句转移到的首条语句属于入口语句
      • 紧跟在跳转语句之后的首条语句属于入口语句

      注意:第二种表示基本块的起点、第三种表示基本块的出口

    2. 确定基本块内容入口语句直到下一条入口语句(or结束语句)之间的所有语句属于同一个基本块

  • 基本块内优化:消除局部公共子表达式、窥孔优化

    • DAG图表示:DAG图是有向无环图

      • 叶节点:由变量名or常量值所标记
      • 中间节点:由中间代码中的操作符所标记
    • 消除局部公共子表达式:输入基本块内的中间代码序列,输出删除局部公共子表达式的DAG

      1. 建立空节点表,该表条目记录了变量名(常量值)+ 节点序号([x, i])
      2. 从第一条中间代码开始,构建DAG:注意每一步用于分配的节点新编号都从“1”开始递增分配
        1. 设 z = x op y,x的节点编号为i(y同理),查找节点表:
          • 若找得到,先记下x的节点编号i(y同理,用于下一步找op
          • 若找不到,在DAG中新建叶节点(标记为x),在节点表中增加新表项 (x, i),y同理
        2. 在DAG中寻找中间节点op,满足其左操作数编号为i右操作数编号为j
          • 若找得到,先记下op的节点编号k(用于下一步更新z的编号
          • 若找不到,设其编号为k,并在DAG中新建子树x-z-y,即把z的左右节点分别连接x和y
        3. 在节点表中寻找z:
          • 若找得到,将z对应的节点编号更改为k
          • 若找不到,在节点表中增加新表项(z, k)
      3. 对所有中间代码重复上述操作步骤

      注意:在 z = x op y 中, x 和 y 均可为空;若 op 为空,则 z 的编号由 x(或 y)赋值

    • 从DAG图重新导出中间代码:输入DAG,输出中间代码序列

      1. 初始化放置DAG图的中间节点的空栈
      2. 若DAG中所有中间节点都已进入栈,则直接转入最终步骤v.
      3. 否则,选取一个尚未进入节点栈,但其所有父节点都已进入节点栈中间节点n,将其推入栈
        或者 选择没有父节点的中间节点n,将其推入栈
      4. 沿着n的最左子节点链,将符合iii.中条件的中间节点推入栈,直到不满足iii.条件就重新转入ii.
      5. 最后,将节点栈中的各中间节点依次弹出,整理成中间代码序列

      op节点实际应被看作 存储运算结果 的中间节点


  • 窥孔优化:“窥孔”指仅关注目标指令的一个较短序列;“优化”指删除(or 改进)其中的部分代码

  • 常数合并和常量传播:

    • 常数合并:将能够在编译时计算出值的表达式用相应的值替代,如 A = 2 + 3 + C \(\Rightarrow\) A = 5 + C
    • 常量传播:在编译时已知的变量值代替程序中对这些变量的引用

二、全局优化

  • 数据流方程:out[S] = gen[S] \(\cup\) (in[S] - kill[S])

    • out[S]:表示在S末尾得到的数据流信息
    • gen[S]:表示在S本身产生的数据流信息
    • in[S]:表示进入S时的数据流信息
    • kill[S]:表示S注销的数据流信息


  • 到达定义的数据流分析:分析某个变量的值是在哪里被定义的;自前向后计算

    • 设基本块中的某条定义语句为 d1: u := v op w,则由数据流方程为 out[d1] = gen[d1] \(\cup\) (in[d1] - kill[d1])

      • gen[d1] = { d1 },表示 d1 语句产生了一个定义点
      • kill[d1]:包含整个程序其它所有对 u 定义的定义语句的集合
    • 基本块B的数据流方程:out[B] = gen[B] \(\cup\) (in[B] - kill[B])

      • kill[B] = kill[d1] \(\cup\) ... \(\cup\) kill[dn]

      • gen[B] = gen[dn] \(\cup\) (gen[d(n-1)] - kill[dn]) \(\cup\) ... \(\cup\) (gen[d1] - kill[d2] - ... - kill[dn])


      • in[B] = \(\bigcup_{\text{B的所有**前驱**基本块P}}\text{out[P]}\)

      • out[B]:直接由数据流方程导出

    • 到达定义数据流分析:输入程序流图、各基本块的kill与gen集合;输出各基本块的in和out集合

      1. 将所有基本块的out集合初始化为\(\varnothing\)
      2. 根据in[B]和out[B]的公式,先后计算(更新)各基本块的in与out集合
        注意:若某个基本块的 out 发生了改变,则要重新求解其所有后继块的 in 集合
      3. 若某一轮更新迭代中存在out[B]发生了变化,则循环迭代ii. 直至所有out[B]都不再变化

  • 活跃变量分析:分析变量x在程序的某个点(及其之后的任意执行路径)是否被使用(活跃);自后向前计算

    • 基本块B的数据流方程:in[B] = use[B] \(\cup\) (out[B] - def[B])

      • def[B]:基本块B中的,定义先于任何对其使用的变量集合

      • use[B]:基本块B中的,使用先于任何对它的定义的变量集合


      • out[B] = \(\bigcup_{\text{B的所有**后继**基本块P}}\text{in[P]}\)

      • in[B]:直接由数据流方程导出

    • 活跃变量数据流分析:输入程序流图、各基本块的use与def集合;输出各基本块的in和out集合

      1. 将所有基本块的in集合初始化为空集
      2. 根据out[B]和in[B]的计算公式,先后计算(更新)各基本块的out与in集合
        注意:若某个基本块的 in 发生了改变,则要重新求解其所有前驱块的 out 集合
      3. 若某一轮更新迭代中存在in[B]发生了变化,则循环迭代ii. 直至所有in[B]都不再变化

『compiler-7』optimization
http://larry0454.github.io/2023/11/05/compiler/optimization/
Author
WangLe
Posted on
November 5, 2023
Licensed under