This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Problem with optimisation of volatiles

Hi there All,

I have a problem which seemed to be defying explanation, but I have come up with a theory. Could I possibly have some feedback on whether the following is likely, plausible, possible, untrue or downright rubbish?

If one reads the contents of a CAN or ADC chip register at a particular address, then the label volatile is placed upon that address to prevent the compiler optimising out repeat readings of the address. If one reads the contents of the address into a variable, then the compiler would automatically treat the contents of this variable with similar care.

Is it possible that there has been an oversight with statements where the contents of a variable depend on the contents of a volatile by way of an if statement, ie...

normal_var=volatile_var;

...is correctly optimised, but...

normal_var=voltile_var;
if (normal_var=0x00)
   {
   another_normal_var+=1;
   }

...is not correctly optimised all of the time, dependant on the surrounding code, unless normal_var itself is declared to be volatile?

For info - am using optimisation level...

OPTIMIZE(3,SPEED)

...and am using version...

C166 COMPILER V4.11

Any thoughts, or is any or all of the above thoughts and understanding way off the mark?


Yours (grateful for any input),


Richard.

  • Hi Richard,

    That's more like it. When discussing anything to do with C, I much prefer C code to abstract words :-)
    It sounds like you don't have an in-circuit debugger set up... Anyway, there is no need to try and guess how the compiler optimized your code when you can actually see the generated code. Print out the disassembly of your code and the questions will go away. If you are not familiar with the C166 instruction set, it wouldn't be too dificult to learn. After all, C166 is a RISC-like architecture, there are not that many instructions.
    I would suggest posting compiler listing here if there wasn't that much code.

    Regards,
    - mike

  • Hi Mike,

    Cheers for this suggestion, it looks like it is bearing fruit.

    Looking at the Main.LST file, the differences seem to be
    in the interpretation of the lines...

    CAN_LED_status=RESET;
    if ((temp_a==0x00)&&(temp_b==0x00))
       CAN_LED_status+=Transmit_OK;
    temp_b=ReceiveB;
    temp_a=ReceiveA;
    ReceiveB=0;
    ReceiveA=0;
    if ((temp_a==0x00)&&(temp_b==0xb8))
       CAN_LED_status+=Receive_OK;
    CAN_board_LEDs=CAN_LED_status;
    

    Without declaring the variables here as volatiles
    (when the code fails to 'work properly'), the
    assembler looks like...

    2A6C E00E          MOV       R14,#00H
    2A6E F480DC00      MOVB      RL4,[R0+#0DCH]; temp_a
    2A72 3D04          JMPR      cc_NZ,?C0236
    2A74 F480DE00      MOVB      RL4,[R0+#0DEH]; temp_b
    2A78 3D01          JMPR      cc_NZ,?C0236
    2A7A 08E5          ADD       R14,#05H
    2A7C         ?C0236:
    2A7C D7408000      EXTP      #080H,#01H
    2A80 F3F80500      MOVB      RL4,05H
    2A84 E480DE00      MOVB      [R0+#0DEH],RL4; temp_b
    2A88 D7408000      EXTP      #080H,#01H
    2A8C F3F80400      MOVB      RL4,04H
    2A90 E480DC00      MOVB      [R0+#0DCH],RL4; temp_a
    2A94 E108          MOVB      RL4,#00H
    2A96 D7408000      EXTP      #080H,#01H
    2A9A F7F80500      MOVB      05H,RL4
    2A9E E108          MOVB      RL4,#00H
    2AA0 D7408000      EXTP      #080H,#01H
    2AA4 F7F80400      MOVB      04H,RL4
    2AA8 F480DC00      MOVB      RL4,[R0+#0DCH]; temp_a
    2AAC 3D07          JMPR      cc_NZ,?C0237
    2AAE F480DE00      MOVB      RL4,[R0+#0DEH]; temp_b
    2AB2 47F8B800      CMPB      RL4,#0B8H
    2AB6 3D02          JMPR      cc_NZ,?C0237
    2AB8 06FE0A00      ADD       R14,#0AH
    2ABC         ?C0237:
    2ABC F04E          MOV       R4,R14
    2ABE D7408000      EXTP      #080H,#01H
    2AC2 F7F82E00      MOVB      02EH,RL4
    

    If I declare the four variables as volatile (which makes
    the code do exactly what I want of it), the assembler looks
    like...

    2A82 E108          MOVB      RL4,#00H
    2A84 E480DA00      MOVB      [R0+#0DAH],RL4; CAN_LED_status
    2A88 F480DC00      MOVB      RL4,[R0+#0DCH]; temp_a
    2A8C 3D08          JMPR      cc_NZ,?C0236
    2A8E F480DE00      MOVB      RL4,[R0+#0DEH]; temp_b
    2A92 3D05          JMPR      cc_NZ,?C0236
    2A94 F480DA00      MOVB      RL4,[R0+#0DAH]; CAN_LED_status
    2A98 0985          ADDB      RL4,#05H
    2A9A E480DA00      MOVB      [R0+#0DAH],RL4; CAN_LED_status
    2A9E         ?C0236:
    2A9E D7408000      EXTP      #080H,#01H
    2AA2 F3F80500      MOVB      RL4,05H
    2AA6 E480DE00      MOVB      [R0+#0DEH],RL4; temp_b
    2AAA D7408000      EXTP      #080H,#01H
    2AAE F3F80400      MOVB      RL4,04H
    2AB2 E480DC00      MOVB      [R0+#0DCH],RL4; temp_a
    2AB6 E108          MOVB      RL4,#00H
    2AB8 D7408000      EXTP      #080H,#01H
    2ABC F7F80500      MOVB      05H,RL4
    2AC0 E108          MOVB      RL4,#00H
    2AC2 D7408000      EXTP      #080H,#01H
    2AC6 F7F80400      MOVB      04H,RL4
    2ACA F480DC00      MOVB      RL4,[R0+#0DCH]; temp_a
    2ACE 3D0B          JMPR      cc_NZ,?C0237
    2AD0 F480DE00      MOVB      RL4,[R0+#0DEH]; temp_b
    2AD4 47F8B800      CMPB      RL4,#0B8H
    2AD8 3D06          JMPR      cc_NZ,?C0237
    2ADA F480DA00      MOVB      RL4,[R0+#0DAH]; CAN_LED_status
    2ADE 07F80A00      ADDB      RL4,#0AH
    2AE2 E480DA00      MOVB      [R0+#0DAH],RL4; CAN_LED_status
    2AE6         ?C0237:
    2AE6 F480DA00      MOVB      RL4,[R0+#0DAH]; CAN_LED_status
    2AEA D7408000      EXTP      #080H,#01H
    2AEE F7F82E00      MOVB      02EH,RL4
    

    If I include my infinite loop immediately after the troublesome
    loop, but don't declare the variables as volatile (which also
    makes the code do exactly what I want), then I get the following
    assembler...

    2A22 E108          MOVB      RL4,#00H
    2A24 E480D600      MOVB      [R0+#0D6H],RL4; CAN_LED_status
    2A28 F480D800      MOVB      RL4,[R0+#0D8H]; temp_a
    2A2C 3D08          JMPR      cc_NZ,?C0236
    2A2E F480DA00      MOVB      RL4,[R0+#0DAH]; temp_b
    2A32 3D05          JMPR      cc_NZ,?C0236
    2A34 F480D600      MOVB      RL4,[R0+#0D6H]; CAN_LED_status
    2A38 0985          ADDB      RL4,#05H
    2A3A E480D600      MOVB      [R0+#0D6H],RL4; CAN_LED_status
    2A3E         ?C0236:
    2A3E D7408000      EXTP      #080H,#01H
    2A42 F3F80500      MOVB      RL4,05H
    2A46 E480DA00      MOVB      [R0+#0DAH],RL4; temp_b
    2A4A D7408000      EXTP      #080H,#01H
    2A4E F3F80400      MOVB      RL4,04H
    2A52 E480D800      MOVB      [R0+#0D8H],RL4; temp_a
    2A56 E108          MOVB      RL4,#00H
    2A58 D7408000      EXTP      #080H,#01H
    2A5C F7F80500      MOVB      05H,RL4
    2A60 E108          MOVB      RL4,#00H
    2A62 D7408000      EXTP      #080H,#01H
    2A66 F7F80400      MOVB      04H,RL4
    2A6A F480D800      MOVB      RL4,[R0+#0D8H]; temp_a
    2A6E 3D0B          JMPR      cc_NZ,?C0237
    2A70 F480DA00      MOVB      RL4,[R0+#0DAH]; temp_b
    2A74 47F8B800      CMPB      RL4,#0B8H
    2A78 3D06          JMPR      cc_NZ,?C0237
    2A7A F480D600      MOVB      RL4,[R0+#0D6H]; CAN_LED_status
    2A7E 07F80A00      ADDB      RL4,#0AH
    2A82 E480D600      MOVB      [R0+#0D6H],RL4; CAN_LED_status
    2A86         ?C0237:
    2A86 F480D600      MOVB      RL4,[R0+#0D6H]; CAN_LED_status
    2A8A D7408000      EXTP      #080H,#01H
    2A8E F7F82E00      MOVB      02EH,RL4
    

    Slightly rusty on my Z80, and 6800 assembler, I'll have a
    stab at working out what's going on. But if this prompts
    any thoughts, then it'd be much appreciated.


    Yours,

    Richard.

  • The only difference I see is that in the 'broken' code one of the variables (CAN_LED_status) is placed in the register R14. All the other veriables are placed on the user stack. Of course, this is perfectly legal and the compiler generated correct code.
    There can be all sorts of reasons why the code fails: changed execution speed, corruption of registers by interrupt service routines we don't know of, plain bug in the code (so it works accidentally sometimes) and so on.

    Best of luck!
    - mike

  • I'll second Mike's observation. There's absolutely no change at all between the three assembly outputs you show, as far as accesses to your CAN registers are concerned. They're those EXTB+MOVB pairs. Which means that whatever the real bug is, it's not in the piece of assembly code you did show. It's elsewhere.

    One thing I'm worried about is that I don't see any remains of the most significan byte actual addresses of your CAN registers (0x20) in any of the quoted code. Now, I don't do 166's at all, but this does feel fishy.

    Are you sure this code succeeds in accessing your CAN hardware registers at all, in the first place?

    You really should trace through this in some simulator or emulator to see what actually happens.

  • One thing I'm worried about is that I don't see any remains of the most significan byte actual addresses of your CAN registers (0x20) in any of the quoted code.

    It's those EXTP instructions. EXTP #080H,#01H is pretty much the same as EXTS #020H,#01H, which wouldn't alarm you. For some reason the C166 compiler prefers EXTP to EXTS, but that doesn't do any harm.

    - mike

  • Hello,
    I would like to ask the next question...
    After I've read this thread I would to ask...
    Here some code listing...

    int idata timer_cnt=0;
    bit global_refresh=0;

    void timer_isr interrupt 1
    {
    timer_cnt++;
    if(timer_cnt==4)
    {
    global_refresh=1;
    timer_cnt=0;
    }
    }

    void main()
    {
    while()
    {
    .......
    global_refresh=0;
    }

    }

    and it works without any error though I've declared the variables as global(without "volatile")???
    May be here there is nothing to optimize?
    Thanks for any input..
    With best wishes,
    S.

  • May be here there is nothing to optimize?

    That, or your usage pattern happens to be a safe one.

    global_refresh should be declared volatile in this case, as it is shared between main() and the ISR. Timer_cnt is not shared, and so need not be volatile.

    Consider that, given this code snippet, it would be legal for a compiler to hoist the assignment out of the while loop. This source

    while ()
        {
        global_refresh = 0;
        ...
        }
    

    is equivalent to

    global_refresh = 0;
    while ()
        {
        ...
        }
    

    if global_refresh is not volatile. But that likely has the wrong results.

    In your case, perhaps you get lucky because your code is actually

    while ()
        {
        ...
        global_refresh = 0;
        }
    

    So, global refresh has some unknown value the first time through the loop (zero, because of the initializer, but that's not obvious from a "peephole" view at this point), then gets set to zero, and then remains zero forever. A compiler could unroll this loop to remove the repeated assignment on the second and later iterations, but it probably wouldn't think it would be worth doing, particularly in the face of an directive to optimize for space. A really clever compiler might figure out the value is zero from initialization, and hoist away.

    If a variable can be changed for any cause not under the immediate control the code generator -- hardware refresh, interrupt routines, shared memory between tasks or processors -- it should be declared volatile. The compiler won't know unless you tell it, especially in the face of seperate compilation. It's a matter of correctness, not just optimization.

  • Hi Drew,

    I try to give it much more precisely...
    here this code snippet...
    ///////////////////////////////////////////
    int idata timer_cnt=0;
    bit global_refresh=0;

    void timer_isr interrupt 1
    {
    timer_cnt++;
    if(timer_cnt==4)
    {
    global_refresh=1;
    timer_cnt=0;
    if(global_refresh) LED=0;//LED is ON
    }
    }

    void main()
    {
    while()
    {
    if(global_refresh)
    {
    .......
    global_refresh=0;
    if(!global_refresh) LED=1;//LED is OFF
    }
    }

    }

    and it's blinking. That's why I sad it works! But WHY does it work without any VOLATILE declaration?

    With best wishes,
    S.

  • A couple of things occur to me here:

    1) I believe that sbit variables are implicitly volatile, although I've had a look in the manual and cannot seem to find anything to back that up. Maybe plain 'bit' variables are also considered volatile?

    2) The compiler would have no difficulty seeing that global_refresh is modified both inside the ISR and in main(). Perhaps it automatically suppresses optimisation in cases like this?

    Stefan

  • A couple of things occur to me here:

    1) I believe that sbit variables are implicitly volatile, although I've had a look in the manual and cannot seem to find anything to back that up. Maybe plain 'bit' variables are also considered volatile?

    2) The compiler would have no difficulty seeing that global_refresh is modified both inside the ISR and in main(). Perhaps it automatically suppresses optimisation in cases like this?


    Optimisation is entirely optional for the compiler.

    In practice, writes to global variables tend not to be optimised out because, in general, it is difficult for the compiler to keep track of where else a variable might be read.

    However, reads of global variables is another matter. In this case SV's code works because it is simply fortunate that the compiler chooses to read the value of global_refresh from memory rather than keeping a copy in a register. With this processor there is probably no benefit in keeping a local copy of a bit, but if global_refresh were to be changed to a char then the code may fail to work as expected. Worse still, the code might work for now, but fail when the compiler is updated!

  • Hi Stefan,
    1. I tried it with
    char global_refresh
    and it works too...
    Regards,
    S.

  • I'm using 8051F124
    compiler keil v7.08
    assembler v7.08a

  • and exactly to say
    I tried
    char xdata global_refresh;

  • just tried without xdata - it works too...

  • S V,

    you're chasing a red herring. What happens in a particular program, using a particular compiler version on a particular platform, if you do not qualify object as volatile which really has to be, misses the point.

    The point is that by not making this variable volatile, you're inviting the compiler to optimize it in whatever way it thinks. This risks the way actually being chosen to be one that breaks your program. It may not happen today, but that says nothing about what may happen next week, after you added some completely unconspicious pieces of code elsewhere in the program, or upgrade your compiler. Your code as given is buggy, and since you obviously know how to fix that bug, there's no valid excuse for leaving it unfixed.

    Not making your variable, which is shared between the interrupt handler and the main code, a volatile is a lie by omission. Let's repeat that: Your code lies to the compiler. And as always, this means you will responsible for whatever the consequences of that lie may be. You're causing undefined behaviour --- and trust me, you don't want to do that.

    Do you really believe it's a good idea to lie to an entity you're supposed to cooperate with to complete the greater project at hand? Lying to your supervisor will get you fired quickly --- lying to the compiler may take a bit longer, but it will ultimately get you fired, too. Don't do it.