LLVM-05 CMake 基础使用与 LLVM 项目工程化实践

LLVM-05 CMake 基础使用与 LLVM 项目工程化实践

本文讲解了 CMake 跨平台构建基础、标准流程与核心指令,并结合前序 LLVM 笔记内容,给出三类可用的实践配置。

CMake is a cross-platform, open-source build system generator that abstracts compiler and platform-specific details, and is the official standard build framework for the entire LLVM/Clang infrastructure. It enables consistent compilation, linking, and toolchain management across Linux, Windows, macOS, and different CPU architectures (x86, ARM, RISC-V).

CMake 是 LLVM 生态的官方标准构建工具,是一套声明式、目标导向、跨平台的构建系统生成器。它通过统一的 CMakeLists.txt 配置文件,屏蔽不同平台、编译器、架构的构建差异,完成从源码到可执行文件、静态 / 动态库、LLVM Pass 插件的全流程工程化构建。

CMake 核心基础与标准规范

核心设计原则与约束

CMake 有 4 条必须遵守的核心规则:

  1. 所有配置必须围绕 add_executable/add_library 等生成的目标 (Target)展开。

  2. 所有头文件、链接库、编译选项必须通过 target_* 指令为目标显式声明,不允许隐式依赖。

  3. 必须采用 out-of-source(源码与构建目录分离)的构建方式,避免构建产物污染源码目录。

  4. CMake 版本必须与目标 LLVM 版本兼容,LLVM 15 + 要求 CMake 最低版本为 3.20

标准构建全流程

CMake 有标准的、可复现的构建流程,所有示例均可通过该流程执行:

# 创建与源码目录分离的构建目录
mkdir build && cd build
# 执行配置,生成对应平台的构建文件(Makefile/Ninja/VS 工程)
# .. 指向顶层 CMakeLists.txt 所在的源码目录
cmake ..
# 执行编译,-j 指定并行编译线程数,提升编译速度
make -j8

核心基础指令与最简可复现示例

源码结构

├── hello.c
└── CMakeLists.txt

hello.c

#include <stdio.h>
int main () {
    printf ("Hello LLVM & CMake\n");
    return 0;
}

CMakeLists.txt

# 指定 CMake 最低兼容版本,必须放在文件最开头
cmake_minimum_required (VERSION 3.20)
# 定义项目名称与支持的编程语言
project (HelloLLVM C CXX)
# 定义构建目标:生成名为 hello 的可执行文件
add_executable (hello hello.c)

核心指令解释表

指令

核心作用

对应 LLVM 场景

cmake_minimum_required (VERSION x.xx)

声明最低 CMake 版本,避免版本兼容问题

匹配 LLVM 版本要求,LLVM 15 + 需≥3.20

project (项目名 C CXX)

定义项目名称与启用的编程语言

LLVM 项目必须启用 C++(CXX),Pass 开发强制要求

add_executable (目标名 源文件列表)

定义可执行文件构建目标

构建调用 LLVM API 的工具、测试用例

add_library (目标名 STATIC/SHARED 源文件列表)

定义静态 / 动态库构建目标

封装 LLVM 工具链、自定义 IR 处理逻辑

target_include_directories (目标名 PRIVATE/PUBLIC 头文件路径)

为目标添加头文件搜索路径

引入 LLVM API 头文件、自定义 Pass 头文件

target_link_libraries (目标名 PRIVATE/PUBLIC 依赖库名)

为目标链接依赖库

链接 LLVM 核心库、第三方编译工具库

add_custom_target ()

定义自定义构建目标

自动化生成 LLVM IR、执行 IR 优化、Pass 测试等

find_package (LLVM REQUIRED)

查找系统中已安装的 LLVM 库与配置

所有 LLVM 二次开发项目的核心入口指令

CMake 与 LLVM 核心场景实践

通过 CMake 自动化生成 LLVM IR

替代手动执行 clang -S -emit-llvm 命令,通过 CMake 实现 C 代码到 LLVM IR(.ll 文本格式)的自动化生成,支持多文件、批量生成。

项目结构

├── main.c
└── CMakeLists.txt

main.c

int main () {
    int a = 10;
    int b = 20;
    int c = a + b;
    return c;
}

CMakeLists.txt

cmake_minimum_required (VERSION 3.20)
project (GenLLVMIR C)

# 将 C 源码编译为未优化的 LLVM IR 文本文件
# OUTPUT:声明生成的产物 main.ll
# COMMAND:执行 clang IR 生成命令
# DEPENDS:声明依赖的源码,源码修改时会重新生成 IR
add_custom_command (
  OUTPUT main.ll
  COMMAND clang -S -emit-llvm -O0 ${CMAKE_SOURCE_DIR}/main.c -o main.ll
  DEPENDS main.c
  COMMENT "正在生成 LLVM IR 文件: main.ll"
)

# 自定义构建目标,ALL 表示默认执行该目标
add_custom_target (gen_ir ALL DEPENDS main.ll)

执行标准构建流程后,会在 build 目录下自动生成 main.ll 文件,与手动执行 clang 命令生成的 IR 完全一致,可直接用于后续优化、Pass 调试。

CMake 构建调用 LLVM API 的可执行程序

用于开发基于 LLVM API 的 IR 生成、内存地址计算、指令分析等工具,核心是通过 CMake 引入 LLVM 头文件与核心库。

前置要求:已安装 LLVM 开发环境,并配置 LLVM_DIR 环境变量(指向 LLVM 安装目录下的 lib/cmake/llvm 文件夹,存放 LLVM 的 CMake 配置文件)。

项目结构

├── llvm_demo.cpp
└── CMakeLists.txt

llvm_demo.cpp

#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/IRBuilder.h"

using namespace llvm;

int main () {
    LLVMContext Context;
    // 创建 Module
    Module *M = new Module ("demo_module", Context);
    IRBuilder<> Builder (Context);

    // 函数类型定义:i32 (i32, i32)
    FunctionType *FuncType = FunctionType::get (Builder.getInt32Ty (), {Builder.getInt32Ty (), Builder.getInt32Ty ()}, false);
    Function *AddFunc = Function::Create (FuncType, Function::ExternalLinkage, "add", M);

    // 创建入口基本块
    BasicBlock *EntryBB = BasicBlock::Create (Context, "entry", AddFunc);
    Builder.SetInsertPoint (EntryBB);

    // 实现加法逻辑
    Value *Arg1 = AddFunc->getArg (0);
    Value *Arg2 = AddFunc->getArg (1);
    Value *Sum = Builder.CreateAdd (Arg1, Arg2, "sum");
    Builder.CreateRet (Sum);

    // 打印生成的 IR 到控制台
    M->print (outs (), nullptr);
    return 0;
}

CMakeLists.txt

cmake_minimum_required (VERSION 3.20)
project (LLVMDemo CXX)

# 查找系统中安装的 LLVM,REQUIRED 表示必须找到,否则配置失败
find_package (LLVM REQUIRED CONFIG)

# 打印 LLVM 版本信息,便于调试版本匹配问题
message (STATUS "找到 LLVM 版本: ${LLVM_PACKAGE_VERSION}")
message (STATUS "LLVM 安装路径: ${LLVM_INSTALL_PREFIX}")

# 为当前项目添加 LLVM 头文件搜索路径
include_directories (${LLVM_INCLUDE_DIRS})
# 添加 LLVM 预设的编译宏定义
add_definitions (${LLVM_DEFINITIONS})

# 设置 C++ 标准,LLVM 15 + 要求 C++17 及以上
set (CMAKE_CXX_STANDARD 17)
set (CMAKE_CXX_STANDARD_REQUIRED ON)

# 定义可执行文件目标
add_executable (llvm_demo llvm_demo.cpp)

# 为目标链接 LLVM 核心库
target_link_libraries (llvm_demo PRIVATE ${LLVM_LIBRARIES})

构建完成后,运行 ./llvm_demo,会直接在控制台输出生成的 add 函数的 LLVM IR。

CMake 构建 LLVM New Pass Manager 插件

LLVM New Pass 插件的官方标准 CMake 构建方式,无需重新编译整个 LLVM 项目,即可实现插件的动态编译与加载。

项目结构

├── MyCustomPass.cpp
└── CMakeLists.txt

MyCustomPass.cpp

#include "llvm/IR/PassManager.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

namespace {
// 自定义 Pass,继承自 PassInfoMixin
struct MyCustomPass : public PassInfoMixin<MyCustomPass> {
    PreservedAnalyses run (Function &F, FunctionAnalysisManager &AM) {
        // 遍历函数,打印函数名,实现简单的插桩逻辑
        errs () << "Pass 遍历到函数:" << F.getName () << "\n";
        // 声明未修改 IR,保留所有分析结果
        return PreservedAnalyses::all ();
    }

    // 注册 Pass 到 Pipeline
    static bool isRequired () { return true; }
};
} //namespace

// 插件注册入口
extern "C" LLVM_ATTRIBUTE_WEAK PassPluginLibraryInfo llvmGetPassPluginInfo () {
    return {
        LLVM_PLUGIN_API_VERSION,
        "MyCustomPass",
        LLVM_VERSION_STRING,
        [](sslocal://flow/file_open?url=PassBuilder+%26PB&flow_extra=eyJsaW5rX3R5cGUiOiJjb2RlX2ludGVycHJldGVyIn0=) {
            PB.registerPipelineParsingCallback (
                [](sslocal://flow/file_open?url=StringRef+Name%2C+FunctionPassManager+%26FPM%2C+ArrayRef%3CPassBuilder%3A%3APipelineElement%3E&flow_extra=eyJsaW5rX3R5cGUiOiJjb2RlX2ludGVycHJldGVyIn0=) {
                    if (Name == "my-custom-pass") {
                        FPM.addPass (MyCustomPass ());
                        return true;
                    }
                    return false;
                }
            );
        }
    };
}

CMakeLists.txt

cmake_minimum_required (VERSION 3.20)
project (MyCustomPass CXX)

# 查找 LLVM 配置
find_package (LLVM REQUIRED CONFIG)

# 版本与 C++ 标准匹配
message (STATUS "LLVM 版本: ${LLVM_PACKAGE_VERSION}")
set (CMAKE_CXX_STANDARD 17)
set (CMAKE_CXX_STANDARD_REQUIRED ON)

# 引入 LLVM 头文件与宏定义
include_directories (${LLVM_INCLUDE_DIRS})
add_definitions (${LLVM_DEFINITIONS})

# LLVM 官方提供的 Pass 插件构建指令,替代手动配置动态库编译规则
# 自动处理插件的符号导出、平台适配、LLVM 库依赖等问题
add_llvm_pass_plugin (MyCustomPass MyCustomPass.cpp)

执行标准构建流程,编译完成后会在 build 目录下生成 libMyCustomPass.so(Linux)/MyCustomPass.dylib(macOS)插件文件。

通过 Clang 加载插件,编译 C 代码时自动执行 Pass:

clang -fpass-plugin=./build/libMyCustomPass.so test.c -o test

执行后会在控制台打印出所有遍历到的函数名。

LICENSED UNDER CC BY-NC-SA 4.0
评论