Author Topic: volatile variable  (Read 2755 times)

0 Members and 1 Guest are viewing this topic.

Offline BlogRahulTopic starter

  • Regular Contributor
  • *
  • Posts: 75
  • Country: in
volatile variable
« on: December 19, 2021, 12:35:40 pm »
How does the compiler work when I use volatile in my following code? Does the compiler look at both inside and outside the ISR if I don't use it volatile?

Code: [Select]
int x;

void main ()
{
x = 0;

while (1)
    {
if ( x == 1)
{
x++;
}
}
}

ISR ()
{
if(flag == 0)
{
flag = 0;
x = 1;
}
}
 

Offline ledtester

  • Super Contributor
  • ***
  • Posts: 3248
  • Country: us
Re: volatile variable
« Reply #1 on: December 19, 2021, 02:12:40 pm »
I'm not sure what you mean by looking inside and outside the ISR.

The "volatile" qualifier means that the compiler will not perform certain kinds of optimizations when accessing the variable that it normally would. It is a directive to the compiler that the value of the variable can be changed by external factors and so it shouldn't make any assumptions about what that variable contains.

Quick example:

Code: [Select]
    int x;
    int y;
    ...

    x = 3;
    y = x;
    ...

For the assignment to y an optimizing C compiler might simply assign 3 to y. However, if x is marked as volatile it should generate code which actually loads the value stored at x even though the previous statement assigned it to 3. And the compiler should do this regardless of where the access to x is located -- whether it occurs in an ISR or not.
 
Even if you have declared a variable to be volatile you still may need to disable interrupts before accessing it if the value cannot be read atomically. See this page on the use of volatile in the Arduino environment for more details:

https://arduinogetstarted.com/reference/arduino-volatile
« Last Edit: December 19, 2021, 02:20:18 pm by ledtester »
 

Offline magic

  • Super Contributor
  • ***
  • Posts: 7236
  • Country: pl
Re: volatile variable
« Reply #2 on: December 19, 2021, 05:04:16 pm »
Without volatile the compiler will "know" that x is 0 in main and may remove the "unnecessary" test for 1 (and x++) inside the loop.
 
The following users thanked this post: BlogRahul

Offline golden_labels

  • Super Contributor
  • ***
  • Posts: 1370
  • Country: pl
Re: volatile variable
« Reply #3 on: December 19, 2021, 08:04:06 pm »
You haven’t specified the language. I am assuming C. Please be clear about the language you are asking about.
You haven’t specified the platform, without which it’s impossible to answer that question in a concise manner. C has no concept of ISRs or their interaction with other parts of the code, so it’s implementation-specific.
Your code doesn’t even contain volatile. I am assuming that you mean x to be volatile.


On all platforms, the code is invalid. volatile doesn’t promise atomicity of reads/stores, so you can’t simply assign to a variable and expect the other side to always see a valid value. volatile is also not affecting atomicity of multiple logically related operations, so you have race conditions in the code. Both of those prove that on any platform the behaviour of this code is undefined.

Besides the problems mentioned above, on some platforms that code is still invalid: volatile doesn’t make any promises about visibility of changes. While on some simplistic architectures a store is automatically visible to all other threads, on many it is not. volatile does not indicate to the compiler that reads and stores should be implemented in a way that ensures memory consistency.

So while volatile may be necessary, it’s not sufficient. With a stress on word “may”: in many currently used solutions it is no longer really needed(1) and is used “just in case”. What you are looking for is either platform-specific synchronization — using e.g. mutexes and disabling interrupts — or the atomic operations API.(2)
____
(1) With some developers supporting a complete ban on its usage. I do not subscribe to that camp, but you may prefer to be aware how strong the lack of necessity for volatile is.
(2) With a warning that atomics are not fully implemented on some platforms.
People imagine AI as T1000. What we got so far is glorified T9.
 

Offline magic

  • Super Contributor
  • ***
  • Posts: 7236
  • Country: pl
Re: volatile variable
« Reply #4 on: December 20, 2021, 09:07:50 am »
C standard atomics are a big, stinking trap, you really need to be careful with them.

This is AVR code which decreases a volatile char variable by one:
Code: [Select]
00000000 <atomic>:
   0:   80 91 00 00     lds     r24, 0x0000
   4:   8f 5f           subi    r24, 0x01       ; 1
   6:   80 93 00 00     sts     0x0000, r24
   a:   08 95           ret

Now volatile int:
Code: [Select]
00000000 <atomic>:
   0:   80 91 00 00     lds     r24, 0x0000
   4:   90 91 00 00     lds     r25, 0x0000
   8:   01 97           sbiw    r24, 0x01       ; 1
   a:   90 93 00 00     sts     0x0000, r25
   e:   80 93 00 00     sts     0x0000, r24
  12:   08 95           ret

These are not quite correct because they could be interrupted in the middle of the operation, but that can be resolved with cli/sei.
For some simpler code than the above, particularly if dealing with char rather than int, disabling interrupts may not even be necessary.

And now atomic_int:
Code: [Select]
00000000 <atomic>:
   0:   0f 93           push    r16
   2:   1f 93           push    r17
   4:   cf 93           push    r28
   6:   df 93           push    r29
   8:   00 d0           rcall   .+0             ; 0xa <atomic+0xa>
   a:   cd b7           in      r28, 0x3d       ; 61
   c:   de b7           in      r29, 0x3e       ; 62
   e:   65 e0           ldi     r22, 0x05       ; 5
  10:   70 e0           ldi     r23, 0x00       ; 0
  12:   80 e0           ldi     r24, 0x00       ; 0
  14:   90 e0           ldi     r25, 0x00       ; 0
  16:   00 d0           rcall   .+0             ; 0x18 <atomic+0x18>
  18:   9a 83           std     Y+2, r25        ; 0x02
  1a:   89 83           std     Y+1, r24        ; 0x01
  1c:   00 c0           rjmp    .+0             ; 0x1e <atomic+0x1e>
  1e:   89 81           ldd     r24, Y+1        ; 0x01
  20:   9a 81           ldd     r25, Y+2        ; 0x02
  22:   48 2f           mov     r20, r24
  24:   59 2f           mov     r21, r25
  26:   41 50           subi    r20, 0x01       ; 1
  28:   51 09           sbc     r21, r1
  2a:   05 e0           ldi     r16, 0x05       ; 5
  2c:   10 e0           ldi     r17, 0x00       ; 0
  2e:   25 e0           ldi     r18, 0x05       ; 5
  30:   30 e0           ldi     r19, 0x00       ; 0
  32:   6c 2f           mov     r22, r28
  34:   7d 2f           mov     r23, r29
  36:   6f 5f           subi    r22, 0xFF       ; 255
  38:   7f 4f           sbci    r23, 0xFF       ; 255
  3a:   80 e0           ldi     r24, 0x00       ; 0
  3c:   90 e0           ldi     r25, 0x00       ; 0
  3e:   00 d0           rcall   .+0             ; 0x40 <atomic+0x40>
  40:   88 23           and     r24, r24
  42:   01 f0           breq    .+0             ; 0x44 <atomic+0x44>
  44:   0f 90           pop     r0
  46:   0f 90           pop     r0
  48:   df 91           pop     r29
  4a:   cf 91           pop     r28
  4c:   1f 91           pop     r17
  4e:   0f 91           pop     r16
  50:   08 95           ret
:scared:

You need to test for the ATOMIC_xxx_LOCK_FREE macro and stay the hell away from types for which it's not defined.
« Last Edit: December 20, 2021, 09:36:05 am by magic »
 

Offline BlogRahulTopic starter

  • Regular Contributor
  • *
  • Posts: 75
  • Country: in
Re: volatile variable
« Reply #5 on: December 20, 2021, 09:40:13 am »
Without volatile, the compiler will "know" that x is 0 in main and may remove the "unnecessary" test for 1 (and x++) inside the loop.

Okay It means without volatile, the compiler doesn't look at the value of variable x inside ISR

With volatile, the compiler looks at the value of variable x in main and inside ISR


Code: [Select]
volatile int x;

void main ()
{
x = 0;

while (1)
    {
if ( x == 1)
{
x++;
}
}
}

ISR ()
{
if(flag == 0)
{
flag = 0;
x = 1;
}
}
 

Offline Ian.M

  • Super Contributor
  • ***
  • Posts: 13130
Re: volatile variable
« Reply #6 on: December 20, 2021, 09:55:40 am »
Errr.... NOPE. 

Remember, C compilers compile each source file individually*.

While compiling main() the compiler doesn't 'look at' the ISR which may even be in a separate compilation unit (source file), which would make it impossible for the compiler to 'look at' it, as it has no way to find the source file containing the ISR.

All volatile does is tell the compiler *NOT* to optimize away any reads or writes to the variable that appear to have no lasting effect, and not to cache its value in scratchpad RAM or a register, and not to make any code flow optimizations that depend on assuming the variable's state.

* Apart from some non-ANSI/ISO standard compliant ones that treat all source files in a project as a single compilation unit.  Such are best regarded as pseudo-C compilers!
 

Offline magic

  • Super Contributor
  • ***
  • Posts: 7236
  • Country: pl
Re: volatile variable
« Reply #7 on: December 20, 2021, 10:06:05 am »
It compiles each function individually, even in a single file. Within each function, if you set a variable to some value, it will assume that the variable stays that way and it will not check for external factors (hardware peripherals or ISRs) changing the variable behind the function's back.

In your example:

main: x is set to zero, which means it obviously cannot be one and the code inside the loop may be removed
ISR: the conditional is only entered when flag is zero, which means that setting the flag to zero may be omitted
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 15413
  • Country: fr
Re: volatile variable
« Reply #8 on: December 20, 2021, 05:57:25 pm »
Without volatile, the compiler will "know" that x is 0 in main and may remove the "unnecessary" test for 1 (and x++) inside the loop.

Okay It means without volatile, the compiler doesn't look at the value of variable x inside ISR

With volatile, the compiler looks at the value of variable x in main and inside ISR

Nope as said already. It's actually much simpler than this, and sort of the opposite. With volatile, the compiler doesn't try to analyze the usage of the corresponding variable, and will generate an access to it in any case.

The reason your example code might optimize out part of the code in main() relative to 'x' is the following:
- Purely from the main() perspective, 'x' is always 0. So it can optimize out anything relative to 'x'.
- Yes, the ISR() function can modify 'x' oustide of main(), but the compiler has no means of knowing ISR() will ever get called. It can't analyze the call chain. That's something pretty typical when defining ISRs or thread functions.

This is a typical setup that will require the use of the 'volatile' qualifier. But I admit this is something that can be confusing with C.
Remember that C will almost always favor efficiency over "correctness", and that some additions (such as the volatile qualifier) are there to manually instruct the compiler when the intent of the programmer can't be clearly conveyed without them. Just the way it works. If you want a language that favors correctness over efficiency in all cases, pick another one. :)
 

Offline golden_labels

  • Super Contributor
  • ***
  • Posts: 1370
  • Country: pl
Re: volatile variable
« Reply #9 on: December 21, 2021, 03:23:02 am »
Okay It means without volatile, the compiler doesn't look at the value of variable x inside ISR
No. It means the compiler assumes the normal meaning of the code, as interpreted from the perspective of that code. In the following fragment, is x ever modified, if it’s not 1?
Code: [Select]
while (1) {
    if (1 == x) {
        ++x;
    }
}
Clearly not, there is no single operation that would modify anything. The code is exactly equivalent to:
Code: [Select]
if (1 == x) {
    x = 2;
}
while (1) {
}
That’s what the compiler sees without x being marked as volatile.

A side note: since there is an infinite loop in this code, the actual meaning of the code is undefined and the compiler may do anything it wishes: including never even generating any loop or simply “make demons fly out of your nose.”

With volatile, the compiler looks at the value of variable x in main and inside ISR
No. It means that the compiler knows the snippets of code shown above are not equivalent, because x may be “magically” modified despite it seems it can not. So it must assume that every read from x may produce a different value, even if it never sees any stores into x in this fragment.

Nope as said already. It's actually much simpler than this, and sort of the opposite. With volatile, the compiler doesn't try to analyze the usage of the corresponding variable, and will generate an access to it in any case.
That is not exactly true. It’s a bit nitpicky, but in the case of volatile such details are important and missing them is what brings pain.

With volatile the compiler assumes that any read from the variable may produce a different value, despite no modification to that variable can be seen. But volatile does not guarantee by itself generation of an access to the actual storage.

The difference is slight, but not understanding it has grave consequences. The presence of volatile tells the compiler that some natural assumptions about the meaning of code can’t be made, which leads to different interpretation of the described logic and in consequence missing some optimizations. But “generate an access” is a much stronger requirement. On simple platforms like small microcontrollers merely skipping an optimization does accidentally generate an access, but that is never true on modern, more complex architectures. Which include even any modern consumer CPU. With multilayer caches, complex CPU-level optimization, and multiple cores or processors it is required to use the right synchronization methods to ensure memory consistency. That includes explicit memory barriers, instructions offering such guarantees as side effects etc. volatile never introduces those. And it is usually not even needed if they are used. For example on any platform supported by pthreads using synchronization primitives alone induces the desired behavior. So is using atomics API of C.

And that’s only about reading a variable! I’m not even touching things like the order of execution. :D

Also note that volatile is not the only situation that prevents the compiler from making assumptions about reads. char pointers behaviour in the context of strict aliasing is another such example.

Remember that C will almost always favor efficiency over "correctness", and that some additions (such as the volatile qualifier) are there to manually instruct the compiler when the intent of the programmer can't be clearly conveyed without them. Just the way it works. If you want a language that favors correctness over efficiency in all cases, pick another one. :)
In this case correctness is not sacrificed at all. The behaviour without volatile is perfectly correct. The addition of volatile is not making that code more correct: it changes its semantics. Do not confuse “correct” with “what a programmer had on their mind”. While C is known to sacrifice safety and clarity for some types of performance gains, this is not the case here.
« Last Edit: December 21, 2021, 03:28:50 am by golden_labels »
People imagine AI as T1000. What we got so far is glorified T9.
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 15413
  • Country: fr
Re: volatile variable
« Reply #10 on: December 21, 2021, 06:40:15 pm »
I knew this was going to be fun...

Remember that C will almost always favor efficiency over "correctness", and that some additions (such as the volatile qualifier) are there to manually instruct the compiler when the intent of the programmer can't be clearly conveyed without them. Just the way it works. If you want a language that favors correctness over efficiency in all cases, pick another one. :)
In this case correctness is not sacrificed at all. The behaviour without volatile is perfectly correct.

You obviously missed the point here, and are using circular logic. Yes it is correct as far as C is concerned. No doubt about it. But that's the whole point. C's take on this *does* favor efficiency over correctness. Now we'll probably have to define "correctness" in programming. It will take a whole book. Or more like several. But I'm strictly speaking about correctness in *translating the intent of the programmer* here. That's all a programming language is supposed to do. It's not supposed to design software for you.

Do not confuse “correct” with “what a programmer had on their mind”.

This is exactly how I define correctness indeed, as far as the programming language and tools are concerned. The language can't second-guess what the programmer really wants to do  - its role is ONLY to make them able to express that in a clear and non-ambiguous way. Of course correctness is EXACTLY what the programmer had on their mind, as far as a programming language is concerned. So ideally, a programming language should strive to make this as easy and unambiguous as possible, and with the smallest number of funky rules possible. As much as I like C (and use it almost exclusively when it comes to compiled languages), it's definitely not very good at that.

There are quite a few examples of that in C, but to take this very example here, this is definitely a choice in C that is, at best, questionable. It makes it possible for a compiler to optimize out access to an object *without knowing if it really can do that safely*, and puts the burden of the proof, so to speak, on the programmer's shoulders. That's a typical choice. A much safer choice would be to make it impossible *unless it can be proven safe after full analysis of the source code, even across compilation units*, which is something C definitely does not mandate. In that, it clearly chose not to favor correctness. Hope the point is clear.
« Last Edit: December 21, 2021, 06:45:28 pm by SiliconWizard »
 

Offline magic

  • Super Contributor
  • ***
  • Posts: 7236
  • Country: pl
Re: volatile variable
« Reply #11 on: December 21, 2021, 11:34:01 pm »
That would preclude optimization of functions like qsort() unless you can prove that they will never be called on an array which something else is monkeying with. If you could prove that, you could also statically prove absence of a lot of bugs.
And what about compiling a library for other programs to use? Then you can't prove anything.

And that's just the single core case. On multicore if you want to enforce full memory consistency you may as well just give up and run one thread :P

Another fun example:
You are compiling some big project like an OS kernel.
You notice it's doing some very shady stuff, downloading data from untrusted networks, executing code from disk :scared:
Not quite sure if the kernel is entirely secure and if a rogue program couldn't overwrite some kernel data structure.
Better safe than sorry - no optimizations for you, dirty kernel :P
All exploits devised by hackers must work intuitively and predictably :-+
« Last Edit: December 21, 2021, 11:47:27 pm by magic »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf