I am starting to learn assembly (x86-64 in NASM on OSX), and am now exploring how functions look in it.
Most resources explaining how "calling conventions" work show examples along the lines of this:
// c code
MyFunction1(a, b);
// assembly code
main:
push a
push b
push rbp ; save frame pointer on stack
mov rsp, rbp ; save stack pointer in frame pointer
xor rax, rax ; set function return value to 0.
call _MyFunction
mov rbp, rsp ; restore stack pointer
pop rbp ; restore frame pointer
ret ; return to calling function
(I just made that up after combining several resources, so there are probably many problems with that, but that's outside the main question.).
The gist of what calling conventions like the cdecl calling convention, is that:
- You push the arguments in reverse order from how they appear in the C code.
- You then save a reference to both the frame and stack pointers (I'm guessing so you can do this recursively, but haven't got that far yet to know).
- Then you compute whatever you need to in your function.
- And after that, pop the stack and frame pointers off the stack
So, in hopes of getting some more practical experience of working with the stack and function calling conventions in assembly, I was hoping to see how existing C compilers converted function calls into assembly (gcc and clang, using this which is great). But, I am not seeing that calling convention pattern (which every resource I've seen so far has said is the way to do it)!
Check it out, here is a relatively complex bit of assembly code generated from some C:
https://gist.github.com/lancejpollard/a1d6a9b4820473ed8797
Looking through that C code, there are a couple levels of nested function calls. But the output assembly code doesn't show that push/pop stack pattern!
So the question is, are these compilers just optimizing the assembly so as to avoid doing that? (Because, that example C code, while having some nested functions, is still pretty simple so I imagine the compiler can precompute a lot of stuff). Or am I missing something?