Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NOP in release build of F# code

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

like image 672
Brian Rasmussen Avatar asked Oct 23 '09 10:10

Brian Rasmussen


1 Answers

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.

like image 141
Jb Evain Avatar answered Oct 03 '22 13:10

Jb Evain