LLVM Pass 제작법 및 script 사용법 정리

안상준·2025년 8월 6일

LLVM

목록 보기
8/12
post-thumbnail

LLVM에 Pass 제작 및 빌드 방법에 대하여 정리

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

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

이제 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 내부에서 확인이 가능하다. 이 파일을 이용하여 패스를 실행하게 된다.

Execute

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에 대한 코드는 여기서 확인 가능하다.

0개의 댓글