6 ms·
>it is more important that the implementation—or the design of the implementation—of a piece of software be simple than that it be correct. This is true and wh
by moody__ 3y ago
>it is more important that the implementation—or the design of the implementation—of a piece of software be simple than that it be correct.
This is true and when your compiler actually abides by these values it is shocking how many issues just go away. The problem is that gcc and clang are nowhere near a simple implementation, asking the question of how exactly gcc or clang arrived at some given assembly for some given input is a really complicated answer. So much so that it makes me say that the relationship between the programmer and the compiler becomes adversarial. It would be one thing to just have very complicated optimizations but the whole "undefined behavior lets us to anything" approach is what makes it unbearable.
People can (and do) point at the C spec for fault of this and it is true that if the C spec was more strict then these compilers would not have the free pass to do these crazy miscompilations. However there is nothing stopping these compilers from just not doing that, there is nothing stopping them from just defining their own sane behavior for what the C spec defines as undefined behavior. It is no longer the case where we have a dozen or so C compilers that people need to target with their programs, the spec is not the bottom line anymore.
The Plan 9 compilers are the perfect example of getting this right. They define sane behavior that a reasonable programmer would expect for what the C spec calls undefined behavior. They are not gargantuan, their optimizations are not crazy. It is generally easy to understand how the compiler ended up with the assembly you see in the binary. Yet they are competent enough to selfhost an entire OS. The insane complexity of these other C compilers is simply not mandatory. They are not perfect of course it is still possible to write bad code but the result is no longer pathological, which is a giant help when you're actually trying to figure out what is going wrong.
- spease 3y agoHow do the Plan 9 compilers compare to gcc/clang when it comes to performance or portability?
- kazinator 3y ago> piece of software be simple than that it be correct Here is the thing: which of these is more plausible or at least less far-fetched? A. Write a program that is not correct now, but will eventually be. B. Write a program that is complicated now, but will eventually be simpler. :) Simplicity isn't something you can leave out now and add later, yet correctness can often be treated that way. Even a shop that values correctness above all else still does debugging. (If not code, then debugging their proofs, and debugging whether their formal specification actually implement the functional requirements).
- the_gipsy 3y agoIf you simplify a correct program, it will most likely still be correct. If you "correct" a simple program, it probably becomes complicated.
- kazinator 3y agoIf you algebraically simplify a correct program it will be correct. But the simplicity which is at play in the context of "worse is better" is simplicity of requirement specifications themselves, before any code is written. (I think I'm the one who muddied the waters here by insinuating that correctness is a matter of debugging an incorrect program against a correct specification.) In projects that value simplicity over correctness, what that means is that what is considered correct (as in the requirement we shall implement) is the simpler requirements, which are regarded as incorrect by other projects. Programs that implement the complex requirements are vanishingly improbable to be simplified into programs that implement simple requirements, simply because those are breaking changes. E.g. you can't take a database engine that provides certain consistency guarantees and make it have weaker guarantees in the next version (for the sake of simplicity), without breaking all the applications that depend on the current guarantees. Correctness can be added --- including at the requirements level, not only debugging. It can be because it's often backwards compatible. E.g. adding handling for cases that were previously ignored.
- moody__ 3y agoYou add in simplicity by taking out code. It is very possible (and common) to find better abstractions or methods to approach a problem that reduce the complexity of the code.
- kazinator 3y agoIt is vanishingly uncommon to simplify an entire application, so that it goes from something complex to something whose number one value is simplicity, such that the simplicity is reflected in the actual functional specification.
- kazinator 3y agoThe main problem is this: the undefined status of one construct A in the program changes the behavior of a different, independent construct B in the program, even in cases when, say, B executes first and is correct. Everything is jumbled together in the optimizer, which establishes logical ties between the pieces that are unrelated to the network of intent. If the undefined behavior of construct A causes just A to misbehave, we are still in sane waters.
- kaba0 3y agoWith all due respect, these “adversial compiler” expression just makes zero sense, and takes a lot away from your comment. Guess what, the world is complex, and software has no bound for complexity. Which is better, a multi-million lines compiler that hundreds of people worked on for decades, or a toy one in a couple thousand lines written by a single programmer? What if the former can create 2-10x faster code than the latter (I probably even underestimate it, loop unswitching, vectorization, etc. can account for even more differences). It turns out that we can build abstractions on top of abstractions, and if it’s designed well, it will scale with complexity (which we require). Would you change back to an OS that didn’t handle multithreading as it’s too complicated? Or that wouldn’t use GPUs?
- moody__ 3y ago>With all due respect, these “adversial compiler” expression just makes zero sense, and takes a lot away from your comment. I was describing how it seems these complex compilers look for excuses to give you miscompilations, this "looking for gotcha's" makes the relationship appear adversarial to me. >Guess what, the world is complex, and software has no bound for complexity. My argument is that it really ought to. I think there is a diminishing return. >What if the former can create 2-10x faster code than the latter (I probably even underestimate it, loop unswitching, vectorization, etc. can account for even more differences). In code I've used with both compilers this has not been the case. Good profiling tools and manual hotspot optimization go a really long way. >It turns out that we can build abstractions on top of abstractions, and if it’s designed well, it will scale with complexity GCC has not scaled with complexity well enough, that's why we have people who lament its behavior. >Would you change back to an OS that didn’t handle multithreading as it’s too complicated? Concurrent programming makes problems easier, not harder in my experience. For what it's worth Plan 9 has excellent concurrent programming facilities. >Or that wouldn’t use GPUs? GPUs these days only work on systems in large part due to graces of the vendors themselves. Would nvidia or AMD be usable (enough) on Linux if there was not dedicated people from those companies working on drivers?
- nikic 3y ago> People can (and do) point at the C spec for fault of this and it is true that if the C spec was more strict then these compilers would not have the free pass to do these crazy miscompilations. However there is nothing stopping these compilers from just not doing that, there is nothing stopping them from just defining their own sane behavior for what the C spec defines as undefined behavior. It is no longer the case where we have a dozen or so C compilers that people need to target with their programs, the spec is not the bottom line anymore. Some compilers (like gcc and clang) do give you the option to make most undefined behaviors well-defined, using flags like -fwrapv, -fno-delete-null-pointer-checks, -fno-strict-aliasing, etc. The key thing to understand is that if you use these flags, you opt-in to a non-standard C dialect. Going forward, you are no longer writing C code. Your code is now non-portable. Your code can no longer be compiled by a compiler that does not support these special C dialects. Using these C dialects is a perfectly sensible thing to do, as long as you understand that this is what you are doing.
- trissylegs 3y agoFor example Linux uses -fno-delete-null-pointer-checks
- Buttons840 3y ago> The key thing to understand is that if you use these flags, you opt-in to a non-standard C dialect. Isn't all C with undefined behavior non-standard. Or is there a standard for undefined behavior (obviously not, I would think)? I don't understand why those flags wouldn't be turned on by default. Or do they affect more than just undefined behavior?
- stjohnswarts 3y agoThey affect performance, forcing the dev to insure they aren't doing unsafe/ill-defined things which is almost impossible on huge code bases in c/c++
- Buttons840 3y agoThey are unhappy being forced to write code that is free of undefined behavior? It reminds me of the joke: Alice claims she is very fast at mental math. Bob asks "what's 4821 times 5997?" Alice replies "ten thousand." Bob says, "What, no, that's wrong, very wrong." Alice says, "But it was fast!" Are you telling me C / C++ developers are like Alice? When given the choice between fast undefined behavior and slower but more likely to be correct undefined behavior, developers will choose the faster option that is more likely to be incorrect?