0

Say I have a function foo defined as:

def foo(x,y):
  return x + y

and say I have function call:

foo(2,3)

which corresponds to the x86-64:

movq $2 %rdi
movq $3 %rsi
call foo

I am aware that if the function foo has a return value it will be stored in the register %rax. but what does this mean exactly?

the assembly would be something like:

movq $2 %rdi
movq $3 %rsi
call foo

foo:
    movq -8(%rsb) %rax
    addq -16(%rsb) %rax

can you please finish the definition of foo in assembly? does it use %rax? how does it use %rax?

My question is how %rax is used to get the return value of a function and how this value in %rax gets passed between the caller and callee. thanks

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
chez93
  • 131
  • 6
  • 3
    Your `foo` is wrong. Also `rsb` is a typo. `rax` is a register. Registers preserve their value upon `call` and `ret`. The callee just puts a value in there that the caller simply uses (same for the arguments but in reverse). Your `foo` could be `lea (%rdi, %rsi), %rax; ret` (or `mov %rdi, %rax; add %rsi, %rax; ret` for more readable version). – Jester Nov 22 '22 at 00:23
  • @Jester ah ok thanks. but what does it mean that the return value gets stored in %rax? – chez93 Nov 22 '22 at 00:29
  • 2
    Just that `%rax` is where the caller is expecting to find the return value (for simple types like int and pointer). – Erik Eidt Nov 22 '22 at 00:40
  • 2
    `rax` is a register. Whatever value is left in there when the callee returns is considered the return value. – Jester Nov 22 '22 at 00:41
  • You forgot the commas, and no, `foo` wouldn't load args from the stack, the caller passed them in registers! Look at actual optimized compiler output on https://godbolt.org/ (e.g. for the caller with just a prototype so it can't inline), and for the callee. And see [How to remove "noise" from GCC/clang assembly output?](https://stackoverflow.com/q/38552116) – Peter Cordes Nov 22 '22 at 08:34

1 Answers1

2

Just to illustrate how this works, I'll use your function foo as an example:

def foo(x,y):
  return x + y

main:
   z = foo(7,10) * 3

So here's how the value of z would be calculated. (Chances are what would actually happen is that the compiler would just precalculate 17*3 and load it as a constant, but that's not helpful for what I'm trying to demonstrate.)

main:
  mov  $7, %edi       # put integer args in 64-bit registers efficiently
  mov  $10, %esi      # with zero-extension of 32-bit
  call foo      

#the ret instruction at the end of "foo" takes us back here
#the result of "foo" is now in %rax, and the rest of the program can use it.

  mov %rax, %rdx    # Save a copy of %rax so we can multiply by 3.
  shl $1, %rax      # multiply by 2
  add %rdx, %rax    # add the original, this means we effectively multiplied by 3.

# lea (%rax, %rax, 2), %rax   # efficient way to multiply by 3

  ret              # exit program by returning from main()

####################################################

foo:
  add %rsi, %rdi    # add the two numbers
  mov %rsi, %rax    # return value in %rax
  ret

# compilers would have used  lea (%rdi, %rsi), %rax  to copy-and-add
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
puppydrum64
  • 1,598
  • 2
  • 15
  • I fixed your code for you; it might have been valid `.intel_syntax prefix`, but wasn't AT&T. (Also `;` isn't a comment character for GAS for x86).. AT&T syntax isn't just putting a `%` on register names; more importantly the destination is the right-most operand, and immediates require a `$`. – Peter Cordes Nov 23 '22 at 15:59
  • @PeterCordes Thank you, I'm not super familiar with 64-bit or AT&T. I misread the original post and thought $2 was on the right. – puppydrum64 Nov 23 '22 at 16:00