- Part 1 - Journey through the internals of .NET Sort
- Part 2 - Everything you wanted to know about Sorting in .NET - part I
- Part 3 - Everything you wanted to know about Sorting in .NET part 3
- Part 4 - Everything you wanted to know about Sorting in .NET part 2
- Part 5 - Everything you wanted to know about Sorting in .NET part 6
- Part 6 - Everything you wanted to know about Sorting in .NET part 5
- Part 7 - This Article
In this blog post we will answer the question
What is a calling convention?
- description how machine code looks like and how function work
A calling convention is like a contract that describes how the functions, on the
assembly code level, communicate with each other using
Contract defines things like:
- the way arguments are passed to a function
- how values are returned
- how the function name is decorated
It specifies how (at a low level) the compiler will pass input parameters to the function and retrieve its results once it’s been executed.1
If we go down to the lowest levels of
code, there is a
This is btw a Fibonnaacci number generation code in
machine code. I wouldn’t be able to write it that way, but what is important is that on this
lowest level it really doesn’t matter if this code was
It would an
impossible task(almost) to write code that way and that is why new abstractions were invented like
assembly language. Example below is the same
fibonacci number generation code but in
On this level which is still very low. We operate very close to
CPU using - registers, stacks, various operations like
jmp. Every machine supports different
instructions[x]. Some operate on very primitive instructions and some have more complicated ones. Example [x].
It is the same code but on different architecture with different instruction sets. It is just like diffeences on the
high level beetwen languages like
Java. Due to this differences you need to compile the code for specific machine. If you are int linux world, it is pretty standard procedure to download source code of some program, tool and build by your own on your machine for your machine specific architecture and instructions sets. More popular distributions have packages with already precompiled binaries. That is why things like
java virtual machine and
clr were created. They server as a intermidiary beetwen code and machine using
byte code and
JIT compilation, compiling code on the fly to match the machine.
If in the the end all there is ‘assembly’ then it should be easy to communicate beetwen different languages. Devil is in the details. The machine doesn’t really know which language ultimately generated instructions set that is execcuted by it. But the more we go
one level up the more differences are there. There are different compilers, different flags, different languages, different architecture, different contexts and different conventions.
One of the important difference, I want to talk aobut is
calling convention. As mentioned earlier it is like a contract exposed by one function to other to tell it how to communicate with it. If function
A uses different convention than function
B they won’t be able to call each other.
How does machine code works? how does code works? Turning machine?
Calling conventions can differ in many ways:
- storage of arguments - registers, stack
- how are the arguments added to the stack - left to right or right to left
- where do you put result of the function call (stack, register, memory)
- who is responsible for stack cleanup - caller or calle ( this makes a difference in assembly code size, if caller is cleaning up the stack - the compiler has to generate cleaning logic every time a function is called)
- who is responsible for
registersand bringing them back to previous state (before the function was called)
Analysing few examples, based on
x86 calling conventions[x].
If one of the functions expects call using
cdecl convention. It is expecting:
- arguments to be on the stack
- caller cleaning up the stack
If we then call this function using
fastcall convention both requirements won’t be met:
- for fastcall first
two) arguments are kept in the reggisters, and calle expectes this values on the stack when it is empty
- stack won’t be cleaned up as fastcall assumes that
calleeis responsible for that.
cdecl example [x]
This is a simple function that all it does is
multiply numbers. We have a
cdecl which is marked with
cdecl attribute to force this calling convention (this is actually default and this attribute is not needed).
I am compiling this code with two important flags:
- -m32 - forces 32 bit executable - without this flag calling coventions are ignored (couldn’t find why)
- -O0 - I don’t want to optimize this code as with such a simple example
-O1in the caller puts a static value
(2 * 3 = 6)
-fomit-frame-pointer- one optimization that removes
frame pointersto make the
asmcode a bit simpler. (At the end of this post there is a example without this optimization explained if you are curious what is the diffference).
Don’t keep the frame pointer in a register for functions that don’t need one. This avoids the instructions to save, set up and restore frame pointers; it also makes an extra register available in many functions. It also makes debugging impossible on some machines. [x]
It removes instructions
So how does the
asm code looks like with all this flags?
We can see all the characteristics of
cdecl call in this
code. I am going to compile now fastcall function using similar flags.
Example of fastcall [x]
third parameter to show that only first
two arguments are passed through the registers. There is one thing, I am going to change in the code to make it more readable -
fastcall looks like this.
But it can be simplified to this.
There is no need to reserver place on
stack, move values from registers to the
stack and then get values from the
stack. Compiler potentialy does it due to
Arguments are first saved in stack then fetched from stack, rather than be used directly. This is because the compiler wants a consistent way to use all arguments via stack access, not only one compiler does like that.[x]
In the end we will analyse this code.
We just discussed differences beetwen
cdecl but what if
caller function used different convention than
calle funciton ? Assuming for a while this scenario - the code will look like this.
In this example
fastcall function will use
somekind of data on the registers. It wouldn’t get the data it was expecting
2 but something. This would generate an unexpected and hard to debug behaviour. That is why
calling conventions are important. There is a long history behind them 3456
Next post in the series will expand on it and describe the link beetwen
calling conventions and
Diffs: (in the simplified code example)
- fastcall uses edx and ecx registers
- cdecl uses stack
- fastcall using mov esp, ebp cleans up the stack
- cdeccl doesnt do this the caller is ussing add esp, 16 to clea up the stack
I replaced leave instructions with mov esp, ebp pop ebp
sub esp add esp instructions are nicely explained here stack operations[x]