コンピュータプログラミングにおいて、インラインアセンブラは、一部のコンパイラの機能であり、アセンブリ言語で記述された低レベルのコードを、 CやAdaなどの高レベル言語からコンパイルされたコードの中にプログラム内に埋め込むことができます。
動機と代替案
アセンブリ言語コードの埋め込みは、通常、次のいずれかの理由で行われます。[1]
- 最適化: プログラマーは、アセンブリ言語コードを使用して、プログラムのアルゴリズムの中で最もパフォーマンスに敏感な部分を実装できます。このコードは、コンパイラによって生成されるコードよりも効率的になる傾向があります。
- プロセッサ固有の命令へのアクセス: ほとんどのプロセッサは、セマフォやその他の同期およびロック プリミティブの構築に使用できる、Compare and Swap命令やTest and Set命令などの特別な命令を提供しています。これらの命令や類似の命令は、マルチタスクの実装に必要であるため、ほぼすべての最新のプロセッサに備わっています。特殊な命令の例としては、 SPARC VIS、Intel MMXおよびSSE、Motorola Altivec命令セットなどがあります。
- コンパイラでまだサポートされていない特殊な呼び出し規約へのアクセス。
- システム コールと割り込み: 高級言語には、任意のシステム コールを直接行う機能がほとんどないため、アセンブリ コードが使用されます。直接的な割り込みが提供されることもほとんどありません。
- セクションやマクロを変更したり、シンボルのエイリアスを作成したりするために、リンカーまたはアセンブラに特別なディレクティブを発行します。
一方、インラインアセンブラは、レジスタ割り当ての重要な部分である各変数に対して何が行われたかの分析を複雑にするため、コンパイラ自体に直接的な問題を引き起こします。[2]これは、パフォーマンスが実際に低下する可能性があることを意味します。インラインアセンブラは、プログラムの将来の移植とメンテナンスも複雑にします。[1]
コンパイラとプログラマの両方の作業を簡素化する方法として、代替機能が提供されることがよくあります。特殊な命令用の組み込み関数はほとんどのコンパイラによって提供されており、任意のシステム コール用の C 関数ラッパーはすべてのUnixプラットフォームで利用できます。
構文
言語基準では
ISO C++ 標準および ISO C 標準 (付録 J) では、インライン アセンブラに対して条件付きでサポートされる構文が指定されています。
asm宣言の形式は
asm宣言:
asm (文字列リテラル);です
。asm宣言は条件付きでサポートされており、その意味は実装定義です。[3]
ただし、この定義は、解釈が自由すぎると同時に、文字列リテラルを 1 つしか使用できないという制限が厳しすぎるため、実際の C ではほとんど使用されません。
実際のコンパイラでは
実際の使用では、値を操作するインライン アセンブリが、自由浮動コードとして単独で使用されることはほとんどありません。プログラマーは変数がどのレジスタに割り当てられるかを予測できないため、コンパイラーは通常、拡張機能としてそれらを置き換える方法を提供します。
一般に、C/C++ コンパイラでサポートされるインライン アセンブリには 2 種類あります。
- GCCではasm (または__asm__ ) を使用します。GCC は ISO 規則を直接拡張しています。アセンブリ コード テンプレートは文字列で記述され、入力、出力、および上書きされたレジスタは文字列の後にコロンで囲んで指定します。C 変数は直接使用されますが、レジスタ名は文字列リテラルとして引用されます。[4]
- __asmは Microsoft Visual C++ (MSVC)、Borland/Embarcadero C コンパイラ、およびその派生コンパイラで使用されています。この構文は ISO 規則にまったく基づいていません。プログラマーは C 構文に準拠する必要なく、ブロック内に ASM を記述するだけです。変数はレジスタのように使用でき、一部の C 式も使用できます。[5] ARM コンパイラにも同様の機能がありました。[6]
2 つの拡張機能ファミリーは、インライン アセンブリの処理における分業の異なる理解を表しています。GCC 形式は、言語の全体的な構文を保持し、コンパイラが知る必要のあること、つまり必要なものと変更されるものを区分化します。コンパイラが命令名を理解することを明示的に要求するわけではありません。コンパイラは、入力要件を処理するために、レジスタ割り当てといくつかのmov操作を置き換えるだけでよいからです。ただし、ユーザーは、上書きされるレジスタを誤って指定する傾向があります。組み込みドメイン固有言語の MSVC 形式は、記述が容易ですが、コンパイラ自体がオペコード名とその上書き特性について知る必要があり、メンテナンスと移植に特別な注意が必要です。[7]命令セットの知識があれば、GCC スタイルのアセンブリで上書きミスをチェックすることは可能です。[8]
GNAT(GCCスイートのAda言語フロントエンド)とLLVMはGCC構文を使用します。[9] [10] Dプログラミング言語は、 x86_64用に公式にMSVC拡張に似たDSLを使用しますが、[11] LLVMベースのLDCもすべてのアーキテクチャでGCCスタイルの構文を提供します。[12] MSVCは32ビットx86でのみインラインアセンブラをサポートします。[5]
Rust言語はその後、LLVM(GCCスタイル)バージョンよりもさらにインラインアセンブリオプションを抽象化する構文に移行しました。バックエンドが埋め込みアセンブリを処理できない場合、ブロックを外部でアセンブルされた関数に変換できるように十分な情報が提供されます。[7]
例
GCCのシステムコール
保護されたメモリを使用するシステムでは、通常、オペレーティング システムを直接呼び出すことはできません。OS は、ユーザー (ユーザー モード) よりも高い特権レベル (カーネル モード) で実行され、(ソフトウェア)割り込みを使用してオペレーティング システムに要求が送信されます。これは高級言語の機能としてはほとんどないため、システム コールのラッパー関数はインライン アセンブラを使用して記述されます。
次の C コード例は、 GNU アセンブラを使用したAT&T アセンブラ構文の x86 システム コール ラッパーを示しています。このような呼び出しは通常、マクロを使用して記述されます。わかりやすいように、完全なコードが含まれています。この特定のケースでは、ラッパーは呼び出し元によって指定された番号のシステム コールを 3 つのオペランドで実行し、結果を返します。[13]
要約すると、GCCは基本アセンブリと拡張アセンブリの両方をサポートしています。前者はテキストをそのままアセンブラに渡すだけですが、後者はレジスタ位置の置換を実行します。[4]
外部intエラー番号;
int syscall3 ( int num 、int arg1 、int arg2 、int arg3 ) { int res ; __asm__ ( "int $0x80" /* OS に要求を行う */ : "=a" ( res ), /* eax ("a") で結果を返す */ "+b" ( arg1 ), /* ebx ("b") で arg1 を渡す [システムコールによって変更される可能性があるため、"+" 出力として] */ "+c" ( arg2 ), /* ecx ("c") で arg2 を渡す [同上] */ "+d" ( arg3 ) /* edx ("d") で arg3 を渡す [同上] */ : "a" ( num ) /* eax ("a") でシステムコール番号を渡す */ : "memory" , "cc" , /* メモリと条件コードが変更されたことをコンパイラーに通知する */ "esi" , "edi" , "ebp" ); /* これらのレジスタも上書きされます [システムコールによって変更されます] */
/* オペレーティング システムはエラー時に負の値を返します。
* ラッパーはエラー時に -1 を返し、errno グローバル変数を設定します。 */
if ( -125 <= res && res < 0 ) { errno = - res ; res = -1 ; } return res ; }
Dにおけるプロセッサ固有の命令
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 ) ; // X をダンプします。これは常に 1 です。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ステータスビットに対応するフラグビットを判断するために使用されます。[14]
参考文献
- ^ ab "DontUseInlineAsm". GCC Wiki . 2020年1月21日閲覧。
- ^ Striegel, Ben (2020年1月13日). 「コンパイラにとって、インラインアセンブリの塊は平手打ちのようなものだ。」. Reddit . 2020年1月15日閲覧。
- ^ C++, [dcl.asm]
- ^ ab 「Extended Asm - C 式オペランドを使用したアセンブラー命令」。GNU C コンパイラの使用。2020年1 月 15 日閲覧。
- ^ ab "インライン アセンブラー". docs.microsoft.com . 2021 年 8 月 3 日。
- ^ 「移行および互換性ガイド: Arm Compiler 6 を使用したインライン アセンブリ」。
- ^ ab d'Antras、Amanieu (2019 年 12 月 13 日)。「Rust RFC-2873: stable inline asm」。2020 年1 月 15 日閲覧。
ただし、代わりに外部アセンブラを使用することで、コンパイラ バックエンドからのサポートなしでインライン アセンブリのサポートを実装することは可能です。
ステータス追跡のためのプルリクエスト - ^ 「⚙ D54891 [RFC] インラインアセンブリの有効性チェック」。reviews.llvm.org。
- ^ 「LLVM 言語リファレンス: インラインアセンブリ式」。LLVMドキュメント。2020年1 月 15 日閲覧。
- ^ 「インラインアセンブリ」。Rustドキュメント (1.0.0) 。2020年1 月 15 日閲覧。
- ^ 「インラインアセンブラ」。Dプログラミング言語。 2020年1月15日閲覧。
- ^ 「LDC インラインアセンブリ式」。D Wiki。2020年1 月 15 日閲覧。
- ^ – Linuxプログラマーズマニュアル– システムコール
- ^ 「FSTSW/FNSTSW — x87 FPU ステータス ワードの保存」。
命令の FNSTSW AX 形式は、主に条件分岐で使用されます...
外部リンク
- GCC インラインアセンブリ HOWTO
- Clang インラインアセンブリ
- GNAT インラインアセンブラ
- GCC インライン アセンブラ リファレンス
- コンパイラエクスプローラ
