Memory barriers in the IAR Compiler
The compiler's speed optimizations can result in a reshuffle of the order in which instructions are executed. This is not a problem for single-threaded applications, but for multi-threaded applications re-ordered memory accesses can create problems for the application semantics, leading to unpredictable behavior. To prevent this, you can use compiler memory barriers (also called memory fences). A compiler memory barrier makes sure that the compiler does not reorder memory access instructions. Typically, instructions meant to be executed before the barrier will be executed before instructions meant to be executed after the barrier—the optimizer cannot re-order memory accesses across the barrier. The most common sequence point is a function call.
Note
The memory barrier constrains the compiler, but not the CPU at runtime—only CPU memory barriers in hardware do that.
Even if the compiler outputs instructions in correct order, some hardware features might need further sequencing. Processor optimizations can result in memory operations occurring in an order different from that specified in the executing code. To force the core to wait for memory accesses to complete, the RISC-V ISA specifies the FENCE group of instructions as memory barriers to safeguard the ordering of memory and device operations for a hart (FENCE, FENCE.I, FENCE.TSO, and SFENCE.VMA—and additional instructions in the Cache Maintenance Operations (CMO) standard extension). The compiler treats these CPU memory barriers also as compiler barriers and will not reorder instructions across them when optimizing.
The intrinsic functions __disable_interrupt and __enable_interrupt are also treated as memory barriers by the IAR Compiler.
Implementing compiler memory barriers using inline assembler
The asm volatile ("" : : : "memory") statement works similar to the corresponding statement in the GCC compiler: it keeps an object in the location where it is placed and most memory accesses cannot move across it.
Note that both GCC and the IAR Compiler allow memory accesses to move across an object if the compiler can know that asm volatile ("" : : : "memory") cannot modify its value. In particular this is the case for variables with automatic storage duration, where the address has not leaked outsize the function. This implies that the loads and stores *val1 in the example below are free to move across the barrier—to prevent this, you must move the declaration of val1 out of the function like this:
int *val1;
int test_mem_barrier( int a);
int test_mem_barrier( int a)
{
int b = a;
val1 = &b;
volatile int val2;
*val1 = (*val1 + 20);
asm volatile (";;; BARRIER" : : : "memory");
val2 = 20;
return (val2 + *val1 + a);
}
int main()
{
int i = 25;
int y = test_mem_barrier(i);
return 0;
}