Sometimes, it is a compiler bug: inding and fixing a bug in a C++ toolchain
quick-lint-js.com
quick-lint-js.com
The problem turned out to be a confluence of two circumstances. First there was the compiler bug, which inverted the order of two instructions when popping values off the stack. It decremented the stack pointer and then pulled a now-unprotected value off the stack into a register. Second, vxWorks did not have a dedicated stack for the operating system, so when an interrupt happened it would be processed using the same stack as the process being interrupted. When an interrupt happened right on the boundary between those two mis-ordered instructions, it would overwrite the temporarily unprotected value still sitting on the stack above the stack pointer.
It took us several months to find a way to reliably reproduce the problem, and another couple of months of single-stepping through machine code to finally figure it all out. Good times.
I remember I had a OS bug in my realtime class where I didn't same the ARM core's flags that get set of the compare operations.
In arm, it's two instructions to do something like `if (x > 3) { blah; }`. First do a subtract of 3 and x, and this will set the NZCV flags. If the result of `3-x` is negative, the N flag will be set.
Then do a branch if N instruction afterwards.
https://developer.arm.com/documentation/dui0801/g/Condition-...
So if there was a context switch / interrupt between the two steps, my code would take the wrong if arm. So I'd get things like
if (x > 0) {
y = 5 / x;
}
throwing a divide by zero error, which was super confusing. And I could work around it by doing if (x > 0) {
if (x > 0) { // For real this time
y = 5 / x;
}
}
since it was very unlikely to happen twice in a row.Eventually I fixed it, but that was fun.
It's possible to get it right.
So... you were writing your own interrupt handler then, yes?
Am I reading this right, you're using ARM now? We were using a Pentium PC (and that was already old for 2004, but good enough for for the course, obviously).
The way to find these bugs is systematic randomized testing, where programs are generated by various random processes, and the compiler tested on them. A bug is found either when the compiler crashes, or the code does something different from similar code (or on a different compiler) that should otherwise perform the same computation. If your compiler has not been subjected to this kind of testing it will inevitably have bugs that this testing will reveal, and possibly a large number of them.
In the compiler I most often test this way (the compiler in the Common Lisp implementation SBCL) it will commonly quickly reveal if new commits (at least, changes that hit the compiler) have bugs. Very handy.
As examples, I've seen intrinsics for new x86 instructions being broken in both GCC and Intel's compiler, as well as Profile-guided optimizations crashing in MSVC with a large program (I reported this to MS but I'm not sure they ever got around to looking at it).
The lesson I took from this is to not use the latest and greatest features except in toy projects.
I use a niche language.
It feels like I find a compiler bug there every month.
> Then I tried a Release build with GCC (MinGW), and I successfully reproduced the bug.
GCC (MinGW) is probably the least battle tested of the major C++ compilers on Windows. The vast majority of commercial software on Windows is built with MSVC. Google builds Chrome with Clang. GCC on Windows doesn’t have nearly the same number of (paid) eyeballs looking for problems.
The chances of finding compiler bugs go up if you use less widely used compilers on a platform.
[1] https://bugs.llvm.org/show_bug.cgi?id=45021
[2] https://github.com/build2-packaging/libmingw-stdthread#backg...
The code was something like:
if (foo.bar < 5) {
a()
} else {
b()
}
But `b()` would never get called. I stepped through with a debugger, checking the relevant values,and the `else` branch would never execute.I remember inspecting the IR and seeing that yes, in fact, the `else` branch was simply missing. It would come back if I made a trivial change like deleting a blank line before the conditional block, which lead me to believe the compiler must be getting into a weird state somehow.
I showed my work to the senior engineers and they confirmed that yes this was a compiler bug. We submitted a bug report to MS but never heard back from them. I wonder if it ever got fixed?
More interestingly, which other statements are wrong?
There's a _huge_ difference between a toolchain bug and a compiler bug. It's not really a slight inaccuracy.
> More interestingly, which other statements are wrong?
"""I switched to the x64 version of VS Code and installed quick-lint-js from the VS Code Marketplace. The buggy squiggly appeared! I successfully reproduced the bug.
Conclusion: The problem was specific to Windows x64 builds."""
The conclusion here is quite a stretch - the author has _not_ confirmed the problem is specific to x64 builds, they have confirmed they can repro it in x64 builds. The reason I call it out is because the author _is_ careful to differentiate between hypothesis and conclusion in the rest of the blog post, so this is either a copy error or a misunderstanding on their part!
The statement is correct though. The bug was specific to Windows x64 builds when compiled with GCC-MinGW.
if x and y then do z
operated wrong, so i had to do:
if x then if y then do z
Kind of hard to wade through the overly descriptive story to figure out what the problem was.
As an aside, I didn't find the writeup to be overly descriptive. The author does a great job linking every step and how they came to each conclusion along the way, even including hypotheses where they came out empty-handed. When a writeup omits these, it makes the author look like they were somehow enlightened to find the solution, instead of just following a methodical approach.
Thanks for the feedback. I redesigned that code block, making it a side-by-side diff. Is it easier to understand now?
But it was in a semi-experimental feature that did some really weird stuff - it was for inline assembly code without an `asm` block - you could just inter-mix MIPS assembly instructions with your C++ code statements. Or rather you could if it worked properly. It was an ambitious feature - a bit too ambitious I think.
That is not a compiler bug.
This is an odd conclusion as the author just layed out how he didn't test with 32-bit vscode.
Make sure you understand all of the subtle rules about how to use it. If you can, use intrinsics instead.
One particularly tricky/subtle item is the early-clobbers indication.
Lately just postgresql kept using dlltool, nobody else.
So if you need that you'd need to either fix dlltool, or do it manually in the code.
https://developercommunity.visualstudio.com/t/lnk1179-invali...