5

In the following program,

module Program

let condition = System.DateTime.Now.Millisecond % 2 = 0

let inline reliesOnCondition (x:int) =
    if condition then
        printfn "%i" x

[<EntryPoint>]
let main args =
    reliesOnConditional System.DateTime.Now.Second
    0

will the JIT optimize away the expression reliesOnCondition System.DateTime.Now.Second if condition turns out to be false upon module loading?

Stephen Swensen
  • 22,107
  • 9
  • 81
  • 136

2 Answers2

4

No*. When F# emits the IL for the program, all accesses to condition are done through a property accessor. Because of this extra layer of indirection the JIT engine cannot optimize away the entire body of reliesOnCondition.

Let me show how I found this out. (Also listed on an old blog post.)

Create new F# Project 'SOQ'

Build our F# application. I just hard-coded condition to be false.

module Program

let condition = false

let inline reliesOnCondition (x:int) =
    if condition then
        printfn "%i" x

[<EntryPoint>]
let main args =
    printfn "(attach a debugger and press any key)"
    System.Console.ReadKey(true) |> ignore

    reliesOnCondition System.DateTime.Now.Second
    0

Disassemble it and reassemble it with IL opcodes in the PDB

Next use ildasm to disassemble the IL binary using the /SOURCE parameter. This will not only get you an IL dump of the source, but also include the original source code left as comments.

ildasm SOQ.exe /OUT=SOQ-annotated.exe.il /SOURCE

Reassemble our binary from IL

Next use ilasm to reassemble the IL binary, but passing in the /DEBUG flag to get a PDB. The resulting application will have two levels of code. First, the origonal F# will be left in as comments, and the actual code will be IL instructions.

ilasm SOQ-annotated.exe.il /DEBUG

Run the process and attach the Visual Studio debugger

Run the newly annotated program. This will cause the application to get JIT-ted like normal. Next, attach the Visual Studio debugger to the active process.

Step through the code

Looking at an IL dump in the VS debugger isn't sufficient. Right click the 'Stack Traces' window and check Go To Disassembly. This will give you a display of the actual x86-instructions.

Here is the x86 op code dump. Note the original F# source line up top (ildasm /SOURCE), the IL instructions beneath that (ilasm /DEBUG), and the x86 instructions below that (courtesy of Visual Studio).

//000014:     reliesOnCondition System.DateTime.Now.Second
    IL_0026:  call       valuetype [mscorlib]System.DateTime [mscorlib]System.DateTime::get_Now()
000000db  lea         ecx,[ebp-58h] 
000000de  call        595E8C00 
    IL_002b:  stloc.3
000000e3  lea         edi,[ebp-30h] 
000000e6  lea         esi,[ebp-58h] 
000000e9  movq        xmm0,mmword ptr [esi] 
000000ed  movq        mmword ptr [edi],xmm0 
    IL_002c:  ldloca.s   V_3
000000f1  lea         eax,[ebp-30h] 
000000f4  mov         dword ptr [ebp-74h],eax 
    IL_002e:  call       instance int32 [mscorlib]System.DateTime::get_Second()
000000f7  mov         ecx,dword ptr [ebp-74h] 
000000fa  call        5960A670 
000000ff  mov         dword ptr [ebp-5Ch],eax 
    IL_0033:  stloc.2
00000102  mov         eax,dword ptr [ebp-5Ch] 
00000105  mov         dword ptr [ebp-28h],eax 
    IL_0034:  call       bool Program::get_condition()
00000108  call        dword ptr ds:[004232D8h] 
0000010e  mov         dword ptr [ebp-60h],eax 
    IL_0039:  brfalse.s  IL_003d
00000111  cmp         dword ptr [ebp-60h],0 
00000115  je          0000011A 
... snip ...

As you can see, the IL instruction 34 calls Program::get_condition(), so the JIT doesn't have enough information to properly eliminate the no-op function call. (Note that only functions can be marked inline, so you can't really go any further than this.)

*On my machine (x64 Win7). There is a difference between the x86 and x64 JIT engines as well as whether or not you used NGEN to produce your executable. Your mileage may vary.

Chris Smith
  • 18,244
  • 13
  • 59
  • 81
  • Thanks @Chris, this is a really powerful debugging technique. – Stephen Swensen Jan 22 '11 at 17:19
  • 1
    I very much appreciate you sharing this valuable technique, but I gave @Hans the check mark because he gave the key information that the JIT does not consider runtime values for dead code optimization. – Stephen Swensen Jan 23 '11 at 20:47
2

The upvoted answer is incorrect, a property accessor indirection does not in general prevent the JIT optimizer from omitting dead code. Most simple ones get inlined, their compile time values are considered. Which means that it will not optimize away the expression since the value of condition is only known at run time. The fact that the code is jitted after the condition value is already known doesn't change this, the optimizer must be able to determine the value statically. Only a literal will suffice here, you'd get one with conditional compilation (#if in C#). Check this answer for more background info on the optimizer.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • if `condition` were a C#-style static readonly field, would the JIT be able to consider it's runtime value in omitting dead code? Also, do you know why the property call in @Chris's example doesn't get omitted noting that he does use a compile time literal? – Stephen Swensen Jan 22 '11 at 17:25
  • That's the clause I covered with "must be able to determine the value statically". I suspect Chris either didn't disassemble the Release build or forgot to turn the "Suppress JIT optimization on module load" debugging option off. It is on by default to make debugging Release build code easy. – Hans Passant Jan 22 '11 at 18:05
  • OK, thanks - I thought perhaps the JIT might be able to do some runtime analysis (that that clause was specific to property optimization). – Stephen Swensen Jan 22 '11 at 22:48