Content: * Function call * Function execution (prologue and epilogue) * Speed up the virtual machine * Function call Function arguments are pushed on the stack in the reverse order (right to left) /* a function prototype */ string func(int arg1, float arg2, int arg3); /* a function call */ { int ret; ... ret = test(a, 2.1, 15); } The sequence of assembler instructions to put arguments on the stack. push $15 push $2.1 load a 0xffffffff ------------- | | ------------- | arg3 | ------------- | arg2 | ------------- | arg1 | ------------- <---- %sp | | ------------- 0x00000000 Call a function after putting arguments. call test 0xffffffff ------------- | | ------------- | arg3 | ------------- | arg2 | ------------- | arg1 | ------------- | ret address | ------------- <---- %sp | | ------------- 0x00000000 A call instruction puts the returning address on the stack and change the %pc register with a function address. * Function execution (prologue and epilogue) Each function starts en execution with a prologue. A prologue saves the previous value of %fp register and fixes a new value of %fp. When we will fix a new %fp we can address all variables and arguments in the function and don't care about %sp. We known only that %sp points to the last operation result. push %fp push %sp pop %fp 0xffffffff ------------- | | ------------- | arg3 | ------------- | arg2 | ------------- | arg1 | ------------- | ret address | ------------- | %fp | - previous frame pointer ------------- <---- %sp (new fixed %fp, %fp == %sp) | | ------------- 0x00000000 At this point we shrink the stack size to prepare a place for function variables. We shrink and grow the stack size each time when come to the new scope or leave it respectively. Since the stack grows to the less addresses we need to shrink it and vice versa. In our example we need place for three 4-bytes variables. push %sp push $12 /* size of all variables */ sub pop %sp 0xffffffff ------------- | | ------------- | arg3 | ------------- +16(%fp) | arg2 | ------------- +12(%fp) | arg1 | ------------- +8(%fp) | ret address | ------------- +4(%fp) | %fp | ------------- <---- %fp | local var1 | ------------- -4(%fp) | local var2 | ------------- -8(%fp) | local var3 | ------------- -12(%fp) | | ------------- 0x00000000 Parts described above are common for all functions. After that a function is executed by a virtual machine. Most instructions use the stack as a holder for their operands. The stack in the virtual machine is balanced it means that it can't be overflowed. Most instrutions pick operands, poke the result and change %sp value. If a function returns a value we need to move it to the right place at the end of the function execution just before a prologue. A calling code except putting all function arguments prepares a place for a return value. 0xffffffff ------------- | | ------------- | return value| | place | | | ------------- +20(%fp) | arg3 | ------------- +16(%fp) | arg2 | ------------- +12(%fp) | arg1 | ------------- +8(%fp) | ret address | ------------- +4(%fp) | %fp | ------------- <---- %fp | local var1 | ------------- -4(%fp) | local var2 | ------------- -8(%fp) | local var3 | -12(%fp) ------------- <---- %sp (in case of void return value) | last oper. | | data | | | | | ------------- <---- %sp | | ------------- 0x00000000 If a function don't return a value (void data type) we don't generate this code. Otherwise we need to place the return value below all arguments with one instruction load 20(%fp) or strload 20(%fp) dependes on the return data type. On the stack top we have the result of the last operations so it is a return value for us. Every function finishes with an epilogue. It makes oposite actions to a prologue. push %fp pop %sp pop %fp ret 0xffffffff ------------- | return value| | | | | ------------- | arg3 | ------------- | arg2 | ------------- | arg1 | ------------- <--- %sp | | ------------- 0x00000000 The last instruction in the function 'ret'. It takes a return value address from the stack top and changes the %pc to return to the calling code. A calling code grows a stack to remove all arguments. push %sp push $12 add pop %sp There is only a return value on the stack top now. 0xffffffff ------------- | return value| | | | | | | ------------- <---- %sp 0x00000000 * Speed up the virtual machine Some of code blocks can be atomic for the virtual machine: - epilogue; - prologue; - grow stack size; - shrink stack size. So this code blocks can be replaced with single instructions. It helps to increase the virtual machine execution speed. Epilogue: push %fp push %sp ---> ent pop %fp Prologue: push %fp pop %sp ---> leave pop %fp ret Grow stack size: push %sp push $12 ---> grow $12 add pop %sp Shrink stack size: push %sp push $12 ---> shrink $12 sub pop %sp