Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > C Programming > Who can explain this bug?

Reply
Thread Tools

Who can explain this bug?

 
 
Tim Rentsch
Guest
Posts: n/a
 
      04-20-2013
Noob <root@127.0.0.1> writes:

> Tim Rentsch wrote:
>
>> Floating-point operations can be effectively non-deterministic.
>> But in C, assigning and accessing floating-point variables is not
>> as non-deterministic as you seem to think it is. The culprit
>> here is gcc, which cheerfully generates non-conforming code for
>> floating point expressions involving assignment and subsequent
>> access.

>
> You're quick to castigate gcc!


Only after running some test cases.

> Can you provide the test-case showing the problem you speak of?


I ran several simple test cases, including the ones
involving use of volatile that I showed else-thread. The
version of gcc I was using is not as recent as the one
mentioned up-thread, but recent enough so extrapolation
seemed reasonable. (And I note with satisfaction that my
advice about using volatile was proved correct.) I think if
you try to generate some test cases yourself they will be
easy enough to find, although that may depend on exactly
which version of gcc is used. It's possible newer versions
of gcc have eliminated this class of problems entirely;
however, knowing that gcc has had this problem historically,
and verifying a particular test case myself, was I thought
sufficient evidence to focus attention on that possibility.
 
Reply With Quote
 
 
 
 
Tim Rentsch
Guest
Posts: n/a
 
      04-20-2013
mathog <(E-Mail Removed)> writes:

> Tim Rentsch wrote:
>
>> The culprit
>> here is gcc, which cheerfully generates non-conforming code for
>> floating point expressions involving assignment and subsequent
>> access.
>>

>
> This little test program shows the same issue:
>
> #include <stdio.h>
> #include <stdlib.h>
>
> void atest(double *ymax, double a, double b){
> double tmp;
> tmp=a/b;
> if(*ymax <= tmp){
> *ymax = tmp;
> printf("True\n");
> }
> else {
> printf("False\n");
> }
> }
>
> int main(void){
> double ymax=0;
> double a, b;
>
> while(1){
> (void) fscanf(stdin,"%lf %lf",&a,&b);
> if(!b)break;
> atest(&ymax,a,b);
> }
> }
>
>> gcc -O3 -o test test.c
>> ./test

> 11 23
> True
> 11 23
> False
> 11 19
> True
> 11 19
> False
> etc.
>
> As suggested elsewhere in this thread, making tmp "volatile"
> does resolve the issue. Volatile is supposed to prevent
> compilers from optimizing away calculations that need to be
> repeated. Here for some reason it forces the result of tmp
> into a variable. Why? The compiler then knows that 'tmp" may
> change unpredictably between where it is set and where it is
> read, but why does that imply that the desired value at the
> second position is the calculated value rather than something
> written into tmp from a register in a hardware device (or
> whatever) outside of this program's control?


The other answers you got weren't especially informative,
so I'll take a shot at it.

First, I recommend forgetting what you think you know about
volatile. It may be right or it may be wrong, but either
way it isn't helping you understand what is happening here.

In a nutshell, what is happening is this. The variable 'tmp'
has a location in storage. The code generator notices that a
value is stored (from a register) into tmp, then later loaded
again (let's say into the same register). The optimizer trys
to improve the code by not bothering to load the value again,
since it's already there. This kind of optimization is very
common, and happens all the time with other variable types.
(Probably you know all this part already, but I thought I
would put it in for context.)

However, for floating point types, including in particular
the type double, the register is wider than the place in
memory where 'tmp' resides. So the register can retain
extra information which could not be retained if the value
were put into the 'tmp' memory location. So re-using the
value in the register can behave differently than storing
and reloading.

Normally the difference between these choices is small enough
so it usually doesn't matter. Moreoever, on the x86, the
cost of doing "the right thing" is high enough so there is a
signifcant motivation to "cut corners", as it were, especially
since "it hardly ever matters". That kind of reasoning is
what led gcc to make the decisions it did regarding how to
optimize this sort of code.

To return to your question -- the reason 'volatile' makes a
difference is that volatile INSISTS the compiler deal with the
actual memory location for 'tmp', no matter what the compiler
may think otherwise. There are various reasons for having
'volatile' in the language, and also various reasons for using
it in certain circumstances, but as far as the compiler goes
the primary consequence of a volatile access is this: the
generated code must reference the actual memory location
involved, no matter _what_ the compiler might think about
what other code might have "the same effect".

So that's why using 'volatile' forces the value to be (stored
and then) fetched from the variable rather than being kept in
a register.
 
Reply With Quote
 
 
 
 
glen herrmannsfeldt
Guest
Posts: n/a
 
      04-20-2013
Tim Rentsch <(E-Mail Removed)> wrote:

(snip on keeping extra precision)

> There's an implication there (perhaps unintended) that discarding
> excess precision wasn't required until C99. That conclusion
> doesn't jibe with other, pre-C99, documents.


> In particular, if we look at comments in Defect Reports, and
> also how the wording in this area has changed over time (note
> for example n843.htm, and compare n1256 to C99), it's clear that
> narrowing behavior was intended all along.


> What's more, assignment has _always_ implied removing any extra
> range and precision, because in the abstract machine we actually
> store the value in an object, and then on subsequent accesses
> convert the stored representation to a value. Under the as-if
> rule, any information beyond what the object representation can
> represent must be lost.


I agree for -O0, and probably -O1, but at the higher optimization
levels one of the things you expect is to keep values in registers
throughout loops, and in general don't evaluate expressions more
often than necessary.

One idea behind optimization is that the compiler knows more than
the programmer, such that the programmer can write things in the
most readable form without worrying about how slow it is.

> Bottom line: assignment of floating point types always requires
> removing any extra range and precision, even in C90.


-- glen
 
Reply With Quote
 
Tim Rentsch
Guest
Posts: n/a
 
      04-20-2013
glen herrmannsfeldt <(E-Mail Removed)> writes:

> Tim Rentsch <(E-Mail Removed)> wrote:
>
> (snip on keeping extra precision)
>
>> There's an implication there (perhaps unintended) that discarding
>> excess precision wasn't required until C99. That conclusion
>> doesn't jibe with other, pre-C99, documents.
>>
>> In particular, if we look at comments in Defect Reports, and
>> also how the wording in this area has changed over time (note
>> for example n843.htm, and compare n1256 to C99), it's clear that
>> narrowing behavior was intended all along.
>>
>> What's more, assignment has _always_ implied removing any extra
>> range and precision, because in the abstract machine we actually
>> store the value in an object, and then on subsequent accesses
>> convert the stored representation to a value. Under the as-if
>> rule, any information beyond what the object representation can
>> represent must be lost.

>
> I agree for -O0, and probably -O1, but at the higher optimization
> levels one of the things you expect is to keep values in registers
> throughout loops, and in general don't evaluate expressions more
> often than necessary.


Whatever you might expect, such optimizations (with wide
floating-point registers) are not allowed in conforming
implementations of ISO C, and that has been true since 1990.

> One idea behind optimization is that the compiler knows
> more than the programmer, such that the programmer can
> write things in the most readable form without worrying
> about how slow it is.


Even if the qualifying assumption is true, the reasoning is
irrelevant, because optimized code is still required to
observe the abstract semantics of the language. The
"optimization" you are talking about is not faithful to the
abstract semantics of ISO C, and ergo is not conforming.
 
Reply With Quote
 
glen herrmannsfeldt
Guest
Posts: n/a
 
      04-21-2013
Tim Rentsch <(E-Mail Removed)> wrote:

(snip)
>>> What's more, assignment has _always_ implied removing any extra
>>> range and precision, because in the abstract machine we actually
>>> store the value in an object, and then on subsequent accesses
>>> convert the stored representation to a value. Under the as-if
>>> rule, any information beyond what the object representation can
>>> represent must be lost.


>> I agree for -O0, and probably -O1, but at the higher optimization
>> levels one of the things you expect is to keep values in registers
>> throughout loops, and in general don't evaluate expressions more
>> often than necessary.


> Whatever you might expect, such optimizations (with wide
> floating-point registers) are not allowed in conforming
> implementations of ISO C, and that has been true since 1990.


I suppose reordering statements is also disallowed by the
standard. Moving things outside loops, and such. Fine, then
don't use the high optimization modes.

>> One idea behind optimization is that the compiler knows
>> more than the programmer, such that the programmer can
>> write things in the most readable form without worrying
>> about how slow it is.


> Even if the qualifying assumption is true, the reasoning is
> irrelevant, because optimized code is still required to
> observe the abstract semantics of the language. The
> "optimization" you are talking about is not faithful to the
> abstract semantics of ISO C, and ergo is not conforming.


I first knew about optimizing compilers in Fortran, where they
were doing it since before C existed. Now, Fortran does seem
to be less restrictive on floating point, but much of it people
live with because the speed-up is worthwhile. If it reduces the
run time from six days to five days, people will do it.

Some people like to do scientific programming in C that would
otherwise be done in Fortran. They also expect reasonable optimization.

Seems to me that compilers should state that with -O3 that it doesn't
conform, and you have the choice to use it or not.

-- glen
 
Reply With Quote
 
Stephen Sprunk
Guest
Posts: n/a
 
      04-22-2013
On 22-Apr-13 02:48, David Brown wrote:
> I don't know the ins and outs of the standards here, but the
> intention is that gcc will follow the standards for floating point
> regardless of the -O level, unless you specifically tell it that you
> are happy with non-conforming behaviour through "-ffast-math" or
> related flags.


It has been known for a decade or two that GCC does this even without
-ffast-math, yet nobody has fixed it, which makes your statement of
intent suspect.

> Optimisation level flags in themselves should never change the
> behaviour of a program - just its size and speed.


It is also well-known that optimization often results in different
manifestations of undefined behavior. ITYM that optimization shouldn't
affect conformance, which I think we can all agree with.

S

--
Stephen Sprunk "God does not play dice." --Albert Einstein
CCIE #3723 "God is an inveterate gambler, and He throws the
K5SSS dice at every possible opportunity." --Stephen Hawking
 
Reply With Quote
 
James Kuyper
Guest
Posts: n/a
 
      04-22-2013
On 04/20/2013 06:26 PM, glen herrmannsfeldt wrote:
> Tim Rentsch <(E-Mail Removed)> wrote:
>
> (snip on keeping extra precision)
>
>> There's an implication there (perhaps unintended) that discarding
>> excess precision wasn't required until C99. That conclusion
>> doesn't jibe with other, pre-C99, documents.


I've never owned a copy of C89; they didn't become cheap enough to
justify buying one until after I'd already switched to C99, and
therefore no longer needed a copy of C89. Therefore, I can't be sure
whether or not C89 can be read as permitting or prohibiting excess
precision. However, I'm fairly certain, from what I've read, that it did
not explicitly address the issue. That would explain why Tim has to use
such subtle arguments as he has, rather than being able to simply cite a
specific clause that would clearly be violated by retaining excess
precision.

I'd be interested in seeing an example of code that, when compiled by
gcc with -std=c99 (and without -fexcess_precision=fast), fails to
satisfy the C99 standard's very lax requirements on the accuracy of
floating point operations, due to incorrect use of excess precision. gcc
does not pre#define __STDC_IEC_559__, even when compiling for hardware
compliant with IEC 60559, thereby exempting it from all but the most
basic of the standard's accuracy requirements. I would be surprised if
gcc's developers mishandled any case that was simple enough to be
meaningfully constrained by such lax requirements.
--
James Kuyper
 
Reply With Quote
 
mathog
Guest
Posts: n/a
 
      04-23-2013
Tim Rentsch wrote:
> There are various reasons for having
> 'volatile' in the language, and also various reasons for using
> it in certain circumstances, but as far as the compiler goes
> the primary consequence of a volatile access is this: the
> generated code must reference the actual memory location
> involved, no matter _what_ the compiler might think about
> what other code might have "the same effect".


That was the explanation I was after.

Thanks,

David Mathog


 
Reply With Quote
 
James Kuyper
Guest
Posts: n/a
 
      04-24-2013
On 04/22/2013 11:25 AM, David Brown wrote:
> On 22/04/13 14:45, Stephen Sprunk wrote:
>> On 22-Apr-13 02:48, David Brown wrote:

....
>>> Optimisation level flags in themselves should never change the
>>> behaviour of a program - just its size and speed.

>>
>> It is also well-known that optimization often results in different
>> manifestations of undefined behavior. ITYM that optimization shouldn't
>> affect conformance, which I think we can all agree with.
>>
>> S
>>

>
> Undefined behaviour is, by definition, undefined. It should not be a
> surprise if the actual effect varies according to optimisation level -
> it should not be a surprise if it varies from run to run, or in any
> other way.
>
> But we do agree that optimisation levels should not affect the
> observable behaviour of well-defined code.



In general, all unspecified behavior can change with optimization level
- which is the whole point, since one key thing that the standards
leaves unspecified is the speed with which your code executes. However,
unspecified behavior also includes implementation-defined behavior.
There's a lot of room for unexpected consequences, without having to
invoke undefined behavior. In principle, INT_MAX could change with
optimization level. This all follows from the simple fact that the
standard doesn't talk about optimization levels.
--
James Kuyper
 
Reply With Quote
 
Tim Rentsch
Guest
Posts: n/a
 
      04-24-2013
glen herrmannsfeldt <(E-Mail Removed)> writes:

> Tim Rentsch <(E-Mail Removed)> wrote:
>
> (snip)
>>>> What's more, assignment has _always_ implied removing any extra
>>>> range and precision, because in the abstract machine we actually
>>>> store the value in an object, and then on subsequent accesses
>>>> convert the stored representation to a value. Under the as-if
>>>> rule, any information beyond what the object representation can
>>>> represent must be lost.
>>>
>>> I agree for -O0, and probably -O1, but at the higher optimization
>>> levels one of the things you expect is to keep values in registers
>>> throughout loops, and in general don't evaluate expressions more
>>> often than necessary.

>>
>> Whatever you might expect, such optimizations (with wide
>> floating-point registers) are not allowed in conforming
>> implementations of ISO C, and that has been true since 1990.

>
> I suppose reordering statements is also disallowed by the
> standard. Moving things outside loops, and such. Fine, then
> don't use the high optimization modes.


Rather than opine in ignorance, why not read what the language
definition actually admits in this regard?

http://www.open-std.org/jtc1/sc22/wg...docs/n1256.pdf
http://www.open-std.org/jtc1/sc22/wg...docs/n1570.pdf

Not official standard documents, but either is close enough
to give accurate answers about source transformations.

>>> One idea behind optimization is that the compiler knows
>>> more than the programmer, such that the programmer can
>>> write things in the most readable form without worrying
>>> about how slow it is.

>>
>> Even if the qualifying assumption is true, the reasoning is
>> irrelevant, because optimized code is still required to
>> observe the abstract semantics of the language. The
>> "optimization" you are talking about is not faithful to the
>> abstract semantics of ISO C, and ergo is not conforming.

>
> I first knew about optimizing compilers in Fortran, where they
> were doing it since before C existed.


The original Fortran didn't have a defining document. Whatever
the compiler did was how the language worked. The orginal C was
that way too, and compounded by the lack of a standard, because
of the variety of choices made by different implementations. If
you yearn for "the good old days", you might read Fred Brooks's
comments in The Mythical Man-Month about System/360 and its
architectural definition, contrasted with the previous generation
of IBM machines.

> Now, Fortran does seem to be less restrictive on floating
> point, but much of it people live with because the speed-up is
> worthwhile. If it reduces the run time from six days to five
> days, people will do it.


What you mean is some people will do it. No doubt some will.
Others want more reliable guarantees.

> Some people like to do scientific programming in C that would
> otherwise be done in Fortran. They also expect reasonable
> optimization.


Another over-generalization. Plus you're implying that what
optimization ISO C does allow is not "reasonable". It seems
more likely that your comment springs from ignorance of just
what ISO C does allow, and also why.

> Seems to me that compilers should state that with -O3 that it
> doesn't conform, and you have the choice to use it or not.


As it is stated, this suggestion is hopelessly naive.
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Really throwing this out there - does anyone have a copy of my oldDancer web browser? steven.miale@gmail.com Python 1 04-10-2013 03:32 PM
Someone can explain this to me? Tosh Cisco 4 11-19-2005 03:48 PM
Can someone explain this route-map command bhamoo@gmail.com Cisco 8 02-04-2005 12:35 PM
Can anyone explain me about this command? Keung Cisco 1 11-28-2004 11:20 AM
Can someone explain this? Ray Microsoft Certification 1 09-01-2003 02:12 AM



Advertisments