#include <stdio.h>
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
int main() {
int result = factorial(5);
printf("5! = %d\n", result);
return 0;
}
1단계: Clang으로 LLVM IR 생성
clang -O3 -emit-llvm hello.c -c -o hello.bc
- -O3: 최대 최적화 수준 (인라이닝, 루프 언롤링 등)
- -emit-llvm: C 코드를 기계어 대신 LLVM IR로 변환
- -c: 컴파일만 하고 링크는 안 함
- -o hello.bc: 출력 파일 (비트코드 형식)
생성된 파일:
- hello.bc: LLVM 비트코드 (바이너리 형식)
비트코드를 사람이 읽을 수 있는 형태로 보려면
llvm-dis hello.bc -o hello.ll
hello.ll 내용
; ModuleID = 'hello.bc'
source_filename = "hello.c"
define dso_local i32 @factorial(i32 %n) {
entry:
%cmp = icmp slt i32 %n, 2
br i1 %cmp, label %return, label %if.end
if.end:
%sub = add nsw i32 %n, -1
%call = tail call i32 @factorial(i32 %sub)
%mul = mul nsw i32 %call, %n
ret i32 %mul
return:
ret i32 1
}
define dso_local i32 @main() {
entry:
%call = tail call i32 @factorial(i32 5)
%call1 = tail call i32 (i8*, ...) @printf(
i8* getelementptr inbounds ([9 x i8], [9 x i8]* @.str, i64 0, i64 0),
i32 %call)
ret i32 0
}
LLVM IR 특징
- SSA (Static Single Assignment) 형식
- 타입 명시적: i32 (32비트 정수), i8* (char 포인터)
- 플랫폼 독립적: x86, ARM, MIPS 어디서나 실행 가능
2단계: opt로 LLVM IR 최적화
opt -O3 hello.bc -o hello-opt.bc
opt의 역할
- 이미 -O3로 컴파일했지만, 추가 최적화 패스 적용
- 플랫폼 독립적인 최적화 수행
적용되는 최적화 예시
- 재귀 → 루프 변환 (tail recursion elimination)
- 상수 폴딩: factorial(5) → 컴파일 타임에 120 계산
- 데드 코드 제거
- 함수 인라이닝
최적화 전후 비교
# 최적화 과정을 보고 싶다면
opt -O3 -print-before-all -print-after-all hello.bc -o hello-opt.bc 2>&1 | less
최적화된 IR을 보면
llvm-dis hello-opt.bc -o hello-opt.ll
hello-opt.ll (최적화 후)
define dso_local i32 @main() {
entry:
; factorial(5)가 컴파일 타임에 계산됨!
%call = tail call i32 (i8*, ...) @printf(
i8* getelementptr inbounds ([9 x i8], [9 x i8]* @.str, i64 0, i64 0),
i32 120) ; <-- 120으로 상수화됨
ret i32 0
}
; factorial 함수가 인라인되고 완전히 제거될 수 있음
3단계: llc로 네이티브 어셈블리 생성
llc hello-opt.bc -o hello.s
llc의 역할:
- LLVM IR → 타겟 아키텍처의 어셈블리 코드 생성
- 레지스터 할당
- 명령어 스케줄링
- 플랫폼별 최적화
생성된 hello.s (x86-64)
.text
.globl main
.type main,@function
main:
pushq %rbp
movq %rsp, %rbp
# printf 호출 준비
movl $.L.str, %edi # 첫 번째 인자: 포맷 스트링
movl $120, %esi # 두 번째 인자: 120 (factorial 결과)
xorl %eax, %eax
callq printf
xorl %eax, %eax # return 0
popq %rbp
retq
.L.str:
.asciz "5! = %d\n"
다른 아키텍처로 생성하기
# MIPS 어셈블리 생성
llc -march=mips -mcpu=mips32r2 hello-opt.bc -o hello-mips.s
# ARM 어셈블리 생성
llc -march=arm hello-opt.bc -o hello-arm.s
# 어셈블리 대신 오브젝트 파일 직접 생성
llc -filetype=obj hello-opt.bc -o hello.o
최종 링크 및 실행
# 어셈블리를 오브젝트 파일로 어셈블
as hello.s -o hello.o
# 링크하여 실행 파일 생성
ld hello.o -lc -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o hello
# 또는 clang으로 한 번에
clang hello.s -o hello
# 실행
./hello
# 출력: 5! = 120
전체 과정 비교
일반적인 컴파일 (한 번에)
clang -O3 hello.c -o hello
내부적으로는 같은 과정을 거치지만, 중간 단계가 숨겨짐
LLVM 툴체인 (단계별)
# C → LLVM IR
clang -O3 -emit-llvm hello.c -c -o hello.bc
# LLVM IR 최적화
opt -O3 hello.bc -o hello-opt.bc
# LLVM IR → 어셈블리
llc hello-opt.bc -o hello.s
# 어셈블리 → 실행 파일
clang hello.s -o hello
Writing an LLVM Analysis (part 2)
https://www.youtube.com/watch?v=v82DmHqmXMA&list=PLDSTpI7ZVmVnvqtebWnnI8YeB8bJoGOyv&index=8
References
- https://llvm.org/docs/GettingStartedTutorials.html
- https://llvm.org/docs/WritingAnLLVMPass.html
- https://jonathan2251.github.io/lbd/
- https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/index.html
- https://lowlevelbits.org/how-to-learn-compilers-llvm-edition/
- https://craftinginterpreters.com/contents.html
- https://interpreterbook.com/
- https://www.cs.princeton.edu/~appel/modern/java/
- https://www.packtpub.com/en-us/product/llvm-techniques-tips-and-best-practices-clang-and-middle-end-libraries-9781838829728
- https://github.com/joydo/CompilerLearning
- https://pangyoalto.com/clang-and-optimization/
- https://widatama.github.io/hn/item?id=40842173
- https://mukulrathi.com/create-your-own-programming-language/llvm-ir-cpp-api-tutorial/
- https://www.cs.cmu.edu/afs/cs/academic/class/15745-s12/public/lectures/L3-LLVM-Part1.pdf
- dfsdf
'System Programming > Compiler' 카테고리의 다른 글
| LLVM Compiler Under the hood #1 컴파일 흐름 이해 (0) | 2026.01.14 |
|---|---|
| Compiler 개발 #1 컴파일러 동작 원리 (0) | 2025.12.19 |
| LLVM #1 IR 이해 하기 (0) | 2025.11.11 |