라이브러리와 메타데이터
컴파일러가 외부 크레이트에 대한 참조를 만나면 해당 크레이트에 대한 몇 가지 정보를 불러와야 합니다. 이 장에서는 그 과정과 크레이트 라이브러리에 지원되는 파일 형식을 개괄적으로 설명합니다.
라이브러리
크레이트 의존성은 rlib, dylib, 또는 rmeta 파일에서 불러올 수 있습니다. 이 파일 형식들의 핵심은 rustc 고유의 메타데이터를 담고 있다는 점입니다. 이 메타데이터 덕분에 컴파일러는 외부 크레이트에 대해 충분한 정보를 파악하여, 그 크레이트에 어떤 항목이 들어 있는지, 어떤 매크로를 내보내는지 등 훨씬 더 많은 것을 이해할 수 있습니다.
rlib
rlib는 tar 파일과 비슷한 아카이브 파일입니다. 이 파일 형식은 rustc에 특화되어 있으며, 시간이 지나면서 바뀔 수 있습니다. 이 파일에는 다음이 들어 있습니다:
- 코드 생성의 결과물인 오브젝트 코드입니다. 이는 일반적인 링크 과정에서 사용됩니다. 각 코드 생성 단위마다 별도의
.o파일이 있습니다.-C linker-plugin-ltoCLI 옵션을 사용하면 코드 생성 단계를 건너뛸 수 있으며, 이 경우 각.o파일에는 LLVM 비트코드만 들어 있게 됩니다. - LLVM 중간 표현의 바이너리 형태인 LLVM 비트코드로,
.o파일 안에 하나의 섹션으로 포함됩니다. 이는 링크 타임 최적화(LTO)에 사용할 수 있습니다. LTO가 필요하지 않다면-C embed-bitcode=noCLI 옵션으로 이를 제거하여 컴파일 시간을 단축하고 디스크 공간을 줄일 수 있습니다. lib.rmeta라는 이름의 파일에 담긴rustc메타데이터입니다.- 심볼 테이블로, 본질적으로 각 심볼과 그 심볼을 포함하는 오브젝트 파일에 대한 오프셋을 나열한 목록입니다. 이는 아카이브 파일에서 꽤 일반적인 구성입니다.
dylib
dylib는 플랫폼별 공유 라이브러리입니다. 여기에는 .rustc라는 특수한 링크 섹션에 rustc 메타데이터가 포함되어 있습니다.
rmeta
rmeta 파일은 크레이트의 메타데이터를 담고 있는 사용자 정의 바이너리 형식입니다. 이 파일은 모든 코드 생성을 건너뛰어 프로젝트를 빠르게 “검사“하거나(cargo check에서처럼), 문서화에 필요한 충분한 정보를 수집하거나(cargo doc에서처럼), 파이프라이닝에 사용할 수 있습니다. 이 파일은 --emit=metadata CLI 옵션을 사용하면 생성됩니다.
rmeta 파일에는 컴파일된 오브젝트 파일이 들어 있지 않으므로 링크를 지원하지 않습니다.
메타데이터
메타데이터에는 매우 다양한 요소가 들어 있습니다. 이 가이드에서는 메타데이터에 들어 있는 필드를 하나하나 자세히 다루지는 않습니다. 메타데이터에 어떤 요소들이 들어 있는지 파악하려면 CrateRoot 정의를 살펴보시기 바랍니다. 메타데이터 인코딩과 디코딩에 관한 모든 내용은 rustc_metadata 패키지에 있습니다.
메타데이터에 들어 있는 주요 항목 몇 가지는 다음과 같습니다.
rustc컴파일러의 버전입니다. 컴파일러는 다른 버전에서 만들어진 파일을 로드하지 않습니다.- 엄격한 버전 해시(Strict Version Hash, SVH)입니다. 이는 올바른 의존성이 로드되도록 보장하는 데 도움이 됩니다.
- 안정 크레이트 ID(Stable Crate Id)입니다. 이는 크레이트를 식별하는 데 사용하는 해시입니다.
- 라이브러리에 포함된 모든 소스 파일에 관한 정보입니다. 이 정보는 의존성의 소스를 가리키는 진단 메시지 등 다양한 용도로 사용할 수 있습니다.
- 내보낸 매크로, 트레이트, 타입, 아이템에 관한 정보입니다. 일반적으로 경로가 크레이트 의존성 내부의 무언가를 참조할 때 알아야 하는 모든 정보가 여기에 해당합니다.
- 인코딩된 MIR입니다. 이 항목은 선택 사항이며 코드 생성에 필요한 경우에만 인코딩됩니다.
cargo check는 성능상의 이유로 이 항목을 건너뜁니다.
엄격한 버전 해시
엄격한 버전 해시(SVH, “크레이트 해시“라고도 합니다)는 올바른 크레이트 의존성이 로드되도록 보장하는 데 사용하는 64비트 해시입니다. 한 디렉터리에 같은 의존성을 서로 다른 설정이나 서로 다른 소스로 빌드한 사본이 여러 개 들어 있을 수 있습니다. 크레이트 로더는 SVH가 맞지 않는 크레이트를 모두 건너뜁니다.
SVH는 증분 컴파일 세션 파일 이름에도 사용되지만, 이는 대체로 과거의 흔적으로 남아 있는 용도입니다.
이 해시에는 다양한 요소가 포함됩니다.
- HIR 노드의 해시입니다.
- 모든 업스트림 크레이트의 해시입니다.
- 모든 소스 파일 이름입니다.
- 특정 명령줄 플래그의 해시(Stable Crate Id를 통한
-C metadata와 같은 플래그, 그리고[TRACKED]로 표시된 모든 CLI 옵션)입니다. 이 해시는 크레이트를 식별하는 데 사용됩니다.
해시가 실제로 계산되는 위치는 compute_hir_hash를 참고하십시오.
안정 크레이트 ID
StableCrateId는 이름이 같을 수도 있는 서로 다른 크레이트를 식별하는 데 사용하는 64비트 해시입니다. 이는 크레이트 이름과 모든 -C metadata CLI 옵션을 해시한 값으로, StableCrateId::new에서 계산됩니다. 이 해시는 심볼 이름 맹글링, 크레이트 로딩 등 다양한 곳에서 사용됩니다.
기본적으로 모든 Rust 심볼은 맹글링되며 안정 크레이트 ID를 포함합니다. 이를 통해 같은 크레이트의 여러 버전을 함께 포함할 수 있습니다. Cargo는 패키지 버전, 소스, 타깃 종류 등 다양한 요소를 바탕으로 -C metadata 해시를 자동으로 생성합니다(라이브러리와 테스트는 같은 크레이트 이름을 가질 수 있으므로 서로 구분해야 합니다).
크레이트 로딩
크레이트 로딩에는 미묘하게 복잡한 부분이 꽤 많습니다. 이름 해석 중에 외부 크레이트가 (extern crate나 경로를 통해) 참조되면, 해석기는 크레이트 라이브러리를 찾고 그 메타데이터를 로드하는 일을 맡은 CStore를 사용합니다. 의존성이 로드되면 CStore는 해석기가 작업(매크로 확장, 경로 해석 등)을 수행하는 데 필요한 정보를 제공합니다.
각 외부 크레이트를 로드하기 위해 CStore는 CrateLocator를 사용하여 특정 크레이트 하나에 대한 올바른 파일을 실제로 찾습니다. locator 모듈에는 로딩이 어떻게 동작하는지 자세히 설명하는 훌륭한 문서가 있으며, 전체 그림을 파악하려면 이 문서를 읽어 보시기를 강력히 권합니다.
의존성의 위치는 여러 곳에서 올 수 있습니다. 직접 의존성은 보통 --extern 플래그로 전달되며, 로더는 이를 직접 살펴볼 수 있습니다. 직접 의존성은 흔히 자기 자신의 의존성을 참조하며, 이들 역시 로드되어야 합니다. 이들은 보통 -L 플래그로 전달된 디렉터리를 탐색하여, 메타데이터에 일치하는 크레이트 이름과 SVH가 포함된 파일을 찾는 방식으로 발견됩니다. 로더는 의존성을 찾기 위해 sysroot도 살펴봅니다.
크레이트가 로드되면, 크레이트 메타데이터가 CrateMetadata 구조체로 감싸진 채 CStore에 보관됩니다. 이름 해석과 확장이 끝나면 CStore는 나머지 컴파일 과정을 위해 GlobalCtxt로 옮겨집니다.
파이프라이닝
컴파일 시간을 개선하는 한 가지 방법은 의존성의 메타데이터를 사용할 수 있게 되는 즉시 크레이트 빌드를 시작하는 것입니다. 라이브러리의 경우 의존성의 코드 생성이 끝날 때까지 기다릴 필요가 없습니다. Cargo는 rustc에 각 의존성에 대해 rlib뿐만 아니라 rmeta 파일도 생성하도록 지시하는 방식으로 이 기법을 구현합니다. rustc는 코드 생성 단계로 넘어가기 전에 가능한 한 일찍 rmeta 파일을 디스크에 저장합니다. 컴파일러는 JSON 메시지를 보내 빌드 도구에 가능하면 다음 크레이트 빌드를 시작할 수 있음을 알립니다.
크레이트 로딩 시스템은 rlib가 없거나 일부만 기록된 경우 rmeta 파일을 발견하면 그 파일을 사용할 줄 알 만큼 똑똑합니다.
바이너리의 경우에는 링크 단계에서 모든 의존성의 코드 생성이 필요하므로 이러한 파이프라이닝이 불가능합니다. 앞으로는 링크를 별도의 명령으로 분리하여 이 상황을 더욱 개선할 수 있을지도 모릅니다(#64191 참조).