Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is `volatile` enough to allow the compiler to handle machine registers with side-effects on read?

I work with microcontrollers where there are occasionally machine registers that have actions that occur when a register is read. Yes, that's not a typo, there's lots of registers that cause actions when they are written, but in a few cases if you read a register, something happens.

The most common instance of this is a UART receive register hooked up to one end of a FIFO; for example let's say there is RXDATA. When you read RXDATA it pulls one byte out of the FIFO and the next time you read RXDATA it will get the next byte.

Is there enough information in volatile to get the compiler to understand that there might be side effects from a read?

Example fragment in C:

#include <stdint.h>
#include <stdbool.h>

volatile uint8_t RXDATA;   
// there is some mechanism for associating this with a known hardware address
// (either in linker information, or in some compiler-specific attribute not shown)    

// Check that bit 0 is 1 and bit 7 is 0.
bool check_bits_1() 
{
   const uint8_t rxdata = RXDATA;
   return (rxdata & 1) && ((rxdata & 0x80) == 0);
}

// Check that bit 0 is 1 and bit 7 is 0.
bool check_bits_2() 
{
   return (RXDATA & 1) && ((RXDATA & 0x80) == 0);
}

// Check that bit 0 is 1 and bit 7 is 0.
bool check_bits_3() 
{
   const bool bit_0_is_1 = RXDATA & 1;
   const bool bit_7_is_0 = (RXDATA & 0x80) == 0;
   return bit_0_is_1 && bit_7_is_0;
}

If I ignore the C standard and pretend that a compiler does exactly what I think I am asking it to do (DWIM), then my intuition is that these three functions have different behavior:

  • In the first case, we read RXDATA once, so we pull out one byte of the FIFO and then do some math on it.
  • In the second case, we read RXDATA either once or twice (because && has short-circuit behavior), doing math directly on the register value, so we might either pull out one or two bytes from the FIFO, and this has incorrect behavior.
  • In the third case, we read RXDATA twice, pulling two bytes from the FIFO, so this is incorrect behavior.

Whereas if RXDATA isn't volatile then presumably all three of the above implementations are equivalent.

Does the C standard require the compiler to interpret volatile in this case in the same way I am looking at it? If not, how can a hardware register be handled properly in C?

like image 379
Jason S Avatar asked Feb 05 '23 05:02

Jason S


1 Answers

Is there enough information in volatile to get the compiler to understand that there might be side effects from a read?

Yes.

The C language formal definition of a side effect actually targets this very scenario. C11 5.1.2.3:

Accessing a volatile object, modifying an object, modifying a file, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment.

Regarding what the compile is allowed to optimize, C11 5.2.3.4:

In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).

In plain English, this means that any form of access, read or write, to a volatile object, is considered a side effect and a compiler is not allowed to optimize away side effects.

...then my intuition is that these three functions have different behavior

Indeed they have. This is why coding standards such as MISRA-C forbids us to mix volatile variable access together with other things in the same expression. In the UART scenario, doing so might cause loss of status flags which would be a severe bug.

Robust programs read/write to volatile variables on a single line and do all other necessary arithmetic in separate expressions.

like image 97
Lundin Avatar answered Feb 16 '23 00:02

Lundin