LLVM에 Pass 제작 및 빌드 방법에 대하여 정리
Pass코드는 /llvm-project/llvm/lib/Transforms에 위치해 있다.
cd ~/llvm-project/llvm/lib/Transforms
해당 위치에 Pass를 담을 폴더를 하나 만들어 사용하였다. Pass 하나당 폴더 하나씩 필요하여 이렇게 하는 방법이 깔끔했다.

각 패스 디렉토리 안에 pass의 클래스와 등록 코드인 cpp와 CMakeLists.txt로 구성하였다. CMakeLists.txt 작성 방법은 아래서 아래서 다루도록 하겠다. 먼저 패스 작성 하는 방법이다.
namespace {
#include "llvm/IR/PassManager.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
using namespace llvm;
namespace {
class CallInstCountPass : public PassInfoMixin<CallInstCountPass> {
public:
PreservedAnalyses run(Function &F, FunctionAnalysisManager &) {
// 기본 루프
for (auto &BB : F) {
for (auto &I : BB) {
// Instruction 단위 처리
}
}
return PreservedAnalyses::all(); // 아무 것도 안 바꿨을 경우(분석 Pass)
}
};
} // namespace
// Pass 등록 (New PM용 Plugin)
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() {
return {
LLVM_PLUGIN_API_VERSION,
"CallInstCountPass", // Pass 이름 (버전과 함께 출력됨)
LLVM_VERSION_STRING,
[](PassBuilder &PB) {
PB.registerPipelineParsingCallback(
[](StringRef Name, FunctionPassManager &FPM,
ArrayRef<PassBuilder::PipelineElement>) {
if (Name == "CallInstCount") { // opt -passes=CallInstCount 로 호출
FPM.addPass(MyPass());
return true;
}
return false;
});
}
};
}
예시로 MyPass라는 이름의 Pass를 작성해 보았다. if (Name == "mypass") 이 부분은 나중에 Pass 호출 시 사용될 이름이다.
CMakeLists.txt를 작성할 부분은 세 군데다.
cd ~/llvm-project/llvm/lib/Transforms
cd ~/llvm-project/llvm/lib/Transforms/MyPass
cd ~/llvm-project/llvm/lib/Transforms/MyPass/[각 Pass]
add_subdirectory(MyPass)
Transforms 내부에 위의 코드를 추가해 주면 된다.
나머지 파일들은 직접 생성해야 한다.
add_subdirectory(FuncName)
add_subdirectory(CountInst)
add_subdirectory(DetectSwitch)
add_subdirectory(CallInstCount)
add_subdirectory(LoopAnalysis)
그 다음 패스가 담긴 디렉토리를 등록해야 하는데 패스들이 담긴 디렉토리 안에 CMakeLists.txt를 만들어 위와 같이 작성해 준다.
add_llvm_pass_plugin(CallInstCountPass
CallInstCountPass.cpp
)
먼저 MyPass 폴더 안에 있는 CMakeList.txt 파일은 이렇게 작성해 주었다.
이렇게 하면 LLVM Pass가 CMake에 등록이 된다.
이제 Build를 해야 한다. 먼저 CMakeLists.txt를 수정하거나 생성 했다면
cmake -G Ninja ../llvm \
-DLLVM_ENABLE_PROJECTS=clang \
-DCMAKE_BUILD_TYPE=Release \
-DLLVM_TARGETS_TO_BUILD=host
위를 실행해 줘야 한다. cpp만 수정한 경우는 불필요
그 다음 ninja를 실행하여 빌드하면 되는데 전체 빌드할 경우 상당히 오래 걸린다. 그래서 제작한 Pass만 빌드하는 방법이 있다.
ninja CallInstCountPass
이렇게 빌드하면 되고 성공적으로 됐다면

이렇게 뜬다..so파일이 생성 되는데 이는 /llvm-project/build/lib 내부에서 확인이 가능하다. 이 파일을 이용하여 패스를 실행하게 된다.
opt -load-pass-plugin=[패스 경로] \
-passes=[패스 명] \
-S [입력 파일] -o [출력 파일]
명령어가 많이 길어 보이지 않지만 패스 경로를 적고 또한, .ll 파일을 생성해 줘야 하는 사전 작업이 필요하여 불편함이 있다. 이를 해결하기 위해 script를 작성하여 편리하게 pass를 이용할 수 있다.
#!/bin/bash
LLVM_BUILD="/home/sangjun19/llvm-project/build"
OPT="$LLVM_BUILD/bin/opt"
# 1. 첫 번째 인자: Pass 종류
case "$1" in
-call)
PASS_NAME="callinstcount"
PASS_SO="$LLVM_BUILD/lib/CallInstCountPass.so"
;;
-func)
PASS_NAME="funcname"
PASS_SO="$LLVM_BUILD/lib/FuncNamePass.so"
;;
-inst)
PASS_NAME="countinst"
PASS_SO="$LLVM_BUILD/lib/CountInstPass.so"
;;
-switch)
PASS_NAME="switchdetect"
PASS_SO="$LLVM_BUILD/lib/DetectSwitchPass.so"
;;
-loop)
PASS_NAME="loopanalysis"
PASS_SO="$LLVM_BUILD/lib/LoopAnalysisPass.so"
;;
*)
echo "Unknown option: $1"
echo "Usage: llvm_pass -call|-func|-inst|-switch|-loop [clang-opt] <file.c|file.ll>"
exit 1
;;
esac
# 2. 두 번째 인자: Clang 최적화 옵션 (예: -O2 또는 -O3)
CLANG_OPT="$2"
# 3. 세 번째 인자: 입력 파일
INPUT="$3"
# 예외 처리
if [ -z "$INPUT" ]; then
if [[ "$CLANG_OPT" == *.c || "$CLANG_OPT" == *.ll ]]; then
INPUT="$CLANG_OPT"
CLANG_OPT="-O0 -Xclang -disable-O0-optnone"
else
echo "No input file provided"
echo "Usage: llvm_pass -call|-func|-inst|-switch|-loop [clang-opt] <file.c|file.ll>"
exit 1
fi
fi
# 입력 확장자 검사
EXT="${INPUT##*.}"
# c 파일이면 ll로 변환
if [ "$EXT" == "c" ]; then
echo "Converting $INPUT to LLVM IR (.ll) with '$CLANG_OPT'..."
BASE="${INPUT%.c}"
clang -S -emit-llvm $CLANG_OPT "$INPUT" -o "${BASE}.ll"
INPUT="${BASE}.ll"
fi
# Pass 실행
echo "Running $PASS_NAME on $INPUT..."
$OPT -load-pass-plugin="$PASS_SO" \
-passes="$PASS_NAME" \
-S "$INPUT" -o "${INPUT%.ll}_${PASS_NAME}.ll"
조금 길긴 하지만 응용은 어렵지 않다. 해당 파일은 home 디렉토리에 있는 bin 내부에 작성하면 된다. 파일 명은 pass 호출시 편리하게 사용하고 싶은 이름을 사용하면 되고 확장자는 .bash 다.
script 사용 법은
llvm_pass -call|-func|-inst|-switch|-loop [clang-opt] <file.c|file.ll>
이렇게 된다. 명령어 | pass 명 | 최적화 옵션 | 대상 파일
첫 번째 인자는 pass 종류를 지정하는 부분으로 pass를 추가 했다면 이 곳에 추가해 주면 된다.
두 번째 인자는 최적화 옵션을 지정하는 부분인데 만약 지정하지 않는 다면 자동으로 -O0 -Xclang -disable-O0-optnone해당 옵션을 적용하도록 되어 있다.
세 번째 인자는 입력 파일로 .c 파일을 입력할 경우 내부에서 컴파일을 통해 .ll 파일을 생성하여 pass를 실행한다.
간단한 실행 예시이다.

각 함수에 존재하는 명령어의 개수를 출력하는 pass를 제작하였고, 위와 같은 결과가 나왔다.
https://github.com/sangjun19/LLVM-Pass
pass에 대한 코드는 여기서 확인 가능하다.