llvm / llvm/llvm-project

[X86][GVN] Redundant pointer load (movq) not eliminated across conditional branch

Open
#205,969 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 the global pointer `e` into `%rcx` on the first branch path
(`if (c)`), then **reloads the same pointer** into `%rax` on a subsequent branch
(`if (ag)`), even though no store to `e` occurs between the two loads.

`GVN` should recognize `movq e(%rip), %rax` as redundant and replace it with
`movq %rcx, %rax` (register copy).

## Reproducer

```c
int ag, b;
short c;
long d;
int *e;

void f(void) {
if (c)
*e = d;
if (ag)
b = *e;
}
```

## Actual output (`-O3`)

```asm
f:
cmpw $0, c(%rip)
je .LBB0_2
movl d(%rip), %eax
movq e(%rip), %rcx
movl %eax, (%rcx)
cmpl $0, ag(%rip)
je .LBB0_4
movq e(%rip), %rax
movl (%rax), %eax
movl %eax, b(%rip)
retq
c:
d:
e:
ag:
b:
```

**Bug:**
```asm
movq e(%rip), %rcx ; load pointer e → %rcx (first path)
movl %eax, (%rcx)
cmpl $0, ag(%rip)
je .LBB0_4
movq e(%rip), %rax ; REDUNDANT: reload e → %rax (already in %rcx)
movl (%rax), %eax
```
Between `movq e(%rip), %rcx` and `movq e(%rip), %rax` there is no store to `e`.
The second load should be `movq %rcx, %rax`.

## Expected output

```asm
movq e(%rip), %rcx ; load e once
movl %eax, (%rcx)
cmpl $0, ag(%rip)
je .done
movl (%rcx), %eax ; use %rcx directly — no memory reload
movl %eax, b(%rip)
.done:
retq
```

## `-O0` for comparison

```asm
f:
pushq %rbp
movq %rsp, %rbp
cmpw $0, c(%rip)
je .LBB0_2
movq d(%rip), %rax
movl %eax, %ecx
movq e(%rip), %rax
movl %ecx, (%rax)
cmpl $0, ag(%rip)
je .LBB0_4
movq e(%rip), %rax
movl (%rax), %eax
movl %eax, b(%rip)
popq %rbp
retq
c:
d:
e:
ag:
b:
```

## Godbolt

https://godbolt.org/z/x37Tfha98

## Root cause

`GVN` tracks available expressions within and across basic blocks. Here, `e` is
loaded in block LBB0_1 and its value enters `%rcx`. Block LBB0_3 (the `if (ag)`
branch) re-loads `e` instead of using the value that arrived via the dominating
load in LBB0_1. No call or store to `e` exists on the path from LBB0_1 to LBB0_3,
so GVN should be able to forward `%rcx` without a second memory access.

Contributor guide

Open the contributing guide

Research direction

Start with the C reproducer and run it through Clang with -O3 for the x86_64 target, comparing the generated assembly with the Godbolt example. Investigate GVN's handling of the pointer load across the conditional branches. Done means the redundant second load of e is eliminated and the generated code uses the existing pointer value without changing behavior.

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
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.