Categories
C standard programming languages

undefined behavior and the purpose of C

The C programming language is designed to  let programmers  work  at both low and high levels. In particular, for applications like operating system development,  cryptography,  numerical methods, and network processing, programmers can go back and forth between treating data as representing abstract types and as machine addressable sequences of bits, between abstract operations and low level, machine dependent operations.  The language has, by any account, been spectacularly successful and remains the essential systems programming language. Over 45  years, one would expect some limitations would show up,  but C currently suffers not just from it’s own limitations but from the combination of being being profoundly out of fashion (see the somewhat mixed up critique here for an example)  and the misguided bureaucratic attentions of the ISO standards process which has had  a negative effect on the development of C compilers. At the heart of the problem is that the C standard process is muddying C semantics while also facilitating dubious “optimizations” from the larger compiler projects.  The key aspect of these “optimization” is that they break C programs that work with lower levels of “optimization” or earlier  The situation was concisely summarized by Linus Torvalds: “the C standard is _clearly_ bogus shit“.  And Torvalds is not the only person to object.

  • An alarming  CERT security alert   was issued in 2008:   “Some C compilers optimize away pointer arithmetic overflow tests that depend on undefined behavior without providing a diagnostic (a warning). Applications containing these tests may be vulnerable to buffer overflows if compiled with these compilers.” 
  • Linux developers forced  GCC and Clang compiler developers  to provide an ad-hoc collection of compiler flags for an unstable non-Standard dialect of C that turn off some of the “optimizations” supposedly permitted by the C standard.
  • A  warning from the chief Clang architect that compiler changes  mean that “huge bodies of C code are land mines just waiting to explode” (followed later by a suggestion that programmers switch to other languages).
  • A  paper from the foremost researcher in cryptography asking for a version of C that would be “safe” from dangerous “optimizations”.
  • A polite and  completely ignored  academic proposal by Regehr et al for a “friendly” C compiler and standard. 
  • A really excellent critical  academic analysis, by M. Ertl  also ignored, and see also  this paper. 
  • A graphics programmer practitioner critique  by Eskil Steenberg.

As an example of what provoked these reactions, consider a small C program that, without a warning, when passed though the current gcc and Clang highest level optimizers can’t recognize a negative number as a negative number. The same program  works correctly when compiled at lower levels of “optimization”, or with optimizations by earlier versions of clang and gcc or even currently by the Intel C compiler.  And consider the following, idiomatic C code, transformed into an infinite loop that never gets to emergency shutdown.

//keep opening valve until pressure down or do emergencyshutdown
for(int i=0; i >=0; i++){ //try it a bunch of times
if ( getpressure() > DANGER)openvalve();
}
if (getpressure() > DANGER)emergency_shutdown();

You can see how this works in the gcc8 generated machine code for the x86 – a processor family that natively wraps arithmetic overflow to do the right thing. The increment, the check, and the call to emergency shutdown are all deleted by the compiler – as an “optimization”. 

/Machine code output of GCC8 with O3 optimization level.
f:
sub rsp, 8
.L4:
call getpressure
cmp eax, 10
jle .L4
call openvalve
jmp .L4

Of course, this code is perhaps not the best C code, but it accords with the customary and expected behavior of the language over decades of practice. Earlier versions of Gcc compile the code correctly as does the Intel compiler, even later versions compile it correctly unless the higher optimizer levels are invoked. So tested, working safety critical code, moved to a new version of the same compiler will fail with no warning (whoops, sorry about the refinery explosion). And all this apparently meets the requirements of the C standard.

  1. The ISO C standard committee  has spent decades on a botched effort to strengthen C type system in a way that has produced a contradictory and complex set of rules under which a vast body of C code can be considered to embody “undefined behavior” (often with no remedy in the Standard).  Not only is is not possible to access “device memory”, but it is impossible to write a memory allocator let alone a virtual memory system, check for arithmetic overflow, reliably check for null or invalid pointers, checksum network packets,  encrypt arbitrary data,  catch hardware interrupts, copy arbitrary data structures, use type punning, or, most likely,  to implement threads. Application programs written in C face the same problems and worse. For example, the standard POSIX function  “mmap” appears to be intrinsically “undefined behavior”.  This only going to get worse. For example see recent proposals to add pointer “provenance” to the standard.
  2. The primary open source compiler developer groups have seized on the sloppy definition of  undefined behavior in the Standard to justify a series of increasingly dangerous silent code transformations that break working code. For reasons explained below, I will call these transformations  Beyond Guideline Transformations (BGTs). 

The key passage in the C11 Standard is the following (my highlight in red)


undefined behavior behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements
  • 2 NOTE Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).
  • 3 EXAMPLE An example of undefined behavior is the behavior on integer overflow.

 The compiler developers have seized on “imposes no requirements” to claim a license for arbitrary program transformations in the presence of “undefined behavior”. As pointed out by Eskil Steenberg the C99 standard changed the wording of this provision to  “Possible”  where the C89 standard used the word “permissible” – which would imply that the compilers must restrict themselves to the range of behaviors. The C99 Standard also moved the sentence on the range of behaviors to a note so which in the arcane world of ISO standards, transforms a rule into a “non-normative”  suggestion.  This has been taken to license reasoning such as:  since overflow of signed integers is “undefined behavior” and the standard imposes no requirements, the compiler can assume that signed overflow never happens and delete a test for overflow as an optimization.    GCC/Clang developers, with the tacit acquiescence of the ISO standards committee, now claim the compiler is free to assume undefined behavior “can’t happen” and that the compilers can silently remove programmer checks for it – even while generating code that produces the condition. This reasoning, such as it is, clearly violates the Standard definition of “optimization”, but nobody seems to care.

Clang architect Chris Lattner believes there is nothing the compiler developers can do: 

The important and scary thing to realize is that just about *any* optimization based on undefined behavior can start being triggered on buggy code at any time in the future. Inlining, loop unrolling, memory promotion and other optimizations will keep getting better, and a significant part of their reason for existing is to expose secondary optimizations like the ones above. To me, this is deeply dissatisfying, partially because the compiler inevitably ends up getting blamed, but also because it means that huge bodies of C code are land mines just waiting to explode.

You’d imagine that faced with warnings like this, complaints from influential C practitioners like Torvalds and DJB, a CERT advisory, and a number of academic papers, there would be some concern at the WG14 committee that develops the C standard – but no. 

When I visited the ISO WG14 C Standards committee meeting in Pittsburgh 2018 to ask for some corrective action, I found a peculiar lack of urgency.  The most interesting comments were from a long-time committee member who seemed to believe that C had become a hopeless cause and from another attendee who suggested that fixing contradictions in the standard was not urgent because there are so many of them.

I refer to these strange transformations as “Beyond the Guideline”, because they violate the range of possible/permissible behaviors in the standard definition of undefined behavior.

At one time, it was generally understood that the undefined status of signed integer overflow meant that the native arithmetic operations of the target processor determined the result: so that often  “i+ j” in C can be compiled to a single machine instruction.  If the result of the addition overflows the processor representation of an signed integer – anything could happen -the burden was on the programmer, processor, and execution environment to detect and handle the problem.  This is either “ignoring the situation” or “behaving during [..] program execution in a documented manner characteristic of the environment” : the compiler was not required to do anything about the overflow.  In fact, the standard modern processors  roll over as expected, although there are some processors that generate traps and some embedded devices with things as odd as saturating arithmetic.  None of that is a problem under the customary compilation of the language. The compiler can just leave it up to the underlying machine, the execution environment, and the programmer to sort out. The “beyond the guideline” (BTG) approach is relatively new and is still unknown to many practitioners and involves major conflicts with the ANSI C definition in the second edition Kernighan and Ritchie book: which is the definitive specification .   You can follow Linux’s lead and block the overflow  “optimization” with “frwapv”  which forces wrapping semantics (in some places) but that’s not even necessarily what programmers want and there are many other places where C code is vulnerable to similar transformations. For example, in machines with linear addressing, it is not unusual to check a pointer is in a range with code like:

if( p < startptr || p >= endptr)error()

This code is straightforward and easy to compile on the dominant processor architectures of the day, ARM, x86, MIPS, and similar. It might be a problem on, say, segmented architectures or elaborately typed architectures such as the not lamented Itanium.  It’s also possible that, depending on how the pointer “p” is generated that it might violate complex, contradictory,  and hard to understand type rules for pointer comparisons and fall into the “undefined behavior” bucket. In that case, the compiler developers claim that they can silently delete the check – after all, undefined behavior “can’t happen”. All the code that depends on these types of checks, checks that are reasonable C code and that have worked for decades, may encounter an unpleasant surprise at any future time when the compilers get around to “optimizing” those checks away. And, as usual, there is no guaranteed way in the Standard to “fix” this check. Suggestions involving converting pointers to integers run into other undefined behavior and the optional status of the required data type.

Since common sense has not sufficed, both the compiler development groups and the Standards need an intervention.  The decision to ignore the guideline is easily repaired, mostly by reversing the wording and formatting changes. 

undefined behavior behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements other than that : Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). Undefined behavior is common in C programs and translators should document operation in the presence of undefined behavior. It is not acceptable to change the semantics of correct program structures due to undefined behavior elsewhere. 

I’d add the following note as well

NOTE: Undefined behavior, particularly, due to non-portable constructs is a normal component of C programs.  Undefined behavior due to errors that the translator can detect should produce a diagnostic message.

One of the common arguments made by some of the compiler developers who champion BTG transformations is based on a false dichotomy: that without license for BTG transformations, compilers would have to provide an intrusive run-time that guaranteed predictable results and the Standard would have to spell out every possible behavior. This speaks to a fundamental misunderstanding of the purpose and use of C. 

  • How can the compiler distinguish between an erroneous read of uninitialized memory which will produce unspecified results (or even a trap on some architectures) and a syntactically equal chunk of code that reads a memory mapped device control register which  cannot be written? The designers of C solved the problem by leaving it up to the programmer and the execution environment: the compiler produces a load operation, the semantics are not its problem – they are “undefined behavior”. 
  • How can the compiler or standard distinguish between a mistaken use of an integer pointer to access a floating point number and a numerical methods expert’s shortcut divide by 2 which involves changing the binary representation of the exponent? That’s not the responsibility of the compiler.
  • How can the compiler tell the difference between a null pointer dereference in user space where it will, usually, cause a trap on memory mapped systems, and one where the zero page is accessible memory? It cannot: the programmer and operating system can handle it. The best the compiler can do is warn.

What would be nice is if the compilers could warn about possible errors and if the Standard provided opt-in methods of locking down access methods and preventing common errors like array boundary and type violations.  C developers increasingly rely on sophisticated static analysis tools and test frameworks to detect common errors and have embraced methodologies to limit the kinds of haphazard programming that was common earlier. But all of that is very different from retroactive, silent, language changes based on opaque and often ill considered rules.

The developers of the C Standard were forced to add a hurried character pointer exception to their type rules when they discovered they had  inadvertently made it impossible to write the fundamental memcpy function in C. The exception makes the type rules ineffective while still being cumbersome ( Brian Kernighan made exactly this criticism of Pascal decades ago). And it certainly did not fix the problem.  For example, the Standard only makes malloc/free/calloc and similar work within the type system by treating them as special exceptions to the type rules (in a footnote, no less) and apparently nobody realized that those functions could not then be implemented in Standard C. 

Lattner is absolutely correct about the “land mines” which this approach to “optimization” produces but he is wrong to imply that the compilers are required to adopt the BTG  approach to undefined behavior or that the code triggering undefined behavior is necessarily “buggy”.  C compilers could optimize on undefined behavior, as they always have, without violating the guidelines suggested in the standard: as Intel’s ICC still does. The choice to go beyond the guidelines is an engineering choice.

The invisible advantages of BTG optimizations 

Some of the defenders of BTG optimizations argue that code producing undefined behavior is not actually C code and so doesn’t have a meaning. That kind of pedantic legalism has no support in the history of the language, the behavior of compilers (which have no trouble compiling and producing expected behavior from the same code prior to “optimization”) or the language of the standard – it’s just a pompous way of excusing crappy engineering.  The C-Standard is a train-wreck on its own, but it does not mandate the bizarre paradoxical undefined behavior GCC and Clang are choosing to haphazardly implement. 

The supposed motivation for BTG transformations is  that they enable optimizations. There are, however, only a bunch of dubious anecdotes to support the claim of great optimizations – not a single example of careful measurement.  And there are studies that show the opposite. Here’s the justification from one of the LLVM developers:


This behavior enables an analysis known as “Type-Based Alias Analysis” (TBAA) which is used by a broad range of memory access optimizations in the compiler, and can significantly improve performance of the generated code. For example, this rule allows clang to optimize this function:
float *P;
 void zero_array() {
   int i;
   for (i = 0; i < 10000; ++i)
     P[i] = 0.0f;
 }
into “memset(P, 0, 40000)“. This optimization also allows many loads to be hoisted out of loops, common subexpressions to be eliminated, etc. This class of undefined behavior can be disabled by passing the -fno-strict-aliasing flag, which disallows this analysis. When this flag is passed, Clang is required to compile this loop into 10000 4-byte stores (which is several times slower), because it has to assume that it is possible for any of the stores to change the value of P, as in something like this:
int main() {
  P = (float*)&P;  // cast causes TBAA violation in zero_array.
  zero_array();
}
This sort of type abuse is pretty uncommon, which is why the standard committee decided that the significant performance wins were worth the unexpected result for “reasonable” type casts.

This is a ridiculously contrived example, of course. The program, as written, makes no sense and will crash on any conceivable system.  If not setup to fail, any half-decent C programmer could have used a local variable parameter for the pointer (in which case -fno-strict-aliasing has no effect) or called memset directly. These are the kinds of things that C programmers find and fix using profiling.  Oddly, the compiler misses the big optimization, which is to do nothing – since nothing is done with the array before the program exits. So here we have the usual elements of an example of why C compilers need to make “unexpected” BTG results out of undefined behavior

  1. A minor or unnecessary optimization
  2. of a contrived example that shows nothing about actual applications
  3. that is possibly a undefined behavior optimization but not a BTG transformation
  4. that is accompanied by absolutely zero in the form of measurement
  5. that ignores actual optimization possibilities.

A  talk by Google’s Clang expert, Chandler Carruth on this topic has an even worse example.  Carruth gives an example of array indexing that has all of the first 4  characteristics above, plus the benefit that if you look at the generated code, the supposedly much better form taking advantage of undefined behavior for optimizations is not really any better than the original.  He also explains how the compiler cannot determine whether programs are correct (!) and makes an unfortunate analogy between compiler behavior and APIs. Imagine if operating systems, for example, were to caution developers that “anything can happen” if the parameters passed to an OS system call are not as expected. Programming languages should minimize surprise.

In the real world, the justification for BTG transformations is convenience for the compilers – which are increasingly driven by not only artificial benchmarks, but by their support for C++ and Java which are languages that have very different semantics. Operating on intermediate codes that are stripped of C specific semantics, it is difficult for the compilers to safely apply optimization passes without making dangerous C transformations. Rather than addressing the weakness of this internal representation of code, it’s easier to find loopholes that allow the customary semantics to be ignored.

 

C is not Java

Here’s something more from Lattner. 

Violating Type Rules: It is undefined behavior to cast an int* to a float* and dereference it (accessing the “int” as if it were a “float”). C requires that these sorts of type conversions happen through memcpy: using pointer casts is not correct and undefined behavior results. The rules for this are quite nuanced and I don’t want to go into the details here (there is an exception for char*, vectors have special properties, unions change things, etc). This behavior enables an analysis known as “Type-Based Alias Analysis” (TBAA) which is used by a broad range of memory access optimizations in the compiler, and can significantly improve performance of the generated code.

To me: “The rules for this are quite nuanced and I don’t want to go into the details here (there is an exception for char*, vectors have special properties, unions change things, etc)” means, “we mucked up the standard and we are going to cause many systems to fail as these nuanced rules confuse and surprise otherwise careful and highly expert programmers”.   Unexpected results are a catastrophe for a C programmer. Limitations on compiler optimizations  based on second guessing the programmer are not catastrophes ( and nothing prevents compiler writers from adding suggestions about optimizations). Programmers who want the compiler to optimize their algorithms using clever transformations should use programming languages that are better suited to large scale compiler transformations where type information is clear indication of purpose. As Chris Lattner notes:

It is worth pointing out that Java gets the benefits of type-based optimizations without these drawbacks because it doesn’t have unsafe pointer casting in the language at all.

Java hides the memory model from the programmer to make programming safer and also to permit compilers to do clever transformations because the programmer is not permitted to interact with the low level system.  Optimization strategies for the C compiler should take into account that C does not put the programmer inside a nice abstract machine.  The  C compiler doesn’t need to force C to become a 5th rate Pascal or Swift.  Unexpected results are far more serious a problem for C than missed minor optimizations.

Categories
Uncategorized

Current reading August 2017

Two-way string matching 

And an implementation in Musl.

The C standard post C11

The Rube-Goldberg approach to fault tolerance in NTPd  which is not worse than PTP fault tolerance but a lot worse than what is done in TimeKeeper.

Interesting but naive article about bank accounting and credit/money creation.

Meanwhile:

Image result for boston march against racism

Categories
academics business software business

James Damore’s defenders (updated)

(recently saw a post claiming that Damore’s firing was an indication of closed minds at Google, so it seemed important to repost this – vy)

men's_lib

The Google manifesto  is a crackpot political argument combined with basic scientific ignorance, not a scientific argument.  The people claiming otherwise are either lying or terrible at reading.  Damore’s essay is packed full of paraphrases of Jordan Peterson’s  body of work, right wing political slogans, and resentment against women. Damore claims that any program to encourage hiring and promotion of women and minorities constitutes discrimination against white men.  According to him a company mentoring program to encourage women programmers would be both wrong and illegal.  He says that there is no gender wage gap and women get lower wages because they don’t want to work as hard or take risks like men do. He claims that feminism is a ploy by “Marxist intellectuals” to replace their old class struggle pitch. He says that Google management are left-wing extremists,  motivated by a “veiled left ideology”, whose diversity initiatives are “encroaching extremist and authoritarian policies”.   He says that he is in favor of diversity, but against actually doing anything to encourage diversity because:

Discriminating just to increase the representation of women in tech is as misguided and biased as mandating increases for women’s representation in the homeless, work-related and violent deaths, prisons, and school dropouts.” 

Damore’s deplorable basket of crackpot resentments is not science. Period. The defenses of Damore by people like University of Texas Computer Science Professor Scott Aaronson and  UW’s Stuart Reges  and Peter Singer are either grossly dishonest or complete failures of scholarship. Aaronson writes to say that all Damore was doing was ” mooting the hypothesis of an evolutionary reason for average differences in cognitive styles between men and women,” and that  “the basic thesis being discussed […]  that natural selection doesn’t stop at the neck, and that it’s perfectly plausible that it acted differently on women and men in ways that might help explain many of the population-level differences that we see today“. He laments the  “clickbait narrative” of Damore’s critics (although he doesn’t cite or quote any of them because that would ruin his  baseless temper tantrum). But Damore is not saying anything is “perfectly plausible”. He is insisting that Google should abandon any attempt to hire or advance women or minorities.  Stuart Reges  wrings his hands in anguish:

For the last ten months I have been discussing this issue at the Allen School of Computer Science & Engineering where I work. I have tried to understand why Damore’s opinions generated such anger and have struggled to decide what I want to do in response.

He tried, so sincerely,  to understand why people would get angry reading stuff like:

Yes, in a national aggregate, women have lower salaries than men for a variety of reasons. For the same work though, women get paid just as much as men. Considering women spend more money than men and that salary represents how much the employee sacrifices (e.g. more hours, stress, and danger), we really need to rethink our stereotypes around power. – Damore

Really Stuart? Ten months of sincere effort and you can’t figure out what about Damore’s memo would make people mad?

As it became clear that the working class of the liberal democracies wasn’t going to overthrow their “capitalist oppressors,” the Marxist intellectuals transitioned from class warfare to gender and race politics. The core oppressor-oppressed dynamics remained, but now the oppressor is the “white, straight, cis-gendered patriarchy.”  – Damore

Although Aaronson won’t cite any of the critics of Damore, he lists a number of “courageous” defenders. For example, here is Peter Singer with more of this nonsense:

James Damore, a software engineer at Google, wrote a memo in which he argued that there are differences between men and women that may explain, in part, why there are fewer women than men in his field of work. For this, Google fired him.

Reges has a similar claim:

In this case, suggesting that men and women are different either in interests or abilities is considered blasphemy. – Reges

Obviously what Reges, Singer, and Aaronson wrote is not true. Insisting that there is no wage gap or that any diversity program at all constitutes discrimination against white guys or that Google management is on some left-wing crusade, is not evolutionary biology and it’s not arguing that “there are differences between men and women” – a totally uncontroversial claim.  It’s not acceptable for these academics to misrepresent Damore’s resentful tract as some musings about evolutionary biology.

But what about all the scientific citations in Damore’s memo?  Giving those all benefit of the doubt, if Aaronson and Singer and all the other handwringing apologists had tried to characterize Damore’s memo more more honestly as, say: “a combination of a political rant and an argument about evolutionary biology”, all of their  indignant claims to be defending free scientific inquiry would have been a lot harder. They would have had to let go of the strawman and come up with arguments less dependent on emotion and name-calling.   Here’s Aaronson

Let that be the measure of just how terrifyingly efficient the social-media outrage machine has become at twisting its victims’ words to fit a clickbait narrative—a phenomenon with which I happen to be personally acquainted.  Strikingly, it seems not to make the slightest difference if (as in this case) the original source text is easily available to everyone.

This is to defend someone who wrote that one problem with Google’s efforts to hire women is that:

“We can make software engineering more people-oriented with pair programming and more collaboration. Unfortunately, there may be limits to how people-oriented certain roles at Google can be and we shouldn’t deceive ourselves or students into thinking otherwise (some of our programs to get female students into coding might be doing this).” 

Or here’s Singer:

So on an issue that matters, Damore put forward a view that has reasonable scientific support, and on which it is important to know what the facts are. 

Really? There’s “reasonable scientific support for  “For the same work though, women get paid just as much as men”? No there is not.  How about  “political correctness is a phenomenon of the Left and a tool of authoritarians.” Some nice double blind studies on that one?  What about:

 

I’ve heard several calls for increased empathy on diversity issues. While I strongly support trying to understand how and why people think the way they do, relying on affective empathy—feeling another’s pain—causes us to focus on anecdotes, favor individuals similar to us, and harbor other irrational and dangerous biases. Being emotionally unengaged helps us better reason about the facts.

Reasonable scientific support for that? Reges wants pity

When I tried to discuss Damore at my school, I found it almost impossible. As a thought experiment, I asked how we could make someone like Damore feel welcome in our community. The pushback was intense. My question was labeled an “inflammatory example” and my comments were described as “hurtful” to women.  – Reges.

No kidding. The head of undergraduate instruction asks how to make a misogynist feel welcome in the community and people push back. Shocking. The entire basis of Reges, Aaronson’s and Singer’s defenses of Damore depends on whitewashing away all the ideology in his essay, taking his variant of “I’m not a racist but ... ” at face value, and then pretending that his wacky “biology” is respectable. Only if you do all that can you denounce the irrational anti-scientific anti-free speech mob from your claimed position as an Olympian Scientist and Defender of Free Speech. How original: to defend a sexist rant by claiming its critics are hysterical.

“Biological males that were castrated at birth and raised as females often still identify and act like males – James Damore” 

Wow, imagine not being struck by how weird that is.  There is a biological argument in Damore’s essay, but it’s  ridiculous. Nobody  argues that there are no biological differences between men and women. Nobody. Damore comes up with:  “Differences in distributions of traits between men and women may in part explain why we don’t have 50% representation of women in tech and leadership.”  Well, who said that those differences definitely did not explain any differences in representation? Nobody, that’s who. Google engineering is 19% female. Google’s management is 22% female.  The issue is not why Google doesn’t have 50% representation, but why it has 19% representation.

Damore cites a number of studies that show or claim to show that certain traits are weakly correlated with gender. The gender based differences in those studies are in single digits so even if we accepted them as authoritative (they are not) they would not begin to explain the gender differences at Google. More striking, not one of those studies concerns programming ability or performance. When people claim Damore’s cited studies represent the scientific consensus (which is not true) they leave out that those studies have absolutely nothing to say about either programmers or diversity programs.   Let me just stress that, because Damore is repeatedly claimed to have been relying on science: Not a single one of the studies that Damore cites claims to show a relationship between gender and ability to code.

Image result for 4 aces

Not one. Traits like  which toys rhesus monkeys pick by gender may be related to human performance writing Java code but there is no evidence of that and it’s pretty implausible. Why being “more thing oriented” would make men more likely to be managers of people at Google or more interested in distributed consensus algorithms is also not addressed. But even if we were as naive as Professors Aaronson, Reges, and Singer, and accepted this collection of studies as meaning what Damore claims they mean, it takes a stupendous level of mathematical ignorance to agree that such studies would explain Google’s gender imbalance.

There are 52 cards in a deck of cards, so a blind pick from a shuffled deck produces an ace with probability 4/52.  Yet, I can guarantee you I can pick 4 aces in a row out of the deck every time  if I look at the faces of the cards. Google and other companies don’t hire based on random sampling from the population. They recruit, interview and test and, because they are a highly profitable company, they can pay more and offer better benefits to attract employees they want. They look at the cards. If they want aces, they can get them. Most of the programmers in the world are average or worse. Does Google have to accept that half of its programmers will be below average? This is obvious, but in a rush to take Mr. Damore’s rant as a serious contribution, Damore’s defenders, who claim to be speaking the name of science, apparently  can’t do basic statistical reasoning.

The really interesting question is why this document has any defenders at all and, particularly, why all the concerned hand wringing from academics.

 

“Civilization’s going to pieces,” broke out Tom violently.
“I’ve gotten to be a terrible pessimist about things. Have you read
‘The Rise of the Coloured Empires’ by this man Goddard?”

“Why, no,” I answered, rather surprised by his tone.

“Well, it’s a fine book, and everybody ought to read it. The idea is if
we don’t look out the white race will be–will be utterly submerged.
It’s all scientific stuff; it’s been proved.”

“Tom’s getting very profound,” said Daisy with an expression of
unthoughtful sadness. “He reads deep books with long words in them.
What was that word we—-“

“Well, these books are all scientific,” insisted Tom, glancing at her
impatiently. “This fellow has worked out the whole thing. It’s up to us
who are the dominant race to watch out or these other races will have
control of things.”

“We’ve got to beat them down,” whispered Daisy, winking ferociously
toward the fervent sun. –  F. Scott Fitzgerald, Great Gatsby.

(h/t to vp at Lobste.rs for reminding me of this )

 

 

Categories
business

Google Manifesto shows that many programmers are bad at logic

Everyone can recognize some kinds of spurious reasoning – like the following:
(A) Most programmers are not great.
(B) Google tries to hire only excellent programmers
(C) A implies B is hopeless and counterproductive and demoralizing

Clearly, step (C) is nonsense – employers are not required to randomly hire and the distribution may make the effort to hire good programmers harder, but it tells us nothing about how valuable such an effort is and whether it can succeed. So (C) is an example of what we call in formal logic, “unsound deduction”.  Yet a much worse argument got a lot of support from people who are supposed to be in the business of logical thinking. This argument starts with a false premise and then uses the same unsound deduction.

(A) Biology ensures more men than women want to be programmers.
(B) Google wants some balance
(C) A implies B is hopeless and counterproductive

Many people, supposedly educated people, have claimed this to be a simple statement of scientific fact that is denied only by “left wing ideology” or something. Ben Franklin was right when he explained that the advantage of being a reasoning creature is you can always find a reason to justify what you want. In this case, some people want to justify feelings of resentment – despite high paying jobs and good working conditions. The defense of the Google Manifesto as “science” though is particularly noxious both because of the unsound deduction at its core and because it’s clearly a political argument – clearly political, but with muddy and poorly thought out political ideas (which is what resentment gives you). Here is a sample:

when it comes to diversity and inclusion, Google’s left bias has created a politically correct monoculture that maintains its hold by shaming dissenters into silence. This silence removes any checks against encroaching extremist and authoritarian policies. For the rest of this document, I’ll concentrate on the extreme stance that all differences in outcome are due to differential treatment and the authoritarian element that’s required to actually discriminate to create equal representation.

Someone should have put in more time working and less listening to hate radio.

 

 

 

 

Categories
state maps

Sorting and groups

Image result for permutation groupI can’t find much reference to this in the literature (see Maus for some hints and an interesting paper) , but surely people have looked at sorting as a problem in group theory? Given a sequence $latex s=[x_1\dots x_n] $ say a permutation $latex \pi$ sorts $latex  s$ if and only if $latex \forall 0 < i < n-1, x_{\pi i} \leq   x_{\pi(i+1)}$ — where we could use any partial order. So sorting becomes a problem of finding an element of the permutation group. If $latex s $ contains no repetitions, if $latex s_i = s_j$ only when $latex j=i$ then there is at most one element of $latex S_n$ that sorts the sequence. If $latex s$ is a sequence of one value, then every element of $latex S_n$ sorts the sequence. Maybe $latex n-  |\{x_i: 0 < i \leq n\}|$ tells us how many elements of $latex S_n$ sort $latex s$, but perhaps we have to differentiate between repetitions of a single value and repetitions of multiple values.  An element of $latex S_n, (i,j)$ corresponds to  a swap in $latex s$ – to

t := s(i); s(i) := s(j); s(j) := t;

so we could describe e.g. quicksort as a sequence of pairs.

Anyone have some references?

Categories
programming languages

C compiler developers are hostile to C programming

From the LLVM developer mailing list this remarkable exchange in which Chris Lattner of LLVM says that the compiler use of undefined behavior (UB) is so “crappy” that the only solution is to abandon C programming (my bold).

On Jul 7, 2017, at 1:40 PM, Peter Lawrence <peterl95124 at sbcglobal.net> wrote:
> Chris,
>           The issue the original poster brought up is that instead of a compiler 
> that as you say “makes things work” and “gets the job done” we have a compiler
> that intentionally deletes “undefined behavior”, on the assumption that since it 
> is the users responsibility to avoid UB this code must be unreachable and 
> is therefore safe to delete.
> 
> It seems like there are three things the compiler could do with undefined behavior
> 1)   let the code go through (perhaps with a warning)
> 2)   replace the code with a trap
> 3)   optimize the code as unreachable (no warning because we’re assuming this is the users intention)

Hi Peter,

I think you have a somewhat fundamental misunderstanding of how UB works (or rather, why it is so crappy and doesn’t
 really work :-).  
The compiler can and does do all three of those, and it doesn’t have to have consistent algorithms for how it picks.  I highly recommend you read some blog posts I wrote about it years ago, starting with:
http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html

John Regehr also has written a lot on the topic, including the recent post:
https://blog.regehr.org/archives/1520

What you should take from this is that while UB is an inseperable part of C programming, 
that this is a depressing and faintly terrifying thing.  
The tooling built around the C family of languages helps make the situation “less bad”, but it is still pretty bad.  
The only solution is to move to new programming languages that don’t inherit the problem of C.  
I’m a fan of Swift, but there are others.

In the case of this particular thread, we aren’t trying to fix UB, we’re trying to switch one very specific syntactic idiom from UB to defined.

-Chris

When Lattner  writes “The compiler can and does do all three of those, and it doesn’t have to have consistent algorithms for how it picks”  the key terms are “can” and “doesn’t have to”. The C standard may permit “depressing and … terrifying” operations in a compiler but there is no requirement for compilers to make UB into a minefield for programmers. The compiler writers have seized onto what is essentially a loophole in the standard as permission to do stupid things and call them optimizations. It seems only reasonable to hope that WG14-C would consider that their standard is used by compiler writers to justify deprecating use of the language, but maybe they don’t care either. The other interesting fact I learned from this exchange is that it is impossible to write conforming C implementations of “malloc” and “free”. Why? Because once “free” returns, any access to the pointer is UB, so malloc cannot allocate the same memory again.

 

 

 

Categories
academics business

Current reading: July 8 2017

  1. Principal type-schemes for functional programs∗ Luis Damas† and Robin Milner First published in POPL ’82: Proceedings of the 9th ACM SIGPLAN-SIGACT symposium on Principles of programming languages, ACM, pp. 207–212
  2. THE PRINCIPAL TYPE-SCHEME OF AN OBJECT IN COMBINATORY LOGIC BY R. HINDLEY
  3. Quicksort. 
  4. Tiny C – sheer genius!
  5. Plan B 
  6. C 89 
  7. Mifid II 
  8.  Rust types.  and Cyclone. 
  9. Badly behaved polynomials.

Consequently, to overcome this restriction, the implementations of Rust’s standard libraries make widespread use of unsafe operations, such as “raw pointer” manipulations for which aliasing is not tracked. The developers of these libraries claim that their uses of unsafe code have been properly “encapsulated”, meaning that if programmers make use of the APIs exported by these libraries but otherwise avoid the use of unsafe operations themselves, then their programs should never exhibit any unsafe/undefined behaviors. In effect, these libraries extend the expressive power of Rust’s type system by loosening its ownership discipline on aliased mutable state in a modular, controlled fashion: Even though a shared reference of type &T may not be used to directly mutate the contents of the reference, it may nonetheless be used to indirectly mutate them by passing it to one of the observably “safe” (but internally unsafe) methods exported by the object’s API – from

 

Proposition: there are no major applications in any safe language that do not depend on either C libraries or “unsafe” magic keywords or both.

Categories
C standard programming languages

The C standard versus C and the mother of all hacks.

The Kafkaesque interaction of the C standard and the main open source C compilers was concisely outlined by one of the main LLVM authors back in 2011:

“knowing that INT_MAX+1 is undefined allows optimizing X+1 > X to “true”. Knowing the multiplication “cannot” overflow (because doing so would be undefined) allows optimizing X*2/2 to X  ( Chris Lattner) 

The C standard defines signed integer types to be fixed length  chunks of binary data  in either 1s or 2s complement format so  x+1 > x is not a theorem and neither is x*2/2= x.  Those are  elementary mathematical facts – facts that make some kinds of optimizations harder. Instead of trying to work with that reality, C compiler developers have chosen to use a loophole in the C Standard to insist that they can transform code using false theorems.   Both the default versions of the GCC and clang compilers that come with current Ubuntu Linux compile the following program so that it works differently depending on optimizations level.

for (i = 0; i < i + 1; i++) {
    if (i == INT_MAX) { flag = 1; }
    if (flag > 0) {printf("%d |", i);flag++; }
    if (flag > 5) { putchar('\n'); exit(0);} 
} 
exit(0);

When compiled without optimization, there is no output. When compiled with optimization output is:
2147483647 |-2147483648 |-2147483647 |-2147483646 |-2147483645 |

Deducing that i < i + 1  is always true, the  “optimizer”  omits the test and then produces code that calculates it was wrong. The compiler is deducing that it won’t produce the executable code that the compiler produces! Nice.  According to the C standard, correct code would have used unsigned integers for the comparison because integer overflow is “undefined behavior” even though using signed integers infor loops is ubiquitous in C programming. The curious thing here is that the compilers don’t warn, complain or reject the signed comparison, instead they generate a paradoxical executable that refutes itself. The program could run correctly for years until someone turns on the optimizer and finds paradoxical results. Similar compiler innovations have produced all sorts of failures in working code  including security failures.  This stupid technology is constructed from the interaction of two reasonable projects that went wrong and then amplified each others difficulties. One project is the C standard committee tightening up C’s loose semantics.  The other project is an effort to import new optimization methods into the C compilers. The flaws in each project have resonated with each other.  In this case, different processor architectures (or processing modes) will produce different behavior when signed words overflow and C is designed to allow use of native arithmetic close to the machine level for utmost efficiency.  But instead of finding ways to make signed integer overflow alternatives precise, the standards committee tossed signed overflow  into special category of things labeled “undefined behavior” which is different from “indeterminate” or not specified in that undefined behavior makes C code “non-conforming” and  “this International Standard imposes no requirements” for undefined behavior. Then,  in a ghastly miscalculation, compiler developers interpreted “imposes no requirements” as a license to produce incorrect code and call it optimization. The standard imposes no requirements and it is convenient to assume there will not be overflows, so QED we can conclude that FALSE is TRUE.  In fact, the position seems to be that if the compiler produces code that encrypts all your files, mails the password to Moldavia, and prints a cryptic message about end times, it’s all good. No requirements means no requirements and you should have declared those variables unsigned. This is no exaggeration. Discussing the undefined behavior of  shift operations Lattner writes: ” shifting by 32-bits on PowerPC could format your hard drive, it is not guaranteed to produce zero.”

The unsound logic of the compilers has drawn complaints for decades: from the most prominent author of cryptography code ( DJB   ), former members of the C standards committee ( Derek Jones), groups of researchers and academics (Regehr) and many C programmers (this one is like horror movie about an engineer trapped in a department of motor vehicles office). As Derek Jones observed,  the main C compiler groups don’t sell software to application developers, so they don’t have to care. And the compiler developers don’t have anyone who can do what Linus Torvalds did for Linux so many times: politely and humbly reject innovations that break applications without compelling justification. Even worse, the standards committee and compiler developers split responsibility – so they can and do blame each other. The example cited by DJB is truly horrendous and is based on the standards edict that dereferencing a null pointer is undefined behavior (even though it’s widely used in embedded code and in some UNIX distributions).

  1. The code erroneously, but harmlessly in this case, dereferences a pointer before checking whether the pointer is null.
  2. The compiler considers two possibilities: either the pointer is not null, in which case the null check that follows is redundant, or the pointer is null in which case undefined behavior has been detected.
  3. The compiler “optimizes” by removing the null check (silently). Note that the code deleted is not “undefined behavior” code, it just follows the offending null pointer dereference so all rules are off.
  4. A security breach is discovered as someone exploited the failure of the code to check for null pointers.

All programming languages have some places where they run into machine or architecture or resource dependencies. If you take the most over-the-top mathematically pretentious highest level  programming language and write code to compute factorial,  a fairly small input will cause the program run out of time or memory  and produce some error.  Because C is so close to the machine, it bumps into hardware a lot. Pointers are machine addresses – just numbers, shift operations are specified to compile down to single machine instructions,  and arithmetic is so close to the hardware that number representation schemes are programmer visible. As another example, C structures may contain “padding” bytes added by the compiler to accommodate processor alignment requirements. A structure struct x{ char c; int i;}; could have 3 bytes of padding inserted after the 1 byte character so that the integer is on a 4 byte boundary.   For efficiency,  padding bytes are generally uninitialized – not set to any particular value – so copying the structure may involve copying uninitialized data. The C90 standard declared that reading uninitialized bytes was undefined behavior.  The unintentional consequence was  that it became impossible to write the C library in conforming C because copying a structure in the memcpy routine might involve copying (reading and writing)  uninitialized data. The standards committee “solved” the problem with a dismally giant hack by making unsigned characters specially immune from undefined behavior. If you copy indeterminate bytes via unsigned character pointers, that’s ok, but otherwise it’s undefined behavior. Here’s Chris Lattner  explaining a small part of the resulting mess:

It is undefined behavior to cast an int* to a float* and dereference it (accessing the “int” as if it were a “float”). C requires that these sorts of type conversions happen through memcpy: using pointer casts is not correct and undefined behavior results. The rules for this are quite nuanced and I don’t want to go into the details here (there is an exception for char*, vectors have special properties, unions change things, etc). – Chris Lattner

To anyone who has ever managed a programming project, the signs of a fatally broken design decision are unmistakable (a cascade of special cases is never good).  Nuance is nice in poetry, not in engineering. But the C standards committee was not piling whole categories of C code into the undefined behavior basket for no reason. They were trying to deal with two different types of pressure. The first is that the definition of C had too many holes in it so that C code was often way too machine or compiler dependent or just sloppy. The second problem was that C is resistant to  a number of useful types of optimization and compiler developers pushed for changes that would allow them to assume things about code that made certain optimizations more widely applicable. It seems like the idea was that by ruling certain types of constructions to be “non-conforming”, the standard would discourage their use and make the language more consistent and more suited to modern optimization methods. As an example, certain widely used crypto code apparently uses uninitialized variables to inject randomness into low level encryption code – relying on the variable to be allocated from the leftover garbage on the stack. That code will fail dismally if stack allocation changes or automatic variables are not allocated from the stack (or the previously called process filled the stack with something predictable). So warning about that code or even refusing to compile it might improve applications, but  because of the nice split in responsibilities, the Standards body can rule such code non-conforming and look away as the compiler writers then perform arbitrary transformations on that code.

And all of this is so unnecessary. As Jones points out, some sort of undefined behavior is intrinsic to C, but implementations are not required to treat it as an excuse for sabotaging programs:

A willingness to accept whatever behavior happens to occur, in an error situation, is the price that has to be paid for efficient execution on a wide range of disparate processors. The C/C++ designers were willing to pay this price while the Java designers were not, with the Ada designers willing to tolerate less variability than C/C++.

Undefined behavior need not be nasty behavior, an implementation could choose to generate a helpful message or try to recover from it.

Changes to the C standard could be used to make it more optimizable. C would definitely benefit from increased use of “restricted pointers”  (the definitions needs to be clarified but it’s a great step forward.) And the gcc “pure” modifier on function types is interesting and should be considered for the standard. The standard could promote methods of writing verifiable, optimizable code as opt-in.  An additional easy step would be to move things like reading uninitialized data from “undefined”  to “indeterminate” and make pointer casting well-defined whenever alignment and object size are compatible. The Committee could also modify the definition of undefined behavior by specifically limiting the freedom of conforming implementations. Replacing  “this International Standard imposes no requirements” with “conforming implementations may reject non-conforming constructs or implement in a consistent implementation dependent manner.” C already provides machine dependent information to the programmer. The file limits.h tells the programmer about the native word sizes and number ranges on the target computer. There is no reason why that cannot be extended to specify signed integer overflow behavior (and in 99.999% of cases that behavior is rollover). The language has intrinsic access to nonportable machine dependent and/or implementation dependent information. Pretending otherwise only creates too much – well – nuance. Underlying all of this, it’s easy to get the feeling that the C standards process and C compiler development has lost the plot – that the objective has become a doomed effort to recreate C as a kind of low grade patched up Ada or (forgive me) Rust. Consider this note on uninitialized data from Lattner

Use of an uninitialized variable: This is commonly known as source of problems in C programs and there are many tools to catch these: from compiler warnings to static and dynamic analyzers. This improves performance by not requiring that all variables be zero initialized when they come into scope (as Java does). For most scalar variables, this would cause little overhead, but stack arrays and malloc’d memory would incur a memset of the storage, which could be quite costly, particularly since the storage is usually completely overwritten.

But the alternatives are not only to either  impose mandatory initialization of variables or to allow the compiler to treat use of uninitialized memory as permission to arbitrarily rewrite code. Where compilers and tools can warn about use of uninitialized variables – that’s good. Where they cannot, it should be up to the programmer. The intent of C is to give the programmer enormous flexibility. C developers are not supposed to need to escape the language and invoke pragmas or “unsafe” keywords (or call out to libraries written in some scorned low level language).  The Linux networking code (lib/checksum) below shows off one of the compelling things about C – the ability to work directly with memory and control the mapping from bit patterns to denotations. The code is computing a checksum for a network packet. Starting with the packet data structure it first looks at it as a sequences of unsigned characters and then does a type coercion to map sequences of 4 unsigned characters into “int” types so it can run through and compute the checksum.

do { 
    unsigned int w = *(unsigned int *) buff; 
    buff += 4; result += carry;
    result += w;
    carry = (w > result); } 
while (buff < end); 
result += carry;
result = (result & 0xffff) + (result >> 16);

This type of code makes it easy to screw up and can complicate optimization for compilers and everything for static analyzers (changes in type, aliased pointers, demonical possession … ) but it’s the essence of systems programming and a lot of application programming too. It’s what C was designed to do and for expert C programmers gives C programming a flexible and fluid character. Is this code legal C under the current C standard? It appears not. But if the standard requires the mother of all hacks to make memcpy implementation conform to spec (and not a real one either, it actually seems to force C compilers to use escape to assembler) it’s going to trip up C. And if the implementations are going to claim that unsound deduction is a legitimate optimization then C is really doomed.

Bonus – from the C89 standard.

A pointer to an object or incomplete type may be converted to a pointer to a different object type or a different incomplete type. The resulting pointer might not be valid if it is improperly aligned for the type pointed to. It is guaranteed, however, that a pointer to an object of a given alignment may be converted to a pointer to an object of the same alignment or a less strict alignment and back again; the result shall compare equal to the original pointer.

rough notes for this are at  https://www.yodaiken.com/2017/06/16/the-c-standard-committee-effort-to-kill-c-continues/

 

 

 

 

Categories
programming languages systems

The C standard committee effort to kill C continues

Consider the following code:

void f(void) {
unsigned char x[1]; /* intentionally uninitialized */
x[0] ^= x[0];
printf("%d\n", x[0]);
printf("%d\n", x[0]);
return;
}

In this example, the unsigned char array x is intentionally uninitialized but cannot contain a trap representation because it has a character type. Consequently, the value is both indeterminate and an unspecified value. The bitwise exclusive OR operation, which would produce a zero on an initialized value, will produce an indeterminate result, which may or may not be zero. An optimizing compiler has the license to remove this code because it has undefined behavior. The two printfcalls exhibit undefined behavior and, consequently, might do anything, including printing two different values for x[0].

The programmer has clearly attempted to set  x[0]=0.  The compiler here is claimed to be “optimizing” by removing the xor. So what is exactly made faster or more efficient by this “optimization” ? (Note that there is no “volatile” used on the variable, so the compiler is permitted to optimize using the result of the first read.)  Let me ask an easier question: is there an example of a program in which such an “optimization” results in a better executable by some sensible measure? If all we care about is execution speed, without regard to program correctness, compilers could optimize by generating nothing at all. Wow, that null program sure runs fast!

 Some compiler writers would prefer to eliminate trap representations altogether and simply make any uninitialized read undefined behavior—the theory being, why prevent compiler optimizations because of obviously broken code? The counter argument is, why optimize obviously broken code and not simply issue a fatal diagnostic?

Why optimize obviously broken code? Why declare a working and common usage in C to be broken in the first place?

According to the current WG14 Convener, David Keaton, reading an indeterminate value of any storage duration is implicit undefined behavior in C, and the description in Annex J.2 (which is non-normative) is incomplete. This revised definition of the undefined behavior might be stated as “The value of an object is read while it is indeterminate.”

And yet:

Uninitialized memory has been used as a source of entropy to seed random number generators in OpenSSL, DragonFly BSD, OpenBSD, and elsewhere.10 If accessing an indeterminate value is undefined behavior, however, compilers may optimize out these expressions, resulting in predictable values.1

So the intent of at least some members of the C standard committee is to make production C code fail in some unpredictable manner as an “optimization”.  Despite the best efforts of developers of rival programming languages, C’s advantages have preserved it as an indispensable systems and applications programming language. Whether it can survive the C standards process is a different question.

As a final note: this type of design failure is not all that unusual, but it’s the job of engineering management to block it. The proposal to silently make working crypto code fail in order to enable some type of efficiency for the compiler should be a non-starter.

 

Thanks to John Regehr for bringing this report from the weird underworld of C standard development to my attention. Also this

Categories
academics theory

Basic math for basic algorithms

It’s odd that all the descriptions of basic programming operations, such as sorting, rely on pseudo code or complex formal logic. All we are doing is modifying finite sequences so it seems like we should be able to use ordinary algebra. I’m going to start by defi ning basic operations on finite sequences and then look at the usual algorithms for sorting them – all without either using any pseudo code or any formal methods.

Download.