Learning about basic example of function call in SPARC assembly

2.7k views Asked by At

I am learning SPARC assembly with a simple example that you can see below. I have several questions about this example which shows passing parameters for a procedure.

In main section, I set 5 to first input parameter %o0 and 7 to second input parameter %o1. After, I do the sum of these registers and put it into %o2. Then, I call the "test" function where I print this sum.

fmt0:
  .asciz "%d\n"
  .align 4

.global main

main:
  save %sp, -64, %sp
  mov 5, %o0
  mov 7, %o1
  add %o0, %o1, %o2
  !st %o2, [%fp-4]   for storing %o2 at adress %fp-4 
  call test
  nop
  ret

test:
  mov %i2, %o1
  !ld [%fp-4], %o1   for loading from %fp-4 into %o1
  set fmt0, %o0
  call printf
  nop
  ret

With this above code, it prints the value "-273929364" and not "12" (compiled with gcc).

It seems that "mov %i2, %o1" doesn't work. I know that %o2 register in main section becomes %i2 in called procedure but why I can't set directly the value of %i2 into %o1 register with this "mov" instruction ?

Second question: If I uncomment the instructions "st %o2, [%fp-4]" in main section and "ld [%fp-4], %o1" in test function and comment "mov %i2, %o1", it prints correctly "12". How can we know the correct offset to put as a function of passing parameters ?

From I have seen, %sp becomes %fp after "save %sp, -64, %sp" insctruction ? Has %fp the same value in main section and test function ?

Finally, I have seen on different examples the instruction "call function, 0" or "call printf, 0" : why do I have to add a "0" after the name of the function called ? Is this the returned value (like with int main(void){ ... return 0;}) ?

Thanks for your help

1

There are 1 answers

6
Eldar Abusalimov On BEST ANSWER

I know that %o2 register in main section becomes %i2 in called procedure but why I can't set directly the value of %i2 into %o1 register with this "mov" instruction?

%o registers only become %i after doing save, usually at the beginning of a function being called. In your example test function doesn't have save/restore.

It is save/restore that rotates register windows, not call/ret. Since test is not a leaf function (it calls printf from inside it), it must have its own register window. So you have to wrap test function with save/restore:

test:
  save %sp, -64, %sp
  mov %i2, %o1
  set fmt0, %o0
  call printf
   nop
  ret
   restore

Otherwise, your argument is still available through %i2, but anyway the code is wrong because call printf instruction would destroy a return address of test which is stored in %o7.

UPD.

Concerning a question in an edit suggestion (BTW don't do that, ask in comments instead):

If %o7 is overwritten in a non-leaf procedure, how to circumvent this problem? I think that I have to push %o7 at the beginning of the non-leaf procedure in another register and pop it at the end, i.e after the call of the nested procedure, is it right?

There is no problem in case of a non-leaf procedure: save/restore do the trick. You might think of save as a "batch" push: it provides you a new register window - a set of 16 registers (8 %i + 8 %l) that preserve their values across nested procedure calls. And accordingly, restore brings you back to a previously saved window.

All %o registers are accessible through %i in a new window. That is, the caller sets arguments to %o0 .. %o5 (up to 6, because %o6 and %o7 are reserved for stack pointer and return address). Callee makes save and gets the arguments from %i0 .. %i5. If it wants to return a value, it puts it into %i0. Upon returning it makes restore, and caller can see the return value (if any) in %o0.

This also answers to another your question:

From I have seen, %sp becomes %fp after "save %sp, -64, %sp" instruction? Has %fp the same value in main section and test function?

%sp is just an alias for %o6, and %fp - for %i6. Apart from rotating windows,save is also able to add values just like ordinal add instruction. save %sp, -64, %sp means the following: take a value of %sp of the old window, rotate windows (%sp becomes %fp), add -64 to that value and put the result into %sp of a new window.

In other words,

save %sp, -64, %sp

does the same as

save
add %fp, -64, %sp  ! notice that the source register is now %fp, not %sp

BTW, -64 is just the size of the register window (16 registers, 4 bytes each). And it is negative because stack grows down.

Here is an excellent answer explaining the concept of SPARC register windows.

UPD. 2

Just noticed your "Looking for an answer drawing from credible and/or official sources" statement. SPARC v8 architecture manual is a must-read, especially chapters covering delay slots, register windows, leaf procedure optimization and software considerations.