概要
.NETの仮想マシンは、スタック型と呼ばれるタイプの構造をしています。
スタック型の命令は、コンパイラー作りの基本だったりします。 .NET の IL 命令は、実 CPU の命令セットと比べるとシンプルで読みやすく、 コンパイラーというものの勉強がてらに眺めてみるのもいいのではないかと思います。
ここでは、サンプル コードを示しつつ、それが実際どういう手順で実行されているかを説明します。
例1: 2 * (x + y)
例として、以下のような C# コードを考えてみます。
static int X(int x, int y)
{
return 2 * (x + y);
}
これをコンパイルすると、以下のような IL が得られます。
.method private hidebysig static int32 X(int32 x,
int32 y) cil managed
{
.maxstack 8
IL_0000: ldc.i4.2
IL_0001: ldarg.0
IL_0002: ldarg.1
IL_0003: add
IL_0004: mul
IL_0005: ret
}
この例で出てきた IL 命令を簡単に説明すると、表1のようになります。
IL アセンブリ命令 | IL マシン語(16進数) | 説明 |
---|---|---|
ldc.i4.2 | 18 | int 型の定数 2 をスタックに読み込む(load constant integer(4byte) 2)。 |
ldarg.0 | 02 | 最初の引数の値をスタックに読み込む(load argument 0)。 |
ldarg.1 | 03 | 2つ目の引数の値をスタックに読み込む(load argument 1)。 |
add | 58 | 加算。スタック上の2つの値を消費して、加算結果をスタックの最上位に積む。 |
mul | 5A | 乗算(multiply)。スタック上の2つの値を消費して、乗算結果をスタックの最上位に積む。 |
ret | 2A | メソッド呼び出し元に戻る(return)。 |
「スタック」という言葉が各所に出てきます。 積み重ねたもの(stack)という意味の単語ですが、文字通り、計算に使うための値を積み上げておくための記憶領域です。
スタックのイメージをつかんでもらうために、 この IL 命令列がどう実行されていくか、スタックの状態も含めて図示していきましょう。 たとえば、引数として、x = 1, y = 3 を与えたとすると、以下のようになります。
例2: 値を2つ入力して、和を出力
もう1つ、ローカル変数やメソッド呼び出しも行う例を示しましょう。 以下のような C# コードを考えてみます。
static void Main()
{
var x = int.Parse(Console.ReadLine());
var y = int.Parse(Console.ReadLine());
Console.WriteLine("{0} + {1} = {2}", x, y, x + y);
}
これをコンパイルすると、以下のような IL が得られます。
.method private hidebysig static void Main() cil managed
{
.entrypoint
.maxstack 5
.locals init ([0] int32 x,
[1] int32 y)
IL_0000: call string [mscorlib]System.Console::ReadLine()
IL_0005: call int32 [mscorlib]System.Int32::Parse(string)
IL_000a: stloc.0
IL_000b: call string [mscorlib]System.Console::ReadLine()
IL_0010: call int32 [mscorlib]System.Int32::Parse(string)
IL_0015: stloc.1
IL_0016: ldstr "{0} + {1} = {2}"
IL_001b: ldloc.0
IL_001c: box [mscorlib]System.Int32
IL_0021: ldloc.1
IL_0022: box [mscorlib]System.Int32
IL_0027: ldloc.0
IL_0028: ldloc.1
IL_0029: add
IL_002a: box [mscorlib]System.Int32
IL_002f: call void [mscorlib]System.Console::WriteLine(string,
object,
object,
object)
IL_0034: ret
}
いくつか新しい IL 命令が出てきました。これらの意味は、表2の通りです。
IL アセンブリ命令 | IL マシン語(16進数) | 説明 |
---|---|---|
call | 28 | メソッドを呼び出す(call)。非仮想メソッド用。 |
ldloc.0 / ldloc.1 | 06 / 07 | 最初 / 2つ目のローカル変数の値をスタックに読み込む(load local)。 |
stloc.0 / stloc.1 | 0A / 0B | スタックの一番上の値を、最初 / 2つ目のローカル変数に書きだす(store local)。 |
ldstr | 72 | 文字列定数をスタックに読み込む(load string)。 |
box | 8C | 値型を object 型にボックス化(boxing)する。 |
こちらも、スタックの状態込みで、IL 命令列がどう実行されていくかを見ていきましょう。