3

For avr 8 bit micro controller a single bit ( flag ) must be set or cleared in some 8 bit integer variable. This set/clear function can be called from normal context ( main ) and also from interrupt handler ( Isr() ). As this, the variable must be made volatile to prevent it from reordering or keeping it somewhere in registers. std::atomic is not a valid option here, as there is no underlying OS nor a multi cpu core nor caches so some kind of memory fences are not needed. Even std::atomic is not part of any avr c++ library.

The operation to set a flag is something like: some_flags|= new_set_flags.

But with c++20 I get the warning: warning: compound assignment with 'volatile'-qualified left operand is deprecated [-Wvolatile].

It is no problem to rewrite the functions by using a temporary variable, but it feels that this is not the intend of making the volatile keyword deprecated in this situation.

BTW: As the variable is stored in RAM, the cpu is not able to set a bit in the memory in a single assembler instruction. As this, the whole operation must be atomic. For that use case the avr-lib has ATOMIC_BLOCK(ATOMIC_RESTORESTATE) which simply disables interrupts.

#include <util/atomic.h>

volatile uint8_t some_flags;

void SetFlag( uint8_t new_set_flags )
{
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) 
    {   
        uint8_t tmp = some_flags;
        tmp |= new_set_flags;
        some_flags = tmp;

        ... vs ...

        some_flags|= new_set_flags; // main.cpp:65:19: warning: compound assignment with 'volatile'-qualified left operand is deprecated [-Wvolatile]
    }   
}

void SomeIsr()
{
    SetFlag( 0x02 );
}

int main()
{
    SetFlag( 0x01);
}

Question: What is the "correct" way of setting a flag to a int variable if this flags/variable is used in different context on a bare metal controller with a single core without OS nor MMU.

It becomes very curious if you want to set a bit on a IO register, which is on AVR something like PORTA|=0x01; and the compiler can take that action within a single assembler instruction.

#include <avr/io.h>

int main()
{
    PORTA|=0x01; // main.cpp:57:10: warning: compound assignment with 'volatile'-qualified left operand is deprecated [-Wvolatile]
}

0000006c <main>:
  6c:   d8 9a           sbi 0x1b, 0 ; 27
  6e:   90 e0           ldi r25, 0x00   ; 0
  70:   80 e0           ldi r24, 0x00   ; 0
  72:   08 95           ret
Klaus
  • 24,205
  • 7
  • 58
  • 113
  • http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1152r4.html – Asteroids With Wings Jul 17 '20 at 12:56
  • @KamilCuk: If you can't implement a lock free atomic on that machine for the given data type, you have to set a lock with your underlying OS. – Klaus Jul 17 '20 at 13:19
  • 1
    @KamilCuk: I have a question about setting a single bit on an int var on a given controller. It is a interesting discussion to implement log free atomics on some machines, but the given controller can't implement it. It simply has no assembler instruction to read/modify/write from a location on ram. So this discussion did not help on the given topic. – Klaus Jul 17 '20 at 13:24
  • The point of the deprecation is that your single-instruction update is not a **portable** assumption. If you don’t care about portability, you can use some implementation-specific **intrinsic** for the purpose (or use inline assembly if no such intrinsic exists for your compiler (yet)). – Davis Herring Jul 17 '20 at 15:42

1 Answers1

1

The rationale is that compound assignments or pre-post incrementations or decrementations are not atomic even on a volatile variable, while a programmer could see it as a single operation. Moreover, the standard says that E1 op= E2 is the same as E1 = E1 op E2 except that E1 is evaluated only once.

That means that non cautious programmers could use

volatile uint8_t some_flags;
...
some_flags|= new_set_flags;

with the expectation that it will be atomic, even in presence of hardware interruptions while it is not required to be.

At the machine level it looks like 3 operations:

load value from memory
update accumulator register
store value to memory

That means that without more precautions, a race condition occurs if 2 executions threads (here normal processing and an ISR) are interleaved:

normal loads
! ISR takes the processor
ISR loads updates and stores
! return from ISR
normal updates and stores erasing the change from ISR

When the program uses a temp variable it is evident that race conditions could occur.

What is bad for you, it that the C++ commitee has deprecated that use with the intention to later fully remove it.

So you can:

  • add to the specification of your code that it depends on allowing compound assignment on volatile variables and just hope that compilers will offer options for it (feels reasonable even if not very nice)
  • add to the specifications of your code that is is C++17 compatible but will not support C++20 and over
  • change that to be compiled as C code (C++ standard still support cross C - C++ linking)
  • just write it as some_flags = some_flags | new_set_flags;

I prefere the last way because for a volatile byte, there are no reason that the compiler produces a less efficient code, and it is conformant from early C version to the last C++ one


References:

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • I can't see that deprecating volatile will help anyone to write better code nor makes it easier for compiler vendors. OK, anyway... Especially for having memory mapped IO registers which are typically defined as `volatile` and mapped to given address, deprecation of volatile sounds a really bad idea and will brake a lot of code. E1 = E1 op E2 makes it really not better :-) OK, I'm not in the standard committee so I have to deal with the results. I will use that "new" way of setting bits even if it is a pain. – Klaus Jul 17 '20 at 14:23
  • @Klaus: volatile is not deprecated! What is deprecated is *compound assignment on volatile*... – Serge Ballesta Jul 17 '20 at 14:45
  • Quite clear. But this will result in making standard IO handling deprecated :-) PORTA|=0x01; to switch on the bit of a io port is now deprecated. That sounds simply wrong to me! – Klaus Jul 17 '20 at 14:46
  • You can always use a C module if you prefere to keep that old C syntax. Anyway I am not supporting the commitee decision here, nor will I try to blame it: it is just a fact that we will have to live with... BTW, `PORTA|=0x01` has always contained a possible race condition when multiple execution threads can exist. – Serge Ballesta Jul 17 '20 at 15:00