[comp.compilers] RS/6000 Optimizer breaks code -- suggestions?

banshee@ucscb.ucsc.edu (Wailin' Through The Nets) (08/18/90)

This fragment of code breaks when the following function is inlined and
the code is compiled with -O on a RS/6000.  Question, why?  Note that
without -O everything works fine, even with the function inlined.
But -O kills it.

tradd(b)
union g {long xx; struct half yy;} *b;
{
	b->yy.high &= 077777;	/* WHY CAN'T I INLINE THIS? */
}

m_mult(a,b,c)
struct mint *a,*b,*c;
{
	long x;
	union {long xx; struct half yy;} sum;
	int carry;
	int i,j;
	c->val=xalloc(a->len+b->len,"m_mult");
	sum.xx=0;
	for(i=0;i<b->len;i++)
	{	carry=0;
		for(j=0;j<i+1;j++)
		{	
		sum.xx += (a->val[j])*(b->val[i-j]);
	   	if (sum.yy.high & 0100000) { 
	      		tradd(&sum); 
/*	sum.yy.high &= 077777;		*/
	      		carry += 1; 
	   	}
		c->val[i]=sum.yy.low & 077777;
		sum.xx=sum.xx >> 15;
		sum.yy.high=carry;
	etc etc etc
[Sounds to me like an optimizer bug.  Have you reported it to IBM? -John]
-- 
Send compilers articles to compilers@esegue.segue.boston.ma.us
{ima | spdcc | world}!esegue.  Meta-mail to compilers-request@esegue.

jar@florida.eng.ileaf.com (Jim Roskind x5570) (08/21/90)

>   This fragment of code breaks when the following function is inlined and
>   the code is compiled with -O on a RS/6000.  Question, why?  Note that
>   without -O everything works fine, even with the function inlined.

As with many optimizer related problems, you were simply lucky it worked
without optimization!  You were slightly justified in your expectation,
but still luck was key!

>   But -O kills it.
>   
>   tradd(b)
>   union g {long xx; struct half yy;} *b;

Note that you define a tag "g" here.  Note that any other union or struct
that "happens" to look like it does not have to *really* be implemented
identically, *UNLESS* you reuse the tag!  (I assume you have defined
"half" somewhere else).  If you have severely different expectations, you
should take them up with the ANSI C Committee, as I believe this is in
keeping with the standard.

>   {
>   	b->yy.high &= 077777;	/* WHY CAN'T I INLINE THIS? */
>   }
>   
>   m_mult(a,b,c)
>   struct mint *a,*b,*c;
>   {
>   	long x;
>   	union {long xx; struct half yy;} sum;

Here you define another union, but you did *NOT* use the tag "g".  As a
result, the compiler has the freedom to "optimize" the layout of these
distinct unions.  *IF* you had simply said "union g sum;" you probably
would be safe (unless there was a real bug in the compiler).  In the form
given here, all bets are off in terms of guarantees of compatibility of
the two union types.  My guess is that the optimizing compiler realized
that you had declared a local union, and placed the elements individually
in a convenient place on the stack or in registers.  A lot of folks use
unions to sneakily cast from one data type to another, but there are very
few guarantees that such attempts will work!

>   	int carry;
>   	int i,j;
>   	c->val=xalloc(a->len+b->len,"m_mult");
>   	sum.xx=0;
>   	for(i=0;i<b->len;i++)
>   	{	carry=0;
>   		for(j=0;j<i+1;j++)
>   		{	
>   		sum.xx += (a->val[j])*(b->val[i-j]);
>   	   	if (sum.yy.high & 0100000) { 
>   	      		tradd(&sum); 

The above statement could have given you a warning that the type of
the argument that you passed to "tradd" was different from the
"expected" type for an argument.  However, since you did *NOT*
prototype the function (and the above old style definition for "tradd"
does *NOT* constitute a prototype) the compiler simply looked the
other way.  In fact, you could have said "tradd(3.14259)" and it would
not have complained :-(.

>   /*	sum.yy.high &= 077777;		*/
>   	      		carry += 1; 
>   	   	}
>   		c->val[i]=sum.yy.low & 077777;
>   		sum.xx=sum.xx >> 15;
>   		sum.yy.high=carry;
>   	etc etc etc


Moral of the story:

1) Use function prototypes and the compiler would have given you an
error message, rather than a subtle bug.

2) Avoid the use of casts.  Avoid the use of unions to hide the
fact that you were wanted to use casts.

3) Optimizers are pretty clever.  

4) If you define the same value twice in a program, you are asking for
trouble.  Generally such redefinitions get out of sync.  Even if they
"look the same", they might not be.  It is better to work to reuse a
single definition.  This was an interesting extreme example of two
definitions "looking the same".

5) This was really a comp.lang.c question, but it shows just how far
good optimizers can go.  It is their job to assume as much as the law
(standard) allows, and not one bit more.  In this case, they followed
the law, and your code, which lived outside the law, broke.  


Jim Roskind
Independent Consultant
(407)729-4348 or (617)577-9813 x5570
jar@hq.ileaf.com or ...!uunet!leafusa!jar
[Actually, my reading of the standard implies pretty strongly that all
similar definitions of a union be equivalent, but it explicitly says that
the behavior when you store one element in a union and read back another is
in almost all cases undefined.  Jim's point that an optimizer often breaks
programs that depend on semantics not guaranteed by the language is a good
one. -John]
-- 
Send compilers articles to compilers@esegue.segue.boston.ma.us
{ima | spdcc | world}!esegue.  Meta-mail to compilers-request@esegue.

worley@compass.com (Dale Worley) (08/25/90)

[In reagard to two similar unions, only one of which has a tag, and which
don't do what the author expected.]
    Note that you define a tag "g" here.  Note that any other union or struct
    that "happens" to look like it does not have to *really* be implemented
    identically, *UNLESS* you reuse the tag!  (I assume you have defined
    "half" somewhere else).  If you have severely different expectations, you
    should take them up with the ANSI C Committee, as I believe this is in
    keeping with the standard.

    Here you define another union, but you did *NOT* use the tag "g".  As a
    result, the compiler has the freedom to "optimize" the layout of these
    distinct unions.

Strictly speaking, you're correct.  However, for any sane implementation,
there are some tight constraints: a pointer to a union must point to each
of its members (ANSI 3.5.2.1) -- therefore, all members start at the same
place, and so unions of the same types of fields pretty will have to be
laid out the same.

Again, if two structs are members of a union, and they have an initial set
of members of the same type, then those members have to be laid out the
same way (ANSI 3.3.2.3).

How this applies in this particular example, I don't know precisely, but
it's likely that the optimizer is disregarding one or the other of these
rules.

Dale Worley		Compass, Inc.			worley@compass.com
-- 
Send compilers articles to compilers@esegue.segue.boston.ma.us
{ima | spdcc | world}!esegue.  Meta-mail to compilers-request@esegue.

daveg@near.cs.caltech.edu (Dave Gillespie) (08/26/90)

>>>>> On 24 Aug 90 18:36:06 GMT, worley@compass.com (Dale Worley) said:

>> Here you define another union, but you did *NOT* use the tag "g".  As a
>> result, the compiler has the freedom to "optimize" the layout of these
>> distinct unions.

> Strictly speaking, you're correct.  However, for any sane implementation,
> there are some tight constraints: a pointer to a union must point to each
> of its members (ANSI 3.5.2.1) -- therefore, all members start at the same
> place, and so unions of the same types of fields pretty will have to be
> laid out the same.

> Again, if two structs are members of a union, and they have an initial set
> of members of the same type, then those members have to be laid out the
> same way (ANSI 3.3.2.3).

> ... it's likely that the optimizer is disregarding one or the other of these
> rules.

I don't have the original code handy, but as I recall it did not take
the addresses of both the unions in question, nor did the unions contain
at least two different structs.  So the optimizer is probably right to
disregard these rules---they don't apply in this particular example.
In fact, if the C standard mentions the things you refer to above, but
also explicitly makes no guarantees about general relationships among the
fields of a union, then probably allowing this optimization was exactly
what the standard-writers had in mind.

But, having said all that, I actually agree with your assertion that the
implentation is not "sane", but only because the situations where it's
safe to rearrange a union are probably rare, and more often than not will
occur because the programmer was trying to use the union to fake up a
type cast.  So the optimization is more likely to hurt than to help.

							-- Dave
--
Dave Gillespie
  256-80 Caltech Pasadena CA USA 91125
  daveg@csvax.cs.caltech.edu, ...!cit-vax!daveg
[Can someone run the original code through the RS/6000 compiler, look at
the object code, and report whether the compiler is in fact doing something
strange? IBM is usually pretty good at reading standards. -John]
-- 
Send compilers articles to compilers@esegue.segue.boston.ma.us
{ima | spdcc | world}!esegue.  Meta-mail to compilers-request@esegue.

johnv@metaware.uucp (John Vinopal) (08/27/90)

I have recieved several responses to my query about the RS/6000
optimizer breaking code.  As it turns out (I generated a much
better test case) the optimizer does have a bug when dealing with
unions in the way I was -- and IBM promises a fix soon.

Thanks for the suggstions and help.
-- 
Send compilers articles to compilers@esegue.segue.boston.ma.us
{ima | spdcc | world}!esegue.  Meta-mail to compilers-request@esegue.

johnv@metaware.com (John Vinopal) (08/28/90)

[Commentary deleted]
>But, having said all that, I actually agree with your assertion that the
>implentation is not "sane", but only because the situations where it's
>safe to rearrange a union are probably rare, and more often than not will
>occur because the programmer was trying to use the union to fake up a
>type cast.  So the optimization is more likely to hurt than to help.
>
>[Can someone run the original code through the RS/6000 compiler, look at
>the object code, and report whether the compiler is in fact doing something
>strange? IBM is usually pretty good at reading standards. -John]

The union in this case was in fact used as a type cast of sorts; a method
to prevent a short from overflowing.  Bad style and not portable begins to
sum up this approach.  However...

This has been submitted to IBM tech and verified as a real bug.  If anyone
would like a 'clean' copy of the code in question, please mail requests to
banshee@ucscb.UCSC.EDU.
-- 
Send compilers articles to compilers@esegue.segue.boston.ma.us
{ima | spdcc | world}!esegue.  Meta-mail to compilers-request@esegue.

worley@compass.com (Dale Worley) (08/29/90)

   From: johnv@metaware.com (John Vinopal)

   The union in this case was in fact used as a type cast of sorts; a method
   to prevent a short from overflowing.  Bad style and not portable begins to
   sum up this approach.  However...

Yes, since the results of using a union for type punning are
undefined (ANSI 3.3, 3.5.2.1), one might say that the code is bad
style.  One might also say that it is incorrect, and that the user
can't expect *anything* from it.

Dale Worley		Compass, Inc.			worley@compass.com
-- 
Send compilers articles to compilers@esegue.segue.boston.ma.us
{ima | spdcc | world}!esegue.  Meta-mail to compilers-request@esegue.