home comics writing pictures archive about

2016-05-07 - In IL: Volume of a Cylinder (Operations)

In IL

So far we've looked at variables, stacks, and instructions. It's time to see how these things work together. For this we are going to use a C# program that calculates the volume of a cylinder.

Program.cs
using System;
namespace Operations
{
class Program
{
static void Main(string[] args)
{
int cylinderRadius = 3;
int cylinderHeight = 15;
double cylinderVolume = 3.141592654 * cylinderRadius *
cylinderRadius * cylinderHeight;
var message = string.Format(
"A Cylinder with radius {0} m and height {1} m has a volume of {2} m^3",
cylinderRadius, cylinderHeight, cylinderVolume);
Console.WriteLine(message);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Once you compile this program and disassemble the IL you get something like this. (Note: I compiled in release mode so optimizations are enabled)

Main
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 58 (0x3a)
.maxstack 4
.locals init ([0] int32 cylinderRadius,
[1] int32 cylinderHeight,
[2] float64 cylinderVolume)
IL_0000: ldc.i4.3
IL_0001: stloc.0
IL_0002: ldc.i4.s 15
IL_0004: stloc.1
IL_0005: ldc.r8 3.1415926540000001
IL_000e: ldloc.0
IL_000f: conv.r8
IL_0010: mul
IL_0011: ldloc.0
IL_0012: conv.r8
IL_0013: mul
IL_0014: ldloc.1
IL_0015: conv.r8
IL_0016: mul
IL_0017: stloc.2
IL_0018: ldstr "A Cylinder with radius {0} m and height {1} m has a volume of {2} m^3"
IL_001d: ldloc.0
IL_001e: box [mscorlib]System.Int32
IL_0023: ldloc.1
IL_0024: box [mscorlib]System.Int32
IL_0029: ldloc.2
IL_002a: box [mscorlib]System.Double
IL_002f: call string [mscorlib]System.String::Format(string, object, object, object)
IL_0034: call void [mscorlib]System.Console::WriteLine(string)
IL_0039: 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

So let's go through this program line by line and see if we can understand what it's doing.

.entrypoint

This is a directive that specifies that this is the function that should be called when the program is launched

.maxstack  4

This directive indicates that maximum number of items on the stack and therefore it's required size. In this case we have 4 items so our stack would look like this

Evaluation Stack
Type Value
   
   
   
   

And right now our stack is empty.

.locals init

This directive we've already seen, it specifies the local variables of the function. The init keyword indicates that variables are initialized to zero. So combining our stack and variables we have.

Evaluation Stack Local Variables
Type Value Index Type Value
    0 int32 0
    1 int32 0
    2 float64 0
     

ldc.i4.3

This instruction loads the value 3 as an int32 onto the stack.

Evaluation Stack Local Variables
Type Value Index Type Value
int32 3 0 int32 0
    1 int32 0
    2 float64 0
     

stloc.0

This instruction pops the value from the top of the stack and stores it in variable 0 which is cylinderRadius. This corresponds to "int cylinderRadius = 3" in the original program

Evaluation Stack Local Variables
Type Value Index Type Value
    0 int32 3
    1 int32 0
    2 float64 0
     

ldc.i4.s   15

stloc.1

These instructions load 15 using the immediate load short form instruction and then stores it in variable 1. This corresponds to "int cylinderHeight = 15".

Evaluation Stack Local Variables
Type Value Index Type Value
    0 int32 3
    1 int32 15
    2 float64 0
     

ldc.r8     3.1415926540000001

This instruction loads 3.141592654 as a float64 onto the stack.

Evaluation Stack Local Variables
Type Value Index Type Value
float64 3.141592654 0 int32 3
    1 int32 15
    2 float64 0
     

ldloc.0

This instruction loads variable 0 onto the stack.

Evaluation Stack Local Variables
Type Value Index Type Value
float64 3.141592654 0 int32 3
int32 3 1 int32 15
    2 float64 0
     

conv.r8

This instruction converts the value on top of the stack into a float64. Because of the way floating point values work the value won't be exactly the same as the original value but we're going to ignore that for the sake of simplicity.

Evaluation Stack Local Variables
Type Value Index Type Value
float64 3.141592654 0 int32 3
float64 ~3.0 1 int32 15
    2 float64 0
     

mul

This instruction pops the top two values from the stack multiplies them together and then pushes the result onto the stack

Evaluation Stack Local Variables
Type Value Index Type Value
float64 ~9.424777962 0 int32 3
    1 int32 15
    2 float64 0
     

ldloc.0

conv.r8

mul

These instructions load variable 0 onto the stack again, convert it to float64 and multiplies it with the existing value on the stack.

Evaluation Stack Local Variables
Type Value Index Type Value
float64 ~28.274333886 0 int32 3
    1 int32 15
    2 float64 0
     

ldloc.1

conv.r8

mul

These instructions load variable 1 onto the stack, converts it to float64 and multiplies it with the existing value on the stack.

Evaluation Stack Local Variables
Type Value Index Type Value
float64 ~424.11500829 0 int32 3
    1 int32 15
    2 float64 0
     

stloc.2

This instruction pops the value from the top of the stack and stores it in variable 2 which is the cylinderVolume. The previous loads, converts, and multiplies correspond to the calculations to determine the value of cylinderVolume in the original program.

Evaluation Stack Local Variables
Type Value Index Type Value
    0 int32 3
    1 int32 15
    2 float64 ~424.11500829
     

ldstr      "A Cylinder with radius {0} m and height {1} m has a volume of {2} m^3"

This instruction loads a reference to the constant string onto the stack. Up until now we have been dealing only with value types which are stored entirely on the stack. Reference types are stored elsewhere and only a reference to them is stored on the stack. To indicate this I'm going to use square brackets [].

Evaluation Stack Local Variables
Type Value Index Type Value
string ["A Cylinder..."] 0 int32 3
    1 int32 15
    2 float64 ~424.11500829
     

ldloc.0

This instruction loads variable 0 onto the stack.

Evaluation Stack Local Variables
Type Value Index Type Value
string ["A Cylinder..."] 0 int32 3
int32 3 1 int32 15
    2 float64 ~424.11500829
     

box        [mscorlib]System.Int32

This instruction converts the value onto of the stack into a reference type. This involves creating a copy of its content and storing it elsewhere. A reference to the copy location is then stored on the stack. Note that this converts the value from the built-in int32 type to System.Int32 type defined in the standard library.

Evaluation Stack Local Variables
Type Value Index Type Value
string ["A Cylinder..."] 0 int32 3
System.Int32 [3] 1 int32 15
    2 float64 ~424.11500829
     

ldloc.1

box        [mscorlib]System.Int32

ldloc.2

box        [mscorlib]System.Double

These instructions load variables 1 and 2 onto the stack and them boxes them as reference types.

Evaluation Stack Local Variables
Type Value Index Type Value
string ["A Cylinder..."] 0 int32 3
System.Int32 [3] 1 int32 15
System.Int32 [15] 2 float64 ~424.11500829
System.Double [~424.11500829]  

call       string [mscorlib]System.String::Format(string, object, object, object)

This instruction calls the String.Format method. It pops the values on the stack as its arguments and pushes the result onto the stack. Note that the argument types to this function are objects. The variables had to be boxed so that they could be passed as objects to this function. The string on the stack is now the formatted string with the variables inserted into it.

Evaluation Stack Local Variables
Type Value Index Type Value
string ["A Cylinder..."] 0 int32 3
    1 int32 15
    2 float64 ~424.11500829
     

call       void [mscorlib]System.Console::WriteLine(string)

This instruction calls the Console.WriteLine method which prints it's arguments to the console. This method returns void so there's nothing pushed onto the stack. Notice that there's a step missing here. In the original program we stored the output of String.Format in a variable and then printed the variable. The compiler decided it didn't need that variable and removed it.

Evaluation Stack Local Variables
Type Value Index Type Value
    0 int32 3
    1 int32 15
    2 float64 ~424.11500829
     

ret

This instruction ends execution of the method. The stack is empty after calling Console.WriteLine which is a requirement for the return instruction. The local variables will also go out of scope at this point and eventually be cleaned up.

Next time we will look at branching instructions.

Comments: