目次

概要

(書きかけ)

C# などの、.NET Framework 上で動作するプログラミング言語は、 一度、.NET の中間言語(intermediate language)にコンパイルされます。

.NET の場合、中間言語のことを、単に略して IL と呼んだり、 MSIL(Microsoft Intermediate Language)や CIL(Common intermediate language)と呼んだりします。 (以後、IL という表記に統一します。)

.NET の仕組み

いくつか、用語を説明

C# など → IL → ネイティブ コード
の流れ

JIT(Just-In-Time コンパイル)

Pre-JIT(NGen)
特に、Windows 8/.NET 4.5 では Auto-NGen

マシン語(数値化した IL)と、アセンブリ言語(human-readable な IL 表現)

C#をはじめとする、.NET対応言語は、.NETの仮想マシンが直接解釈できるILマシン語にコンパイルされます。アプリやライブラリは、このILの状態で配布します。

一般的な(仮想マシンか実CPUか問わず)実行環境と同様に、ILには、マシン語と1対1に対応していて、ある程度人間でも読めるような言語(アセンブリ言語)が定義されています。

ILは、.NET Frameworkの仮想マシンによって、都度、ネイティブ コード(実CPUが直接解釈できるマシン語)にコンパイルしながら(Just-In-Time方式のコンパイル、略してJIT)実行されます。

なぜ IL

・コンパイラー作る側の都合

C# などの高級言語の専門家と、
x86, x64, ARM などの CPU に応じた最適化の専門家に分けたい

C# とかに新機能を追加しつつ、
CPU ごとに高度な最適化をしようと思うと、
一旦 IL を介さざるを得ない。


・セキュリティ的な検証できるメリット


・メタデータ
これは IL だからというわけでもなく、COM なんかでもそうだけど
メタデータを持ちたい

.NET アセンブリ

C#などのソース コードのコンパイル結果、つまり、.NET向けの実行可能形式(exe)やライブラリ(dll)を総称して、.NETアセンブリ(assembly)と言います。

アセンブリの中には、メタデータ(型情報や属性)と、ILコードが入っています。

図を入れる

メタデータ

public class Sample
{
    private readonly string _name;
    public string Name { get { return _name; } }

    public int Value { get; set; }

    public Sample(string name, int value = 0)
    {
        _name = name;
        Value = value;
    }
}

メタデータの例:

.class public auto ansi beforefieldinit Sample
       extends [mscorlib]System.Object
{
}

.field private initonly string _name

.property instance string Name()
{
  .get instance string Sample::get_Name()
}

.property instance int32 Value()
{
  .get instance int32 Sample::get_Value()
  .set instance void Sample::set_Value(int32)
}

...後略...

どのクラスがどういうメンバーを持っていてとか、そういう情報が全部アセンブリ内に残る。

これはアセンブリ言語表現の IL。 実際にアセンブリに入るのは、この情報をバイナリ化したもの。

. から始まる行がメタデータ定義

TypeDef (リフレクションで使うような型全部の情報)と
TypeRef (型の弁別情報だけ)

デリゲートとか列挙型とかも、IL ネイティブ
(クラスとかが生成されてるわけじゃない。IL レベルにある。)

ジェネリック

IL 命令

IL コードの例:

.method public hidebysig specialname rtspecialname 
        instance void  .ctor(string name,
                             [opt] int32 'value') cil managed
{
  .param [2] = int32(0x00000000)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
  IL_0006:  ldarg.0
  IL_0007:  ldarg.1
  IL_0008:  stfld      string Sample::_name
  IL_000d:  ldarg.0
  IL_000e:  ldarg.2
  IL_000f:  call       instance void Sample::set_Value(int32)
  IL_0014:  ret
}

IL_0000 とかはラベル。制御フローに使う。 ldarg.0 とかが IL 命令。

ほとんどが1バイト命令。 ごく一部、2バイト命令、4バイト命令もあり。

いくつか紹介。 アセンブリ言語 ⇔ マシン語 対応表込みで。 例えば ldarg.0 なら0x02。

スタック型マシン
任意の型を受け付けるスタック
(+ ローカル変数テーブル、引数テーブル)

call 命令とかの後ろにメソッド名が入ってるけども、マシン語的には、Token と呼ばれる4バイトの数値になってる。
Token の値を元に、メタデータを検索して実際の型やメソッド名を得る。

プリミティブ
(int とかだけ、パフォーマンス的な理由でいろいろ特別扱い受けてる。
  専用命令がある。)

定数ロードとか、いくつかの命令はオペランドなし版がある。
(命令長的にお得)

オペランド付きの命令は、1バイト(short)版と4バイト(long)版がある。

if とか for に相当する命令はない。
br 命令で(高級言語でいうと if goto 相当)

switch だけは専用命令がある。
case の数次第で、if-else 相当コードか、ジャンプ テーブルに JIT


ガベコレ

ネイティブ interop

更新履歴

ブログ