llvm base development environment configuration

之前一直以为编译llvm的pass需要编译一整个llvm,然后llvm编译的内存要求要又很高(50G交换空间都不够!然后发现其实完全不需要,安装库就可以,编译参数也可以通过llvm-config来获取。 我这里使用arch,做一下简单的记录

安装

Ubuntu/Debain

如果是ubuntu/debain就很方便,llvm官方有安装脚本

可以通过这个脚本安装指定的包,指定的版本。

安装最新版本

bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)"

安装指定版本

wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh <version number>

安装所有包

wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh <version number> all
# or
sudo ./llvm.sh all

Arch

Arch就没有这么方便了,不过pacman/yay也可以指定安装llvm的包以及版本,这里以安装llvm18为例

yay -Syu llvm18 llvm18-libs

llvm18-libs 是 LLVM 18 runtime库

Other OS

如果想使用命令安装包,可以参考

# LLVM
apt-get install libllvm-19-ocaml-dev libllvm19 llvm-19 llvm-19-dev llvm-19-doc llvm-19-examples llvm-19-runtime
# Clang and co
apt-get install clang-19 clang-tools-19 clang-19-doc libclang-common-19-dev libclang-19-dev libclang1-19 clang-format-19 python3-clang-19 clangd-19 clang-tidy-19
# compiler-rt
apt-get install libclang-rt-19-dev
# polly
apt-get install libpolly-19-dev
# libfuzzer
apt-get install libfuzzer-19-dev
# lldb
apt-get install lldb-19
# lld (linker)
apt-get install lld-19
# libc++
apt-get install libc++-19-dev libc++abi-19-dev
# OpenMP
apt-get install libomp-19-dev
# libclc
apt-get install libclc-19-dev
# libunwind
apt-get install libunwind-19-dev
# mlir
apt-get install libmlir-19-dev mlir-19-tools
# bolt
apt-get install libbolt-19-dev bolt-19
# flang
apt-get install flang-19
# wasm support
apt-get install libclang-rt-19-dev-wasm32 libclang-rt-19-dev-wasm64 libc++-19-dev-wasm32 libc++abi-19-dev-wasm32 libclang-rt-19-dev-wasm32 libclang-rt-19-dev-wasm64
# LLVM libc
apt-get install libllvmlibc-19-dev

手动

在github/llvm-project的releases页面中,可以下载指定系统的llvm包,比如我要下载llvm19在Linux上x64的库,就选择LLVM-19.1.7-Linux-X64.tar.xz

解压到指定位置后,给bin目录添加权限,并添加进入PATH中,也可以做到相似效果。

Windows

Windows其实就是使用手动的方式,在releases页面中下载clang+llvm-19.1.7-x86_64-pc-windows-msvc.tar.xz

解压到指定位置后,添加bin目录到环境变量中即可

注意:不要使用LLVM-19.1.7-win64.exe这些图形化来安装,这个不会安装库,只安装了bin目录那些软件

编译Pass

pass的例子选择llvm/examples/Bye/Bye.cpp,这个例子有NewPassManager和LegacyPassManager版本,都在一个文件中

#include "llvm/IR/Function.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Pass.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

static cl::opt<bool> Wave("wave-goodbye", cl::init(false),
                          cl::desc("wave good bye"));

namespace {

bool runBye(Function &F) {
  if (Wave) {
    errs() << "Bye: ";
    errs().write_escaped(F.getName()) << '\n';
  }
  return false;
}

struct LegacyBye : public FunctionPass {
  static char ID;
  LegacyBye() : FunctionPass(ID) {}
  bool runOnFunction(Function &F) override { return runBye(F); }
};

struct Bye : PassInfoMixin<Bye> {
  PreservedAnalyses run(Function &F, FunctionAnalysisManager &) {
    if (!runBye(F))
      return PreservedAnalyses::all();
    return PreservedAnalyses::none();
  }
};

} // namespace

char LegacyBye::ID = 0;

static RegisterPass<LegacyBye> X("goodbye", "Good Bye World Pass",
                                 false /* Only looks at CFG */,
                                 false /* Analysis Pass */);

/* New PM Registration */
llvm::PassPluginLibraryInfo getByePluginInfo() {
  return {LLVM_PLUGIN_API_VERSION, "Bye", LLVM_VERSION_STRING,
          [](PassBuilder &PB) {
            PB.registerVectorizerStartEPCallback(
                [](llvm::FunctionPassManager &PM, OptimizationLevel Level) {
                  PM.addPass(Bye());
                });
            PB.registerPipelineParsingCallback(
                [](StringRef Name, llvm::FunctionPassManager &PM,
                   ArrayRef<llvm::PassBuilder::PipelineElement>) {
                  if (Name == "goodbye") {
                    PM.addPass(Bye());
                    return true;
                  }
                  return false;
                });
          }};
}

#ifndef LLVM_BYE_LINK_INTO_TOOLS
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo
llvmGetPassPluginInfo() {
  return getByePluginInfo();
}
#endif

命令行编译

在bash中,zsh和fish不太行,主要是没有``语法

clang++ `llvm-config-18 --cxxflags` -fPIC -shared pass.cpp -o pass.so `llvm-c
onfig-18 --ldflags`
  • llvm-config-18 --cxxflags 输出的实际上是c++编译器标志,主要是llvm头文件的路径。
  • llvm-config-18 --ldflags 输入的实际上是连接库位置 下面是我环境的输入
$ llvm-config-18 --cxxflags
-I/usr/lib/llvm18/include -std=c++17   -fno-exceptions -funwind-tables -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS

$ llvm-config-18 --ldflags
-L/usr/lib/llvm18/lib

所以上面的命令可以拼接起来,就是可以用于脚本

clang++ -I/usr/lib/llvm18/include -std=c++17   -fno-exceptions -funwind-tables -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -fPIC -shared pass.cpp -o pass.so -L/usr/lib/llvm18/lib

CMake编译

其实就是上面的编译命令改成脚本

cmake_minimum_required(VERSION 3.10)
project(MyPass)

# Set the C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# Set compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -funwind-tables -fPIC")

# Add preprocessor definitions
add_definitions(
    -D_GNU_SOURCE
    -D__STDC_CONSTANT_MACROS
    -D__STDC_FORMAT_MACROS
    -D__STDC_LIMIT_MACROS
)

# Include directories
include_directories(/usr/lib/llvm18/include)

# Link directories
link_directories(/usr/lib/llvm18/lib)

# Add the shared library target
add_library(pass SHARED pass.cpp)

# Set the output name for the shared library
set_target_properties(pass PROPERTIES OUTPUT_NAME "pass.o")

运行

有两种运行方式

  • 一种是适合调试时使用的,使用opt加载pass,修改IR
  • 一种是使用clang加载插件,直接编译成可执行文件

编译、修改并运行 IR

clang -emit-llvm -S main.c -o main.ll
opt -S -load-pass-plugin=./pass.so -passes="dynamic-cc" main.ll -o new_main.ll -print-pipeline-passes
cat main.ll
lli ./new_main.ll

clang 方式

clang -Xclang -fpass-plugin=./pass.so main.c -o main.exe
./main.exe

Vscode cpp && clangd 配置

安装了库之后,直接引入头文件,虽然编译可以,但是没有语法提示。这是因为编译我指定了llvm头文件路径,但是语法提示没有指定。

vscode中c++语法提示一般就两个,一个微软官方出的C++,需要在.vscode中设置。一个是clangd,实际上是封装使用LSP协议的clangd,所以使用clangd的通用配置就可以。

可以通过llvm-config-18 –includedir获取头文件目录

$ llvm-config-18 --includedir
/sur/lib/llvm18/include

clangd

可以直接在项目更目录下添加.clangd文件

CompileFlags:
  Add:
    - "-I/usr/lib/llvm18/include" # LLVM头文件路径
    - "-std=c++17"                # 使用C++17标准
    - "-DLLVM_ENABLE_ASSERTIONS"  # 如果需要启用断言

或者在vscode的setting中添加参数,这个就只适用vscode,毕竟每个IDE封装LSP肯定有差异

{
    "clangd.fallbackFlags": [
        "-std=c++17",
        "-I/usr/lib/llvm18/include"
    ]
}

微软C++插件

在项目根目录下的.vscode/c_cpp_properties.json中添加库路径

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/usr/lib/llvm18/include/llvm/**",
                "/usr/lib/llvm18/include/llvm-c/**"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/clang",
            "cStandard": "c17",
            "intelliSenseMode": "linux-clang-x64"
        }
    ],
    "version": 4
}

参考文档

LLVM Debian/Ubuntu nightly packages

LLVM 从零开始实现LLVM PASS


Last modified on 2025-01-25