llvm / llvm/llvm-project

[X86] wrong code: array index truncated from a _BitInt(98) value is used at full width at -O1

Open
#222,714 1 comment 0 reactions 0 assignees View on GitHub
backend:X86 confirmed generated by fuzzer miscompilation
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

Live Reproducer: [https://godbolt.org/z/KbMqjcevv]()

Compiling the following C code with `clang -O1` and running it segfaults. It reads `a[value & 1]`, where `value` is an `unsigned char`, so the index can only be 0 or 1.

```cpp
#include
#include
volatile unsigned char vc = 48;
volatile int vi = 1;
typedef unsigned _BitInt(34) uint34_t;
typedef unsigned _BitInt(96) uint96_t;
typedef _BitInt(98) int98_t;
typedef _BitInt(128) int128_t;
static uint64_t a[2];
static int128_t matrix[2];
static inline int98_t safe_add(int98_t lhs, int98_t rhs) {
int98_t result;
return __builtin_add_overflow(lhs, rhs, &result) ? lhs : result;
}
int main(void) {
uint8_t value = 0;
a[0] = 0;
value = (uint8_t)safe_add(
(int98_t)(uint34_t)(
-(uint34_t)((uint34_t)0 ==
(uint34_t)a[vc & 1])),
(int98_t)(uint96_t)(-(uint96_t)(~(uint32_t)value)));
matrix[a[value & 1] & 1] = (int128_t)vi == matrix[0];
printf("%llu\n", (unsigned long long)value);
return 0;
}
```

clang command:

```sh
clang -std=c2y -O1 repro.c && ./a.out
```

Output (wrong):

```text
Segmentation fault (core dumped)
```

Expected (what -O0 prints, and what GCC 16 prints at -O0 and -O2):

```text
0
```

`value` is `(uint8_t)` of the 98-bit sum, so `value & 1` is 0 or 1 and `a[value & 1]` is in bounds. The generated code instead indexes `a` with the full 64-bit low word of the sum, which is `0x300000000` here:

```asm
movabs rdx, 17179869183 # 0x3FFFFFFFF
movabs rsi, -4294967295 # 0xFFFFFFFF00000001
add rdx, rsi # rdx = 0x300000000, the 98-bit sum's low word
test rcx, rcx
cmovne rdx, rsi
mov esi, edx # value = (uint8_t)sum -> low bits only
...
mov eax, dword ptr [rax + 8*rdx] # a[rdx] with rdx = 0x300000000 -> faults
```

The truncation to `uint8_t` and the `& 1` are both missing from the address: `edx` is only used for the value that is printed, while the load uses `rdx`.

Notes:

* Correct at -O0
* Compiling the -O1 IR with `llc -O0` prints 0, `llc -O1` faults, so I assume it's a backend bug

clang version:

```text
clang version 24.0.0git (https://github.com/llvm/llvm-project 05fa66e6624e6b14346e559cd7b692aaf48f3ca7)
```

Contributor guide

Open the contributing guide

Research direction

Start with the attached repro.c and reproduce the difference between clang -O1 and -O0. Compare the -O1 IR compiled with llc -O0 and llc -O1, then inspect the generated address calculation. Done means the truncation to uint8_t and the & 1 mask affect the array index, producing 0 without a fault.

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
Active
Clarity
Clearly specified
Newbie friendliness
63/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.