GCC null pointer check removed
gcc.gnu.org
gcc.gnu.org
I was helping a high school student learn how compilers work and we started by opening up GCC and I was astonished at just how mindbogglingly complex it had become. We closed that down and opened up the delightful Bellard "Tiny C"[1] which was comprehensible at the level where this student was working. We created a new 'bool' type for that compiler.
[1]: https://github.com/CraneStation/cranelift
[2]: https://cranelift.readthedocs.io/en/latest/compare-llvm.html
One of the primary expectations / use cases for cranelift is its use for rustc's debug builds: https://github.com/CraneStation/cranelift/blob/master/rustc....
It's the price of being good ;)
> Reload is the GCC equivalent of Satan. See [gccsource:reload.c], [gccsource:reload1.c], and [gccsource:reload.h] if you have a brave soul.
I wouldn't say any of the compilers runs circles around another in general
There is also a tendency for, whatever compiler you used mainly while developing your thing, it appears the best since you have been tweaking for that target the whole time.
You can also look at the flags and the different optimizations you can turn on and off.
MSVC had some issues with C++ compatibility (a long time ago) but overall it is not bad. A compiler that can compile a whole operating system probably has most of its issues ironed out.
That's still black box testing. There ~may be~ almost certainly is hidden state that could mean that the optimization you expect to be applied is not (or an optimization is misapplied) under certain circumstances, and you can't/won't discover this in advance due to those conditions.
> A compiler that can compile a whole operating system probably has most of its issues ironed out.
History has shown this to be mostly wrong, given how many critical bugs have been discovered in GCC since it was capable of compiling linux
Then I actually had to test that working on Envoy proxy. I was disappointed to get utterly incomprehensible error messages. I'm "GCC-by-default", so a coworker suggested I try switching to clang++... and sure enough, clang++ gave me decent error messages.
Competition has definitely served us well
I personally tried to convert one of them but damn is it hard. bring out all the bugs!
https://gcc.gnu.org/ml/gcc/2007-11/msg00460.html https://gcc.gnu.org/ml/gcc/2004-12/msg00888.html
In retrospect I agree more and more with stallman that perhaps we should be actively avoiding helping to build proprietary software, but the tactic they used failed badly, hurt the quality of the software, and turned away prospective contributors such as myself.
LLVM is a lot cleaner (and they are basically even in terms of 1* benchmarks)
Not to mention producing code for a huge list of architectures.
I mean gcc is really advanced and it does work very well. But if there is something that intuitively seems bad in there I wouldn't assume it's that way for a really deep reason beyond my comprehension.
That's still a compiler bug.
But yes, this is a bug in GCC and the title should show that.
A compiler may exploit the assumption that source code does not invoke undefined behavior.
Not at all the expectation I would have for facing a compiler bug. I guess it also helps that the report seemed clear and high quality. (Even had a revision where it was introduced.)
For those who understand UB, the problem is not that 'node' is NULL. The compiler can "prove" that 'node' is non-NULL, but the results are propagated to 'module' from main, which is not correct because 'main' is not the only call site of 'walk'.
I'm not sure I understand why people say this has nothing to do with the UB already derefed therefore not null optimization. How else is the compiler inferring the pointer could be not null?
This is an incorrect interpretation, it only proves that 'node' is non-null. Recall that 'walk' is called twice—once for 'node', and once for 'node->child'. The compiler bug (and this IS a compiler bug, make no mistake) is that the the proof that 'node != NULL' is incorrectly treated as a proof that 'module != NULL', which is not sound, and the bug manifests as incorrect program behavior during the recursive call 'walk(module->child, ...)'.
But how did the compiler determine main.node is not null?
The test case in the bug happens to use UB to generate the constraint, but this is just an example of how to trigger the bug. The bug itself is not related to UB, and there are other ways to generate the same constraint.
Here is an example on godbolt showing what I mean: https://godbolt.org/z/pRW97L
You'll notice that I removed the UB, but the bug is still there. You can see that the 'return;' statement is not emitted because it is colored with a white background. So the bug is not related to UB at all.
Uh, what? The code looks correct to me:
func:
test rdi, rdi
je .L7
mov esi, 1
jmp walk
.L7:
ret
There's a conditional branch on $rdi that jumps to .L7 (which returns) when node is NULL. walk:
push rbx
mov rbx, rdi
test esi, esi
je .L3
mov rdi, QWORD PTR [rdi]
call walk
Note that esi = cleanup = 1 so the branch to .L3 is not taken, and the recursive call to 'walk' is taken. The crash will occur on the instruction before the call, mov rdi, QWORD PTR [rdi]
Because rdi = 0 on the second call to 'walk'.This is totally unlike the user hostility that results from playing gotcha with UB which has also resulted in removing null pointer checks. Those aren't bugs and don't get fixed.
If your desired behavior is to obey the spec and run as quickly as possible then they are not bugs. The user hostility could be argued to be in the spec.
The compiler author are helping you with things like -fsanitize=undefined. Or even lets you define some behavior which is normaly undefined with things like -fwrapv, -fno-strict-aliasing, -fno-delete-null-pointer-checks
But you are asking the compiler to optimize your program, if you give an invalid program to optimize, you get an invalid result. There is no bug there.
However the gcc 9.1.0 in nix/nixos is still affected (but not the default gcc, which is still gcc7).
EDIT: nix just updated to gcc 9.2.0. I was using a slightly outdated release: https://github.com/NixOS/nixpkgs/pull/66836
If you want an easy start re-implement an interpreted language first, then write a compiler for it.
Guile has been getting some really neat optimizations lately (jit, declarative modules, efficient local recursive bindings and much more) and it is really fun to try to follow the code that gets published. Andy's blog over at wingolog.org is also great fun to read.
The last Gcc bug I submitted was a regression that made my program take fully twice as long to run. For that case, they decided making it slow was the correct choice. (Anyway it matched Clang afterward. :-)
The program's main loop, where it had spent 90+% of its time, was just four instructions, which Haswell and later can do in one cycle per iteration, provided the instructions are presented right. Occasionally, it would encounter something that would take 50+ cycles to deal with, and then continue looping. The regression was that it re-arranged the instructions to save a cycle on the slow path and add a cycle to the fast path, so it now takes two cycles per iteration instead of one.
(note: portrait slides!)
> So the algorithm designer (viewed as a machine) is an optimizing compiler?
> Nonsense. Compiler designers have narrower focus. Example: “A compiler will not change an implementation of bubble sort to use mergesort.” — Why not?
(No answer is given.)
An optimising compiler is permitted to replace your bubble-sort by a merge-sort, provided apparent program-behaviour is unchanged.
The only reason real compilers aren't likely to do this, is that the transformation is too sophisticated/too specific, for current compiler technology.
> compiler designers take responsibility only for “machine-specific optimization”. Outside this bailiwick they freely blame algorithm designers
This is wrong.
It's wrong in the shallow sense: in a typical modern compiler, most optimisations are performed at the level of an IR, not at the level of the particular target machine language. Common subexpression elimination, for instance.
It's also wrong in a deeper sense: compilers may apply optimisations which improve time complexity. That is to say, they may transform the algorithm itself. The target machine has no bearing there.
Consider the following unoptimised loop:
int found = 0;
for (size_t i = 0; i != n; ++i) {
if (arr[i]) {
found = 1;
// break;
}
}
An optimising compiler may be capable of essentially uncommenting the commented-out 'break'. This changes the time-complexity of the scan in an obvious way.This optimisation could even be applied by a source-to-source optimiser, with no awareness of the the target machine.
(We can nitpick about possible tradeoffs regarding branch-prediction when adding the 'break', but I think my point is clear.)
I linked to an issue where all current GCC versions emits incorrect memory corrupting output from a pretty boring correct input which has been sitting idle for almost 4 months. The issue-- a morally similar issue of applying a constraint to the wrong scope-- was found in production code, in the wild-- it's not like its some machine generated test case. Currently the finders are working around it with -fno-stack-reuse but it's unclear how much other software is impacted... Should I be lobbying Linux distros to recompile everything with -fno-stack-reuse ? ... hard to say: there isn't an easy way to instrument the compiler to detect when the bug is being triggered.
A rapid and highly knowledgeable response to miscompilation is what I've historically experienced from GCC and thats what's exhibited on the headline issue, but seemingly not in the case I linked to.
void foo(int x) {
if (x) do_something();
else do_something_else();
}
void fn1() { foo(0); }
void fn2() { foo(1); }
When the code is inlined, the compiler can propagate constant and optimize the `if`. Do you really expect two warnings there?These warning would happen all the time.
And anyway, this does not apply to this particular bug which is just a compiler bug (which has been fixed). The warning was there all along:
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.