初探LLVM&clang&pass
字数 2424 2025-08-24 16:48:07

LLVM & Clang & Pass 深入解析

1. 编译器架构概述

1.1 经典三段式设计

现代编译器通常采用三段式设计架构:

  1. 前端 (Frontend)

    • 进行词法分析、语法分析
    • 生成抽象语法树(AST)
    • 生成中间语言(IR)
    • 示例:Java字节码、LLVM IR、GCC的GIMPLE Tuples
  2. 优化器 (Optimizer)

    • 分析中间语言
    • 避免多余计算,提高性能
    • 通过Pass系统进行优化
  3. 后端 (Backend)

    • 根据中间语言生成对应CPU架构指令
    • 支持多种架构如X86、ARM等

这种设计实现了模块化,增加新语言只需实现新前端,增加新CPU架构只需实现新后端。

1.2 LLVM特点

  • 模块化设计:前后端解耦,支持灵活扩展
  • 统一的IR:所有语言前端生成相同的中间表示
  • 强大的Pass系统
    • 自动对Pass进行排序
    • 管道化提高效率
    • 支持分析、转换和代码生成Pass

1.3 LLVM的广义与狭义定义

  • 广义LLVM:指整个编译器框架,包括前端、优化器、后端
  • 狭义LLVM:特指LLVM后端,即优化器对IR进行优化直到生成目标代码的过程

2. LLVM主要子项目

项目名称 描述
LLVM Core 包含源代码和目标架构无关的独立配置器,支持多种CPU的汇编代码生成
Clang C/C++/Objective-C编译器,提供高效编译和良好错误信息
LLDB 基于LLVM和Clang构建的本地调试器,支持多线程调试
LLD clang/llvm内置的链接器
dragonegg GCC插件,可将GCC优化和代码生成器替换为LLVM工具
libc++, libc++ ABI 高性能C++标准库实现,完整支持C++11
compiler-rt 为动态测试工具提供运行时库实现
OpenMP 提供OpenMP运行时
vmkit 基于LLVM的Java和.NET虚拟机实现
polly 支持高级循环和数据本地化优化的框架
libclc OpenCL标准库实现
klee 符号化虚拟机,用于发现错误和证明函数属性
SAFECode 用于C/C++程序的内存安全编译器

3. LLVM编译安装

3.1 环境准备

sudo apt-get install subversion
sudo apt-get install cmake

3.2 下载源码

建议使用8.0.0版本:

#!/usr/bin/env bash
cd ~ && mkdir LLVM && cd LLVM
wget http://releases.llvm.org/8.0.0/llvm-8.0.0.src.tar.xz
tar -xf llvm-8.0.0.src.tar.xz && rm llvm-8.0.0.src.tar.xz
mv ./llvm-8.0.0.src ./llvm-8.0.0

cd llvm-8.0.0/tools/
wget http://releases.llvm.org/8.0.0/cfe-8.0.0.src.tar.xz
tar -xf ./cfe-8.0.0.src.tar.xz && rm ./cfe-8.0.0.src.tar.xz
mv ./cfe-8.0.0.src ./clang

cd ./clang/tools
wget http://releases.llvm.org/8.0.0/clang-tools-extra-8.0.0.src.tar.xz
tar -xf ./clang-tools-extra-8.0.0.src.tar.xz && rm ./clang-tools-extra-8.0.0.src.tar.xz
mv ./clang-tools-extra-8.0.0.src ./clang-tools-extra

cd ../../../projects/
wget http://releases.llvm.org/8.0.0/compiler-rt-8.0.0.src.tar.xz
wget http://releases.llvm.org/8.0.0/libcxx-8.0.0.src.tar.xz
wget http://releases.llvm.org/8.0.0/libcxxabi-8.0.0.src.tar.xz
tar -xf ./compiler-rt-8.0.0.src.tar.xz && rm ./compiler-rt-8.0.0.src.tar.xz
tar -xf libcxx-8.0.0.src.tar.xz && rm libcxx-8.0.0.src.tar.xz
tar -xf libcxxabi-8.0.0.src.tar.xz && rm libcxxabi-8.0.0.src.tar.xz
mv compiler-rt-8.0.0.src ./compiler-rt
mv libcxx-8.0.0.src ./libcxx
mv libcxxabi-8.0.0.src ./libcxxabi

cd ../../ && mkdir build && cd build
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release ../llvm-8.0.0
make -j4
sudo make install

3.3 验证安装

./build/bin/clang++ -v

3.4 使用Clang重新编译LLVM

CC=clang CXX=clang++ cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release ../llvm-8.0.0
make -j4
sudo make install

4. Clang使用

4.1 基本编译

#include <iostream>
using namespace std;

int main() {
    cout << "Hello, world!" << endl;
    return 0;
}

编译C++,使用C++11标准和libc++库:

clang++ -std=c++11 -stdlib=libc++ test.cpp

4.2 语法检查

clang test.cpp -fsyntax-only

4.3 编译选项

  • -c:仅进行预处理和编译,输出重定位ELF

    clang++ -std=c++11 -stdlib=libc++ ./test1.cpp -c -o test
    
  • -S:仅进行预处理和编译,输出可读汇编文本

    clang++ -std=c++11 -stdlib=libc++ ./test1.cpp -S -o test
    

4.4 生成LLVM IR

生成未优化的LLVM IR:

clang++ -std=c++11 -stdlib=libc++ test.cpp -S -emit-llvm -o test

生成O3优化的LLVM IR:

clang++ -std=c++11 -stdlib=libc++ test.cpp -S -emit-llvm -o test -O3

4.5 编译过程分析

示例代码:

#include <stdio.h>
int main() {
    printf("hello world\n");
    return 0;
}

查看编译各阶段:

clang -ccc-print-phases test1.c

4.5.1 预处理

clang -E test1.c

4.5.2 词法分析

clang -fmodules -E -Xclang -dump-tokens test1.c

4.5.3 语法分析

clang -fmodules -fsyntax-only -Xclang -ast-dump test1.c

AST节点类型:

  • TranslationUnitDecl:根节点,表示编译单元
  • FunctionDecl:函数声明
  • ParmVarDecl:参数声明
  • CompoundStmt:具体语句
  • DeclStmt:语句声明
  • VarDecl:变量声明
  • IntegerLiteral:整数字面量
  • BinaryOperator:操作符
  • ImplicitCastExpr:隐式转换
  • DeclRefExpr:引用类型声明
  • ReturnStmt:返回语句

5. 中间语言IR

5.1 IR表示形式

  1. 文本格式:便于阅读的汇编格式,后缀为.ll

    clang test1.c -S -emit-llvm -o test.ll
    
  2. 二进制格式:不可读,后缀为.bc

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

5.2 IR格式转换

  • .bc.ll

    llvm-dis test.bc
    
  • .ll.bc转汇编:

    llc ./test.ll -o test.s
    

5.3 IR语法

5.3.1 关键字

  • ;:注释
  • define:函数定义
  • declare:函数声明
  • i32:32位整数
  • ret:函数返回
  • alloca:栈空间分配内存
  • align:内存对齐
  • load:读取数据
  • store:写入数据
  • icmp:整数值比较
  • br:选择分支
  • label:代码标签

5.3.2 标识符

  • 全局标识符:以@开头(函数、全局变量)
  • 本地标识符:以%开头(寄存器名称、类型)

5.3.3 示例

乘法运算的三种IR表示:

; 乘法运算
%result = mul i32 %X, 8

; 左移运算
%result = shl i32 %X, 3 

; 三次加法运算
%0 = add i32 %X, %X
%1 = add i32 %0, %0
%result = add i32 %1, %1

5.3.4 其他语法元素

  • unnamed_addr:标记全局变量,表示地址不重要
  • nsw:"No Signed Wrap",无符号值运算标识
  • nuw:"No Unsigned Wrap",有符号值运算标识
  • bitcast ... to ..:类型转换指令
  • dso_local:表示解析为同一链接单元中的符号

6. Pass系统

6.1 Pass分类

  1. 分析Pass:收集信息用于调试或可视化
  2. 转换Pass:修改IR,进行优化或混淆
  3. 代码生成Pass:生成目标代码

6.2 编写简单Pass

6.2.1 项目结构

outpass/
├── CMakeLists.txt
└── Print_FuncPass/
    ├── CMakeLists.txt
    └── Print_FuncPass.cpp

6.2.2 顶层CMakeLists.txt

cmake_minimum_required(VERSION 3.4)
set(ENV{LLVM_DIR} ~/LLVM/bulid/lib/cmake/llvm)
find_package(LLVM REQUIRED CONFIG)
add_definitions(${LLVM_DEFINITIONS})
include_directories(${LLVM_INCLUDE_DIRS})
link_directories(${LLVM_LIBRARY_DIRS})
add_compile_options(-std=c++14)
add_subdirectory(Print_FuncPass)

6.2.3 Pass目录CMakeLists.txt

add_library(PrintFunctions MODULE
    Print_FuncPass.cpp)
set_target_properties(PrintFunctions PROPERTIES COMPILE_FLAGS "-fno-rtti")
if(APPLE)
    set_target_properties(PrintFunctions PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
endif(APPLE)

6.2.4 Pass实现

#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) {}
        
        virtual bool runOnFunction(Function &F) {
            errs() << "A function has been called: " << F.getName() << "!\n";
            return false;
        }
    };
}

char Hello::ID = 0;

// 注册到opt中
static RegisterPass<Hello> X("print_func", "print func name 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());
    });

6.2.5 测试代码

#include <stdio.h>

int func2() {
    int a, b = 1;
    return a + b;
}

int func1() {
    int a, b = 1;
    func2();
    return a + b;
}

int main() {
    func1();
    return 0;
}

6.2.6 使用Pass

  1. 直接使用clang

    clang -Xclang -load -Xclang ./outpass/Print_FuncPass/libPrintFunctions.so ./test.c
    
  2. 使用opt

    clang -emit-llvm -c ./test.c
    opt -load ./outpass/Print_FuncPass/libPrintFunctions.so -print_func < ./test.bc
    

7. 总结

本文详细介绍了LLVM编译器框架的核心概念、安装配置、使用方法和Pass开发:

  1. 理解了LLVM的三段式架构设计优势
  2. 掌握了LLVM的编译安装过程
  3. 学习了Clang的基本使用和编译过程分析
  4. 深入了解了LLVM IR的语法和特性
  5. 实践了如何开发自定义Pass

LLVM强大的模块化设计和Pass系统使其成为编译器技术研究和开发的理想平台。通过自定义Pass,开发者可以实现代码优化、分析、混淆等多种功能。

LLVM & Clang & Pass 深入解析 1. 编译器架构概述 1.1 经典三段式设计 现代编译器通常采用三段式设计架构: 前端 (Frontend) : 进行词法分析、语法分析 生成抽象语法树(AST) 生成中间语言(IR) 示例:Java字节码、LLVM IR、GCC的GIMPLE Tuples 优化器 (Optimizer) : 分析中间语言 避免多余计算,提高性能 通过Pass系统进行优化 后端 (Backend) : 根据中间语言生成对应CPU架构指令 支持多种架构如X86、ARM等 这种设计实现了模块化,增加新语言只需实现新前端,增加新CPU架构只需实现新后端。 1.2 LLVM特点 模块化设计 :前后端解耦,支持灵活扩展 统一的IR :所有语言前端生成相同的中间表示 强大的Pass系统 : 自动对Pass进行排序 管道化提高效率 支持分析、转换和代码生成Pass 1.3 LLVM的广义与狭义定义 广义LLVM :指整个编译器框架,包括前端、优化器、后端 狭义LLVM :特指LLVM后端,即优化器对IR进行优化直到生成目标代码的过程 2. LLVM主要子项目 | 项目名称 | 描述 | |---------|------| | LLVM Core | 包含源代码和目标架构无关的独立配置器,支持多种CPU的汇编代码生成 | | Clang | C/C++/Objective-C编译器,提供高效编译和良好错误信息 | | LLDB | 基于LLVM和Clang构建的本地调试器,支持多线程调试 | | LLD | clang/llvm内置的链接器 | | dragonegg | GCC插件,可将GCC优化和代码生成器替换为LLVM工具 | | libc++, libc++ ABI | 高性能C++标准库实现,完整支持C++11 | | compiler-rt | 为动态测试工具提供运行时库实现 | | OpenMP | 提供OpenMP运行时 | | vmkit | 基于LLVM的Java和.NET虚拟机实现 | | polly | 支持高级循环和数据本地化优化的框架 | | libclc | OpenCL标准库实现 | | klee | 符号化虚拟机,用于发现错误和证明函数属性 | | SAFECode | 用于C/C++程序的内存安全编译器 | 3. LLVM编译安装 3.1 环境准备 3.2 下载源码 建议使用8.0.0版本: 3.3 验证安装 3.4 使用Clang重新编译LLVM 4. Clang使用 4.1 基本编译 编译C++,使用C++11标准和libc++库: 4.2 语法检查 4.3 编译选项 -c :仅进行预处理和编译,输出重定位ELF -S :仅进行预处理和编译,输出可读汇编文本 4.4 生成LLVM IR 生成未优化的LLVM IR: 生成O3优化的LLVM IR: 4.5 编译过程分析 示例代码: 查看编译各阶段: 4.5.1 预处理 4.5.2 词法分析 4.5.3 语法分析 AST节点类型: TranslationUnitDecl :根节点,表示编译单元 FunctionDecl :函数声明 ParmVarDecl :参数声明 CompoundStmt :具体语句 DeclStmt :语句声明 VarDecl :变量声明 IntegerLiteral :整数字面量 BinaryOperator :操作符 ImplicitCastExpr :隐式转换 DeclRefExpr :引用类型声明 ReturnStmt :返回语句 5. 中间语言IR 5.1 IR表示形式 文本格式 :便于阅读的汇编格式,后缀为 .ll 二进制格式 :不可读,后缀为 .bc 5.2 IR格式转换 .bc 转 .ll : .ll 或 .bc 转汇编: 5.3 IR语法 5.3.1 关键字 ; :注释 define :函数定义 declare :函数声明 i32 :32位整数 ret :函数返回 alloca :栈空间分配内存 align :内存对齐 load :读取数据 store :写入数据 icmp :整数值比较 br :选择分支 label :代码标签 5.3.2 标识符 全局标识符:以 @ 开头(函数、全局变量) 本地标识符:以 % 开头(寄存器名称、类型) 5.3.3 示例 乘法运算的三种IR表示: 5.3.4 其他语法元素 unnamed_addr :标记全局变量,表示地址不重要 nsw :"No Signed Wrap",无符号值运算标识 nuw :"No Unsigned Wrap",有符号值运算标识 bitcast ... to .. :类型转换指令 dso_local :表示解析为同一链接单元中的符号 6. Pass系统 6.1 Pass分类 分析Pass :收集信息用于调试或可视化 转换Pass :修改IR,进行优化或混淆 代码生成Pass :生成目标代码 6.2 编写简单Pass 6.2.1 项目结构 6.2.2 顶层CMakeLists.txt 6.2.3 Pass目录CMakeLists.txt 6.2.4 Pass实现 6.2.5 测试代码 6.2.6 使用Pass 直接使用clang : 使用opt : 7. 总结 本文详细介绍了LLVM编译器框架的核心概念、安装配置、使用方法和Pass开发: 理解了LLVM的三段式架构设计优势 掌握了LLVM的编译安装过程 学习了Clang的基本使用和编译过程分析 深入了解了LLVM IR的语法和特性 实践了如何开发自定义Pass LLVM强大的模块化设计和Pass系统使其成为编译器技术研究和开发的理想平台。通过自定义Pass,开发者可以实现代码优化、分析、混淆等多种功能。