Friday, February 18, 2011

ILSpy and F#

Suddenly a new contender in the post-Reflector age -- ILSpy, emerging from the SharpDevelop stable.

So I decided to give it a whirl with the same F# code as with the simple tests I made last week with Cecil.Decompiler -- a few extension methods for Option

module Augment =
type Microsoft.FSharp.Core.Option<'T> with
static member filter (f : 'T -> bool) (x : option<'T>) =
match x with
| Some v when f(v) -> Some v
| _ -> None
static member getOrElse (fallback : 'T) (x : option<'T>) = defaultArg x fallback
static member select (f : 'T -> bool) (x : 'T) =
if f(x) then Some x
else None
static member nullable (x : 'a when 'a : null) : option<'a> =
if x <> null then Some x
else None
view raw gistfile1.fs hosted with ❤ by GitHub

The first thing I notice is that I can't copy and paste code from the decompilation screen -- I have to save it, and which point I get a button to open Explorer in the same directory as I saved. The C# code generated looks sane

public static FSharpOption<T> Option`1.select.Static(FSharpFunc<T, bool> f, T x)
{
if (f.Invoke(x))
{
}
else
{
goto Block_3;
}
return FSharpOption<T>.Some(x);
Block_3:
return null;
}
view raw gistfile1.cs hosted with ❤ by GitHub

Unlike Cecil, this gets the F# branching right in representing the highlighted section

IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldarg.1
IL_0003: callvirt !1 Microsoft.FSharp.Core.FSharpFunc`2<T,System.Boolean>::Invoke(!0)
IL_0008: brfalse.s IL_000c
IL_000a: br.s IL_000e
IL_000c: br.s IL_0015
IL_000e: ldarg.1
IL_000f: call Microsoft.FSharp.Core.FSharpOption`1<!0> Microsoft.FSharp.Core.FSharpOption`1<T>::Some(!0)
IL_0014: ret
IL_0015: ldnull
IL_0016: ret
view raw gistfile1.cs hosted with ❤ by GitHub

It also handles compiler generated temporaries just fine -- albeit with the same 'C'-style if

public static FSharpOption<T> Option`1.filter.Static(FSharpFunc<T, bool> f, FSharpOption<T> x)
{
FSharpOption<T> fSharpOption = x;
if (fSharpOption)
{
FSharpOption<T> fSharpOption2 = fSharpOption;
T t = fSharpOption2.Value;
if (f.Invoke(t))
{
goto Block_4;
}
}
return null;
Block_4:
return FSharpOption<T>.Some(fSharpOption2.Value);
}
view raw gistfile1.cs hosted with ❤ by GitHub

And ૼ bonus ૼ while Cecil choked on this C# code

private static void DumpAssembly(string path)
{
var assembly = AssemblyDefinition.ReadAssembly(path);
var pdbpath = Path.ChangeExtension (path, ".pdb");
var provider = new PdbReaderProvider();
var reader = provider.GetSymbolReader(assembly.MainModule, pdbpath);
assembly.MainModule.ReadSymbols(reader);
assembly.MainModule.Types.SelectMany(x => x.Methods).ToList().ForEach(Decompile);
}
view raw gistfile1.cs hosted with ❤ by GitHub

ILSpy comes up with

private static void DumpAssembly(string path)
{
AssemblyDefinition assemblyDefinition = AssemblyDefinition.ReadAssembly(path);
string text = Path.ChangeExtension(path, ".pdb");
ISymbolReader symbolReader = new PdbReaderProvider().GetSymbolReader(assemblyDefinition.MainModule, text);
assemblyDefinition.MainModule.ReadSymbols(symbolReader);
var arg_5F_0 = assemblyDefinition.MainModule.Types;
if (!Program.CS$<>9__CachedAnonymousMethodDelegate1)
{
Program.CS$<>9__CachedAnonymousMethodDelegate1 = delegate(TypeDefinition x)
{
IEnumerable<MethodDefinition> enumerable = x.Methods;
return enumerable;
}
;
}
Enumerable.ToList<MethodDefinition>(Enumerable.SelectMany<TypeDefinition, MethodDefinition>(arg_5F_0, Program.CS$<>9__CachedAnonymousMethodDelegate1)).ForEach(new Action<MethodDefinition>(Program.Decompile));
}
view raw gistfile1.cs hosted with ❤ by GitHub

which leaks some of the internal plumbing -- delegate caching; how extension methods are actually handled -- but does at least deliver.

No comments :