llvm / llvm/llvm-project

[clang] 21.0.0 and 21.1.8 miscompilation of a loop in a C function

Open
#203,703 11 comments 0 reactions 0 assignees View on GitHub
clang undefined behaviour
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

Report for clang 21.0.0 and 21.1.8 (tested), suspecting that greater versions such as v22 also have the same issue, judging from Compiler Explorer assembly comparisons. I hope (or hoped) that I did something wrong, but looked at this with others. We strongly suspect that the clang C compiler is miscompiling the simple `list()` function in the attached program that is a greatly simplied version of a Lisp interpeter written in C.

- Observed with clang 21.0.0 on MacOS Apple M1 arm64
- Observed with clang 21.1.8 on Ubuntu in a Docker container on Apple M1 arm64
- There doesn't appear to be undefined behavior in the code.
- The code does not appear to violate the strict aliasing rule.
- When compiled with optimizations (-O1,-O2,-O3) the behavior of the `list()` function is incorrect, see the `list()` function that should return the list `(1 2 3)` but incorrectly returns `()` (nil).

The `list()` function constructs a list `t` using a pointer `p`, initially `t = nil` and `p = &t`, then we repeat 3 loop iterations to add cons pairs to the end of list `t` using pointer `p` at the tail end. This is a common method to extend a list at its tail, e.g. when parsing a list from input, which is `(1 2 3)` in this example:

| step | assign | update `p` | result
| ---: | --- | --- | ---
| init | `t = nil` | `p = &t` | `t = nil`
| 1 | `*p = cons(parse(),nil)` | `p = &CDR(*p) = &cell[ord(*p)]` | `t = cons(1,nil)`
| 2 | `*p = cons(parse(),nil)` | `p = &CDR(*p) = &cell[ord(*p)]` | `t = cons(1,cons(2,nil))`
| 3 | `*p = cons(parse(),nil)` | `p = &CDR(*p) = &cell[ord(*p)]` | `t = cons(1,cons(2,cons(3,nil)))`

Then the condition `T(t) == CONS` fails, when `t` should obviously be a cons list. The compiled code incorrectly gives `t = nil`. Other compilers work fine. Works with `--fno-strict-aliasing` even though there are no pointer accesses to the same memory location through pointers of two different types. There is only one pointer type `L*` which is `double*`. Works when `t` and `*p` are `volatile`, which shouldn't be necessary.

[clang-bug.c](https://github.com/user-attachments/files/28913654/clang-bug.c)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.