12 ms·
PLOS2021: ISO-C became unusable for operating systems
- davidgerard 5y agodirect PDF link: https://www.yodaiken.com/wp-content/uploads/2021/10/yodaiken_plos_c2-.pdf https://www.yodaiken.com/wp-content/uploads/2021/10/yodaiken...
- wffurr 5y agoThanks. That embedded viewer is unusable on mobile.
- bombcar 5y agoThe fact that a compiler is allowed/encouraged to silently remove whole sections of code because of some obscure factoid is an amazing source of footguns. At least the warnings are getting a bit better for some of these.
- Asooka 5y agoAbsolutely, those optimisations should be opt-in, otherwise it's impossible to reason about the correctness of your code. At work we had to replace some arithmetic by inline assembly, as there was literally no other way of making the compiler generate the correct expression.
- cocoafleck 5y agoI'm very curious to learn more about this as I thought it was easily possible to opt out of the overly aggressive defaults of GCC, Clang, and the like.
- bombcar 5y agoIt is possible - but not whilst maintaining standards-compliance and there are always/often another one where you least expect it. At the root is taking a “assume the programmers know what they are doing” language and turning it into a “assume the programmers are drooling morons” language.
- not2b 5y agoI think it's the opposite: compiler writers under pressure to produce fast code assume that the programmers are smart, not morons, and that they understand the rather complex rules. For example: the compiler is assuming someone isn't ignorant and knows, for example, if they want to write a variable as one type and read it as another, they need to consider two things: they are now in implementation-dependent territory (big endian vs little endian effects, for example), and they need to use unions, not just casts, if the same data can be interpreted as two different types and neither is array of char. Failure to use unions properly triggers aliasing problems.
- ncmncm 5y agoAmusingly, unions are also defined so that operations that pun types are undefined. Gcc has private extensions that provide a way to pun types in unions, but those are not portable. Specifically: if you have a union with members a and b, and you assign into member a, then reading from member b afterward is, by the C and C++ Standards, usually undefined. You are presumed to have some way to know that member a is live, and use that. You can then write into b, and later read that back out.
- tpush 5y agoThat’s undefined only in C++ but not in C, AFAIK.
- not2b 5y agoThe C99 and C11 standards say that the behavior is unspecified (in standard-ese, "unspecified" and "undefined" are different), meaning that the standard doesn't say what you get. Of course that's true: the result is different on a big-endian and a little-endian machine. Packing the bytes of a double might generate a signalling NaN so you get a trap when you read it, or not, and the standards don't require that floating point representations match the IEEE standard, either.
- Asooka 5y agoIt was many months ago and I no longer remember the full details, plus it's proprietary code, so I can't tell them to you. But the gist was that after optimisation, some floating point arithmetic was done in different order by different compilers. This particular expression really needed to be computed in exact order and, since nothing else worked, we had to just write it in compiler-specific assembly. IIRC some language extensions are coming to better specify float semantics on a per-function basis that might help one day.
- int_19h 5y agoCould you give an example of such input?
- not2b 5y agoWithout these optimizations, you can't write fast scientific code in C. This was realized back in the early 1980s and it's why those rules were added. In Fortran the aliasing rules are even stricter: given two arrays passed in as arguments the compiler can assume that they do not overlap, for example. I remember messing that up as a student long ago and getting strange results. The Fortran rule was to enable vectorization, which has been done for many decades.
- vyodaiken 5y agoI doubt that very much, and one of the points of the article is that there is a shortage of data to back up claims like yours. In any case, for people who want to write OS or cryptography or embedded systems or arithmetic libraries or ... in C, this is not a relevant point.
- Gibbon1 5y agoMy understanding is the reason FORTRAN is faster than C isn't because of stupid stuff like noalias and the like. It's because FORTRAN has arrays and C doesn't.
- qsort 5y agoWhy would that put C at a disadvantage performance-wise? Array decay is, with hindsight, an unfortunate feature, but optimizing access to contiguous memory is very low-hanging fruit as far as compiler optimizations go.
- rini17 5y agoSome optimizations are low-hanging fruit only if you know length of the array. Which C avoids by design at all costs.
- Gibbon1 5y agoIf you want better safety, runtime and compile time performance the lowest hanging fruit would be to fire the C standards committee and hard fork C++/C into C++ and C only ecosystems.
- alerighi 5y agoI don't get how the fact that the compiler can remove or modify the code was thought to be a good idea. I get removing unused functions, but not conditions and changing the flow of the code. If there is unreachable code, best to issue a warning and let the programmer fix it. The compiler should optimize without changing the semantic of the code, even if it contains undefined/unspecified behavior. To this it's impossible to write C without using a ton of non standard attributes and compiler options to just make it do the correct thing.
- astrange 5y ago> The compiler should optimize without changing the semantic of the code, even if it contains undefined/unspecified behavior. That is what it does. "Undefined behavior" is a lack of semantics, so it is preserving semantics when it leaves those paths out. You can make a "defined C" with `-fsanitize-trap=undefined`, but C was never a high level assembler, and performance is critical for C users too.
- Asooka 5y agoC was always a high-level assembler. UB was meant to be "this cannot be defined portably, refer to your CPU's documentation". What do you gain by making C unusable by default? Just make it 5% slower by default, but possible to reason about and give people the option to shoot themselves in the foot. I don't know what more you need than the creator of the language telling you "this is not possible to implement sanely".
- pcwalton 5y agoEven assuming the 5% number were correct (depending on how expansive your definition of UB is, it may not be), asking everyone who doesn't adjust their compiler flags to accept a 5% slowdown for some theoretical benefits is at odds with economic reality.
- vyodaiken 5y agoMaybe you should tell the Linux developers that they are making a mistake.
- deleted 5y ago[deleted]
- po1nt 5y agoI think it's about time to abandon C language entirely.
- Asooka 5y agoSometimes I do wonder if all these UB optimisations aren't pushed by people aiming to make C and C++ unusable, so that people will be forced to move to other languages.
- jjtheblunt 5y agoUB = Undefined Behavior ?
- MobiusHorizons 5y agocorrect. In my experience a focus on UB (often actually said U.B.) tends to come from the C++ community, although a lot of it trickles down to the C community as well. I can recommend watching a few talks from C++ conferences. I especially enjoyed ones by Matt Godbolt, and Miro Knejp.
- ncmncm 5y agoNot correct. Most UB is the same in C and C++. If you disallowed optimizations based on UB, it might have more effect on C++ programs, just because C++ tends to more deeply inlined abstractions that can benefit more from them. But those don't tend to need much attention. The cases people worry about are more common in C code, just because the concept of type is less important in C.
- MobiusHorizons 5y agoHow is this not correct? UB = Undefined Behavior. Or are you saying that UB is not from the C++ community? That could certainly be true, I am just describing the situation as I see it where C++ disallows more types of casting or reinterpreting than C does (even if compilers allow it). One salient example I heard was that the only way to legally read the bits of a float in C++ is to memcpy the float onto an integer type. whereas in C its legal (I believe by using a union).
- gjm11 5y agoThe actual title of the paper is "How ISO C became unusable for operating systems development". Is there a particular reason why the first and last words have been removed here?
- Hemospectrum 5y agoHN removes words like "how" and "why" automatically. I think it's intended as a clickbait filter. Sometimes the mods change it back. The submitter may have left out "development" to fit the title in the character limit.
- pdw 5y agoHow much of this is driven by modern C++ style? I always assumed optimizers needed to become much more aggressive because template-heavy code results in convoluted IR with tons of unreachable code. And UB-based reasoning is the most effective tool to prove unreachability.
- not2b 5y agoNone of it: we're talking about C here, and every point in the article applies to the C standard as it existed when Linus posted his message to comp.os.minux so long ago.
- aw1621107 5y ago> template-heavy code results in convoluted IR with tons of unreachable code Does it? Honest question; my impression was that template-heavy code can tend to produce deep call trees, but not necessarily outright unreachable code unless you count instantiations ruled out by SFINAE/std::enable_if/tag dispatching, for which UB-based analyses are not necessary. In addition, I thought template (meta)programming relied very heavily on compile-time knowledge, which seems to obviate the need for UB-based analyses in many cases. I'm not particularly experienced, though, so maybe there's a gaping hole I'm missing.
- ncmncm 5y agoIt is quite common for any heavily inlined code to have lots of dead sections. Template code is very frequently heavily inlined. So, it is common for compilers to prune out the dead branches. But C code is often heavily inlined, too, and similarly pruned.
- aw1621107 5y agoGuess I need to pay more attention to the template code I use. I knew that they tended to rely very heavily on inlining for good performance, but I haven't read as much about the presence/absence of dead code, though to be fair maybe the deadness isn't always obvious to the programmer? In any case, thanks for correcting me!
- alerighi 5y agoTo me the stupid thing is the abuse of undefined behavior for changing the semantic of the code. The fact that a behavior is not defined in the standard doesn't mean that on a particular hardware platform it doesn't have a particular meaning (and most C programs doesn't need to be portable, since C it's mainly used for embedded these days and thus you are targeting a particular microcontroller/SOC). These optimizations leave for C++ folks. C doesn't need all of that, just leave it as the "high level assembler" that it was in the old days, where if I write an instruction I can picture the assembler output in my mind. Optimizers should not change the code semantics to me. Unfortunately with gcc it's impossible to rely on optimizations, so the only safe option is to turn them off entirely (-O0).
- pcwalton 5y ago> These optimizations leave for C++ folks. C doesn't need all of that Yes, it does. In fact C needs it more, because of the "for (int i = 0; i < n; i++)" idiom. At least idiomatic C++ code uses iterators.
- astrange 5y agoApproximately the entire software stack on any PC is shipped with gcc/clang/MSVC with optimizations turned on, and yet the Earth keeps turning.
- gHosts 5y agoSo define the behaviour and get it pushed through the standards committee. Or even define the behavior and get your compiler writer to implement it. ps: If I index past the end of the array... what behaviour are you going to define?
- pcwalton 5y agoIn general I've long been very skeptical of removing optimizations that rely on undefined behavior. People say "I'd happily sacrifice 1% for better theoretical semantics", but theoretical semantics don't pay the bills of compiler writers. Instead, compiler developers are employed by the largest companies, where a 1% win is massive amounts of dollars saved. Any complaint about undefined behavior in C must acknowledge the underlying economics to have relevance to the real world. As the paper notes, there are plenty of alternative C compilers available to choose from. The reason why GCC and LLVM ended up attaining overwhelming market share is simply that they produce the fastest possible code, because, at the end of the day, that is what users want. If you want to blame someone, blame the designers of the C language for doing things like making int the natural idiom to iterate over arrays even when size_t would be better. The fact that C programmers continue to write "for (int i = 0; i < n; i++)" to iterate over an array is why signed overflow is undefined, and it is absolutely a critical optimization in practice.
- userbinator 5y agoThe reason why GCC and LLVM ended up attaining overwhelming market is simply that they produce the fastest possible code No, I think it's more because they are free. In my experience, ICC can be much better at instruction selection while also not being so crazy with exploiting UB.
- Someone 5y agoICC nowadays is based on LLVM (https://software.intel.com/content/www/us/en/develop/blogs/adoption-of-llvm-complete-icx.html https://software.intel.com/content/www/us/en/develop/blogs/a...). That might mean the differences have mostly disappeared, but that may depend on what the front end (icx vs clang) does.
- pcwalton 5y agoI'm also skeptical about the often-claimed superiority of ICC. The numbers I've seen are very equivocal. Besides, it's irrelevant: there are lots of free C compilers that don't exploit UB, and also rarely get used.
- astrange 5y agoThis is a typical whining about UB article, but removing it won't get what you want, in particular your program still won't behave correctly across architectures. Overflow on shift left may be undefined, but how do you want to define it? If you want a "high level assembler", well, the underlying instructions behave differently on ARM, x86 scalar, and x86 SIMD. The reason they claim program optimizations aren't important is because you can do it by hand for a specific architecture pretty easily, but you'll still want them when porting to a new one, eg if it wants loop counters to go in the opposite direction.
- vyodaiken 5y agoNobody claims optimizations are not important.
- Asooka 5y agoYes, your program will have different semantics on different architectures. This is already the case with e.g. big vs little endian. But you will be able to reason about the program's semantics rather than going "I sure hope there's no UB in here" and throwing your hands up.
- tialaramex 5y agoA bad workman blames his tools, so they say. There is a large population of C "real programmers" who, when they write a C program that unsurprisingly doesn't work, conclude this must be somebody else's fault. After all, as a real programmer they certainly meant for their program to work, and so the fact it doesn't can't very well be their fault. Such programmers tend to favour very terse styles, because if you don't write much then it can't be said to be your fault when, invariably, it doesn't do what you intended. It must instead be somebody else's fault for misunderstanding. The compiler is wrong, the thing you wanted was the obviously and indeed only correct interpretation and the compiler is willfully misinterpreting your program as written. Such programmers of course don't want an error diagnostic. Their program doesn't have an error, it's correct. The compilers are wrong. Likewise newer better languages are unsuitable because the terse, meaningless programs won't compile in such languages. New languages often demand specificity, the real programmer is obliged to spell out what they meant, which introduces the possibility that they're capable of mistakes because what they meant was plain wrong.
- boomlinde 5y agoWhat an unimaginative, dull-witted way to skirt around the concrete criticisms made in TFA.