Volatile keyword, and optimizations - Page 1
Forum section: electronics / general

author: Dan Moos
author_rank: Frequent Contributor
timestamp: February 28, 2017, 06:48:49 am
content: Ok, I am having some crazy things happen. A couple questions are in order.If a problem persists with compiler optimizations turned off, does that mean it can't be solved with the volatile keyword? My (scant) understanding of volatile is that it is specifically for thwarting the optimizer from making decisions not in your interest. So if I have optimization turned off completely, and my trouble persists, volatile isn't gonna change anything, correct?As an aside, I read somewhere that any globals that are within spitting distance of interrupts should be volatile. Is this true? Is there a downside?

author: obiwanjacobi
author_rank: Super Contributor
timestamp: February 28, 2017, 07:05:07 am
content: 'volatile' tells the compiler that the value may change out-of-bounds of the path of execution. This will ensure that the compiler will not change order on reads and writes and/or optimize using the variable away.The typical scenario is that variables (all - not only globals) that are shared between an ISR and the main code will need the volatile keyword - because the ISR can change the value of that var at any point during the execution of the main code.If a var is only used by an ISR and nowhere else, you don't need volatile. If a var is only used by the main code and not by ISRs, you don't need volatile.

author: ataradov
author_rank: Super Contributor
timestamp: February 28, 2017, 07:23:04 am
content: What compilers understand under 'no optimization' differs greatly from one compiler to another. In general, it will not hurt to have correct code (with volatiles in proper places) before debugging any further.

author: newbrain
author_rank: Super Contributor
timestamp: February 28, 2017, 07:54:05 am
content: Volatile can be used, misused and abused, as can most of C language...I findthisa good read on the topic (though a bit dated), in addition to thestandard, of course.

author: AndyC_772
author_rank: Super Contributor
timestamp: February 28, 2017, 08:53:24 am
content: Quote from: Dan Moos on February 28, 2017, 06:48:49 amIt's always important to understand what's really happening in order to predict and avoid rare situations in which code doesn't quite do what you expected.'Volatile' implies that a variable cannot be relied upon to retain the same value unless it's changed by the C code being executed at the time. Hardware registers, for example, should be 'volatile' because their values might change independently of the code.Suppose your hardware includes a memory mapped status register that indicates whether or not some operation has completed. You might, quite reasonably, include some code along the lines of:Code:do {result = HW_STATUS_REGISTER;} while ((result & STATUS_COMPLETE) == 0);The intent of the code is, of course, to poll the status register until a status bit gets set.But: the compiler could, quite reasonably, determine that the value of 'result'alwaysequals whatever value is in HW_STATUS_REGISTER, and only read it once. Not reading it each time round the loop saves time, and means the loop executes faster. It doesn't particularly matter in this example, but if the loop also included other interesting things for the CPU to be doing whilst waiting for the hardware to complete its operation, then that would be a good thing.Not re-reading the value each time is completely valid if HW_STATUS_REGISTER isn't declared as volatile. If it is declared volatile, then the assumption that its value doesn't change is false, and the compiler will ensure it gets re-read each time round the loop.It's exactly the same if the value of a variable is being changed by an ISR rather than by hardware, hence the rule-of-thumb about declaring variables that are used by an ISR as volatile. (Note, that's variables which are used both insideandoutside the ISR. You don't need to declare as volatile every variable used inside the ISR. Typically they should be local in scope and not externally visible at all anyway).'Volatile' won't get you out of every problem, though. You still need to consider cases like:- read, modify, write cycles can break if the value being modified gets changed by an ISR in between the read and the write.- reads of variables which are wider than the CPU data bus will break if they're changed by an ISR in between different bytes being read. For example, if you're on an 8 bit CPU, and you read a uint32_t, then you might get three bytes of 'old' data and one byte of 'new' data.The general rule is, if an ISR can happen to execute at exactly the wrong time, then it will. Always consider whether you need to disable interrupts around any set of operations which access data generated by an ISR.

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: March 01, 2017, 10:12:41 am
content: Quote"Volatile" is not the solution to a problem. It is simply essential where appropriate.As mentioned, misuse will often become apparent when optimisation is higher, but this is somewhat compiler dependent.The question implies that the OP doesn't really understand what their code is doing, and stabbing at possible solutions instead of methodically tracking down the problem.Any variable that can be used by either foreground or background tasks MUST be declared volatile. You might have got away with not doing it but it is simply wrong not to.The issue of hardware registers is generally taken care of by manufacturers' header files, so you rarely see this.However a related issue, which can cause similarly puzzling and infrequent symptoms is where interrupt and foreground code both access bits within registers in a non-atomic fashion. Interrupt flag bits are a common example.e.g.Timer 1 Interrupt code :<do stuff><clear T1 interrupt flag>Foreground code :If <timer 2 interrupt flag><do stuff><clear T2 interrupt flag>If T1 and T2 int flags are in the same register, and the values are updated by ANDing with a bitmask, what can happen is that the <clear T2 interrupt flag> VERY occasionally does this :Read interrupt flag register into a variable in the cycle before the T1 interrupt happensT1 interrupt happens, intcode clears T1 int flagForeground code clears T2 flag in variable and writes back to flag register.but the variable has the T1 flag set as it was read the cycle before the T1 int happenned,so when it is written back to the int flag register it causes a second spurious T1 interrupt.This can cause those "once in a blue moon" type problems, as there is only a tiny time window where the interrupt can cause problems.Sometimes the fact that the flags share a register can be hidden by the use of convenient macros etc, in the manufacturer's peripheral library.

author: Ian.M
author_rank: Super Contributor
timestamp: March 01, 2017, 11:13:36 am
content: Its possible to update separate bits in a flag register from multiple threads of execution without inherently causing any races conditions provided the processor in question has an atomic Read-Modify-Write XOR instruction that can be applied to the register. The basic strategy is to determine which bits need to be changed then flip them with an XOR. It does not help if two threads are trying to modify the same bit, and risks leaving the bit in the opposite state to that intended by either thread.Seehttp://www.microchip.com/forums/FindPost/518913for a discussion of the method and a link to a C macro that implements it, provided the compiler can be expected to optimise the XOR to the aforementioned atomic Read-Modify-Write XOR instruction.

author: AndyC_772
author_rank: Super Contributor
timestamp: March 01, 2017, 11:31:52 am
content: Quote from: mikeselectricstuff on March 01, 2017, 10:12:41 am...and sometimes, the fact that flags share a register can be obfuscated by the manufacturer's peripheral library, leaving the developer with no obvious clue as to why a design sporadically fails. Personally, I'd much rather know what the underlying register map looks like, so I at least have some chance of knowing what non-obvious precautions I might need to take.Some architectures include write-to-clear registers for exactly this reason. If you read a register, change a bit, then write it back, there's always the chance that the register value will have independently been changed at some point during that sequence. If, however, there's a separate register to which you can write a '1' to cause some other bit to be cleared, then the read-modify-write sequence no longer exists, and nor does the potential problem associated with it.

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: March 01, 2017, 11:40:33 am
content: Quote from: Ian.M on March 01, 2017, 11:13:36 amYes - the problem is there are usually multiple ways to do the same thing, and it isn't always obvious which is the right one.e.g. on PIC32, the compiler device files define bitfields for flag registers, but do not compile them to atomic operationsThere are 3 obvious ways to clear an interrupt flag, all compile to different code and have different pitfalls.IFS0bits.T1IF=0; // compiles to non-atomic read/write (with not even a warning)INTClearFlag(INT_T1); // PLIB subroutine call, a problem if used in a bootloaderIFS0CLR=_IFS0_T1IF_MASK; // uses hardware atomicity - the "correct" way to do it.

author: Ian.M
author_rank: Super Contributor
timestamp: March 01, 2017, 12:21:11 pm
content: Yes, if the hardware supports atomic bit operations, or write to clear, set or toggle registers then using them explicitly is the best choice. What you get out of the compiler for single bit bitfields is a bit of a gamble as it may or may not be smart enough to optimise it to an atomic bit operation as a good assembler programmer would.