Learning about basic example of function call in SPARC assembly
Asked Answered
K

1

9

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

Krenn answered 6/12, 2014 at 23:53 Comment(0)
W
11

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.

Willie answered 7/12, 2014 at 0:18 Comment(6)
Shouldn't the restore appear before the ret?Fondness
In my code restore is executed in a delay slot of ret (notice a one space indentation). You could however perform restore first, but in such case you should use retl (which takes the return address from %o7), not ret (%i7).Willie
@EldarAbusalimov I've not seen the one-space indentation before. Just to clarify, that indentation indicates that the instruction is executed in the delay slot of the preceding instruction?Mckean
@Jonathon yes, you're right. This is just an agreement (AFAIK) for SPARC assembly. I've seen it in some projects, in Linux kernel, for instance. The only purpose is to improve readability of the code.Willie
@EldarAbusalimov I don't understand why we should put retl after restore. Indeed, restore in this case is executed before retl (in a delay slot), so %o7becomes %i7in both cases (restore after ret and ret after restore)Krenn
@Krenn No. If an instruction appears in a delay slot of another one, it doesn't mean that it is executed before the latter. Think of it as a momentum of a motion of a car: CPU executes ret, but because this instruction is a jump, CPU (the car) can't "turn" at once and also executes the next one "amain".Willie

© 2022 - 2024 — McMap. All rights reserved.