使用LLVM Pass进行程序依赖关系的分析

一、LLVM

1.1 LLVM简介

LLVM项目是模块化和可重用的编译器和工具链技术的集合。LLVM全称为Lower Level Virtual Machine,最初是以C/C++为编译目标,现如今LLVM已经能够为ActionScript、D、Fortran、Haskell、Java、Objective-C、Swift、Python、Ruby、Rust、Scala等众多语言提供了编译支持,有一些新兴的语言就直接使用LLVM作为后端。此外LLVM目前已经不仅仅是个编程框架,它目前还包含了很多的子项目,比如最具盛名的Clang。
在LLVM中,程序的基本组成单位是模块(Module)。函数(Function)是Module的基本组成单位,一个Module由一个或多个函数组成;基本块(Basic Block)是函数的基本组成单位;指令(Instruction)是基本块的基本组成单位。
LLVM主要由3部分组成:前端(Frontend)、Pass、后端(Backend)。前端将源代码编译为LLVM IR。LLVM IR主要有三种格式:一种是在内存中的编译中间语言;一种是磁盘上存储的二进制中间语言(字节码文件,以.bc结尾),最后一种是用户可读的文本表现形式的中间格式(以.ll结尾)。这三种中间格式是完全相等的。LLVM IR是前端的输出,后端的输入,桥接前后端。LLVM IR基于静态单赋值(Static Single Assignment —SSA)的,有很大的灵活性和类型的安全性,能够清楚的表达绝大多数高级语言。Pass对程序的中间表示进行分析转换,是LLVM系统转换和优化工作的一个节点,每个节点(Pass)做一些工作,这些工作加起来就构成 了LLVM整个系统的优化和转化。后端用来生成实际的机器码。
图1 LLVM 架构

1.2 程序从源代码到可执行文件

编译简单来说其实就是把人类可读的高级语言映射到机器执行码。编译的过程是非常复杂的,为了使这个复杂的过程简化,LLVM就使用了模块化的思想,这样可以使得每一个编译阶段都被独立出来。总的分为以下几个部分:预处理(Preprocess)、词法分析 (Lexical Analysis)、语法分析 (Semantic Analysis)、IR代码生成 (CodeGen)、生成字节码 (LLVM Bitcode)、生成相关汇编文件、生成目标文件、、生成可执行文件。
在这里插入图片描述

二、编写LLVM Pass

LLVM 的pass框架是LLVM系统的一个很重要的部分。LLVM的优化和转换工作就是由多个pass来一起完成的。类似流水线操作一样,每个pass完成特定的优化工作。 要想真正发挥LLVM的威力,掌握pass是不可或缺的一环。LLVM中pass架构的可重用性和可控制性都非常好,这允许用户自己开发pass或者关闭一些默认提供的pass。总的来说,所有的pass大致可以分为两类:分析和转换。分析类的pass以提供信息为主,转换类的会修改中间代码。

2.1 Pass初探

首先,我们可以参照LLVM官方介绍Pass的文档,通过编写简单的分析类型的Pass来对Pass进行一个初步的认识。
(1) 基于Pass构建动态加载库
在llvm/lib/Transforms/Hello中,新建Hello.cpp:

#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
namespace {
struct Hello : public FunctionPass {
  static char ID;
  Hello() : FunctionPass(ID) {}
  // Pass的入口点
  bool runOnFunction(Function &F) override {
    errs() << "Hello: ";
    errs().write_escaped(F.getName()) << '\n';
    return false;
  }
}; // end of struct Hello
}  // end of anonymous namespace
char Hello::ID = 0; //初始化Pass Id,LLVM使用ID的地址来标识Pass,因此初始化值并不重要
// 注册Pass,命令行参数“hello”,并命名为“Hello World Pass”
static RegisterPass<Hello> X("hello", "Hello World Pass",
                             false /* Only looks at CFG */,
                             false /* Analysis Pass */);

在llvm/lib/Transforms/Hello中,新建CMakeLists.txt:

# 指定当前目录中的MyHello.cpp文件将被编译并链接到共享对象build/lib/LLVMHello.so,
# 可由opt工具通过其-load选项动态加载。
add_llvm_loadable_module( LLVMHello
	Hello.cpp
	PLUGIN_TOOL
	opt
)

在llvm/lib/Transforms/CMakeLists.txt中添加

add_subdirectory(Hello)

在build目录下执行make,生成LLVMHello.so
在这里插入图片描述
(2) 将测试程序转换为LLVM IR文件
clang++ test.cpp -c -emit-llvm -o test.bc
(3) 使用opt -load加载共享库以执行Pass对IR文件进行分析
opt -load .../build/lib/LLVMHello.so -hello test.bc
在这里插入图片描述
这样的一个过程,就是从编写LLVM Pass到使用编写的Pass对程序进行分析的一个完整的实现步骤。

2.2 程序依赖关系的分析

以往对于程序依赖关系的分析大多是依据信息流策略,使用LLVM Pass来对程序进行依赖关系的分析,主要包括以下3个方面:通过分析Def-Use指令链构建数据依赖图(DDG)、通过调用llvm/Analysis/MemoryDependenceAnalysis.h来构建内存依赖图(MDG)、通过调用llvm/Analysis/PostDominators.h实现对控制依赖图(CDG)的构建。进一步对DDG、MDG和CDG进行合并,生成程序依赖关系图(PDG)。其中,DDG和MDG构建的是指令(Instruction)之间的依赖关系,而CDG构建的是基本块(Basic Block)之间的依赖关系。
(1) 定义DependenceGraph
首先先定义依赖图,用于记录节点(指令或基本块)之间的依赖关系

template <typename ValueType> class DependenceGraph {
  protected:
    // 依赖关系表示为从节点到依赖于它们的节点集的映射
    map<const ValueType *, set<const ValueType *>> Nodes;
  public:
    // 节点迭代器
    typedef typename map<const ValueType *, set<const ValueType *>>::iterator nodes_iterator;
    typedef typename map<const ValueType *, set<const ValueType *>>::const_iterator const_nodes_iterator;
    typedef typename set<const ValueType *>::iterator dependant_iterator;
    typedef typename set<const ValueType *>::const_iterator const_dependant_iterator;
    void addNode(const ValueType *Value);
    void addEdge(const ValueType *From, const ValueType *To);
    void removeNode(const ValueType *Value);
    void removeEdge(const ValueType *From, const ValueType *To);
    bool dependsOn(const ValueType *A, const ValueType *B);
    // 获取依赖于节点A的节点集。
    const set<const ValueType *> &getDependants(const ValueType *A) const;
    dependant_iterator child_begin(const ValueType *A);
    dependant_iterator child_end(const ValueType *A);
    void clear();
    nodes_iterator nodes_begin();
    nodes_iterator nodes_end();
  };

(2) DDG(数据依赖关系图)的构建
DDG主要是通过对def-use指令链的分析进行构建:

  bool DataDependenceGraphPass::runOnFunction(Function &F) {
    for (inst_iterator I = inst_begin(F), E = inst_end(F); I != E; ++I) {
      Instruction *Inst = &*I;
      DDG.addNode(Inst);
    }
    for (inst_iterator I = inst_begin(F), E = inst_end(F); I != E; ++I) {
      for (User *U : I->users()) {
        if (Instruction *Inst = dyn_cast<Instruction>(U)) {
          DDG.addEdge(&*I, Inst);
        }
      }
    }
    return false;
  }

对于数据依赖图的构建,首先在图中将函数中每条指令添加到节点中,然后在图中将具有数据依赖关系的两条指令添加对应的边。
(3) MDG(内存依赖关系图)的构建
MDG是通过使用LLVM内置的MemoryDependenceAnalysis Pass来进行构建:

bool MemoryDependenceGraphPass::runOnFunction(Function &F) {
  MemoryDependenceAnalysis &mda = Pass::getAnalysis<MemoryDependenceAnalysis>();
  for (inst_iterator I = inst_begin(F), E = inst_end(F); I != E; ++I) {
    Instruction *Inst = &*I;
    MDG.addNode(Inst);
  }
  for (inst_iterator I = inst_begin(F), E = inst_end(F); I != E; ++I) {
    Instruction *Inst = &*I;
    if (!Inst->mayReadOrWriteMemory()) {
      continue;
    }
    MemDepResult mdr = mda.getDependency(Inst);
    Instruction *DepInst = mdr.getInst();
    if (DepInst != nullptr && mdr.isDef()) {
      MDG.addEdge(DepInst, Inst);
    }
  }
  return false;
}

对于内存依赖图的构建,首先在图中将函数中每条指令添加到节点中,然后在图中将具有内存依赖关系(这里仅考虑def依赖)的两条指令添加对应的边。
(4) CDG(控制依赖关系图)的构建
CDG是通过使用LLVM内置的PostDominatorTree pass来进行构建,基本组成单位是Basic Block:

bool ControlDependenceGraphPass::runOnFunction(Function &F) {
    const PostDominatorTree &pdt = Pass::getAnalysis<PostDominatorTree>();
    stack<DomTreeNode *> bottom_up_traversal = getBottomUpTraversal(pdt);
    map<BasicBlock *, set<BasicBlock *>> post_dom_frontier =
        getPostDomFrontier(pdt, std::move(bottom_up_traversal));
    for (auto &kv : post_dom_frontier) {
      BasicBlock *node = kv.first;
      CDG.addNode(node);
    }
    for (auto &kv : post_dom_frontier) {
      BasicBlock *to = kv.first;
      for (BasicBlock *from : kv.second) {
        CDG.addEdge(from, to);
      }
    }
    return false;
  }

后序支配树被用于构建控制依赖图,首先我们构建当前函数的后序支配树的自下而上的遍历的stack,然后使用它来为控制流图中的每个节点构建后序支配边界,从基本块到其后序支配边界的映射的reverse就是从基本块到具有控制依赖关系的基本块的映射,这样就完成了CDG的构建。
(5) 组合DDG,MDG和CDG形成PDG

bool ProgramDependenceGraphPass::runOnFunction(Function &F) {
  const DependenceGraph<BasicBlock> &cdg =
      Pass::getAnalysis<ControlDependenceGraphPass>().getCDG();
  const DependenceGraph<Instruction> &mdg =
      Pass::getAnalysis<MemoryDependenceGraphPass>().getMDG();
  const DependenceGraph<Instruction> &ddg =
      Pass::getAnalysis<DataDependenceGraphPass>().getDDG();
  for (auto I = ddg.nodes_cbegin(), E = ddg.nodes_cend(); I != E; ++I) {
    PDG.addNode(I->first);
  }
  for (auto I = ddg.nodes_cbegin(), E = ddg.nodes_cend(); I != E; ++I) {
    const Instruction *source = I->first;
    for (const Instruction *target : I->second) {
      PDG.addEdge(source, target);
    }
  }
  for (auto I = mdg.nodes_cbegin(), E = mdg.nodes_cend(); I != E; ++I) {
    const Instruction *source = I->first;
    for (const Instruction *target : I->second) {
      PDG.addEdge(source, target);
    }
  }
  for (auto I = cdg.nodes_cbegin(), E = cdg.nodes_cend(); I != E; ++I) {
    const Instruction &source = I->first->back();
    for (const BasicBlock *BB : I->second) {
      for (BasicBlock::const_iterator it = BB->begin(); it != BB->end(); ++it) {
        PDG.addEdge(&source, &*it);
      }
    }
  }
  DepGraphTraitsWrapper<Instruction>(PDG).writeToFile("pdg.test.dot");
  return false;
}

PDG的构建是将DDG、MDG和CDG进行合并,PDG的基本组成单元是指令,与DDG,MDG一致。首先我们从DDG和MDG中复制节点和依赖关系到PDG中,由于CDG中表示的是基本块之间的依赖关系,并且依赖基本块的所有指令取决于父基本块的最后一条指令,将这样的指令间的依赖关系加入到PDG以实现对CDG的转换,这样就完成了PDG的构建,实现了对于程序数据、控制和内存依赖关系的分析。

三、总结

本文简要介绍了LLVM的基本组成部分、LLVM IR和程序的编译过程,通过编写简单的LLVM Pass来初步认识Pass和如何使用自己编写的Pass对程序进行分析,进一步说明了使用LLVM Pass对程序进行依赖关系的分析分为数据、控制和内存依赖关系的分析,通过合并DDG,MDG和CDG来形成PDG。

参考 & 引用
[1] Jeanne Ferrante, Karl J. Ottenstein, Joe D. Warren. The program dependence graph and its use in optimization. ACM Transactions on Programming Languages and Systems, Vol.9, No.3, July 1987, Pages 319-349.
[2] Stanislav Manilov, Christos Vasiladiotis, Björn Franke. Generalized Profile-Guided Iterator Recognition. The University of Edinburgh. CC’18, February 24–25, 2018, Vienna, Austria.
[3] http://llvm.org/docs/WritingAnLLVMPass.html
[4] https://blog.csdn.net/hypercode/article/details/53815894
[5] https://github.com/compor/icsa-dswp

猜你喜欢

转载自blog.csdn.net/qq_23599965/article/details/84673600