Сгенерировать код операции хвостового вызова

Из любопытства я пытался сгенерировать код операции хвостового вызова, используя C #. Фибиначи прост, поэтому мой пример c # выглядит так:

    private static void Main(string[] args)
    {
        Console.WriteLine(Fib(int.MaxValue, 0));
    }

    public static int Fib(int i, int acc)
    {
        if (i == 0)
        {
            return acc;
        }

        return Fib(i - 1, acc + i);
    }

Если я собрал его в релиз и запустил без отладки, я не получил переполнение стека. Отладка или запуск без оптимизации, и я получаю переполнение стека, подразумевая, что хвостовой вызов работает, когда в выпуске включены оптимизации (что я и ожидал).

MSIL для этого выглядит так:

.method public hidebysig static int32 Fib(int32 i, int32 acc) cil managed
{
    // Method Start RVA 0x205e
    // Code Size 17 (0x11)
    .maxstack 8
    L_0000: ldarg.0 
    L_0001: brtrue.s L_0005
    L_0003: ldarg.1 
    L_0004: ret 
    L_0005: ldarg.0 
    L_0006: ldc.i4.1 
    L_0007: sub 
    L_0008: ldarg.1 
    L_0009: ldarg.0 
    L_000a: add 
    L_000b: call int32 [ConsoleApplication2]ConsoleApplication2.Program::Fib(int32,int32)
    L_0010: ret 
}

Я бы ожидал увидеть хвостовой код операции, в соответствии сMSDN, но это не там. Это заставило меня задуматься, был ли JIT-компилятор ответственным за его установку? Я попытался Ngen сборки (используяngen install <exe>, перейдите к списку сборок Windows, чтобы получить его) и загрузить его обратно в ILSpy, но для меня это выглядит так же:

.method public hidebysig static int32 Fib(int32 i, int32 acc) cil managed
{
    // Method Start RVA 0x3bfe
    // Code Size 17 (0x11)
    .maxstack 8
    L_0000: ldarg.0 
    L_0001: brtrue.s L_0005
    L_0003: ldarg.1 
    L_0004: ret 
    L_0005: ldarg.0 
    L_0006: ldc.i4.1 
    L_0007: sub 
    L_0008: ldarg.1 
    L_0009: ldarg.0 
    L_000a: add 
    L_000b: call int32 [ConsoleApplication2]ConsoleApplication2.Program::Fib(int32,int32)
    L_0010: ret 
}

Я до сих пор не вижу этого.

Я знаю, что F # хорошо обрабатывает хвостовой вызов, поэтому я хотел сравнить то, что сделал F # с тем, что сделал C #. Мой пример F # выглядит так:

let rec fibb i acc =  
    if i = 0 then
        acc
    else 
        fibb (i-1) (acc + i)


Console.WriteLine (fibb 3 0)

И сгенерированный IL для метода fib выглядит так:

.method public static int32 fibb(int32 i, int32 acc) cil managed
{
    // Method Start RVA 0x2068
    // Code Size 18 (0x12)
    .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = { int32[](Mono.Cecil.CustomAttributeArgument[]) }
    .maxstack 5
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: brtrue.s L_0006
    L_0004: ldarg.1 
    L_0005: ret 
    L_0006: ldarg.0 
    L_0007: ldc.i4.1 
    L_0008: sub 
    L_0009: ldarg.1 
    L_000a: ldarg.0 
    L_000b: add 
    L_000c: starg.s acc
    L_000e: starg.s i
    L_0010: br.s L_0000
}

Что, согласно ILSpy, эквивалентно этому:

[Microsoft.FSharp.Core.CompilationArgumentCounts(Mono.Cecil.CustomAttributeArgument[])]
public static int32 fibb(int32 i, int32 acc)
{
    label1:
    if !(((i != 0))) 
    {
        return acc;
    }
    (i - 1);
    i = acc = (acc + i);;
    goto label1;
}

Таким образом, F # генерирует хвостовой вызов, используя операторы goto? Это не то, что я ожидал.

Я нигде не пытаюсь положиться на хвостовой вызов, но мне просто интересно, где именно устанавливается этот код операции? Как C # делает это?

Ответы на вопрос(3)

Ваш ответ на вопрос