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

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`<T, bool> f, T x)
 if (f.Invoke(x))
  goto Block_3;
 return FSharpOption<T>.Some(x);
 return null;

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

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;
 return FSharpOption<T>.Some(fSharpOption2.Value);

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.Types.SelectMany(x => x.Methods).ToList().ForEach(Decompile);    

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);
 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));

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

Post a Comment