[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
asm
を使った場合のアセンブラ命令で、その命令のオペランドを
C の式を使って指定することができる。これは、読者が使いたいデータが
どのレジスタにあるのか、メモリのどこにあるのかを考える必要が
ないということを意味する。
マシン記述に現れるようなアセンブラ命令のテンプレート、それに 各オペランドに対するオペランド制約文字列を指定しなければならない。
例えば、以下に 68881 の fsinx
命令の使い方を示す。
asm ("fsinx %1,%0" : "=f" (result) : "f" (angle)); |
ここで、angle
入力オペランドを表す C の式であり、
result
は出力オペランドを表す C の式である。それぞれ、
‘"f"’ をオペランド制約として持ち、浮動小数点レジスタが必要である
事を示している。‘=f’ の ‘=’ は、オペランドが出力であることを
表している。出力オペランドの制約は全て ‘=’ を使わなければならない。
制約の書き方はマシン記述で使われているものと同じである(see section オペランド制約)。
各オペランドは一個のオペランド制約文字列とそれに続く括弧に入った C の式 で記述される。コロンでアセンブラテンプレートと出力オペランドの先頭を 区切り、もう一つのコロンで最後の出力オペランドと入力オペランドの先頭を 区切る。カンマで、それぞれのグループのオペランドを区切る。 オペランドの総数は、10 か、マシン記述中の命令パターンで使われている ものの中で最大のオペランド数か、どちらか大きいほうに制限される。
出力オペランドはないが、入力オペランドはあるという場合は、 出力オペランドが本来置かれる場所を囲むようにコロンを二つ続けて 置かなければならない。
出力オペランドの式は左辺値でなければならない。GNU CC はこれを検査する
ことが可能である。入力オペランドは左辺値である必要はない。
GNU CC は、オペランドのデータ型が実行される命令に適切なものかどうかの
検査は行えない。アセンブラ命令テンプレートの構文解析は行わないので、
それが何を意味するものか、あるいはそれがアセンブラへの入力として
有効なものかどうかさえも判らないのである。
拡張された asm
の機能がもっとも良く使われるのは、
GNU CC がその存在を知らないような機械語命令に対してである。
出力式が直接アドレスを指定できないものである場合は
(例えば、ビットーフィールド)、制約でレジスタを許すようにしなければ
ならない。その場合、GNU CC は asm
の出力としてレジスタを使い、
その後そのレジスタを出力先に格納する。
通常の出力オペランドは書き込み専用でなければならない。 GNU CC は、現在の命令の前にこれらのオペランドに入っていた値は 死んでおり、生成する必要がないと想定する。拡張 asm は、入力/出力 両用、あるいは読み書き両用のオペランドをサポートしている。 そういうオペランドであることを示すには制約文字 ‘+’ を使い、 出力オペランドとして列挙する。
読み書き両用のオペランド(あるいは一部のビットしか変化しないオペランド)
に対する制約がレジスタを許しているなら、別の選択肢として、
論理的にはその機能を二つの別々のオペランドに分割し、一つを入力
オペランド、もう一つを書き込み専用出力オペランドとすることもできる。
この二つのオペランドの間の関係は、制約により、命令を実行するときには
二つのオペランドが同じ位置にある必要があると指定することで、表される。
両方のオペランドに同じ C の式を使うことも出来るし、違う式を使っても良い。
例えば、以下では、(架空の) ‘combine’ 命令を bar
を
読みだし専用読みだし元オペランドとして、foo
を読み書き両用
目的先オペランドとして書いている。
asm ("combine %2,%0" : "=r" (foo) : "0" (foo), "g" (bar)); |
オペランド 1 の制約 ‘"0"’ は、オペランド 0 と同じ位置を占めなければ ならないということを表す。制約の中に数字を置くのは入力オペランドの 場合だけ許され、それは出力オペランドを参照していなければならない。
制約の中の数字だけが、一つのオペランドがもう一つ別のオペランドと
同じ位置になることを保証できる。単に foo
が両方のオペランドの
値になっているというだけでは、生成されるアセンブラコードで
同じ位置になるということを保証するには充分ではない。
以下のように書いたのでは正しく動作しない。
asm ("combine %2,%0" : "=r" (foo) : "r" (foo), "g" (bar)); |
色々な最適化や再ロードにより、オペランド 0 と 1 が異なるレジスタに
置かれる可能性はある。GNU CC は、そうしない理由はないということを
知っている。例えば、GCC は foo
の値のコピーが一つのレジスタに
あることを見つけ、オペランド 1 用にそれを使うが、オペランド 0 を
別のレジスタに出力するかもしれない(後で foo
自身のアドレスに
コピーする)。当然、オペランド 1 用のレジスタはアセンブラコードでは
言及さえされていないので、その結果は動作しないのだが、GNU CC には
それが判らないのである。
命令によっては特定のハードレジスタを上書きするものがある。 このことを記述するには、入力オペランドの後ろに3番目のコロンを置き、 その後に上書きされるハードレジスタ名を、レジスタ一つを一つの文字列 として置く。以下に VAX 用の実際の例を示す。
asm volatile ("movc3 %0,%1,%2" : /* no outputs */ : "g" (from), "g" (to), "g" (count) : "r0", "r1", "r2", "r3", "r4", "r5"); |
入力オペランドと出力オペランドが重なるような破壊指定はエラーである
(例えば、一つのメンバからなるレジスタクラスを記述するオペランドを
破壊されるリストで言及すること)。一番はっきりしている例は、
入力オペランドが変更を受けるのに、出力として使われていないという
記述を行うのは正しくない。いずれにしても入力オペランドと出力オペランド
として指定する必要がある。使われない出力オペランドしかない場合は、
以下で説明するように、asm
分に volatile
を指定する必要も
ある。
このアセンブラコードから特定のハードウェアレジスタを参照する場合は、 おそらく、三番目のコロンの後にそのレジスタを列挙して、 GCC に対してそのレジスタの値が変更を受けることを知らせる必要が あるだろう。
読者の書いたアセンブラ命令が条件コードレジスタを変更し得るなら、 ‘cc’ を破壊されるレジスタのリストに入れておく。 いくつかの機種では GNU CC は、条件コードをある特定のハードウェアレジスタ で表現する。それ以外の機種では、条件コードは違う取扱いを受けるので、 ‘cc’ を指定しても意味がない。だが、どんな機種でもコードとしては 有効である。
読者の書いたアセンブラ命令が予期できない形でメモリを変更するなら、 ‘memory’ を破壊されるレジスタのリストに追加しておく。 こうすると、GCC は、そのアセンブラ命令を越えては、メモリ値をレジスタに キャッシュしておくことはしない。
複数のアセンブラ命令をまとめて一個の asm
テンプレートに
入れることができる。その場合、それぞれを改行(‘\n’ と書く)か
アセンブラが許すなら、セミコロンで区切る。GNU アセンブラはセミコロンが
使えるし、ほとんどの Unix アセンブラでも使えるようだ。入力オペランドは
どの破壊レジスタも使わないこと、そして、出力オペランド
のアドレスも使われないことが保証されている。このため、破壊レジスタは
好きなだけ何度でも読み書きすることができる。一個のテンプレートに
複数の命令を入れる例を以下に示す。サブルーチン _foo
は
レジスタ 9 と 10 で引数を受け取ることを想定している。
asm ("movl %0,r9;movl %1,r10;call _foo" : /* no outputs */ : "g" (from), "g" (to) : "r9", "r10"); |
出力オペランドに ‘&’ 制約修飾子がない限り、GNU CC は、関係のない 入力オペランドと同じレジスタにそれを割り当てる。この時、入力は 出力が生成される前に消費されるという想定を行う。この想定は、 アセンブラコードが実際に複数の命令から成っている場合は偽になる可能性がある。 そういう場合、入力と重なってはならない出力オペランドにはそれぞれ ‘&’ を付けること。 See section 制約修飾子文字。
アセンブラ命令により生成される条件コードをテストしたい場合は、
以下のように、asm
構文に分岐と命令を含めなければならない。
asm ("clr %0;frob %1;beq 0f;mov #1,%0;0:" : "g" (result) : "g" (input)); |
これはアセンブラが、GNU アセンブラやほとんどの Unix のアセンブラのように、 ローカルラベルをサポートしていることを想定している。
ラベルについて言えば、一つの asm
から別の asm
への
ジャンプはサポートされていない。GCC の最適化器はそういう
ジャンプについては知らないので、どのように最適化するかを決めるときに
考慮にいれることができないのである。
普通、これらの asm
命令を最も手軽に使うには、
関数のように見えるマクロに押し込めることである。例えば、
以下のようにする。
#define sin(x) \ ({ double __value, __arg = (x); \ asm ("fsinx %1,%0": "=f" (__value): "f" (__arg)); \ __value; }) |
ここでは、変数 __arg
を使って、命令が適切な double
型の
値に作用すること、それに引数 x
として自動的に double
型に
変換可能なものだけを受け付けることを保証している。
命令が正しいデータ型に作用することを保証するもう一つの方法は、
asm
の中でキャストを使うことである。これは、変数 __arg
を
使う方法とは、さらに差異の大きな型に変換するという点において異なる。
例えば、望ましい型が int
だとすると、引数を int
に
キャストした場合、その引数としてポインタを何の不満もなく受け付ける。
一方、その引数を、__arg
で指定される int
型の変数に
代入すると、呼出し側で明示的にキャストしていない限り、ポインタ型を
使うと警告がでる。
asm
文に出力オペランドがあると、GNU CC は最適化目的のために
その命令に、出力オペランドを変更する以外の副作用がないことを想定する。
これは副作用がある命令を使うことができないということではないが、
注意しなくてはならない。GCC は、出力オペランドが使われていないと
その命令を消去したり、ループの外側に移動したり、共通部分式を
構成している場合には二つの命令を一つに置き換えたりするからである。
また、読者の命令に変数について副作用があり、その変数がそれ以外には
変更を受けないものに見えたなら、その変数の元の値は、レジスタに
残っていれば、後で再使用される可能性がある。
asm
の後にキーワード volatile
を指定することで、
その asm
命令が削除されたり、著しく移動されたり、組み合わせられたり
するのを避けることができる。
#define get_and_set_priority(new) \ ({ int __old; \ asm volatile ("get_and_set_priority %0, %1": "=g" (__old) : "g" (new)); \ __old; }) |
出力のない asm
命令を書くと、GNU CC はその命令に副作用がある
ことを知り、その命令を消したりループの外側に移動したりしない。
読者の書いた命令の副作用が純粋に外部的なものでないが、
入力を読み込み、指定されたレジスタやメモリを破壊する以外の方法で
読者のプログラム中の変数に影響を与えるなら、キーワード volatile
を指定して、GNU CC の将来のバージョンが中心領域内で命令を
移動するのを防いでおくべきである。
オペランドや破壊が一つもない asm
命令(それに「古い形式の」
asm
)は、到達不能でない限り、おかまいなしに削除されたり
大きく移動されたりすることはない。キーワード volatile
を
書いたのと同じである。
volatile 指定付きの asm
命令であっても、GCC にとって
違いがないと見れば、例えばジャンプ命令をまたぐといったような移動も
行われる可能性があるので注意して欲しい。
揮発性 asm
命令の列がそのまま連続に保たれると期待することは
できない。出力が連続になって欲しければ、一個の asm
文を
使うことである。
アセンブラ命令で残された条件コードをアクセスする方法を探そうと 誰しも思うだろう。だが、それを実装しようとすると、信頼できる方法が ないことがわかった。問題は、出力オペランドは再ロードを必要とする ことがあり、それにはストア命令を続ける必要が出てくる。ほとんどの 機種ではこれらの命令により、値を検査する前に条件コードを変えてしまう ということにある。この問題は、通常の「テスト」命令と「比較」命令では 起きない。これらの命令には出力オペランドがないからである。
ANSI C のプログラムからインクルードされるヘッダファイルでは、
asm
の代わりに __asm__
と書くようにする。
See section もう一組のキーワード。
This document was generated
using texi2html 1.78.