Why does F# compiler generates IL NOP even in release builds?

f#

Solution

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

Problem

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: Why are NOP instructions generated in F# in release build when PDB files are included, when C# seems to be able to avoid this. 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.

Original source

Related problems