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

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

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

GNU スタイルのインライン・アセンブリー (IA-32 アーキテクチャーおよびインテル(R) 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 あるいは __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 へ続行します。オペランドの合計数は 30 個 (0 から 29) です。
input-list output-list と同様、input-list はカンマで区切られた 1 つ以上の input-specs から構成されます。asm-template に代入するため、各 input-spec には番号が付けられます。番号は、output-list のオペランドから続きます。
clobber-list clobber-listasm が特定のマシンレジスターを使用または変更することをコンパイラーに伝えます。特定のマシンレジスターは直接 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 アセンブラーのようなフル機能はサポートされていないため、asm-template の内容に関して制限があります。特に、次のアセンブラー機能は現在サポートされていないことに注意してください。

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

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

__asm__("addl $5, _x");

 

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

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

 

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

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

この機能は、Windows、Linux、Mac OS* 間での移植性が重要な場合に、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.lo64 = 1;

    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 オブジェクトとして表現されています。加算を実装するのに使用されるインライン・アセンブリー文は、3 つの 128 ビット整数値を表す、3 つの my_i128 引数をとる ADD128 マクロに含まれています。最初の引数は、出力です。次の 2 つの引数は入力です。Linux または Windows 上でインテル・コンパイラーを使用してこの例のコンパイルと実行を行うと、次の出力結果が生成されます。

  0x0000000000000000ffffffffffffffff

+ 0x00000000000000410000000000000001

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

+ 0x00000000000000420000000000000000

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

mov       r13, 0xffffffffffffffff

mov       r12, 0x000000000

add       r13, 1

adc       r12, 65

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

コンパイラーは、オペランド 4 の制御子に一致させるため 1.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 個のメモリー参照をとります。