Apr 4, 2020 • RustEditsPermalink

Debugging rustc type layouts

This post is a “public service announcement” for people working on the guts of rustc. I wish I had known about this a year ago, so I hope this post can make this feature more widely known.

When working with MIR in rustc, one key data structure that comes up a lot is Layout (formerly LayoutDetails), usually paired up with a type in a TyAndLayout (formerly TyLayout). This data structure describes everything that there is to know about how a type “looks like” in memory: size and alignment of the entire type, at which offset we can find which field, how enum variants are represented, which “niche” can be used in this type to optimize enums.

Layout is quite versatile and can be hard to interpret, and when debugging Miri I regularly have to know what exactly the Layout of a certain type looks like or what exactly some aspect of Layout actually means in practice. While debugging MIR is easy via rustc --emit mir or the “MIR” button on the playground, debugging Layout was much more tedious. But not any more. :)

All you have to do is enter the following code in the playground:

#![feature(rustc_attrs)]

#[rustc_layout(debug)]
type T = (u8, u16);

The (permanently) unstable rustc_layout attribute can now be used to dump some information about the type it is attached to (also works with struct/enum/union definitions). In this case, it prints:

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,
    },
}

That is quite a lot, but it contains all the key information about this type: the fields are at offsets 0 and 2, the type has alignment 2 (but preferred alignment 8) and size 4. We can also see that it uses the ScalarPair abi which is relevant for Miri and when passing data as arguments to another function. To learn more about what all this information means, see the Layout type docs.

Update: After a suggestions by @jschievink, this can now also be used to print the underlying type and layout of named opaque types, which is particularly useful for generators. /Update

So the next time you work with Layout and wonder how exactly the niche gets represented, or whether an enum can have ScalarPair abi (hint: yes it can), you can easily look at a few examples to see how rustc thinks about this type internally. This is basically the type-level equivalent of --emit mir. I have wanted this since forever, so much that some time ago I wrote an awful hack for this based on rustc debug tracing. Only very recently did I learn about the rustc_layout attribute and then I had to immediately extend it to support dumping all the information. Now Layout can be debugged in the browser on the playground, which is so much more convenient. :D

Posted on Ralf's Ramblings on Apr 4, 2020.
Comments? Drop me a mail or leave a note in the forum!