Cecil.Decompiler and F#
In this new post-Reflector age, I thought I'd have a look-see how the main competition worked on F#. So I whipped up a simple driver for Cecil.Decompiler
:
private static void Decompile(MethodDefinition m) | |
{ | |
try { | |
Formatter.WriteMethodBody(Console.Out, m); | |
var lang = CSharp.GetLanguage(CSharpVersion.V3); | |
var writer = lang.GetWriter(new PlainTextFormatter(Console.Out)); | |
writer.Write(m); | |
} catch (Exception e) { | |
Console.WriteLine(m.FullName); | |
Console.WriteLine(e.Message); | |
} | |
} | |
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); | |
} |
and tried it on an assembly of some fairly simple F# -- 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 |
and we get things like
public static FSharpOption<T> Option`1.select.Static(FSharpFunc<T, bool> f, T x) | |
{ | |
if (f.Invoke(x)) | |
{ | |
} | |
return FSharpOption<T>.Some(x); | |
return null; | |
} |
This is better than Reflector was when I first tried it about 18 months ago -- F#'s cluster of branch instructions
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 |
caused the old Reflector to just crash.
F#'s habit of lacing in temporaries did flummox the decompilation a bit:
public static FSharpOption<T> Option`1.filter.Static(FSharpFunc<T, bool> f, FSharpOption<T> x) | |
{ | |
if (x) | |
{ | |
FSharpOption<T> = ; | |
T v = .get_Value(); | |
if (f.Invoke(v)) | |
{ | |
} | |
T v = .get_Value(); | |
return FSharpOption<T>.Some(v); | |
} | |
return null; | |
} |
compared with the current Reflector's take of
public static FSharpOption<T> Option`1.filter.Static<T>(FSharpFunc<T, bool> f, FSharpOption<T> x) | |
{ | |
FSharpOption<T> option = x; | |
if (option != null) | |
{ | |
FSharpOption<T> option2 = option; | |
T v = option2.get_Value(); | |
if (f.Invoke(v)) | |
{ | |
return FSharpOption<T>.Some(option2.get_Value()); | |
} | |
} | |
return null; | |
} |
MonoDevelop 2.4.2.'s Assembly Browser gets it a bit better than the raw decompiler, though it still has the same 'C'-style if
in there:
public static FSharpOption<T> Option`1.filter.Static(FSharpFunc<T, bool> f, FSharpOption<T> x) | |
{ | |
if (x) | |
{ | |
FSharpOption<T> V_1 = V_0; | |
T V_2 = V_1.get_Value(); | |
if (f.Invoke(V_2)) | |
{ | |
} | |
T V_3 = V_1.get_Value(); | |
return FSharpOption<T>.Some(V_3); | |
} | |
return null; | |
} |
Amusingly, I was able to get the decompilation to throw by feeding the little driver program into itself -- the DumpAssembly
method would not turn back into C#! MonoDevelop silently refused to load the contents of the whole assembly.
No comments :
Post a Comment