rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.31k stars 12.72k forks source link

Inefficient Match Statement Optimization for Unit-Only Enums with Fixed Offsets #129131

Open Shadowcat650 opened 2 months ago

Shadowcat650 commented 2 months ago

Description: The match statement in the provided Rust code generates a jump table with -O3 optimizations, despite the fact that the discriminant values for the enum variants have a fixed offset from their target characters. This results in suboptimal code generation when a more efficient approach is possible.

Reproduction:

use std::fmt::{Display, Formatter};

#[derive(Copy, Clone)]
enum Letter {
    A,
    B,
    C,
}

impl Display for Letter {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let letter = match self {
            Letter::A => 'a',
            Letter::B => 'b',
            Letter::C => 'c',
        };
        write!(f, "{}", letter)
    }
}

Expected Behavior: Since each discriminant value of the Letter enum is exactly 97 units away from the corresponding target characters in the match statement, the compiler should ideally optimize this code to use a direct calculation rather than generating a jump table.

Example of Hand-Optimized Code:

use std::fmt::{Display, Formatter};

#[derive(Copy, Clone)]
enum Letter {
    A,
    B,
    C,
}

impl Display for Letter {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let letter = ((*self as u8) + 97) as char;
        write!(f, "{}", letter)
    }
}

Compiler Explorer Links:

Additional Notes: Although manual optimization achieves the desired performance, automatic optimization by the compiler is preferred to ensure both efficiency and maintainability of the code.

theemathas commented 2 months ago

Minimized:

enum Letter {
    A = 0,
    B = 1,
    C = 2,
}

#[no_mangle]
fn bad(x: Letter, f: fn(&u8)) {
    let y = match x {
        Letter::A => 4,
        Letter::B => 5,
        Letter::C => 6,
    };
    f(&y);
}

#[no_mangle]
fn good(x: Letter, f: fn(&u8)) {
    let y = x as u8 + 4;
    f(&y);
}

View on Compiler Explorer

nikic commented 2 months ago

Upstream issue: https://github.com/llvm/llvm-project/issues/104567

nikic commented 2 months ago

Fixed upstream for LLVM 20.