EmbeddedRelated.com
Forums
The 2024 Embedded Online Conference

A compiler optimization puzzle

Started by ratishpunnoose March 1, 2011
I'd like to share an optimization puzzle that I'm stumped with.

In the following function, how many times is "logging"
printed out if the compiler can optimize to the fullest.
This manufactured example is for illustration.
===============================================================
/* A function with the side-effect of output */
static int log_calls(int num_iters)
{
puts("\nlogging");
return (num_iters-1);
}
void myfunc(void)
{
signed int remaining_calls = 1;

while( (P1IN==0) /* Check volatile hardware register.
* Note, compiler does not know
* value of this register*/
&& /* also check following condition */
((remaining_calls = log_calls(remaining_calls)) > 0 )
)
{
/* empty */
}

}

===============================================================
I saw an instance where log_calls() is never called
(regardless of the value of the hardware register)
OR compiled into the assembly.
I don't quite understand why.
Thanks
Ratish

Beginning Microcontrollers with the MSP430

It probably has nothing to do with the compiler.

I have seen lots and lots of postings that asked: "The compiler did not generate any error or warning. Why does it not work?"

--OCY

--- In m..., "ratishpunnoose" wrote:
>
> I'd like to share an optimization puzzle that I'm stumped with.
>
> In the following function, how many times is "logging"
> printed out if the compiler can optimize to the fullest.
> This manufactured example is for illustration.
> ===============================================================>
> /* A function with the side-effect of output */
> static int log_calls(int num_iters)
> {
> puts("\nlogging");
> return (num_iters-1);
> }
> void myfunc(void)
> {
> signed int remaining_calls = 1;
>
> while( (P1IN==0) /* Check volatile hardware register.
> * Note, compiler does not know
> * value of this register*/
> && /* also check following condition */
> ((remaining_calls = log_calls(remaining_calls)) > 0 )
> )
> {
> /* empty */
> }
>
> }
>
> ===============================================================> I saw an instance where log_calls() is never called
> (regardless of the value of the hardware register)
> OR compiled into the assembly.
> I don't quite understand why.
> Thanks
> Ratish
>

The compiler doesn't need to generate the function log_calls - but it
/does/ have to print out "\nlogging" once (if P1IN goes to 0, of
course). It can translate the function into the equivalent code:

void myfunc(void) {
if (P1IN != 0) return;
puts("\nlogging");
}

On 01/03/11 20:07, ratishpunnoose wrote:
> I'd like to share an optimization puzzle that I'm stumped with.
>
> In the following function, how many times is "logging"
> printed out if the compiler can optimize to the fullest.
> This manufactured example is for illustration.
>
> ===============================================================>
> /* A function with the side-effect of output */
> static int log_calls(int num_iters)
> {
> puts("\nlogging");
> return (num_iters-1);
> }
>
> void myfunc(void)
> {
> signed int remaining_calls = 1;
>
> while( (P1IN==0) /* Check volatile hardware register.
> * Note, compiler does not know
> * value of this register*/
> && /* also check following condition */
> ((remaining_calls = log_calls(remaining_calls)) > 0 )
> )
> {
> /* empty */
> }
>
> }
>
> ===============================================================>
> I saw an instance where log_calls() is never called
> (regardless of the value of the hardware register)
> OR compiled into the assembly.
> I don't quite understand why.
>
> Thanks
> Ratish
>

Thanks David, That is what I expected.

In this case, it does not print out "\nlogging" ever (if P1IN is 0).
In fact myfunc is effectively transformed into a single instruction.

myfunc:
0041A2 93C2 0020 tst.b &P1IN
0041A6 4130 ret

It has to access P1IN, since it is volatile, but that is all that happens.

If remaining_calls is 2, then "\nlogging" is printed out twice (as expected), and the extra compare and print is generated as seen below.

myfunc:
00414C 120A push.w R10
00414E 436A mov.b #0x2,R10
004150 93C2 0020 tst.b &P1IN
004154 2006 jne 0x4162
004156 403C 400D mov.w #0x400D,R12
00415A 12B0 40B8 call #puts
00415E 537A add.b #0xFF,R10
004160 23F7 jne 0x4150
004162 413A pop.w R10
004164 4130 ret

Also, eliminating the volatile hardware register read, makes it work as expected:
while( (1)
&&
((remaining_calls = log_calls(remaining_calls)) > 0 )
)
{
/* empty */
}

------
Ratish

--- In m..., David Brown wrote:
>
> The compiler doesn't need to generate the function log_calls - but it
> /does/ have to print out "\nlogging" once (if P1IN goes to 0, of
> course). It can translate the function into the equivalent code:
>
> void myfunc(void) {
> if (P1IN != 0) return;
> puts("\nlogging");
> }
>
>
> On 01/03/11 20:07, ratishpunnoose wrote:
> > I'd like to share an optimization puzzle that I'm stumped with.
> >
> > In the following function, how many times is "logging"
> > printed out if the compiler can optimize to the fullest.
> > This manufactured example is for illustration.
> >
> > ===============================================================> >
> > /* A function with the side-effect of output */
> > static int log_calls(int num_iters)
> > {
> > puts("\nlogging");
> > return (num_iters-1);
> > }
> >
> > void myfunc(void)
> > {
> > signed int remaining_calls = 1;
> >
> > while( (P1IN==0) /* Check volatile hardware register.
> > * Note, compiler does not know
> > * value of this register*/
> > && /* also check following condition */
> > ((remaining_calls = log_calls(remaining_calls)) > 0 )
> > )
> > {
> > /* empty */
> > }
> >
> > }
> >
> > ===============================================================> >
> > I saw an instance where log_calls() is never called
> > (regardless of the value of the hardware register)
> > OR compiled into the assembly.
> > I don't quite understand why.
> >
> > Thanks
> > Ratish
>

On 01/03/2011 21:18, ratishpunnoose wrote:
> Thanks David, That is what I expected.
>
> In this case, it does not print out "\nlogging" ever (if P1IN is 0).
> In fact myfunc is effectively transformed into a single instruction.
>
> myfunc:
> 0041A2 93C2 0020 tst.b &P1IN
> 0041A6 4130 ret
>
> It has to access P1IN, since it is volatile, but that is all that happens.
>

What compiler are you using? I believe that your compiler has a bug
here, and that should be reported.

Of course, it is hideous code - no sane programmer would write anything
like that. But the compiler should generate correct code no matter what
the programmer throws at it.

mvh.

David

> If remaining_calls is 2, then "\nlogging" is printed out twice (as
> expected), and the extra compare and print is generated as seen below.
>
> myfunc:
> 00414C 120A push.w R10
> 00414E 436A mov.b #0x2,R10
> 004150 93C2 0020 tst.b &P1IN
> 004154 2006 jne 0x4162
> 004156 403C 400D mov.w #0x400D,R12
> 00415A 12B0 40B8 call #puts
> 00415E 537A add.b #0xFF,R10
> 004160 23F7 jne 0x4150
> 004162 413A pop.w R10
> 004164 4130 ret
>
> Also, eliminating the volatile hardware register read, makes it work as
> expected:
> while( (1)
> &&
> ((remaining_calls = log_calls(remaining_calls)) > 0 )
> )
> {
> /* empty */
> }
>
> ------
> Ratish
>
> --- In m... , David
> Brown wrote:
> >
> > The compiler doesn't need to generate the function log_calls - but it
> > /does/ have to print out "\nlogging" once (if P1IN goes to 0, of
> > course). It can translate the function into the equivalent code:
> >
> > void myfunc(void) {
> > if (P1IN != 0) return;
> > puts("\nlogging");
> > }
> >
> >
> >
> >
> > On 01/03/11 20:07, ratishpunnoose wrote:
> > > I'd like to share an optimization puzzle that I'm stumped with.
> > >
> > > In the following function, how many times is "logging"
> > > printed out if the compiler can optimize to the fullest.
> > > This manufactured example is for illustration.
> > >
> > > ===============================================================> > >
> > > /* A function with the side-effect of output */
> > > static int log_calls(int num_iters)
> > > {
> > > puts("\nlogging");
> > > return (num_iters-1);
> > > }
> > >
> > > void myfunc(void)
> > > {
> > > signed int remaining_calls = 1;
> > >
> > > while( (P1IN==0) /* Check volatile hardware register.
> > > * Note, compiler does not know
> > > * value of this register*/
> > > && /* also check following condition */
> > > ((remaining_calls = log_calls(remaining_calls)) > 0 )
> > > )
> > > {
> > > /* empty */
> > > }
> > >
> > > }
> > >
> > > ===============================================================> > >
> > > I saw an instance where log_calls() is never called
> > > (regardless of the value of the hardware register)
> > > OR compiled into the assembly.
> > > I don't quite understand why.
> > >
> > > Thanks
> > > Ratish
> > >
>

On 03/01/2011 02:07 PM, ratishpunnoose wrote:
>
>
> I'd like to share an optimization puzzle that I'm stumped with.
>
> In the following function, how many times is "logging"
> printed out if the compiler can optimize to the fullest.
> This manufactured example is for illustration.

Rewritten for clarity:

void myfunc(void) {
signed int remaining_calls = 1;

while( (P1IN==0) && ((remaining_calls = log_calls(remaining_calls)) > 0 ) )
{ }

}

Okay, this is easy, if P1IN is not 0 then log_calls does not get
called. It's your logic, the &&. 0 && 1 or 0 && 0 are false so there
is no need to execute the second part of the while conditional.

--
Linux Home Automation Neil Cherry n...@linuxha.com
http://www.linuxha.com/ Main site
http://linuxha.blogspot.com/ My HA Blog
Author of: Linux Smart Homes For Dummies
--- In m..., Neil Cherry wrote:

> signed int remaining_calls = 1;
>
> while( (P1IN==0) && ((remaining_calls = log_calls(remaining_calls)) > 0 ) )
> { }
> Okay, this is easy, if P1IN is not 0 then log_calls does not get
> called. It's your logic, the &&. 0 && 1 or 0 && 0 are false so there
> is no need to execute the second part of the while conditional.

It is not called even if P1IN is zero.
Please see the assembly listing in previous post.
Ratish

> What compiler are you using? I believe that your compiler has a bug
> here, and that should be reported.
>
> Of course, it is hideous code - no sane programmer would write anything
> like that. But the compiler should generate correct code no matter what
> the programmer throws at it.
>
> mvh.
> David

David, I agree that this is code that is quite unusual. I ran across this as a result of a macro expansion. I simplified it to an easy to demonstrate example. The compiler is IAR (latest version).
I can't help wondering if there is some subtlety that I have missed.

Thanks
Ratish

On 02/03/11 18:20, ratishpunnoose wrote:
> > What compiler are you using? I believe that your compiler has a bug
> > here, and that should be reported.
> >
> > Of course, it is hideous code - no sane programmer would write anything
> > like that. But the compiler should generate correct code no matter what
> > the programmer throws at it.
> >
> > mvh.
> > David
>
> David, I agree that this is code that is quite unusual. I ran across
> this as a result of a macro expansion. I simplified it to an easy to
> demonstrate example. The compiler is IAR (latest version).
> I can't help wondering if there is some subtlety that I have missed.
>

I don't think we have missed anything - I have tested your code with
several gcc compilers (new and old msp430 compilers, several AVR
compilers, and gcc for the ColdFire - I have a batch file setup that I
use for this sort of testing). They all agree with our interpretation
of the code.

However, I suggest you check it with IAR. Either it /is/ a bug, or they
know more about the interpretation of horrible C code than we do :-)

> I don't think we have missed anything - I have tested your code with
> several gcc compilers (new and old msp430 compilers, several AVR
> compilers, and gcc for the ColdFire - I have a batch file setup that I
> use for this sort of testing). They all agree with our interpretation
> of the code.
>
> However, I suggest you check it with IAR. Either it /is/ a bug, or they
> know more about the interpretation of horrible C code than we do :-)
>

Thanks very much David, I'll do that. I refactored the code into the following easy to understand snippet and it exhibits the same problem.
-------
static int log_calls(int num_iters)
{
puts("\nlogging");
return (num_iters-1);
}

void myfunc(void)
{
signed int remaining_calls = 1;
while(P1IN==0)
{
remaining_calls = log_calls(remaining_calls);
if (remaining_calls > 0 )
{
continue;
} else {
break;
}

}
}
-------


The 2024 Embedded Online Conference