LLVM : Insert Inc function 실습

손지웅·2025년 8월 8일

https://github.com/leejaymin/llvm8-tutorials-jemin/tree/master?tab=readme-ov-file

해당 주소에 있는 튜토리얼로 진행하였다.

해당 실습에서는 LLVM의 ModulePass를 활용하여, 새로운 함수와 명령어를 생성한다.
모듈패스란, LLVM의 분석 단위이다. 프로그램 전체 즉 여러 함수와 글로벌 변수를 한꺼번에 다루어 효율적으로 처리한다. 클래스에서 runOnModule이라는 메서드를 구현하여 실제 작업을 수행한다.

간단한 함수를 삽입하고, 해당 함수에 필요한 Basic block과 명령어를 추가하는 코드를 작성하여 보자.

  1. InsertInFuction class 구현: ModulePass를 상속하여 새로운 패스 생성
  2. inc 함수 생성
  3. Basic Block 직접 생성
  4. 명령어 삽입
  5. 최적화 도구로 적용

목표는 int inc(int n) { return n+1; } 함수를 LLM 모듈에 삽입하는 패스를 작성하는 것이다.

작성해야 하는 파일은 다음과 같다.

  • InsertInc.cpp

    • Inc 함수 삽입 패스의 실제 로직 구현 소스
    • ModulePass 상속 클래스 코드와 inc 함수 생성, 관련 명령어 삽입 포함
  • InsertInc.h

    • InsertInc.cpp의 클래스 및 함수 선언부
    • 재사용 및 코드 구조 관리를 위한 헤더 파일
  • Makefile

    • 컴파일, 빌드, 테스트 자동화 빌드 스크립트
    • 소스코드와 의존성, 빌드 순서 명시
  • test.c

    • 패스 적용을 확인할 테스트용 C 소스
    • 함수 삽입, 호출 정상 여부 검증 사례 코드 포함
  • test.ll

    • test.c를 LLVM IR로 변환한 파일
    • 패스 적용 결과를 IR 수준에서 점검하고 확인

InsertInc.cpp

//===- Hello.cpp - Example code from "Writing an LLVM Pass" ---------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// docs/WritingAnLLVMPass.html 문서의 Hello World 패스 예제 코드(2가지 버전 구현)
//
//===----------------------------------------------------------------------===//

#include "llvm/Support/raw_ostream.h"        // LLVM용 출력 지원
#include "llvm/IR/IRBuilder.h"               // IR 명령어 생성용 빌더
#include "llvm/IR/DerivedTypes.h"            // 함수 타입 등 파생 타입
#include "llvm/IR/Instructions.h"            // IR 명령어 관련
#include "InsertInc.h"                       // 본인 패스 선언부(헤더)
#include "llvm/IR/Value.h"                   // Value 타입 관련

#define DEBUG_TYPE "hello"                   // 디버그용(디버그 메시지 필터 등)

// InsertInc 패스 본체 (레거시 패스 방식)
bool InsertInc::runOnModule(Module &M) {
  
  LLVMContext &Context =M.getContext();      // LLVM 컨텍스트(타입, 상수 등 생성에 사용)

  // "inc"라는 함수(시그니처: int inc(int))를 모듈에 삽입 또는 이미 있으면 반환
  Constant *newF = M.getOrInsertFunction("inc", \
    Type::getInt32Ty(Context),               // 리턴 타입: int32
    Type::getInt32Ty(Context));              // 파라미터 타입: int32
   
  // inc 함수의 "entry" 기본 블록 생성, 함수에 연결
  BasicBlock *BB = BasicBlock::Create(Context, "entry", 
    dyn_cast<Function>(newF));

  // 함수의 첫 번째(유일한) 인자 포인터 획득 (int n)
  Argument *Arg = dyn_cast<Function>(newF)->arg_begin();

  // 1이라는 상수 생성(int32)
  Constant *One = ConstantInt::get(Type::getInt32Ty(Context), 1);

  // IR 명령어를 BB에 삽입할 빌더 초기화(BB 끝에 명령어 자동 추가)
  IRBuilder<> Builder(BB); 

  // n + 1 (Add 명령어) 삽입, 결과를 result 변수에 저장
  Value *result = Builder.CreateAdd(Arg, One);

  // result를 반환하는 Return 명령어 삽입
  Builder.CreateRet(result);

  // 모듈을 수정했으니 true 반환(새 함수와 명령어를 추가)
  return true;
}

// 다른 분석 패스 결과를 전혀 깨뜨리지 않겠다고 명시(패스 실행 전후로 변화 없음)
void InsertInc::getAnalysisUsage(AnalysisUsage &AU) const {
  AU.setPreservesAll();
}

// 레거시 패스 매니저용 ID(필수, 글로벌 변수)
char InsertInc::ID = 0;

// 패스 등록 (opt 등에서 "InsertInc"로 사용, "InsertInc Pass" 설명 표시)
static RegisterPass<InsertInc> X("InsertInc", "InsertInc Pass ");
  1. 함수 만들기
  2. 함수 정의 블록(Entry Point) 생성
  3. 기본 블록 조립
  4. 함수에 연결

InsertInc.h

Github에서 해당 프로젝트의 소스코드를 보면, Legacy Pass Manager 기반으로 작성되어 있는 것을 확인할 수 있다. 하지만 최신 LLVM에서는 New Pass Manager을 사용하므로 해당 API에 맞게 코드를 수정해야 한다.

레거시 패스 관리자와 새로운 패스 관리자 차이점

  • 패스 등록 방식

    • 레거시: RegisterPass 매크로 사용, 상속 방식
    • new: PassPluginLibraryInfo, PassBuilder 등 구조체 기반, 플러그인 방식
  • 패스 실행 구조

    • 레거시: 암묵적/묵시적 구조, 함수·모듈 패스 혼합 등록 가능
    • new: 명시적으로 어떤 패스가 어디에 등록될지 구분, 파이프라인 구조 명확
  • 결과 반환

    • 레거시: 함수에서 true/false 반환(모듈 변경 여부)
    • new: PreservedAnalyses 객체로 어떤 분석 결과가 보존되는지 반환
  • 코드 사용 및 관리

    • 레거시: 초기 등록 절차 복잡, 글로벌 변수 필요
    • new: 플러그인 중심, 깔끔한 확장 구조
#ifndef LLVM_TUTORIAL_OPTIMIZATION_INSERTINCFUN_INSERTINC_H
#define LLVM_TUTORIAL_OPTIMIZATION_INSERTINCFUN_INSERTINC_H

// 여러 번 포함되어도 문제 없도록 인클루드 가드 설정

#include "llvm/IR/PassManager.h"
#include "llvm/IR/Module.h"
#include "llvm/Passes/PassPlugin.h"

// 다른 파일과 이름 충돌 막기 위해 익명 네임스페이스 사용
namespace {

class InsertInc : public llvm::PassInfoMixin<InsertInc> {
public:
  // New Pass Manager에서 패스 로직이 들어가는 함수 선언
  // M: 전체 LLVM 모듈(프로그램 전체)
  // ModuleAnalysisManager &: 분석 도구 제공
  // 반환값: 어떤 분석 결과가 보존되는지 나타내는 PreservedAnalyses
  llvm::PreservedAnalyses run(llvm::Module &M,
                              llvm::ModuleAnalysisManager &);
};

} // end anonymous namespace

#endif // LLVM_TUTORIAL_OPTIMIZATION_INSERTINCFUN_INSERTINC_H

test.c

#include <stdio.h>

#define MATSIZE 1024

int add(int x, int y);

int main(){
  int x = 1;
  int y = 2;
  int i, j, k;
  int a[MATSIZE][MATSIZE];
  int b[MATSIZE][MATSIZE];
  int c[MATSIZE][MATSIZE];
  
  printf("Hello world!\n");
  printf("%d + %d = %d \n",x,y,add(x,y));

  for (i = 0; i < MATSIZE; i++) {
    for (j = 0; j < MATSIZE; j++) {
      a[i][j] = i;
      b[i][j] = j;
    }
  }
  
  // TODO: Optimize this loop
  for (i = 0; i < MATSIZE; i++) {
    for (k = 0; k < MATSIZE; k++) {
      for (j = 0; j < MATSIZE; j++) {
        c[i][j] += a[i][k] * b[k][j];
      }
    }
  }

  printf("%d",c[i][j]);
 
  return 0;
}

int add(int x, int y){
  return x+y;
}

실행

빌드 후,
test.ll 파일을 읽어 패스를 적용 한 뒤, test.out.ll 에 저장.

위에서 정의했던 inc 함수 정의가 삽입되어 있다.

  • 빌드

    • InsertInc.cpp 등 패스 소스 코드 컴파일
    • LLVM 패스(InsertInc.so) 생성
  • test.ll 파일 준비

    • 테스트 코드(test.c)를 LLVM IR 파일(test.ll)로 변환
  • 패스 적용

    • opt 명령어로 test.ll에 InsertInc 패스 적용
    • inc 함수 등 새로운 IR 코드가 삽입됨
  • 결과 저장(test.out.ll)

    • 패스가 적용된 IR 코드(수정본)를 test.out.ll 파일에 저장
    • 기존 코드 + inc 함수가 포함된 IR

0개의 댓글