rust-lang / rust-lang/rust

Missed `match` optimization of riscv

Open
#136,216 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-codegen A-LLVM C-optimization I-slow T-compiler
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

I was code golfing different implementations for a function that "inverts" (rotates by 2) an enum with discriminants 1, 2, 4, 8 (i.e. f(1) = 4), the obvious implementation being this:

pub enum Side {
    Top = 1 << 0,
    Right = 1 << 1,
    Bottom = 1 << 2,
    Left = 1 << 3,
}

pub fn opposite_match(x: Side) -> Side {
    use Side::*;
    match x {
        Top => Bottom,
        Right => Left,
        Bottom => Top,
        Left => Right,
    }
}

I expected to see this happen: on riscv opposite_match produces the optimal code, or at least a reasonable one.

Instead, this happened: compiler generates the following asm:

opposite_match:
        addi    a0, a0, -1
        slli    a0, a0, 24
        srai    a0, a0, 24
        lui     a1, %hi(.Lswitch.table.opposite_match)
        addi    a1, a1, %lo(.Lswitch.table.opposite_match)
        add     a0, a1, a0
        lbu     a0, 0(a0)

example::table_lookup::T::h07c70895e308d45b:
        .ascii  "\004\b\004\001\004\004\004\002"

The problem is the slli (shift left logical immediate) and srai (shift right arithmetic immediate) instructions -- those together are noop, since a0 <= 7 at that point and 7 << 24 < 1.rotate_right(1).

The underlying issue is that llvm at some point replaces indexing by u8 with indexing by u32, inserting a sext in the process; sext is not getting optimized out afterwards and is later lowered to ssli+srai. LLVM issue: https://github.com/llvm/llvm-project/issues/124841.

If you write the lookup table by hand compiler generates better asm:

pub fn table_lookup(x: Side) -> Side {
    static T: [Side; 8] = [
        Side::Bottom, // <--
        Side::Left, // <--
        Side::Bottom,
        Side::Top, // <--
        Side::Bottom,
        Side::Bottom,
        Side::Bottom,
        Side::Right, // <--
    ];
    T[x as usize - 1]
}
example::table_lookup::h8d56712109e87652:
        lui     a1, %hi(example::table_lookup::T::h07c70895e308d45b)
        addi    a1, a1, %lo(example::table_lookup::T::h07c70895e308d45b)
        add     a0, a1, a0
        lbu     a0, -1(a0)
        ret

example::table_lookup::T::h07c70895e308d45b:
        .ascii  "\004\b\004\001\004\004\004\002"

There are no shifts and the -1 is merged into the the lbu (load byte unsigned).

Godbolt link.

Meta

rustc version:

1.86.0-nightly (2025-01-27 2f348cb7ce4063fa4eb4)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the opposite_match and table_lookup examples and the linked Godbolt reproduction on RISC-V. Investigate the reported LLVM u8 indexing and sext optimization issue, then compare generated assembly for the two functions. Done means the redundant shift pair is eliminated from the match implementation.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.