编译器优化–4--消除无用和不可达代码

2023-11-08

编译器优化–4--消除无用和不可达代码

概述

有时候,程序包含的一些计算不具有外部可见的效应。如果编译器能够确定给定操作不会影响程序的结果,那么它完全可以消除该操作。大多数程序员都不会有意编写这种代码。但是,这种代码在大多数程序中作为编译器中优化的直接结果出现,通常是因宏展开或编译器前端的"朴素"转换所致。

有两种不同的效应可以使操作成为消除的合格对象。操作可能是无用的,意即其结果没有外部可见的效应。另外,操作可能是不可达的,意即它不可能执行。如果一个操作属于上述两类之一,那么它就可被移除。术语"死代码"通常可用于指代无用代码或不可达代码,但我们使用"死代码"来指无用代码。

Note:

  • 无用的,对给定操作来说,如果没有其他操作使用其结果,或使用其结果的所有指令本身都是死代码,那么这个操作是无用的(useless)。

  • 不可达的,对于给定操作来说,如果没有有效的控制流路径包含该操作,则其是不可达(unreachable)。

删除无用或不可达代码可以缩减IR代码,可使程序更小、编译更快、(通常)执行也更快。同事它还可以增强编译器改进代码的能力。例如,不可达代码的效果可能呈现在静态分析结果中,从而阻碍应用某些变换。在这种情况下,消除不可达程序块可以改变分析结果,从而能够进一步应用变换。

某些形式的冗余消除也会删除无用代码。例如,局部值编号算法会应用代数恒等式来简化代码。这样的例子包括 x + 0 → x x + 0 \rightarrow x x+0x y ∗ 1 → y y * 1 \rightarrow y y1y m a x ( z , z ) → z max(z,z) \rightarrow z max(z,z)z。对这些例子的每个简化都会消除一个无用操作,即根据定义,在删除后不会对程序的外部可见行为造成影响的操作。

因为消除无用和不可达代码的算法会修改程序的控制流图(CFG),我们将审慎地区分术语分支(指令br)跳转(jump)

消除无用代码

用于消除无用代码的经典算法,其运作方式类似于标记——清除垃圾收集器,只是输入是IR代码而已。类似于标记——清除收集器这类算法会在代码上处理两趟。第一趟清除所有的标记字段,并将"关键"操作均标记为"有用的"。如果一个操作会设置过程的返回值,是输入输出语句,或者会影响从当前过程外部可访问的某个内存的位置中的值,则我们称该操作为"关键的"。关键操作的例子包括过程的其实代码序列和收尾代码序列,以及调用位置处的调用前代码序列和返回后代码序列。接下来,算法将跟踪"有用"操作的操作数,回溯到其定义位置,将想用的操作标记为"有用"。在简单的Worklist迭代模式下,这个过程会一直持续下去,直至无法将更多操作标记为有用为止。第二趟将遍历代码并删除任何没有标记为有用的操作。

Note:

  • 一个操作可能有几种方法设置一个返回值,包括对引用调用参数或全局变量赋值,通过具有歧义的指针赋值,或通过return语句传递一个返回值。

消除无用控制流

优化可能会改变程序的IR形式,使之包含无用的控制流。如果编译器包括可能产生无用控制流(作为一种副效应)的优化,那么其中也应该包括一趟对应的处理,即通过消除无用控制流来简化CFG。

消除无用控制流的算法(Clean)非常简单,该算法直接运行在过程的CFG上。它使用4个变换,它们按以下顺序应用

  1. 合并冗余分支指令(Merge redundant branch instruction) ,如果Clean发现一个程序块以分支指令结束,但该分支指令的两个目标是同一个程序块,则将其替换为到目标程序块的跳转指令。之所以会出现这种情况,是因为其他简化所致。例如 B i B_i Bi可能有两个后继节点,而每个后继节点结束时都跳转到 B j B_j Bj

  2. 删除空程序块(Delete empty basicblocks),如果Clean发现一个程序块只包含一条跳转指令,则可以将改程序块合并到其后继节点。当有其他趟从程序 B i B_i Bi中删除了所有操作时,就会出现这种情况。如下图中两个CFG途中的左侧的那个。由于 B i B_i Bi只有一个后继节点 B j B_j Bj,变换将进入 B i B_i Bi的边重定目标到 B j B_j Bj,并将 B i B_i Bi B j B_j Bj的前驱节点集合中删除。这简化了CFG。也应该能够加速代码的执行。在原来的图中,穿越 B i B_i Bi的代码路径需要两个控制流操作才能到达 B j B_j Bj。在变换后的图中,相应的代码路径只需一个操作即可到达 B j B_j Bj

  3. 合并程序块(Merge basicblock),如果Clean发现一个程序块 B i B_i Bi结束于到 B j B_j Bj的跳转指令,且 B j B_j Bj只有一个前驱节点,那么就可以合并这两个程序块,如下图所示。这种情况可能有几种原因。可能有另一个变换消除了进入 B j B_j Bj的其他边,当然 B i B_i Bi B j B_j Bj也可能是合并冗余分支指令的结果。但无论是哪种情况,这两个程序块都可以合并为一个程序块。这样就消除了 B i B_i Bi末尾的跳转指令。

  4. 提升分支指令(Promotion branch instruction),如果Clean发现程序块 B j B_j Bj是空程序块,且 B j B_j Bj以分支指令结束,那么Clean可以将 B i B_i Bi程序块末尾的跳转指令替换为 B j B_j Bj中分支指令的副本。实际上,这相当于将分支指令提升到 B i B_i Bi中,如下图所示。当有其他趟处理删除了 B j B_j Bj中的其他操作,使得 B j B_j Bj中的跳转指令直接跳转到一个分支指令时,就会出现这种情况。变换后的代码只有一个分支指令就达到了同样的效果。这向CFG中添加了一条边。请注意, B i B_i Bi不可能是空的,否则负责消除空程序块的趟已经删除了它。(提升之后, B j B_j Bj任然至少有一个前驱节点。)

为了实现这些变换优化器需要进行一些工作。上述提到的4种变换所作的某些修改颇为简单。在使用IR和CFG表示的程序中合并冗余分支指令,Clean将程序块末尾的分支指令改写为跳转指令,并调整程序块的后继结点和前驱结点列表。其他修改则较为困难。合并两个基本块涉及为合并后的程序块分配空间,将各个操作复制到新程序块中,调整新程序块(以及在CFG中的相邻结点)的前驱结点和后继结点列表,并丢弃原来的两个程序块。

Clean以一种系统化的方式来应用这4种变换。它按后根次序遍历流图,因此 B i B_i Bi的后继结点会在 B i B_i Bi结点之前被简化,除非其后继结点在后根次序编号下位于某条后向边上。在这种情况下,Clean将先访问前驱结点,而后访问后继结点。在有环图中这是不可避免的。在前驱结点之前简化后继结点可以减少实现必须移动某些边的次数。

消除不可达代码

有时候CFG中包含了不可达的代码。编译器应该找到不可达程序块并删除它们。程序块可能因为以下两个原因成为不可达代码:

  1. 可能没有穿越CFG的代码路径到达该程序块
  2. 到达该程序块的代码路径是不可能执行的

如受控于一个条件判断的代码,如果条件表达式的值始终是false,那么该代码是不可能被执行的。

前一种情形易于处理。编译器可以在CFG上执行一种简单的标记——清除类的可达性分析。首先,他在每个程序块上初始化一个标记,值为"不可达"。接下来,它从CFG的入口结点开始,将每个可以到达的CFG结点标记为"可达"。如果所有分支和跳转指令都是无歧义的,那么所有未标记为可达的程序块都可以删除。如果存在有歧义的分支或跳转指令,则编译器必须保留这种分支或跳转指令可能到达的那些程序块。这种分析简单且代价不高。可以在因其他目的遍历CFG期间完成此种分析,也可以在CFG构造期间完成。

Note

如果源语言允许对代码指针或标号进行算术运算,则编译器必须保留所有程序块。否则,保留下来的程序块集合可以仅限于哪些标号被引用到的程序块。

第二种情况处理起来要困难些。它要求编译器推断控制分支指令的表达式的值。

参考:

《编译原理》

《Engineering a Compiler》

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

编译器优化–4--消除无用和不可达代码 的相关文章

  • 类的构造函数和析构函数

    1 把对象的初始化工作放在构造函数中 把清除工作放在析构函数中 当对象被创建时 构造函数被自动执行 当对象消亡时 析构函数被自动执行 这下就不用担心忘了对象的初始化和清除工作 2 构造函数 析构函数与类同名 由于析构函数的目的与构造函数的相
  • 搭建 llvm 学习环境

    1 下载llvm git clone https github com llvm llvm project git 因为国内网络的原因 clone的时候没有反应 可以多此 Ctrl C 重新 clone 2 下载安装cmake 注意 下载的
  • 如何将二维数组作为函数的参数传递

    如何将二维数组作为函数的参数传递 今天写程序的时候要用到二维数组作参数传给一个函数 我发现将二维数组作参数进行传递还不是想象得那么简单里 但是最后我也解决了遇到的问题 所以这篇文章主要介绍如何处理二维数组当作参数传递的情况 希望大家不至于再
  • 更换编译器,CODE::BLOCKS 无法DEBUG 至断点

    想在LINUX下使用CODE BLOCKS 编写调试并编译连接ARM运行程序 IDE编译环境默认为 GNU GCC 编译器 修改如下 1 至Settings gt Compiler and debugger settings 将Setect
  • CGAL的简介及安装设置

    一 CGAL库的介绍 CGAL Computational Geometry Algorithms Library 库 计算几何算法库 是一个大型的C 几何数据结构和算法库 包含Delaunay三角网 网格生成 布尔运算的多边形 各种几何处
  • 条件编译小结

    编码的时候经常要用到条件编译 每次都到网上去查比较浪费时间 今天总结一下以备后用 编译器 GCC ifdef GNUC if GNUC gt 3 GCC3 0以上 Visual C ifdef MSC VER 非VC编译器很多地方也有定义
  • 程序分析 clang系列学习 (二)

    clang静态检测 clang API AST匹配部分 UseAfterMoveCheck 问题概述 示例 代码 AST CFG 检测步骤 算法大致流程 代码 这里 我主要通过clang API实现自定义的代码检测工具 采用的方式类似于cl
  • LLVM系列第十五章:写一个简单的中间代码生成器IR Generator

    系列文章目录 LLVM系列第一章 编译LLVM源码 LLVM系列第二章 模块Module LLVM系列第三章 函数Function LLVM系列第四章 逻辑代码块Block LLVM系列第五章 全局变量Global Variable LLV
  • printf标识总结(转)

    printf标识总结 转 Dev C 下基本数据类型学习小结 环境 Dev C 4 9 6 0 gcc mingw32 使用 Wall编译选项 基本类型包括字节型 char 整型 int 和浮点型 float double 定义基本类型变量
  • Q_UNUSED()函数的作用

    Q UNUSED 函数在程序中的作用 就如它所代表的英文一样 unused 即无用的意思 即Q UNUSED 函数在程序中没有实质性的作用 用来避免编译器警告 下面我们来看一组程序 void ColorItem paint QPainter
  • C++多态性:虚函数的调用原理

    C 多态性 虚函数的调用原理 多态性给我们带来了好处 多态使得我们可以通过基类的引用或指针来指明一个对象 包含其派生类的对象 当调用函数时可以自动判断调用的是哪个对象的函数 一个函数说明为虚函数 表明在继承的类中重载这个函数时 当调用这个函
  • 快速排序之“采取“尾递归”和“三数取中”技术的快速排序”

    快速排序之 采取 尾递归 和 三数取中 技术的快速排序 下面针对快速排序进行一些优化 QUICKSORT算法包含两个对其自身的递归调用 即调用PARTITION后 左边的子数组和右边的子数组分别被递归排序 QUICKSORT中的第二次递归调
  • bin、hex、elf、axf文件解析

    冰冻三尺非一日之寒 滴水穿石非一日之功 文章目录 引言 文件分类 1 bin文件 2 hex文件 3 axf文件 4 elf文件 总结 参考资料 深度理解编译过程 参考资料 深度理解编译文件 引言 bin hex elf axf作为嵌入式开
  • dynamic_cast报错 异常

    转载请标明是引用于 http blog csdn net chenyujing1234 代码 http www rayfile com zh cn files 89459c23 7a0b 11e1 908f 0015c55db73d UnH
  • C++头文件

    作为一个二手的 net程序员 你看到了C 头文件一定就犯迷糊了 这到底是个啥玩意 再我纠结了24个小时 google20次 度娘10下 看过10来骗文章以后 我可能稍微开窍了 我对C 头文件总结 与 net比较如下 一 C 头文件究竟是什么
  • VC文件扩展名一览表

    VC文件扩展名一览表 2003 12 7 23 05 34 阅读589次 APS 存放二进制资源的中间文件 VC把当前资源文件转换成二进制格式 并存放在APS文件中 以加快资源装载速度 BMP 位图资源文件 BSC 浏览信息文件 由浏览信息
  • 使用嵌入式linux完全手册光盘的arm-linux-gcc 遇到问题 自己编译

    Redhat9下重新生成交叉编译器gcc 3 4 5 glibc 2 3 6 看到论坛上有兄弟也遇到 arm linux gcc lib tls libc so 6 version GLIBC 2 4 not found required
  • 源文件字符集,编译器内部字符集,执行字符集,控制台乱码问题,Qt中文问题

    源文件字符集 源文件本身也是文本文件 所以源文件字符集是指源文件保存时采用哪种字符集编码 VC 下源文件默认是gbk编码 如果想要更改 可以通过 文件 高级保存选项 修改某个源文件的编码方式 似乎没有什么选项能够设置创建项目时的源文件编码
  • 编译原理实验一(C-语言词法分析器的编写C语言版本)

    编译原理实验一 C 语言词法分析器的编写C语言版本 一 tiny词法分析程序源代码阅读笔记 重要变量和函数 变量和函数 A 要计算的唯一特性是词法或是被识别的记号的串值 变量t o k e n S t r i n g B 扫描程序使用3个全
  • 结构体对齐(内存对齐)

    本文转自 http www ksarea com articles 20071004 sizeof struct memory html 有的时候 在脑海中停顿了很久的 显而易见 的东西 其实根本上就是错误的 就拿下面的问题来看 struc

随机推荐

  • 圆柱体的投影特点_常用的地理信息系统地图投影详解

    今天想和大家分享下更加具体的几种地图投影知识 言归正传 直接进入正题 1 高斯 克吕格投影 1 基本概念 高斯 克吕格投影是等角横切椭圆柱投影 详细来讲就是有一椭圆柱面横套在地球椭球体外面 并与某一条子午线相切 椭圆柱的中心轴通过椭球体中心
  • python类对象内存分析_python 对象内存分析

    python对象内存分析 一 python内建对象 python内建对象占用内存的情况又分为定长对象与非定长对象 变长 1 1 定长对象 对象在内存中所占大小不会变化的对象 包括int float long bool complex和dic
  • kettle调用存储过程_Kettle(PDI)客户端工具Spoon详解

    概述 PDI 客户端 Spoon 是一个您安装在工作台上的桌面应用程序 使您能够构建转换和作业或安排作业何时运行 启动PDI客户端 Pentaho目录启动PDI客户端 启动Pentaho服务器 导航到安装PDI的文件夹 例如 pentaho
  • [Java]远程下载文件并读取实例方法

    简单的文件下载后读取显示 该方法可返回内容的结果集 一般适用于文本文档的下载 以供学习交流 远程下载文件并读取返回p param filePath 文件网络地址 如http www baidu com 1 txt return String
  • linux花生壳

    动态域名解析 花生壳 ddns dns dhcp 配置dhcp服务 在服务端 yum install dhcp y cp usr share doc dhcp 4 2 5 dhcpd conf example etc dhcp dhcpd
  • 【Unity编程】欧拉角与万向节死锁(图文版)

    Unity编程 欧拉角与万向节死锁 图文版 标签 unity万向节死锁欧拉角欧拉旋转 2017 03 11 17 08 5361人阅读 评论 4 收藏 举报 分类 Unity标准编程导引 13 版权声明 本文为博主原创文章 欢迎转载 请保留
  • 联宝盒子算法迁移

    直接把这个盒子的算法包 迁移到另一个新盒子 第一步 将算法库的第三方库 放置到该路径下 2 第二步运行 但是报错了 找不到库 如下图 但是文件夹该路径下有这个库 直接运行代码 报错如下 ImportError libopenblas so
  • 日期格式转换工具类(线程安全)

    import java text ParseException import java time import java time format DateTimeFormatter import java time temporal Chr
  • 国网DLT698.45协议——采集系统、数据交换(一)

    国网DLT698 45协议 采集系统 数据交换 面向对象协议 对于国网698协议 是一种面向对象的通信协议 用于远程监控和控制电力系统中的设备 面向对象使得对协议的思考更趋向于正常思维 使计算机中描述的抽象世界于现实世界中能够更好的对应起来
  • VirtualBox虚拟机安装64位Linux系统(Ubuntu)

    如果在VirtualBox中安装64位的Linux操作系统时提示如下错误 This kernel needs a x86 64 CPU but only detects an i386 CPU 解决方法如下 第一步 重启计算机 进入Bios
  • 基于ABB工业机器人工作站的设计———毕业设计

    基于ABB工业机器人搬运工作站的设计 通过RobotStudio软件来 模拟机器人灌装液体 混合液体并封装的过程 去年的毕业设计和毕业论文 还一些不了解的老师没见过 还说感觉很高级 感觉这个题目 目前还算是比较新 可以直接拿来用 还有一些关
  • 手机号码的正则表达式

    手机号码的正则表达式可以是这样的 13 0 9 14 5 7 15 0 3 5 9 17 0 3 5 8 18 0 9 166 198 199 147 d 8 这个正则表达式可以匹配大多数中国大陆的手机号码 包括 13 14 15 17 1
  • java.net.SocketException: Software caused connection abort: recv failed 异常分析

    关键字 recv failed java net SocketException Software caused connection abort recv failed at java net SocketInputStream sock
  • SMTP端口选择

    SMTP Port 25 465 587 or 2525 how to choose the right SMTP Port 原文地址 https pepipost com blog 25 465 587 2525 choose the r
  • 如何有理有据地给元宇宙泼一盆冷水?

    从来没有一个词像 元宇宙 一样如此虚无缥缈 如此镜花水月却受到如此热烈的追捧 元宇宙和之前的科技概念不同 以前 在科技圈的一个概念至少是明确而又具体的 比如 区块链 5G 3D打印 然而 元宇宙不是 它虚幻缥缈而错综复杂 一如 浑元形意拳
  • TypeScript基本类型、类、封装、继承、泛型、接口、命名空间

    简介 前几篇介绍了Vue3的Composition API Vue Router pinia Vue3能更好支持TS 因此 本节将介绍TS 详细描述了TS的优缺点 安装 如何配置自动编译 tsconfig json的常用配置 使用webpa
  • HTML-CSS(四十一)animation动画

    animation属性 animation name 设置动画的名字 自定义的 animation duration 动画的持续时间 animation delay 动画的延迟时间 animatio iteration count 动画的重
  • 集合学习总结

    集合 1 java集合框架概述 1 集合 數組都是多個數據存儲操作的結構 簡稱java容器 說明 主要指的存儲是內存層面的存儲 不涉及到持久化的存儲 可以分為Collection和Map兩種 Collection接口 單列數據 定義了存取一
  • (GAE)Google App Engine入门程序——helloworld

    参考资料 http blog xuming net gae tutorial 官网例子 https developers google com appengine docs python gettingstartedpython27 hel
  • 编译器优化–4--消除无用和不可达代码

    编译器优化 4 消除无用和不可达代码 概述 有时候 程序包含的一些计算不具有外部可见的效应 如果编译器能够确定给定操作不会影响程序的结果 那么它完全可以消除该操作 大多数程序员都不会有意编写这种代码 但是 这种代码在大多数程序中作为编译器中