9

I've just stumbled across this and I'm a bit puzzled.

I have an out-of-the-box VS 2010 F# project, with all default settings, targeting .NET 4.0.

The F# code is like this:

let test(a:int, b:int, c:int) = min a (min b c)

When I compile it for release, the generated IL contains some strange NOP instructions scattered around. Like this:

The generated IL for this (with all default settings):

.method public static int32  test(int32 a,
                                  int32 b,
                                  int32 c) cil managed
{
  // Code size       20 (0x14)
  .maxstack  4
  .locals init ([0] int32 V_0)

  // HERE
  IL_0000:  nop

  IL_0001:  ldarg.1
  IL_0002:  ldarg.2
  IL_0003:  bge.s      IL_0009
  IL_0005:  ldarg.1

  // HERE
  IL_0006:  nop

  IL_0007:  br.s       IL_000b
  IL_0009:  ldarg.2

  // HERE
  IL_000a:  nop

  IL_000b:  stloc.0
  IL_000c:  ldarg.0
  IL_000d:  ldloc.0
  IL_000e:  bge.s      IL_0012
  IL_0010:  ldarg.0
  IL_0011:  ret
  IL_0012:  ldloc.0
  IL_0013:  ret
} // end of method Module1::test

My .fsproj project configuration is:

  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <Tailcalls>true</Tailcalls>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <WarningLevel>3</WarningLevel>
    <DocumentationFile>bin\Release\TestFsIL.XML</DocumentationFile>
  </PropertyGroup>

Now, if I comment out line <DebugType>pdbonly</DebugType>, the NOP instructions disappear. But of course so does the PDB file!

.method public static int32  test(int32 a,
                                  int32 b,
                                  int32 c) cil managed
{
  // Code size       17 (0x11)
  .maxstack  4
  .locals init (int32 V_0)
  IL_0000:  ldarg.1
  IL_0001:  ldarg.2
  IL_0002:  bge.s      IL_0007
  IL_0004:  ldarg.1
  IL_0005:  br.s       IL_0008
  IL_0007:  ldarg.2
  IL_0008:  stloc.0
  IL_0009:  ldarg.0
  IL_000a:  ldloc.0
  IL_000b:  bge.s      IL_000f
  IL_000d:  ldarg.0
  IL_000e:  ret
  IL_000f:  ldloc.0
  IL_0010:  ret
} // end of method Module1::test

There is also a subtle different in the .locals line:

.locals init ([0] int32 V_0)

vs

.locals init (int32 V_0)

When I tried C# compiler, it generates NOP instructions only in the debug builds, but these seem to go away in release builds, even when PDB files are included using <DebugType>pdbonly</DebugType>.

Questions:

  1. Why are NOP instructions generated in F# in release build when PDB files are included, when C# seems to be able to avoid this.

  2. Is there any way to get rid of those NOP, but still have the PDB files?

PS. There are related questions on SO here and here, but all the answers there say

you are compiling in debug mode, if you compile in release mode, the NOP goes away

which contradicts my experience with F# compiler as demonstrated.

Community
  • 1
  • 1
Philip P.
  • 2,364
  • 2
  • 15
  • 16
  • 2
    I am curious why you care, or why does it matter? – Brian Apr 13 '12 at 16:48
  • 1
    @Brian - I was looking through IL generated by F# as the result of [this SO post](http://stackoverflow.com/a/6104300/190460), noticed `NOP` instructions, got curious, wondered what the were for, googled it, found answer that it's only in debug mode, checked that I'm actually compiling for release and still got `NOP's`, was wondering why. TL;DR: curiosity :) – Philip P. Apr 13 '12 at 17:16

1 Answers1

8

I don't exactly know how this works internally in the F# compiler (somebody from the F# team may have a better answer), but my guess is that generating nop instructions is just a fairly easy way to generate locations that can be referred to from the pdb file.

The pdb file needs to specify some IL range for expression in the code where you can place a breakpoint - and this is the case in both Debug and Release mode. This means that if you place a breakpoint somewhere, there needs to be a corresponding location in the IL. However, if there is no actual instruction, corresponding to the source code location, the compiler needs to insert something - so it adds nop.

In Release mode, the F# compiler does more optimizations, but if you want pdb files, it still needs to provide location for all source code locations. This might not be needed in C#, because C# maps more closely to the source IL, but it might be harder to avoid this in F#.

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • yes this sounds plausible, what are the chances of anyone from F# team confirming this? – Philip P. Apr 13 '12 at 13:19
  • @KomradeP. - perhaps try compiling without specifying `pdbonly` (omitting the `DebugType` all-together indicates that no debug info whatsoever shall be generated). If the `nop` instructions disappear, then that will be pretty compelling evidence for Tomas' proposition. If not, then you probably can't conclude anything either way. – Stephen Swensen Apr 13 '12 at 14:11
  • @StephenSwensen - Wouldn't that be equivalent of not having `DebugType` element at all? The msbuild xsd suggests that the only valid values in this element are `none`, `pdbonly`, and `full`. I would imagine specifying an empty string would or removing the element would default to `none`? But anyway, specifying empty string results in exactly same result as not having the `DebugType` at all: no PDF file, and no `NOP` instructions. Not sure this proves anything beyond the fact that the presence of PDB file leads to `NOP` which we already know.... – Philip P. Apr 13 '12 at 14:28
  • @KomradeP.: ah, I didn't read your question closely enough, you already verified that no PDB suppresses `nop` generation! That may not prove anything, but it is pretty compelling evidence for Tomas' theory (at least for me). – Stephen Swensen Apr 13 '12 at 18:00