インラインアセンブラ
インラインアセンブラ(英: inline assembler)は、主にC言語やC++などで書かれたプログラムの中にアセンブリ言語によるプログラムを埋め込むことができるようにする、コンパイラの機能である[1]。
概要
コンパイラの一機能を指しており、高水準言語で書かれたソースコードに埋めこまれたアセンブリ言語によるコードも機械語に変換する機能のこと。
この機能があれば、アセンブリ言語で書かれたコードをわざわざ別途アセンブラでアセンブル(機械語に変換)してリンクするという手間が省ける。特定のプロセッサが持つ特殊な拡張命令も使えるというメリットや、高速化が図れるなどのメリットがある[1]。
一方デメリットとして、機種に強く依存したアセンブリ言語のコードをソースコードに含んでしまうことになるため、C言語などの高級言語の強みであるソースレベル互換性が低下する。
インラインアセンブラを利用する目的には次のようなものがある。
- 最適化
- アルゴリズムで最も性能に影響する部分をアセンブリ言語に置き換える。これによりプログラマはコンパイラの制約を受けることなく自由に細工を施すことができる。
- プロセッサ固有の特殊な命令の利用
- コンペア・アンド・スワップやテスト・アンド・セットのような、セマフォやロックを実装するための命令があるプロセッサがあるが、それらの機能を言語拡張などではなく[2]、インラインアセンブラにより直接簡便に利用できる。他には、SIMD拡張命令など具体的にはSPARCのVIS、インテルのMMXやSSE、モトローラのAltivecといった命令はコンパイラからの有効的利用が難しく(研究はさかんに行われているが)、インラインアセンブラを利用してC言語中から直接利用することで高い性能を実現できることがある。
- システムコール
- システムコールのAPIは、現代では通常はC言語のライブラリとして定義されているが、上述の特殊な命令と同様にSVC命令などを直接利用して呼び出すためにアセンブリ言語が利用される。
最適化の例とプロセッサ固有命令の例
x86のFPUを利用して変数xのタンジェントを計算するD言語で記述されたインラインアセンブラの例。x86系プロセッサで利用可能な円周率の近似値を得るためのfldpi
命令を利用でき、コンパイラが浮動小数点を用いた場合より高速である。
// 変数xのタンジェントを計算する
real tan(real x)
{
asm
{
fld x[EBP] ; // xをロード
fxam ; // 不正な値の検査
fstsw AX ;
sahf ;
jc trigerr ; // xはNAN(非数)、無限、または空
// 387は非正規化数を扱えない
SC18: fptan ;
fstp ST(0) ; // dump X, which is always 1
fstsw AX ;
sahf ;
jnp Lret ; // C2 = 1 (xは範囲外)
// argument reductionしてxを有効範囲内に収める
fldpi ;
fxch ;
SC17: fprem1 ;
fstsw AX ;
sahf ;
jp SC17 ;
fstp ST(1) ; // piをスタックから除去
jmp SC18 ;
}
trigerr:
return real.nan;
Lret:
;
}
システムコールの例
メモリが保護されている環境でOSの機能を直接呼び出すことは一般的に不可能である。OSはユーザーモードより上位の特権モード(カーネルモード)で動作しており、OSにリクエストするためには(ソフトウェア)割り込みを利用する。これを行う機能をもつ高級言語はほとんどなく、システムコールのためのラッパー関数はインラインアセンブラを用いて記述されている。
下記のC言語によるサンプルにはシステムコールのラッパーも含まれる。一般的にはマクロと組み合わせて記述するが、ここでは説明のためにあえてマクロを利用していない。
基本的なインラインアセンブラの形式は非常に単純である。基本形は下記のとおり(GCCのみ、Visual Studio では __asm{ }の形式に直す必要がある)。
asm("アセンブリコード");
例:
asm("movl %ecx, %eax"); /* ecxの中身をeaxに移動する */
または
__asm__("movb %bh, (%eax)"); /* bhから1バイトをeaxが指し示すメモリに移動する */
asm
と__asm__
はいずれも正しい。もしasm
というキーワードがソースコード中のほかのキーワードと重複している場合は__asm__
を利用してもよい。
extern int errno;
int funcname(int arg1, int *arg2, int arg3)
{
int res;
__asm__ volatile(
"int $0x80" /* OSに命令を発行する */
: "=a" (res) /* eaxの中身("a")を結果として返す */
"+b" (arg1), /* arg1をebxに渡す ("b") */
"+c" (arg2), /* arg2をecxに渡す ("c") */
"+d" (arg3) /* arg3をedxに渡す ("d") */
: "a" (128) /* システムコール番号をeaxに渡す ("a") */
: "memory", "cc"); /* メモリと条件レジスタが修正されたことをコンパイラに通知する */
/* エラーが発生した場合、OSは負数を返す。
* エラーが発生するとラッパー関数は-1を返し、グローバル変数errnoに値をセットする */
if (-125 <= res && res < 0) {
errno = -res;
res = -1;
}
return res;
}
注
- ^ a b IT用語辞典バイナリ【インラインアセンブラ】[1]
- ^ GCCはそれらをサポートする組込み関数を持っている( https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html )