rust-lang / rust-lang/rust

MIR GVN does not eliminate duplicate commutative expressions with swapped operands

Open
#162,220 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-mir-opt C-optimization needs-triage
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

I tried this code:

#![crate_type = "lib"]

unsafe extern "C" {
    safe fn consume_pair(x: u32, y: u32) -> u32;
}

#[unsafe(no_mangle)]
pub fn add_commuted(a: u32, b: u32) -> u32 {
    consume_pair(a.wrapping_add(b), b.wrapping_add(a))
}

#[unsafe(no_mangle)]
pub fn mul_commuted(a: u32, b: u32) -> u32 {
    consume_pair(a.wrapping_mul(b), b.wrapping_mul(a))
}

#[unsafe(no_mangle)]
pub fn and_commuted(a: u32, b: u32) -> u32 {
    consume_pair(a & b, b & a)
}

I inspected optimized MIR using:

rustc -O --emit=mir -Zmir-opt-level=4 repro.rs

MIR GVN eliminates duplicate expressions when their operands appear in the same order, but it does not appear to canonicalize operands of commutative integer operations.

For example, the optimized MIR for add_commuted still contains two additions:

_3 = Add(copy _1, copy _2);
_4 = Add(copy _2, copy _1);
_0 = consume_pair(move _3, move _4) -> ...

The same happens for multiplication and bitwise-and:

_3 = Mul(copy _1, copy _2);
_4 = Mul(copy _2, copy _1);
_3 = BitAnd(copy _1, copy _2);
_4 = BitAnd(copy _2, copy _1);

I expected the second expression in each function to reuse the result of the first expression.

LLVM subsequently eliminates the duplicate operation in native code, so the final x86-64 output is already optimal. For example:

add_commuted:
        add     esi, edi
        mov     edi, esi
        jmp     qword ptr [rip + consume_pair@GOTPCREL]

The opportunity is therefore specifically in optimized MIR. Canonicalizing operands before constructing the GVN value should reduce redundant MIR, expose reuse before backend lowering, and avoid relying on individual codegen backends to rediscover the equivalence.

This should only be done for operations that are commutative for the relevant MIR types, such as integer Add, Mul, BitAnd, BitOr, BitXor, Eq, and Ne. Floating-point operations should not be included without accounting for their semantics.

The closest existing report I found was the general [MIR simplification tracking issue #111442](https://github.com/rust-lang/rust/issues/111442), but it does not list commutative operand canonicalization or this example.

Meta
rustc 1.100.0-nightly (5db7f4be8 2026-09-01)
binary: rustc
commit-hash: 5db7f4be8a36c1b8ae19299469e2be2b0f052c21
commit-date: 2026-09-01
host: x86_64-unknown-linux-gnu
release: 1.100.0-nightly
LLVM version: 23.1.0

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 locating rustc's MIR GVN optimization and reproduce the issue with the provided add_commuted, mul_commuted, and and_commuted examples using the shown optimized-MIR command. Done means commutative integer operations with swapped operands reuse one GVN value, while floating-point operations are not included without accounting for their semantics.

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
Active
Clarity
Mostly clear
Newbie friendliness
65/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.