
When the Compiler Bites - ingve
http://nullprogram.com/blog/2018/05/01/
======
userbinator
_The unit test issues in my real program, which was a bit more sophisticated
than what was presented here, gave me artificial intelligence vibes. It’s that
situation where a computer algorithm did something really clever and I felt it
outsmarted me._

As someone who has been reading compiler output for many years, it seems like
these "flashes of inspiration" are relatively sparse compared to the bulk
plain-stupid code they normally produce; once in a while you'll see something
and think "that's clever... _really_ clever", followed immediately by
something so stupid it makes you wonder how it managed to generate such code.
You can easily distinguish between that and human-written Asm that way. The
latter may not have as much "peak cleverness" but certainly keeps up its
average level of efficiency.

~~~
textmode
Has there ever been a program (not including any so-called "optimised
assembler") that accepts handwritten asm on stdin and sends "optimised" asm to
stdout?

    
    
       optasm <1.asm >2.asm
       diff -U0 1.asm 2.asm|less 
    

The person writing the handwritten asm can then look at the suggested
"optimisations" and can learn from/evaluate them.

Looking at x86 asm source code, I have always been amazed at the relatively
small number of commonly used instructions versus the large number of
available instructions that are listed in the manuals. As someone learning
asm, how would I ever be introduced to apropos usage of these uncommon
instructions absent real-world examples?

------
unwind
This was interesting, but I don't get this part of the example code:

    
    
        if (w == 0 || h <= SIZE_MAX / w)
    

To me, that condition is just super-strange: if w is zero, the product w x h
is zero too, so the entire allocation becomes pointless. Surely it should be:

    
    
        if (w != 0 && h <= SIZE_MAX / w)
    

or something? Having these kinds of discussions based on code that is
confusing in itself just becomes too much, for me.

~~~
TheRealPomax
pointless, but not wrong: calloc on a zero width image should still yield a
memory pointer, even if it points to a zero length stretch of memory. It would
be strictly wrong to return a null pointer just because (one of) the
dimensions happens to be zero.

It might read a little funny, but it's actually quite correct with respects to
what the code path should be expected to do. Valid dimensions should yield a
valid pointer. Even if they're validly zero =)

~~~
8xde0wcNwpslOw
>It would be strictly wrong to return a null pointer just because (one of) the
dimensions happens to be zero.

Strictly wrong according to whom? At least not any C standard I know of.

Besides, even if the function did return a non-null pointer value (which it is
allowed to do; the behaviour is implementation-defined) you aren't allowed to
dereference it any more than a null pointer, making it hardly any more
"valid", either.

~~~
TheRealPomax
According to code design mostly. It's not a syntax issue (both are fine
syntax) but a logic issue. You should be able to trust that as long as your
dimensions are valid, any for loop over the resulting list works. And in
graphics contexts, zero is typically a perfectly valid value to set one (or
both) dimensions to (negative values, however, are not). Of course, you _can_
say "I think 0 is an invalid dimension" but given that plenty of frameworks
and applications consider 0 just fine, that's kind of an unnecessary
restriction for the sake of writing a conditional that is arguably
irrelevantly "easier" to read.

------
vardump
GCC float comparison: since C doesn't have a portable way to denote single
precision float constants, it's a good idea to cast them in order to avoid
implementation specific differences. See:
[https://godbolt.org/g/t7UYoi](https://godbolt.org/g/t7UYoi)

    
    
      int
      float_compare(void)
      {
          float x = 1.3f;
          return x == (float) 1.3f;
      }
    

This correctly returns 1 on all compilers I cared to check.

Couldn't reproduce clang calloc removal bug with that code. Tried clang 6.0,
-O1, -O3.

Clang did remove calloc when there were no new_image calls where parameter
"shade" could be zero. But that's to be expected, as it is a static function,
only accessible from same compilation unit. Entirely correct behavior.

[https://godbolt.org/g/FfV7G2](https://godbolt.org/g/FfV7G2)

When it comes to the filed clang bug, I fail to see any bug at all:
[https://bugs.llvm.org/show_bug.cgi?id=37304](https://bugs.llvm.org/show_bug.cgi?id=37304)

When calloc return value is not stored (or used), how could it be accessed or
even freed at all? I think compiler is right to remove whole call.

~~~
unwind
Can you clarify this part:

 _[...] since C doesn 't have a portable way to denote single precision float
constants [...]_

That sounds like the suffix 'f' which you're also using, how is that not a way
to "denote single precision float constants"?

My draft copy of the C11 spec says:

 _An unsuffixed floating constant has type double. If suffixed by the letter f
or F, it has type float. If suffixed by the letter l or L, it has type long
double._

But this is not new syntax, I just looked in the most recent document I have.

Do you mean that C doesn't have a way to require IEEE 754 floating-point?

~~~
vardump
_See edit below. C99 allows greater precision than required by the type!_

Hmm, you're right.

I think something similar bit me long ago and left me under impression
f-suffix for a float is not portable.

Perhaps it's time to read the standard through once again...

Then again, explicit casts generate code that works on all compilers,
standards compliant or not.

Edit: C99, 6.3.1.8.2: "The values of floating operands and of the results of
floating expressions may be represented in greater precision and range than
that required by the type; the types are not changed thereby."

So I guess explicit floating point casts are needed when explicit operations
are wanted!

~~~
enqk
You might have encountered in the past is C/C++ silently promoting your floats
to double.

An example, `printf("%f", float_value)` will actually promote float_value to a
double. Or calling a function from `math.h` with a float constant

------
GlitchMr

        t->counter = calloc(n, sizeof(t->counter));
    

I think you are checking the sizeof of `unsigned *` here, not `unsigned`,
which is itself a bug.

------
giomasce
It strikes me that the System V amd64 ABI is not defined on such an
(apparently) obvious point as padding of values smaller than a word. Is there
a reason why it does not get fixed?

------
maccard
I’m amazed that we still have people being bitten by exact floating point
comparisons in 2018 really. Nobody working with floating point numbers should
be surprised by the behaviour in the post. This article [0] is over 6 years
old and is a follow up from I don’t know when, talking about this exact
subject.

[0] [https://randomascii.wordpress.com/2012/02/25/comparing-
float...](https://randomascii.wordpress.com/2012/02/25/comparing-floating-
point-numbers-2012-edition/)

~~~
bloat
The first example is about optimizing away multiplication by zero. The second
is about GCC treating float literals with greater than float precision, and
the third is about dead code elimination.

Not convinced your comment is relevant.

------
DannyBee
So, afaik, the bug is not a bug. It should be, but the language specification
says it's not.

To quote richard smith on a related issue (malloc(-1) succeeds):

" * malloc is part of the implementation, so LLVM is allowed to optimize it to
allocate memory from "somewhere else".

* In this case, LLVM allocates SIZE_MAX bytes of memory in some hypothetical space that exists only in its imagination.

* This allocation succeeds, so the pointer is non-null, and errno is not set.

* However, as a result, malloc need not actually be called at runtime. "

Does this lead to silly results? Yes.

------
mar77i
I was once told that optimizations in the sense of dead code elimination the
way clang does it could be bypassed by making a variable volatile... Wouldn't
that help here as well?

    
    
        volatile unsigned char *p = malloc(w * h);
        ...

~~~
dooglius
Not quite, what you want is

    
    
      unsigned char * volatile p = malloc(w * h);
    

This indicates that p itself is a volatile variable, rather than a pointer to
memory that is volatile.

~~~
mar77i
Ah, that was the thing I was after. Thanks.

------
lihes
I love when people fail to understand the specification of the language and
blame the compiler.

