3

Considering a 32-bit system (such as an ARM RISC MCU), how can one ensure that 16-bit variables are written/ read in an atomic way? Based on this doc, If I understood correctly, both 16-bit and 8-bit operations are atomic, but only assuming the memory is aligned. Question is, does the compiler always align the memory to 32-bit words (excluding cases like packed structures)?

The rationale here is to use uint16_t whenever possible instead of uint32_t for better code portability between 32-bit and 16-bit platforms. This is not about typedefing a type that is different on either platform (16 or 32 bit).

Łukasz Przeniosło
  • 2,725
  • 5
  • 38
  • 74
  • in general mcu or 32 bit or 16 bit or 64 bit there are no atomic assumptions unless expressly documented. As processors mature atomic operations cause a latency that makes performance worse compared to processor improvements to help performance, so the assumption is that atomic operations are a thing of the past. – old_timer Jan 25 '23 at 15:25
  • arm is an ip vendor, and arm has moved away as well so it is up to the chip vendor for that arm BASED chip, so you have to look there. if not documented and not documented exactly how to do it, you dont have it. – old_timer Jan 25 '23 at 15:25
  • compiler has nothing to do with it, as answered there may be functions named atomic, but take that with a grain of salt there may be systemic requirements for that function to work (if there is nothing in the architecture to perform atomic). – old_timer Jan 25 '23 at 15:27
  • Do not take it for granted. You need to look at the entire system. Example, the memory device only support 32bit read/writes. An atomic read/modify/write is needed to preserve the alternate 16bits. For cases where the memory conforms to AXI and supports the 'byte masks' and the memory is aligned, it will work on many ARM CPUs. It is certainly possible for vendors and system designers to make this possible, but they can also break it. And as you noted, the compiler is also part of this equation. The portability argument doesn't hold. Why limit the 32bit CPU. Type check for portability. – artless noise Jan 26 '23 at 14:00
  • I could write an answer to this, but I think you need to clarify. Is this an SMP system (multiple CPUs)? Does this have an MMU and the memory is being translated and/or cached? You are referring to data access and not self-modifying code? Can you verify that the hardware supports it via schematics and vendor documentation or the intent is general purpose platform code like Android or IOS? All of these items matter as well as the compiler support. For this reason your atomicity should be compartmentalize or use a tool to check such as static type checker or languages. – artless noise Jan 26 '23 at 14:12
  • Here is [another Q/A](https://stackoverflow.com/questions/70206510/cprover-fence-arguments) on using CMBC to deal with different memory models. It is also related to Ludin's answer. The term 'atomicity' can mean a great many things. The simplest is that the value written is committed to memory 'in whole' and not partial bytes. I take that is the meaning of this question. Atomic operations and 'lock free' usually involve multiple accesses to memory. – artless noise Jan 26 '23 at 14:17

2 Answers2

3

The compiler may align any (scalar) object as it pleases unless it is part of an array or similar, there's no restriction or guarantee from C. Arrays are guaranteed to be allocated contiguously however, with no padding. And the first member of a struct/union is guaranteed by be aligned (since the address of the struct/union may be converted to a pointer to the type of the first member).

To get atomic operations, you have to use something like atomic_uint_fast16_t (stdatomic.h) if supported by the compiler. Otherwise any operation in C cannot be regarded as atomic no matter the type, period.

It is a common mistake to think "8 bit copy is atomic on my CPU so if I use 8 bit types my code is re-entrant". It isn't, because uint8_t x = y; is not guaranteed to be done in a single instruction, nor is it guaranteed to result in atomic machine code instructions.

And even if an atomic instruction is picked, you could still get real-time bugs from code like that. Example with pseudo-machine code:

  • Store the contents of y in register A.
  • An interrupt which only occurs once every full moon fires, changing y.
  • The old value of y is stored in x - yay, this is an atomic instruction!
  • Now x has the old, outdated value.

Correct real-time behavior would have been to either completely update x before the interrupt hit, or alternatively update it after the interrupt hit.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Hi, thanks for the explanation. I follow your explanation only until the given example with an interrupt: "The old value of y is stored in x - yay, this is an atomic instruction!" – Łukasz Przeniosło Jan 25 '23 at 12:03
  • @ŁukaszPrzeniosło As in, you have no benefit of that particular instruction being atomic because you already missed the variable update. Depending on your real-time requirements, this could either be a huge bug or no problem at all. – Lundin Jan 25 '23 at 12:33
  • The case in here is to ensure that an interrupt cannot occur during a variable update- it either occurs before or after. So I either work on old or new value, not on Partially old/ new one. – Łukasz Przeniosło Jan 25 '23 at 13:04
  • 1
    @ŁukaszPrzeniosło Then you have no guarantees from C unless you use the atomic types from C11. – Lundin Jan 25 '23 at 13:29
1

The place were I work has a special agreement with all the relevant compiler suppliers that they are responsible for notifying our tool chain experts of cases were that assumption is not applicable.
This turned out to be necessary, because it otherwise cannot be reliably determined. Documentation does not say and the standard even lesss.
Or that is what the tooling people told me.

So it seems that for you the answer is: Ask the supplier.

Yunnosch
  • 26,130
  • 9
  • 42
  • 54