코드 생성
코드 생성(또는 “codegen”)은 실행 가능한 바이너리를 실제로 생성하는 컴파일러의 부분입니다. 일반적으로 rustc는 코드 생성에 LLVM을 사용하지만, Cranelift와 GCC도 지원합니다. 핵심은 rustc가 코드 생성을 직접 구현하지 않는다는 점입니다. 다만 Rust 소스 코드에서는 백엔드의 많은 부분이 이름에 codegen을 포함하고 있다는 점에 유의할 필요가 있습니다(명확한 경계는 없습니다).
참고: 코드 생성 버그를 디버깅하는 방법에 대한 힌트를 찾고 있다면 디버깅 장의 이 절을 참고하십시오.
LLVM이란 무엇입니까?
LLVM은 “모듈식이며 재사용 가능한 컴파일러 및 툴체인 기술의 모음“입니다. 특히 LLVM 프로젝트에는 플러그인 방식의 컴파일러 백엔드(이 역시 “LLVM“이라고 부릅니다)가 포함되어 있으며, 이 백엔드는 clang C 컴파일러와 우리가 사랑하는 rustc를 비롯한 많은 컴파일러 프로젝트에서 사용됩니다.
LLVM은 LLVM IR 형태로 입력을 받습니다. LLVM IR은 기본적으로 저수준 타입과 주석이 추가된 어셈블리 코드입니다. 이러한 주석은 LLVM IR과 출력되는 기계어 코드에 최적화를 수행하는 데 도움이 됩니다. 이 모든 과정의 최종 결과물은 (마침내) 실행 가능한 무언가(예: ELF 오브젝트, EXE 또는 wasm)입니다.
LLVM을 사용하면 몇 가지 이점이 있습니다:
- 컴파일러 백엔드 전체를 직접 작성할 필요가 없습니다. 이로써 구현 및 유지보수 부담이 줄어듭니다.
- LLVM 프로젝트가 축적해 온 방대한 고급 최적화 모음의 혜택을 누릴 수 있습니다.
- LLVM이 지원하는 모든 플랫폼으로 Rust를 자동으로 컴파일할 수 있습니다. 예를 들어 LLVM이 wasm 지원을 추가하자마자, 짜잔! rustc, clang을 비롯한 여러 언어가 wasm으로 컴파일할 수 있게 되었습니다! (물론 추가로 해야 할 작업이 조금 있었지만, 어쨌든 90%는 이미 된 상태였습니다.)
- 우리와 다른 컴파일러 프로젝트들은 서로 혜택을 주고받습니다. 예를 들어 Spectre 및 Meltdown 보안 취약점이 발견되었을 때 LLVM만 패치하면 되었습니다.
LLVM 실행, 링크 및 메타데이터 생성
모든 함수와 정적 변수 등에 대한 LLVM IR이 만들어지고 나면, LLVM과 그 최적화 패스를 실행하기 시작할 차례입니다. LLVM IR은 “모듈” 단위로 묶입니다. 멀티코어 활용을 돕기 위해 여러 “모듈“을 동시에 코드 생성할 수 있습니다. 이러한 “모듈“을 우리는 코드 생성 단위라고 부릅니다. 이 단위들은 훨씬 이전의 단형화 수집 단계에서 정해졌습니다.
LLVM이 이 모듈들로부터 오브젝트를 생성하면, 이 오브젝트들은 선택적으로 메타데이터 오브젝트와 함께 링커로 전달되고, 그 결과 아카이브나 실행 파일이 생성됩니다.
최적화를 실행하는 것이 반드시 위에서 설명한 코드 생성 단계인 것은 아닙니다. 특정 종류의 LTO에서는 최적화가 대신 링크 시점에 일어날 수도 있습니다. 또한 일부 최적화는 오브젝트가 링커로 전달되기 전에 일어나고, 일부는 링크 도중에 일어날 수도 있습니다.
이 모든 과정은 컴파일의 거의 마지막 단계에서 일어납니다. 이에 대한 코드는 rustc_codegen_ssa::back과 rustc_codegen_llvm::back에서 찾을 수 있습니다. 안타깝게도 이 코드는 LLVM에 의존하는 코드가 제대로 분리되어 있지 않아서, rustc_codegen_ssa에도 LLVM 백엔드에 특화된 코드가 상당히 많이 들어 있습니다.
이 구성 요소들이 작업을 마치면 요청한 출력물에 해당하는 여러 파일이 파일 시스템에 생성됩니다.