In my last post I went over the differences between using a continuation in F# and C#. As it turns out I was right about the limits and symptoms but wrong about the reason.

The F# code does indeed generate tail calls for part of the continuation. However this is only a very small portion of the actual code and is in fact only generated for the call in the empty case. I misread this function to be the call for the overall continuation. Instead it is the function for the entire ‘inner’ lambda.

So why does F# perform differently than C# in this scenario?

Andrew Kennedy pointed out that F# will actually transform the ‘inner’ function into a loop. In affect the code generated looks like the following.

TypeFunc func = this._self3;
while (true)
    if (!this.e.MoveNext())
    A cur = this.e.Current;
    cont = new Program.clo@9<U V, A ,>(this.combine, cont, cur);
return cont.Invoke(this.acc);

The actual transformation into a loop is what is preventing F# from overflowing the stack here. Iteration incurs no stack overhead in this case.

Even more interesting is that the tail opcode is quite simply ignored when dealing with un-trusted code. It therefore cannot be relied on to generate performant code in all scenarios.


Share Post


comments powered by Disqus