home comics writing pictures archive about

2017-03-18 - In IL: Prize Calculator (switch-2)

In IL

Last time we looked at a switch statement in C# which was implemented in IL by the switch instruction. The IL switch instruction is a lot more restrictive than the C# switch statement, for example the instruction expects its options to be continuous and only supports integers. The statement on the other hand can have large gaps between its options and also supports strings. So what happens when we have a switch statement that can't easily be implemented using the instruction?

The following program determines a user's prize based on a number. Only specific numbers get prizes and there's large gaps between winning numbers.

Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace csSwitch2
{
class Program
{
static void Main(string[] args)
{
int position = 266;
switch (position)
{
case 45:
Console.WriteLine("You win a car");
break;
case 102:
Console.WriteLine("You win a TV");
break;
case 513:
Console.WriteLine("You win a cheeseburger");
break;
case 668:
Console.WriteLine("You win a hat");
break;
case 998:
Console.WriteLine("You win a potted plant");
break;
case 1054:
Console.WriteLine("You win a house");
break;
case 2045:
Console.WriteLine("You win a carrot");
break;
case 2102:
Console.WriteLine("You win a teddy bear");
break;
case 2513:
Console.WriteLine("You win a double cheeseburger");
break;
case 2668:
Console.WriteLine("You win a toque");
break;
case 2998:
Console.WriteLine("You win a pot of gold");
break;
case 3054:
Console.WriteLine("You win a mouse");
break;
default:
Console.WriteLine("You win nothing");
break;
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

Their are 12 options with a range of 3009. That would require the switch instruction to have an array with 3009 addresses where 2997 of them point to the default case. Let's see what the compiler generates for us.

Main
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 283 (0x11b)
.maxstack 2
.locals init ([0] int32 position)
IL_0000: ldc.i4 0x10a
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: ldc.i4 0x41e
IL_000c: bgt.s IL_004a
IL_000e: ldloc.0
IL_000f: ldc.i4 0x201
IL_0014: bgt.s IL_002d
IL_0016: ldloc.0
IL_0017: ldc.i4.s 45
IL_0019: beq.s IL_008c
IL_001b: ldloc.0
IL_001c: ldc.i4.s 102
IL_001e: beq.s IL_0097
IL_0020: ldloc.0
IL_0021: ldc.i4 0x201
IL_0026: beq.s IL_00a2
IL_0028: br IL_0110
IL_002d: ldloc.0
IL_002e: ldc.i4 0x29c
IL_0033: beq.s IL_00ad
IL_0035: ldloc.0
IL_0036: ldc.i4 0x3e6
IL_003b: beq.s IL_00b8
IL_003d: ldloc.0
IL_003e: ldc.i4 0x41e
IL_0043: beq.s IL_00c3
IL_0045: br IL_0110
IL_004a: ldloc.0
IL_004b: ldc.i4 0x9d1
IL_0050: bgt.s IL_006f
IL_0052: ldloc.0
IL_0053: ldc.i4 0x7fd
IL_0058: beq.s IL_00ce
IL_005a: ldloc.0
IL_005b: ldc.i4 0x836
IL_0060: beq.s IL_00d9
IL_0062: ldloc.0
IL_0063: ldc.i4 0x9d1
IL_0068: beq.s IL_00e4
IL_006a: br IL_0110
IL_006f: ldloc.0
IL_0070: ldc.i4 0xa6c
IL_0075: beq.s IL_00ef
IL_0077: ldloc.0
IL_0078: ldc.i4 0xbb6
IL_007d: beq.s IL_00fa
IL_007f: ldloc.0
IL_0080: ldc.i4 0xbee
IL_0085: beq.s IL_0105
IL_0087: br IL_0110
IL_008c: ldstr "You win a car"
IL_0091: call void [mscorlib]System.Console::WriteLine(string)
IL_0096: ret
IL_0097: ldstr "You win a TV"
IL_009c: call void [mscorlib]System.Console::WriteLine(string)
IL_00a1: ret
IL_00a2: ldstr "You win a cheeseburger"
IL_00a7: call void [mscorlib]System.Console::WriteLine(string)
IL_00ac: ret
IL_00ad: ldstr "You win a hat"
IL_00b2: call void [mscorlib]System.Console::WriteLine(string)
IL_00b7: ret
IL_00b8: ldstr "You win a potted plant"
IL_00bd: call void [mscorlib]System.Console::WriteLine(string)
IL_00c2: ret
IL_00c3: ldstr "You win a house"
IL_00c8: call void [mscorlib]System.Console::WriteLine(string)
IL_00cd: ret
IL_00ce: ldstr "You win a carrot"
IL_00d3: call void [mscorlib]System.Console::WriteLine(string)
IL_00d8: ret
IL_00d9: ldstr "You win a teddy bear"
IL_00de: call void [mscorlib]System.Console::WriteLine(string)
IL_00e3: ret
IL_00e4: ldstr "You win a double cheeseburger"
IL_00e9: call void [mscorlib]System.Console::WriteLine(string)
IL_00ee: ret
IL_00ef: ldstr "You win a toque"
IL_00f4: call void [mscorlib]System.Console::WriteLine(string)
IL_00f9: ret
IL_00fa: ldstr "You win a pot of gold"
IL_00ff: call void [mscorlib]System.Console::WriteLine(string)
IL_0104: ret
IL_0105: ldstr "You win a mouse"
IL_010a: call void [mscorlib]System.Console::WriteLine(string)
IL_010f: ret
IL_0110: ldstr "You win nothing"
IL_0115: call void [mscorlib]System.Console::WriteLine(string)
IL_011a: 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
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185

The compiler generated a ton of branching statements but no switch instructions. So what are those branching instructions doing? Let's map them out.

It starts by branching if the number is greater than 1054. This is the middle option of our switch statement. If it is greater than 1054 it then branches if the value is greater than 2513, else it continues on and branches if the value is greater than 513. 2513 is the middle option of the block greater than 1054 and 513 is the middle option of the block less than or equal to 1054. You may recognize this process of repeatedly dividing the options in half and checking if the value is greater than or less than the half way value as binary search. Instead of checking every value it checks ranges and focuses in on the value. It only checks for equality when it gets down to a few remaining options. This way it only takes 5 comparisons to get to the largest value of 3054 instead of 12. Now let's go a step further and see what happens if we use strings instead of integers.

Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace csSwitch3
{
class Program
{
static void Main(string[] args)
{
string position = "266";
switch (position)
{
case "45":
Console.WriteLine("You win a car");
break;
case "102":
Console.WriteLine("You win a TV");
break;
case "513":
Console.WriteLine("You win a cheeseburger");
break;
case "668":
Console.WriteLine("You win a hat");
break;
case "998":
Console.WriteLine("You win a potted plant");
break;
case "1054":
Console.WriteLine("You win a house");
break;
case "2045":
Console.WriteLine("You win a carrot");
break;
case "2102":
Console.WriteLine("You win a teddy bear");
break;
case "2513":
Console.WriteLine("You win a double cheeseburger");
break;
case "2668":
Console.WriteLine("You win a toque");
break;
case "2998":
Console.WriteLine("You win a pot of gold");
break;
case "3054":
Console.WriteLine("You win a mouse");
break;
default:
Console.WriteLine("You win nothing");
break;
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

As you can see this program is similar to the first except it uses a string variable and string case statements. Let's see what it compiles into.

Main
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 569 (0x239)
.maxstack 2
.locals init ([0] string position,
[1] uint32 V_1)
IL_0000: ldstr "266"
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: call uint32 '<PrivateImplementationDetails>'::ComputeStringHash(string)
IL_000c: stloc.1
IL_000d: ldloc.1
IL_000e: ldc.i4 0x710e415f
IL_0013: bgt.un.s IL_0066
IL_0015: ldloc.1
IL_0016: ldc.i4 0x4981fbad
IL_001b: bgt.un.s IL_0043
IL_001d: ldloc.1
IL_001e: ldc.i4 0x2e9ae600
IL_0023: beq IL_00db
IL_0028: ldloc.1
IL_0029: ldc.i4 0x3d24bbac
IL_002e: beq IL_012f
IL_0033: ldloc.1
IL_0034: ldc.i4 0x4981fbad
IL_0039: beq IL_0183
IL_003e: br IL_022e
IL_0043: ldloc.1
IL_0044: ldc.i4 0x55973d92
IL_0049: beq IL_0144
IL_004e: ldloc.1
IL_004f: ldc.i4 0x6933d7c2
IL_0054: beq.s IL_00c6
IL_0056: ldloc.1
IL_0057: ldc.i4 0x710e415f
IL_005c: beq IL_016e
IL_0061: br IL_022e
IL_0066: ldloc.1
IL_0067: ldc.i4 0x94902f75
IL_006c: bgt.un.s IL_008e
IL_006e: ldloc.1
IL_006f: ldc.i4 0x84aadb8f
IL_0074: beq.s IL_00f0
IL_0076: ldloc.1
IL_0077: ldc.i4 0x8ce38d62
IL_007c: beq.s IL_00b1
IL_007e: ldloc.1
IL_007f: ldc.i4 0x94902f75
IL_0084: beq IL_011a
IL_0089: br IL_022e
IL_008e: ldloc.1
IL_008f: ldc.i4 0x9aff1550
IL_0094: beq IL_0159
IL_0099: ldloc.1
IL_009a: ldc.i4 0xb0d0fd37
IL_009f: beq IL_0198
IL_00a4: ldloc.1
IL_00a5: ldc.i4 0xdbe097dd
IL_00aa: beq.s IL_0105
IL_00ac: br IL_022e
IL_00b1: ldloc.0
IL_00b2: ldstr "45"
IL_00b7: call bool [mscorlib]System.String::op_Equality(string,
string)
IL_00bc: brtrue IL_01aa
IL_00c1: br IL_022e
IL_00c6: ldloc.0
IL_00c7: ldstr "102"
IL_00cc: call bool [mscorlib]System.String::op_Equality(string,
string)
IL_00d1: brtrue IL_01b5
IL_00d6: br IL_022e
IL_00db: ldloc.0
IL_00dc: ldstr "513"
IL_00e1: call bool [mscorlib]System.String::op_Equality(string,
string)
IL_00e6: brtrue IL_01c0
IL_00eb: br IL_022e
IL_00f0: ldloc.0
IL_00f1: ldstr "668"
IL_00f6: call bool [mscorlib]System.String::op_Equality(string,
string)
IL_00fb: brtrue IL_01cb
IL_0100: br IL_022e
IL_0105: ldloc.0
IL_0106: ldstr "998"
IL_010b: call bool [mscorlib]System.String::op_Equality(string,
string)
IL_0110: brtrue IL_01d6
IL_0115: br IL_022e
IL_011a: ldloc.0
IL_011b: ldstr "1054"
IL_0120: call bool [mscorlib]System.String::op_Equality(string,
string)
IL_0125: brtrue IL_01e1
IL_012a: br IL_022e
IL_012f: ldloc.0
IL_0130: ldstr "2045"
IL_0135: call bool [mscorlib]System.String::op_Equality(string,
string)
IL_013a: brtrue IL_01ec
IL_013f: br IL_022e
IL_0144: ldloc.0
IL_0145: ldstr "2102"
IL_014a: call bool [mscorlib]System.String::op_Equality(string,
string)
IL_014f: brtrue IL_01f7
IL_0154: br IL_022e
IL_0159: ldloc.0
IL_015a: ldstr "2513"
IL_015f: call bool [mscorlib]System.String::op_Equality(string,
string)
IL_0164: brtrue IL_0202
IL_0169: br IL_022e
IL_016e: ldloc.0
IL_016f: ldstr "2668"
IL_0174: call bool [mscorlib]System.String::op_Equality(string,
string)
IL_0179: brtrue IL_020d
IL_017e: br IL_022e
IL_0183: ldloc.0
IL_0184: ldstr "2998"
IL_0189: call bool [mscorlib]System.String::op_Equality(string,
string)
IL_018e: brtrue IL_0218
IL_0193: br IL_022e
IL_0198: ldloc.0
IL_0199: ldstr "3054"
IL_019e: call bool [mscorlib]System.String::op_Equality(string,
string)
IL_01a3: brtrue.s IL_0223
IL_01a5: br IL_022e
IL_01aa: ldstr "You win a car"
IL_01af: call void [mscorlib]System.Console::WriteLine(string)
IL_01b4: ret
IL_01b5: ldstr "You win a TV"
IL_01ba: call void [mscorlib]System.Console::WriteLine(string)
IL_01bf: ret
IL_01c0: ldstr "You win a cheeseburger"
IL_01c5: call void [mscorlib]System.Console::WriteLine(string)
IL_01ca: ret
IL_01cb: ldstr "You win a hat"
IL_01d0: call void [mscorlib]System.Console::WriteLine(string)
IL_01d5: ret
IL_01d6: ldstr "You win a potted plant"
IL_01db: call void [mscorlib]System.Console::WriteLine(string)
IL_01e0: ret
IL_01e1: ldstr "You win a house"
IL_01e6: call void [mscorlib]System.Console::WriteLine(string)
IL_01eb: ret
IL_01ec: ldstr "You win a carrot"
IL_01f1: call void [mscorlib]System.Console::WriteLine(string)
IL_01f6: ret
IL_01f7: ldstr "You win a teddy bear"
IL_01fc: call void [mscorlib]System.Console::WriteLine(string)
IL_0201: ret
IL_0202: ldstr "You win a double cheeseburger"
IL_0207: call void [mscorlib]System.Console::WriteLine(string)
IL_020c: ret
IL_020d: ldstr "You win a toque"
IL_0212: call void [mscorlib]System.Console::WriteLine(string)
IL_0217: ret
IL_0218: ldstr "You win a pot of gold"
IL_021d: call void [mscorlib]System.Console::WriteLine(string)
IL_0222: ret
IL_0223: ldstr "You win a mouse"
IL_0228: call void [mscorlib]System.Console::WriteLine(string)
IL_022d: ret
IL_022e: ldstr "You win nothing"
IL_0233: call void [mscorlib]System.Console::WriteLine(string)
IL_0238: 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
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285

That's follows the same basic flows as the integer case but with a few differences. Firstly on line 68 the compiler used an internal function to generate a hash value for the string which is a unsigned integer and then it compares the computed hash against the pre-computed hashes for the string cases. Secondly instead of branching directly to the print statements it branches to a call to the string's equality function. This is to ensure that string actually matches the desired value and doesn't just have the same hash value.

Next time we will look at the Visual Basic .NET Select statement and see how it handles even more complicated cases.

Comments: