[LoopIdiomRecognize] wcslen idiom miscompiles loop with unaligned wide loads
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## Summary
Starting with LLVM/Clang 21, `LoopIdiomRecognize` recognizes a loop performing `wchar_t`-sized loads via `memcpy` and replaces it with `wcslen`.
The original loop supports unaligned source addresses because the loads are performed via `memcpy`. The generated `wcslen` call is passed the same unaligned address, seems to introduce a stronger alignment requirement and results in wrong code.
Disabling only the `wcslen` loop idiom transformation with `-mllvm -disable-loop-idiom-wcslen` fixes the result.
The storage is an actual `unsigned char` array.
All pointer arithmetic is performed on `unsigned char*` and remains within that array.
The source program does not create or dereference an unaligned `wchar_t*`.
Each `wchar_t` value is copied from the byte buffer with `memcpy` into a properly aligned local `wchar_t` object
## Reproducer
```c++
#include
#include
#include
#if defined(__clang__) || defined(__GNUC__)
__attribute__((noinline))
#endif
static std::size_t get_size(const unsigned char* bytes)
{
std::size_t i = 0;
wchar_t ch{};
do {
std::memcpy(
&ch,
bytes + i * sizeof(wchar_t),
sizeof(wchar_t)
);
++i;
} while (ch != wchar_t{}) ;
return i * sizeof(wchar_t);
}
int main()
{
alignas(wchar_t) static unsigned char aligned_buf[256] = {};
alignas(wchar_t) static unsigned char unaligned_buf[256] = {};
constexpr wchar_t ws[] = L"test str";
static_assert(sizeof(ws) == 9 * sizeof(wchar_t));
constexpr std::size_t offset = 1;
std::memcpy(
aligned_buf,
ws,
sizeof(ws)
);
std::memcpy(
unaligned_buf + offset,
ws,
sizeof(ws)
);
const unsigned char* aligned =
aligned_buf;
const unsigned char* unaligned =
unaligned_buf + offset;
std::printf(
"alignof(wchar_t) = %zu\n"
"aligned: address %% alignment = %zu\n"
"aligned: get_size = %zu, expected = %zu\n"
"unaligned: address %% alignment = %zu\n"
"unaligned: get_size = %zu, expected = %zu\n" ,
alignof(wchar_t),
reinterpret_cast(aligned) % alignof(wchar_t),
get_size(aligned), sizeof(ws),
reinterpret_cast(unaligned) % alignof(wchar_t),
get_size(unaligned), sizeof(ws)
);
}
```
## Output
**clang++ -O2**
```
alignof(wchar_t) = 4
aligned: address % alignment = 0
aligned: get_size = 36, expected = 36
unaligned: address % alignment = 1
unaligned: get_size = 32, expected = 36
```
**clang++ -O2 -mllvm -disable-loop-idiom-wcslen**
```
alignof(wchar_t) = 4
aligned: address % alignment = 0
aligned: get_size = 36, expected = 36
unaligned: address % alignment = 1
unaligned: get_size = 36, expected = 36
```
## Godbolt
https://godbolt.org/z/x9o55r9av
## Environment
- Target: x86-64 Linux
- Optimization level: `-O2`
- Affected compiler:
LLVM/Clang 21
LLVM/Clang trunk
- Workaround / control flag: `-mllvm -disable-loop-idiom-wcslen`
Contributor guide
Research direction
Start by reproducing the issue with the provided C++ program using clang++ -O2, then inspect the LoopIdiomRecognize wcslen transformation and its handling of alignment. Compare the generated result with -mllvm -disable-loop-idiom-wcslen. Done means the transformation preserves the correct result for the unaligned buffer while retaining the intended optimization for aligned input.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100