Monday, August 05, 2019

F# under the covers XVII -- Code reuse loopiness

For some level of complexity and size, the F# compiler is happy to say "I've already done something like that, let's jump back and do it again!" putting an apparent loop into iteration-free code. It's not a common thing -- I've seen it exactly once the the FSharp.Core v4.7 library --

        match t1, t2 with
        | SetEmpty, t2  -> add comparer k t2 // drop t1 = empty 
        | t1, SetEmpty  -> add comparer k t1 // drop t2 = empty 
        | SetOne k1, t2 -> add comparer k (add comparer k1 t2)
        | t1, SetOne k2 -> add comparer k (add comparer k2 t1)
        | SetNode (k1, t11, t12, h1), SetNode (k2, t21, t22, h2) ->

The IL for this contains the expected forward conditional jumps from the match to the cases, but the second SetOne case preps the k2, t1 arguments then jumps backwards into the middle of the previous case, and exits after completing the shared call structure.

 IL_0029: ldloc.2
 IL_002a: isinst class Microsoft.FSharp.Collections.SetTree`1/SetOne
 IL_002f: brtrue.s IL_0055

 // item2 = t2;
 IL_0031: ldarg.3
 IL_0032: stloc.0
 // item = setOne.item;
 IL_0033: ldloc.1
 IL_0034: ldfld !0 class Microsoft.FSharp.Collections.SetTree`1/SetOne::item
 IL_0039: stloc.3

 // return add(comparer, k, add(comparer, item, item2));
 IL_003a: ldarg.0
 IL_003b: ldarg.2
 IL_003c: ldarg.0
 IL_003d: ldloc.3
 IL_003e: ldloc.0
 IL_003f: call class Microsoft.FSharp.Collections.SetTree`1 Microsoft.FSharp.Collections.SetTreeModule::'add'(class [mscorlib]System.Collections.Generic.IComparer`1, !!0, class Microsoft.FSharp.Collections.SetTree`1)
 IL_0044: call class Microsoft.FSharp.Collections.SetTree`1 Microsoft.FSharp.Collections.SetTreeModule::'add'(class [mscorlib]System.Collections.Generic.IComparer`1, !!0, class Microsoft.FSharp.Collections.SetTree`1)
 // (no C# code)
 IL_0049: ret

 // item2 = t1;
 IL_004a: ldarg.1
 IL_004b: stloc.0

 // return add(comparer, k, item2);
 IL_004c: ldarg.0
 IL_004d: ldarg.2
 IL_004e: ldloc.0
 IL_004f: call class Microsoft.FSharp.Collections.SetTree`1 Microsoft.FSharp.Collections.SetTreeModule::'add'(class [mscorlib]System.Collections.Generic.IComparer`1, !!0, class Microsoft.FSharp.Collections.SetTree`1)
 // (no C# code)
 IL_0054: ret

 // item2 = t2;
 IL_0055: ldarg.3
 // item = setOne.item;
 IL_0056: ldloc.1
 IL_0057: ldfld !0 class Microsoft.FSharp.Collections.SetTree`1/SetOne::item
 IL_005c: stloc.3
 // (no C# code)
 IL_005d: stloc.0
 IL_005e: br.s IL_003a

This provided an interesting exercise for the branch-chasing algorithm in AltCover (inspired by the one in OpenCover), which hadn't anticipated a backwards leap inside a sequence point, and went off into a spin until told to look for ret and similar termination points.

No comments :