You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Enable optimizing repr(Rust) enum layout by omitting the tag in uninhabited variants in the tagged and niche-filling layouts, and omitting the tag entirely if there is only one inhabited variant.
Current layout algorithm for repr(Rust) enums
Definition: An "absent" variant is a variant that is uninhabited and has only 1-aligned 0-sized ("1-ZST") fields. A "present" variant is not "absent".
First, if an enum has no "present" variant(s), then it has the same layout as !, with no tag stored.
Second, if an enum has only a single "present" variant, then it has the layout of that variant interpreted as a struct, with no tag stored.
Otherwise, we compute two possible layouts: the tagged layout and the niche-filling layout.
The tagged layout is the "normal" enum layout, where there is an integer tag field encoding the discriminant at offset 0, and the variants fields are laid out after the tag.
The niche-filling layout chooses one variant whose struct-like layout is the largest and has a niche with enough available values for the number of remaining present variants, and where there is enough space before or after the niche field to layout the other present variants struct-like layouts. If there is no niche in the largest variant, the niche-filling layout is not computed and the tagged layout is chosen.
The smaller of these is chosen. If they are the same size, the one with the larger niche is chosen. If they have same niche size, the tagged layout is chosen (as it is simpler).
Proposed changes
If all variants are uninhabited, return an uninhabited layout with large enough size/alignment for the variants, but no tag or fields of its own. The variants' layouts (needed for offset_of!) are their struct-like layouts.
In the tagged layout, do not reserve space for the tag field in uninhabited variants (allow uninhabited variants' struct-like layouts to overlap the tag field).
In the niche-filling layout, do not reserve space for the niche field in uninhabited variants (allow uninhabited variants' struct-like layouts to overlap the niche field). If there are multiple largest variants, prefer an inhabited one as the niche-filled variant.
Compute a third layout: the no-tag layout: If there is exactly one inhabited variant, compute the struct-like layout of that variant (with no tag), and pad it to the size and alignment required to fit each other (uninhabited) variant's struct-like layout. That will be the layout of the enum as a whole. If there is not exactly one inhabited variant, this layout is not computed.
If multiple valid layouts are computed, the smallest is chosen. If multiple are the smallest, and the niche-filling layout's niche covers the whole size of the enum, it is preferred (to preserve the guaranteed-NPO optimization). Otherwise, if multiple are the smallest, the layout with the largest niche is chosen. If multiple have the largest niche, the no-tag layout is preferred over the tagged layout, and the tagged layout is preferred over the niche-filling layout.
As a consequence of these changes (specifically changes 1 and 4), the compiler will need to keep track of the layouts of uninhabited variants even when a tag is not stored (Variants::Empty for change 1 and Variants::Single for change 4), so it knows the offsets of fields in such variants for offset_of!. This was not required previously because currently Variants::Empty and Variants::Single are used when only "absent" variants are omitted, and those have only 1-ZST fields which can all be at offset 0. (See also here and here for discussion of possible future optimizations that omit uninhabited variants from layout computation entirely. Nothing related to that is proposed here; under this MCP, there is still always space for the fields of an enum, even if they are in an uninhabited variant).
I propose making all of these changes, but some of them are not strictly required to be implemented together. Specifically: 1 and 4 each require 5; 2 requires 11; 3 does not require any other.
Examples:
Layout examples
enumFoo{A{a:u8,never: ! },B{b:u8,never: ! },}
Currently laid out:
Variant
Byte 0
Byte 1
A (uninhabited)
tag=0
a
B (uninhabited)
tag=1
b
With the first change:
Variant
Byte 0
A (uninhabited)
a
B (uninhabited)
b
enumFoo{A,B{b:u8,never: ! }}
Currently laid out:
Variant
Byte 0
Byte 1
A
tag=0
padding
B (uninhabited)
tag=1
b
With the second change:
Variant
Byte 0
A
tag=0
B (uninhabited)
b
(This enum is not affected by the fourth change, since the tagged layout is preferred over the no-tag layout for its niche in this case)
This enum is subject to guaranteed-NPO, so it's layout must not change.
I have a WIP implementation at rust-lang/rust#145337 (the PR description is outdated). The changes are implemented in a different order than they are described in this MCP: change 5 is the first commit ("Prepare..."), change 1 and 2 are the fifth commit ("Don't encode..."), change 4 is the sixth commit ("Do not store tag..."), change 3 is the seventh commit ("Do not hold space...").
Mentors or Reviewers
If you have a reviewer or mentor in mind for this work, mention them here. You can put your own name here if you are planning to mentor the work.
or some other handling of the all-uninhabited-variant edge-case; without it enum Foo { A(Aligned2Never), B(Aligned2Never) } got a size-0 layout with an i16 tag field, which I assume would cause problems. ↩
Proposal
Enable optimizing
repr(Rust)enum layout by omitting the tag in uninhabited variants in the tagged and niche-filling layouts, and omitting the tag entirely if there is only one inhabited variant.Current layout algorithm for
repr(Rust)enumsDefinition: An "absent" variant is a variant that is uninhabited and has only 1-aligned 0-sized ("1-ZST") fields. A "present" variant is not "absent".
First, if an enum has no "present" variant(s), then it has the same layout as
!, with no tag stored.Second, if an enum has only a single "present" variant, then it has the layout of that variant interpreted as a
struct, with no tag stored.Otherwise, we compute two possible layouts: the tagged layout and the niche-filling layout.
The tagged layout is the "normal" enum layout, where there is an integer tag field encoding the discriminant at offset 0, and the variants fields are laid out after the tag.
The niche-filling layout chooses one variant whose struct-like layout is the largest and has a niche with enough available values for the number of remaining present variants, and where there is enough space before or after the niche field to layout the other present variants struct-like layouts. If there is no niche in the largest variant, the niche-filling layout is not computed and the tagged layout is chosen.
The smaller of these is chosen. If they are the same size, the one with the larger niche is chosen. If they have same niche size, the tagged layout is chosen (as it is simpler).
Proposed changes
If all variants are uninhabited, return an uninhabited layout with large enough size/alignment for the variants, but no tag or fields of its own. The variants' layouts (needed for
offset_of!) are their struct-like layouts.In the tagged layout, do not reserve space for the tag field in uninhabited variants (allow uninhabited variants' struct-like layouts to overlap the tag field).
In the niche-filling layout, do not reserve space for the niche field in uninhabited variants (allow uninhabited variants' struct-like layouts to overlap the niche field). If there are multiple largest variants, prefer an inhabited one as the niche-filled variant.
Compute a third layout: the no-tag layout: If there is exactly one inhabited variant, compute the struct-like layout of that variant (with no tag), and pad it to the size and alignment required to fit each other (uninhabited) variant's struct-like layout. That will be the layout of the enum as a whole. If there is not exactly one inhabited variant, this layout is not computed.
If multiple valid layouts are computed, the smallest is chosen. If multiple are the smallest, and the niche-filling layout's niche covers the whole size of the enum, it is preferred (to preserve the guaranteed-NPO optimization). Otherwise, if multiple are the smallest, the layout with the largest niche is chosen. If multiple have the largest niche, the no-tag layout is preferred over the tagged layout, and the tagged layout is preferred over the niche-filling layout.
As a consequence of these changes (specifically changes 1 and 4), the compiler will need to keep track of the layouts of uninhabited variants even when a tag is not stored (
Variants::Emptyfor change 1 andVariants::Singlefor change 4), so it knows the offsets of fields in such variants foroffset_of!. This was not required previously because currentlyVariants::EmptyandVariants::Singleare used when only "absent" variants are omitted, and those have only 1-ZST fields which can all be at offset 0. (See also here and here for discussion of possible future optimizations that omit uninhabited variants from layout computation entirely. Nothing related to that is proposed here; under this MCP, there is still always space for the fields of an enum, even if they are in an uninhabited variant).I propose making all of these changes, but some of them are not strictly required to be implemented together. Specifically: 1 and 4 each require 5; 2 requires 11; 3 does not require any other.
Examples:
Layout examples
Currently laid out:
abWith the first change:
abCurrently laid out:
bWith the second change:
b(This enum is not affected by the fourth change, since the tagged layout is preferred over the no-tag layout for its niche in this case)
Currently laid out:
abWith the second (and not fourth) change:
abWith the fourth change:
abCurrently laid out:
a.0a.1a.2bpaddingpaddingxypaddingWith the third change:
a.0a.1(niche)a.2b0x02(nichea.1)paddingxypaddingLaid out currently and with these changes:
a.0(niche)a.0)This enum is subject to guaranteed-NPO, so it's layout must not change.
I have a WIP implementation at rust-lang/rust#145337 (the PR description is outdated). The changes are implemented in a different order than they are described in this MCP: change 5 is the first commit ("Prepare..."), change 1 and 2 are the fifth commit ("Don't encode..."), change 4 is the sixth commit ("Do not store tag..."), change 3 is the seventh commit ("Do not hold space...").
Mentors or Reviewers
If you have a reviewer or mentor in mind for this work, mention them here. You can put your own name here if you are planning to mentor the work.
Process
The main points of the Major Change Process are as follows:
@rustbot secondor kickoff a team FCP with@rfcbot fcp $RESOLUTION.You can read more about Major Change Proposals on forge.
Caution
Concerns (1 active)
option-layout-guaranteesresolved in this commentManaged by
@rustbot—see help for details.Footnotes
or some other handling of the all-uninhabited-variant edge-case; without it
enum Foo { A(Aligned2Never), B(Aligned2Never) }got a size-0 layout with ani16tag field, which I assume would cause problems. ↩