avr-libc  2.0.0
Standard C library for AVR-GCC

AVR Libc Home Page

AVRs

AVR Libc Development Pages

Main Page

User Manual

Library Reference

FAQ

Example Projects

コンパイラによる最適化

コードの並び替え問題

著者
Jan Waclawek

プログラムには、命令文の順序が記述されており、単純なコンパイラーは書かれたとおりの順序に命令を実行します。しかし、最適化コンパイラーは、結果が「実質的な影響」が同じになるならば、命令文やその一部を自由に並び替えます。「実質的な影響」の「尺度」は「副作用」と呼ばれる基準であり、volatile修飾子のついた変数へのアクセス(読み書き)のみで行われます。したがって、すべてのvolatile変数の読み書きは、同じアドレスで同じ順序に(そして書込みは同じ値を書き込む)で行われる限り、プログラムは他の動作に関係なく正しくなります。(注意すべき点は、連続するvolatile変数へアクセスされる間隔はまったく考慮されないことです。)

残念ながら、volatile変数へのアクセスで対象とならない動作もあります。avr-gcc/avr-libcでの例として、<avr/interrupt.h>で定義されたcli()sei()マクロで、__asm__()命令を使いアセンブラニーモニックへ直接変換されています。これらは、変数へのアクセスもなく、volatile変数でもないため、コンパイラーは自由に配置できます。__asm__()命令に付けられる「volatile」修飾子がありますが、ドキュメントからは並び替えへの影響は明確ではありません(最適化による完全な削除を防ぐだけの可能性が高いです)。(特に)つぎのように述べてるようにです。

asm volatile命令でさえ、ジャンプ命令を含む他のコードと相対的に移動できることに注意してください。 [...] 同様に、asm volatile命令の動作順序が完全に連続することは期待できません。

参考
http://gcc.gnu.org/onlinedocs/gcc-4.3.4/gcc/Extended-Asm.html

メモリバリアという、同様の機能を実現できるメカニズムがあります。インラインasm命令文で破壊レジスタに"memory"を指定することで実現でき、命令文の前に全てレジスタからメモリにフラッシュされ、命令文の後で再読込されることが保証されます。メモリバリアの目的は、コードの順序を強制することとは少し違い、レジスタに「キャッシュされた」変数が無いことを保証するためで、たとえばマルチタスクOSでコンテキストスイッチの時にレジスタを安全に変更します(「大きな」プロセッサでのアウト・オブ・オーダー実行は、「順序通り」状態をプロセッサに強制する特別な命令の使用を暗示しています。(AVRではありません))。

しかし、メモリバリアは、バリアに関して指定された順にバリアの前後すべてのvolatileアクセスが保証されるように機能します。ただし、コンパイラがバリアを超えてvolatileに関連しない命令文を移動することは保証されていません。Peter Danneggerは、この影響の良い例を提供してくれました。

#define cli() __asm volatile( "cli" ::: "memory" )
#define sei() __asm volatile( "sei" ::: "memory" )
unsigned int ivar;
void test2( unsigned int val )
{
val = 65535U / val;
cli();
ivar = val;
sei();
}

最適化をオン(-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アクセスが順序通り発生することに注意してください。そのため、「実質的な影響」で求められた基準は意図した通り達成され、オフになっているのは「いっとき」のタイミングです。ただし、おおくの組込アプリケーションでは、タイミングは重要で、クリティカルな要素になります。

参考
https://www.mikrocontroller.net/topic/65923

残念ながら、現時点では、avr-gcc(およびC標準)には、記述と実行のコード順序を完全に一致させるメカニズムは、最適化を完全にオフにするスイッチ(-O0)を指定するか、アセンブラでクリティカル部分を全部書く以外にありません。

まとめると: