home comics writing pictures archive about

2016-09-18 - In IL: Largest of Three Numbers (If-ElseIf-Else)

In IL

Last time we looked at a program which used an If statement to determine which of two numbers was the largest. If statements can actually get a lot more complicated. They can be extended with additional else if clauses which allow alternative conditions to be tested. The condition can also contain multiple comparisons combined using logical operators.

Continuing from last time let's look at a program that reports which of three numbers is the largest using if, else if, else statements in Visual basic .NET.

Module1.vb
Module Module1
Sub Main()
Dim numberA As Integer = 17
Dim numberB As Integer = 96
Dim numberC As Integer = 204
If numberA > numberB AndAlso numberA > numberC Then
Console.WriteLine("numberA ({0}) is larger than numberB ({1}) and numberC ({2})",
numberA, numberB, numberC)
ElseIf numberB > numberA And numberB > numberC Then
Console.WriteLine("numberB ({0}) is larger than numberA ({1}) and numberC ({2})",
numberB, numberA, numberC)
Else
Console.WriteLine("numberC ({0}) is larger than numberA ({1}) and numberB ({2})",
numberC, numberA, numberB)
End If
End Sub
End Module
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Before we look at the compiled output I want to point out one thing. The If condition uses the AndAlso operator (&& in C#) while the ElseIf uses the And operator (& in C#). We will be able to see the difference between these two operators when we look at the compiled code. So let's do that now.

Main
.method public static void Main() cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// Code size 118 (0x76)
.maxstack 4
.locals init ([0] int32 numberA,
[1] int32 numberB,
[2] int32 numberC)
IL_0000: ldc.i4.s 17
IL_0002: stloc.0
IL_0003: ldc.i4.s 96
IL_0005: stloc.1
IL_0006: ldc.i4 0xcc
IL_000b: stloc.2
IL_000c: ldloc.0
IL_000d: ldloc.1
IL_000e: ble.s IL_0031
IL_0010: ldloc.0
IL_0011: ldloc.2
IL_0012: ble.s IL_0031
IL_0014: ldstr "numberA ({0}) is larger than numberB ({1}) and num"
+ "berC ({2})"
IL_0019: ldloc.0
IL_001a: box [mscorlib]System.Int32
IL_001f: ldloc.1
IL_0020: box [mscorlib]System.Int32
IL_0025: ldloc.2
IL_0026: box [mscorlib]System.Int32
IL_002b: call void [mscorlib]System.Console::WriteLine(string,
object,
object,
object)
IL_0030: ret
IL_0031: ldloc.1
IL_0032: ldloc.0
IL_0033: cgt
IL_0035: ldloc.1
IL_0036: ldloc.2
IL_0037: cgt
IL_0039: and
IL_003a: brfalse.s IL_0059
IL_003c: ldstr "numberB ({0}) is larger than numberA ({1}) and num"
+ "berC ({2})"
IL_0041: ldloc.1
IL_0042: box [mscorlib]System.Int32
IL_0047: ldloc.0
IL_0048: box [mscorlib]System.Int32
IL_004d: ldloc.2
IL_004e: box [mscorlib]System.Int32
IL_0053: call void [mscorlib]System.Console::WriteLine(string,
object,
object,
object)
IL_0058: ret
IL_0059: ldstr "numberC ({0}) is larger than numberA ({1}) and num"
+ "berB ({2})"
IL_005e: ldloc.2
IL_005f: box [mscorlib]System.Int32
IL_0064: ldloc.0
IL_0065: box [mscorlib]System.Int32
IL_006a: ldloc.1
IL_006b: box [mscorlib]System.Int32
IL_0070: call void [mscorlib]System.Console::WriteLine(string,
object,
object,
object)
IL_0075: ret
} // end of method Module1::Main
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146

As expected we have three sets of instructions which print text to the console. There are also three branching instructions used to implement the if-else chain.

Branching instructions are generally of the form Value1 Operation Value2 where Value1 is the second value popped off the stack, Value2 is the first value popped off the stack, and Operation is the condition specified by the branch instruction. Last time we determined Value1 and Value2 by running through the program and determining the values on the stack at the point of the branch instruction. This will become very time consuming as our programs get more and more complicated so I'd like to find an easier way.

The value at the top of the stack will be the last value pushed onto the stack and not removed. The value second from the top of the stack will be the second to last value pushed onto the stack and not removed. This means we can determine Value1 and Value2 by working backwards from the branch instruction and looking at what gets pushed onto the stack.

Looking at the first branch instruction on line 90 we see the first instruction before it loads local variable 1 while the second instruction before it loads local variable 0.  This means Value1 is NumberA and Value2 is NumberB. The instruction is ble so the condition is NumberA <= NumberB. Looking at the code this matches up with the first part of the if condition (Remember the compiler likes to inverse branch conditions compared to if conditions). If the condition is true we jump to line 110 which is the start of the ElseIf block.

The second branch instruction will only be encountered if the first condition is false. It is also a ble instruction but this time it compares local variable 0 and local variable 2. So the condition is NumberA <= NumberC which matches up with the second part of the if condition. If the condition is true we again jump to line 110. If the condition is false we execute the If block and print that NumberA is the largest.

If either of those conditions are true we branch to the ElseIf Condition. This time though we have two greater-than compare instructions and an and instruction followed by a brfalse instruction. The process for determining comparison values is the same as branching instructions so the full condition is NumberB > NumberA And NumberB > NumberC which is exactly the ElseIf condition. If the result is false we branch to the Else block otherwise we execute the ElseIf block and print that NumberB is the largest.

Why does the ElseIf condition perform the full comparison while the If condition had two branch instructions? As I mentioned earlier the ElseIf uses And while the If uses AndAlso. The difference between these two operators is that AndAlso is lazy. Since logical and requires all of its arguments to be true in order for the result to be true it's possible to know that the result is false after only checking some of the arguments if one of them turns out to be false. AndAlso uses this fact to skip the other comparison if it determines that the first is false. And on the other hand performs all comparisons.

AndAlso is able to make less comparisons which in some ways makes it more efficient. And always executes all comparisons which is important if some of those comparisons have side effects.

Next time we are going to look at the IL Switch instruction.

Comments: