Skip to content

Large match statements that compile down to small assembly don't get inlined automatically #118921

@pozix604

Description

@pozix604

It seems that function inlining heuristics fail in the following use case and does not inline when it should. Given huge match statement:

pub enum XXX {
    A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1, L1, M1, N1, O1, P1, Q1, R1, S1, T1, U1, V1, W1, X1, Y1, Z1,
    A2, B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, L2, M2, N2, O2, P2, Q2, R2, S2, T2, U2, V2, W2, X2, Y2, Z2,
}

impl std::convert::TryFrom<u8> for XXX {
    type Error = ();
    fn try_from(x: u8) -> Result<Self, Self::Error> {
            match () {
                    _ if x == XXX::A1 as u8 => Ok(XXX::A1),
                    _ if x == XXX::B1 as u8 => Ok(XXX::B1),
                    _ if x == XXX::C1 as u8 => Ok(XXX::C1),
                    _ if x == XXX::D1 as u8 => Ok(XXX::D1),
                    _ if x == XXX::E1 as u8 => Ok(XXX::E1),
                    _ if x == XXX::F1 as u8 => Ok(XXX::F1),
                    _ if x == XXX::G1 as u8 => Ok(XXX::G1),
                    _ if x == XXX::H1 as u8 => Ok(XXX::H1),
                    _ if x == XXX::I1 as u8 => Ok(XXX::I1),
                    _ if x == XXX::J1 as u8 => Ok(XXX::J1),
                    _ if x == XXX::K1 as u8 => Ok(XXX::K1),
                    _ if x == XXX::L1 as u8 => Ok(XXX::L1),
                    _ if x == XXX::M1 as u8 => Ok(XXX::M1),
                    _ if x == XXX::N1 as u8 => Ok(XXX::N1),
                    _ if x == XXX::O1 as u8 => Ok(XXX::O1),
                    _ if x == XXX::P1 as u8 => Ok(XXX::P1),
                    _ if x == XXX::Q1 as u8 => Ok(XXX::Q1),
                    _ if x == XXX::R1 as u8 => Ok(XXX::R1),
                    _ if x == XXX::S1 as u8 => Ok(XXX::S1),
                    _ if x == XXX::T1 as u8 => Ok(XXX::T1),
                    _ if x == XXX::U1 as u8 => Ok(XXX::U1),
                    _ if x == XXX::V1 as u8 => Ok(XXX::V1),
                    _ if x == XXX::W1 as u8 => Ok(XXX::W1),
                    _ if x == XXX::X1 as u8 => Ok(XXX::X1),
                    _ if x == XXX::Y1 as u8 => Ok(XXX::Y1),
                    _ if x == XXX::Z1 as u8 => Ok(XXX::Z1),
                    _ if x == XXX::A2 as u8 => Ok(XXX::A2),
                    _ if x == XXX::B2 as u8 => Ok(XXX::B2),
                    _ if x == XXX::C2 as u8 => Ok(XXX::C2),
                    _ if x == XXX::D2 as u8 => Ok(XXX::D2),
                    _ if x == XXX::E2 as u8 => Ok(XXX::E2),
                    _ if x == XXX::F2 as u8 => Ok(XXX::F2),
                    _ if x == XXX::G2 as u8 => Ok(XXX::G2),
                    _ if x == XXX::H2 as u8 => Ok(XXX::H2),
                    _ if x == XXX::I2 as u8 => Ok(XXX::I2),
                    _ if x == XXX::J2 as u8 => Ok(XXX::J2),
                    _ if x == XXX::K2 as u8 => Ok(XXX::K2),
                    _ if x == XXX::L2 as u8 => Ok(XXX::L2),
                    _ if x == XXX::M2 as u8 => Ok(XXX::M2),
                    _ if x == XXX::N2 as u8 => Ok(XXX::N2),
                    _ if x == XXX::O2 as u8 => Ok(XXX::O2),
                    _ if x == XXX::P2 as u8 => Ok(XXX::P2),
                    _ if x == XXX::Q2 as u8 => Ok(XXX::Q2),
                    _ if x == XXX::R2 as u8 => Ok(XXX::R2),
                    _ if x == XXX::S2 as u8 => Ok(XXX::S2),
                    _ if x == XXX::T2 as u8 => Ok(XXX::T2),
                    _ if x == XXX::U2 as u8 => Ok(XXX::U2),
                    _ if x == XXX::V2 as u8 => Ok(XXX::V2),
                    _ if x == XXX::W2 as u8 => Ok(XXX::W2),
                    _ if x == XXX::X2 as u8 => Ok(XXX::X2),
                    _ if x == XXX::Y2 as u8 => Ok(XXX::Y2),
                    _ if x == XXX::Z2 as u8 => Ok(XXX::Z2),
                    _ => Err(()),
                }
        }
}

Rust does a great job of boiling it down:

<XXX as core::convert::TryFrom<u8>>::try_from:
        cmp dil, 52
        mov eax, 52
        cmovb eax, edi
        ret

But it doesn't inline this properly when used in other functions:

impl XXX {
    pub fn new(x: u8) -> Self {
        Self::try_from(x).expect("bad num")
    }
}

generates:

XXX::new:
        push rax
        call qword ptr [rip + <XXX as core::convert::TryFrom<u8>>::try_from@GOTPCREL]
        cmp al, 52
        je .LBB139_1
        pop rcx
        ret

An explicit #[inline] given to try_from results in this instead:

XXX::new:
        cmp dil, 52
        jae .LBB139_1
        mov eax, edi
        ret

rustc --version --verbose:

rustc 1.74.1 (a28077b28 2023-12-04)
binary: rustc
commit-hash: a28077b28a02b92985b3a3faecf92813155f1ea1
commit-date: 2023-12-04
host: x86_64-unknown-linux-gnu
release: 1.74.1
LLVM version: 17.0.4

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.C-bugCategory: This is a bug.I-heavyIssue: Problems and improvements with respect to binary size of generated code.I-slowIssue: Problems and improvements with respect to performance of generated code.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions