views:

325

answers:

1

I am playing with F# in VS2010 beta2, and since I am new to F#, I just picked one of the common examples and went ahead and implemented a factorial function as:

let rec factorial n =
  if n <= 1 then 1 else n * factorial (n - 1);;

If I build this and look at the generated code in Reflector, I get the corresponding C# code:

public static int Factorial(int n) {
   if (n <= 1) 
      return 1;

      return n * Factorial(n - 1);
}

So if I compile Reflector's C# representation of the F# code, I would expect to get identical IL.

However, if I compile both of these snippets in release mode and compare the generated IL, they are different (they are functionally identical, but still differs a bit).

The C# implementation compiles to:

.method public hidebysig static int32 Factorial(int32 n) cil managed
{
   .maxstack 8
   L_0000: ldarg.0 
   L_0001: ldc.i4.1 
   L_0002: bgt.s L_0006
   L_0004: ldc.i4.1 
   L_0005: ret 
   L_0006: ldarg.0 
   L_0007: ldarg.0 
   L_0008: ldc.i4.1 
   L_0009: sub 
   L_000a: call int32 TestApp.Program::Factorial(int32)
   L_000f: mul 
   L_0010: ret 
}

The F# implementation compiles to:

.method public static int32 factorial(int32 n) cil managed
{
   .maxstack 5        <=== Different maxstack
   L_0000: nop        <=== nop instruction?
   L_0001: ldarg.0 
   L_0002: ldc.i4.1 
   L_0003: bgt.s L_0007
   L_0005: ldc.i4.1 
   L_0006: ret 
   L_0007: ldarg.0 
   L_0008: ldarg.0 
   L_0009: ldc.i4.1 
   L_000a: sub 
   L_000b: call int32 FSharpModule::factorial(int32)
   L_0010: mul 
   L_0011: ret 
}

The generated code is identical except for different maxstack and the additional NOP instruction in the F# method.

This is probably not significant, but I am curious as to why the F# compiler would insert NOP in a release build.

Can anyone explain why?

(I am perfectly aware that the F# compiler hasn't gone through the same level of real world testing as the C# compiler, but this is so obvious that I image it would have been caught).

EDIT: The compile command is as follows

C:\Program Files\Microsoft F#\v4.0\fsc.exe -o:obj\Release\FSharpLib.dll 
--debug:pdbonly --noframework --define:TRACE --optimize+ 
--target:library --warn:3 --warnaserror:76 --vserrors --utf8output --fullpaths 
--flaterrors "C:\Temp\.NETFramework,Version=v4.0.AssemblyAttributes.fs" Module1.fs

(referenced assemblies removed for brevity).

+15  A: 

The maxstack difference is due to the fact that the C# compiler compiles the first method with a «light» method body header, that is used whenever the code is small, has no exceptions and no locals. In that case, the maxstack is not specified and defaults to 8.

The F# compiler is using a «fat» method body header, and specifies the maxstack it has computed.

As for the nop, it's because you're compiling in debug mode. They always start a method body with a nop. See from fsharp/ilxgen.ml:

// Add a nop to make way for the first sequence point. There is always such a 
// sequence point even when zapFirstSeqPointToStart=false
do if mgbuf.cenv.generateDebugSymbols  then codebuf.Add(i_nop);

If I compile your factorial without debug symbols, I don't get a nop.

Jb Evain
Well, I use the default "release" build profile in VS2010, so I would assume that I am actually building in release mode. The output window says "Build started: Project: FSharpLib, Configuration: Release Any CPU". If I change to "debug" I get entirely different MSIL as expected.
Brian Rasmussen
I'm testing using F# 1.9.7.8 and the command line. If I don't pass /debug, I dont't get a nop.
Jb Evain
I updated the question with the command line for the compile and I tried to compile using fsc.exe directly. Same result. According to the output window VS2010b2 doesn't ship with the most recent compiler as it reports a version number of F# Version 1.9.7.4. Could that be the difference?
Brian Rasmussen
The command line you posted contains `--debug:pdbonly`, which triggers the inclusion of the nop.
Jb Evain
Is the NOP IL instruction JITted to the NOP machine code instruction, or is it a true no-op and ignored?
Lasse V. Karlsen
It depends on the implementation of the JIT. Mono ignores them. I think .net ignores them as well.
Jb Evain
Just to be clear, the reason why the NOP is emitted in conjunction with PDBs is that in order to set a debugger breakpoint at certain locations you need an instruction at this point.For example, in C# to set a breakpoint at "}" you need an instruction for that code, even though the code doesn't do anything.
Chris Smith
You're right. --debug:pdbonly is the culprit. I am still a little surprised why the NOP is needed when outputting symbols, but it fits very well with the quote from ilxgen.ml.Thanks for the many updates, and thank you for taking the time.
Brian Rasmussen