I am having problems with a fixed point combinator in F #:
let rec fix fa = f (fix f) a fix (fun body num -> if num = 1000000 then System.Console.WriteLine "Done!" else body (num + 1) ) 0
(This code is just to demonstrate the problem, it was written specifically to make the generated IL code easy to read.)
This code β if compiled with optimizations and tailcalls enabled β raises a StackOverflowException . I looked at the IL code and was able to trace the lambda problem inside the fix call:
.method assembly static void f@1 (class FSharpFunc`2<int32, class Unit> body,int32 num) { ldarg.1 ldc.i4 1000000 bne.un.s IL_0014 ldstr "Done!" call void Console::WriteLine(string) ret IL_0014: ldarg.0 // Load the 'body' function onto the stack. ldarg.1 // Load num onto the stack. ldc.i4.1 add // Invoke the 'body' function with num+1 as argument. callvirt instance !1 class FSharpFunc`2<int32, class Unit>::Invoke(!0) // Throw away the unit result. pop ret }
(I changed the code a bit to make it easier to read.)
The reason for the StackOverflowException is that the body call is not a tail call (the callvirt statement below). And the reason is that the compiler created a lambda call that actually returns Unit !
So, in C # terms: Func<Int32,Unit> body, when it really should be Action<Int32> . Since the call returns something that needs to be dropped, it cannot be tail. Also note that the f@1 method is compiled as void , not Unit , which is the reason why the result of calling the argument should be discarded.
Is it really intended or can I do something? The way the compiler views this lambda makes the fixed-point collector useless for all the purposes I intended to use.
I just want to add that while you return something as a result, it works fine. Only functions that return nothing do not work properly.
It works:
let rec fix fa = f (fix f) a fix (fun body num -> if num = 1000000 then System.Console.WriteLine "Done!"; 0 else body (num + 1) ) 0 |> ignore
And now this is the code generated for lambda:
.method assembly static int32 f@11 (class FSharpFunc`2<int32, int32> body, int32 num) { ldarg.1 ldc.i4 1000000 bne.un.s IL_0015 ldstr "Done!" call void Console::WriteLine(string) ldc.i4.0 ret IL_0015: ldarg.0 ldarg.1 ldc.i4.1 add tail. callvirt instance !1 class FSharpFunc`2<int32, int32>::Invoke(!0) ret }
Now there is a shank. And everything is working fine.
IL code for fix (for discussion in the comments):
.method public static !!b fix<a, b> (class FSharpFunc`2<class FSharpFunc`2<!!a, !!b>, class FSharpFunc`2<!!a, !!b>> f, !!aa) { ldarg.0 ldarg.0 newobj instance void class Program/fix@11<!!a, !!b>::.ctor(class FSharpFunc`2<class FSharpFunc`2<!0, !1>, class FSharpFunc`2<!0, !1>>) ldarg.1 tail. call !!0 class FSharpFunc`2<class FSharpFunc`2<!!a, !!b>, !!a>::InvokeFast<!!b>(class FSharpFunc`2<!0, class FSharpFunc`2<!1, !!0>>, !0, !1) ret }
So, it seems to me that (fix f) inside the definition of fix is ββnot a recursive call that occurs at this time, but simply a reference to fix itself, which, together with the argument f - gets stored in closure with the name Program/fix@11 and passed lambda as an argument that actually calls fix through this closure.
Otherwise, it will be infinite recursion from the very beginning, and fix will be useless.
I am using F # version 3.1.2, F # Interactive version 12.0.30815.0
You are welcome:
I'm not interested in alternative solutions. I just want to know why the compiler returns a Unit that needs to be thrown when the lambda fails.