私が知る限り、___asm { ... };
_と__asm__("...");
の唯一の違いは、最初のものが_mov eax, var
_を使用し、2番目が_movl %0, %%eax
_を:"=r" (var)
とともに使用することです。終わり。他にどんな違いがありますか?そして、asm
はどうですか?
どちらを使用するかは、コンパイラによって異なります。これはC言語のような標準ではありません。
MSVCインラインasmとGNU Cインラインasmの間には大きな違いがあります。GCC構文は、単一の命令などをラップするために、無駄な命令なしで最適な出力が得られるように設計されています。MSVC構文はかなりシンプルになるように設計されています、しかし、AFAICTは、入力と出力のために、メモリを介した往復の待ち時間と追加の指示なしに使用することは不可能です。
パフォーマンス上の理由からインラインasmを使用している場合、これはMSVCインラインasmを実行可能にします。これは、ループ全体をasmで記述した場合にのみ実行可能です。インライン関数で短いシーケンスをラップするためではありません。以下の例(idiv
を関数でラップする)は、MSVCが得意としない種類のものです:〜8の追加のストア/ロード命令。
MSVCインラインasm(MSVCおよびおそらくiccで使用され、一部の商用コンパイラでも使用可能):
mov ecx, shift_count
_の準備のためにコンパイラーによって保管されます。そのため、コンパイラーが生成しない単一のasm命令を使用するには、出入りの途中でメモリーを往復する必要があります。GNU Cインラインasm は、asm を学ぶ良い方法ではありません。コードについてコンパイラーに伝えるために、asmを十分に理解する必要があります。また、コンパイラが知っておくべきことを理解する必要があります。その回答には、他のインラインasmガイドとQ&Aへのリンクもあります。 x86 タグwikiには、一般的にasmに役立つものがたくさんありますが、GNU inline asmにリンクしているだけです。その答えはGNUインラインasmにも適用されます。)
GNU Cインラインasm構文は、gcc、clang、icc、およびGNU C:
"c" (shift_count)
は、インラインasmが実行される前に、コンパイラに_shift_count
_変数をecx
に入れさせます。asmは文字列定数の内部にある必要があるため、コードの大きなブロックでは余分に扱いにくいです。したがって、通常は
_"insn %[inputvar], %%reg\n\t" // comment
"insn2 %%reg, %[outputvar]\n\t"
_
非常に寛容/より困難ですが、特にオーバーヘッドを低くすることができます。単一の命令をラップするため。 (単一の命令をラップすることが元々の設計意図でした。そのため、問題が発生した場合に、入力と出力に同じレジスタを使用しないように、初期のクローバーについてコンパイラに特別に通知する必要があります。)
div
)32ビットCPUでは、64ビット整数を32ビット整数で除算するか、完全乗算(32x32-> 64)を行うと、インラインasmのメリットが得られます。 gccとclangは_(int64_t)a / (int32_t)b
_のidiv
を利用していません。おそらく、結果が32ビットレジスタに収まらないと命令が失敗するためです。 1つのdiv
から商と剰余を取得することに関するこのQ&Aとは異なり、これはインラインasmの使用例です。 (結果が収まるようにコンパイラーに通知する方法がない限り、idivは失敗しません。)
レジスタにいくつかの引数を配置する呼び出し規約を使用して( right レジスタでもhi
を使用)、見た目により近い状況を示しますこのような小さな関数をインライン化するとき。
Inline-asmを使用する場合は、register-arg呼び出し規約に注意してください。インラインasmのサポートが不適切に設計/実装されているため、 インラインasmで引数が使用されていない場合、コンパイラはインラインasmの周囲の引数レジスタを保存/復元しない可能性があります 。これを指摘してくれた@RossRidgeに感謝します。
_// MSVC. Be careful with _vectorcall & inline-asm: see above
// we could return a struct, but that would complicate things
int _vectorcall div64(int hi, int lo, int divisor, int *premainder) {
int quotient, tmp;
__asm {
mov edx, hi;
mov eax, lo;
idiv divisor
mov quotient, eax
mov tmp, edx;
// mov ecx, premainder // Or this I guess?
// mov [ecx], edx
}
*premainder = tmp;
return quotient; // or omit the return with a value in eax
}
_
更新:明らかにeax
または_edx:eax
_に値を残してから、非void関数(return
なし)の終わりから落ちる インライン化されている場合でもサポートされています。これが機能するのは、asm
ステートメントの後にコードがない場合のみです。 Does __asm {}を参照してください。 eax? の値を返します。これにより、出力(少なくともquotient
の場合)のストア/リロードが回避されますが、入力については何もできません。スタック引数を持つ非インライン関数では、それらはすでにメモリ内にありますが、このユースケースでは、便利にインライン化できる小さな関数を書いています。
MSVC 19.00.23026でコンパイルされた_/O2
_ on rextester (exeのディレクトリを見つけるmain()
および は、コンパイラのasm出力をstdout にダンプします。
_## My added comments use. ##
; ... define some symbolic constants for stack offsets of parameters
; 48 : int ABI div64(int hi, int lo, int divisor, int *premainder) {
sub esp, 16 ; 00000010H
mov DWORD PTR _lo$[esp+16], edx ## these symbolic constants match up with the names of the stack args and locals
mov DWORD PTR _hi$[esp+16], ecx
## start of __asm {
mov edx, DWORD PTR _hi$[esp+16]
mov eax, DWORD PTR _lo$[esp+16]
idiv DWORD PTR _divisor$[esp+12]
mov DWORD PTR _quotient$[esp+16], eax ## store to a local temporary, not *premainder
mov DWORD PTR _tmp$[esp+16], edx
## end of __asm block
mov ecx, DWORD PTR _premainder$[esp+12]
mov eax, DWORD PTR _tmp$[esp+16]
mov DWORD PTR [ecx], eax ## I guess we should have done this inside the inline asm so this would suck slightly less
mov eax, DWORD PTR _quotient$[esp+16] ## but this one is unavoidable
add esp, 16 ; 00000010H
ret 8
_
余分なmov命令がたくさんあり、コンパイラーはそのいずれかを最適化することすらしません。インラインasm内の_mov tmp, edx
_を見て理解し、そのストアをpremainder
にすることを考えていました。ただし、インラインasmブロックの前にスタックからレジスタにpremainder
をロードする必要があると思います。
この関数は実際には、通常のスタック上のすべてのABIよりも__vectorcall
_の方が worse です。レジスタに2つの入力があると、インラインasmが名前付き変数からそれらをロードできるように、それらをメモリに格納します。これがインライン化された場合、さらに多くのパラメーターが正規表現に含まれる可能性があり、それらをすべて格納する必要があるため、asmにメモリオペランドが含まれます。したがって、gccとは異なり、これをインライン化することで得られることはあまりありません。
Asmブロック内で_*premainder = tmp
_を実行すると、asmで記述されたより多くのコードを意味しますが、残りの部分の完全に無駄なストア/ロード/ストアパスは回避されます。これにより、命令数が合計2から11に減ります(ret
は含まれません)。
私はMSVCから可能な限り最高のコードを取得しようとしています。「間違って使用する」のではなく、ストローマン的な議論を作成しています。しかしAFAICTは、非常に短いシーケンスをラップすることは恐ろしいことです。 おそらく64/32-> 32除算用の組み込み関数があり、コンパイラーがこの特定のケースに適したコードを生成できるようにするため、MSVCでこれにインラインasmを使用するという前提はすべて、ストローマン引数。しかし、それは組み込み関数がMSVCのインラインasmよりもずっと優れていることを示しています。
Gccは、div64をインライン化するときに、ここに示す出力よりも優れています。これは、通常、前のコードが最初にedx:eaxで64ビット整数を生成するように調整できるためです。
32ビットのvectorcall ABI用にgccをコンパイルできません。 Clangはできますが、_"rm"
_制約を使用してインラインasmを吸います(godboltリンクで試してください:制約でレジスタオプションを使用する代わりに、メモリを介して関数argをバウンスします)。 64ビットMS呼び出し規約は32ビットvectorcallに近く、最初の2つのパラメーターはedx、ecxにあります。違いは、スタックを使用する前に、さらに2つのパラメーターがregsに入れられることです(呼び出し先はスタックから引数をポップしません。これは、MSVC出力の_ret 8
_のことです)。
_// GNU C
// change everything to int64_t to do 128b/64b -> 64b division
// MSVC doesn't do x86-64 inline asm, so we'll use 32bit to be comparable
int div64(int lo, int hi, int *premainder, int divisor) {
int quotient, rem;
asm ("idivl %[divsrc]"
: "=a" (quotient), "=d" (rem) // a means eax, d means edx
: "d" (hi), "a" (lo),
[divsrc] "rm" (divisor) // Could have just used %0 instead of naming divsrc
// note the "rm" to allow the src to be in a register or not, whatever gcc chooses.
// "rmi" would also allow an immediate, but unlike adc, idiv doesn't have an immediate form
: // no clobbers
);
*premainder = rem;
return quotient;
}
_
_gcc -m64 -O3 -mabi=ms -fverbose-asm
_ でコンパイル。 -m32を使用すると、3つのロード、idiv、およびストアが取得されます。これは、そのgodboltリンクの変更内容からわかるようにです。
_mov eax, ecx # lo, lo
idivl r9d # divisor
mov DWORD PTR [r8], edx # *premainder_7(D), rem
ret
_
32ビットvectorcallの場合、gccは次のようになります。
_## Not real compiler output, but probably similar to what you'd get
mov eax, ecx # lo, lo
mov ecx, [esp+12] # premainder
idivl [esp+16] # divisor
mov DWORD PTR [ecx], edx # *premainder_7(D), rem
ret 8
_
MSVCは、gccの4と比較して、13の命令(retを含まない)を使用します。インライン化を使用すると、前述のように1つにコンパイルされる可能性がありますが、MSVCはおそらく9を使用します(スタックスペースやロードを予約する必要はありません) premainder
; 3つの入力のうち約2つを保存する必要があると想定しています。次に、それらをasm内に再読み込みし、idiv
を実行し、2つの出力を保存し、asmの外部に再読み込みします。つまり、入力用に4つのロード/ストアがあり、出力用にもう4つあります。)
Gccコンパイラーでは、大きな違いはありません。 asm
または__asm
または__asm__
も同じです。名前空間の目的の競合を避けるために使用します(asmという名前のユーザー定義関数があります)。