LLVM passes: MergeFunctions Pass

2023-11-07

目录

What is MergeFunctions Pass

概述

FnTree和Deferred

基本流程

相同函数搜索

函数哈希值比较

函数哈希值的计算

函数哈希值比较的使用

函数结构比较

FunctionNodeCmp

函数比较方法

相同函数合并

What is MergeFunctions Pass

源码项目中有时会出现多个相同的函数或者功能相同但写法不同的函数,例如:乘2操作和shl 2操作。产生这种情况的原因有许多,其中比较常见的是template和code generator的使用。在编译过程中如果能识别并合并这些相同的函数,就能够减少编译生成的二进制规模并提高代码执行效率。

在LLVM中,MergeFunctions pass实现的就是上述识别相同函数并合并的优化。本文将会着重于MergeFunctions中函数比较的部分,而函数合并部分只会简单涉及。

概述

MergeFunctions pass定义在 lib/Transforms/IPO/MergeFunctions.cpp 文件中,继承了ModulePass,可以在module内做跨函数的分析和转换。与所有的ModulePass一样,通过runOnModule成员方法可以执行该优化。MergeFunctions优化的整体逻辑如下图所示,是一个循环迭代的过程。

FnTree和Deferred

首先看一下MergeFunctions类中两个关键的成员变量:FnTree和Deferred。

上图是FnTree的定义。FnTree存储了分析module中所有独立的、不能进一步合并的函数,可以认为FnTree中存储的就是经过优化后的module中的所有函数。可以看到,FnTree其实是一个C++ STL set,其中存储的元素类型是FunctionNode,FunctionNodeCmp则是对FunctionNode的比较算子。

上图是Deferred的定义。Deferred中存储了所有需要进行比较和分析的函数。在MergeFunctions的优化过程中,找到两个相同的函数并进行合并之后,有可能会影响到之前已经分析过的、放到了FnTree中的函数(所有调用了当前被合并函数的函数),此时需要将这些函数移出FnTree,重新放到Deferred中等待再次分析。

基本流程

就像上面最开始的流程图所示:

  1. 开始时初始化FnTree为空,初始化Deferred为当前module中需要分析的函数。
  2. 对于Deferred中每一个需要分析的函数F,尝试在FnTree中寻找与它相同的函数G:
    1. 如果G不存在,表示当前F是独立的,于是将F添加到FnTree中;
    2. 如果G存在,则将F和G合并,修改代码中对它们的调用语句并将FnTree中所有调用了F和G的函数移出到Deferred中。
  3. 整个优化流程循环进行,直至Deferred中不再存在需要进一步分析的函数。此时,FnTree中存储的就是分析模块中所有独立的函数。

相同函数搜索

为了实现MergeFunctions的功能,首先需要识别出哪些函数有相同的功能,这样才能将它们进行合并。MergeFunctions优化中同时使用了两种函数比较的方法:函数哈希值比较和函数结构比较。这一张首先介绍一下函数的哈希比较。

函数哈希值比较

函数哈希值的计算

上文提到FnTree中存储的对象是FunctionNode。FunctionNode是MergeFunctions优化过程中定义的对LLVM Function的封装类,定义如下图所示。可以看到除了封装的函数本身外,FunctionNode中还存储了函数的哈希值Hash。

从FunctionNode的构造函数中可以看到函数哈希计算使用的是FunctionComparator::functionHash方法。FunctionComparator类定义在 lib/Transforms/Utils/FunctionComparator.cpp 文件中,除了提供函数哈希的计算外,它还实现了针对不同函数结构的比较算法。

计算函数哈希的函数functionHash定义如上图所示。在计算时,首先考虑了函数是否有可变参数和函数的参数个数两个因素。之后以深度优先的方式遍历函数的AST,依次将遍历到的基本块中语句的操作符添加到函数的哈希计算中。最终综合上述这些因素,计算得到函数的哈希值。

MergeFunctions优化中使用的函数哈希计算方式只考量了函数的部分特性,并不能完全代表完整的函数。所以,即使两个函数的哈希相同也不能表示它们是相同的,依然需要进一步的比较;反之,如果两个函数的哈希不同则可以直接认为它们是不同的。这里之所以使用这样的设计主要有两个原因:

  1. 设计一个能够完整表示函数所有特征的哈希算法过于复杂。
  2. 虽然使用能够完整表示函数所有特征的哈希算法能够省去后续的基于函数结构的比较,但是这样的哈希计算实际开销过大,实验测试表明直接使用完整哈希算法的效率比当前简单哈希+函数结构比较设计的效率要低。

函数哈希值比较的使用

由于函数哈希值相同是函数相同的必要不充分条件,所以在MergeFunctions优化中被作为一种过滤方式使用:通过函数哈希值过滤掉大部分的函数对,只保留少数的函数对进行开销较大的函数结构比较。

函数哈希值比较在两个地方有使用:Deferred初始化部分和FunctionNodeCmp算子的比较开始阶段。这里介绍一下Deferred初始化部分,FunctionNodeCmp部分在后面会介绍到。

Deferred初始化的代码如下所示。在MergeFunctions优化开始时,需要收集可能可以进行合并的函数到Deferred中。对于分析module中的所有函数计算函数哈希值,只把有相同哈希值的函数初始化到Deferred中,因为哈希值唯一的函数一定是与其他所有的函数都不相同的,不需要进行后续的比较。这样就可以大大节省分析优化的时间。

函数结构比较

对于函数哈希值相同的函数,MergeFunctions会通过函数结构的比较来判定函数是否相同。在进行函数结构比较时秉承的一个思想是:将当前分析的程序模块转换为一些更小的模块,通过比较小模块来决定当前的大模块是否相同。例如:将函数的比较转换为基本块之间的比较;将基本块的比较转换为基本块内指令的比较等。同时,与其他的优化一样,MergeFunctions应该是conservative的,也就是宁可放过一千,不能错杀一个。

为了实现函数的比较,LLVM中针对不同的程序结构定义了不同的全序关系。这些按照全序关系比较函数结构的方法实现在FunctionComparator中。而MergeFunctions pass则是通过FnTree中定义的FunctionNodeCmp算子对集合内的函数进行比较和查找。

FunctionNodeCmp

FunctionNodeCmp定义在MergeFunctions类内,如下图所示。在比较两个FunctionNode是否相同时,首先会判断它们的函数哈希值是否相同:如果不同则直接返回。这里就是上文所说的函数哈希值比较的另一处使用,可以快速过滤不同的函数,提高优化的速度。对于函数哈希相同的函数对,构造FunctionComparator并通过FunctionComparator::compare方法进行比较,判定是否相同。

函数比较方法

FunctionComparator中设计实现了对于多种不同程序结构的比较算法,通过compare方法可以直接比较函数是否相同。FunctionComparator::compare方法定义在lib/Transforms/Utils/FunctionComparator.cpp文件中。判断两个函数是否相同分为两步:判断函数签名是否相同以及判断函数体是否相同。如果函数签名不同,就不需要进行后续函数体内容的比较;只要在函数签名相同的情况下才去比较函数体的内容。

函数签名比较

函数签名比较的实现在FunctionComparator::compareSignature方法中。虽然这部分的名字叫做函数签名比较,但是比较的内容远不止函数签名,包含了所有除了函数体外会影响函数是否相同判断的因素。只有满足如下的所有条件,才认为两个函数的函数签名是相同的。

  • attributes:两个函数的函数attributes列表相同。
  • GC:两个函数都没有GC,或者两个函数有相同的GC。
  • Section:两个函数都不在section内,或者两个函数在相同的section内。
  • Various Args:两个函数都有可变参数或者都没有可变参数。
  • Calling Convention:两个函数的calling convention相同。
  • Function Type:两个函数的函数类型相同。
  • Arg number:两个函数的参数个数相同。

函数体内容比较

对于函数签名相同的函数,需要进一步比较它们的函数体是否相同。函数体比较是基于AST树结构和基本块内的指令的比较。中心思想是:对于两个函数F和G,按照相同的遍历方法遍历AST,分别得到两个基本块链ChainF和ChainG。那么,如果两个函数相同,则基本块链上相同位置的基本块内的指令必然是相同的。

需要注意的是,这样的比较逻辑决定了MergeFunctions pass是依赖AST结构的,因此难以处理函数功能相同但是控制流发生改变的情况。

下图就是compare方法中对函数体的比较部分。可以看到,MergeFunctions以深度优先的方式同时遍历两个函数的AST去获得在相同位置的两个基本块;之后通过cmpBasicBlocks方法比较基本块是否相同。最终函数是否相同的结果取决于AST的结构是否相同和基本块内容比较的结果。

基本块与指令比较

基本块比较的实现在cmpBasicBlocks方法中。比较过程中,遍历两个基本块内对应位置的指令并进行比较。指令的比较可以分为两步:首先比较指令的操作符是否相同,之后依次比较指令对应位置的操作数是否相同。指令操作符的比较方法在FunctionComparator::cmpOperations方法中。在比较操作符的时候,考虑了如下的这些因素:

  • 两条指令的操作符在LLVM中对应的Opcode是否相同。
  • 两条指令的操作数个数是否相同。
  • 两条指令的类型是否相同。
  • 两条指令的optional flags是否相同。
  • 两条指令所有对应位置上的操作数的类型是否相同。
  • 特殊指令的处理:对于GetElementPtrInst,则使用cmpGEP方法进行比较。
  • 对于特殊的指令的特殊的检查。例如:对于内存分配指令,会比较分配内容的类型是否相同以及分配内存的alignment是否相同。

至此,MergeFunctions pass中函数比较的主要逻辑基本都已经完成。就像上文所说:在比较的过程中通过层层分解(将函数分解为函数签名和函数体-->将函数体分解为基本块位置和基本块内容-->将基本块分解为指令),将复杂的函数比较问题分解为了一些更小的单元的比较,而这些小单元的比较只需要特定的一些“元比较”操作就能完成,例如:cmpValues、cmpTypes、cmpNumbers等等。这些“元比较”操作都定义在FunctionComparator类中,每一个都实现了LLVM为特定代码结构定义的全序关系和比较,都可以在FunctionComparator.cpp中找到实现。在这里就不再做过多的介绍。

相同函数合并

对于两个相同的函数,需要对它们进行合并。函数合并的主要逻辑在MergeFunctions::mergeTwoFunctions方法中。本文重点在前面的函数比较部分,这部分只做简单的介绍。

假设对于当前分析的函数G,在FnTree中找到了相同的函数F,MergeFunctions在合并函数的时候会保留已有的函数F而删除函数G。相应的,需要修改所有对函数G的调用为新的函数调用。最后,对于FnTree中存在的调用了G的函数,将它们移出FnTree并重新添加到Deferred中等待下一轮的比较。

上图是mergeTwoFunctions方法的实现代码,可以看到,LLVM在实现函数调用替换的过程中,对于不同linkage的函数做了不同的处理。

LLVM中定义interposable如下:

global's definition can be substituted with an arbitrary definition at link time.

对于interposable的函数,在替换的过程中将会构建新的函数H。构建的函数H将会完全复制函数F,并且将所有函数F的调用全部替换为函数H的调用。之后使用writeThunk方法将所有对函数F和函数G的调用替换为对bitcast(H)函数的尾调用。对于非interposable的函数,MergeFunctions pass的替换过程就比较简单:使用writeThunk将所有对函数G的调用替换为对bitcast(F)的调用。

参考资料MergeFunctions pass, how it works — LLVM 6 documentation

文中涉及的代码版本:LLVM-6.0.0

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

LLVM passes: MergeFunctions Pass 的相关文章

  • 为什么相同的代码在同一台计算机上的执行时间可能不同?

    我是 C 编程新手 我编写了代码并希望获得它的运行时 这就是我所做的 每次运行代码时 我都会得到不同的运行时值 这样对吗 或者我的代码有问题吗 int main int argc char argv time t start end sta
  • c和java语言中的换行符

    现在行分隔符取决于系统 但在 C 程序中我使用 n 作为行分隔符 无论我在 Windows 还是 Linux 中运行它都可以正常工作 为什么 在java中 我们必须使用 n 因为它与系统相关 那么为什么我们在c中使用 n 作为新行 而不管我
  • 如何使用MemoryCache代替Timer来触发一个方法?

    以下方法通过等待已运行操作的结果来处理并发请求 对数据的请求可能会使用相同 不同的凭据同时出现 对于每组唯一的凭据 最多可以有一个GetCurrentInternal呼叫正在进行中 当准备就绪时 该呼叫的结果将返回给所有排队的服务员 pri
  • 使用Physics.Raycast 和Physics2D.Raycast 检测对象上的点击

    我的场景中有一个空的游戏对象 带有 2D 组件盒碰撞器 我将脚本附加到该游戏对象 void OnMouseDown Debug Log clic 但是当我点击我的游戏对象时 没有任何效果 你有什么想法 如何检测我的盒子碰撞器上的点击 使用光
  • C++ 中本地类中的静态成员变量?

    我知道我们不能宣布static本地类中的成员变量 但其原因尚不清楚 那么请问有人可以解释一下吗 另外 为什么我们不能访问非static函数内部定义的变量 内部已经定义了局部类 直接在局部类成员函数中 在下面给出的代码中 int main i
  • 启动时出现 OData v4 错误:找不到段“Whatever”的资源

    我正在构建新的 v4 服务 一切进展顺利 直到我为新模型 实体添加了新控制器 并在启动站点进行测试运行时收到此错误 控制器似乎编码正确 就像其他控制器一样 控制器 CustomersOData 中的操作 GetFeed 上的路径模板 Cus
  • 将内置类型转换为向量

    我的 TcpClient 类接受vector
  • 单元测试一起运行时失败,单独运行时通过

    所以我的单元测试遇到了一些问题 我不能只是将它们复制并粘贴到这里 但我会尽力而为 问题似乎是 如果我一项一项地运行测试 一切都会按预期进行 但如果我告诉它一起运行测试 则 1 5 将通过 TestMethod public void Obj
  • C++中的类查找结构体数组

    我正在尝试创建一个结构数组 它将输入字符串链接到类 如下所示 struct string command CommandPath cPath cPathLookup set an alarm AlarmCommandPath send an
  • 存储来自其他程序的事件

    我想将其他应用程序的事件存储在我自己的应用程序中 事件示例 打开 最小化 Word 或打开文件时 这样的事可能吗 运行程序 http msdn microsoft com en us library ms813609 aspx and 打开
  • C# Dns.GetHostEntry 不返回连接到 WiFi 的移动设备的名称

    我有一个 C 中的 Windows 窗体应用程序 我试图获取列表中所有客户端的主机名 下面给出的是 ra00l 来自此链接的代码示例 GetHostEntry 非常慢 https stackoverflow com questions 99
  • 获取 WPF 控件的所有附加事件处理程序

    我正在开发一个应用程序 在其中动态分配按钮的事件 现在的问题是 我希望获取按钮单击事件的所有事件 因为我希望删除以前的处理程序 我尝试将事件处理程序设置为 null 如下所示 Button Click null 但是我收到了一个无法分配 n
  • 未经许可更改内存值

    我有一个二维数组 当我第一次打印数组的数据时 日期打印正确 但其他时候 array last i 的数据从 i 0 到 last 1 显然是一个逻辑错误 但我不明白原因 因为我复制并粘贴了 for 语句 那么 C 更改数据吗 I use g
  • 批量更新 SQL Server C#

    我有一个 270k 行的数据库 带有主键mid和一个名为value 我有一个包含中值和值的文本文件 现在我想更新表格 以便将每个值分配给正确的中间值 我当前的方法是从 C 读取文本文件 并为我读取的每一行更新表中的一行 必须有更快的方法来做
  • 如何将自定义 JSON 文件添加到 IConfiguration 中?

    我正在使用 asp net Autofac 我正在尝试加载自定义 JSON 配置文件 并基于该文件创建 实例化 IConfiguration 实例 或者至少将我的文件包含到默认情况下构建的 IConfiguration asp net 中
  • Server.MapPath - 给定的物理路径,预期的虚拟路径

    我正在使用这行代码 var files Directory GetFiles Server MapPath E ftproot sales 在文件夹中查找文件 但是我收到错误消息说 给定物理路径但虚拟路径 预期的 我对在 C 中使用 Sys
  • 如何在按钮单击时模拟按键 - Unity

    我对 Unity 中的脚本编写非常陌生 我正在尝试创建一个按钮 一旦单击它就需要模拟按下 F 键 要拾取一个项目 这是我当前的代码 在编写此代码之前我浏览了所有统一论坛 但找不到任何有效的东西 Code using System Colle
  • memset 未填充数组

    u32 iterations 5 u32 ecx u32 malloc sizeof u32 iterations memset ecx 0xBAADF00D sizeof u32 iterations printf 8X n ecx 0
  • 使用 GhostScript.NET 打印 PDF DPI 打印问题

    我在用GhostScript NET http ghostscriptnet codeplex com打印 PDF 当我以 96DPI 打印时 PDF 打印效果很好 但有点模糊 如果我尝试以 600DPI 打印文档 打印的页面会被极大地放大
  • 当另一个线程可能设置共享布尔标志(最多一次)时,是否可以读取共享布尔标志而不锁定它?

    我希望我的线程能够更优雅地关闭 因此我尝试实现一个简单的信号机制 我不认为我想要一个完全事件驱动的线程 所以我有一个工作人员有一种方法可以使用关键部分优雅地停止它Monitor 相当于C lock我相信 绘图线程 h class Drawi

随机推荐

  • 推荐!国外程序员整理的机器学习资源大全

    本文汇编了一些机器学习领域的框架 库以及软件 按编程语言排序 C 计算机视觉 CCV 基于C语言 提供缓存 核心的机器视觉库 新颖的机器视觉库 OpenCV 它提供C C Python Java 以及 MATLAB接口 并支持Windows
  • vrep笔记

    这些天主要对vrep做了一些探索 一些笔记如下 1 urdf机器人模型文件的导入 点击plugins urdf importing即可 2 动力学模型的配置 将此处改成零 大意是以方框的正中心为质心 否则很容易抖 模型导入后坐标系都会被这个
  • NLP预训练模型系列-BERT

    NLP预训练模型系列文章目录 1 BERT 2 GPT 3 GPT 2 4 GPT 3 5 RoBERTa 6 BART 7 XLNet 目录 NLP预训练模型系列文章目录 前言 从BERT开始 1 Abstract 2 Introduct
  • 基于Django的员工管理系统1

    主题 员工管理系统 1 新建项目 2 创建app python manage py startapp app01 点击 run manage py Task 然后输入startapp app01 注册app 3 设计表结构 models p
  • IT技术岗位面试怎么介绍自己的项目经验?

    泽林又一批学员即将毕业 需要为面试做一些准备 都说面试7份靠能力 3份靠技能 而开始时的介绍项目又是技能中的重中之重 决定一次面试的成败 那么面试时如果介绍自己的项目呢 泽林教育为你们梳理了一份详细的项目经验介绍 预测面试官提问 先规划好答
  • android-smart-image-view源码分析

    public class BitmapImage implements SmartImage 定义Bitmap对象 private Bitmap bitmap 构造方法 public BitmapImage Bitmap bitmap th
  • 字面值。。

    1概念 不能改变的量 2 分类 基本类型 整型 short int 没有短整型字面值 int 100 d long int 100L ld long long int 100LL lld unsigned short int 没有短整型字面
  • git 在不同服务器主机上同步 git 仓库

    git 在不同服务器主机上同步 git 仓库 参考链接 https opentechguides com how to article git 177 git sync repos html 1 在本地的一个文件夹中执行 git clone
  • js实现AES加密

    安装第三方加密包 npm i crypto js 加密代码 let str 需要加密的字符串 let keyStr 密钥 let ivStr iv偏移量 const key CryptoJS enc Utf8 parse keyStr 十六
  • WGS84坐标系下大地坐标转换为空间直角坐标

    大地坐标表示方法 BLH 空间直角坐标表示方法 XYZ 进行地图投影的一般操作步骤为先将BLH转换为XYZ 然后将XYZ通过三参数或者7参数的办法转换为xyz 涉及到两个椭球体以及坐标系之间的转换 本文主要讨论BLH转换为XYZ的办法 通过
  • 线性代数的本质(二)——线性变换与矩阵

    文章目录 线性变换与矩阵 线性变换与二阶方阵 常见的线性变换 复合变换与矩阵乘法 矩阵的定义 列空间与基 矩阵的秩 逆变换与逆矩阵 线性变换与矩阵 线性变换与二阶方阵 本节从二维平面出发学习线性代数 通常选用平面坐标系 O x y Oxy
  • Java中jdbc的框架

    使用框架可以简化代码 提高开发效率 所以了解和掌握一些框架也是必须的 下面简单介绍几个jdbc框架 1 jdbcTemplate Spring提供 2 commons dbutils Apache提供 小巧的jdbc轻量级封装的工具包 主要
  • 【YARN】(1)-- 整体架构、RM、NM、AM等基础组件快速理解

    一 Yarn的功能和整体架构 Apache Hadoop YARN Yet Another Resource Negotiator 另一种资源协调者 是一种新的 Hadoop 资源管理器 它是一个通用资源管理系统和调度平台 可为上层应用提供
  • 什么是自动化测试?如何开展自动化测试你需要知道这些点

    目录 前言 什么是自动化测 分层的自动化测试 我为什么要做自动化测试 什么项目适合做自动化测试 选择什么工具进行自动化测试 selenium 用前须知 selenium IDE selenium Grid selenium RC selen
  • 怎样用苹果手机播放html文件夹,无需转格式 如何用iPhone轻松爽看各种片

    iPhone 5问世后 瞬间就成为了大家追随的最热门产品之一 无论是最具创新还是最热门 每一款产品推出后总是会存在遗憾的 iPhone 5同样不例外 在大家眼中它可能有这样或那样的问题 但是在我看来 自带视频播放器仅支持指定苹果标准视频 不
  • uni-app根据经纬度逆解析详细地址

    uni app中的getLocation 方法可以获取到用户当前的地理位置 经纬度 速度 但是返回参数中的address在app中才会显示 小程序中不会显示 所以我们需要进行逆解析其地址 解析出它的地址信息 1 首先要在腾讯位置服务中 控制
  • 第三方登陆--接入谷歌和FaceBook

    一 第三方登陆流程 一 用户点击登录 前端会调用第三方的SDK 获取到对应的数据 一般会有token userId 二 前端拿到这些信息之后 回调自己后端服务端的接口 进行token校验 主要目的是后端得防止他人使用恶意手段 别的平台 或者
  • Ubuntu下安装LLVM/Clang

    关于LLVM和Clang 参考原文 https blog csdn net SiberiaBear article details 103111028 LLVM 起初的作者是 Chris Lattner 博硕期间研究关于编译器优化的东西 其
  • 区块链:盗版者的噩梦?

    传统版权保护是用文本或数据库来进行处理的 用纸张文本处理有诸多不便之处 如记录搜寻 纸质保存 文件遗失等 而使用普通数据库 虽然查询速度加快 但其中的数据是可以被篡改的 因此很难被视为有效的电子证据 数字资产难以确权 同时再加上如今极度便利
  • LLVM passes: MergeFunctions Pass

    目录 What is MergeFunctions Pass 概述 FnTree和Deferred 基本流程 相同函数搜索 函数哈希值比较 函数哈希值的计算 函数哈希值比较的使用 函数结构比较 FunctionNodeCmp 函数比较方法