コンピュータプログラミングにおいて、インラインアセンブラとは、一部のコンパイラに搭載されている機能で、アセンブリ言語で書かれた低レベルのコードを、 CやAdaなどの高レベル言語からコンパイルされたコードの中に埋め込むことができるものです。
アセンブリ言語コードの埋め込みは、通常、次のいずれかの理由で行われます。[ 1 ]
一方、インラインアセンブラは、レジスタ割り当ての重要な部分である各変数に対して何が行われるかの分析を複雑にするため、コンパイラ自体に直接的な問題を引き起こします。これは、パフォーマンスが実際に低下する可能性があることを意味します。インラインアセンブラは、プログラムの将来の移植と保守も複雑にします。[ 1 ]
コンパイラとプログラマ双方の作業を簡素化するために、代替機能が提供されることがよくあります。ほとんどのコンパイラは特殊命令用の組み込み関数を提供しており、任意のシステムコール用のC関数ラッパーはすべてのUnixプラットフォームで利用可能です。
ISO C++規格およびISO C規格(附属書J)は、インラインアセンブラの条件付きサポート構文を規定している。
しかし、この定義は実際のC言語ではほとんど使われていません。なぜなら、解釈が自由すぎる一方で、使用できる文字列リテラルが1つだけという制限が厳しすぎるからです。
実際の使用においては、値を操作するインラインアセンブリは、独立したコードとして単独で使用されることは稀です。プログラマは変数がどのレジスタに割り当てられるかを予測できないため、コンパイラは通常、拡張機能として変数を代入する方法を提供します。
一般的に、C/C++コンパイラがサポートするインラインアセンブリには2種類あります。
2 つの拡張機能ファミリーは、インライン アセンブリの処理における分業の異なる理解を表しています。GCC 形式は、言語の全体的な構文を保持し、コンパイラが知る必要があること、つまり何が必要で何が変更されるかを区分けします。コンパイラは入力要件を処理するためにレジスタ割り当てといくつかのmov操作を置き換えるだけでよいので、命令名を明示的に理解する必要はありません。ただし、ユーザーは上書きレジスタを誤って指定する傾向があります。組み込みドメイン固有言語の MSVC 形式は記述の容易さを提供しますが、コンパイラ自体がオペコード名とその上書き特性について知っている必要があり、保守と移植において特別な注意が必要です。[ 6 ]命令セットの知識があれば、GCC スタイルのアセンブリの上書きミスをチェックすることは依然として可能です。[ 7 ]
GNAT (GCC スイートの Ada 言語フロントエンド) とLLVM はGCC 構文を使用します。[ 8 ] [ 9 ] D プログラミング言語は、公式には x86_64 用の MSVC 拡張機能に似た DSL を使用しますが、[ 10 ] LLVM ベースの LDC は、すべてのアーキテクチャで GCC スタイルの構文も提供します。[ 11 ] MSVC は 32 ビット x86 でのみインライン アセンブラをサポートします。[ 4 ]
Rust言語はその後、LLVM(GCCスタイル)バージョンよりもさらにインラインアセンブリオプションを抽象化した構文に移行しました。バックエンドが埋め込みアセンブリを処理できない場合でも、ブロックを外部アセンブリ関数に変換できるように十分な情報を提供します。[ 6 ]
保護メモリを使用するシステムでは、オペレーティングシステムを直接呼び出すことは一般的に不可能です。OSはユーザー(ユーザーモード)よりも高い特権レベル(カーネルモード)で動作するため、(ソフトウェア)割り込みを使用してオペレーティングシステムに要求を行います。これは高水準言語ではまれな機能であるため、システムコール用のラッパー関数はインラインアセンブラを使用して記述されます。
以下のCコード例は、GNUアセンブラを使用してAT&Tアセンブラ構文で記述されたx86システムコールラッパーを示しています。このような呼び出しは通常マクロを使用して記述されますが、ここでは分かりやすくするためにコード全体を掲載しています。この例では、ラッパーは呼び出し元から指定された数値と3つのオペランドを使用してシステムコールを実行し、結果を返します。[ 12 ]
要約すると、GCC は基本アセンブリと拡張アセンブリの両方をサポートしています。前者はテキストをそのままアセンブラに渡すだけですが、後者はレジスタ位置に対していくつかの置換を実行します。[ 3 ]
#include <errno.h>int syscall3 ( int num , int arg1 , int arg2 , int arg3 ) { int res = 0 ; asm ( "int $0x80" // OSに要求を行う: "=a" ( res ), // 結果をeax ("a")に返す"+b" ( arg1 ), // arg1をebx ("b")に渡す [システムコールによって変更される可能性があるため、"+"出力として] "+c" ( arg2 ), // arg2をecx ("c")に渡す [同上] "+d" ( arg3 ) // arg3をedx ("d")に渡す [同上] : "a" ( num ) // システムコール番号をeax ("a")に渡す: "memory" , "cc" , // メモリと条件コードが変更されたことをコンパイラに通知する"esi" , "edi" , "ebp" // これらのレジスタも上書きされる [システムコールによって変更される] );// オペレーティングシステムはエラー時に負の値を返します。// ラッパーはエラー時に -1 を返し、errno グローバル変数を設定します。if ( -125 <= res && res < 0 ) { errno = - res ; res = -1 ; } return res ; }これは、 Dプログラミング言語によるインラインアセンブリの例で、x86のFPU(x87 )命令を使用してxの正接を計算するコードを示しています。
// x の正接を計算しますreal tan ( real x ) { asm { fld x [ EBP ] ; // x をロードfxam ; // 奇数値をテストfstsw AX ; sahf ; jc trigerr ; // C0 = 1: x は NAN、無限大、または空// 387 は非正規化数を処理できますSC18 : fptan ; fstp ST ( 0 ) ; // 常に 1 である X をダンプfstsw AX ; sahf ; // if (!(fp_status & 0x20)) goto Lret jnp Lret ; // C2 = 1: x は範囲外なので、引数削減を実行fldpi ; // pi をロードfxch ; SC17 : fprem1 ; // メモ (部分) fstsw AX ; sahf ; jp SC17 ; // C2 = 1: 部分的なリマインダー、ループが必要fstp ST ( 1 ) ; // スタックから pi を削除jmp SC18 ; } trigerr : return real . nan ; Lret : // 値は既に FP スタックにあるため、手動で何も返す必要はありません; }fstsw -sahfに続いて条件付きジャンプを行うイディオムは、x87 FPU ステータスワードビット C0 と C2 にアクセスするために使用されます。fstswはステータスを汎用レジスタに格納し、sahf はFLAGS レジスタを上位 8 ビットに設定します。そして、ジャンプは、FPU ステータスビットに対応するフラグビットに基づいて判定を行うために使用されます。[ 13 ]
ただし、コンパイラ バックエンドのサポートなしでインライン アセンブリのサポートを実装するには、代わりに外部アセンブラを使用する方法があります。ステータス追跡のためのプルリクエスト
AX形式の命令は、主に条件分岐で使用されます...