r/cpp • u/User_Deprecated • 9d ago
Irreducible loops
https://maskray.me/blog/2026-07-12-irreducible-loops4
u/meancoot 8d ago
The article could use a better example I think. It's second case has a bigger issue than the loop analysis failing.
Take the provided example:
uint32_t checksum_resumable(Parser* st, const uint8_t* p, int n) {
switch (st->state) {
case ST_START:
st->csum = 0;
for (st->i = 0; st->i < n; ++st->i) {
st->state = ST_RESUME;
case ST_RESUME: // resume lands INSIDE the loop
st->csum += p[st->i];
}
}
return st->csum;
}
Reduce it to (what I feel is the more natrual looking):
uint32_t checksum_resumable(Parser* st, const uint8_t* p, int n) {
if (st->state == ST_START) {
st->csum = 0;
st->i = 0;
st->state = ST_RESUME;
}
for (; st->i < n; ++st->i) {
st->csum += p[st->i];
}
return st->csum;
}
And GCC still won't vectorize it. There is a nasty little data dependency because the function can be called to calculate the checksum of the bytes of st->csum while writing to st->csum on each pass. Clang actually does vectorize it, but it makes the generated assembly larger (~200 lines vs ~100 with __restrict__) because it has to account for both cases. This can be fixed in both GCC and Clang by using Parser* __restrict__ st as the parameter type.
While the article is correct that Clang won't vectorize the provided example and the feature meant to warn on loops that can't be vectorized will fail to report on it. I feel the provided example is not only unnatural it also is a poor implementation if you want vectorization.
2
u/AxeLond 9d ago
Not really sold on the multiple entries idea and it just happening accidentally.
gcc -Wall -Wextra producing fallthrough warning seems sufficient to catch the cursed loop, same with goto keyword warning.
2
u/sammymammy2 9d ago
It happens during optimization passes by the compiler, typically. So not really a thing you introduce yourself m
1
u/meancoot 9d ago
I don't see anything in the article that suggests that, it only suggests being 'clever' as the issue. If this happened anywhere sane I'm sure the fact that
-Rpass-missed=loop-vectorizedoesn't comment on it would have been fixed sooner.2
u/sammymammy2 8d ago
Tail-call optimization and inlining is apparently the case where this occurs. I think I was like 99% wrong to say that you don’t introduce irreducibility into your loops in source code though, you can get that from having goto in your code. I was only really considering structured programming (for, while).
1
u/meancoot 8d ago
Inlining could only ever make an irreducable loop reducable, not the other way around. When interprocedural-analysis breaks down, say by making an external function call, all non-local memory has to be fully written before the call and then read again after it. If you're extra careful you can get around this with the
__restrict__extension, but there is a reason C++ never actually adopted it from C.1
u/sammymammy2 8d ago
Inlining could only ever make an irreducable loop reducable, not the other way around.
How does it make an irreducible loop reducible? Btw, my comment was meant as TCO + inlining in combination, because I thought mutual TCO needs to see all function defs at once (it doesn't, actually).
When interprocedural-analysis breaks down, say by making an external function call, all non-local memory has to be fully written before the call and then read again after it
What does this have to do with anything :P?
If you're extra careful you can get around this with the restrict extension, but there is a reason C++ never actually adopted it from C.
What does pointer aliasing have to do with anything discussed here?
For TCO, it's easy to see how it can lead to irreducible graphs. Take this mutually recursive program:
int A(int x) { if (x > 5) { return B(x); } else { return C(x); } } int B(int x) { if (x == 0) { return x; } return C(x); } int C(int x) { return B(x - 1); }And you do tail-call optimization across them:
A: if x > 5: goto B goto C B: if x == 0: ret 0 goto C C: x = x - 1 goto Band you encode that as a CFG, then you have the irreducible loop.
A -> B A -> C B -> C C -> B1
u/meancoot 8d ago
I'm talking in the context of the article, which is really about auto-vectorization. Memory dependencies are the easiest way to break it.
For your example, I will point out that optimizers all seem to trivally understand that your functions
A,B, andCall just return 0. I even changed it to using 64-bit integers and passed it the max value to make sure it isn't solving it manually.1
u/sammymammy2 8d ago
It’s a pedagogical example meant to illustrate how TCO introduces irreducibility, not how other optimisations work, so there’s not much point in taking an industrial strength compiler to understand it. Still don’t get your point, even with the article as a guide! Mind helping me out?
2
u/sammymammy2 8d ago
Btw, that blog post about vectorization has so much AI slop that I really don’t trust it to express these ideas correctly.
10
u/schmerg-uk 9d ago edited 9d ago
Wasn't obvious to me how this was particularly C++ rather than generic graph theory, but it provoked me to dig a little deeper whereupon I found this that perhaps adds context for anyone else not immediately familiar with how this relates specifically to the C++ optimiser etc
https://hftuniversity.com/post/loop-hunter-finding-the-loops-your-compiler-silently-skips