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`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;
}

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;
 Block_4:
 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.ReadSymbols(reader);

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

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

Post a Comment