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.

Parents Reply Children
  • Cheers Hans, a fair point.

    I guess what I should be saying is, I'm assuming the compiler automatically treat a variable as volatile if it sees that the contents of an address 'labelled' as volatile are being read into the variable.

    Is this a reasonable assumption?

    Yours,

    Richard.

  • Is this a reasonable assumption?

    No it is not. To do this the compiler would have to be very clever indeed as sometimes the variable would contain data from a volatile source and sometimes not. Anyway, what would be the point, once a value has been read from a volatile source and placed in a variable, I cannot see any reason why the compiler should not do whatever optimisation it pleases.

  • Hi there Graham,

    I see what you're getting at.

    I'm considering, for example, the case where perhaps you read from a volatile address into a variable. Then manipulate the variable, then maybe output to a DAC.

    From what's been said here, the variable must also be defined as volatile, otherwise the optimised code would always read from the address (as a volatile is envolved in the procedure), but that the repeat manipulations of the variable could be optimised out so that they only happened once, with only one value possibly being written to the DAC.


    To do this the compiler would have to be very clever indeed...


    ...which in some case is not possible, hence the reason for the inclusion of volatile in the C language I guess.

    Anyway, cheers for the help.

    Yours,

    Richard.

  • Hi Richard,

    I followed the discussion, and I got a feeling that perhaps you are mistaken in your assumptions. I can't exactly point out where though, since I'm not sure what you mean, but I can try.

    I'm considering, for example, the case where perhaps you read from a volatile address into a variable. Then manipulate the variable, then maybe output to a DAC.

    From what's been said here, the variable must also be defined as volatile, otherwise the optimised code would always read from the address (as a volatile is envolved in the procedure), but that the repeat manipulations of the variable could be optimised out so that they only happened once, with only one value possibly being written to the DAC.


    A small code snippet in C would explain a lot better, I think. Example:

    extern int volatile ADC;
    int i;
    for (;;)
    {
        i = ADC;
        i *= 2;
        write_to_dac(i);
    }
    You are suggesting that the compiler might optimize it into something like this:
    extern int volatile ADC;
    int i;
    i = ADC;
    i *= 2;
    for (;;)
    {
        write_to_dac(i);
    }
    Well, it's not going to happen because ADC is volatile. That's the meaning of valitile: when a volatile variable is being read, the compiler will not use a cached value from a previous read, it will read from the actual memory location of the variable.

    - mike

  • Hi Mike,

    Cheers for this.

    Taking your example code...

    extern int volatile ADC;
    int i;
    for (;;)
    {
        i = ADC;
        i *= 2;
        write_to_dac(i);
    }
    

    I'm happy that the line...

    i = ADC;
    

    ...should should not be effected by the compilers optimiser, because as you say ADC is defined as volatile, and that it the meaning of volatile. My question is what will happen to the lines...

    i *= 2;
    write_to_dac(i);
    

    ...because i isn't defined as volatile?

    You suggest that it should be untouched by the optimiser. Reading between the lines Hans seems to be suggesting I need to define i as volatile, because the compielr won't assume that it is. Am I misinterpretting whats being said here?


    Yours,

    Rich.

  • My question is what will happen to the lines...

    	i *= 2;
            write_to_dac(i);

    ...because i isn't defined as volatile?

    Impossible to say. Whatever the compiler decides is best, but remains the meaning of the code, will happen.

    You suggest that it should be untouched by the optimiser.

    No. Nobody was suggesting that. It's the exact opposite: it may be manipulated by the optimizer, exactly because there's no volatile qualifier stopping it from doing that. But even so, the optimizer is not allowed to just break your code --- volatile qualifiers are needed only in those cases where the optimizer might break code because it doesn't know all there is to know about a variable. That's the case whenever a variable's value can change, but there's no line of C code in the main execution path responsible for it that the compiler would know about being responsible for that.

    You somehow seem to believe that there's no middle ground between constant and volatile. But there is. Lots of it, actually, and it's what all ordinary variables do behave like.

  • Well, I can't really see what you mean. Give us a piece of code in C with a real-world example of whatever undesirable optimizations you think the compiler could apply, and we can discuss that. Because in my example I can't see what optimization with side effects the compiler could apply to the i variable, apart from replacing maltiplication with shift, but that has nothing to do with volatile, of course.

    - mike

  • Hi Hans,


    You suggest that it should be untouched by the optimiser.

    No. Nobody was suggesting that.


    In Mike's message is says...


    You are suggesting that the compiler might optimize it...Well, it's not going to happen because ADC is volatile.


    ...which I took to mean that Mike was suggesting it would be untouched by the optimiser.


    You somehow seem to believe that there's no middle ground between constant and volatile... - ordinary variables.


    I am happy with the fact that variable may, well vary (for want of a better word). I am also happy with the opimiser optimising algorithms where all the numbers start off within the code.

    I am less happy (in the knowledge sense) or what happens to algorithms which manipulate variables that have been loaded with numbers from external sources such as ADC's and CAN chips.

    From what I can make out...

    1) We assign the volatile qualifier to anything coming from an external source.

    2) The compiler will not assume a variable is volatile unless you explicitly define it as such.

    ...so given this piece of code...

    extern int volatile ADC;
    int i;
    for (;;)
    {
        i = ADC;
        i *= 2;
        write_to_dac(i);
    }
    

    ...you would expect the part of the code...

    for (;;)
       {
       i = ADC;
       }
    

    ...to be protected from optimisation, but that the compiler would treat the lines containing non volatile variables...

    
    for (;;)
       {
       i *=2;
       write_to_DAC(i);
       }
    
    

    ...as though it could caculate the final result of i, and just write it to the DAC once, the value of i being whatever the compiler thinks the answer of 0^inf is.
    by the compiler in the case.

    I'm assuming that this is not the case, and am looking for some kind of formal statement of whatever rules 3 onwards are for how we should treat variables which interact with volatiles.

    Does that make my problem/confusion any clearer?


    Yours,


    Rich.

  • You've broken your original code into pieces in a way the optimizer is already forbidden to do it, regardless of any usages of the "volatile" keyword.

    The two parts of the code that really exist are:

    	i = ADC
    and
    	i += 2;
    	write_do_DAC(i)

    The loop around this all has nothing to do with the effect of volatile, because there's one volatile variable (ADC) being used inside the loop.

    The optimizer is already forbidden to remove the call to write_to_DAC() by the simple fact that it's a function call. No volatile needed to explain that.

    The optimizer is forbidden to assume it can pre-compute the value of i to be passed to write_to_DAC because it's computed using a non-local, non-constant-qualified variable: ADC. It doesn't matter at all that ADC is qualified volatile, for that.

    In short: I think you're worrying too much. You really only need "volatile" to tell the compiler "Beware! There's stuff going on with this variable that you don't understand."

  • The meaning of volatile is NOT (do not optimize). The meaning of volatile is (ALWAYS read this variable from its storage location -- do not use a compiler-generated temporary copy - even one stored in a register).

    The following examples may help to demonstrate this.

    The following while loop (using a volatile ADC)...

    extern int volatile ADC;
    .
    .
    .
    while (ADC < 0x0800);

    does the following.

    1. Reads ADC into a register.
    2. Compares the register with 0x800.
    3. If the comparison is less, go to step 1.


    The following while loop (using a normal, non-volatile ADC)...

    extern int ADC;
    .
    .
    .
    while (ADC < 0x0800);

    does the following.

    1. Reads ADC into a register.
    2. Compares the register with 0x800.
    3. If the comparison is less, go to step 2.

    This is the basic difference between a volatile and a non-volatile variable.

    Probably the best way to KNOW if you should use volatile is to ask youself:

    What is the scope of this variable -- does it get changed anywhere but here by either an interrupt, hardware, or some other external source (like an A/D converter)?

    The answer is YES for a memory-mapped A/D converter, so it should be VOLATILE.

    If you read the value of the A/D into a local variable, there is probably no good reason to make that variable volatile since it can't be changed outside the scope of that function. Once the variable contains the value of the A/D, you can scale or manipulate it any way you see fit. And, you WANT the compiler optimizer to help you with that.

    Jon

  • Volatility is not contagious. A variable not explicitly declared as volatile will be treated however the optimizer treats it, regardless of where its value came from.

    (This is a good thing, as it allows you to sample a volatile register into a variable, and then carry that one sample around without having to worry that it will be re-read.)

  • In Mike's message is says...

    You are suggesting that the compiler might optimize it...Well, it's not going to happen because ADC is volatile.

    ...which I took to mean that Mike was suggesting it would be untouched by the optimiser.


    Wrong. Words taken out of context.
    I still suggest that you come up with a real-world example of C code and possibly undesirable optimizations by the compiler on that code. Then there would be a basis for discussion. If it's not possible to think of such an example, then the problem does not exist.

    Regards,
    - mike

  • Hi there Mike,

    Cheers for your patience.

    Stripping out all the unnecesary stuff, the code which I am working with is as shown below.

    Note that it is for testing for successful transmission and reception of CAN messages. The aim being to pass one of four suitable values to a set of lights on the CAN card to set them green or red to say if CAN messages are being successfully sent of received...

    //define the addresses on the CAN chip containing the send and receive flags
    #define TransmitA  (*((volatile unsigned char far*)0x200008))
    #define TransmitB  (*((volatile unsigned char far*)0x200009))
    #define ReceiveA   (*((volatile unsigned char far*)0x200004))
    #define ReceiveB   (*((volatile unsigned char far*)0x200005))
    
    // define the address which governs the CAN card LED's and the values which could be assigned to it.
    #define CAN_board_LEDs  (*((volatile unsigned char far*)0x20002E))
    
    #define Transmit_OK 0x05
    #define Receive_OK  0x0A
    #define RESET       0x00
    
    //define the variables
    unsigned char CAN_LED_status;
    unsigned char temp_a;
    unsigned char temp_b;
    unsigned char test;
    
    //piece of code located in the main function
    Start_timing();
    TransmitB=0x47;
    TransmitA=0xff;
    ReceiveB=0;
    ReceiveA=0;
    Timing_pause(); \\waits for end of 100Hz cycle time to be flagged
    
    test=0x00;
    while (test!=0xb8)//test for at least one CAN message being received
       {
       Read_signals();//fills CAN message registers with fresh data from ADC's
       temp_b=TransmitB;
       temp_a=TransmitA;
       TransmitB=0x47;
       TransmitA=0xff;
       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;
       test=temp_b;
       Watchdog();
       Timing_pause();
       }
    

    ...by applying tests to the system (like flashing LED's using lines from the C165 processor) I am sure that I am stuck in this loop. (Note that I disconnect from the CANbus so both transmissions and receives will fail).

    The problem is that if I define all four of my variables as given, then I the code performs the lines...

    CAN_LED_status+=Transmit_OK;
    CAN_LED_status+=Receive_OK;
    

    ...and both my LED's go green (suggesting successful transmission and reception) BUT stays in the loop indicating not one CAN message received(which seems to contradict the logic)!!!

    If define all four of my variables as volatile, then the code correctly doesn't perform the two lines...

    CAN_LED_status+=Transmit_OK;
    CAN_LED_status+=Receive_OK;
    

    ...my LED's stay red, and I stay in the loop, which is all corrrect.

    BUT indicating the unpredictability of the situation, if I add the lines...

    while(1)
       {
       Watchdog();
       }
    

    ...after the above code, and don't define my variables as volatile, then the code works again.

    This is my problem. I have carefully checked the symptoms carefully and feel that I have ruled out anything other than the optimisation of the code with the variables which have been loaded with values from the volatile addresses, which is why I am asking the list.

    Any further thoughts on this would be appreciated.


    Yours,


    Richard.

  • PS I ought to add about this piece of code, that if the loop to detect if any CAN messages are received is repeated further down the piece of code. If these further repeats are removed (by for example the use of the lines...

    while(1)
       {
       Watchdog();
       }
    

    ...before the repeat (which the compilers optimiser should hopefully then regard as dead code and ignore it) or by just deleting it, then the problem goes away.

    To my mind this suggests that perhaps when the optimiser is picking up on repeat code, it is only placing it once in the final code, but making two calls to it, and that it is somewhere in this attempt to optimise that the difference in interpretation of my code is occuring.

    Yours,

    Rich.

  • 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