This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

  • Resolved

how to use intrinsic of "_even_in_range()"

Prodigy 475 points

Replies: 12

Views: 8353

I want to know why intrinsic of "_even_in_range" is used

in sample of "MSP430F51x2_td0_26.c" ( MSP430ware).

-----------------------------------------------------
#pragma vector=TIMER0_D1_VECTOR
__interrupt void TIMER0_D1_ISR(void)
{
switch(__even_in_range(TD0IV,30))
  {
      case 0:
      ...
-----------------------------------------------------

I think I can do the same thing by below.;
-----------------------------------------------------
  switch(TD0IV)
  {
      case 0:
      ....
-----------------------------------------------------

I read "MSP430 Optimizing C/C++ Compiler v 4.0 User's Guide"(slau132f.pdf P.96) already. 
but I can't understand yet.

Regards
Toshiaki
  • This intrinsic identifies to the compiler that the switch value will always be even and within the range 0-n.  It basically tells it that case1, case3, case5.... case(n-1) will never be executed and therefore allows some optimisation.

    You don't need to use it if you don't want to.

  • In reply to chris_m:

    Basically the compiler is able to generate an efficient jump table, just through adding the switch argument to the instruction pointer. This is a lot faster than comparing and checking for different cases.

    The TI compiler also offers a more generic intrinsic, which in my opinion is more elegant but maybe not alerting enough: it's the _never_executed() intrinsic.

    switch (something)
    {
    case 1:
        xxxx
    case 2:
       xxxxx
    default:
       _never_executed();
    }

    This tells the compiler that the argument of the switch will always have a value which leads to one of the cases and no other values might occur. This allows also optimizations and is not limited to even numbers in a certain range.

    The support team of the compiler told me that there should be no difference in optimization if you use either one of these - though I have never verified this.

    Also noteworthy: the IAR compiler doesn't support the _never_executed() intrinsic as far as I know.

    This technique is most commonly used in interrupt routines with vectors to get them as responsive as possible, and because hardware generally assures you that the value is indeed even and in range - it resembles the way a interrupt vector jump table is written in assembler.

    --------------------------------------------------------------------------------------
    Use the verify answer button to mark your questions as solved.

  • In reply to Bernhard Weller:

    Thank you so much for your reply.

    I understand effect of this intrinsic. I will try to check created code used intrinsic and not.

  • In reply to Bernhard Weller:

    Bernhard Weller
    Basically the compiler is able to generate an efficient jump table, just through adding the switch argument to the instruction pointer.

    That's the key purpose.

    Normally, a switch/case construct translates into something like this:

    switch(a){
      case 1: case 2:
        break;
      case 3:
        break;
      default: ;
    }

    ->

    int b = a; // so a is read only once, to avoid side-effects
    if(b==1 or b==2){
    }
    else
    if (b==3) {
    } else
    ;

    Using the __even_in_range() intrinsic, the compiler creates something like this

    ADD a, PC    ; adds 0,2,4,6... to PC, which effectively is a 'jump' onto one of the following jumps
    JMP case0
    JMP case2
    JMP case4
    ...
    case0: [code]
    case2: [code]
    case4: [code]
    ...

    no matter which case is hit, it only takes 5 MCLK cycles to start executing the associated code, while the 'standard' code is a chain of comparisons and conditional jumps, the last case being the slowest one executed. So it's faster and shorter to use this intrinsic. And the preferred code for ISRs for modules that provide a vector register (e.g. USCI, timers, ADCs and on newer MSPs even the ports)

    Bernhard Weller
    it's the _never_executed() intrinsic.

    It's impact is actually the same as not providing the default case at all, which omits the final else, but triggers a warning on newer compilers. So it just suppresses the warning (allows to be compliant to newer C standard which require the default) without having to generate an empty else in your code (which would waste two bytes of code for a useles branch)
    No wonder that IAR doesn't implement it. :)



    _____________________________________

    Time to say goodbye - I don't have the time anymore to read and answer forum posts. See my bio for details.

    Before posting bug reports or ask for help, do at least quick scan over this article. It applies to any kind of problem reporting. On any forum. And/or look here.
    I'm sorry that  I can no longer provide help  in the forum or by private conversation.

  • In reply to Jens-Michael Gross:

     

    Jens-Michael Gross
    It's impact is actually the same as not providing the default case at all, which omits the final else, but triggers a warning on newer compilers.

    If I'm reading you correctly you are implying that this has no effect on other optimizations - such as generating a jump table. From my readings of the C/C++ code generation tools manual (see section 6.7.3, page 123) this would be incorrect and also the answer I got in the compiler forum suggests that these two intrinsics have the same effect used on a vector generator although _never_executed() is more general and can be used in general switch statements.

     

    --------------------------------------------------------------------------------------
    Use the verify answer button to mark your questions as solved.

  • In reply to Bernhard Weller:

    Well, telling that the default case is never used does not imply that there aren't any cases from 0 to 65535. Including non-even ones. This makes  a jumptable of the add-to-PC type impossible.
    By reading the manual you pointed to, it indeed looks like the compiler tries to make sort of a jumptable liek with the __even_in_range intrinsic. However, the manual doesn't say that a case 1: is invalid when using the _never_executed().

    So I was talking about the case described in 6.7.3.2 of the manual.
    The benefit is that after testing all but one case, you knowdon't need to test for the last one as ther eis no other option (as to the coder's promise). And if, then the code runs wild, introducing a backdoor for bugs.

    Maybe the compiler analyzes the cases and decides whether a jumptable is possible (only even cases) and useful (based on the size required for a complete table compared to individual compares). Or sees that the parameter is an IV register (since it can be resolved at compiletime to a low address)

    Indeed, an __even_in:range() is slower and bigger than a traditional switch when you only need one specific case or two. Or only a few out of a larger number of possible cases.
    But when you say there is no default case and you only use a few of all possible cases...
    This is probably why the example defines all cases, most just with a break. More to type then. With __even_in_range(9 you define only the cases you need and leave the rest to the default, and the jumptable will contain entries to the default code (or to the end of the switch if ther ei sno default code) for all cases you didn't define in the range.

    Really, I don't know why this new intrinsic had to be introduced. There are more important things than that to implement (such as seamless support for >64k and large data model, and 64 bit variables. All of which are still not as good as they could be.

    _____________________________________

    Time to say goodbye - I don't have the time anymore to read and answer forum posts. See my bio for details.

    Before posting bug reports or ask for help, do at least quick scan over this article. It applies to any kind of problem reporting. On any forum. And/or look here.
    I'm sorry that  I can no longer provide help  in the forum or by private conversation.

  • In reply to Jens-Michael Gross:

     

    Jens-Michael Gross
    Really, I don't know why this new intrinsic had to be introduced.

    George said that _never_executed() was supported a quite long time before _even_in_range() - though I don't know if IAR implemented the _even_in_range() before TI implemented _never_executed(). But well that's not a really important matter.

    You are of course right about the possible backdoor for bugs - but you have that possibility with _even_in_range() as well, if it's not even or in range something bad will happen as well.

    I guess I have to try out both intrinsics and see for myself what assembly code is generated. 

    --------------------------------------------------------------------------------------
    Use the verify answer button to mark your questions as solved.

  • In reply to Jens-Michael Gross:

    Jens-Michael Gross
    Bernhard Weller
    it's the _never_executed() intrinsic.
    It's impact is actually the same as not providing the default case at all, which omits the final else, but triggers a warning on newer compilers. So it just suppresses the warning (allows to be compliant to newer C standard which require the default)

    It's been a few  years since I kept close track of C standardization efforts, but this surprised me: can you provide a reference to what version of C started (or will start) requiring a default clause in a switch statement?  I thought compilers just warned about missing cases/defaults to help expose coding errors.

  • In reply to Bernhard Weller:

     

    Bernhard Weller
    I guess I have to try out both intrinsics and see for myself what assembly code is generated. 

    I must be doing something wrong - it won't create a simple addition, the code I tried (based on a TI code example):

    #include "msp430f5418A.h"

    volatile int a = 0;

    void main(void)
    {
      WDTCTL = WDTPW + WDTHOLD;                 // Stop WDT
      TA1CTL = TASSEL_1 + MC_2 + TACLR + TAIE;  // ACLK, contmode, clear TAR
                                                // enable interrupt

      __bis_SR_register(LPM3_bits + GIE);       // Enter LPM3, enable interrupts
      __no_operation();                         // For debugger
    }

    // Timer_A3 Interrupt Vector (TAIV) handler
    #pragma vector=TIMER1_A1_VECTOR
    __interrupt void TIMER1_A1_ISR(void)
    {
      switch(__even_in_range(TA1IV,14))
      {
         case  0: a=0;                
         break;                          
         case  2: a=1;                 
         break;                        
         case  4: a=2;                 
         break;                         
         case  6: a=3;                
         break;                         
         case  8: a=4;                
         break;                        
         case 10: a=5;                
         break;                        
         case 12: a=6;                
         break;                        
         case 14: a=7;                 
         break;
         default:               
         break;
      }
    }

    I've turned on optimizations to level 4, tried both optimization for speed or for size. I haven't selected any of the advanced optimization options (I can't see how they would influence this optimization).

    The generated assembly is quite long. I've attached the ISR part of it as separate text file:3247.generatedASM.txt

    What it does is: take TA1IV and put it to R15, compare it, jump, decrease R15, compare it, jump etc. That's not what I expected.

    On second thought - maybe the volatile variable prevents the compiler from doing it's optimization right, so I've changed it to toggling pins of P1OUT, with the same result. _never_executed() didn't work as well, only difference was (like expected from the name) that the default case was missing.

    Okay P1OUT is also volatile, so third try: instead of using volatile "a", use a static "a" inside the ISR - same result.

    Fourth try: just use a normal int "a" - but well then the complete code is optimized away because it's useless. Well saw that coming, assign the value of "a" after the switch to P1OUT. And I'm back to square zero with the whole cmp-jmp party again.

    So is my expectation wrong? I was thinking of something like  Jens pointed out:

    Jens-Michael Gross
    ADD a, PC ; adds 0,2,4,6... to PC, which effectively is a 'jump' onto one of the following jumps
    JMP case0
    JMP case2
    JMP case4
    ...
    case0: [code]
    case2: [code]
    case4: [code]
    ...

    Also the description in the compiler manual indicates that something like this should be created.

    What am I doing wrong? 

     

    --------------------------------------------------------------------------------------
    Use the verify answer button to mark your questions as solved.

  • In reply to Bernhard Weller:

    Bernhard Weller
    I was thinking of something like  Jens pointed out:
    [...]
    Also the description in the compiler manual indicates that something like this should be created.

    What am I doing wrong?


    Maybe you must not provide a default case if you have all the possible cases defined.
    Or, if you're using the newest CCS, there might be a bug.
    Or, since the description of this intrinsic appears in the paragraph about the vector pragma, it maybe only works as expected if inside an ISR.
    (No reason for this kind of limitation, but well...)

    The code is 16 bytes longer and definitely much slower than what the intrinsic should normally (?) provide.

    I don't use CCS, and I did this coding manually in MSPGCC3 inline assembly where appropriate.

    Peter Bigot
    can you provide a reference to what version of C started (or will start) requiring a default clause in a switch statement?

    I remember getting compile errors on GCC when using -pedantic option. So I assumed it was a change in the standard (C99?). I never checked whether it was indeed a standard change or just a GCC interpretation of what 'shouldn't be' in a 'good program' (the GCC folks can sometimes get a bit "nosy"). It's been some 8 years since I used GCC under Linux.
    Normally, there's indeed only a warning (as I wrote), which is still annoying when you know that there is no 'default' case.
    On older compilers (e.g. BorlandC 3.x and 4.x, the last ones I used before) there was no warning at all. I don't remember whether VisualC++ gives a warning or an error or nothing. But then, Microsoft products usually tend to ignore standards (including Microsofts own ones)
    However, if you have to write the default case and then tell the compiler that the default case is never executed, it is even more to type and (at least on a desktop) no gain.

    _____________________________________

    Time to say goodbye - I don't have the time anymore to read and answer forum posts. See my bio for details.

    Before posting bug reports or ask for help, do at least quick scan over this article. It applies to any kind of problem reporting. On any forum. And/or look here.
    I'm sorry that  I can no longer provide help  in the forum or by private conversation.

This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.