Krister Walfridsson
kristerw.blogspot.com · Personal blog · on HN since 2015
Krister Walfridsson writes about compilers, mainly GCC and LLVM, covering optimizations, undefined behavior, and C and C++ details.
What HN saysCommenters cite it in debates on undefined behavior and compiler optimization, and for his experiment building the 31-year-old GCC 1.27.
53HN people shared it
28linked it in comments
61stories submitted
1.5Kstory points
3 months agolast shared
2015people per year2026
Most-shared posts
- 1Why undefined behavior may call a never-called function14 people · 144 points · first shared 2017 · 169 comments on HN
- 2How undefined signed overflow enables optimizations in GCC8 people · 100 points · first shared 2016 · 90 comments on HN
- 3How LLVM Optimizes Geometric Sums7 people · 7 points · first shared 2019 · discussion on HN
- 4Useful GCC warning options not enabled by -Wall -Wextra5 people · 293 points · first shared 2017 · 166 comments on HN
- 5Building GCC 1.27 (first GCC with x86 support) (2019)4 people · 81 points · first shared 2019 · 66 comments on HN
- 6Code behaving differently in C90, C99, C11, C++98, and C++112 people · 134 points · first shared 2016 · 17 comments on HN
- 7The structure of a GCC back end2 people · 105 points · first shared 2017 · discussion on HN
- 8Type-based aliasing in C2 people · 73 points · first shared 2016 · 51 comments on HN
- 9C pointers are not hardware pointers2 people · 45 points · first shared 2016 · 36 comments on HN
- 1020 years of NetBSD code bloat2 people · 11 points · first shared 2016 · 1 comments on HN
- 11GCC target description macros and functions2 people · 5 points · first shared 2017 · discussion on HN
- 12The cost of conditional moves and branches2 people · 4 points · first shared 2017 · discussion on HN
- 13Branch Prediction2 people · 4 points · first shared 2017 · discussion on HN
- 14GCC code generation for C++ Weekly Ep 43 example1 people · 86 points · first shared 2017 · 17 comments on HN
- 15Interprocedural optimization in GCC1 people · 84 points · first shared 2017 · 8 comments on HN
What HN people wrote when they linked it
almostgotcaught · 2025-02-20
I feel like you have poor reading comprehension. I literally said yes for simple cases it works.
> https://kristerw.blogspot.com/2019/04/how-llvm-optimizes-geometric-sum…
Literally not a single loop on that page has a conditional in it. They're all the same stupid "polynomial" example. Hint: how often do you think people are trying to optimize loops that compute polynomials....
> I assure you they can do this efficiently.
It's amazing you think your assurances mean anything. So let me assure you: I know exactly what I'm talking about. In the last month I landed basically SCEV in a production compiler. Except since our programs are real and not just goofy polynomial examples, my SCEV is actually an abstract interpreter.
optionalsquid · 2022-12-01
> While `.unwrap().foo()` is still just as crashy as an unguarded `x->foo()` in other languages
I think it is also worth noting that unwrap itself is much safer than an unguarded `x->foo()`, since it doesn't involve undefined behavior. Meanwhile an unguarded `x->foo()` allows compiler to assume that x is never null and consequently do things that the programmer wouldn't expect. The recent post on undefined behavior [1] had a (to me) quite shocking example of that [2]. So while bad Rust programmers misusing unwrap get crashes, they won't get nasal demons.
[1] https://news.ycombinator.com/item?id=33771922
[2] https://kristerw.blogspot.com/2017/09/why-undefined-behavior-may-call-…
cesarb · 2020-07-16
> That could literally produce no output program?
Way worse than even that (you might be noticing a theme here...). Once the optimizer has removed as dead code any branch that, if taken, would provably lead to UB at some arbitrary future point of execution, it can conclude that the other branch is now the only possible execution, and call it unconditionally, even if that leads to removing all your files (the classic example is https://kristerw.blogspot.com/2017/09/why-undefined-behavior-may-call-…
aw1621107 · 2020-03-16
That particular behavior (and some other examples of pathological optimizer behavior) is probably an emergent property of how several optimizations combine as opposed to being a specifically targeted optimization. I can imagine something the optimizer doing something like:
1. Determine possible function call targets
2. Eliminate invalid targets from call set
3. If only one target remains, replace indirect call with direct call
That might be useful for something like devirtualization, but also could lead to the behavior exhibited in that blog post.
Indeed, the second blog post [0] shows that if there are two possible call targets, LLVM could generate an indirect call like what you may expect.
[0]: https://kristerw.blogspot.com/2017/09/follow-up-on-why-undefined-behav…
butteroverflow · 2019-06-28
Yeah. Here's a GCC developer experimenting with a 31-year old GCC version on a modern system (the first one with x86 support):
>It is amazing how well things work – the modern assembler, linker, and debugger handles the code generated by GCC 1.27 without any problems.
https://kristerw.blogspot.com/2019/01/building-gcc-127.html
And the write-up he based his experiments upon:
https://miyuki.github.io/2017/10/04/gcc-archaeology-1.html
I'd say this kind of source-based backwards compatibility is superior to what's happening in Windows world -- not only it encourages you to keep the source code open, but you also don't have to keep as many hacks in the kernel/system libraries.
colanderman · 2016-11-28
While I like a lot of what's in here,
for (size_t i = 9; i <= 9; --i)
is a pretty terrible example to put in the second chapter. I would not let that line pass code review. There is no need or place for cutesy cleverness in C.
EDIT: Ugh, just found this too:
isset[!!largeA[i]] += 1;
Not only is that confusingly cutesy, but largeA[i] is a double. Please DON'T write – or encourage beginners to write! – such smug code!
EDIT2: In section 5 is the statement than unsigned integers "can be optimized best." This is flatly untrue on x86 and I suspect many other architectures. Compilers can and do take advantage of undefined signed overflow to optimize signed arithmetic; the same is not possible with unsigned arithmetic. See https://kristerw.blogspot.com/2016/02/how-undefined-signed-overflow-en…
titzer · 2025-12-29
Indeed, UB is literally whatever the compiler feels like. A famous one [1] has the compiler deleting code that contains UB and falling through to the next function.
"But it's right there in the name!" Undefined behavior literally places no restrictions on the code generated or the behavior of the program. And the compiler is under no obligation to help you debug your (admittedly buggy) program. It can literally delete your program and replace it with something else that it likes.
[1] https://kristerw.blogspot.com/2017/09/why-undefined-behavior-may-call-…
Someone · 2025-12-03
LLVM can do more complex sums, too. See https://kristerw.blogspot.com/2019/04/how-llvm-optimizes-geometric-sum…
saagarjha · 2025-02-20
Put the theory aside and look at the loop I showed you. Surely you can see that its CFG is trivial? You can track all the variables across the basic block and it's really easy to prove (yes, prove) what I claimed. And compilers can of course do more advanced forms of this, here are some more examples: https://kristerw.blogspot.com/2019/04/how-llvm-optimizes-geometric-sum… I'm confused why you think this is hard because major compilers have been doing it for decades now. I assure you they can do this efficiently.
rwmj · 2024-12-11
This explanation of why signed int overflow is undefined is interesting (although the behaviour is still very annoying): https://kristerw.blogspot.com/2016/02/how-undefined-signed-overflow-en… (HN discussion: https://news.ycombinator.com/item?id=11146384)
More examples here: http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html
atiedebee · 2024-06-25
if( x > INT_MAX - n ) // overflow for x + n
Your definition of a far more complicated dance is absurd.
Signed integer being undefined makes a lot of integer math faster actually: https://kristerw.blogspot.com/2016/02/how-undefined-signed-overflow-en…
There's a reason zig made unsigned overflow undefined: it allows for more optimisations (Zig can also afford it thanks to their wrapping addition (+%) operator).
hansvm · 2023-09-09
Unthankfully, the definition is so vague as to be totally useless from a programming perspective. Avoid invoking UB, or all hell breaks loose. [0]
Separately, I don't think their point was that they were speaking as a layman, but rather that the loose definition they gave is historically accurate, easier to reason about, and what a working programmer not versed in the nuances of UB would expect. The fact that UB has its current definition is indicative of "undefined [getting] lost somewhere."
[0] https://kristerw.blogspot.com/2017/09/why-undefined-behavior-may-call-…
Zagitta · 2023-02-14
Take a look at this and you'll see it actually just randomly blows up :^)
https://kristerw.blogspot.com/2017/09/why-undefined-behavior-may-call-…
hxhxhrra · 2022-11-28
> I don't believe that it does. If the invalid arithmetic proceeds without crashing, and produces a value in the int32_t i variable, then that issue is settled. The subsequent statement should behave according to accessing that value.
You may dislike it, but that is not how UB in C and C++ works. See [1] for a guide to UB in C/C++ that may already have been posted elsewhere here.
It is a common misconception that UB on a particular operation means "undefined result", but that is not the case. UB means there are no constraints whatsoever on the behavior of the program after UB, often referred to as "may delete all your files". See [2] for a real-world demo doing that.
[1] https://blog.regehr.org/archives/213
[2] https://kristerw.blogspot.com/2017/09/why-undefined-behavior-may-call-…
layer8 · 2022-11-21
It might remove too many compiler optimization opportunities (see e.g. http://kristerw.blogspot.com/2016/02/how-undefined-signed-overflow-ena… for the standard to disallow them now.
wizeman · 2022-11-06
> Everyone says that and they have no proof.
Here's an entire blog post with proof:
https://kristerw.blogspot.com/2016/02/how-undefined-signed-overflow-en…
dirtydroog · 2020-03-16
It's C++, but UB allows the compiler to call a function the programmer never called
https://kristerw.blogspot.com/2017/09/why-undefined-behavior-may-call-…
I'm not sure how any compiler writer can condone this behaviour.
slacka · 2019-10-04
Many answers suggest passing -Wall -Wextra to the compiler. In my experience, this misses many useful warnings. Here's a good list of additions warnings that usually produce useful results:
https://kristerw.blogspot.com/2017/09/useful-gcc-warning-options-not-e…
deathanatos · 2018-05-31
For some examples of the types of optimizations enabled by allowing overflow to be undefined: https://kristerw.blogspot.com/2016/02/how-undefined-signed-overflow-en…
DougBTX · 2017-10-03
The example of optimizing in a block of code that is never explicitly called is even more fun: https://kristerw.blogspot.co.uk/2017/09/why-undefined-behavior-may-cal…
the_why_of_y · 2016-12-29
> ANSI Common LISP lives up to that standard.
There is only one version of the ANSI Common Lisp standard, so it is trivially compatible with itself.
> C's versioning lives up to that standard.
Then how do you explain this astonishing work of art?
https://kristerw.blogspot.com/2016/07/code-behaving-differently-in-c90…
elcritch · 2016-12-27
To reinforce this, I still believe C is one of the most elegant syntax's for a "portable assembler". There are many dark corners of C, take for example pointers:
https://kristerw.blogspot.com/2016/03/c-pointers-are-not-hardware-poin…
rom-antics · 2023-02-27
That's a guarantee on the level of the hardware/OS, but hardware semantics are not the same as language/compiler semantics. Even if according to the source code you're dereferencing a pointer value 0x0 or 0x2, that doesn't mean the compiler-emitted machine code will end up telling the hardware to do the same.
Remember this gem?
https://kristerw.blogspot.com/2017/09/why-undefined-behavior-may-call-…
Once you trigger UB, all bets are off and your code could do anything. A segfault just means you spun the roulette wheel, bet it all on red, and got lucky your house wasn't bulldozed.
Zig also uses LLVM under the hood, right? So it's subject to these same semantics. An LLVM pointer value cannot legally contain arbitrary non-null non-pointer integers such as 0x2. That's a dead giveaway of UB. And I doubt the emitted Zig code safety-checks every pointer dereference for a value less than 0x1000 before performing the dereference.
nmilo · 2022-11-28
I thought this was going to be an actually useful post about the details of UB, when it happens, and what it generally looks like, but instead it's just more of the same often repeated "if you write a program with UB your compiler will literally summon dragons and wipe your hard drive and kill you in your sleep."
The truth is that practice diverges from theory, and, in practice, it is actually useful to talk about UB as a thing that happens and not as some mystical aura the compiler can sense or not. Leading to, in practice, almost all of these points being, if not flat-out wrong, at least unhelpful or misleading to the average programmer. For example:
> 14. Okay, but if the line with UB is unreachable (dead) code, then it's as if the UB wasn't there.[1]
The linked post ([1]) doesn't even showcase this phenomenon, it shows something completely different, where ignoring a 3 transmuted into a bool causes more UB. The problem was that you already had UB to begin with when transmuting a 3 into a bool. I want to see a godbolt output where the claimed:
if (false)
{
run_ub();
}
actually causes problems.
> 28. At least it won't completely wipe the drive.[2]
This is just unhelpful for anyone trying to learn more about UB. UB can call parts of your code that would otherwise be dead, which is a useful piece of information to know. And if you literally write "rm -rf /" in these dead sections, then yeah, I guess UB is technically wiping your drive, but stating it…
ssokolow · 2021-04-16
> What does that even mean in the absence of a formal standard and competing compiler implementations? (Hint: nothing.)
Undefined behaviour is stuff that you compiler's optimizers have been promised can never occur, so they are allowed to transform your code based on that assumption.
Here's a post I made on Reddit in 2019 with a list of resources on what undefined behaviour is:
https://www.reddit.com/r/rust/comments/dpxswt/rust_2020_lets_embrace_t…
...and here's the link to an example of what invoking undefined behaviour can do in C++:
https://gcc.godbolt.org/z/0ubnbS
...and here's an explanation of why it does what it does:
https://kristerw.blogspot.com/2017/09/why-undefined-behavior-may-call-…
(TL;DR: It injects a call to EraseAll because calling Do while it's still null is undefined behaviour, and Do is a static, so the optimizer determines that the only possible answer within the rules it was given is that code outside that compilation unit will have called NeverCalled to set Do = EraseAll before invoking main().)
zrm · 2019-08-22
> Setting aside the question of what exactly "undefined behavior" means, why does a language spec have to include it? If there is behavior that cannot be defined, why not just omit it from the standard?
The original reason was that there were things they didn't want to define. For example, signed integer overflow works differently on different hardware architectures. If they defined one behavior in the standard then compilers for architectures that didn't do it that way would have to do something inefficient to make it work the way the standard says it should rather than the way that hardware actually does it.
Calling it "undefined behavior" lets the compiler do whatever the hardware does even if that means the program produces different results on different architectures. It also means that if some new architecture comes out that does it slightly differently, nobody can be surprised when compilers use the native overflow behavior for that architecture.
The flaw was in giving compilers too much discretion. They were generally expected to implement one of the sane versions of signed integer overflow, and specifically the one corresponding to the relevant hardware architecture, but according to the spec they can literally do whatever they want. So we get this:
https://kristerw.blogspot.com/2016/02/how-undefined-signed-overflow-en…
x + c < x -> false
Which means you can't use that to check whether signed overflow occurred even when you know the…
zAy0LfpBZLC8mAC · 2017-04-22
> First, it can assume the programmer is infallible and will never make a mistake, and use that to actually change the code as defined for performance reasons.
You keep saying that it changed the code. It didn't. It compiled exactly what the code said. You dislike C, and that's OK, but that doesn't mean that a C compiler compiling C code into machine language that is semantically equivalent to the C code is somehow changing the program. It's not.
> That is, it's trivial because the hard work of detecting the problem cases is already being done.
No, it's not, you are still committing the same fallacy as before. The compiler doesn't "collect knowledge about undefined behaviour in the program", because that is useless knowledge for the compiler.
The compiler collects knowledge that helps it reason about the program. A big part of that is tracking the range of values variables can take on. That is information that is useful in selecting how to express certain code in machine code. Undefined behaviour plays into this because operations that are defined to have undefined behaviour have no results that need to be considered as possible values of the variable that the result is stored in. So, if the compiler sees an if(x>0&&y>0){ x+=y;x/=2; }, with x and y being ints, it can derive that x will be positive, and therefore, for example, the division can be compiled to a right-shift.
There is no code in the compiler that goes "well, there is this pointer dereference, so let's…