rustc에서 MIR을 다룰 때 자주 등장하는 Layout 구조체를 손쉽게 출력해 디버깅하는 방법을 소개합니다. rustc_layout 속성을 활용해 타입의 메모리 표현(필드 오프셋, 정렬, ABI 등)을 확인하는 요령과 예시를 다룹니다.
이 글은 rustc 내부를 다루는 사람들을 위한 “공익 안내”입니다. 이걸 1년만 더 일찍 알았더라면 싶었고, 이 글이 이 기능을 더 널리 알리는 데 도움이 되길 바랍니다.
rustc에서 MIR을 다룰 때 자주 마주치는 핵심 자료구조 중 하나가 Layout (이전 이름은 LayoutDetails)이며, 보통은 TyAndLayout(이전의 TyLayout)으로 타입과 함께 다뤄집니다. 이 자료구조는 어떤 타입이 메모리에서 “어떻게 생겼는지”에 대한 모든 것을 설명합니다. 즉, 전체 타입의 크기와 정렬, 어떤 필드가 어느 오프셋에 있는지, enum 변형이 어떻게 표현되는지, enum 최적화를 위해 이 타입에서 사용할 수 있는 “니치(niche)”가 무엇인지 등을 담고 있습니다.
Layout은 꽤나 다재다능하고 해석이 어려울 수 있습니다. Miri를 디버깅할 때 저는 어떤 타입의 Layout이 정확히 어떻게 생겼는지, 혹은 Layout의 어떤 측면이 실제로는 정확히 무엇을 의미하는지 자주 알아야 했습니다. MIR 디버깅은 rustc --emit mir나 플레이그라운드의 “MIR” 버튼으로 간단하지만, Layout 디버깅은 훨씬 번거로웠습니다. 하지만 이제는 아닙니다. :)
해야 할 일은 플레이그라운드에 다음 코드를 입력하는 것뿐입니다:
#![feature(rustc_attrs)]
#[rustc_layout(debug)]
type T = (u8, u16);
(영구적으로) 불안정한 rustc_layout 속성을 이제 사용해 이 속성이 달린 타입(또는 struct/enum/union 정의)에 대한 정보를 덤프할 수 있습니다. 이 예에서는 다음과 같이 출력됩니다:
error: layout_of((u8, u16)) = Layout {
fields: Arbitrary {
offsets: [
Size {
raw: 0,
},
Size {
raw: 2,
},
],
memory_index: [
0,
1,
],
},
variants: Single {
index: 0,
},
abi: ScalarPair(
Scalar {
value: Int(
I8,
false,
),
valid_range: 0..=255,
},
Scalar {
value: Int(
I16,
false,
),
valid_range: 0..=65535,
},
),
largest_niche: None,
align: AbiAndPrefAlign {
abi: Align {
pow2: 1,
},
pref: Align {
pow2: 3,
},
},
size: Size {
raw: 4,
},
}
정보가 꽤 많지만, 이 타입에 대한 핵심 내용이 모두 들어 있습니다. 필드는 오프셋 0과 2에 있고, 타입의 정렬은 2(권장 정렬은 8)이며, 크기는 4입니다. 또한 Miri나 다른 함수에 인자를 전달할 때 중요한 ScalarPair ABI를 사용한다는 것도 볼 수 있습니다. 이 정보가 구체적으로 무엇을 의미하는지 더 알고 싶다면 Layout 타입 문서를 참고하세요.
Update: @jschievink의 제안 이후, 이제 이름이 붙은 불투명 타입의 내부 타입과 레이아웃도 출력할 수 있게 되었고, 이는 특히 제네레이터에 유용합니다. /Update
그러니 다음에 Layout을 다루면서 니치가 정확히 어떻게 표현되는지, 혹은 어떤 enum이 ScalarPair ABI를 가질 수 있는지(힌트: 됩니다) 궁금해진다면, 몇 가지 예시를 쉽게 확인해 rustc가 내부적으로 이 타입을 어떻게 생각하는지 살펴보세요. 이것은 사실상 타입 수준의 --emit mir에 해당합니다. 저는 예전부터 이 기능을 간절히 원했기에, 한동안은 rustc 디버그 트레이싱에 기반한 끔찍한 핵(hack)까지 작성해 사용했습니다. 아주 최근에야 rustc_layout 속성을 알게 되었고, 곧바로 모든 정보를 덤프할 수 있도록 이를 확장했습니다. 이제는 플레이그라운드에서 브라우저로 Layout을 디버깅할 수 있어 훨씬 편리해졌습니다. :D