Cコードでインラインアセンブリのアセンブリ命令テンプレートを使用する方法

アセンブリ命令テンプレート
アセンブリテンプレート:
アセンブラテンプレートは、アセンブラ命令を含むテキスト文字列です。
コンパイラは、テンプレート内のinput、output、およびgotoタグを参照するタグを置き換え、生成された文字列をアセンブラに出力します。
文字列には、インジケータを含め、アセンブラによって認識される任意の命令を含めることができます。
GCCは、アセンブラ命令自体を分析せず、それらの意味も、それらが有効なアセンブラ入力であるかどうかさえも知りません。
システムアセンブリコードで通常使用される文字で区切って、複数のアセンブラ命令をasm文字列に入れることができます。
ほとんどの場所で機能する組み合わせは、新しい行の文字と、指示フィールドに移動するためのタブ(「\ n \ t」と表記)です。
一部のアセンブラでは、行区切り文字としてセミコロンを使用できます。ただし、一部のアセンブリ言語ではコメントを開始するためにセミコロンを使用することに注意してください。
揮発性修飾子が使用されている場合でも、アセンブリ後にasmステートメントのシーケンスが完全に連続したままになることを期待しないでください。
一部の命令を出力で連続させる必要がある場合は、それらを複数命令のasmステートメントに入れてください。
入力/出力オペランドを使用しない場合(たとえば、アセンブラーテンプレートでグローバルシンボルを直接使用する場合)、Cプログラムからデータにアクセスできます。期待どおりに動作しません。
同様に、アセンブラテンプレートから直接関数を呼び出すには、ターゲットアセンブラとABIを詳細に理解する必要があります。
GCCはアセンブラテンプレートを解析しないため、参照されているシンボルには表示されません。
これにより、GCCは、入力、出力、またはgotoオペランドとしてもリストされていない限り、これらのシンボルを参照されていないものとして破棄する可能性があります。

特別な形式の文字列:
入力、出力、およびgotoオペランドによって記述されるトークンに加えて、次のトークンはアセン
ブラーテンプレートで特別な意味を持ちます。'%%':
単一の '%'をアセンブリに出力しますプログラムコード内。
'%=':
コンパイルプロセス全体でasmステートメントの各インスタンスに固有の番号を出力します
。このオプションは、複数のアセンブラ命令を生成する単一のテンプレートでローカルタグを作成し、それらを複数回引用する場合に非常に便利です。
'%{ ':
'%| ':
'%} ':
対応して' {'、' |および '}'をアセンブラコードに出力します。

asmテンプレートの複数のアセンブリ言語:

出力オペランド:
asmステートメントには0個以上の出力オペランドがあり、アセンブリコードによって変更されたC変数の名前を表します。
例:
このi386の例では、old(テンプレート文字列では%0と呼ばれます)と* Base(%1として)が出力であり、Offset(%2)が入力です
。boolold;

		__asm__ ("btsl %2,%1\n\t" // Turn on zero-based bit #Offset in Base.
				 "sbb %0,%0"      // Use the CF to calculate old.
		   : "=r" (old), "+rm" (*Base)
		   : "Ir" (Offset)
		   : "cc");

		return old;

出力オペランドの構文形式:
オペランドはコンマで区切られます。
制約
|
V
[[asmSymbolicName]]制約(cvariablename)

asmSymbolicName:

オペランドの記号名を指定します。
名前を角括弧で囲んで、アセンブラテンプレートの名前を参照します(つまり、 '%[Value]')。
名前の範囲は、それを定義するasmステートメントに対応することです。
周囲のコードですでに定義されている名前を含め、任意の有効なC変数名を使用できます。
同じasmステートメントの2つのオペランドで同じシンボリック名を使用することはできません。
asmSymbolicNameを使用しない場合は、アセンブラテンプレートのオペランドリストでオペランドの(ゼロベースの)位置を使用してください。
たとえば、出力オペランドが3つある場合、テンプレートの「%0」を使用して最初のオペランドを参照し、「%1」を使用して2番目のオペランドを参照し、「%2」を使用して3番目のオペランドを参照します。

constraint:

オペランドの位置制約を指定する文字列定数。
出力制約は、「=」(既存の値を上書きする変数)または「+」(読み取りおよび書き込み用)で始まる必要があります。
'='を使用する場合、オペランドが入力にバインドされていない限り、位置にasmエントリの既存の値が含まれていると想定しないでください。
プレフィックスの後に、値の位置を記述するための1つ以上の追加の制約が必要です。
レジスタとmについて:一般的な制約は、rが含まれるメモリのために。
あなたは、複数の可能な位置を(例えば、「= RM」)をリストすると、コンパイラは現在のコンテキストに基づいて、最も効果的な場所を選択します。
場合asmステートメントの範囲内で可能な限り多くの選択肢をリストし、オプティマイザーが可能な限り最良のコードを生成できるようにします。
特定のレジスタを使用する必要があるが、マシンの制約によって必要な特定のレジスタを選択するための十分な制御が提供されない場合は、ローカルレジスタ変数で解決策を提供できます。

cvariablename:

C左辺値式を指定して、出力(通常は変数名)を保存します。
ブラケットは文法の必須部分です

输出操作数表达式必须是左值。使用“+”约束修饰符的操作数被计算为两个操作数(即同时作为输入和输出),每个asm语句的操作数最多为30个

入力とオーバーラップできないすべての出力オペランドには、「&」制約修飾子を使用します。
それ以外の場合、アセンブリコードが出力を生成する前に入力を使用する場合、GCCは出力オペランドを同じレジスタ内の無関係な入力オペランドとして割り当てることができます。アセンブラコードに実際に複数の命令が含まれている場合、この仮定は間違っている可能性があります。
1つの出力パラメーター(a)がレジスターの制約を許可し、別の出力パラメーター(b)がメモリーの制約を許可する場合、同じ問題が発生します。
bのメモリアドレスにアクセスするためにGCCによって生成されたコードには、aによって共有される可能性のあるレジスタを含めることができ、GCCはこれらのレジスタをasmの入力と見なします。
前述のように、GCCは、出力を書き込む前にこれらの入力レジスタを使用する必要があると
想定しています。asmステートメントがbを使用して記述されている場合、この想定は誤った動作につながる可能性があります。「&」修飾子とレジスタ制約を組み合わせて、変更がアドレス参照に影響を与えないようにします。それ以外の場合、使用前にbを変更すると、bは未定義の位置になります。
asmは、オペランドのオペランド修飾子をサポートします(単に「%2」ではなく「%k2」など)。通常、これらの修飾子はハードウェアによって異なります。asmの
背後にあるCコードが出力オペランドを使用しない場合は、volatile for asmステートメントを使用して、オプティマイザーがasmステートメントを不要なときに破棄
しないようにしますこのコードはオプションのasmSymbolicNameを使用しません。したがって、最初の出力オペランドを%0(または、2番目のオペランドがある場合は%1など)として参照します。最初の入力オペランドの数が最後の出力オペランドの数よりも多い。このi386の例では、マスク参照を%1にします
。uint32_tMask= 1234;
uint32_t Index;

	  asm ("bsfl %1, %0"
		 : "=r" (Index)
		 : "r" (Mask)
		 : "cc");

该代码覆盖变量Index(' = '),将该值放在寄存器(' r ')中。

特定のレジスタ制約の代わりに一般的な「r」制約を使用すると、コンパイラは使用するレジスタを選択できるため、より効率的なコードを生成できます。
アセンブラ命令で特定のレジスタが必要な場合、これは不可能な場合があります。
以下のi386の例では、asmSymbolicName構文を使用しています。
上記のコードと同じ結果が得られますが、オペランドを追加または削除するときにインデックス番号を並べ替える必要がないため、読みやすく、保守しやすいと考える人もいます。
この例では、aIndexとaMaskという名前は、どの名前がどこで使用されているかを強調するためにのみ使用されています。名前インデックスとマスクは再利用できます。
uint32_tマスク= 1234;
uint32_tインデックス;

	  asm ("bsfl %[aMask], %[aIndex]"
		 : [aIndex] "=r" (Index)
		 : [aMask] "r" (Mask)
		 : "cc");



下面是一些输出操作数的示例。
	uint32_t c = 1;
	uint32_t d;
	uint32_t *e = &c;

	asm ("mov %[e], %[d]"
	   : [d] "=rm" (d)
	   : [e] "rm" (*e));		

这里,d可以在寄存器中,也可以在内存中。
由于编译器可能已经在寄存器中有e所指向的uint32_t位置的当前值,所以可以通过指定这两个约束来让它为d选择最佳位置。

入力オペランド:
入力オペランドは、Cからの変数と式の値をアセンブリコードで使用できるようにします。
オペランドを区切るにはコンマを使用します。
入力オペランドの構文形式:
[[asmSymbolicName]]制約(cexpression)

asmSymbolicName:

オペランドの記号名を指定します。
名前を角括弧で囲んで、アセンブラテンプレートの名前を参照します(つまり、「%[値]」)。
名前の範囲は、それを定義するasmステートメントを含めることです。
周囲のコードですでに定義されている名前を含め、任意の有効なC変数名を使用できます。
同じasmステートメントの2つのオペランドで同じシンボリック名を使用することはできません。
asmSymbolicNameを使用しない場合は、アセンブラテンプレートのオペランドリストでオペランドの(ゼロベースの)位置を使用してください。
たとえば、2つの出力オペランドと3つの入力がある場合、テンプレートで「%2」を使用して最初の入力オペランドを参照し、「%3」を使用して2番目を参照し、「%4」を使用して3番目を参照します。
制約:
オペランドの位置制約を指定する文字列定数。
入力制約文字列を「=」または「+」で始めることはできません。
複数の可能な位置(「irm」など)をリストすると、コンパイラは現在のコンテキストに従って選択します。最も効果的な場所。
特定のレジスタを使用する必要があるが、マシンの制約が必要な特定のレジスタを選択するための十分な制御を提供しない場合、ローカルレジスタ変数が解決策を提供できます。
入力制約は数値(たとえば、「0」)にすることもできます。これは、指定された入力が、出力制約リストのインデックス(ゼロから開始)の出力制約と同じ位置にある必要があることを意味します。出力オペランドにasmSymbolicName構文を使用する場合、数値の代わりにこれらの名前(括弧 "[]"で囲まれている)を使用できます。
cexpression:
これは、asmステートメントへの入力として渡されるC変数または式です。ブラケットは文法の必要な部分です。
出力オペランドがないが入力オペランドがある場合は、出力オペランドの位置に2つの連続するコロンを配置します
。asm(「いくつかの命令」
:/ *出力なし。* /
:「r」(オフセット/ 8));

警告:不要修改仅输入操作数的内容(与输出绑定的输入除外)。编译器假设从asm语句中退出时,这些操作数包含与执行语句之前相同的值。
不可能使用clobbers通知编译器这些输入中的值正在更改。一种常见的解决方法是将正在更改的输入变量绑定到从未使用过的输出变量。

ただし、asmステートメントに続くコードが出力オペランドを使用しない場合、GCCオプティマイザーはasmステートメントを不要なステートメントとして破棄する可能性があることに注意してください(揮発性を参照)。
この例では、架空の結合命令が入力操作に使用されます。番号1の制約「0」は、出力オペランド0と同じ位置を占める必要があることを意味します。制約で数値を使用できるのは入力オペランドのみであり、それぞれが出力オペランドを参照する必要があります。
制約内の1つの数値(またはシンボリックアセンブラ名)のみが、一方のオペランドがもう一方のオペランドと同じ位置にあることを保証できます。
fooが2つのオペランドの値であるという事実は、生成されたアセンブリコード内の同じ位置にあることを保証するのに十分ではありません。
asm( "combine%2、%0"
: "= r"(foo)
: "0"(foo)、 "g"(bar));

下面是一个使用符号名称的示例
	asm ("cmoveq %1, %2, %[result]" 
	   : [result] "=r"(result) 
	   : "r" (test), "r" (new), "[result]" (old));

クローバーとスクラッチレジスタ:
コンパイラは出力オペランドにリストされたエントリの変更を認識しますが、インラインasmコードは出力だけでなく変更する場合があります。
たとえば、計算には追加のレジスタが必要な場合や、特定のアセンブリ命令の副作用として必要な場合があります。プロセッサがレジスタを上書きする場合があります。
これらの変更をコンパイラに通知するには、それらをclobberリストにリストしてください。
Clobberリスト項目は、レジスター名または特別なClobber(以下に示す)にすることができます。各クローバーリストアイテムは文字列定数であり、二重引用符で囲まれ、コンマで区切られています。
クローバーの説明は、入力または出力のオペランドと重複することはできません。
もう1つの制限は、クローバーリストにスタックポインターレジスタを含めないことです。
これは、コンパイラがスタックポインタの値をasmステートメントの後とステートメントエントリで同じにする必要があるためです。
ただし、以前のバージョンのGCCはこのルールを適用しておらず、スタックポインターをリストに表示することができ、セマンティクスが不明確でした。この動作は非推奨であり、スタックポインターの一覧表示は、GCCの将来のバージョンでエラーになる可能性があります。

下面是一个实际的VAX例子,展示了如何使用Clobber寄存器:
	asm volatile ("movc3 %0, %1, %2"
					   : /* No outputs. */
					   : "g" (from), "g" (to), "g" (count)
					   : "r0", "r1", "r2", "r3", "r4", "r5", "memory");

此外,还有两个特殊的clobber参数:

「cc」:
「cc」クローバーは、アセンブラコード変更フラグレジスタを意味します。
一部のマシンでは、GCCは条件コードを特定のハードウェアレジスタとして表します。「cc」はこのレジスタに名前を付けるために使用されます。他のマシンでは、条件コードの処理が異なり、「cc」を指定しても効果はありません。しかし、目標が何であれ、それは効果的です。
"Memory":
"memory" clobberは、アセンブリコードが、入力および出力オペランドにリストされている項目以外の項目(たとえば、入力パラメーターの1つが指すメモリーへのアクセス)に対してメモリーの読み取りおよび書き込みを実行することをコンパイラーに通知します。
メモリに正しい値が含まれていることを確認するために、GCCはasmを実行する前に特定のレジスタ値をメモリにフラッシュする必要がある場合があります。
さらに、コンパイラは、asmの前にメモリから読み取られた値がasmの後も変更されないままであるとは想定していません。必要に応じて、値を再読み込みします。「メモリ」クローバーを使用すると、コンパイラの読み取り/書き込みメモリバリアを効果的に形成できます。
このクローバーは、プロセッサがasmステートメントを実行した後の投機的読み取りを防止しないことに注意してください。これを防ぐには、プロセッサ固有のフェンス命令が必要です。
レジスタをメモリにフラッシュすると、パフォーマンスに影響し、時間に敏感なコードでは問題になる可能性があります。以下の例に示すように、この状況を回避するために、GCCにより良い情報を提供できます。
少なくとも、エイリアスルールにより、GCCはどのメモリをフラッシュする必要がないかを知ることができます。
これは架空の二乗和命令です。これは、メモリ内の浮動小数点値への2つのポインタを受け入れ、浮動小数点レジスタ出力を生成します。xとyの両方がasmパラメーターに2回表示されることに注意してください。1回はアクセスするメモリを指定し、もう1回はasmが使用するベースレジスタを指定します。
GCCは両方の目的で同じレジスタを使用できるため、これは通常、レジスタを無駄にしません。ただし、%1と%3の両方を使用してasmでxを表し、それらが同じであると期待する場合、それはばかげています。
実際、%3はおそらくレジスターではありません。これは、xが指すオブジェクトへのシンボリックメモリ参照である可能性があります。
asm(“ sumsq%0、%1、%2”
:“ + f”(結果)
:“ r”(x)、“ r”(y)、“ m”(* x)、“ m”(* y ));

这里是一个虚构的*z++ = *x++ * *y++指令。注意,必须将x、y和z指针寄存器指定为输入/输出,因为asm会修改它们。
	asm ("vecmul %0, %1, %2"
		 : "+r" (z), "+r" (x), "+r" (y), "=m" (*z)
		 : "m" (*x), "m" (*y));	

一个x86示例,其中字符串内存参数的长度未知。
	asm("repne scasb"
		: "=c" (count), "+D" (p)
		: "m" (*(const char (*)[]) p), "0" (-1), "a" (0));


如果您知道上面只读取一个10字节数组,那么您可以使用类似于这样的内存输入:
		"m" (*(const char (*)[10]) p)

Gotoラベル:
asm gotoを使用すると、アセンブリコードを1つ以上のCラベルにジャンプできます。
asm gotoステートメントのGotoLabels部分には、アセンブリコードがジャンプする可能性のあるすべてのCラベルのコンマ区切りのリストが含まれています。
GCCは、asmの実行が次のステートメントに続くことを前提としています(そうでない場合は、asmステートメントの後に__builtin_unreachableを使用することを検討してください)
asm gotoの最適化は、ホットタグ属性とコールドタグ属性を使用して
出力なしのasmgotoステートメントを改善することで改善できます。
これは、コンパイラの内部制限によるものです:制御送信命令は出力できません
アセンブリコードが何かを変更する場合は、「メモリ」クローバーを使用して、オプティマイザにすべてのレジスタ値をメモリに更新させ、asmステートメントの後に必要に応じてリロードします彼ら。
また、asm gotoステートメントは、常に暗黙的に揮発性ステートメントと見なされることに注意してください。
アセンブラテンプレートでラベルを引用するには、
その前に「%l」(小文字の「l」)を追加し、その(ゼロベースの)位置と入力オペランドをGotoLabelsに追加します。
たとえば、asmに3つの入力があり、2つのタグを参照する場合、最初のタグは「%l3」として参照され、2番目のタグは「%l4」として参照されます。
さらに、括弧内の実際のCタグ名を使用してタグを参照できます。たとえば、carryという名前のタグを引用するには、「%l [carry]」を使用できます。
この方法を使用する場合でも、ラベルはGotoLabelsセクションにリストされている必要があります。
以下は、i386のasm gotoの例です。asmgoto

"btl%1、%0 \ n \ t"
"jc%l2"
:/ *出力なし。* /
:「r」(p1)、「r」(p2)
:「cc」
:キャリー);

	return 0;

	carry:
	return 1;


下面的例子显示了一个使用内存clobber的asm goto。	
	int frob(int x)
	{
	  int y;
	  asm goto ("frob %%r5, %1; jc %l[error]; mov (%2), %%r5"
				: /* No outputs. */
				: "r"(x), "r"(&y)
				: "r5", "memory" 
				: error);
	  return y;
	error:
	  return -1;
	}

おすすめ

転載: blog.csdn.net/wzc18743083828/article/details/100543968