インテル® C++ コンパイラー 18.0 デベロッパー・ガイドおよびリファレンス

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

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

インテル® C++ コンパイラーは、Windows* で Microsoft* スタイルのインライン・アセンブリーをサポートします。-use-msasm オプションを指定すると、インテル® C++ コンパイラーは、Linux* で Microsoft* スタイルのインライン・アセンブリーをサポートします。構文については、Microsoft* のマニュアルを参照してください。

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

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

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

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

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

構文要素

説明

asm-keyword

アセンブリー文は、キーワード asm で始まります。または、互換性のために __asm あるいは __asm__ が使用されることもあります。UNIX* スタイルと Microsoft* スタイルの asm が混在する場合は、__asm__ キーワードを使用してください。

コンパイラーは __asm__ キーワードのみを受け付けます。asm__asm キーワードは、Microsoft* スタイルのアセンブリー用に予約されています。

volatile-keyword

オプションの volatile キーワードが指定されたら、asm は volatile です。2 つの volatile asm 文は、お互いに移動しません。volatile 変数への参照は、volatile asm へ相対移動しません。または、互換性のために __volatile あるいは __volatile__ が使用されることもあります。

asm-template

asm-template は、アセンブリー・コードを出力する方法を指定する C 言語の ASCII 文字列です。テンプレートの多くは固定文字列です。代入ディレクティブ以外はすべて、アセンブリーにそのまま渡されます。代入ディレクティブの構文は、% の後に 1 または 2 文字続きます。

asm-interface

asm-interface は次の 3 つの部分で構成されます。

  1. output-list (オプション)
  2. input-list (オプション)
  3. clobber-list (オプション)

これらは、コロン (:) で区切ります。output-list がなく、input-list が指定される場合、output-list の代わりに、input-list は 2 つのコロン (::) の後に指定します。asm-interface がすべて省略された場合、volatile-keyword の指定の有無にかかわらず、asm 文は volatile とみなされます。

output-list output-list は、カンマで区切られた 1 つ以上の output-specs から構成されます。asm-template に代入するため、各 output-spec には番号が付けられます。output-list の最初のオペランドは 0 で、次は 1 のようになります。番号付けは、output-list から input-list へ続行します。オペランドの合計数は 0-29 個です。
input-list output-list と同様、input-list はカンマで区切られた 1 つ以上の input-specs から構成されます。asm-template に代入するため、各 input-spec には番号が付けられます。番号は、output-list のオペランドから続きます。
clobber-list clobber-list は、asm が特定のマシンレジスターを使用または変更することをコンパイラーに伝えます。特定のマシンレジスターは直接 asm にコードされるか、アセンブリーの命令によって暗黙的に変更されます。clobber-list は、カンマで区切られた clobber-specs のリストです。
input-spec input-specs は、挿入されたアセンブリーの命令によって必要とされる値の式をコンパイラーに伝えます。asm の必須入力をすべて示すのに、実際には asm-template に参照されない input-spec を一覧表示できます。
clobber-spec clobber-spec は、壊れた 1 つのマシンレジスター名を指定します。レジスター名は、オプションで先頭に % を使用できます。任意の有効なマシンレジスターを指定することができます。また、clobber-spec で "メモリー" を指定することもできます。これを指定すると、コンパイラーはレジスターにキャッシュされたデータを asm 文に渡さないようにします。

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

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

* 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* スタイルのインライン・アセンブリー文では、AT&T* アセンブリー構文と呼ばれる Linux* と同じアセンブリー命令形式が使用されます。これは、デスティネーション・オペランドが右側、ソースオペランドが左側であることを意味します。このオペランド順は、インテルのアセンブリー構文と逆です。

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

この機能は、Windows*、Linux*、macOS* 間での移植性が重要な場合に、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; 
}

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

  0x0000000000000000ffffffffffffffff 
+ 0x00000000000000410000000000000001 
------------------------------------ 
+ 0x00000000000000420000000000000000

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

mov       r13, 0xffffffffffffffff 
mov       r12, 0x000000000 
add       r13, 1 
adc       r12, 65

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

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

入力オペランド 2 と 3 の制御子は、オペランドにレジスターの場所 ("r")、メモリーの場所 ("m")、または符号付き 32 ビット整数値の定数 ("e") を割り当てることを許可しています。この例では、オペランド 2 と 3 が定数値 165 に一致するように選択され、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 個のメモリー参照になります。