Bitpacking the flag and data of an enum

Consider the following snippet:

enum Padding {
    Space,
    None,
}

enum Specifier {
    Day { padding: Padding },
    Year { padding: Padding },
}

fn main() {
    dbg!(std::mem::size_of::<Specifier>());
}

(Playground)

Output:

[src/main.rs:12] std::mem::size_of::<Specifier>() = 2

Is it feasible for the compiler to optimize Specifier to be equivalent to a C-like enum with four variants? If Specifier were pub in a library, it is conceivable that, without LTO, the user might want to obtain the obtain the inner variant directly, where a single internal representation would be presumed. With LTO or if the enum were defined in the end binary, all use cases would be known, so an optimizer should be able to "flatten" the enum, encompassing all possible combinations.

Obviously this example seems a bit contrived, but it's actually a reduced snippet from a rewrite I'm doing of the time crate's formatting and parsing mechanism. In reality, there is a Year variant on Specifier that is five bytes, despite containing less than one byte of information — the full enum has ~100 possible variants, given the number of variants in the anonymous struct fields.

As I recall from previous discussions, the top problem with this idea was that when you match on the outer enum, Rust guarantees that you can get a reference to the inner enum, and a reference points to a specific byte – it doesn't have a way to include the sub-byte packing information.

3 Likes

That's basically what I was referring to when I mentioned LTO and/or definition in the final binary. If all possible use cases are known, I'd think it's possible, no? Without LTO or when it's defined in a library (which admittedly is the situation I'm in), it isn't possible due to the compilation units.

Ah, so the idea is to cleverly notice if the final binary doesn't actually take a reference to the inner enum anywhere, and apply an optimization based on that? I can't think of a technical reason that wouldn't be possible, but it does seem like it could be very nonintuitive for users (e.g. "Why did the size of my enum change when I wrote some random code that refers to it...?")

5 Likes

That's exactly what I was thinking.

I could see it being unintuitive, but I suppose that's part of the discussion! I'm fairly certain most people don't guarantee the size of a certain struct, and a sizeable portion probably never even bother to check the size of them.

For sure. There's probably a lot of details to this that I haven't considered. I just wish I could find the previous threads about it, I can't remember the relevant search terms…

Optimizing layout of nested enums? and the related https://github.com/rust-lang/rfcs/issues/1230 share the same issues, although I don't see this particular suggestion. So far I can only think of "too weird to be really good ideas" suggestions for how to fix this issue. Better ergonomics around anonymous and/or _::Var enums might make this possible:

enum DayPadding {
    Space,
    None,
}

enum YearPadding {
    Space,
    None,
}

enum Specifier {
    Day { padding: DayPadding },
    Year { padding: YearPadding },
}

fn main() {
    dbg!(std::mem::size_of::<Specifier>());
}

Although that also prints

[src/main.rs:17] std::mem::size_of::<Specifier>() = 2

Which I guess is kind of a bug, but also a prerequisite for the OP to work ergonomic changes to help.

Your code is equivalent to:

enum DayPadding {
    Space = 0,
    None = 1,
}

enum YearPadding {
    Space = 0,
    None = 1,
}

enum Specifier {
    Day { padding: DayPadding },
    Year { padding: YearPadding },
}

Such an optimization would only be possible if the enums would use distinct sets of discriminants. Even then the current enum optimizations don't support this yet. There are currently only three ways to encode the discriminant: Fixed discriminant when there is only a single inhabited variant. Niche-filling when a single variant has data and a niche, which is then filled with all other dataless variants. And finally a tag with the discriminant value as fallback.

I drafted an RFC once describing a feature that would enable this.

5 Likes

bolzano-weierstrass

compact-fields

:laughing:

5 Likes