home comics writing pictures archive about

2016-07-24 - In IL: Largest of Two Numbers (if-else)

In IL

Last time we looked at IL branching Instructions. The simplest use of branching instructions is probably the If statement which allows the program to conditionally execute a set of instructions. If the condition is true then the program executes the code within the if block. Statements like this exist in most high level languages because they allow the program to make decisions about whether or not code should be executed. If statements can also have Else blocks which are only executed if the condition is false.

Let's look at a simple program that reports which of two numbers is the largest using if, else statements in C#.

Program.cs
using System;
namespace csIf
{
class Program
{
static void Main(string[] args)
{
int numberA = 17;
int numberB = 96;
if (numberA > numberB)
{
Console.WriteLine("numberA ({0}) is larger than numberB ({1})",
numberA, numberB);
}
else
{
Console.WriteLine("numberB ({0}) is larger than numberA ({1})",
numberB, numberA);
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

Compile it and we get something like this.

Main
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 56 (0x38)
.maxstack 3
.locals init ([0] int32 numberA,
[1] int32 numberB)
IL_0000: ldc.i4.s 17
IL_0002: stloc.0
IL_0003: ldc.i4.s 96
IL_0005: stloc.1
IL_0006: ldloc.0
IL_0007: ldloc.1
IL_0008: ble.s IL_0021
IL_000a: ldstr "numberA ({0}) is larger than numberB ({1})"
IL_000f: ldloc.0
IL_0010: box [mscorlib]System.Int32
IL_0015: ldloc.1
IL_0016: box [mscorlib]System.Int32
IL_001b: call void [mscorlib]System.Console::WriteLine(string,
object,
object)
IL_0020: ret
IL_0021: ldstr "numberB ({0}) is larger than numberA ({1})"
IL_0026: ldloc.1
IL_0027: box [mscorlib]System.Int32
IL_002c: ldloc.0
IL_002d: box [mscorlib]System.Int32
IL_0032: call void [mscorlib]System.Console::WriteLine(string,
object,
object)
IL_0037: ret
} // end of method Program::Main
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

The first few lines are things we've seen before. You have the stack size directive, the local variable declaration and then variable initialization. At line 69 is where things get interesting. We start by loading both local variables and then calling a branch less-than-or-equal instruction.

The target of the branch is encoded using a label. labels appear at the start of a line and ends with a colon. When the program is compiled into its binary form the branching instruction encodes the target as an integer offset from the branching instruction to the instruction marked with the given label. The IL disassembler generates a label for each instruction based on its offset from the start of the method. This is the IL_XXXX: that appears at the start of each line.

If we look past the branch instructions we will see two sets of instructions which print a formatted string to the console. This code is very similar to instructions to print the volume of a cylinder that we saw earlier except it uses Console.WriteLine to perform the formatting and not string.format. The first set of instructions prints "numberA ({0}) is larger than numberB ({1})" and the second prints "numberB ({0}) is larger than numberA ({1})". If we compare this to the original C# program we see these sets of instructions match the if and else blocks respectively.

From last time we know that ble.s will cause the program to jump if the second value it pops off the stack is less than or equal to the first value it pops off the stack. The target of the branch has the label IL_0021 which is on line 83. At this point in the program the stack and local variables would look like this.

Evaluation Stack Local Variables
Type Value Index Type Value
int32 17 0 int32 17
int32 96 1 int32 96
     

The first value popped off the stack will be 96 and the second will be 17. Since 17 is less than or equal to 96 the condition will be true and the program branches. The next instruction to be executed will be at line 83. The program will print out "numberB (96) is larger than numberA (17)" and then return.

The ble instruction checks if numberB <= numberA. If we compare this to the original program we see that this is the inverse of the if statement condition, why is that? If you think about it the if statement and branching are opposites. An If statement says "Execute this code if the condition is true" while a branch instruction says "Jump away from this code if the condition is true". So the compiler inverses the logic to better match the instructions.

If you compile this program in debug mode you will see that the un-optimized  version includes a greater-than comparison and then a branch false. This matches the original if statement better but requires more instructions. When running with optimizations on, the compiler tries to come up with the simplest set of instructions that functionally match the original program. This is also why both sets of instructions contain their own return instruction instead of a common return instruction which would have required the if set to end with a branch back to a point past the else instructions.

Next time we are going to do the same thing with three numbers.

Comments: