C#をコンパイルするとCILになりますが、CILの情報は結構少ないので、メモがてらに書いておきます。
基本
たとえば1 + 10とするC#コードがあったとして、これをCILにすると以下のようなものに論理的にはなります。
ldc.i4.1
ldc.i4.s 10
add
逆ポーランド記法とかを知っている人ならどういう変換をされたかわかるかと思います。
C#での+の数値の加算はCILではaddになりますが、このaddはスタックにある2つの値を加算するものになります。
日本語で言うと「1 足す 10」は「1と10を読み込んで、読み込んだ2つの値を足す」になります。
どうしてこのような変換をするかとかは逆ポーランド記法を調べたら利点がわかるかと思いますので割愛。
ちなみに、先ほど「以下のようなものに論理的にはなります」と言いましたが、実際のところは1 + 10ぐらいはコンパイラーが最適化してldc.i4.s 11になってしまうことがあります。
命令一覧
スタック操作
| 命令 | 動作 |
|---|---|
| nop | 何もしない |
| dup | スタックのトップを複製 |
| pop | スタックのトップを破棄 |
定数読み込み
| 命令 | 動作 |
|---|---|
| ldc.i4 {value} | int32な{value}を読み込む |
| ldc.i4.s {value} | int8な{value}を読み込む |
| ldc.i4.m1 | -1を読み込む |
| ldc.i4.0 | 0を読み込む |
| ldc.i4.1 | 1を読み込む |
| ldc.i4.2 | 2を読み込む |
| ldc.i4.3 | 3を読み込む |
| ldc.i4.4 | 4を読み込む |
| ldc.i4.5 | 5を読み込む |
| ldc.i4.6 | 6を読み込む |
| ldc.i4.7 | 7を読み込む |
| ldc.i4.8 | 8を読み込む |
| ldc.i8 {value} | int64な{value}を読み込む |
| ldc.r4 {value} | float32な{value}を読み込む |
| ldc.r8 {value} | float64な{value}を読み込む |
ldc.i4.m1~ldc.i4.8は短縮命令です。
算術演算
| 命令 | 動作 |
|---|---|
| add | 加算(+) |
| sub | 減算(-) |
| mul | 乗算(*) |
| div | 除算(/) |
| div.un | 符号無し整数の除算のときに使われる |
| rem | 乗除(%) |
| rem.un | 符号無し整数の乗除のときに使われる |
| neg | 符号反転(-) |
ビット演算
| 命令 | 動作 |
|---|---|
| and | 論理積(&) |
| or | 論理和( |
| xor | 排他的論理和(^) |
| not | ビット反転(~) |
| shl | 左シフト |
| shr | 右シフト |
| shr.un | 符号無し右シフト |
変換
| 命令 | 動作 |
|---|---|
| conv.i1 | int8へ変換 |
| conv.u1 | uint8へ変換 |
| conv.i2 | int16へ変換 |
| conv.u2 | uint16へ変換 |
| conv.i4 | int32へ変換 |
| conv.u4 | uint32へ変換 |
| conv.i8 | int64へ変換 |
| conv.u8 | uint64へ変換 |
| conv.i | native intへ変換 |
| conv.u | native uintへ変換 |
| conv.r4 | float32へ変換 |
| conv.r8 | float64へ変換 |
| conv.r.un | uintをnative floatへ変換 |
まとめ
数が多すぎて一覧にはポインターやオーバーフローチェックのことは載せきれませんでしたが、単純な算術演算は表を見れば理解できるようになるかと思います。
実際のC#コードでは、これの応用で引数やローカル変数などからの読み込みとかもあるので、もっとややこしくなりますが……