Sunday, November 08, 2009

F# under the covers VIII

I'm in the tidying up stages for the little project I've been working on lately, a set of FxCop rules, mainly aimed at providing some complementary features for nCover-style code coverage, including static analysis for trivial methods -- or compiler generated ones. I've done the red -- add a set of methods that should give expected static analysis results -- and green -- write the simplest implementation that works when given the code base to analyse as a post-build activity. And now I'm on the refactor leg, abstracting out the common activities, taming sprawling methods into tighter ones, and reducing the level of imperative features (perhaps also making the code more idiomatic), even at times managing to delete whole methods.

And while I'm doing so, I'm running various tools over the code; FxCop and nCover over a small nUnit run every build, of course, but in turns Reflector and NDepend -- the results of which have been the inspiration for this series of posts.

And as we have seen, the code emitted by the F# compiler in its various iterations is unlike that coming from the more traditional .net langauges, in a way that doesn't always play nice with tools developed against C# or VB.net (and perhaps C++/CLI).

NDepend will deserve an essay of its own in due course -- part of the quid pro quo for the copy that Patrick Smacchia has generously donated -- but today's point of interest is that some IL code generated from F# cannot (at least with current Reflector builds) be easily folded back into C# source.

Take this active pattern used to match a property getter method that just returns a backing field with a name related to the property name, which is at a current intermediate stage of refactoring

let (|SimpleGetter|_|) (fn:Method) =
if (not fn.IsAccessor) || (not (fn.Name.ToString() @ "get_")) then None
else match MapToTypes fn.Body.Statements with
| [NodeType.Block; NodeType.Block] ->
match (fn.Body.Statements.[0], fn.Body.Statements.[1], fn) with
| CSharpGetterInterior -> Some ()
| _ -> None
| [NodeType.Block] ->
match (fn.Body.Statements.[0], fn) with
| SimpleGetterInterior -> Some()
| _ -> None
| _ -> None
view raw gistfile1.fs hosted with ❤ by GitHub

where MapToTypes turns an FxCop StatementCollection into a list of their corresponding NodeTypes, and @ wraps String.StartsWith with ordinal comparison.

Confronted with a request to decompile into C#, Reflector 5.1.6.0 asks me to file a bug report (which I have done) for an exception "Invalid branching statement for condition expression with target offset 002A."

Now, as far as I can tell from the IL, this is part of line 2 where it is balking

L_0000: nop
L_0001: ldarg.0
L_0002: call instance bool [Microsoft.Cci]Microsoft.FxCop.Sdk.Method::get_IsAccessor()
L_0007: brtrue.s L_000b
L_0009: br.s L_000d
L_000b: br.s L_0011
L_000d: ldc.i4.1
L_000e: nop
L_000f: br.s L_002a
L_0011: ldarg.0
L_0012: call instance class [Microsoft.Cci]Microsoft.FxCop.Sdk.Identifier [Microsoft.Cci]Microsoft.FxCop.Sdk.Member::get_Name()
L_0017: callvirt instance string [mscorlib]System.Object::ToString()
L_001c: ldstr "get_"
L_0021: call bool Tinesware.Rules.Assist.Local::op_Append(string, string)
L_0026: ldc.i4.0
L_0027: ceq
L_0029: nop
L_002a: brfalse.s L_002e
L_002c: br.s L_0030
L_002e: br.s L_0032
L_0030: ldnull
L_0031: ret
...
view raw gistfile1.cs hosted with ❤ by GitHub

Presumably -- I would have to build some equivalent C# code to validate -- the brfalse.s is not one that C# (or any of the other languages Reflector knows of) would actually emit. Certainly it seems that the new F# CTP compiler is fond of emitting it, and it breaks Reflector every time it does.

Another good reason for avoiding too many imperative constructs in your F# code, it would seem.


Later: this C# code that approximates the above F#

public static int? SimpleGetter(System.Reflection.MethodInfo fn)
{
if (!fn.IsSpecialName || !(fn.Name.StartsWith("get_"))) return null;
return fn.Name.Length;
}
view raw gistfile1.cs hosted with ❤ by GitHub

compiles to

L_0000: nop
L_0001: ldarg.0
L_0002: callvirt instance bool [mscorlib]System.Reflection.MethodBase::get_IsSpecialName()
L_0007: brfalse.s L_001b
L_0009: ldarg.0
L_000a: callvirt instance string [mscorlib]System.Reflection.MemberInfo::get_Name()
L_000f: ldstr "get_"
L_0014: callvirt instance bool [mscorlib]System.String::StartsWith(string)
L_0019: br.s L_001c
L_001b: ldc.i4.0
L_001c: stloc.1
L_001d: ldloc.1
L_001e: brtrue.s L_002c
L_0020: ldloca.s CS$0$0002
L_0022: initobj [mscorlib]System.Nullable`1
L_0028: ldloc.2
L_0029: stloc.0
L_002a: br.s L_003f
L_002c: ldarg.0
L_002d: callvirt instance string [mscorlib]System.Reflection.MemberInfo::get_Name()
L_0032: callvirt instance int32 [mscorlib]System.String::get_Length()
L_0037: newobj instance void [mscorlib]System.Nullable`1::.ctor(!0)
L_003c: stloc.0
L_003d: br.s L_003f
L_003f: ldloc.0
L_0040: ret
view raw gistfile1.cs hosted with ❤ by GitHub

which indeed, as I suspected, uses a brtrue.s for the final branch out of the if statement (though there is a brfalse.s to perform the short-circuiting.

Code generation here is unchanged in 1.9.9.9 from the previous CTP.

No comments :