分类

编译 LLVM 项目

2019-08-17 20:53 compiler

最近开始研究 llvm 项目了. Rust 编译器也是基于它实现的.

LLVM 项目本身有多个子项目组成:

  • llvm, 编译器
  • lldb, 类似 gdb 的调试工具
  • clang, c/c++ 编译器前端
  • libcxx, c++ 标准库实现, 质量很棒, 可读性比较强
  • polly, 用来做数据计算优化
  • openmp, c/c++/fortran 并行计算库

首先是下载源代码, llvm 项目在 github 上面有完整的源码镜像:

$ git clone https://github.com/llvm/llvm-project

之后是安装系统依赖包, 编译 llvm 项目, 通常使用 clang 和 ninja, 速度要快些:

$ sudo apt update
$ sudo apt install ninja-build cmake clang
$ sudo apt build-dep cmake clang llvm

编译 llvm 项目

llvm-project 源码根目录, 创建 llvm-out 目录, 作为编译时的工作目录.

$ mkdir -v llvm-out
$ cd llvm-out
$ cmake -DCMAKE_BUILD_TYPE=Release -G Ninja -DCMAKE_INSTALL_PREFIX=/opt \
  -DLLVM_ENABLE_BINDINGS=OFF -DCMAKE_C_COMPILER=clang \
  -DCMAKE_CXX_COMPILER=clang++ ../llvm
$ cmake --build .

上面的选项:

  • -G Ninja 用于生成 ninja.build, 使用 ninja 编译系统
  • -DCMAKE_INSTALL_PREFIX=/opt, 定义库的安装目录是 /opt
  • -DLLVM_ENABLE_BINDINGS=Off, 禁用其它语言绑定, 比如 ocaml/python/go
  • -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, 使用 clang 编译器

编译完成后, 安装 llvm 库到系统中:

$ cmake --build . --target install

hello pass 来测试 llvm

接下来写一个 hello pass, 测试以上安装的 llvm 库是可用的.

$ tree
|-- hello
|   |-- CMakeLists.txt
|   `-- hello.cpp
|-- hello-out
|-- test.bc
`-- test.c

其中 hello/ 目录里面是 hello pass 的源代码, CMakeLists.txt 的内容是:

project(hello)
cmake_minimum_required(VERSION 3.2)

include_directories(/opt/include)

add_library(hello MODULE hello.cpp)
target_compile_features(hello PRIVATE cxx_range_for cxx_auto_type)
set_target_properties(hello PROPERTIES COMPILE_FLAGS "-fno-rtti")

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");

编译完之后, 会生成 libhello.so 动态库, 这个就是 hello pass 模块.

test.c 内容如下:

#include <stdio.h>

int main(void) {
  printf("Hello, world!\n");
  return 0;
}

使用 clang 编译生成 llvm 的字节码:

$ clang -c test.c -emit-llvm -o test.bc

之后使用 opt 命令验证上面生成的 libhello.so:

$ opt -load libhello.so -hello test.bc

终端打印的是:

WARNING: You're attempting to print out a bitcode file.
This is inadvisable as it may cause display problems. If
you REALLY want to taste LLVM bitcode first-hand, you
can force output with the `-f' option.

Hello: main

Hello: main 是上面 hello.cpp 中打印的, 这个测试 pass 是正常的.

参考: