llvm / llvm/llvm-project

[X86][LoadPRE/GVN] Redundant 16-byte MOVUPS struct load not eliminated across conditional branch

Open
#205,968 2 comments 0 reactions 0 assignees View on GitHub
backend:X86 llvm:GVN missed-optimization
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

**Clang:** Ubuntu clang version 22.1.8 · reproduced on Godbolt 22.1.0
**Target:** x86_64-pc-linux-gnu · **Compile:** `clang -O3`
**Labels:** `missed-optimization` `backend:X86`

## Summary

With `-O3`, Clang loads a 16-byte struct `f` via `movups` in the entry block, then
**unconditionally reloads** the same 16 bytes from `f` in the taken branch, even
though `%xmm0` still holds the value and no store to `f` has occurred.

`LoadPRE` or `GVN` should hoist the second load out of the branch, or simply
reuse the value already in `%xmm0`.

## Reproducer

```c
struct { long b; int c; } a, d, f;
unsigned e;

void g(void) {
d = f;
if (e)
a = f;
}
```

## Actual output (`-O3`)

```asm
g:
movups f(%rip), %xmm0
movups %xmm0, d(%rip)
cmpl $0, e(%rip)
je .LBB0_2
movups f(%rip), %xmm0
movups %xmm0, a(%rip)
retq
d:
f:
e:
a:
```

**Bug:** `movups f(%rip), %xmm0` appears **twice** — once unconditionally, once
inside the taken branch. The second load is a full memory read; the compiler
should emit `movups %xmm0, a(%rip)` directly (reusing `%xmm0` from the first load).

## Expected output

```asm
g:
movups f(%rip), %xmm0 ; load f once
movups %xmm0, d(%rip) ; d = f
cmpl $0, e(%rip)
je .done
movups %xmm0, a(%rip) ; a = f ← reuse %xmm0, NO second memory read
.done:
retq
```

## `-O0` for comparison

```asm
g:
pushq %rbp
movq %rsp, %rbp
movq f(%rip), %rax
movq %rax, d(%rip)
movq f+8(%rip), %rax
movq %rax, d+8(%rip)
cmpl $0, e(%rip)
je .LBB0_2
movq f(%rip), %rax
movq %rax, a(%rip)
movq f+8(%rip), %rax
movq %rax, a+8(%rip)
popq %rbp
retq
d:
f:
e:
a:
```

## Godbolt

https://godbolt.org/z/Kh84qqo9e

## Root cause

After the first `d = f` assignment (which loads `f` into `%xmm0` and stores it),
`GVN` or `LoadPRE` should track that `f`'s value is still live in `%xmm0` and
no aliasing store occurred. The conditional branch should not force a reload.
This is a classic cross-basic-block Load-PRE failure.

Contributor guide

Open the contributing guide

Research direction

Reproduce the issue with the C snippet using Clang 22.1.0 or newer at -O3 and inspect the generated x86_64 assembly. Start by tracing the LoadPRE and GVN handling of the load across the conditional branch. Done means the second 16-byte load from f is eliminated and a regression test covers the case.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.