monoでC#のILを確認してみる

# はじめに
C#をコンパイルすると中間言語（IL）というものに変換される。
コンパイルでどのような最適化がされるのか知りたいようなときにILを確認できると便利な気がしたので、その方法を調べた。
環境はmac。

# 事前準備
```shell-session
$ brew install mono
```
で必要なものは揃うはず。
この記事で試したmonoのバージョンは4.8.0。

# デコンパイル

適当にC#のコードを書く。

```csharp:Program.cs
using System;

class MainClass
{
    public static void Main(string[] args)
    {
        Console.WriteLine("hello");
    }
}
```

コンパイルする。

```shell-session
$ mcs Program.cs
```

これにより、Program.exeが作られる。
Program.exeの中身はオブジェクトコードと呼ばれ、バイナリなので、人が読める形のILにデコンパイルしたい。
これにはmonodisを使う。

```shell-session
$ monodis Program.exe
```

手元の環境では、以下のようなILが出力された。

```
.assembly extern mscorlib
{
  .ver 4:0:0:0
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
}
.assembly 'Program'
{
  .custom instance void class [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::'.ctor'() =  (
		01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78   // ....T..WrapNonEx
		63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01       ) // ceptionThrows.

  .hash algorithm 0x00008004
  .ver  0:0:0:0
}
.module Program.exe // GUID = {D15CB2F0-5E55-443C-A977-C6DE47EB30C8}


  .class private auto ansi beforefieldinit MainClass
  	extends [mscorlib]System.Object
  {

    // method line 1
    .method public hidebysig specialname rtspecialname 
           instance default void '.ctor' ()  cil managed 
    {
        // Method begins at RVA 0x2050
	// Code size 7 (0x7)
	.maxstack 8
	IL_0000:  ldarg.0 
	IL_0001:  call instance void object::'.ctor'()
	IL_0006:  ret 
    } // end of method MainClass::.ctor

    // method line 2
    .method public static hidebysig 
           default void Main (string[] args)  cil managed 
    {
        // Method begins at RVA 0x2058
	.entrypoint
	// Code size 11 (0xb)
	.maxstack 8
	IL_0000:  ldstr "hello"
	IL_0005:  call void class [mscorlib]System.Console::WriteLine(string)
	IL_000a:  ret 
    } // end of method MainClass::Main

  } // end of class MainClass
```
なんとなく、"hello"という文字列をロードして、System.Console::WriteLineという関数を呼んでいるというようなことが分かる。

# ILの読み方
https://codezine.jp/article/detail/2624
が参考になった。
どんな命令があるかの一覧はどこを見ればいいか分からなかったが、
https://msdn.microsoft.com/ja-jp/library/system.reflection.emit.opcodes(v=vs.110).aspx
は参考になりそう。

# ILからオブジェクトコードへの変換
逆に、ILからオブジェクトコードに変換したい場合は、ilasmを使う。

```shell-session
$ ilasm Program.il
```

実は、helloを出力するILとしては、以下のようなもので十分そう（クラスすらなくても大丈夫らしい）。

```Program.il
.assembly extern mscorlib { }
.assembly Program { }

.method static void Main() cil managed {
    .entrypoint
    .maxstack 8
    ldstr "hello"
    call void [mscorlib]System.Console::WriteLine(string)
    ret
}
```

```
$ ilasm Program.il
Assembling 'Program.il' , no listing file, to exe --> 'Program.exe'

Operation completed successfully
$ mono Program.exe
hello
```

# ILから読み解く最適化の実例
## swtichの最適化
@neueccさんの記事で
http://engineering.grani.jp/entry/2017/02/20/175816
というのがあって、これが実際に手元のmonoでどうなるかを調べた。

```csharp:Program.cs
    static int Func(int x)
    {
        switch (x)
        {
            case 0:
                return 0;
            case 1:
                return 1;
            default:
                return -1;
        }
    }
```

これのILを出力すると、以下のようになった。

```:Program.il
   .method private static hidebysig 
           default int32 Func (int32 x)  cil managed 
    {
	// Method begins at RVA 0x2061
	// Code size 24 (0x18)
	.maxstack 8
	IL_0000:  ldarg.0 
	IL_0001:  brfalse IL_0012

	IL_0006:  ldarg.0 
	IL_0007:  ldc.i4.1 
	IL_0008:  beq IL_0014

	IL_000d:  br IL_0016

	IL_0012:  ldc.i4.0 
	IL_0013:  ret 
	IL_0014:  ldc.i4.1 
	IL_0015:  ret 
	IL_0016:  ldc.i4.m1 
	IL_0017:  ret 
    } // end of method MainClass::Func
```

brfalseというのが独特という感じがするが、確かにifの羅列というようなコードになっている。
もう一つcaseを追加してみた。

```csharp:Program.cs
    static int Func(int x)
    {
        switch (x)
        {
            case 0:
                return 0;
            case 1:
                return 1;
            case 2:
                return 2;
            default:
                return -1;
        }
    }
```

これは、以下のようになった。

```:Program.il
    .method private static hidebysig 
           default int32 Func (int32 x)  cil managed 
    {
	// Method begins at RVA 0x2061
	// Code size 31 (0x1f)
	.maxstack 8
	IL_0000:  ldarg.0 
	IL_0001:  switch (
	  IL_0017,
	  IL_0019,
	  IL_001b)
	IL_0012:  br IL_001d

	IL_0017:  ldc.i4.0 
	IL_0018:  ret 
	IL_0019:  ldc.i4.1 
	IL_001a:  ret 
	IL_001b:  ldc.i4.2 
	IL_001c:  ret 
	IL_001d:  ldc.i4.m1 
	IL_001e:  ret 
    } // end of method MainClass::Func
```

swtichという命令が使われていて、確かに効率的っぽい感じになっている。
他にも、試しに文字列のswitchのILを見てみたら、内部的にDictionaryが作られるようなコードになった。
これは、@neueccさんの記事とは違う結果であったが、文字列の比較を愚直に直列でやってなさそうということで一安心した。

## forの中での変数宣言
forの中で変数宣言すると、ループする度にオーバーヘッドが発生するから良くないとかいう都市伝説があったりするが、実際にILはどうなるかを調べた。
つまり、

```csharp:ForTest1.cs
    static void Func()
    {
        for (int i = 0; i < 10; i++)
        {
            int x = i * 2;
            Console.WriteLine(x);
        }
    }
```

ではxの宣言にコストがあるので、

```csharp:ForTest2.cs
    static void Func()
    {
        int x;
        for (int i = 0; i < 10; i++)
        {
            x = i * 2;
            Console.WriteLine(x);
        }
    }
```

と書こうという話である。

それぞれ、ILに変換してみると、

```:ForTest1.il
    .method private static hidebysig 
           default void Func ()  cil managed 
    {
	// Method begins at RVA 0x2060
	// Code size 30 (0x1e)
	.maxstack 2
	.locals init (
		int32	V_0,
		int32	V_1)
	IL_0000:  ldc.i4.0 
	IL_0001:  stloc.0 
	IL_0002:  br IL_0015

	IL_0007:  ldloc.0 
	IL_0008:  ldc.i4.2 
	IL_0009:  mul 
	IL_000a:  stloc.1 
	IL_000b:  ldloc.1 
	IL_000c:  call void class [mscorlib]System.Console::WriteLine(int32)
	IL_0011:  ldloc.0 
	IL_0012:  ldc.i4.1 
	IL_0013:  add 
	IL_0014:  stloc.0 
	IL_0015:  ldloc.0 
	IL_0016:  ldc.i4.s 0x0a
	IL_0018:  blt IL_0007

	IL_001d:  ret 
    } // end of method MainClass::Func
```

```:ForTest2.il
    .method private static hidebysig 
           default void Func ()  cil managed 
    {
	// Method begins at RVA 0x2060
	// Code size 30 (0x1e)
	.maxstack 2
	.locals init (
		int32	V_0,
		int32	V_1)
	IL_0000:  ldc.i4.0 
	IL_0001:  stloc.1 
	IL_0002:  br IL_0015

	IL_0007:  ldloc.1 
	IL_0008:  ldc.i4.2 
	IL_0009:  mul 
	IL_000a:  stloc.0 
	IL_000b:  ldloc.0 
	IL_000c:  call void class [mscorlib]System.Console::WriteLine(int32)
	IL_0011:  ldloc.1 
	IL_0012:  ldc.i4.1 
	IL_0013:  add 
	IL_0014:  stloc.1 
	IL_0015:  ldloc.1 
	IL_0016:  ldc.i4.s 0x0a
	IL_0018:  blt IL_0007

	IL_001d:  ret 
    } // end of method MainClass::Func
```

となった。
これを見ると、前者ではILのローカル変数V_0がC#の変数`i`、V_1が`x`に対応しているのに対して、後者ではその対応が逆になっている。
しかしそれ以外は全く同じILが生成されているため、実行速度に違いは生じないと考えられる。
この事から、xのスコープが小さくなり保守性が高まる前者の書き方を採用すればいいという事が分かる。
この都市伝説は何だったのか…。

# まとめ
monoでC#のILを確認する方法をまとめた。
少しC#の事を知ることができた気がする。