The reverse volatile pitfall !

Go To Last Post
25 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I observed, that the volatile pitfall not only exist on reading a variable in main from an interrupt.

Also the writing inside main was rejected:

#include 

uint8_t val;

ISR( INT0_vect )
{
  TCNT0 = val;                  // read rubbish
}


int main( void )
{
  for(;;){
    val = ADCH;                 // rejected !!!
  }
}

AVR-GCC 4.3.3,
-Os

So "val" must be defined or casted as volatile :!:

Peter

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What is your understanding of

Quote:
// rejected !!!
?

Einstein was right: "Two things are unlimited: the universe and the human stupidity. But i'm not quite sure about the former..."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

For sure in your example above, val must be declared volatile. That's, what volatile is intended to do.

/Martin.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

DO1THL wrote:
What is your understanding of
Quote:
// rejected !!!
?

"verworfen"
No code for "val" was generated.
Only the access to "ADCH" was made.

Peter

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Why should the optimizer assume, that val is being changed outside the ISR without being declared as/casted to volatile?
The compiler does simply not know, when your ISR is being called, neither where the program will continue after leaving the ISR.

/Martin.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

danni wrote:
Also the writing inside main was rejected:

#include 

uint8_t val;

ISR( INT0_vect )
{
  TCNT0 = val;                  // read rubbish
}


int main( void )
{
  for(;;){
    val = ADCH;                 // rejected !!!
  }
}

AVR-GCC 4.3.3,
-Os

So "val" must be defined or casted as volatile :!:

My guess is that "TCNC = val;" was done as desired.
In main, there seems no need to assign val.
'Tain't used in main and there seems no mechanism to go elsewhere.
My guess is that if you added a reachable function call
to an external function, val would be assigned in main.

That said, use volatile anyway.

It might be worth noting that val is only volatile in main.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

It might be worth noting that val is only volatile in main.

??? OK, I'll bite--what does that mean?

Quote:

ISR( INT0_vect )
{
TCNT0 = val; // read rubbish
}

TCNT0 is volatile by definition, right? So the assignment will always be done, right? And it wouldn't assign an arbitrary value, so I'd guess "val" would be used.

Or are you talking of this scenario:

ISR( INT0_vect )
{
  TCNT0 = val;                  // read rubbish
... do some simple stuff, so that "val" stays in a register
  TCNT0 = val;                  // another assignment

} 

If "val" is volatile, then it should be read again--at least that would be my understanding.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

danni wrote:
I observed, that the volatile pitfall not only exist on reading a variable in main from an interrupt.

Also the writing inside main was rejected:

#include 

uint8_t val;

ISR( INT0_vect )
{
  TCNT0 = val;                  // read rubbish
}


int main( void )
{
  for(;;){
    val = ADCH;                 // rejected !!!
  }
}

AVR-GCC 4.3.3,
-Os

So "val" must be defined or casted as volatile :!:

Peter

volatile works both ways [reads & writes thus main->ISR and ISR->main] in this case the compiler sees that "val" is being assigned, but never used, so it simply discards the storing of the result to memory in the optimization pass. It performs the read of ADCH because it is volatile, so the access must be generated, even though it thinks the result is not needed.

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I would only inform, that if your program works not as expected, you must check both directions of data transfer from interrupt to main and also from main to interrupt, that they marked as volatile.

Instead volatile you can also use atomic access or a memory barrier.

Peter

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

To be more concise, volatile is to be used [or memory barriers/atomic access routines] whenever you are comunicating between two [or more] threads of execution with a shared object, or when dealing with hardware, where the contents of the memory location can change externally, or that accesses have side effects like advancing an internal pointer for a FIFO.

Volatile essentially tells the compiler not to cache the contents of the memory location into a local temporary copy. As a result the compiler generates accesses to the memory location each time it is referencedd, as those accesses may have side effects beyond the scope of the compiler. Thus whenever the memory location is accessed for a read, or write, that access must be generated, even if the result is to be ultimately discarded, or that the source has not changed from the last write.

It is important to understand that volatile applies to the memory location, and not the value itself. Thus when you read from a volatile object, that does not guarantee that the result will be written to the destination objects memory location. [as demonstrated above]. Conversely when you write to a volatile object, it does not guarantee that a read from the source objects memory location will be made, only that the write will be performed with whatever data is held in the local copy. [meaning it could be stale data]

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:
Quote:

It might be worth noting that val is only volatile in main.

??? OK, I'll bite--what does that mean?
In main, assignments to val matter,
although from its code, one could not tell.
val should be declared volatile for main.
In the ISR, interrupts are disabled.
Superfluous reads of val would only take extra time.
The value read would be the same.
Likewise, if the ISR made assignments to val,
only the last one would matter.
The ISR does not need val declared volatile.

That said, some things, such as PIN registers,
need to be volatile in ISRs.
PORT and DDR registers do not.

Quote:
Quote:

ISR( INT0_vect )
{
TCNT0 = val; // read rubbish
}

TCNT0 is volatile by definition, right? So the assignment will always be done, right? And it wouldn't assign an arbitrary value, so I'd guess "val" would be used.

Or are you talking of this scenario:

ISR( INT0_vect )
{
  TCNT0 = val;                  // read rubbish
... do some simple stuff, so that "val" stays in a register
  TCNT0 = val;                  // another assignment

} 

If "val" is volatile, then it should be read again--at least that would be my understanding.

Not necessarily.
It depends on how hard one wants to work the as-if rule.
If val is an ordinary user variable that is declared volatile
only because of one or more ISRs and if the tool chain can tell,
then within an ISR val could be treated as non-volatile.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
That said, some things, such as PIN registers,
need to be volatile in ISRs.
PORT and DDR registers do not.
All AVR registers are declared volatile, so this is a moot point.
Quote:
and if the tool chain can tell
How would the tool chain be able to "tell"? The point of volatile is for the programmer to "tell" the compiler, not the other way around.

Regards,
Steve A.

The Board helps those that help themselves.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

there would be some added performance for ISR's if volatile could be made to exist on one side of the usage only, however this is not possible, so the added penalty of additional accesses is a necessary evil. If you really want to optimize them out, create your own intermediary local copy.

ISR( INT0_vect )
{
  uint8_t tval;

  tval = val; // cache it

... do whatever necessary with tval

  TCNT0 = tval;

  val = tval; // write it back

} 

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

If val is an ordinary user variable that is declared volatile
only because of one or more ISRs and if the tool chain can tell,
then within an ISR val could be treated as non-volatile.

Huh?

"because of one or more ISRs.." -- huh?

Wouldn't it be perfectly valid to unroll a busy check loop, or UDRE check loop?

volatile unsigned char val;
...
val = SPSR;
if (val & (1 << SPIF)) return;
val = SPSR;
if (val & (1 << SPIF)) return;
val = SPSR;
if (val & (1 << SPIF)) return;
...

Quote:

In the ISR, interrupts are disabled.
Superfluous reads of val would only take extra time.
The value read would be the same.

Not necessarily. I wouldn't normally use the sequence I had above, but SPI speeds are fast, and one can cycle-count...or expect only one or two reps. Perfectly valid coding, as far as I can see. Now, you and I may not do it that way, but it [the compiler] better not throw away any of the fragment above. (I don't even think it can fold the operations, and just SBIS -- the mainline may use the "val" value.)

Lee

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Koshchi wrote:
Quote:
That said, some things, such as PIN registers,
need to be volatile in ISRs.
PORT and DDR registers do not.
All AVR registers are declared volatile, so this is a moot point.
I was referring to the underlying reality, not the declarations.
Quote:
Quote:
and if the tool chain can tell
How would the tool chain be able to "tell"? The point of volatile is for the programmer to "tell" the compiler, not the other way around.
In the case of AVRs, I think testing for an address above 0xFF would do the trick.

@Lee: None of your reads of val are superfluous.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
I think testing for an address above 0xFF would do the trick.
How would the address of a variable make any difference as to whether or not it is volatile?
Quote:
I was referring to the underlying reality
The underlying reality is that PORT and DDR registers can be changed in other threads just as easily as any other variable.

Regards,
Steve A.

The Board helps those that help themselves.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Koshchi wrote:
Quote:
I think testing for an address above 0xFF would do the trick.
How would the address of a variable make any difference as to whether or not it is volatile?

The hardware registers all have addresses in the range 0..0xFF.
Most can be changed behind the code's back.
Such variables are volatile even to ISRs.
Variables with addresses above 0xFF are in normal SRAM.
They will not change behind the back of
an ISR running with interrupts disabled.
Quote:
Quote:
I was referring to the underlying reality
The underlying reality is that PORT and DDR registers can be changed in other threads just as easily as any other variable.
An ISR running with interrupts disabled doesn't have to worry about other threads.
The other threads have to worry about the ISR.

Note that though we are not arguing in a vacuum,
the air is pretty thin.
Skinny ISRs often don't do enough to make volatile matter.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Skinny ISRs often don't do enough to make volatile matter.


??? Look at the opening post--that ISR is about as skinny as it gets, and you simply need the "volatile" to pass val as a "parameter" to the ISR.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:
Quote:

Skinny ISRs often don't do enough to make volatile matter.


??? Look at the opening post--that ISR is about as skinny as it gets, and you simply need the "volatile" to pass val as a "parameter" to the ISR.
main needs to know val is volatile.
The ISR dosn't.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Agree.
There are no things to optimisation in so small ISR.
With memory barrier (for main()) and without volatile:

#include 

uint8_t val;

ISR( INT0_vect )
{
  TCNT0 = val;                  // read rubbish
}

int main( void )
{
  for(;;){
    __asm__ ( "" ::: "memory");
    val = ADCH;
  }
}
.global	__vector_1
	.type	__vector_1, @function
__vector_1:
	push __zero_reg__
	push r0
	in r0,__SREG__
	push r0
	clr __zero_reg__
	push r24
/* prologue: Signal */
/* frame size = 0 */
	lds r24,val
	out 82-32,r24
/* epilogue start */
	pop r24
	pop r0
	out __SREG__,r0
	pop r0
	pop __zero_reg__
	reti
	.size	__vector_1, .-__vector_1

.global	main
	.type	main, @function
main:
.L4:
	in r24,37-32
	sts val,r24
	rjmp .L4

wbr, ReAl

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

skeeve wrote:
main needs to know val is volatile.
The ISR dosn't.

Exact.

The interrupt must always read a value on the start and write a value on the end.

So only the main loop need volatile, atomic or memory barrier.

A further solution was also, the main calls a function to access this variable and this function was forbidden to be inlined.

Peter

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

danni wrote:
skeeve wrote:
main needs to know val is volatile.
The ISR dosn't.

Exact.

The interrupt must always read a value on the start and write a value on the end.

So only the main loop need volatile, atomic or memory barrier.

A further solution was also, the main calls a function to access this variable and this function was forbidden to be inlined.

Peter


Indeed, main could call any non-inline function, provided the other function is located in a different compilation unit (such as an avr-libc library call -- NOT a macro), and the memory barrier effect would be achieved, forcing main to refresh the contents of all global variables, volatile or otherwise.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
Variables with addresses above 0xFF are in normal SRAM.
Unless they are not, which is something that is possible in several AVRs.
Quote:
an ISR running with interrupts disabled.
Which is something that the compiler has absolutely no knowledge of. To the compiler, an ISR is simply a function.
Quote:
The interrupt must always read a value on the start and write a value on the end.
Unless the variable needs to be read or written in the middle and interrupts are enabled (something that the compiler doesn't know anything about).

Regards,
Steve A.

The Board helps those that help themselves.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Koshchi wrote:
Quote:
Variables with addresses above 0xFF are in normal SRAM.
Unless they are not, which is something that is possible in several AVRs.
So pick the limit that goes with the AVR.
Isn't that what avr/io.h is for?
Quote:
Quote:
an ISR running with interrupts disabled.
Which is something that the compiler has absolutely no knowledge of. To the compiler, an ISR is simply a function.
Good point.
The compiler wouldn't know, but the linker could.
The compiler could provide a list of instructions to be
relaxed away if the ISR were unreachable from .init0 .

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

skeeve wrote:
Koshchi wrote:
Quote:
Variables with addresses above 0xFF are in normal SRAM.
Unless they are not, which is something that is possible in several AVRs.
So pick the limit that goes with the AVR.
Isn't that what avr/io.h is for?
Quote:
Quote:
an ISR running with interrupts disabled.
Which is something that the compiler has absolutely no knowledge of. To the compiler, an ISR is simply a function.
Good point.
The compiler wouldn't know, but the linker could.
The compiler could provide a list of instructions to be
relaxed away if the ISR were unreachable from .init0 .
Edit: Actually, that is the wrong criterion.
The correct criterion is a lot more complex
and could use help from the compiler.
It involves reachability from places interrupts might be enabled.
I doubt it would be worth the effort.
That amount of effort would be better spent
on multiplication or register-saving.

Iluvatar is the better part of Valar.