LLVM

1. 서론 - 컴파일러의 재구성

본 섹션에서는 LLVM을 단순한 정의를 넘어 컴파일러 설계의 철학적, 구조적 전환점으로 조명하며, 그 근본적인 맥락을 구축합니다. LLVM의 어원적 뿌리를 추적하고, 핵심 원칙을 정의하며, 방대한 생태계의 전체적인 지도를 제공할 것입니다.

1.1 “저수준 가상 머신“에서 핵심 기반 기술로: LLVM 프로젝트의 기원과 진화

LLVM 프로젝트는 2000년 미국 일리노이 대학교 어배너-섐페인에서 비크람 애드브(Vikram Adve)와 크리스 래트너(Chris Lattner)의 주도하에 시작되었습니다.1 초기 목표는 특정 프로그래밍 언어에 구애받지 않고 정적 및 동적 컴파일을 모두 지원할 수 있는 현대적인 SSA(Static Single Assignment) 기반 컴파일 전략을 제공하는 것이었습니다.3

초기에 LLVM이라는 이름은 ’저수준 가상 머신(Low Level Virtual Machine)’의 약자였습니다.1 이는 당시 성공적인 패러다임이었던 자바 가상 머신(JVM)과 같은 플랫폼 독립적인 기술에서 영감을 얻었을 가능성이 높습니다. 그러나 LLVM 프로젝트의 핵심은 JVM이나.NET CLR처럼 가비지 컬렉션이나 런타임 자원 관리 같은 전통적인 런타임 환경을 구축하는 것이 아니었습니다.2 LLVM의 진정한 혁신은 런타임이 아닌 컴파일 타임의 모듈성에 있었습니다.

프로젝트의 범위가 확장됨에 따라, ’가상 머신’이라는 초기 이름은 오해를 불러일으킬 소지가 커졌습니다.5 이로 인해 커뮤니티는 “LLVM“이 더 이상 약어가 아니며, 프로젝트의 전체 이름(full name)이라는 의도적인 결정을 내렸습니다.3 이는 LLVM이 런타임 가상 머신이 아니라, 컴파일 타임과 링크 타임에 동작하는 기술의 집합체임을 명확히 하기 위한 중대한 전략적 전환이었습니다. 이러한 재정의는 LLVM이 기존의 JVM이나.NET과의 직접적인 경쟁 구도에서 벗어나, ’모듈형 컴파일러 툴체인’이라는 새로운 기술 범주를 정의하며 독자적인 정체성을 확립하는 데 결정적인 역할을 했습니다.

LLVM 역사의 또 다른 전환점은 2005년 애플(Apple)이 크리스 래트너를 영입하고 프로젝트에 대한 깊이 있는 투자를 시작한 것입니다.2 이 막대한 지원을 통해 LLVM의 C/C++/Objective-C 프론트엔드인 Clang이 개발되고 전체 생태계가 성숙할 수 있었으며, 이는 LLVM이 애플의 플랫폼(macOS, iOS)을 넘어 GCC의 강력한 대안으로 자리매김하는 계기가 되었습니다.

1.2 LLVM의 철학: 모듈성, 재사용성, 그리고 관심사의 분리

LLVM의 근본적인 철학은 모듈화되고 재사용 가능한 컴파일러 및 툴체인 기술의 집합체를 제공하는 것입니다.3 전체 아키텍처는 거대한 단일체(monolithic) 컴파일러를 독립적이고 교체 가능한 구성 요소로 분해하는 원칙에 기반합니다.

이 철학은 소스 코드를 파싱하는 ‘프론트엔드’, 코드를 최적화하는 ‘미들엔드(옵티마이저)’, 그리고 기계어를 생성하는 ’백엔드’라는 고전적인 3단계 설계에 완벽하게 구현되어 있습니다.2 이러한 관심사의 분리는 LLVM의 강력함과 유연성의 핵심입니다.

LLVM은 견고하고 공유 가능한 옵티마이저와 백엔드를 제공함으로써 새로운 프로그래밍 언어를 만드는 데 필요한 진입 장벽을 극적으로 낮춥니다. 언어 설계자는 LLVM의 중간 표현(Intermediate Representation, IR)을 생성하는 프론트엔드 개발에만 집중하면, 여러 하드웨어 아키텍처를 대상으로 하는 세계적 수준의 최적화 컴파일러를 사실상 ‘무료로’ 얻게 됩니다.3 이는 컴파일러 개발의 민주화에 기여했습니다.

1.3 LLVM 생태계: 툴체인 기술의 “우산” 프로젝트

LLVM은 단일 프로그램이 아니라, 완전한 툴체인을 형성하는 수많은 하위 프로젝트를 포괄하는 “우산 프로젝트(umbrella project)“입니다.3 아래 표는 LLVM 생태계를 구성하는 주요 프로젝트와 그 역할을 요약한 것입니다.

표 1: 주요 LLVM 하위 프로젝트와 기능

프로젝트 명주요 기능생태계 내 역할
LLVM Core중간 표현(IR), 옵티마이저, 코드 생성기 라이브러리생태계의 심장부. 언어와 아키텍처에 독립적인 최적화 및 코드 생성의 핵심 기능을 제공.3
ClangC, C++, Objective-C를 위한 프론트엔드빠른 컴파일 속도와 우수한 진단 메시지를 제공하는 LLVM 네이티브 컴파일러 프론트엔드.3
LLDB고성능 네이티브 디버거LLVM과 Clang 라이브러리를 기반으로 구축되어 빠르고 효율적인 디버깅 경험을 제공.3
lld고속 링커기존 시스템 링커를 대체할 수 있는 매우 빠른 드롭인(drop-in) 링커.3
libc++C++ 표준 라이브러리표준을 준수하는 고성능 C++ 표준 라이브러리 구현체.3
compiler-rt런타임 및 로우레벨 지원 루틴AddressSanitizer(ASan)와 같은 동적 테스팅 도구를 위한 런타임 라이브러리와 코드 생성 지원 루틴을 제공.3
MLIR다단계 중간 표현 인프라여러 IR과 도메인 특화 하드웨어를 지원하기 위해 설계된 차세대 컴파일러 인프라.4
Polly고수준 루프 및 데이터 지역성 옵티마이저루프 변환을 통해 코드의 캐시 성능과 병렬성을 최적화하는 폴리히드럴 모델 기반 옵티마이저.
BOLT링크 후 바이너리 옵티마이저프로파일링 정보를 기반으로 애플리케이션의 코드 레이아웃을 최적화하여 성능을 향상시키는 도구.4

2. 3단계 아키텍처 패러다임

본 섹션에서는 LLVM의 핵심 아키텍처 설계를 심층적으로 분석합니다. 이는 전통적인 단일체 컴파일러(예: 초기 GCC)와 구별되는 가장 중요한 특징입니다. 3단계 모델의 각 구성 요소를 분석하고 이 구조가 왜 혁신적인지 명확히 설명합니다.

2.1 프론트엔드: 소스 언어의 추상화

프론트엔드의 유일한 책임은 C++, Swift, Rust와 같은 특정 소스 언어를 파싱하여 공통된 LLVM 중간 표현(IR)으로 변환하는 것입니다.10 이 과정은 여러 단계로 이루어집니다. 먼저 소스 코드를 더 작은 단위인 토큰으로 분해하는 어휘 분석(lexical analysis)을 수행합니다. 그 후, 이 토큰들을 기반으로 코드의 구조를 나타내는 추상 구문 트리(Abstract Syntax Tree, AST)를 구축하는 구문 분석(parsing)을 진행합니다. 마지막으로, 타입 검사와 같은 의미 분석(semantic analysis)을 거쳐 최종적으로 LLVM IR을 생성합니다.8

Clang은 LLVM 프론트엔드의 대표적인 예시로, C, C++, Objective-C 언어를 담당합니다.3 Clang은 GCC 프론트엔드 사용 시 발생할 수 있는 라이선스 문제 등을 피하고 LLVM 프로젝트에 최적화된 프론트엔드를 제공하기 위해 특별히 개발되었습니다.1 LLVM의 모듈식 설계 덕분에 Ada, D, Fortran, Haskell, Julia, Kotlin, Rust, Swift 등 매우 다양한 언어를 위한 프론트엔드가 존재하며, 이들은 모두 동일한 백엔드 인프라를 공유할 수 있습니다.1

2.2 옵티마이저(미들엔드): 코드 개선을 위한 공유 엔진

옵티마이저의 역할은 명확합니다. LLVM IR을 입력으로 받아, 더 효율적으로 실행될 수 있는 최적화된 LLVM IR을 출력하는 것입니다.10 이 단계의 가장 중요한 특징은 원본 소스 언어와 최종 목표 하드웨어 아키텍처 모두로부터 완전히 독립적이라는 점입니다.10 루프 언롤링(loop unrolling), 함수 인라이닝(function inlining), 불필요한 코드 제거(dead code elimination)와 같은 최적화 기법들은 모두 이 보편적인 IR 위에서 수행됩니다.

옵티마이저는 ’패스(pass)’라고 불리는 개별적인 변환 알고리즘들의 파이프라인으로 구성됩니다.12 각 패스는 IR에 대해 특정 최적화를 수행합니다. 컴파일 시 -O2-O3와 같은 최적화 레벨 플래그를 통해 어떤 패스들을 어떤 순서로 실행할지 구성할 수 있습니다.12

2.3 백엔드: 다양한 하드웨어 아키텍처 대응

백엔드, 또는 ’코드 생성기(code generator)’는 최적화된 LLVM IR을 입력받아 특정 목표 명령어 집합 아키텍처(Instruction Set Architecture, ISA)를 위한 기계어로 번역하는 역할을 담당합니다.1 이 과정은 다음과 같은 여러 단계를 포함합니다.

  • 명령어 선택(Instruction Selection): 추상적인 IR 명령어를 목표 아키텍처의 구체적인 기계어 명령어로 매핑합니다.

  • 레지스터 할당(Register Allocation): IR의 가상 레지스터를 목표 CPU의 한정된 물리 레지스터에 할당합니다. 이 과정에서 메모리 접근을 최소화하고 레지스터 간 데이터 전송을 최적화하는 전략이 사용됩니다.17

  • 명령어 스케줄링(Instruction Scheduling): 목표 CPU의 파이프라인과 실행 유닛의 특성을 고려하여 명령어의 순서를 재배치함으로써 실행 성능을 극대화합니다.

LLVM은 x86, x86-64, ARM, AArch64, PowerPC, MIPS 등 광범위한 CPU 아키텍처를 지원하며, 최근에는 웹 환경을 위한 WebAssembly(WASM)까지 지원 대상에 포함되었습니다.3

2.4 아키텍처의 이점: N x M 문제 해결과 혁신 촉진

전통적인 단일체 컴파일러는 프론트엔드, 옵티마이저, 백엔드가 긴밀하게 결합되어 있습니다. 이러한 구조에서 N개의 프로그래밍 언어를 M개의 하드웨어 아키텍처에서 지원하려면, 이론적으로 N x M개의 완전히 분리된 컴파일러를 개발하고 유지보수해야 합니다.2 이는 엄청난 중복 작업을 유발하고 새로운 언어나 아키텍처 지원을 어렵게 만들어 혁신을 저해합니다.

LLVM의 모듈식 설계는 이 문제를 우아하게 해결합니다. 새로운 프로그래밍 언어를 지원하기 위해서는 새로운 프론트엔드 하나만 작성하면 되고(N), 새로운 하드웨어 아키텍처를 지원하기 위해서는 새로운 백엔드 하나만 작성하면 됩니다(M). 따라서 총 노력은 N + M에 비례하게 되어, 기존 방식에 비해 개발 및 유지보수 비용이 획기적으로 감소합니다.2 예를 들어, C++ 코드를 x86-64와 ARM 아키텍처용으로 빌드해야 할 경우, 전통적인 방식에서는 두 개의 완전한 툴체인이 필요할 수 있습니다. 하지만 LLVM을 사용하면, 동일한 Clang 프론트엔드가 생성한 IR을 각각 다른 두 개의 백엔드(x86-64, ARM)에 전달하기만 하면 되므로, 전체 프론트엔드와 최적화 파이프라인을 재사용할 수 있습니다.2

이러한 구조는 인터넷 아키텍처의 “좁은 허리(narrow waist)” 모델과 유사합니다. 인터넷에서 수많은 애플리케이션 프로토콜(HTTP, SMTP 등)과 물리 계층 기술(Ethernet, Wi-Fi 등)이 IP라는 단일 프로토콜을 통해 연결되는 것처럼, LLVM에서는 LLVM IR이 바로 그 ‘좁은 허리’ 역할을 합니다.2 모든 프론트엔드와 백엔드는 LLVM IR이라는 공통 언어를 통해 소통하기 때문에, 서로 완벽하게 분리되어 독립적으로 개발되고 발전할 수 있습니다. 이 아키텍처 패턴은 LLVM의 막대한 재사용성을 가능하게 하고, 컴파일러 스택의 각 계층에서 독립적인 혁신을 촉진하는 힘의 원천입니다.

3. LLVM IR - 보편적 중간 표현

본 섹션에서는 LLVM 프로젝트의 심장부인 LLVM 중간 표현(IR)을 심층적으로 분석합니다. IR의 설계 철학, SSA와 같은 핵심 구조적 속성, 구체적인 문법을 탐구하고, 소스 코드가 어떻게 IR로 변환되는지 실제 예시를 통해 살펴봅니다.

3.1 LLVM IR의 설계 원칙: 엄격하게 정의된, 언어 독립적 어셈블리

LLVM IR은 LLVM 생태계의 중심이 되는 데이터 구조이자 인터페이스입니다.15 이는 저수준의 RISC와 유사한 가상 명령어 집합이지만 1, 타입이나 함수 구조와 같은 고수준 정보는 보존하고 있습니다. IR은 세 가지 상호 변환 가능한 동형(isomorphic)의 형태로 존재합니다.10

  1. 인메모리(In-memory) 데이터 구조: 옵티마이저 패스가 컴파일러 내부에서 직접 조작하는 표현 방식입니다.

  2. 인간이 읽을 수 있는 텍스트 형식: .ll 확장자를 가진 텍스트 어셈블리 언어입니다. 이는 컴파일러 동작을 디버깅하고 분석하는 데 매우 유용합니다.18

  3. 바이너리 비트코드(Bitcode) 형식: .bc 확장자를 가진 압축된 바이너리 표현입니다. 효율적인 저장 및 JIT(Just-In-Time) 컴파일에 적합합니다.2

이러한 세 가지 형태의 존재는 LLVM IR의 뛰어난 유연성을 보여줍니다. 텍스트 형식은 컴파일러 개발자에게 투명성을 제공하여 디버깅을 용이하게 하고, 바이너리 형식은 성능이 중요한 LTO(Link-Time Optimization) 같은 시나리오에서 효율적인 데이터 교환을 가능하게 합니다. 이처럼 IR이 단순한 내부 데이터 구조를 넘어, 디버깅 도구이자 고성능 데이터 형식으로 기능하는 이중성은 LLVM의 큰 장점 중 하나입니다.16

또한, IR은 특정 하드웨어 아키텍처에 대한 종속성을 완전히 배제하도록 설계되었습니다. 물리 레지스터의 개수나 특정 호출 규약(calling convention)과 같은 세부 사항을 추상화하여, 어떤 플랫폼으로든 이식 가능한 진정한 의미의 독립성을 유지합니다.5

3.2 정적 단일 할당(SSA)의 힘: 진보된 최적화의 기반

LLVM IR은 정적 단일 할당(Static Single Assignment, SSA) 형태를 기반으로 합니다.1 SSA 형식에서는 모든 변수(가상 레지스터)가 프로그램 텍스트 내에서 단 한 번만 값을 할당받습니다.18 만약 원본 소스 코드에서 변수값이 재할당되면, IR에서는 이전 변수를 덮어쓰는 대신 새로운 버전의 변수를 생성합니다.20

SSA는 수많은 컴파일러 최적화 기법을 극적으로 단순화합니다. 예를 들어, 특정 변수가 어디에서 정의되었는지를 추적하는 ‘사용-정의 연쇄(use-def chain)’ 분석이 매우 간단해집니다. 어떤 변수를 사용하든 그 값의 출처는 단 하나뿐이기 때문입니다.22 이로 인해 상수 전파(constant propagation), 죽은 코드 제거(dead code elimination)와 같은 알고리즘들이 더 효율적이고 강력하게 동작할 수 있습니다.18

if/else 블록과 같이 제어 흐름에 따라 변수값이 달라지는 경우를 처리하기 위해, SSA는 phi라는 특별한 명령어를 도입합니다. phi 노드는 특정 기본 블록(basic block)의 시작 부분에 위치하여, 해당 블록에 도달하기까지 거쳐온 제어 흐름 경로에 따라 여러 이전 값 중 하나를 선택합니다. 이를 통해 단일 할당 원칙을 위배하지 않으면서도 조건부 값 할당을 표현할 수 있습니다.18

3.3 LLVM IR의 구조: 모듈, 함수, 기본 블록, 그리고 명령어

LLVM IR은 명확한 계층 구조를 가지고 있습니다.16

  • 모듈(Module): 최상위 컨테이너로, 하나의 번역 단위(예: .c 파일 하나)에 해당합니다. 전역 변수와 함수들의 정의를 포함합니다.

  • 함수(Function): 기본 블록들로 구성된 제어 흐름 그래프(Control-Flow Graph, CFG)를 포함합니다.

  • 기본 블록(Basic Block): 분기 없이 순차적으로 실행되는 명령어들의 시퀀스이며, 항상 하나의 터미네이터(terminator) 명령어로 끝납니다(예: br, ret).

  • 명령어(Instruction): add, load, store, call과 같은 단일 연산입니다. 명령어는 타입이 있는 값(value)에 대해 연산을 수행하며, 이 값은 상수이거나 다른 명령어의 결과(주로 % 기호로 시작하는 가상 레지스터)일 수 있습니다.16

3.4 실제 예시: C++ 소스에서 LLVM IR로의 변환 과정

간단한 C++ 소스 파일로부터 사람이 읽을 수 있는 LLVM IR을 생성하는 과정은 clang 명령어를 통해 수행할 수 있습니다. 예를 들어, clang -S -emit-llvm <source>.cpp -o <source>.ll 명령어는 <source>.cpp 파일을 컴파일하여 텍스트 형식의 LLVM IR 파일인 <source>.ll을 생성합니다.20

다음은 간단한 C++ 함수와 그에 해당하는 LLVM IR 예시입니다.

C++ 소스 코드 (sum.cpp):

int sum(int a, int b) {
return a + b;
}

생성된 LLVM IR (sum.ll):

; ModuleID = 'sum.cpp'
source_filename = "sum.cpp"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"

; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @_Z3sumii(i32 noundef %0, i32 noundef %1) #0 {
%3 = alloca i32, align 4
%4 = alloca i32, align 4
store i32 %0, i32* %3, align 4
store i32 %1, i32* %4, align 4
%5 = load i32, i32* %3, align 4
%6 = load i32, i32* %4, align 4
%7 = add nsw i32 %5, %6
ret i32 %7
}

attributes #0 = {... }
  • define: _Z3sumii라는 이름(C++ 이름 맹글링 결과)의 함수를 정의합니다.

  • %0, %1: 함수에 전달된 두 개의 i32 타입 인자입니다.

  • %7 = add nsw i32 %5, %6: 두 인자를 더하여 그 결과를 가상 레지스터 %7에 저장합니다. 이는 SSA 형식을 명확히 보여줍니다.16

  • ret i32 %7: 덧셈의 결과인 %7을 반환하며, 이는 함수의 터미네이터 명령어입니다.

  • target datalayout: 엔디언, 포인터 크기 등 플랫폼 ABI 관련 세부 정보를 명시하는 문자열입니다.18

4. 비교 분석: LLVM/Clang 대 GCC

본 섹션에서는 LLVM 툴체인(특히 C++ 프론트엔드로서의 Clang)과 그의 주요 오픈소스 경쟁자인 GNU 컴파일러 모음(GCC)을 균형 잡힌 시각으로 비교 분석합니다. 이 분석은 아키텍처, 라이선스, 성능, 그리고 개발자 경험에 초점을 맞춥니다.

4.1 아키텍처의 차이: 모듈형 인프라 대 통합형 컴파일러

LLVM의 아키텍처는 근본적으로 모듈화되어 있으며, 3단계 설계를 기반으로 한 라이브러리 집합체입니다.2 이러한 설계 덕분에 정적 분석, 코드 포맷팅 등 컴파일러의 일부 기능을 다른 도구에서 재사용하기가 매우 용이합니다.

반면, GCC는 역사적으로 더 통합된 단일체(monolithic) 컴파일러로 개발되었습니다.2 GCC 내부에도 중간 표현과 여러 단계가 존재하지만, 그 구성 요소들은 처음부터 LLVM처럼 쉽게 분리되거나 라이브러리로 재사용되도록 설계되지 않았습니다. 물론 시간이 지나면서 GCC 역시 C++ 기반의 모듈화된 코드베이스로 점차 전환해왔지만 2, 두 프로젝트의 근본적인 설계 철학에는 여전히 차이가 있습니다.

4.2 라이선스의 영향: 허용적 라이선스 대 카피레프트와 그 파급 효과

LLVM은 “UIUC” BSD 스타일 라이선스(현재는 LLVM 예외 조항이 포함된 Apache 2.0 라이선스)를 채택하고 있습니다.3 이는 기업들이 LLVM을 자사의 독점 소프트웨어에 통합하고 수정하더라도, 자체 소스 코드를 공개할 의무가 없는 허용적(permissive) 라이선스입니다.10

반면, GCC는 GNU 일반 공중 사용 허가서(GPL)라는 ‘카피레프트(copyleft)’ 라이선스를 사용합니다. 이 라이선스는 GCC 라이브러리를 링크하는 등 파생된 작업을 수행할 경우, 해당 결과물 역시 호환되는 오픈소스 라이선스로 배포해야 한다는 의무를 부과합니다.

이 라이선스의 차이는 기술 경쟁 구도에 결정적인 영향을 미쳤습니다. LLVM의 허용적 라이선스는 자사의 독점 운영체제와 개발 도구에 컴파일러를 깊숙이 통합하고자 했던 애플과 같은 기업들에게 매우 매력적인 조건이었습니다.2 애플은 GPL의 의무 조항을 피하고자 LLVM에 막대한 투자를 단행했고, 이는 LLVM/Clang이 GCC와 대등하거나 그 이상으로 발전하는 원동력이 되었습니다. 결국, 라이선스라는 법적 프레임워크가 기술 생태계의 진화 방향을 결정하고, GCC의 대안이 될 수 있는 강력한 경쟁자를 탄생시킨 핵심 요인으로 작용한 것입니다.

4.3 성능 벤치마크: 컴파일 속도와 코드 최적화에 대한 다각적 고찰

  • 컴파일 속도: 역사적으로 Clang은 GCC에 비해 더 빠른 컴파일 속도로 잘 알려져 있습니다. 이는 대규모 코드베이스를 다루는 개발자들에게 상당한 생산성 향상을 가져다줍니다.2

  • 런타임 성능(생성된 코드): 이는 매우 복잡하고 끊임없이 변화하는 경쟁 분야입니다. 오랜 역사를 가진 GCC는 코드 최적화 노하우가 축적되어 오랫동안 우위를 점해왔습니다.2 그러나 LLVM은 빠르게 격차를 좁혔고, 현재는 많은 벤치마크에서 GCC와 대등하거나 능가하는 성능을 보여주고 있습니다.28 성능은 특정 워크로드, 하드웨어 아키텍처, 그리고 사용된 컴파일러 버전에 따라 크게 달라질 수 있습니다.29

  • 링크 타임 최적화(LTO): 두 컴파일러 모두 링크 시점에 프로그램 전체를 분석하여 최적화를 수행하는 LTO를 지원합니다. 특히 LLVM의 비트코드 기반 접근 방식은 LTO에 매우 적합한 구조를 가지고 있습니다.26

4.4 개발자 경험: 진단, 오류 메시지, 그리고 도구 생태계

Clang의 가장 널리 칭찬받는 특징 중 하나는 오류 및 경고 메시지의 품질입니다. Clang의 진단 메시지는 매우 정확하고, 문제 해결에 도움이 되도록 설계되었습니다. 종종 오류가 발생한 소스 코드 조각과 함께 문제 지점을 정확히 가리키는 캐럿(^) 기호, 그리고 수정 제안까지 포함하여 제공됩니다.4 이는 프로젝트 초기부터 중요한 설계 목표였습니다.

또한, LLVM의 라이브러리 기반 설계는 소스 코드 수준의 도구를 구축하기 위한 훌륭한 플랫폼을 제공합니다. Clang 정적 분석기(Clang Static Analyzer), clang-tidy, clang-format과 같은 프로젝트들은 Clang의 C++ 파싱 라이브러리 위에 직접 구축되어, 자동화된 버그 탐지 및 코드 스타일 강제 적용과 같은 강력한 기능을 제공합니다.4

아래 표는 LLVM/Clang과 GCC의 주요 특징을 비교 요약한 것입니다.

표 2: LLVM/Clang과 GCC 비교 개요

특징LLVM/ClangGCC분석 및 영향
핵심 아키텍처모듈형, 라이브러리 기반전통적으로 단일체, 통합형LLVM의 모듈성은 도구 생태계 확장에 유리하며, 재사용성이 높음.2
주요 라이선스Apache 2.0 (LLVM 예외 포함)GPL (GNU General Public License)LLVM의 허용적 라이선스는 상용 제품 통합을 용이하게 하여 애플 등 기업의 투자를 유치하는 결정적 계기가 됨.10
컴파일 속도일반적으로 더 빠름일반적으로 상대적으로 느림대규모 프로젝트에서 개발자 생산성 향상에 기여.2
런타임 성능GCC와 대등하거나 우위인 경우 다수오랜 최적화 노하우, 여전히 강력함워크로드와 버전에 따라 결과가 달라지는 지속적인 경쟁 관계.28
진단 품질매우 상세하고 사용자 친화적기능적이지만 상대적으로 덜 상세함Clang의 우수한 오류 메시지는 디버깅 시간을 단축시켜 개발자 경험을 향상시킴.11
도구 생태계정적 분석, 포맷팅 등 도구 개발 용이상대적으로 도구 통합이 복잡함LLVM의 라이브러리 기반 설계는 강력한 서드파티 도구 생태계의 기반이 됨.4
주요 후원 기업Apple, Google, Microsoft 등(주로 커뮤니티 및 재단 기반)기업의 적극적인 후원은 LLVM의 빠른 발전에 기여함.

5. 실제 적용 사례 - 현대 언어의 백엔드

본 섹션에서는 이론에서 벗어나, LLVM이 어떻게 가장 중요한 현대 프로그래밍 언어들의 컴파일 백엔드로 기능하는지 실제 사례를 통해 살펴봅니다. Swift와 Rust를 상세한 사례 연구로 사용하여, 이들 언어가 LLVM 인프라를 어떻게 활용하고 때로는 확장하는지 조명합니다.

5.1 사례 연구: Swift 컴파일러

Swift 컴파일러 파이프라인은 소스 코드를 LLVM IR로 직접 변환하지 않는 다단계 프로세스입니다. 먼저 Swift 코드를 파싱하여 AST를 생성합니다.8

그 후, AST는 SIL(Swift Intermediate Language)이라는 고수준의 Swift 전용 중간 언어로 변환됩니다.8 자동 참조 카운팅(ARC) 최적화나 객체 지향 의미론과 관련된 고수준 최적화는 바로 이 SIL 단계에서 수행됩니다. LLVM IR은 언어에 독립적이기 때문에 Rust의 소유권 규칙이나 Swift의 클래스 계층 구조와 같은 고수준의 언어별 의미를 직접 표현할 수 없습니다. 따라서 이러한 언어별 의미론과 관련된 분석 및 최적화는 SIL과 같은 언어 전용 IR에서 수행하는 것이 훨씬 효율적입니다.

SIL 단계의 최적화가 완료된 후에야 코드는 비로소 LLVM IR로 변환됩니다.8 이 시점부터 코드는 LLVM이 제공하는 모든 표준 최적화(기계 독립적 및 종속적 최적화)의 혜택을 받게 됩니다. Swift 프로젝트는 자체적으로 LLVM/Clang의 포크(fork)를 유지 관리하는데, 이러한 긴밀한 통합을 통해 Swift 팀은 주 LLVM 릴리스 주기에 얽매이지 않고 Swift에 특화된 기능과 최적화를 LLVM 내부에 신속하게 구현하고 반복적으로 개선할 수 있습니다.31

5.2 사례 연구: Rust 컴파일러 (rustc)

Rust 컴파일러 rustc는 Rust 언어 자체로 작성되었습니다.33 rustc 역시 다단계 파이프라인을 따르며, 소스 코드를 파싱하여 AST를 생성하는 것으로 시작합니다.34

AST는 HIR(High-level IR)로, 그리고 다시 MIR(Mid-level IR)로 변환됩니다.34 MIR은 Rust의 가장 특징적인 기능인 빌림 검사기(borrow checker)와 기타 안전성 분석을 수행하는 데 결정적인 역할을 합니다. 많은 Rust 고유의 최적화 또한 MIR 수준에서 이루어집니다.34

마지막 단계는 코드 생성(codegen)으로, MIR이 LLVM IR로 번역됩니다.34 이 과정에서 제네릭 Rust 코드가 단형성화(monomorphization)됩니다. 즉, 제네릭 함수가 사용되는 각각의 구체적인 타입에 대해 특화된 버전의 함수가 생성됩니다. 이렇게 생성된 LLVM IR은 LLVM 백엔드로 전달되어 추가 최적화와 기계어 생성을 거치게 됩니다.34 Rust 컴파일러는 llvm-sys라는 바인딩 크레이트(crate)를 통해 LLVM의 C API에 접근하여 이 모든 과정을 처리합니다.35

Swift와 Rust의 사례는 현대 고성능 컴파일러 설계의 모범 사례를 보여줍니다. 즉, ’2계층 IR 전략’을 채택하는 것입니다. 언어별 고유 의미론은 고수준 IR(SIL, MIR)에서 처리하고, 기계 수준의 저수준 최적화는 LLVM의 범용 IR에 위임함으로써 관심사를 명확히 분리하고 각 단계의 효율을 극대화합니다. 이 패턴은 이후 MLIR 철학의 직접적인 선구자가 되었습니다.

5.3 더 넓은 시야: 다른 언어에서의 LLVM의 역할

LLVM의 영향력은 Swift와 Rust에만 국한되지 않습니다.

  • Kotlin/Native: LLVM을 백엔드로 사용하여 코틀린 코드를 JVM을 거치지 않고 직접 네이티브 바이너리로 컴파일합니다. 이를 통해 성능이 중요한 애플리케이션에서 가상 머신으로 인한 오버헤드를 제거할 수 있습니다.5

  • Julia: 기술 컴퓨팅을 위한 고성능 언어로, LLVM 기반의 JIT(Just-In-Time) 컴파일러를 사용하여 C 언어에 필적하는 속도를 달성합니다.

  • Emscripten과 그 너머: LLVM의 다재다능함은 수많은 다른 언어들(Ruby, Python, Haskell, D 등)이 컴파일 타겟으로 삼는 기반이 되었습니다.4 특히 Emscripten은 LLVM을 사용하여 C/C++ 및 기타 LLVM 지원 언어를 WebAssembly로 컴파일함으로써, 네이티브 코드를 웹 브라우저에서 실행하는 혁신을 이끌었습니다.36

6. 컴파일의 새로운 지평

본 섹션에서는 LLVM의 유연하고 모듈화된 아키텍처가 어떻게 전통적인 CPU용 AOT(Ahead-Of-Time) 컴파일의 범위를 넘어, 새롭게 부상하는 기술 영역에서 핵심 기반 기술로 자리 잡게 되었는지 탐구합니다.

6.1 현대 웹의 구현: Emscripten을 이용한 WebAssembly 컴파일

WebAssembly(WASM)는 웹을 위한 이식 가능한 저수준 바이너리 명령어 형식으로, C, C++, Rust와 같은 언어의 안전하고 빠른 컴파일 타겟으로 설계되었습니다.14 LLVM은 wasm32/wasm64를 공식 백엔드로 지원하며 14, 이는 LLVM 프론트엔드를 가진 모든 언어가 원칙적으로 웹 브라우저에서 실행될 수 있음을 의미합니다.

Emscripten은 LLVM(특히 Clang)을 사용하여 C/C++ 코드를 WASM으로 컴파일하는 완전한 컴파일러 툴체인입니다.36 Emscripten은 단순히 LLVM을 통한 컴파일만 처리하는 것이 아니라, 복잡한 네이티브 애플리케이션(예: 게임 엔진, 그래픽 도구)이 웹에서 실행될 수 있도록 자바스크립트 ‘글루(glue)’ 코드와 libc, SDL2, OpenGL과 같은 공통 라이브러리의 WASM 호환 구현체를 함께 제공합니다.37 개발자는 emcc라는 컴파일러 드라이버를 clang이나 gcc의 대체재로 사용합니다. emcc는 내부적으로 Clang을 호출하여 LLVM IR을 생성하고, 이를 LLVM WASM 백엔드로 보낸 후, Binaryen과 같은 도구를 통해 WASM에 특화된 추가 최적화를 수행하고 최종적으로 필요한 JS 로더 코드를 생성합니다.37

6.2 AI/ML 혁명: MLIR과 도메인 특화 컴파일

현대의 인공지능 및 머신러닝 워크로드는 CPU, GPU, TPU, 그리고 다양한 커스텀 가속기 등 이기종(heterogeneous) 하드웨어에서 실행됩니다. 이처럼 다양한 하드웨어 환경에 맞춰 코드를 최적화하는 것은 극도로 복잡한 과제입니다. 텐서 컨볼루션과 같은 고수준 연산을 표현하고 최적화하기에 LLVM IR과 같은 단일 저수준 IR은 충분하지 않습니다.14

이 문제를 해결하기 위해 LLVM 프로젝트 내에서 MLIR(Multi-Level Intermediate Representation)이라는 새로운 컴파일러 인프라가 탄생했습니다.4 MLIR은 단일 IR이 아니라, 여러 단계의 IR을 사용하는 컴파일러를 구축하기 위한 프레임워크입니다.14 MLIR에서는 ’다이얼렉트(dialect)’라는 개념을 통해 다양한 추상화 수준을 표현합니다(예: TensorFlow 다이얼렉트, 루프를 위한 affine 다이얼렉트, GPU 다이얼렉트 등). 컴파일 과정은 고수준 다이얼렉트에서 저수준 다이얼렉트로 프로그램을 점진적으로 ‘낮추는(lowering)’ 과정이며, 각 단계에서 도메인에 특화된 최적화를 수행한 후 최종적으로 LLVM IR로 변환하여 코드 생성을 마무리합니다.14 IREE(Intermediate Representation Execution Environment) 프로젝트는 MLIR을 활용하여 TensorFlow나 PyTorch와 같은 프레임워크의 모델을 다양한 하드웨어에서 실행할 수 있도록 컴파일하는 완전한 솔루션을 제공합니다.14

LLVM이 처음에는 컴파일러를 만들기 위한 ’부품 키트’였다면, MLIR의 등장은 LLVM이 ’부품 키트를 만드는 프레임워크’로 진화했음을 의미합니다. 이는 LLVM 프로젝트가 스스로의 핵심 원칙을 한 단계 더 추상화함으로써 차세대 컴퓨팅의 도전에 대응하고 있음을 보여주며, 컴파일 기술의 기반으로서 그 생명력을 이어가게 하는 원동력입니다.

6.3 미래 전망: 오픈 프로젝트, 연구, 그리고 확장되는 영향력

LLVM 프로젝트는 모든 하위 프로젝트에서 끊임없는 개발이 이루어지는 매우 활발한 생태계입니다. 구글 서머 오브 코드(Google Summer of Code)와 같은 프로그램들은 JIT 컴파일 개선, 새로운 최적화 기법, GPU 지원 강화, 정적 분석 발전 등 현재의 연구 및 개발 우선순위를 엿볼 수 있는 창을 제공합니다.40

LLVM은 학계와 산업계의 전 세계적인 기여자들로 구성된 활기찬 오픈소스 커뮤니티에 의해 발전하고 있습니다. 그 발전은 커뮤니티의 제안, 메일링 리스트와 포럼에서의 토론, 그리고 정기적인 개발자 미팅을 통해 이루어집니다.14

결론적으로, LLVM은 학술 연구 프로젝트에서 출발하여 오늘날 소프트웨어 산업의 거대한 부분을 지탱하는 핵심 컴파일러 인프라로 진화했습니다. 모듈성과 재사용성이라는 그 아키텍처 원칙은 성공적이었을 뿐만 아니라, 웹과 인공지능과 같은 새로운 컴퓨팅 패러다임에 적응하고 번성할 수 있는 기반을 마련해주었습니다. LLVM의 미래는 차세대 언어, 도구, 그리고 도메인 특화 컴파일러를 위한 구성 요소를 지속적으로 제공하는 데 있습니다.

7. 참고 자료

  1. LLVM - 위키백과, 우리 모두의 백과사전, https://ko.wikipedia.org/wiki/LLVM
  2. LLVM (r136 판) - 나무위키, https://namu.wiki/w/LLVM?uuid=a8d94736-3d77-4119-8e1c-356753c114d8
  3. LLVM 이란? - jacking75, https://jacking75.github.io/Trans_llvm/
  4. The LLVM Compiler Infrastructure Project, https://llvm.org/
  5. LLVM - 나무위키, https://namu.wiki/w/LLVM
  6. namu.wiki, https://namu.wiki/w/LLVM#:~:text=%EC%9B%90%EB%9E%98%20LLVM%EC%9D%80%20%EC%A0%80%EC%88%98%EC%A4%80%20%EA%B0%80%EC%83%81,%EC%9D%98%20%EC%A0%95%EC%8B%9D%20%EB%AA%85%EC%B9%AD%EC%9D%B4%20%EB%90%9C%EB%8B%A4.
  7. LLVM - 지재유경(志在有逕) - 티스토리, https://dulidungsil.tistory.com/entry/LLVM
  8. LLVM이란 - ZeddiOS - 티스토리, https://zeddios.tistory.com/1175
  9. LLVM (r270 판) - 나무위키, https://namu.wiki/w/LLVM?uuid=10d56658-bbac-4cfc-b16f-6f1f68131de2
  10. LLVM 컴파일러 - 레드의 노트 - 티스토리, https://etst.tistory.com/385
  11. [LLVM & Clang] LLVM 최적화, 어디까지 아시나요?(LTO, PGO, BOLT) - Pangyoalto Blog, https://pangyoalto.com/clang-and-optimization/
  12. 오픈소스 이야기 - LLVM 프로젝트 (2/2) - 하모니카, https://hamonikr.org/oss/119156
  13. What is LLVM? The power behind Swift, Rust, Clang, and more : r/programming - Reddit, https://www.reddit.com/r/programming/comments/7vfmiu/what_is_llvm_the_power_behind_swift_rust_clang/
  14. KC-ML2, https://www.kc-ml2.com/posts/blog_LLVMAsia2025
  15. LLVM 개념과 구성요소 및 사용사례, https://securitychum.com/research/llvm-%EA%B0%9C%EB%85%90%EA%B3%BC-%EA%B5%AC%EC%84%B1%EC%9A%94%EC%86%8C-%EB%B0%8F-%EC%82%AC%EC%9A%A9%EC%82%AC%EB%A1%80/
  16. 대학원생을 위한 LLVM - 유나’s blog - 티스토리, https://gayuna.tistory.com/11
  17. LLVM 컴파일러의 활용, https://velog.io/@thdalwh3867/LLVM-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC%EC%9D%98-%ED%99%9C%EC%9A%A9
  18. LLVM Concepts - SSA Form and PHI Nodes - llvmpy, https://www.llvmpy.org/llvmpy-doc/dev/doc/llvm_concepts.html
  19. [LLVM] LLVM - 개요 - Blue-Moon의 정리노트!!, https://bluemoon-1st.tistory.com/100
  20. LLVM IR이란 - 공과대학 5호관 - 티스토리, https://trixie.tistory.com/23
  21. velog.io, https://velog.io/@thdalwh3867/LLVM-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC%EC%9D%98-%ED%99%9C%EC%9A%A9#:~:text=LLVM%20IR%EC%9D%80%20%EB%86%92%EC%9D%80%20%EC%88%98%EC%A4%80,%EB%90%98%EB%8A%94%20%ED%98%95%ED%83%9C%EB%A5%BC%20%EA%B0%96%EC%8A%B5%EB%8B%88%EB%8B%A4.
  22. Static single-assignment form - Wikipedia, https://en.wikipedia.org/wiki/Static_single-assignment_form
  23. Lesson 6: Static Single Assignment - CS@Cornell, https://www.cs.cornell.edu/courses/cs6120/2022sp/lesson/6/
  24. Intermediate Representations in Compilers: Static Single Assignment (SSA) - Sasank’s Blog, https://chsasank.com/compiler-intermediate-representations-1-ssa.html
  25. Single-Static Assignment Form and PHI - Mapping High Level Constructs to LLVM IR, https://mapping-high-level-constructs-to-llvm-ir.readthedocs.io/en/latest/control-structures/ssa-phi.html
  26. How to make clang compile to llvm IR - Stack Overflow, https://stackoverflow.com/questions/9148890/how-to-make-clang-compile-to-llvm-ir
  27. Compiling a C++ file into LLVM IR using clang::CompilerInstance - Beginners, https://discourse.llvm.org/t/compiling-a-c-file-into-llvm-ir-using-clang-compilerinstance/69957
  28. LLVM vs GCC : r/cpp - Reddit, https://www.reddit.com/r/cpp/comments/9or8s1/llvm_vs_gcc/?tl=ko
  29. GCC 15 - LLVM Clang 20 Compiler Performance comparison · Issue #127069 - GitHub, https://github.com/llvm/llvm-project/issues/127069
  30. Swift compiler vs Objective-C compiler - Stack Overflow, https://stackoverflow.com/questions/31275721/swift-compiler-vs-objective-c-compiler
  31. Why does swift use it’s own llvm/clang? - Reddit, https://www.reddit.com/r/swift/comments/sv020v/why_does_swift_use_its_own_llvmclang/
  32. Version of LLVM used by Swift - LLDB, https://forums.swift.org/t/version-of-llvm-used-by-swift/66819
  33. Understanding how the Rust compiler is built - help - The Rust Programming Language Forum, https://users.rust-lang.org/t/understanding-how-the-rust-compiler-is-built/87237
  34. Overview of the compiler - Rust Compiler Development Guide, https://rustc-dev-guide.rust-lang.org/overview.html
  35. Writing an LLVM backend for the Move language in Rust, https://brson.github.io/2023/03/12/move-on-llvm
  36. emscripten-core/emscripten: Emscripten: An LLVM-to-WebAssembly Compiler - GitHub, https://github.com/emscripten-core/emscripten
  37. About Emscripten — Emscripten 4.0.15-git (dev) documentation, https://emscripten.org/docs/introducing_emscripten/about_emscripten.html
  38. Emscripting a C library to Wasm | Articles - web.dev, https://web.dev/articles/emscripting-a-c-library
  39. Optimizing Code — Emscripten 4.0.15-git (dev) documentation, https://emscripten.org/docs/optimizing/Optimizing-Code.html
  40. The LLVM Compiler Infrastructure Project - LLVM.org, https://llvm.org/OpenProjects.html