llvm / llvm/llvm-project

clang fails to vectorize a select-of-two-array-loads on AArch64 (gcc does)

Open
#219,918 2 comments 0 reactions 0 assignees View on GitHub
missed-optimization vectorizers
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

At `-O3` for AArch64, clang leaves the loop below scalar while gcc vectorizes it (same flags, same target). The loop reads `a[i]` and `b[i]`, compares a field, and stores a value from whichever is larger. gcc emits `ld2 + cmge + bsl`; clang keeps it scalar.

I hit this in a Viterbi decoder (EEMBC viterb00) — the add-compare-select loop is this exact pattern, and it's a big reason the clang build is slower than gcc there.

### Testcase
```c
typedef short s16;
struct S { s16 tag; s16 key; };
void f(s16 *dst, const struct S *a, const struct S *b, int n)
{
for (int i = 0; i < n; i++) {
s16 ka = a[i].key;
s16 kb = b[i].key;
if (ka >= kb)
dst[i] = a[i].tag;
else
dst[i] = b[i].tag;
}
}
```
Flags: `-O3 -mcpu=cortex-a57`
- clang (trunk): https://godbolt.org/z/hGEa687ae — not vectorized
- gcc (trunk): https://godbolt.org/z/PhY5cW3Ed — vectorized

gcc output:
```
ld2 {v30.8h - v31.8h}, [x4], 32
ld2 {v28.8h - v29.8h}, [x6], 32
cmge v29.8h, v31.8h, v29.8h
bsl v29.16b, v30.16b, v28.16b
```
clang output: scalar loop (loads both keys, compares, csel/branch, loads the chosen tag, stores). No `ld2`/`bsl`.

### Root cause
Before the loop vectorizer runs, the conditional read is already a load from a selected pointer:
```llvm
%cmp = icmp slt i16 %ka, %kb
%spec.select = select i1 %cmp, ptr %arrayidx2, ptr %arrayidx
%v = load i16, ptr %spec.select
```
The load address depends on the compare, so the vectorizer can't widen it. To match gcc it needs to be `select(cmp, load a[i], load b[i])` — load both (contiguous) and select the value.

### Observation
InstCombine has this fold in `visitLoadInst`: `load(select(c,p1,p2)) -> select(c, load p1, load p2)`. But it only fires when `isSafeToLoadUnconditionally` holds for both pointers, which fails for `a[i]`/`b[i]`: those checks have no loop context, so they can't prove per-iteration dereferenceability or add a runtime check.
There's already a loop-aware check, `isDereferenceableAndAlignedInLoop` (`Analysis/Loads.cpp`): it uses SCEV, can emit runtime predicates, and the vectorizer already calls it elsewhere (e.g. `LoopVectorizationLegality::canVectorizeWithIfConvert`). It just isn't wired up for this case.

Contributor guide

Open the contributing guide

Research direction

Start with Analysis/Loads.cpp and the loop-aware isDereferenceableAndAlignedInLoop check, then trace InstCombine’s visitLoadInst and LoopVectorizationLegality::canVectorizeWithIfConvert. Compile the supplied C testcase for AArch64 with -O3 -mcpu=cortex-a57 and compare the generated code; done means the safe load-and-select pattern is vectorized without losing the required safety checks.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.