はかせのラボ

私の頭の中を書いていく雑記ブログです

IL IL命令でLdc_I4とLdc_I4_SみたいにSが付いてるやつと付いてないやつの違いを調べてみた話

あいさつ

どうも、はかせです。
今日は前回作った拡張メソッドを改造していたのですが、
その中でSが付いてるものと付いてないものの違いが気になったので
調べてみました。

今回はその調査の報告記事になります。

Ldc_I4_SとかのSの意味は?

私は今回調査するまでこのSのことを
Safeを意味していてS付きの命令は、
安全に操作できる命令のことなのかななんて思ってました。

ただ調べてみたところこのSは
Short、つまり短いでした。

短い命令ってどういうことやねん!って感じですが、
この短いは命令ではなく、命令が扱う値が短いということみたいです。

どゆこと??

命令が扱う値が短いってのもイマイチピンと来ないかもしれません。

ようは8bitで扱える値です。
Ldarg_Sのような正の値だけ扱うような命令は0~255(byteと同じ)
Ldc_I4_Sのような負の値も扱う命令は-128~127(sbyteと同じ)が範囲です。

なんで分かれてるの?

ぶっちゃけやろうと思えばLdc_I4で-128も127も扱えます。
じゃあなんでLdc_I4_Sなんていう
限られた範囲のみ扱える命令があるかというと
そちらのほうがより効率的なコードとして実行されるからだそうです。

ILってのは最終的に.NET上で機械語に変換され実行されます。
おそらくその時吐く機械語のコードに最適化がかかるものと思われます。

実際問題そこ気にした方がいいの?

気にできるならした方がいいです。
ただILを手書きしてくとコードの補完とか効かんせいで
色々気にすべきとこが大量にあって
少なくとも私はそこまで気は回りません。

というわけでそんなん気にせずとも
適したものをEmitしてくれる拡張メソッドを作ってお茶を濁しましょう。

public static void LdcI4(this ILGenerator il,int value)
{
    //-1~8までは専用のコードが存在する
    //sbyteまでの範囲ならsbyteにした方が最適化がかかるらしい
    switch (value)
    {
        case -1:
            il.Emit(OpCodes.Ldc_I4_M1);
            break;
        case 0:
            il.Emit(OpCodes.Ldc_I4_0);
            break;
        case 1:
            il.Emit(OpCodes.Ldc_I4_1);
            break;
        case 2:
            il.Emit(OpCodes.Ldc_I4_2);
            break;
        case 3:
            il.Emit(OpCodes.Ldc_I4_3);
            break;
        case 4:
            il.Emit(OpCodes.Ldc_I4_4);
            break;
        case 5:
            il.Emit(OpCodes.Ldc_I4_5);
            break;
        case 6:
            il.Emit(OpCodes.Ldc_I4_6);
            break;
        case 7:
            il.Emit(OpCodes.Ldc_I4_7);
            break;
        case 8:
            il.Emit(OpCodes.Ldc_I4_8);
            break;
        default:
            if (value <= 127 && value >= -128) il.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
            else il.Emit(OpCodes.Ldc_I4, value);
            break;
    }
}

やっぱり拡張メソッドは便利
あとはこのメソッドにツッコみたい値を渡せば
自動的に適したコードでEmitしてくれます。

そして話は変わるんですけど、
C#8.0でswitch文がラムダ式っぽく書けるようになったじゃないですか

こんな感じで

public void Wayaku(string eigo)
{
    var yaku = eigo switch
    {
        "Apple" => "りんご",
        "Walk" => "歩く",
        "Dragon" => "ドラゴン",
        "Quest" => "探求",
        _ => throw new InvalidOperationException()
    };
    Console.WriteLine(eigo + "は" + yaku + "を意味しています")
}


これこう書ける未来来ないですかね?

public void Wayaku(string eigo)
{
    eigo switch
    {
        "Apple" => Console.WriteLine(e + "はりんごを意味しています"),
        "Walk" => Console.WriteLine(e + "は歩くを意味しています"),
        "Dragon" => Console.WriteLine(e + "はドラゴンを意味しています"),
        "Quest" => Console.WriteLine(e + "は探求を意味しています"),
        _ => throw new InvalidOperationException()
    };
}

今回拡張メソッド作ってく中でこんな風に書けたらいいのになあ~
なんて思いました。

あとがき

今回は_Sの意味の調べてみた話でした。

C++とかではmemcpy_sみたいな感じで_sが付くと
バッファサイズの指定とかして安全にできるって意味合いだったので
_Sが短いを表しているとは夢にも思いませんでしたね。

ただ最適化されるなら使ったほうがいいと思いますし、
今回作ったみたいな拡張メソッドを用意しとけば
そんな手間も変わらんし良きかなと思います。

それでは今回はこの辺でノシ