強火で進め

このブログではプログラム関連の記事を中心に書いてます。

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

この2つプログラムの「動作結果」と「コンパイラが生成したアセンブリコード」は以下の様になりました。

正しいプログラム

▼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] にストアされてしまいます。

検証結果

このようにインラインアセンブラではその前処理やレジスタ使用に以下の様な特徴があるようです。

特に最後の特徴が問題でサンプルで確認した様な症状が発生するようです。

__asm__ volatile (
"(アセンブリの記述)"
:(出力オペランドに対応するCの式) ← (B)
:(入力オペランドに対応するCの式) ← (A)
:(破壊されるデータの情報)
);

もちろん、入力オペランドである (A) が無い場合やデータをすべて使用した後に (B) 出力オペランドの値を使用するのであれば問題ないのですがその様な場合以外すべて & を使用することになります。

iPhoneインラインアセンブラを使うのエントリー一覧はこちら