Rust와 LLVM IR 코드 생성을 통한 컴파일러 개발 입문
컴파일러 개발의 핵심 중 하나는 원시 코드를 중간 표현(Intermediate Representation)으로 변환하는 과정이다.本章에서는 Rust 언어와 LLVM 라이브러리를 활용하여 AST(추상 구문 트)에서 LLVM IR을 생성하는 과정을详细히 다룬다. 예제 프로젝트인 Kata-Kaleidoscope를 통해 실제 코드 생성기의 구조와 구현 방식을 학습한다.
LLVM IR의 이해: 컴파일러의 핵심 다리
LLVM IR은 LLVM 컴파일러 프레임워크의 중심이 되는 중간 표현이다. 다음 세 가지 형태롤 존재한다:
- 메모리 내 존재하는 컴파일러 IR
- 바이트코드 형태의 디스크 저장소
- 인간이 읽을 수 있는 어셈블리 형태
이러한统一된 중간 표현은 코드 최적화와 플랫폼 간 컴파일을 가능하게 한다. IR 생성 모듈은 Kata-Kaleidoscope 프로젝트의 chapters/2/src/builder.rs에 위치하며, AST에서 LLVM IR로의 변환을 담당한다.
개발 환경 구성: Rust와 LLVM 연동
프로젝트 설정은 Cargo.toml에서 다음과 같이 정의한다:
[dependencies]
llvm-sys = "*"
[dependencies.iron_llvm]
git = "https://github.com/jauhien/iron-llvm.git"
코드 생성 컨텍스트는 chapters/2/src/lib.rs에 정의되어 있으며, 네 가지 핵심 구성 요소를 포함한다:
- Context: LLVM 전역 컨텍스트
- Builder: IR 명령어 빌더
- symbol_table: 함수 매개변수 매핑 테이블
- ty: 기본 데이터 타입(본 장에서는 f64 사용)
아키텍처 설계: 모듈과 함수 생성
모듈 설계 패턴
Module은 LLVM IR의 컴파일 단위로, 함수와 전역 변수를 포함한다. 프로젝트에서는 ModuleProvider trait을 통해 모듈 작업을 추상화하여 JIT 컴파일러 통합을 단순화한다:
pub trait ModuleProvider {
fn dump(&self);
fn get_module(&mut self) -> &mut core::Module;
fn get_function(&mut self, name: &str) -> Option<(FunctionRef, bool)>;
}
SimpleModuleProvider가 기본 모듈 관리 기능을 제공하며, 구현 코드는 chapters/2/src/builder.rs에 있다.
함수 코드 생성流程
함수 생성은 IR 구성의 핵심으로, 세 단계로 나뉜다:
- 프로토타입 생성: 함수 시그니처와 매개변수 정의
- 기본 블록 생성: 함수 진입 기본 블록 구성
- 명령어 생성: AST 노드를 LLVM 명령어로 변환
함수 생성 핵심 구현:
impl IRBuilder for parser::Function {
fn codegen(&self, ctx: &mut Context, mp: &mut ModuleProvider) -> IRResult {
ctx.symbol_table.clear();
let (func, _) = try!(self.prototype.codegen(ctx, mp));
let mut function = unsafe { FunctionRef::from_ref(func) };
let entry_bb = function.append_basic_block_in_context(&mut ctx.context, "entry");
ctx.builder.position_at_end(&mut entry_bb);
// 매개변수 설정
for (param, arg) in function.params_iter().zip(&self.prototype.args) {
ctx.symbol_table.insert(arg.clone(), param.to_ref());
}
// 함수 본문 생성
let body = match self.body.codegen(ctx, mp) {
Ok((val, _)) => val,
Err(msg) => {
unsafe { LLVMDeleteFunction(function.to_ref()) };
return Err(msg);
}
};
ctx.builder.build_ret(&body);
function.verify(LLVMAbortProcessAction);
ctx.symbol_table.clear();
Ok((function.to_ref(), self.prototype.name.as_str() == ""))
}
}
표현식 생성: AST에서 LLVM 명령어로
표현식은 IR 생성의最小 단위이며, Kata-Kaleidoscope는 네 가지 기본 표현식 타입을 지원한다. 구현은 chapters/2/src/parser.rs에 있다.
리터럴 표현식
상수 값을 직접 생성한다:
&parser::LiteralExpr(ref value) => {
Ok((RealConstRef::get(&ctx.ty, *value).to_ref(), false))
}
변수 표현식
심볼 테이블에서 변수 값을 조회한다:
&parser::VariableExpr(ref name) => {
match ctx.symbol_table.get(name) {
Some(val) => Ok((*val, false)),
None => error("undeclared variable")
}
}
이항 연산 표현식
산술 연산을 위한 명령어를 생성한다. 加法 연산 예시:
"+" => Ok((ctx.builder.build_fadd(lhs_val, rhs_val, "addtmp"), false))
함수 호출 표현식
함수 매개변수를 처리하고 호출 명령어를 생성한다:
&parser::CallExpr(ref name, ref args) => {
let (callee, _) = match mp.get_function(name) {
Some(func) => func,
None => return error("undefined function")
};
let mut arg_values = Vec::new();
for arg in args.iter() {
let (arg_val, _) = try!(arg.codegen(ctx, mp));
arg_values.push(arg_val);
}
Ok((ctx.builder.build_call(callee.to_ref(), arg_values.as_mut_slice(), "calltmp"), false))
}
구현 실무: 일반적인 문제와 해결책
타입 안전성 보장
Rust의 타입 시스템은 LLVM IR 생성에 추가적인 안전성을 제공한다. iron-llvm 라이브러리는 LLVM C API를 감싸서不安全한 연산을 최소한으로 제한하면서도 Rust 풍의 API를 제공한다.
오류 처리 전략
함수 생성 중에는 "失败即清理" 방식을 채택한다. 코드 생성 실패 시 不完整한 함수 정의를 즉시 삭제한다:
Err(msg) => {
unsafe { LLVMDeleteFunction(function.to_ref()) };
return Err(msg);
}
검증과 디버깅
IR 생성 후 function.verify(LLVMAbortProcessAction)를 통해 검증하여 생성된 코드가 LLVM 사양을 준수하는지 확인한다. dump() 메서드로 모듈 내용을 출력하여 디버깅할 수 있다:
fn dump(&self) {
self.module.dump();
}
핵심 정리
Chapter 2는 완전한 IR 생성기를 구축하면서 컴파일러前端에서 중간 표현으로의 변환 과정을 보여준다. 주요 학습 내용은 다음과 같다:
- LLVM IR의 설계 철학과 활용 시나리오
- Rust와 LLVM 결합의 최적화된 방법
- 모듈화된 코드 생성 아키텍처 설계
- 표현식에서 LLVM 명령어로의 변환 기법
프로젝트 코드 구조는 명확하며, 각 컴포넌트는 명확한 역할을 갖는다. 이는 이후 장에서 최적화와 JIT 컴파일러를 구현하는 데 좋은 기반이 된다. chapters/2/src/ 디렉토리下的 코드를 통해 개발자들은 IR 생성의 각 세부를 심층적으로 이해하고 컴파일러 개발의 핵심 기술을 습득할 수 있다.
실습을 시작하려면 다음 명령어를 실행한다:
git clone https://gitcode.com/gh_mirrors/ir/iron-kaleidoscope
cd iron-kaleidoscope/chapters/2
cargo run
Kata-Kaleidoscope 프로젝트는 점진적인 방식으로 복잡한 LLVM 기술을 쉽게 이해하고 구현할 수 있도록 도와주며, 컴파일러 개발 학습에理想的 인 선택이다.