IAR コンパイラのメモリバリア
コンパイラの速度最適化により、命令が実行される順序が再混合される場合があります。これはシングルスレッドのアプリケーションでは問題ありませんが、マルチスレッドのアプリケーションでは、メモリアクセスの順序を変更すると、アプリケーションの意味論に問題が生じ、予測不可能な動作につながる可能性があります。これを防ぐためには、コンパイラメモリバリア(メモリフェンスとも呼ばれます)を使用できます。コンパイラメモリバリアは、コンパイラがメモリアクセス命令を並べ替えないようにします。典型的には、バリアの前に実行されることを意図した命令は、バリアの後に実行されることを意図した命令の前に実行され、最適化機能は、バリアを横断してメモリアクセスを再オーダーすることはできない。最も一般的なシーケンスポイントは関数呼び出しです。
注記
このメモリバリアはコンパイラには制約を課しますが、ランタイム時のCPUには制約しません——ハードウェアにおけるCPUメモリバリアのみがそれを実行します。
たとえコンパイラが命令を正しい順序で出力しても、一部のハードウェア機能ではさらにシーケンシングが必要な場合があります。プロセッサの最適化により、メモリ操作が実行コードで指定された順序とは異なる順序で発生する場合があります。コアにメモリアクセスの完了を待つように強制するには、多くの Arm アーキテクチャで CPU メモリバリア命令 DMB、 DSB、および ISB が指定されています。__DMB、 __DSB、および __ISB 組込み関数を使用して、コードからこれらの関数に直接アクセスします。コンパイラは、これらの CPU メモリバリアをコンパイラバリアとしても扱い、最適化時にそれら全体で命令を並べ替えません。
組み込み関数__disable_interruptおよび__enable_interruptも IARコンパイラによってメモリバリアとして扱われます。
インラインアセンブラを使用したコンパイラメモリバリアの実装
asm volatile ("" : : : "memory") ステートメントは、GCCコンパイラの対応するステートメントと同様に動作します:オブジェクトを配置された場所に保持し、ほとんどのメモリアクセスはこれを越えて移動できません。
GCCとIARコンパイラの両方は、コンパイラがasm volatile ("" : : : "memory")がオブジェクトの値を変更できないと判断できる場合、メモリアクセスがオブジェクトを越えて移動することを許可することに注意してください。これは特に、アドレスが関数外に漏洩していない自動記憶域期間を持つ変数に当てはまります。これは、下記の例における*val1のロードおよびストアがバリアを越えて自由に移動できることを意味します——これを防ぐには、val1の宣言を次のように関数の外に移動する必要があります:
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;
}