共通中間言語(CIL)とは
共通中間言語(Common Intermediate Language、CIL)は、
共通言語基盤(Common Language Infrastructure、CLI)で定義される、人間が理解可能な最も低水準の
プログラミング言語です。主に
.NET FrameworkやMono環境で使用され、CLI互換の実行環境を対象とする言語はCILにコンパイルされます。CILは
バイトコード形式のオブジェクトコードで構成され、オブジェクト指向の
アセンブリ言語であり、完全なスタックベースで動作します。この
バイトコードは、最終的にネイティブコードに変換されるか、仮想マシン上で実行されます。
かつて
.NET言語がベータ版であった頃、CILはMicrosoft Intermediate Language(MSIL)と呼ばれていました。しかし、C#とCLIの標準化に伴い、現在では公式にCILという名称が用いられています。
CILの概要
CLI
プログラミング言語のコンパイルプロセスでは、ソースコードは特定のプラットフォームやプロセッサに依存するオブジェクトコードではなく、CILコードに変換されます。CILは
CPUやプラットフォームに依存しない命令セットであるため、Windows上の
.NETランタイムや
クロスプラットフォームなMonoランタイムなど、CLIをサポートするあらゆる環境で動作可能です。
この特性により、理論的にはプラットフォームや
CPUの種類ごとに異なる実行ファイルを配布する必要がなくなります。また、CILコードは実行時に安全性が検証されるため、ネイティブにコンパイルされた実行可能ファイルよりも高い安全性と信頼性を提供します。
実行プロセスは以下の通りです。
1. ソースコードはCILに変換されます。CILはCLIにとって、
CPUに対する
アセンブリ言語のような役割を果たします。
2. CILは
バイトコードの形に組み立てられ、アセンブリが生成されます。
3. CLIアセンブリの実行時、ランタイムのJITコンパイラによってCILコードがネイティブコードに変換されます。事前コンパイルも可能ですが、移植性が損なわれる可能性があります。
4. 生成されたネイティブコードが、コンピュータのプロセッサ上で実行されます。
CILの命令
CIL
バイトコードは、以下のタスクグループに分類される命令で構成されています。
ロードとストア
算術演算
型変換
オブジェクトの作成と操作
評価スタック管理(push/pop)
制御転送(分岐)
メソッド呼び出しと復帰
例外処理
モニタベースの同時実行制御
CILの計算モデル
CILはオブジェクト指向であり、スタックベースで動作します。これは、命令のパラメータと結果が、レジスタや他のメモリ領域ではなく、単一のスタック上に保持されることを意味します。例えば、x86アーキテクチャでの加算命令をCILで表現すると以下のようになります。
// x86アセンブリコード例
add eax, edx // eax = eax + edx
// CILコード例
ldloc.0 // スタックにローカル変数0をロード
ldloc.1 // スタックにローカル変数1をロード
add // スタック上の2つの値を加算し結果をプッシュ
stloc.0 // スタックから値をポップしローカル変数0に格納
オブジェクト指向の概念
CILはオブジェクト指向の概念をサポートしており、オブジェクトの作成、メソッドの呼び出し、フィールドへのアクセスなどが可能です。各メソッドはクラスに属する必要があり、静的メソッドも例外ではありません。以下にCILでのクラス定義の例を示します。
cil
.class public Foo
{
.method public static int32 Add(int32, int32) cil managed
{
.maxstack 2
ldarg.0 // 1つ目の引数をロード;
ldarg.1 // 2つ目の引数をロード;
add // それらを加算;
ret // 結果を戻す;
}
}
この例では、`Foo`クラスに静的メソッド`Add`が定義されています。C#での利用例は以下のようになります。
csharp
int result = Foo.Add(2, 3);
CILでは、この呼び出しは以下のように変換されます。
cil
ldc.i4.2
ldc.i4.3
call int32 Foo::Add(int32, int32)
stloc.0
インスタンスクラス
インスタンスクラスは、少なくとも1つのコンストラクタと、複数のインスタンスメンバーを含みます。以下に`Car`クラスの例を示します。
cil
.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
{
/ 実装は省略 */
}
}
オブジェクトの作成
C#でのクラスインスタンスの作成は、CILでは以下のように変換されます。
csharp
Car myCar = new Car(1, 4);
Car yourCar = new Car(1, 3);
cil
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);
インスタンスメソッド呼び出し
インスタンスメソッドの呼び出しは、CILでは以下のようになります。
csharp
myCar.Move(3);
cil
ldloc.0 // "myCar"オブジェクトをスタックにロード
ldc.i4.3
call instance void Car::Move(int32)
CLIは、コンパイルされたクラスに関する情報を
メタデータとして保持します。
メタデータにより、アプリケーションはアセンブリ内のインターフェース、クラス、型、メソッド、フィールドをサポートし、発見できます。この情報を読み取る処理はリフレクションと呼ばれます。
メタデータは属性の形式で表現され、カスタム属性は`Attribute`クラスを継承することで作成可能です。
CILの例
以下は、CILで書かれた基本的なHello Worldプログラムです。
cil
.assembly Hello {}
.assembly extern mscorlib {}
.method static void Main()
{
.entrypoint
.maxstack 1
ldstr "Hello, world!"
call void [mscorlib]System.Console::WriteLine(string)
ret
}
また、より複雑なCILコードの例として、以下のコードがあります。
cil
.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
rem
brfalse.s IL_001b
ldloc.1
ldc.i4.1
add
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
add
stloc.0
IL_001f: ldloc.0
ldc.i4 0x3e8
blt.s IL_0004
ret
}
CILの生成と逆アセンブル
CILアセンブリと命令は、コンパイラまたはILアセンブラー(ILASM)を使用して生成されます。アセンブルされたILは、IL逆アセンブラー(ILDASM)を使用して再びコードに逆アセンブルすることができます。
.NET Reflectorなどのツールを使用すると、C#やVisual Basicなどの高水準言語に逆コンパイルすることも可能です。CILはリバースエンジニアリングが容易なため、コード難読化ツールが使用されることもあります。
CILの実行
CILの実行には、実行時コンパイル(JITコンパイル)と事前コンパイルの2つの方法があります。
実行時コンパイル
バイトコードは、プログラムの実行中に
CPUが直接実行できるコードに変換されます。実行時コンパイルは、環境固有の最適化、実行時型安全性、アセンブリ検証などの機能を提供します。JITコンパイラはアセンブリ
メタデータをチェックし、不正アクセスを適切に処理します。
事前コンパイル
CLI互換の実行環境では、アセンブリを事前にコンパイルして、実行時のJIT処理を回避し、より高速な実行を実現できます。
.NET Frameworkには、ネイティブイメージジェネレーター(NGEN)という事前コンパイルツールがあり、Monoでも同様のオプションが利用可能です。