EmbeddedRelated.com
Forums

IAR generates very long "switch" assembler

Started by htrada2 September 21, 2006
Hi,
Im programming in C with IAR and Ive found that some switch
statements generate really long assembler code, and they even call a
subrutine called switchkey8.

here is an example of the switch statement

switch(menu_param[i][COL_TIPO])
to calculate the jump needed for every case, it take more than
200bytes of asm code. Has anyone analized this problem??
thanks
Hernan



Beginning Microcontrollers with the MSP430

It's not the switch{}, it's because you're using a 2 dimensional array.
Runtime needs to calculate the offset twice into the array.
If you're pressed with execution times, replacing array offsets with pointers makes a big
difference in speed and code size in most cases.

HTH
-- Kris

-----Original Message-----
From: m... [mailto:m...] On Behalf Of htrada2
Sent: Thursday, 21 September 2006 10:46 PM
To: m...
Subject: [msp430] IAR generates very long "switch" assembler

Hi,
Im programming in C with IAR and Ive found that some switch
statements generate really long assembler code, and they even call a
subrutine called switchkey8.

here is an example of the switch statement

switch(menu_param[i][COL_TIPO])
to calculate the jump needed for every case, it take more than
200bytes of asm code. Has anyone analized this problem??
thanks
Hernan



htrada2 wrote:
> Hi,
> I'm programming in C with IAR and I've found that some switch
> statements generate really long assembler code, and they even call a
> subrutine called switchkey8.
>
> here is an example of the switch statement
>
> switch(menu_param[i][COL_TIPO])
> to calculate the jump needed for every case, it take more than
> 200bytes of asm code. Has anyone analized this problem??

Yes, it's true that it does that. The reason is that the subroutine is
fast since it utilizes binary search (in each iteration it halves the
remainder of cases left to try).

Note that the switch code strategy choice depends on the optimization
goal of your compiler, try to use "size" rather than speed.

Also, if you don't have the latest version of the compiler you should
consider an upgrade -- in that version we added an inlined version of
binary search that is both small and fast.

-- Anders Lindgren, IAR Systems
--
Disclaimer: Opinions expressed in this posting are strictly my own and
not necessarily those of my employer.
You really need to be using the even_in_range() IAR-ism:-
This generates a very efficient branch table using the the
program counter and an even value offset. This is very efficent.

#pragma vector=TIMERA1_VECTOR
__interrupt void Timer_A1(void) {
switch(__even_in_range(TAIV,10))
{
case 2: /* TACCR1 */ break;
case 4: /* TACCR2 */ break;
case 6: /* TACCR3 */ break;
case 8: /* TACCR4 */ break;
case 10: /* TAIFG OVR */ break;
}
}
--- In m..., "Microbit" wrote:
>
> It's not the switch{}, it's because you're using a 2 dimensional
array.
> Runtime needs to calculate the offset twice into the array.
> If you're pressed with execution times, replacing array offsets
with pointers makes a big
> difference in speed and code size in most cases.
>
> HTH
> -- Kris
>
> -----Original Message-----
> From: m... [mailto:m...] On
Behalf Of htrada2
> Sent: Thursday, 21 September 2006 10:46 PM
> To: m...
> Subject: [msp430] IAR generates very long "switch" assembler
>
> Hi,
> Im programming in C with IAR and Ive found that some switch
> statements generate really long assembler code, and they even call
a
> subrutine called switchkey8.
>
> here is an example of the switch statement
>
> switch(menu_param[i][COL_TIPO])
>
>
> to calculate the jump needed for every case, it take more than
> 200bytes of asm code. Has anyone analized this problem??
> thanks
> Hernan
>
>
>
>
>
>
>
>
>
>
colin_garlick wrote:
> You really need to be using the even_in_range() IAR-ism:-
> This generates a very efficient branch table using the the
> program counter and an even value offset. This is very efficent.
>
> #pragma vector=TIMERA1_VECTOR
> __interrupt void Timer_A1(void) {
> switch(__even_in_range(TAIV,10))
> {
> case 2: /* TACCR1 */ break;
> case 4: /* TACCR2 */ break;
> case 6: /* TACCR3 */ break;
> case 8: /* TACCR4 */ break;
> case 10: /* TAIFG OVR */ break;
> }
> }

Oh, yes, I forgot about that one -- thanks Colin!

However, if you plan to use the __even_in_range method make sure you
read the fine print in the manual. This only works if the value actually
is even and in the range you promise -- if not, things will become very
bad indeed...

For you that likes to understand the underlying mechanisms, this
construction generates a single instruction: "ADD.W xxx, PC". This is
specifically tailored for interrupt dispatchers that handle TAIV and
similar interrupts.

-- Anders Lindgren, IAR Systems
--
Disclaimer: Opinions expressed in this posting are strictly my own and
not necessarily those of my employer.
Hi,
Ive tried using __even_in_range and it generated the "ADD.W xxx, PC"
assembler as you told me. Despite this, my code got only 1 byte
smaller. I guess Ill make IAR decide wich is the best way to do it.
Because __even_in_range has to be used with care cause it might make
my code buggier.
What was really usefull was changing the values of the case XXX
here is an example of optimization. This seems to be very inefficient
switch(a)
{
case 1:
break;
case 10:
break;
case 15:
break;
case 90:
break;
}

I changed it for:
switch(a)
{
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
break;
}

in my case I reduced the code 60 bytes.
Thanks for your help,
Hernan

--- In m..., Anders Lindgren
wrote:
>
> colin_garlick wrote:
> > You really need to be using the even_in_range() IAR-ism:-
> > This generates a very efficient branch table using the the
> > program counter and an even value offset. This is very efficent.
> >
> > #pragma vector=TIMERA1_VECTOR
> > __interrupt void Timer_A1(void) {
> > switch(__even_in_range(TAIV,10))
> > {
> > case 2: /* TACCR1 */ break;
> > case 4: /* TACCR2 */ break;
> > case 6: /* TACCR3 */ break;
> > case 8: /* TACCR4 */ break;
> > case 10: /* TAIFG OVR */ break;
> > }
> > }
>
> Oh, yes, I forgot about that one -- thanks Colin!
>
> However, if you plan to use the __even_in_range method make sure
you
> read the fine print in the manual. This only works if the value
actually
> is even and in the range you promise -- if not, things will become
very
> bad indeed...
>
> For you that likes to understand the underlying mechanisms, this
> construction generates a single instruction: "ADD.W xxx, PC". This
is
> specifically tailored for interrupt dispatchers that handle TAIV
and
> similar interrupts.
>
> -- Anders Lindgren, IAR Systems
> --
> Disclaimer: Opinions expressed in this posting are strictly my own
and
> not necessarily those of my employer.
>



It's not a code-size optimistion, although it could be.
It's main purpose fro being is to keep interrupt routines
efficiently fast and responsive. Very much more effective,
than a chain of sequential CMP and BNE type instructions.

The only "buggier" aspect would be only if you enter the routine
with an unexpected value (ie PC+value, where value is beyond the end
of the jump table). Or you get the 2nd parameter wrong. The only
resrtriction is that it needs to be any number of even values
0,2,4,6,8 to work.

Every jump entry has to point to an even address. It will not work
for 0,1,2,3,4. But I guess you could shift the operand left before
feeding it to the switch statement safely ANDed back within range
eg switch(__even_in_range((TAIV & 0x07)<<1),16)) or similar.
This ought to prevent it running out of range. And appear to work
safely for 0,1,2,3,4 instead of 0,2,4,6.

Colin,

--- In m..., "htrada2" wrote:
>
> Hi,
> Ive tried using __even_in_range and it generated the "ADD.W xxx,
PC"
> assembler as you told me. Despite this, my code got only 1 byte
> smaller. I guess Ill make IAR decide wich is the best way to do
it.
> Because __even_in_range has to be used with care cause it might
make
> my code buggier.
> What was really usefull was changing the values of the case XXX
>
>
> here is an example of optimization. This seems to be very
inefficient
> switch(a)
> {
> case 1:
> break;
> case 10:
> break;
> case 15:
> break;
> case 90:
> break;
> }
>
> I changed it for:
> switch(a)
> {
> case 0:
> break;
> case 1:
> break;
> case 2:
> break;
> case 3:
> break;
> }
>
> in my case I reduced the code 60 bytes.
> Thanks for your help,
> Hernan
>
>
>
>
> --- In m..., Anders Lindgren
> wrote:
> >
> > colin_garlick wrote:
> > > You really need to be using the even_in_range() IAR-ism:-
> > > This generates a very efficient branch table using the the
> > > program counter and an even value offset. This is very
efficent.
> > >
> > > #pragma vector=TIMERA1_VECTOR
> > > __interrupt void Timer_A1(void) {
> > > switch(__even_in_range(TAIV,10))
> > > {
> > > case 2: /* TACCR1 */ break;
> > > case 4: /* TACCR2 */ break;
> > > case 6: /* TACCR3 */ break;
> > > case 8: /* TACCR4 */ break;
> > > case 10: /* TAIFG OVR */ break;
> > > }
> > > }
> >
> > Oh, yes, I forgot about that one -- thanks Colin!
> >
> > However, if you plan to use the __even_in_range method make sure
> you
> > read the fine print in the manual. This only works if the value
> actually
> > is even and in the range you promise -- if not, things will
become
> very
> > bad indeed...
> >
> > For you that likes to understand the underlying mechanisms, this
> > construction generates a single instruction: "ADD.W xxx, PC".
This
> is
> > specifically tailored for interrupt dispatchers that handle TAIV
> and
> > similar interrupts.
> >
> > -- Anders Lindgren, IAR Systems
> > --
> > Disclaimer: Opinions expressed in this posting are strictly my
own
> and
> > not necessarily those of my employer.
>



As I said, it's not the switch{}, but the two-dim array offset that's creating all the code
overhead.
__even_in_range is a nice intrinsic actually, but that will only help when you have fixed, preset
case values, not arbitrary values from eg. the array offset - especially if they're variable at
runtime.

The difference between the 2 switch bodies you gave is that the values are "spread out" in the 1st
and consecutively enumerated in the 2nd.

For the 2nd case, a computed branch can be used. The branch takes the offset value and fetches a
value from a jump table (the case statements). The code then jumps straight to that case.
If there are too many "gaps" between the case values, the computed branch needs to stuff all these
gaps with a jump to default.
There's a threshold where all this extra stuffing will use more code than a simple decision tree
ie. if-then, if-then, if-then etc....
If you use say only 2 case statements, then the compiler probably will still use a decision tree,
even if they are consecutive values.

Best is to always try to make the case values as consecutive as possible. This will ensure nice
tight, fast code.

B rgds
Kris

-----Original Message-----
From: m... [mailto:m...] On Behalf Of htrada2
Sent: Friday, 22 September 2006 1:41 AM
To: m...
Subject: [msp430] Re: IAR generates very long "switch" assembler

Hi,
Ive tried using __even_in_range and it generated the "ADD.W xxx, PC"
assembler as you told me. Despite this, my code got only 1 byte
smaller. I guess Ill make IAR decide wich is the best way to do it.
Because __even_in_range has to be used with care cause it might make
my code buggier.
What was really usefull was changing the values of the case XXX
here is an example of optimization. This seems to be very inefficient
switch(a)
{
case 1:
break;
case 10:
break;
case 15:
break;
case 90:
break;
}

I changed it for:
switch(a)
{
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
break;
}

in my case I reduced the code 60 bytes.
Thanks for your help,
Hernan

--- In m..., Anders Lindgren
wrote:
>
> colin_garlick wrote:
> > You really need to be using the even_in_range() IAR-ism:-
> > This generates a very efficient branch table using the the
> > program counter and an even value offset. This is very efficent.
> >
> > #pragma vector=TIMERA1_VECTOR
> > __interrupt void Timer_A1(void) {
> > switch(__even_in_range(TAIV,10))
> > {
> > case 2: /* TACCR1 */ break;
> > case 4: /* TACCR2 */ break;
> > case 6: /* TACCR3 */ break;
> > case 8: /* TACCR4 */ break;
> > case 10: /* TAIFG OVR */ break;
> > }
> > }
>
> Oh, yes, I forgot about that one -- thanks Colin!
>
> However, if you plan to use the __even_in_range method make sure
you
> read the fine print in the manual. This only works if the value
actually
> is even and in the range you promise -- if not, things will become
very
> bad indeed...
>
> For you that likes to understand the underlying mechanisms, this
> construction generates a single instruction: "ADD.W xxx, PC". This
is
> specifically tailored for interrupt dispatchers that handle TAIV
and
> similar interrupts.
>
> -- Anders Lindgren, IAR Systems
> --
> Disclaimer: Opinions expressed in this posting are strictly my own
and
> not necessarily those of my employer.
>