IT 이야기/default

컴파일러 구조와 LLVM

Kjun25 2024. 8. 29. 17:21
반응형

컴퓨터 프로그래밍에서 소스 코드를 기계어로 번역하여 컴퓨터에서 실행 가능한 프로그램으로 만드는 작업은 컴파일러의 역할입니다. 전통적인 컴파일러는 코드 변환 과정에서 최적화를 수행하고, 각 CPU 아키텍처에 맞는 기계어를 생성합니다. 

 

하지만 이 과정에서 발생하는 비효율성이나 다양한 프로그래밍 언어 및 아키텍처에 대한 대응이 어렵다는 문제점이 있었습니다. 이를 해결하기 위해 등장한 것이 LLVM이라는 모듈식 컴파일러 인프라스트럭처입니다. 이 글에서는 전통적인 컴파일러와 LLVM의 구조와 차이점을 설명하고, LLVM이 제공하는 장점들을 살펴보겠습니다.

전통적인 컴파일러 구조

컴파일러는 일반적으로 세 가지 단계로 구성됩니다: 프런트엔드(Frontend), 미들엔드(Middle-end), 그리고 백엔드(Backend)입니다.

Compiler Architecture

 

프런트엔드

이 단계에서는 소스 코드의 어휘 분석(Lexical Analysis)을 통해 코드를 토큰으로 분할하고, 구문 분석(Syntax Analysis)을 통해 구문 트리(Syntax Tree)를 생성합니다. 이후 의미 분석(Semantic Analysis)을 통해 코드의 의미를 해석하고, 추상 구문 트리(Abstract Syntax Tree, AST)를 만들어 미들엔드로 전달합니다.

미들엔드

소스 코드를 단어별로 분석하여 어휘 분석(Lexical Analysis)을 수행한 후, 구문 트리(Syntax Tree)를 생성하고(Syntax Analysis)의미 분석(Semantic Analysis)을 통해 코드를 해석합니다. 이 과정에서 에러와 경고를 처리하고, 추상 구문 트리(Abstract Syntax Tree, AST)를 만들어 미들엔드로 넘깁니다.

백엔드

백엔드는 최적화된 IR을 사용하여 대상 아키텍처에 맞는 기계어를 생성합니다. 이 과정에서 CPU 아키텍처에 특화된 명령어 세트가 생성되며, 최종적으로 목적 코드(Object Code)가 만들어집니다.

 

GCC(GNU Compiler Collection)은 이러한 구조를 따르는 전통적인 컴파일러의 예로, C/C++, Objective-C 등의 언어를 지원합니다. 그러나 GCC는 모놀리식(Monolithic) 아키텍처로 구성되어 있어, 특정 언어와 아키텍처에 대해 유연하게 대처하기 어렵다는 한계가 있습니다.

LLVM(Low Level Virtual Machine)의 구조와 장점

LLVM은 이러한 전통적인 컴파일러의 한계를 극복하기 위해 개발된 모듈식 컴파일러 인프라스트럭처입니다. LLVM은 프런트엔드, 미들엔드(Optimizer), 백엔드로 구성되어 있으며, 이 중 프론트엔드만 구현하면 나머지 부분은 LLVM Core를 통해 공유할 수 있는 구조로 되어 있습니다.

LLVM Compiler Architecture

프론트엔드

프런트엔드는 Clang과 같은 컴파일러를 사용하여 소스 코드를 LLVM IR(Intermediate Representation)로 변환합니다. LLVM IR은 어셈블리어와 유사한 저수준 언어로, 다양한 언어와 아키텍처에 대응할 수 있습니다.

LLVM IR은 어셈블리어와 비슷한 Low level Languages입니다.
 @.str = internal constant [14 x i8] c"hello, world\0A\00"

    declare i32 @printf(i8*, ...)

    define i32 @main(i32 %argc, i8** *argv) nounwind {
    entry:
        %tmp1 = getelementptr [14 x i8]* @.str, i32 0, i32 0
        %tmp2 = call i32 (i8*, ...)* @printf( i8* %tmp1 ) nounwind
        ret i32 0
    }

미들엔드

LLVM Optimizer는 LLVM IR에 대해 다양한 최적화를 수행합니다. 이 과정에서는 LTO(Link Time Optimization), PGO(Profile Guided Optimization), BOLT(Binary Optimization and Layout Tool) 등의 최적화 작업이 이루어집니다.

백엔드

백엔드는 최적화된 LLVM IR을 각 아키텍처에 맞는 기계어로 변환합니다. LLVM의 백엔드는 여러 아키텍처에 대해 동일한 IR을 사용하여 기계어를 생성할 수 있어, 다양한 플랫폼에 쉽게 이식할 수 있는 코드를 작성할 수 있습니다.

 

LLVM의 모듈식 구조는 특히 다양한 언어와 아키텍처에 대한 지원을 용이하게 합니다. 예를 들어, C, C++, Swift, Rust 등 다양한 언어의 소스 코드를 각각의 프런트엔드에서 LLVM IR로 변환한 후, LLVM Core를 사용하여 최적화와 기계어 생성을 수행할 수 있습니다.

결론

전통적인 컴파일러는 모놀리식 아키텍처로 인해 특정 언어와 아키텍처에 대한 유연성이 부족하다는 단점이 있었습니다. 그러나 LLVM은 모듈식 컴파일러 인프라스트럭처를 도입하여 이러한 한계를 극복하고, 다양한 언어와 아키텍처에 유연하게 대응할 수 있도록 설계되었습니다. 

 

LLVM을 사용하면 프런트엔드만 구현하여 LLVM Core와 연결함으로써, 최적화와 기계어 생성을 포함한 전체 컴파일 과정을 효율적으로 수행할 수 있습니다. 이로써 LLVM은 현대 컴파일러 인프라스트럭처의 중요한 구성 요소로 자리 잡고 있으며, 다양한 프로그래밍 언어와 플랫폼에서 널리 사용되고 있습니다.

반응형