https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128208

            Bug ID: 128208
           Summary: [x86-64] Failure to vectorize a conditional reduction
                    with a unit-stride test and a stride-8 accumulate
           Product: gcc
           Version: 16.1.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: tree-optimization
          Assignee: unassigned at gcc dot gnu.org
          Reporter: bug_hunters at yeah dot net
  Target Milestone: ---

## Description

GCC 16.1.0 fails to vectorize a loop that tests `data[i].a != 0` (unit stride)
and conditionally accumulates `data[i * 8].a` (stride 8) into a `char`
reduction, on x86-64. GCC reports `unsupported SLP instances` and generates
scalar code.

Clang 22.1.0 successfully vectorizes the same loop with a vectorization width
of 8 and an interleaved count of 4, when `#pragma clang loop vectorize(enable)`
is present (without the pragma, Clang's cost model declines to vectorize).

The failure depends on the combination of a unit-stride load in the condition
and a stride-8 load in the reduction body: changing the stride-8 access to unit
stride, or making the condition also use the stride-8 access, makes GCC
vectorize the loop.

## Test case

```c
typedef struct {
    unsigned int a;
} elem;

char
foo(const elem * __restrict__ data, int n)
{
    char sum = 0;

    #pragma clang loop vectorize(enable)
    for (int i = 0; i < n; i += 1)
    {
        if (data[i].a != 0) {
            sum += data[(i * 8)].a;
        }
    }

    return sum;
}
```

## GCC version

```text
gcc version 16.1.0
```

## GCC options

```text
-S -O3 -ftree-vectorize -ftree-slp-vectorize -march=x86-64-v3
-fopt-info-vec-all -fvect-cost-model=unlimited 
```

## GCC output

```text
<source>:11:23: missed: couldn't vectorize loop
<source>:11:23: missed: unsupported SLP instances
<source>:6:1: note: vectorized 0 loops in function.
<source>:14:17: note: ***** Analysis failed with vector mode V8SI
<source>:14:17: note: ***** The result for vector mode V32QI would be the same
<source>:14:17: note: ***** Re-trying analysis with vector mode V16QI
<source>:14:17: note: ***** Analysis failed with vector mode V16QI
<source>:14:17: note: ***** Re-trying analysis with vector mode V8QI
<source>:14:17: note: ***** Analysis failed with vector mode V8QI
<source>:14:17: note: ***** Re-trying analysis with vector mode V4QI
<source>:14:17: note: ***** Analysis failed with vector mode V4QI
```

GCC generates scalar code for the loop:

```asm
foo(elem const*, int):
        test    esi, esi
        jle     .L5
        mov     esi, esi
        xor     eax, eax
        xor     edx, edx
.L4:
        mov     ecx, DWORD PTR [rdi+rax*4]
        test    ecx, ecx
        je      .L3
        lea     ecx, [0+rax*8]
        add     dl, BYTE PTR [rdi+rcx*4]
.L3:
        add     rax, 1
        cmp     rsi, rax
        jne     .L4
        mov     eax, edx
        ret
.L5:
        xor     edx, edx
        mov     eax, edx
        ret
```

The scalar loop tests `data[i].a` (`mov ecx, DWORD PTR [rdi+rax*4]`) and
conditionally accumulates `data[i*8].a` (`add dl, BYTE PTR [rdi+rcx*4]`), with
no vectorization.

## Comparison with Clang

### Clang version

```text
clang version 22.1.0
LLVM commit:
4434dabb69916856b824f68a64b029c67175e532
```

Clang options:

```text
-S -O3 -ftree-vectorize -ftree-slp-vectorize --target=x86_64-linux-gnu
-march=x86-64-v3 -Rpass=.*vectorize.* -Rpass-missed=.*vectorize.*
-Rpass-analysis=.*vectorize.*
```

Clang reports:

```text
<source>:11:5: remark: vectorized loop (vectorization width: 8, interleaved
count: 4) [-Rpass=loop-vectorize]
```

Without `#pragma clang loop vectorize(enable)`, Clang reports:

```text
<source>:10:5: remark: the cost-model indicates that vectorization is not
beneficial [-Rpass-missed=loop-vectorize]
<source>:10:5: remark: the cost-model indicates that interleaving is not
beneficial [-Rpass-missed=loop-vectorize]
```

so the divergence depends on Clang being allowed to override its cost model via
the pragma.

Clang vectorizes the loop with width 8 and interleaved count 4. The generated
code is large because it uses masked gathers and byte-wise accumulation for the
stride-8 access, but it is genuinely vectorized. Relevant excerpt — mask
construction and masked `char` accumulation:

```asm
; build the byte mask for data[i].a != 0
        vpcmpeqd        ymm3, ymm14, ymm5
        vpxor   ymm3, ymm3, ymm6
        vpackssdw       xmm3, xmm3, xmm4
        vpacksswb       xmm14, xmm3, xmm3
; gather the stride-8 char values and accumulate with the mask
        vpshufb xmm5, xmm10, xmm6
        ...
        vpand   xmm4, xmm4, xmm5
        vpaddb  xmm9, xmm9, xmm4
```

The `vpcmpeqd` / `vpackssdw` / `vpacksswb` sequence builds the `data[i].a != 0`
predicate as a byte mask, `vpshufb` / `vpunpckldq` / `vpinsrb` collect the
stride-8 `char` values, and `vpand` / `vpaddb` conditionally accumulate them.

## Additional observations

The following variants were tested (each recompiled and re-checked; the
criterion was that GCC remains unvectorized — the missed reason in the log may
vary — while Clang still vectorizes with vectorize(enable)):

1. **The struct layout is not essential.** Removing the unused `short` and
`long` fields, leaving only `unsigned int a`, reproduces the failure
identically.
2. **The outer stride is not essential.** Changing `i += 67` to `i += 1`
reproduces the failure identically.
3. **The `idx` temporary is not essential.** Using `i` directly reproduces the
failure identically.
4. **An explicit `(char)` cast is not essential.** Assigning `data[(i * 8)].a`
to `char sum` without an explicit cast reproduces the failure identically.
5. **The stride-8 access is essential.** Changing `data[(i * 8)].a` to
`data[i].a` makes GCC vectorize the loop.
6. **The unit-stride condition is essential.** Changing the condition to also
read the stride-8 access (`data[(i * 8)].a != 0`) makes GCC vectorize the loop.
The failure requires a unit-stride test combined with a stride-8 accumulate.
7. **Clang needs `vectorize(enable)`.** Without the pragma, Clang's cost model
declines to vectorize. The GCC side uses `-fvect-cost-model=unlimited` as the
corresponding relaxation.

## Live examples

GCC:
https://godbolt.org/z/fzzv5q8Wa
Clang:
https://godbolt.org/z/9YPGhhh83

Reply via email to