编写 LLVM Pass

介绍 —— 什么是 pass

官方文档

LLVM Pass框架是LLVM系统的重要组成部分,pass执行构成编译器的转换和优化,构建这些转换所使用的分析结果,最重要的是,它们是编译器代码的结构化技术。

所有的pass是 Pass 的子类,通过重写 Pass 中的方法来实现功能。根据你的 pass 是如何工作的,可以从 ModulePass , CallGraphSCCPass, FunctionPass , LoopPass, RegionPass 继承,这样系统就能更多地了解你的 pass 的作用,以及如何与其他 pass 相结合。LLVM Pass框架的主要特性之一是,它根据 pass 满足的约束(由它们派生的类来指示),以一种有效的方式调度 pass 运行。

快速开始 —— hello world

这里我们介绍如何书写 pass 的 "hello world",这个 pass 的作用是打印出正在编译的程序中存在的非外部函数的名称。它不会修改程序,它只是检查它,该 pass 的源代码和文件可在 LLVM 源代码树中找到 lib/Transforms/HelloHello.cpp

可以参考 llvm 进行阅读实现。

设置 build 环境

先 configure 以及 build LLVM,然后在 LLVM 源代码中的某个位置创建一个新的目录,在该例子中,假设建立了 lib/Transforms/Hello,然后要设置脚本文件编译 pass。可以将下面代码复制到 Hello 目录下的 CMakeLists.txt

add_llvm_library( LLVMHello MODULE
  Hello.cpp

  PLUGIN_TOOL
  opt
  )

add_llvm_library() 是 LLVM 的内部 cmake 宏函数,它接受不同的参数来指示将构建指定源文件的库类型,包括 SHARED, BUILDTREE_ONLY, MODULE, 和 INSTALL_WITH_TOOLCHAIN

lib/Transforms/CMakeLists.txt 里面加入:

add_subdirectory(Hello)

(请注意,已经有一个名为 Hello 的目录,其中包含一个示例 "Hello" pass)

这个 build 脚本指定工作目录中的 Hello.cpp 文件将被编译并链接到一个共享对象 $(LEVEL)/lib/LLVMHello.so,这个文件可以通过 opt 工具的 -load 选项被动态加载。如果您的操作系统使用了其他后缀,不是 .so (如 Windows 或 macOS) ,将使用适当的扩展。

现在我们已经建立了 build 脚本,我们只需要为 pass 本身编写代码。

所需的基本代码

因为我们在写 Pass,我们需要操作 Functions,并且我们需要输出一些东西,所以首先添加头文件:

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

然后使用命名空间:

using namespace llvm;

然后使用:

namespace {

开始是一个匿名命名空间。匿名命名空间对于 C++ 就像 static 关键字对于 C 一样(在全局范围内)。它使匿名命名空间内声明的内容只对当前文件可见。如果你不熟悉它们,可以查阅一本像样的 C++ 书籍以获得更多信息。

然后声明我们要书写的 pass:

struct Hello : public FunctionPass {

Hello 是 FunctionPass 的子类,可以在 classes 中查看不同 pass 子类的区别。现在我们只需要知道 FunctionPass 一次操作一个函数。

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

这声明了 LLVM 用于标识 pass 的 pass 标识符。这使得 LLVM 避免使用昂贵的 C++ 运行时信息。

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

我们声明了 runOnFunction 方法,它重写了从 FunctionPass 继承的方法,这就是我们要做的事情,所以我们只需要打印出带有每个函数名的消息。

char Hello::ID = 0;

我们在这里初始化 pass ID,LLVM 使用 ID 地址来识别 pass,所以初始值并不重要。

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

最后,我们注册 Hello 类,给它一个命令行参数 "hello",以及名字 "Hello World Pass",最后两个参数描述了它的行为:如果一个 pass 在没有修改的情况下遍历 CFG,那么第三个参数设置为 true; 如果 pass 是 analysis pass,例如 dominator tree pass,那么第四个参数设置为 true。

如果我们希望将 pass 注册为现有管道的一个步骤,则会提供一些扩展点,如:PassManagerBuilder::EP_EarlyAsPossible 在任何优化之前应用我们的 pass,或者 PassManagerBuilder::EP_FullLinkTimeOptimizationLast 在链接时间优化之后应用它。

static llvm::RegisterStandardPasses Y(
    llvm::PassManagerBuilder::EP_EarlyAsPossible,
    [](const llvm::PassManagerBuilder &Builder,
       llvm::legacy::PassManagerBase &PM) { PM.add(new Hello()); });

最后, .cpp 文件为:

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

#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"

using namespace llvm;

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

  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;
static RegisterPass<Hello> X("hello", "Hello World Pass",
                             false /* Only looks at CFG */,
                             false /* Analysis Pass */);

static RegisterStandardPasses Y(
    PassManagerBuilder::EP_EarlyAsPossible,
    [](const PassManagerBuilder &Builder,
       legacy::PassManagerBase &PM) { PM.add(new Hello()); });

现在一切都完成了,使用简单的 gmake 命令从构建目录的顶层编译该文件,您应该得到一个新文件 lib/LLVMHello.so。请注意,这个文件中的所有内容都包含在一个匿名命名空间中ーー这反映了这样一个事实,即 pass 是自包含的单元,不需要外部接口(尽管它们可以有外部接口)才能发挥作用。

使用 opt 运行 pass

按照 LLVM 系统入门 末尾的示例将 "Hello World" 编译为 LLVM。 我们现在可以通过像这样的转换来运行程序的位码文件(hello.bc)(当然,任何位码文件都可以工作):

$ opt -load lib/LLVMHello.so -hello < hello.bc > /dev/null
Hello: __main
Hello: puts
Hello: main

-load 选项指定 opt 应该将您的 pass 作为共享对象加载,这使得 -hello 成为有效的命令行参数(这是您需要注册 pass 的原因之一)。因为 Hello pass 不会以任何方式修改程序,所以我们只是丢弃 opt 的结果(将其发送到/dev/null)。

要查看您注册的其他字符串发生了什么,请尝试使用 -help 选项运行 opt:

$ opt -load lib/LLVMHello.so -help
OVERVIEW: llvm .bc -> .bc modular optimizer and analysis printer

USAGE: opt [subcommand] [options] <input bitcode file>

OPTIONS:
  Optimizations available:
...
    -guard-widening           - Widen guards
    -gvn                      - Global Value Numbering
    -gvn-hoist                - Early GVN Hoisting of Expressions
    -hello                    - Hello World Pass
    -indvars                  - Induction Variable Simplification
    -inferattrs               - Infer set function attributes
...

pass 名称被添加为 pass 的信息字符串,为 opt 的用户提供了一些文档。一旦你让它全部工作和测试,它可以用于找出你的 pass 有多快。PassManager 提供了一个很好的命令行选项(-time-passes),它允许你获取关于你的 pass 以及你排队等待的其他 pass 的执行时间的信息。例如:

$ opt -load lib/LLVMHello.so -hello -time-passes < hello.bc > /dev/null
Hello: __main
Hello: puts
Hello: main
===-------------------------------------------------------------------------===
                      ... Pass execution timing report ...
===-------------------------------------------------------------------------===
  Total Execution Time: 0.0007 seconds (0.0005 wall clock)

   ---User Time---   --User+System--   ---Wall Time---  --- Name ---
   0.0004 ( 55.3%)   0.0004 ( 55.3%)   0.0004 ( 75.7%)  Bitcode Writer
   0.0003 ( 44.7%)   0.0003 ( 44.7%)   0.0001 ( 13.6%)  Hello World Pass
   0.0000 (  0.0%)   0.0000 (  0.0%)   0.0001 ( 10.7%)  Module Verifier
   0.0007 (100.0%)   0.0007 (100.0%)   0.0005 (100.0%)  Total

如您所见,我们上面的实现非常快。列出的其他 pass 会自动被 opt 工具插入,以验证你的 pass 发出的 LLVM 是否仍然有效且格式良好,且未以某种方式被破坏。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,622评论 6 544
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,716评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,746评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,991评论 1 318
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,706评论 6 413
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 56,036评论 1 329
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 44,029评论 3 450
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,203评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,725评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,451评论 3 361
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,677评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,161评论 5 365
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,857评论 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,266评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,606评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,407评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,643评论 2 380

推荐阅读更多精彩内容