Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

enum

Source

Initialize a new workspace with cargo init --lib.

#![allow(unused)]
fn main() {
pub enum Color {
    Red,
    Green,
    Blue,
}

#[unsafe(no_mangle)]
pub fn u32_to_color(value: u32) -> Color {
    match value {
        0 => Color::Red,
        1 => Color::Green,
        2 => Color::Blue,
        _ => Color::Red,
    }
}

#[unsafe(no_mangle)]
pub fn simple_enum_match(color: Color) -> u8 {
    match color {
        Color::Red => 0,
        Color::Green => 1,
        Color::Blue => 2,
    }
}
}
#![allow(unused)]
fn main() {
pub enum BasicShape {
    Circle(i32),
    Point,
}

#[unsafe(no_mangle)]
pub fn basic_shape_match(shape: BasicShape) -> i32 {
    match shape {
        BasicShape::Circle(radius) => radius * radius,
        BasicShape::Point => 0,
    }
}

#[unsafe(no_mangle)]
pub fn make_basic_circle(radius: i32) -> BasicShape {
    BasicShape::Circle(radius)
}

#[unsafe(no_mangle)]
pub fn make_basic_point() -> BasicShape {
    BasicShape::Point
}
}
#![allow(unused)]
fn main() {
pub enum Shape {
    Circle(i32),
    Rectangle(i32, i32),
    Point,
}

#[unsafe(no_mangle)]
pub fn enum_with_data_match(shape: Shape) -> i32 {
    match shape {
        Shape::Circle(radius) => radius * radius,
        Shape::Rectangle(width, height) => width * height,
        Shape::Point => 0,
    }
}

#[unsafe(no_mangle)]
pub fn make_circle(radius: i32) -> Shape {
    Shape::Circle(radius)
}

#[unsafe(no_mangle)]
pub fn make_rectangle(w: i32, h: i32) -> Shape {
    Shape::Rectangle(w, h)
}

#[unsafe(no_mangle)]
pub fn make_point() -> Shape {
    Shape::Point
}
}

The enum type is described in the official docs in detail. We are using no_mangle to simplify things, the reason can be found here. The source code is split into 3 parts:

Note: for this analysis the unit-only and data-carrying types have been chosen as they are the most common ones.

Build

$ cargo rustc --release -- --emit obj

Ghidra

Load the .o file (located at target/aarch64-unknown-linux-musl/release/deps/) into Ghidra and auto-analyze it.

Layout

This description provides a good overview of the enum types and their layouts (even the ones we will not discuss such as empty enum and enum with a single variant). enums are also called tagged unions and their layout is unspecified, unless you use #[repr(...)].

Still, we will see that in our examples an enum is either represented by a discriminant only or a discriminant plus the data/payload.

Unit-only enum

#![allow(unused)]
fn main() {
pub enum Color {
    Red,
    Green,
    Blue,
}

#[unsafe(no_mangle)]
pub fn u32_to_color(value: u32) -> Color {
    match value {
        0 => Color::Red,
        1 => Color::Green,
        2 => Color::Blue,
        _ => Color::Red,
    }
}

#[unsafe(no_mangle)]
pub fn simple_enum_match(color: Color) -> u8 {
    match color {
        Color::Red => 0,
        Color::Green => 1,
        Color::Blue => 2,
    }
}
}

Since the layout is unstable, there is no fixed size for the enum, although the compiler will typically choose the smallest representation possible. In this case it is u8 or i8.

$ cargo rustc --release --quiet -- -Z print-type-sizes       
print-type-size type: `Color`: 1 bytes, alignment: 1 bytes
print-type-size     discriminant: 1 bytes
print-type-size     variant `Red`: 0 bytes
print-type-size     variant `Green`: 0 bytes
print-type-size     variant `Blue`: 0 bytes

If we check simple_enum_match(), we can see that it just forwards the input value (0/1/2) to the output (from w0 to w0, so effectively it does nothing), meaning that the enum uses the same values for its discriminants. u32_to_color() behaves the same, with the difference that it also handles the default case which makes the code a bit more complicated.

Listings:

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined simple_enum_match()
             undefined         <UNASSIGNED>   <RETURN>
                             simple_enum_match                               XREF[3]:     Entry Point(*), 001000c0(*), 
                                                                                          _elfSectionHeaders::00000110(*)  
        00100014 c0 03 5f d6     ret

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined u32_to_color()
             undefined         <UNASSIGNED>   <RETURN>
                             u32_to_color                                    XREF[4]:     Entry Point(*), 001000ac(*), 
                                                                                          _elfSectionHeaders::00000090(*), 
                                                                                          _elfSectionHeaders::000000d0(*)  
        00100000 1f 04 00 71     cmp        w0,#0x1
                             set w8=1 if input==1, else w8=0
        00100004 e8 17 9f 1a     cset       w8,eq
        00100008 1f 08 00 71     cmp        w0,#0x2
                             if input==2, keep input, else use w8
        0010000c 00 00 88 1a     csel       w0,w0,w8,eq
        00100010 c0 03 5f d6     ret

Data-carrying enum (largest variant: tuple with 1 field)

#![allow(unused)]
fn main() {
pub enum BasicShape {
    Circle(i32),
    Point,
}

#[unsafe(no_mangle)]
pub fn basic_shape_match(shape: BasicShape) -> i32 {
    match shape {
        BasicShape::Circle(radius) => radius * radius,
        BasicShape::Point => 0,
    }
}

#[unsafe(no_mangle)]
pub fn make_basic_circle(radius: i32) -> BasicShape {
    BasicShape::Circle(radius)
}

#[unsafe(no_mangle)]
pub fn make_basic_point() -> BasicShape {
    BasicShape::Point
}
}
$ cargo rustc --release --quiet -- -Z print-type-sizes
print-type-size type: `BasicShape`: 8 bytes, alignment: 4 bytes
print-type-size     discriminant: 4 bytes
print-type-size     variant `Circle`: 4 bytes
print-type-size         field `.0`: 4 bytes
print-type-size     variant `Point`: 0 bytes

In case the largest data-carrying variant is a tuple with 1 field, the compiler chooses to pass the discriminant and data via 2 registers (w0: discriminant and w1: data). A discriminant of 0 means Circle while 1 means Point.

Listings:

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined basic_shape_match()
             undefined         <UNASSIGNED>   <RETURN>
                             basic_shape_match                               XREF[3]:     Entry Point(*), 00100124(*), 
                                                                                          _elfSectionHeaders::00000250(*)  
        0010006c 28 7c 01 1b     mul        w8,w1,w1
                             check discriminant
        00100070 1f 00 00 72     tst        w0,#0x1
                             if discriminant != 0, return 0, else return w8
        00100074 e0 13 88 1a     csel       w0,wzr,w8,ne
        00100078 c0 03 5f d6     ret
                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined make_basic_circle()
             undefined         <UNASSIGNED>   <RETURN>
                             make_basic_circle                               XREF[3]:     Entry Point(*), 00100138(*), 
                                                                                          _elfSectionHeaders::00000290(*)  
        0010007c e1 03 00 2a     mov        w1,w0
        00100080 e0 03 1f 2a     mov        w0,wzr
        00100084 c0 03 5f d6     ret
                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined make_basic_point()
             undefined         <UNASSIGNED>   <RETURN>
                             make_basic_point                                XREF[3]:     Entry Point(*), 0010014c(*), 
                                                                                          _elfSectionHeaders::000002d0(*)  
        00100088 20 00 80 52     mov        w0,#0x1
        0010008c c0 03 5f d6     ret

Data-carrying enum (largest variant: tuple with 2 fields)

#![allow(unused)]
fn main() {
pub enum Shape {
    Circle(i32),
    Rectangle(i32, i32),
    Point,
}

#[unsafe(no_mangle)]
pub fn enum_with_data_match(shape: Shape) -> i32 {
    match shape {
        Shape::Circle(radius) => radius * radius,
        Shape::Rectangle(width, height) => width * height,
        Shape::Point => 0,
    }
}

#[unsafe(no_mangle)]
pub fn make_circle(radius: i32) -> Shape {
    Shape::Circle(radius)
}

#[unsafe(no_mangle)]
pub fn make_rectangle(w: i32, h: i32) -> Shape {
    Shape::Rectangle(w, h)
}

#[unsafe(no_mangle)]
pub fn make_point() -> Shape {
    Shape::Point
}
}
$ cargo rustc --release --quiet -- -Z print-type-sizes
print-type-size type: `Shape`: 12 bytes, alignment: 4 bytes
print-type-size     discriminant: 4 bytes
print-type-size     variant `Rectangle`: 8 bytes
print-type-size         field `.0`: 4 bytes
print-type-size         field `.1`: 4 bytes
print-type-size     variant `Circle`: 4 bytes
print-type-size         field `.0`: 4 bytes
print-type-size     variant `Point`: 0 bytes

In case the largest data-carrying variant is a tuple with 2 fields, the compiler chooses to pass the discriminant and data via 1 register (w0), which holds a pointer to the enum's memory location where the struct begins with the discriminant (here: offset 0), then contains the remaining fields associated with that variant (here: offset 4). A discriminant of 0 means Circle, 1 means Rectangle and 2 means Point.

Note: x8 is the indirect result register according to the AAPCS64. It is also explained here:

XR (X8) is a pointer to the memory allocated by the caller for returning the struct.

Listings:

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined enum_with_data_match()
             undefined         <UNASSIGNED>   <RETURN>
                             enum_with_data_match                            XREF[3]:     Entry Point(*), 001000d4(*), 
                                                                                          _elfSectionHeaders::00000150(*)  
        00100018 08 00 40 b9     ldr        w8,[x0]
        0010001c c8 00 00 34     cbz        w8,LAB_00100034
        00100020 1f 05 00 71     cmp        w8,#0x1
        00100024 e1 00 00 54     b.ne       LAB_00100040
                             rectangle
        00100028 08 a4 40 29     ldp        w8,w9,[x0, #0x4]
        0010002c 20 7d 08 1b     mul        w0,w9,w8
        00100030 c0 03 5f d6     ret
                             circle
                             LAB_00100034                                    XREF[1]:     0010001c(j)  
        00100034 08 04 40 b9     ldr        w8,[x0, #0x4]
        00100038 00 7d 08 1b     mul        w0,w8,w8
        0010003c c0 03 5f d6     ret
                             point
                             LAB_00100040                                    XREF[1]:     00100024(j)  
        00100040 e0 03 1f 2a     mov        w0,wzr
        00100044 c0 03 5f d6     ret
                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined make_circle()
             undefined         <UNASSIGNED>   <RETURN>
                             make_circle                                     XREF[3]:     Entry Point(*), 001000e8(*), 
                                                                                          _elfSectionHeaders::00000190(*)  
        00100048 1f 01 00 29     stp        wzr,w0,[x8]
        0010004c c0 03 5f d6     ret
                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined make_rectangle()
             undefined         <UNASSIGNED>   <RETURN>
                             make_rectangle                                  XREF[3]:     Entry Point(*), 001000fc(*), 
                                                                                          _elfSectionHeaders::000001d0(*)  
        00100050 29 00 80 52     mov        w9,#0x1
        00100054 00 85 00 29     stp        w0,w1,[x8, #0x4]
        00100058 09 01 00 b9     str        w9,[x8]
        0010005c c0 03 5f d6     ret
                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined make_point()
             undefined         <UNASSIGNED>   <RETURN>
                             make_point                                      XREF[3]:     Entry Point(*), 00100110(*), 
                                                                                          _elfSectionHeaders::00000210(*)  
        00100060 49 00 80 52     mov        w9,#0x2
        00100064 09 01 00 b9     str        w9,[x8]
        00100068 c0 03 5f d6     ret