rust-lang / rust-lang/rust

Poor codegen for derived `==` on simple 2-field struct

Open
#117,800 4 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

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

Description

Given a basic struct like this,

#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Entity {
    g: u32,
    i: u32
}

The generated == is suboptimal:

#[no_mangle]
pub fn derived_eq(x: &Entity, y: &Entity) -> bool {
    x == y
}
derived_eq:
        movq    xmm0, qword ptr [rdi]
        movq    xmm1, qword ptr [rsi]
        pcmpeqd xmm1, xmm0
        pshufd  xmm0, xmm1, 80
        movmskpd        eax, xmm0
        cmp     eax, 3
        sete    al
        ret

https://rust.godbolt.org/z/1b1xsnzx6

For comparison, not using short-circuiting

#[no_mangle]
pub fn good_eq(x: &Entity, y: &Entity) -> bool {
    (x.g == y.g) & (x.i == y.i)
}

gives a much-simpler codegen

good_eq:
        mov     rax, qword ptr [rsi]
        cmp     qword ptr [rdi], rax
        sete    al
        ret

This appears to be related to LLVM not knowing whether the second field is poison, as Alive2 confirms that LLVM isn't allowed to convert the former into the latter (at least for the optimized forms): https://alive2.llvm.org/ce/z/bAsJGN

Is there maybe some metadata we could put on the parameter attributes to tell LLVM that reading them isn't poison? It appears that just reading them first, like (same godbolt link above)

#[no_mangle]
pub fn failed_workaround(x: &Entity, y: &Entity) -> bool {
    let Entity { g: g1, i: i1 } = *x;
    let Entity { g: g2, i: i2 } = *y;
    g1 == g2 && i1 == i2
}

still isn't enough for it to remove the short-circuiting, as even though that emits the !noundef loads first, it seems like LLVM's SROAPass moves them behind the branch from &&.


FWIW, clang(trunk) has the same codegen difference: https://cpp.godbolt.org/z/bbaz196GP

It might not have a choice, though, since C++ references are mostly just pointers.

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 by reproducing the derived_eq and good_eq examples from the issue and comparing their optimized assembly. Then inspect derived PartialEq code generation and the LLVM SROAPass and parameter-attribute behavior described here. Done means identifying a sound change that improves the derived comparison without violating poison semantics, with regression coverage for the example.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers, performance
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.