avr-libc
2.0.0
Standard C library for AVR-GCC
|
AVR Libc Home Page |
AVR Libc Development Pages |
||||
Main Page |
User Manual |
Library Reference |
FAQ |
Example Projects |
プログラムには、命令文の順序が記述されており、単純なコンパイラーは書かれたとおりの順序に命令を実行します。しかし、最適化コンパイラーは、結果が「実質的な影響」が同じになるならば、命令文やその一部を自由に並び替えます。「実質的な影響」の「尺度」は「副作用」と呼ばれる基準であり、volatile
修飾子のついた変数へのアクセス(読み書き)のみで行われます。したがって、すべてのvolatile変数の読み書きは、同じアドレスで同じ順序に(そして書込みは同じ値を書き込む)で行われる限り、プログラムは他の動作に関係なく正しくなります。(注意すべき点は、連続するvolatile変数へアクセスされる間隔はまったく考慮されないことです。)
残念ながら、volatile変数へのアクセスで対象とならない動作もあります。avr-gcc/avr-libcでの例として、<avr/interrupt.h>で定義されたcli()やsei()マクロで、__asm__()命令を使いアセンブラニーモニックへ直接変換されています。これらは、変数へのアクセスもなく、volatile変数でもないため、コンパイラーは自由に配置できます。__asm__()命令に付けられる「volatile」修飾子がありますが、ドキュメントからは並び替えへの影響は明確ではありません(最適化による完全な削除を防ぐだけの可能性が高いです)。(特に)つぎのように述べてるようにです。
asm volatile命令でさえ、ジャンプ命令を含む他のコードと相対的に移動できることに注意してください。 [...] 同様に、asm volatile命令の動作順序が完全に連続することは期待できません。
メモリバリアという、同様の機能を実現できるメカニズムがあります。インラインasm
命令文で破壊レジスタに"memory"を指定することで実現でき、命令文の前に全てレジスタからメモリにフラッシュされ、命令文の後で再読込されることが保証されます。メモリバリアの目的は、コードの順序を強制することとは少し違い、レジスタに「キャッシュされた」変数が無いことを保証するためで、たとえばマルチタスクOSでコンテキストスイッチの時にレジスタを安全に変更します(「大きな」プロセッサでのアウト・オブ・オーダー実行は、「順序通り」状態をプロセッサに強制する特別な命令の使用を暗示しています。(AVRではありません))。
しかし、メモリバリアは、バリアに関して指定された順にバリアの前後すべてのvolatileアクセスが保証されるように機能します。ただし、コンパイラがバリアを超えてvolatileに関連しない命令文を移動することは保証されていません。Peter Danneggerは、この影響の良い例を提供してくれました。
最適化をオン(-Os)にしてコンパイルします。
00000112 <test2>: 112: bc 01 movw r22, r24 114: f8 94 cli 116: 8f ef ldi r24, 0xFF ; 255 118: 9f ef ldi r25, 0xFF ; 255 11a: 0e 94 96 00 call 0x12c ; 0x12c <__udivmodhi4> 11e: 70 93 01 02 sts 0x0201, r23 122: 60 93 00 02 sts 0x0200, r22 126: 78 94 sei 128: 08 95 ret
遅い可能性がある割り算が、cli()を超えて移動し、その結果として、意図したよりも長く割込みが無効になります。cli()やsei()に関してはvolatileアクセスが順序通り発生することに注意してください。そのため、「実質的な影響」で求められた基準は意図した通り達成され、オフになっているのは「いっとき」のタイミングです。ただし、おおくの組込アプリケーションでは、タイミングは重要で、クリティカルな要素になります。
残念ながら、現時点では、avr-gcc(およびC標準)には、記述と実行のコード順序を完全に一致させるメカニズムは、最適化を完全にオフにするスイッチ(-O0)を指定するか、アセンブラでクリティカル部分を全部書く以外にありません。
まとめると: