03-LLVM Pass使用(传统Pass)

本文按照LLVM官网编写Pass(旧版)的入门教程进行实验,对LLVM Pass进行初步的学习了解,并解决实验中遇到的问题,官网教程链接如下:
https://llvm.org/docs/WritingAnLLVMPass.html

本文只是学习并记录笔记,如有错误或不足请谅解指正,谢谢!

什么是pass

LLVM Pass就是“遍历一遍IR,可以同时对它做一些操作”的意思

LLVM Pass框架是LLVM系统中很重要的一部分。LLVM的优化和转换就是由多个pass一起完成的,类似流水线操作一样,每个pass都有着自己特定的优化工作。

Pass总体上分为两类:

  • 分析类:通过pass提供信息
  • 转换类:修改中间代码

注:该教程涉及的是传统Pass管理器(旧版),LLVM默认使用新的Pass管理器进行优化(只有codegen 管道还在使用旧版Pass)。后面的时间也会对新版Pass进行学习


配置Pass构建环境

在LLVM源代码库某处创建新目录,例如源码中提供了:llvm/lib/Transforms/Hello

设置构建脚本,将以下内容复制到llvm/lib/Transforms/Hello/CMakeLists.txt

add_llvm_library( LLVMHello MODULE
Hello.cpp

PLUGIN_TOOL
opt
)

代码的含义是:此构建脚本指定当前目录中的Hello.cpp文件将被编译并链接到一个共享项$(LEVEL)/lib/LLVMHello.so中,共享项可以由opt工具通过 -load 选项动态加载。

之后将以下内容放置在lib/Transforms/CMakeLists.txt中,这样才可以选择在之后的编译中对’Hello’pass编译

add_subdirectory(Hello)

注:源码中已经有了一个“Hello”pass 的目录及示例,因此对于“Hello”不需要修改上面的内容。


编写Pass代码

头文件:

#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"

llvm命名空间

using namespace llvm;

匿名命名空间:

namespace {

声明pass本身:这里声明了一个“ Hello”类,它是FunctionPass的子类,以及LLVM 用来识别 pass 的 pass 标识符

struct Hello : public FunctionPass {
static char ID;
Hello() : FunctionPass(ID) {}

声明一个 runOnFunction 方法,它覆盖了从 FunctionPass 继承的抽象虚函数,打印出每个函数名称:

bool runOnFunction(Function &F) override {
errs() << "Hello: ";
errs().write_escaped(F.getName()) << '\n';
return false;
}
}; // end of struct Hello
} // end of anonymous namespace

初始化 pass ID。LLVM 使用 ID 的地址来识别 pass,所以初始化值并不重要;最后注册Hello类:

char Hello::ID = 0;
static RegisterPass<Hello> X("hello", "Hello World Pass",
false /* Only looks at CFG */,
false /* Analysis Pass */);

总的来说,整个Hello.cpp应该为这样:

#include "llvm/ADT/Statistic.h"
#include "llvm/IR/Function.h"
#include "llvm/Pass.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

#define DEBUG_TYPE "hello"

STATISTIC(HelloCounter, "Counts number of functions greeted");

namespace {
// Hello - The first implementation, without getAnalysisUsage.
struct Hello : public FunctionPass {
static char ID; // Pass identification, replacement for typeid
Hello() : FunctionPass(ID) {}

bool runOnFunction(Function &F) override {
++HelloCounter;
errs() << "Hello: ";
errs().write_escaped(F.getName()) << '\n';
return false;
}
};
}

char Hello::ID = 0;
static RegisterPass<Hello> X("hello", "Hello World Pass");

当然,因为源码中已经提供了初始样本,可以不对代码进行改动,这与官方教程中的内容有些许不同:

//===- Hello.cpp - Example code from "Writing an LLVM Pass" ---------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements two versions of the LLVM "Hello World" pass described
// in docs/WritingAnLLVMPass.html
//
//===----------------------------------------------------------------------===//

#include "llvm/ADT/Statistic.h"
#include "llvm/IR/Function.h"
#include "llvm/Pass.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;

#define DEBUG_TYPE "hello"

STATISTIC(HelloCounter, "Counts number of functions greeted");

namespace {
// Hello - The first implementation, without getAnalysisUsage.
struct Hello : public FunctionPass {
static char ID; // Pass identification, replacement for typeid
Hello() : FunctionPass(ID) {}

bool runOnFunction(Function &F) override {
++HelloCounter;
errs() << "Hello : ";
errs().write_escaped(F.getName()) << '\n';
return false;
}
};
}

char Hello::ID = 0;
static RegisterPass<Hello> X("hello", "Hello World Pass");

namespace {
// Hello2 - The second implementation with getAnalysisUsage implemented.
struct Hello2 : public FunctionPass {
static char ID; // Pass identification, replacement for typeid
Hello2() : FunctionPass(ID) {}

bool runOnFunction(Function &F) override {
++HelloCounter;
errs() << "Hello: ";
errs().write_escaped(F.getName()) << '\n';
return false;
}

// We don't modify the program, so we preserve all analyses.
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.setPreservesAll();
}
};
}

char Hello2::ID = 0;
static RegisterPass<Hello2>
Y("hello2", "Hello World Pass (with getAnalysisUsage implemented)");

编译及运行Pass

在对llvm/lib/Transforms/Hello下的Hello.cpp完成编写或修改后,重新编译如下:

# root @ zzz in ~/Desktop/llvm-project/build on git:main x 
$ ninja -j8
[2/2] Linking CXX shared module lib/LLVMHello.so

在lib文件夹下会生成对应的LLVMHello.so,之后可以使用opt命令通过 pass 运行 LLVM 程序

这里随便使用一个C程序:

//hello_llvm.c
#include <stdio.h>
int add(int a, int b)
{
return a+b;
}
int sub(int a, int b)
{
return a-b;
}
int main() {
printf("hello LLVM\n");
return 0;
}

编译为bitcode:

clang -emit-llvm -c hello_llvm.c -o hello_llvm.bc

运行pass,识别函数名:

# root @ zzz in ~/Desktop 
$ opt -load ~/Desktop/llvm-project/build/lib/LLVMHello.so -hello < hello_llvm.bc > /dev/null -enable-new-pm=0
Hello : add
Hello : sub
Hello : main

注:若在最后运行时遇到opt: unknown pass name 'hello'报错,则可根据提醒,添加-enable-new-pm=0 参数,使用旧版的pass

至此完成第一个Pass编写、运行。

文章作者: HotSpurzzZ
文章链接: http://example.com/2022/05/10/03-LLVM Pass使用(一)/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 HotSpurzzZ