Tuesday, September 15, 2009

F# under the covers III

To date we've seen examples where some simple F# code maps to something horrid in the C# view we get from Reflector.

Now for something completely different...

Simple properties like

[<DefaultValue(true)>]
val mutable private points : int
member self.Points with get() = self.points and set(v) = self.points <- v
view raw gistfile1.fs hosted with ❤ by GitHub

map quite directly from F# into IL, even in debug build

.method public specialname instance void set_Points(int32 v) cil managed
{
.maxstack 4
L_0000: nop
L_0001: ldarg.0
L_0002: ldarg.1
L_0003: stfld int32 Tinesware.InfrastructureTests.CoverageExemptionTest::points
L_0008: ret
}
.method public specialname instance int32 get_Points() cil managed
{
.maxstack 3
L_0000: nop
L_0001: ldarg.0
L_0002: ldfld int32 Tinesware.InfrastructureTests.CoverageExemptionTest::points
L_0007: ret
}
view raw gistfile1.cs hosted with ❤ by GitHub

where set_Points just assigns straight into the field, and get_Points just returns it, roughly as expected.

The corresponding C#

private int points;
public int Points { get { return points; } set { points = value; } }
view raw gistfile1.cs hosted with ❤ by GitHub

becomes

.method public hidebysig specialname instance void set_Points(int32 'value') cil managed
{
.maxstack 8
L_0000: nop
L_0001: ldarg.0
L_0002: ldarg.1
L_0003: stfld int32 Tinesware.TestData.Class1::points
L_0008: ret
}
.method public hidebysig specialname instance int32 get_Points() cil managed
{
.maxstack 1
.locals init (
[0] int32 CS$1$0000)
L_0000: nop
L_0001: ldarg.0
L_0002: ldfld int32 Tinesware.TestData.Class1::points
L_0007: stloc.0
L_0008: br.s L_000a
L_000a: ldloc.0
L_000b: ret
}
view raw gistfile1.cs hosted with ❤ by GitHub

The set_Points code is the same, modulo the maxstack -- but what are that temporary and that branch doing in get_Points?

This is a bit of a "lolwut!?" discovery!

Reassuringly, the release build of the C# get_Points is almost a nop-free version of the F# debug code

.method public hidebysig specialname instance int32 get_Points() cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: ldfld int32 Tinesware.TestData.Class1::points
L_0006: ret
}
view raw gistfile1.cs hosted with ❤ by GitHub

but as the debug version is the one for doing code analysis on, that's less useful.

This is unchanged with the Feb 2010 CTP (1.9.9.9).

1 comment :

Yann Schwartz said...

The added IL instructions are meant to better support debugging (the jump makes it easier to set breakpoints, and the temp local makes it possible to add a watch on the value and change it).

There's a lot of fluff added to a C# debug build to support these kind of scenarios. Not so much in F#, it seems.