共通中間言語
共通中間言語(きょうつうちゅうかんげんご、Common Intermediate Language、略称 : CIL())は、共通言語基盤 (Common Language Infrastructure、CLI) において定義されている、人間が解読可能な最も低水準なプログラミング言語である。これは.NET FrameworkやMonoにより使用される。CLI互換な実行環境をターゲットとしている言語はCILにコンパイルされる。そのCILはバイトコードスタイルフォーマットであるオブジェクトコードにより組み立てられている。CILはオブジェクト指向なアセンブリ言語であり、完全なスタックベースである。そのバイトコードはネイティブコードに翻訳されるか、仮想機械により実行される。
.NET言語がベータリリースである間、CILはMicrosoft Intermediate Language (MSIL) と呼ばれていた。C#およびCLIの標準化により、現在ではバイトコードは公式にCILと呼ばれる。
CLIプログラミング言語のコンパイルにより、ソースコードはプラットフォームやプロセッサ固有のオブジェクトコードではなく、CILコードに翻訳される。CILはCPUやプラットフォームに依存しない命令セットである。従って、CILはWindows上の.NETランタイムやクロスプラットフォームなMonoランタイムのようなCLIをサポートするどんな環境でも動作する。この性質により、理論的にはプラットフォームやCPUの種類に応じて異なる実行可能ファイルを配布する必要がなくなる。CILコードは安全のため実行時に検証され、ネイティブにコンパイルされた実行可能ファイルよりも優れた安全性と信頼性を提供する。
実行プロセスは以下のようなものである。
ソースコードはCILに変換される。CILは、CLIにとってはCPUに対するアセンブリと等価なものである。
さらに、CILはいわゆるバイトコードの形に組み立てられ、アセンブリが生成される。
CLIアセンブリの実行中、ネイティブコードを生成するためランタイムのJITコンパイラにコードが渡される。このステップを省き事前コンパイルも利用できるが、実行可能ファイルの移植性が犠牲となる。
ネイティブコードがコンピュータのプロセッサで実行される。
CILバイトコードは以下のタスクのグループに分けられる命令である。
CILはオブジェクト指向かつスタックベースである。これは、たいていのプログラミング言語同様、命令のパラメータと結果が、いくつかのレジスタや他のメモリ領域に保持されるのではなく、単一のスタック上に保持されることを意味する。
x86における加算命令のアセンブリコード例を挙げる。
ここで、eaxとedxは汎用レジスタであり、上記はeaxにedxの内容を加算代入している。
これに相当する中間言語 (IL) のコードは以下のように表せる。
<pre>
ldloc.0
ldloc.1
stloc.0    // a = a + b または a += b;
</pre>
ここで、スタック上に2つのローカル変数がプッシュされる。加算命令が呼び出された際にオペランドがポップされ結果がプッシュされる。残った値はその後ポップされ最初のローカル変数にストアされる。
CILは同様にオブジェクト指向概念に拡張される。オブジェクトを作成したり、メソッドを呼び出したり、そしてフィールドのような他の型のメンバーを使用したりできる。
CILはオブジェクト指向に設計され、各メソッドは(いくつかの例外を除き)クラスに属する必要がある。これは静的メソッドにもあてはまる。
<pre>
.class public Foo
{
.method public static int32 Add(int32, int32) cil managed
{
.maxstack 2
ldarg.0 // 1つ目の引数をロード;
ldarg.1 // 2つ目の引数をロード;
add     // それらを加算;
ret     // 結果を戻す;
}
}
</pre>
このメソッドは、Fooのインスタンスを宣言することを要求しない。なぜならそれは静的だからである。このことは、メソッドがクラスに属し、C#では以下のように利用されることを意味する。
CILにおいては、以下のようになる。
<pre>
ldc.i4.2
ldc.i4.3
call int32 Foo::Add(int32, int32)
stloc.0
</pre>
インスタンスクラスには、最低でも1つのコンストラクタと、いくつかのインスタンスメンバーが含まれる。以下のクラスはCarオブジェクトの振る舞いを表すメソッドのセットである。
<pre>
.class public Car
{
.method public specialname rtspecialname instance void .ctor(int32, int32) cil managed
{
/* コンストラクタ */
}
.method public void Move(int32) cil managed
{
/* 実装は省略 */
}
.method public void TurnRight() cil managed
{
/* 実装は省略 */
}
.method public void TurnLeft() cil managed
{
/* 実装は省略 */
}
.method public void Brake() cil managed
{
/* 実装は省略 */
}
}
</pre>
C#クラスインスタンスは以下のようにして作成される。
上記のステートメントは大体以下のような命令と同じである。
<pre>
ldc.i4.1
ldc.i4.4
newobj instance void Car::.ctor(int, int)
stloc.0    // myCar = new Car(1, 4);
ldc.i4.1
ldc.i4.3
newobj instance void Car::.ctor(int, int)
stloc.1    // yourCar = new Car(1, 3);
</pre>
インスタンスメソッドは以下のように呼び出される。
CILにおいては、以下のようになる。
<pre>
ldloc.0    // "myCar"オブジェクトをスタックにロード
ldc.i4.3
call instance void Car::Move(int32)
</pre>
CLIはコンパイルされたクラスについての情報をメタデータとして記録する。Component Object Modelのタイプライブラリのように、メタデータによってアプリケーションが、アセンブリ内にあるインターフェイス、クラス、型、メソッド、そしてフィールドをサポートし発見することを可能とする。このようなメタデータを読み取る処理はリフレクションと呼ばれる。
メタデータは属性 の形式のデータである。カスタム属性はAttributeクラスを継承することで作ることができる。これはとても強力な機能である。クラスの作成者が付加した追加情報は、クラスの消費者によってアプリケーション毎に様々な方法で活用される。
以下はCILで書かれた基本的なHello worldプログラムであり、文字列 "Hello, world!" をコンソールに表示する。
<pre>
.assembly Hello {}
.assembly extern mscorlib {}
.method static void Main()
{
.entrypoint
.maxstack 1
ldstr "Hello, world!"
call void [mscorlib]System.Console::WriteLine(string)
}
</pre>
以下のコードはオペコードの数をより複雑にしたものである。
以下のコードはJavaバイトコードについての記事の当該コードと比較することもできる。
CILシンタックスでは、以下のようになる。
<pre>
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack  2
.locals init (int32 V_0,
int32 V_1)
ldc.i4.2
stloc.0
br.s       IL_001f
IL_0004:  ldc.i4.2
stloc.1
br.s       IL_0011
IL_0008:  ldloc.0
ldloc.1
brfalse.s  IL_001b
ldloc.1
ldc.i4.1
stloc.1
IL_0011:  ldloc.1
ldloc.0
blt.s      IL_0008
ldloc.0
call       void [mscorlib]System.Console::WriteLine(int32)
IL_001b:  ldloc.0
ldc.i4.1
stloc.0
IL_001f:  ldloc.0
ldc.i4     0x3e8
blt.s      IL_0004
}
</pre>
これはVMレベル近くでどのようにCILが見えるかを表現したものである。コンパイルされた場合、メソッドはテーブルにストアされ、アセンブリ内部にバイトとして命令がストアされる。そしてそれはPortable Executable (PE) である。
CILアセンブリおよび命令は、コンパイラと、実行環境と共に送られるIL アセンブラー (ILASM) と呼ばれるユーティリティのどちらかで生成される。
アセンブルされたILはIL 逆アセンブラー (ILDASM) を使用して再びコードへと逆アセンブルすることもできる。高水準言語(例えばC#やVisual Basic)へと逆コンパイルする.NET Reflectorのような他のツールもある。これにより、ILはリバースエンジニアリングのとても容易なターゲットとなる。この特徴はJavaバイトコードと共通である。しかしながら、コードを難読化するツールもあり、そうすることによりコードが容易に読めなくなるが実行はできるようになる。
実行時コンパイルによりバイトコードは、CPUにより即座に実行可能なコードへと変換される。この変換はプログラムの実行中、徐々に実行される。実行時コンパイルは環境固有の最適化、実行時型安全性、そしてアセンブリ検証を提供する。これを達成するため、実行時コンパイラは、任意の不正アクセスに対してアセンブリメタデータを調査し、違反を適切に処理する。
CLI互換な実行環境には、実行時のJIT処理を省いてより高速に実行できるようにするため、アセンブリの事前コンパイルを処理するためのオプションがある。
.NET Frameworkには、事前コンパイルを行うネイティブ イメージ ジェネレーター (Native Image Generator、NGEN) と呼ばれる特殊なツールがある。Monoにも、事前コンパイルを処理するためのオプションがある。