インライン・アセンブリー

Microsoft* スタイルのインライン・アセンブリー

インテル(R) C++ コンパイラーは、-use-msasm オプションにより Microsoft スタイルのインライン・アセンブリーをサポートしています。構文については、Microsoft のマニュアルを参照してください。

GNU スタイルのインライン・アセンブリー (IA-32 アーキテクチャーおよびインテル(R) 64 アーキテクチャーのみ)

インテル(R) C++ コンパイラーは、GNU* スタイルのインライン・アセンブリーをサポートします。構文は、次のとおりです。

asm-keyword [ volatile-keyword ] ( asm-template [ asm-interface ] ) ;

インテル(R) C++ コンパイラーは、UNIX スタイルの asm と Microsoft スタイルの asm の混在もサポートしています。-use_msasm スイッチを使用する際は、GNU スタイルの ASM 用に __asm__ キーワードを使用してください。

Note icon

インテル(R) C++ コンパイラーは、アセンブラー・コードが AT&T* System V/386 構文を使用する場合、gcc スタイルのインライン・アセンブリーをサポートします。

Linux* では、アセンブリー文をコンパイルする際、コンパイラーは必要なオペランドの代入を行ってから、asm-template をアセンブリー・ファイルに出力します。その後、GNU アセンブラーを呼び出し、マシンコードを生成します。一方、Windows* では、コンパイラー自身で asm-template 文字列に含まれているテキストをマシンコードにアセンブルしなければなりません。コンパイラーには組み込みアセンブラーが含まれています。

コンパイラーの組み込みアセンブラーでは、GNU の .byte 宣言子はサポートされていますが、その他の GNU アセンブラー機能はサポートされていないため、asm-template の内容に関して制限があります。次のアセンブラー機能は現在サポートされていないことに注意してください。

Note icon

* asm-template におけるシンボルの直接参照はサポートされていません。C++ オブジェクトにアクセスするには、代入宣言子で asm-interface を使用します。

C++ オブジェクトへの誤ったアクセスの方法:

__asm__("addl $5, _x");

C++ オブジェクトへの正しいアクセスの方法:

__asm__("addl  $5, %0" : "+rm" (x));

さらに、ラベルの使用に関してもいくつかの制限があります。コンパイラーでは、ローカルラベルのみ使用することができます。また、同じアセンブリー文内のラベルのみ参照することができます。ローカルラベルの形式は “N:” です。N は負ではない整数値で、一意でなくてもかまいません。また、同じアセンブリー文内になくてもかまいません。ラベル N の最新の定義を参照するには、“Nb” を使用します。ラベル N の次の定義を参照するには、“Nf” を使用します。“b” はバックワード、“f” はフォワードを意味します。詳細は、GNU アセンブラーのドキュメントを参照してください。

Windows の GNU スタイルのインライン・アセンブリー文では、Linux と同じアセンブリー命令形式が使用されます。これは、デスティネーション・オペランドが右側、ソースオペランドが左側であることを意味します。このオペランド順は、インテルのアセンブリー構文と逆です。

コンパイラーの組み込みアセンブラーの制限により、Linux 上ではコンパイルされ、実行される多くのアセンブリー文が、Windows 上ではコンパイルされません。一方、Windows 上でコンパイルされ、実行されるアセンブリー文は、Linux 上でも実行することができます。

この機能は、Windows、Linux、Mac OS* X 間での移植性が重要な場合に、Microsoft スタイルのインライン・アセンブリー文にハイパフォーマンスな代替方法を提供します。この機能は、周囲の C++ コードとのハイパフォーマンスな統合が不可欠な小さなプリミティブにおける使用が目的とされています。

#ifdef _WIN64

#define INT64_PRINTF_FORMAT "I64"

#else

#define __int64 long long

#define INT64_PRINTF_FORMAT "L"

#endif

#include <stdio.h>

typedef struct {

__int64 lo64;

__int64 hi64;

} my_i128;

 

#define ADD128(out, in1, in2) \

__asm__("addq %2, %0; adcq %3, %1" : \

"=r"(out.lo64), "=r"(out.hi64) : \

"emr" (in2.lo64), "emr"(in2.hi64), \

"0" (in1.lo64), "1" (in1.hi64));

 

extern int

main()

{

my_i128 val1, val2, result;

val1.lo64 = ~0;

val1.hi64 = 0;

 

val2.hi64 = 65;

ADD128(result, val1, val2);

printf("0x%016" INT64_PRINTF_FORMAT "x%016" INT64_PRINTF_FORMAT "x\n",

val1.hi64, val1.lo64);

 

printf("+ 0x%016" INT64_PRINTF_FORMAT "x%016" INT64_PRINTF_FORMAT "x\n",

val2.hi64, val2.lo64);

 

printf("------------------------------------\n");

printf("0x%016" INT64_PRINTF_FORMAT "x%016" INT64_PRINTF_FORMAT "x\n",

result.hi64, result.lo64);

return 0;

}

 

インテル(R) 64 アーキテクチャー向けに記述された上記の例では、GNU スタイルのインライン・アセンブリー文を使用して、2 つの 128 ビット整数値を加算する方法を示しています。この例では、128 ビット整数値が my_i128 構造体の 2 つの __int64 オブジェクトとして表現されています。加算を実装するためのインライン・アセンブリー文は、ADD128 マクロに含まれています。このマクロでは、それぞれ 128 ビット整数値を表す、3 つの my_i128 引数を指定します。最初の引数は、出力です。次の 2 つの引数は入力です。Linux または Windows 上でインテル(R) コンパイラーを使用してこの例をコンパイルし、実行すると、次の出力結果が生成されます。

0x0000000000000000ffffffffffffffff

+ 0x00000000000000410000000000000001

------------------------------------

+ 0x00000000000000420000000000000000

 

GNU スタイルのインライン・アセンブリーの実装では、asm インターフェイスは asm 文のすべての入力、出力、および副作用を指定して、コンパイラーで非常に効率の良いコードを生成できるようにします。

mov r13, 0xffffffffffffffff

mov r12, 0x000000000

add r13, 1

adc r12, 65

 

Windows 上でコンパイラーによりアセンブリー・ファイルが生成される場合は、アセンブリー文が Linux アセンブリー 構文を使用して記述されていても、インテルの構文が使用されることに注目してください。

コンパイラーは、オペランド 4 の制御子に一致させるため in1.lo64 をレジスターに移動します。オペランド 4 の制御子 "0" は、出力オペランド 0 と同じ場所に割り当てなければならないことを示します。また、オペランド 0 の制御子は "=r" で、整数レジスターに割り当てられなければならないことを示します。この例では、コンパイラーは r13 を選択しています。同様に、コンパイラーは in1.hi64 をレジスター r12 に移動します。

入力オペランド 2 と 3 の制御子は、オペランドにレジスターの場所 ("r")、メモリーの場所 ("m")、または符号付き 32 ビット整数値の定数 ("e") を割り当てることを許可しています。この例では、オペランド 2 と 3 が定数値 1 と 65 に一致するように選択され、add 命令と adc 命令により "レジスター - 即値" 形式を利用できるようにしています。

この操作を Microsoft スタイルのインライン・アセンブリー文を使用して行う場合は、アセンブリー文と周囲の C++ コード間とのインターフェイスがすべてメモリーを通して行われるため、大幅に負荷がかかります。Microsoft アセンブリーを使用する場合、ADD128 マクロは次のように記述します。

#define ADD128(out, in1, in2) \

{ \

__asm mov rax, in1.lo64 \

__asm mov rdx, in1.hi64 \

__asm add rax, in2.lo64 \

__asm adc rdx, in2.hi64 \

__asm mov out.lo64, rax \

__asm mov out.hi64, rdx \

}

 

コンパイラーは、アセンブリー文の前に入力値をメモリーに移動するコードを追加し、アセンブリー文の後にメモリーから出力値を取得するコードを追加する必要があります。これにより、コンパイラーが一部の最適化処理を行うことを防ぎ、次のアセンブリー・コードが生成されます。

mov QWORD PTR [rsp+32], -1

mov QWORD PTR [rsp+40], 0

mov QWORD PTR [rsp+48], 1

mov QWORD PTR [rsp+56], 65

 

; Begin ASM

 

mov rax, QWORD PTR [rsp+32]

mov rdx, QWORD PTR [rsp+40]

add rax, QWORD PTR [rsp+48]

adc rdx, QWORD PTR [rsp+56]

mov QWORD PTR [rsp+64], rax

mov QWORD PTR [rsp+72], rdx

 

; End ASM

 

mov rdx, QWORD PTR [rsp+72]

mov r8, QWORD PTR [rsp+64]

 

GNU スタイルのインライン・アセンブリーでは 4 個の命令と 0 個のメモリー参照の演算が、Microsoft スタイルのインライン・アセンブリーでは、12 個の命令と 12 個のメモリー参照になります。