2016-07-02 - I Dream of School
I haven't been in school for years but for some reason I still have the occasional school dream. Dreams where I'm late to class, didn't study, or got lost in the school. Things that I have absolutely no reason to worry about and yet my brain still brings them up. The weirdest part is that in the dreams I start buying into the idea of being in back in school.
I remember one dream recently where I was in class and couldn't find my textbook. I started thinking I should get a locker so I always know where my books. The idea of being back in school seemed completely normal and my brain started problem solving the issue. Then I woke up and realized I don't have to go to school. None of this is relevant to me and it doesn't matter anymore.
I want to realize I am having a school dream while I am having it so I can run out of the class screaming "Screw you guys, I don't have to be here". That would be fun.
2016-06-18 - In IL: Branching Instructions
Conditional logic is what allows a program to appear as though it's making decisions. Certain parts of code are executed only if specific conditions are met. In higher level programming languages this would be handled by if, switch, or select statements. In IL these decisions are handled by branching instructions. Instructions that change the next instruction to be executed if certain conditions are true.
Generally instructions are executed one after the other. A branching instruction causes a jump. Instead of executing the next instruction it jumps to the instruction indicated by the branching instruction. It then continues to executes instructions after that point until another branching instruction is found.
br (BRanch)
The unconditional branch instruction always jumps to the instruction indicated by its argument.
Instruction | Description | Binary Format |
---|---|---|
br.s<target> | Short branch unconditional | 0x2B <int8> |
br | Branch unconditional | 0x38 <int32> |
brfalse (BRanch FALSE), brtrue (BRanch TRUE)
Pops a single value off of the stack. The brfalse instruction jumps to the instruction indicated by its argument if the value is 0. The brtrue instruction jumps to the instruction indicated by its argument if the value is nonzero.
The brfalse instruction is aliased as brnull (BRanch NULL) and brzero (BRanch ZERO). The brtrue function is aliased as brinst (BRanch INSTance)
Instruction | Description | Binary Format |
---|---|---|
brfalse.s<target> | Short branch if false (brnull.s, brzero.s) | 0x2C <int8> |
brtrue.s<target> | Short branch if true (brinst.s) | 0x2D <int8> |
brfalse | Branch if false (brnull, brzero) | 0x39 <int32> |
brtrue | Branch if true (brinst) | 0x3A <int32> |
beq (Branch EQual), ceq (Compare EQual)
Pops two values off of the stack, and compares them. The branching instruction jumps to the instruction indicated by its argument if they are equal. The compare instruction pushes 1 onto the stack if they are equal and 0 otherwise.
Instruction | Description | Binary Format |
---|---|---|
beq.s <target> | Short branch if values are equal | 0x2E <int8> |
beq <target> | Branch if values are equal | 0x3B <int32> |
ceq | Compares if values are equal | 0xFE 01 |
bge (Branch Greater-than or Equal)
Pops two values off of the stack, and compares them. The branching instruction jumps to the instruction indicated by its argument if the second value popped is greater than or equal to the first.
Instruction | Description | Binary Format |
---|---|---|
bge.s <target> | Short branch greater than or equal | 0x2F <int8> |
bge.un.s<target> | Short branch greater than or equal unsigned | 0x34 <int8> |
bge | Branch greater than or equal | 0x3C <int32> |
bge.un | Branch greater than or equal unsigned | 0x41 <int32> |
bgt (Branch Greater-Than), cgt (Compare Greater-Than)
Pops two values off of the stack, and compares them. The branching instruction jumps to the instruction indicated by its argument if the second value popped is greater than the first. The compare instruction pushes 1 onto the stack if the second value popped is greater than the first and 0 otherwise.
Instruction | Description | Binary Format |
---|---|---|
bgt.s <target> | Short branch greater than | 0x30 <int8> |
bgt.un.s<target> | Short branch greater than unsigned | 0x35 <int8> |
bgt | Branch greater than | 0x3D <int32> |
bgt.un | Branch greater than unsigned | 0x42 <int32> |
cgt | Compare greater than | 0xFE 02 |
cgt.un | Compare greater than unsigned | 0xFE 03 |
ble (Branch Less-than or Equal)
Pops two values off of the stack, and compares them. The branching instruction jumps to the instruction indicated by its argument if the second value popped is less than or equal to the first.
Instruction | Description | Binary Format |
---|---|---|
ble.s<target> | Short branch less than or equal to | 0x31 <int8> |
ble.un.s<target> | Short branch less than or equal to unsigned | 0x36 <int8> |
ble | Branch less than or equal to | 0x3E <int32> |
ble.un | Branch less than or equal to unsigned | 0x43 <int32> |
blt (Branch Less-Than), clt (Compare Less-Than)
Pops two values off of the stack, and compares them. The branching instruction jumps to the instruction indicated by its argument if the second value popped is less than the first. The compare instruction pushes 1 onto the stack if the second value popped is less than the first and 0 otherwise.
Instruction | Description | Binary Format |
---|---|---|
blt.s<target> | Short branch less than | 0x32 <int8> |
blt.un.s<target> | Short branch less than unsigned | 0x37 <int8> |
blt | Branch less than | 0x3F <int32> |
blt.un | Branch less than unsigned | 0x44 <int32> |
clt | Compare less than | 0xFE 04 |
clt.un | Compare less than unsigned | 0xFE 05 |
bne (Branch Not Equal)
Pops two values off of the stack, and compares them. The branching instruction jumps to the instruction indicated by its argument if they are not equal.
Instruction | Description | Binary Format |
---|---|---|
bne.un.s<target> | Short branch unequal unsigned | 0x33 <int8> |
bne.un | Branch unequal unsigned unsigned | 0x40 <int32> |
and, or, xor, not
These instructions perform the specified bitwise action on values popped from the stack. The not instruction takes a single value while the rest take two.
Instruction | Description | Binary Format |
---|---|---|
and | Bitwise and | 0x5F |
or | Bitwise or | 0x60 |
xor | Bitwise exclusive-or | 0x61 |
not | Bitwise complement | 0x66 |
Next time we will look at if statements and how they get represented in IL.
2016-05-28 - Every Programming Language is Alright
I love programming languages. I want to learn as many programming languages as I can. I find all the differences between languages to be fascinating. The way two similar languages choose to focus on different things. The different way languages solve the same problem. While reading about programming languages I sometimes come across very general comparisons. Language Y is better than Language X. Language Y sucks, use Language Z instead. These kind of statements bug me because they miss the point of why multiple languages exist.
There are very few accidental languages. No one has ever been bashing their keyboard and went "Woops, I made a compiler" or "Damn, I inadvertently wrote a language specification". These things are difficult and very complex so people only put the time and effort into making them if they actually want them to exist for some reason. Languages also only become popular if people want to use them for some reason. It's those reasons which are the key.
If you look at why different programming languages exist you will often find that their creators wanted to solve a particular type of problem and felt that the existing languages weren't suitable for doing so. C was created to make it easier to develop operating systems. Basic was designed to make it easier for non-mathematicians to program. The multitude of web frameworks exist to try and find an easy way of making web applications. The problem that a language was created to solve should be the focus of most comparisons.
Language Y is better at solving A Problems than Language X. Language Y sucks at solving B Problems, use Language Z instead. There are no languages that are good at solving all problems and there are no languages that aren't good at solving any problems. By giving context to the comparison it becomes less general and more specific.
2016-05-07 - In IL: Volume of a Cylinder (Operations)
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.
Once you compile this program and disassemble the IL you get something like this. (Note: I compiled in release mode so optimizations are enabled)
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.