iPhoneでインラインアセンブラを使う(プログラム編[5] 制約修飾子「&」)
自分もかなり理解に手間取った(※)制約修飾子 & の解説をします。
※分かりづらかったため実際に比較サンプルなども作成してみてやっと理解できました。
今まで解説したデータの特性を表す "+r" や "=r" の + や = は制約修飾子と呼ばれ、これら以外にも & が存在します。
この & ですが
GNU コンパイラ集(GCC) の使い方と移植について
http://www.sra.co.jp/wingnut/gcc/gcc-j.html
こちらのサイトで確認すると以下の様に説明されています。
出力オペランドに & 制約修飾子がない限り、GNU CC は、関係のない入力オペランドと同じレジスタにそれを割り当てる。この時、入力は出力が生成される前に消費されるという想定を行う。
※ここで「GNU CC」は「GNU Cコンパイラ」の略です。
こちらサイトのサンプルを参考に
ARM GCC Inline Assembler Cookbook
http://www.ethernut.de/en/documents/arm-inline-asm.html
実際のプログラムの動作を確認するために以下の様なルールに従った正しいプログラムと
正しいプログラム
int rdv = 5; int wdv = 10; int table[] = { 1, 2 }; NSLog(@"rdv:%d wdv:%d table[0]:%d table[1]:%d", rdv, wdv, table[0], table[1]); __asm__ volatile ( "ldr %0, [%1] \n\t" "str %2, [%1, #4] \n\t" : "=&r" (rdv) : "r" (&table), "r" (wdv) ); NSLog(@"rdv:%d wdv:%d table[0]:%d table[1]:%d", rdv, wdv, table[0], table[1]);
"=&r" (rdv) を "=r" (rdv) に変更した間違ったプログラムをそれぞれ作成しました。
間違ったプログラム
int rdv = 5; int wdv = 10; int table[] = { 1, 2 }; NSLog(@"rdv:%d wdv:%d table[0]:%d table[1]:%d", rdv, wdv, table[0], table[1]); __asm__ volatile ( "ldr %0, [%1] \n\t" "str %2, [%1, #4] \n\t" : "=r" (rdv) : "r" (&table), "r" (wdv) ); NSLog(@"rdv:%d wdv:%d table[0]:%d table[1]:%d", rdv, wdv, table[0], table[1]);
簡単にプログラムを解説しておくと table[0] の値を rdv にロードし(読み込み)、 wdv の値を table[1] にストアする(書き込む)プログラムです。
C言語風に記述するとこうなります。
rdv = *table
*(table+1) = wdv
正しいプログラム
▼NSLogの出力
rdv:5 wdv:10 table[0]:1 table[1]:2
rdv:1 wdv:10 table[0]:1 table[1]:10
ldr r1, [r2]
str r3, [r2, #4]
間違ったプログラム
▼NSLogの出力
rdv:5 wdv:10 table[0]:1 table[1]:2
rdv:1 wdv:10 table[0]:1 table[1]:1
ldr r3, [r2]
str r3, [r2, #4]
アセンブリコードを確認してもらればすぐ分かる様に r3 が ldr と str の2ヶ所で使われています。
ldr r3, [r2]
str r3, [r2, #4]
ここでの r3 の値は比較では省略してありますがコンパイラが追加した r3 = wdv とするためのアセンブリコードにより wdv に格納されている10がロード済みです。
しかし、 ldr の所で table[0] の値で上書きされているため r3 には 1 がロードされ、その値が table[1] にストアされてしまいます。
検証結果
このようにインラインアセンブラではその前処理やレジスタ使用に以下の様な特徴があるようです。
- なるべく使用するレジスタの数を節約する
- プログラマが記述したアセンブリコードの部分の前に "=&r" (rdv) になどに対応するためのCで定義された変数の値をレジスタにロードするアセンブリコードを追加する
- その追加されるアセンブリコードはデータが途中(入力オペランドをすべて使い切るまで)で書き変わらない前提で作成される
特に最後の特徴が問題でサンプルで確認した様な症状が発生するようです。
__asm__ volatile (
"(アセンブリの記述)"
:(出力オペランドに対応するCの式) ← (B)
:(入力オペランドに対応するCの式) ← (A)
:(破壊されるデータの情報)
);
もちろん、入力オペランドである (A) が無い場合やデータをすべて使用した後に (B) 出力オペランドの値を使用するのであれば問題ないのですがその様な場合以外すべて & を使用することになります。
iPhoneでインラインアセンブラを使うのエントリー一覧はこちら