It's not me, it's the compiler
parsa.wtf
parsa.wtf
I don't mean this in a snarky way or the like... and I guess the fact there are so few of these are a good indicator that the current processes are working decently well... but I would be very curious on a post-mortem from maintainers on how this bug got through. Miscompiles feel like the scariest sort of thing.
Maybe the deep and dark secret is simply that compiler optimizations are just extremely prone to mistakes and we all are just lucky enough that most messed up optimizations will break _something somewhere_ early enough to not get merged.
Simplest answer is that there's no representative test case in the test suite yet.
Unfortunately the problem of compiler testing is a very challenging one IMO. You can't test it exhaustively, or perhaps if you did write such a suite it would take longer to execute than is feasible.
> we all are just lucky enough that most messed up optimizations will break _something somewhere_ early enough to not get merged
Your compiler is only as good as the set of code people routinely use it on. Rust has exploded in popularity but it's still much less popular than C, C++.
This is definitely true - there has been quite a lot of formal work done to prove that optimisation passes don't change semantics. I guess it hasn't made it into Rust yet. Or maybe even LLVM.
Larger-scale things operating over an unbounded amount of instructions require significant amounts of effort of verification on each pass. CompCert apparently has 1200LoC of proving DCE, one of the simplest whole-function passes - https://github.com/AbsInt/CompCert/blob/02fc017cf69210db5fd5...
My experience writing Roq (very limited!!!) is that it also lends towards kinda brute forcing your way through something. If you have your set of proof lemmas and you tie it all together there's not that much incentive to simplify the proofs (that are irrelevant anyways, according to the theories). So the 1200 LoC might be "oh we could have maybe done this in 400 with more thinking but ... well... this is working yeah?"
trying not to trivialize it all. Just kinda hoping that we do have a more provable future ahead of us, and that CompCert represents (hopefully) an upper bound of difficulty just due to the nature of the source language.
Of course, I also feel this way about the vast majority of optimizations. If the compiler can optimize a piece of code, it can also show the user what it thinks the optimal code would be so that they can rewrite it themselves, if they so choose. This both prevents these kinds of miscompiles and prevents compilation times from exploding because the compiler doesn't need to do much work, it primarily just translates the human readable code into machine code.
Tying such in with optimizations largely just does not work, given that functions with an unused return value exist, being dead code after inlining, and compilers can emit dead code themselves (e.g. duplicating a piece of code, and then DCEing unused things in one copy; or dead branches of inlined functions); never mind the complete unpredictability of various compiler heuristics now being able to change warning behavior (gcc has some of this type of optimization-dependent warnings, and it annoys the hell out of me)
Copying the compiler's work into your code falls apart the moment you target multiple architectures, as different architectures can often benefit from quite-different implementations.
And there's the whole thing that most compiler optimization stages often do not translate well or at all to the source language (e.g. LLVMs poison semantics do not exist in C, nor any language afaik; goto spam!; and there are optimizations that can be applied to safe code that cannot be translated back to safe code without entirely undoing the optimization (e.g. replacing known-unused variables or array elements with undefined ones))
This is not straightforward. Apart from the mapping from a several-layers-deep optimization to the source level being very difficult, it may not be even representable in the original language. And even if it is, it may require complicating the code significantly. Part of the point of compiler optimization is so that you can write straightforward code and still have it be fast.
Compilers will often warn on dead code, but only at fairly early stages of translation where it's obvious that something is definitely dead code in all possible contexts and the fix is obvious. These rules are different to what the optimizer actually uses much later on in the pipeline.
Turns out it was a bug in the Java runtime, one that was fixed in v1.0.3.
as an aside, I think the original code, with the if statement, was clearer and would be easier to debug, etc, even if it was a bit longer
It's about the cognitive load and having to follow branches.
The second version minimizes the cyclomatic complexity, taking it to 1. The reader doesn't have to keep the if statement in mind when reading through the code, and doesn't have to worry about all the ways the code can get there if they want to modify it (e.g. to add logging, metrics, other logic).
Whether or not consume should be a separate function depends on how often it's called. Here, I'm guessing it's once. :)
And even if you don't need to keep an if statement in mind, you still need to keep the variable in mind anyway.
Then years later I started playing with Nim when it was still in beta, I think I found 3 compiler bugs in a few weeks! Reported them all, all got fixed!
And then a few years later found a bug in LLVM, doing weird stuff with Rust and SIMD intrinsics. Was difficult to even communicate what was going wrong but did get it reported and confirmed eventually!
If you have a very large scale test suite for your application, a large codebase, and exert most compiler features including the optimizer , you’ll probably find a few every time you upgrade .
In most other languages this would be written as "this.current_index" or some other property name.
self is the name for a value of this type, apparently a type named LexerConsumer, this function took "&mut self" a mutable reference to a LexerConsumer. In Rust using any variety of "self" in this way means the function can (if you want) be called as a method on a LexerConsumer like this:
foo.consume(); // If foo is a LexerConsumer, we will call that `consume` function and pass it a mutable reference to foo.
Presumably the LexerConsumer doesn't give its fields names, so hence self.0 meaning just the zeroth field. Perhaps the author couldn't think of a good name for it.