Skip to content

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

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

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.llvm-fixed-upstreamIssue expected to be fixed by the next major LLVM upgrade, or backported fixes

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions