Closed Phantom1003 closed 4 years ago
That’s a GCC implementation detail, not an ABI guarantee. The ABI doesn’t require that the callee end with “jr ra”; there are other legal ways to return. So ra could contain garbage upon return. For example, this is a silly but correct implementation of “void foo() {}”:
foo:
mv t0, ra
li ra, 1234
jr t0
Since ra can contain an arbitrary value upon return, the caller must save ra if it cares about the current value (as it usually does).
On Thu, Jan 23, 2020 at 8:31 AM Yan notifications@github.com wrote:
Hi, I found that $ra is a caller saved register, and the most asm code I find compiled by gcc is callee style, just like MIPS. Since a "caller saved register" is "Not preserved across function call", but how could be possible for $ra, if the value has changed, how could be back ? Thanks ...
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/riscv/riscv-isa-manual/issues/478?email_source=notifications&email_token=AAH3XQTJ2VAB2IOX65DWNTLQ7HA6RA5CNFSM4KKZKFGKYY3PNVWWK3TUL52HS4DFUVEXG43VMWVGG33NNVSW45C7NFSM4IIJO3FA, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAH3XQU6P3QEVVR6BOFVG2LQ7HA6RANCNFSM4KKZKFGA .
Thanks for your quick reply, there are many conflict I meet, for example in riscv-linux, when context switch, it saves and reload ra + callee-saved registers (it should only save callee-saved register...), and also the asm compiled by gcc mentioned above. What are the benefits of this design ? (I means ra is caller-saved, not the above details, since the design may not fit some framework right now) And by the way, are there any compilers that strictly obey ABI?
GCC does obey the ABI.
Because function calls clobber ra, it really doesn’t make sense to me to describe ra as callee-saved, because if the caller wants to preserve ra, it must save it.
If by "context switch" you mean an asynchronous change from one software thread (or process) to another software thread, which could be induced by a hardware interrupt for example, then all of the registers (integer and floating point) could need to be saved on entry and restored on exit (there are some optimisations in the case of the floating point registers on some processors).
I suspect that in general you are perhaps confusing ABI interfaces (Pascal binary code linking into C binary code for example) with internal compiler calls and also with context switching. All three are very different.
On 23/01/2020 17:11, Yan wrote:
Thanks for your quick reply, there are many conflict I meet, for example in riscv-linux, when context switch, it saves and reload ra + callee-saved registers (it should only save callee-saved register...), and also the asm compiled by gcc mentioned above. What are the benefits of this design ? And by the way, are there any compilers that strictly obey ABI?
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/riscv/riscv-isa-manual/issues/478?email_source=notifications&email_token=AK35PAD4CGGWCERW3HFGRA3Q7HFULA5CNFSM4KKZKFGKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEJYDESI#issuecomment-577778249, or unsubscribe https://github.com/notifications/unsubscribe-auth/AK35PAB7YOHY2FAY7UN7AEDQ7HFULANCNFSM4KKZKFGA.
@PeterHarrisUK I means the process context switch in 'arch/riscv/kernel/entry.S':
/*
* Integer register context switch
* The callee-saved registers must be saved and restored.
*
* a0: previous task_struct (must be preserved across the switch)
* a1: next task_struct
*
* The value of a0 and a1 must be preserved by this function, as that's how
* arguments are passed to schedule_tail.
*/
ENTRY(__switch_to)
/* Save context into prev->thread */
li a4, TASK_THREAD_RA
add a3, a0, a4
add a4, a1, a4
REG_S ra, TASK_THREAD_RA_RA(a3)
REG_S sp, TASK_THREAD_SP_RA(a3)
REG_S s0, TASK_THREAD_S0_RA(a3)
REG_S s1, TASK_THREAD_S1_RA(a3)
REG_S s2, TASK_THREAD_S2_RA(a3)
REG_S s3, TASK_THREAD_S3_RA(a3)
REG_S s4, TASK_THREAD_S4_RA(a3)
REG_S s5, TASK_THREAD_S5_RA(a3)
REG_S s6, TASK_THREAD_S6_RA(a3)
REG_S s7, TASK_THREAD_S7_RA(a3)
REG_S s8, TASK_THREAD_S8_RA(a3)
REG_S s9, TASK_THREAD_S9_RA(a3)
REG_S s10, TASK_THREAD_S10_RA(a3)
REG_S s11, TASK_THREAD_S11_RA(a3)
/* Restore context from next->thread */
...
@aswaterman According to common classification, there are 2 classes in Register Conventions: For Caller, the register that
But I confused to define callee by using this, when callee is a no-leaf function, it calls other functions, the callee become a caller, so the option to save ra can be seen both caller-saved and callee-saved, but I prefer caller-saved, because changing ra is aimed to call a function
Since this definition is difficult to distinguish them here, let's change to:
For Callee
So, for ra, since it mantain the return address, it must save before use, even in your 'void foo()', it need to save ra in t0 before use ra, from this point, it seems more like a callee ... @_@
I think my issue is about call convention, not ABI. These days, I read some riscv-gcc source code, and I find that gcc do not care about caller-save or callee-save, it just want to know whether the register is clobbered during a function call.
So the call convention is the use standard for the asm programmer, with ra is caller saved, they can use ra to do some other things, just like the code Andrew mentioned, but NOT for gcc, gcc will never generate the code like that, since it is NOT defined as the register that volatile across calls.
/* a0-a7, t0-t6, fa0-fa7, and ft0-ft11 are volatile across calls.
The call RTLs themselves clobber ra. */
#define CALL_USED_REGISTERS /// 0 is callee-saved ! \
{ /* General registers. */ \
1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, \
1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, \
/* Floating-point registers. */ \
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, \
1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, \
/* Others. */ \
1, 1 \
}
Since ra mantains the return address, it must save before use, even in Andrew's 'void foo()', it still need to save ra in t0 before use ra. So, I suggest, regard the ra as a callee-saved register.
Whether ra is caller-save or callee-save depends on whether it belongs to the caller or the callee. Logically, it belongs to the callee, which makes it caller save. However, it is actually set in the caller via the jalr instruction, which makes it callee save. So whether we call it caller save or callee save is just semantics. It doesn't change how the register is used.
It is interesting to note that in the wikipedia page for the MIPS ABI, it uses N/A for the return address register. They apparently chose not to get involved in this semantics argument. https://en.wikipedia.org/wiki/Calling_convention#MIPS
Here is an example from stack overflow where they are discussing for MIPS whether ra is caller save or callee save and one guy makes the argument that it is neither. https://stackoverflow.com/questions/37906276/whether-ra-register-callee-saved-or-caller-saved-in-mips
In my opinion, ra is clearly caller-save, as the caller cannot rely on any particular value in ra upon return from a call. It is effectively another argument to the function saying where to return to, and like the other args can be overwritten by the callee, provided the callee has some other way of returning to the correct address at exit. That said, it would be extremely unlikely for a caller to actually need to use the value in ra, which is why this has not been important to define. (The caller could recreate the value trivially using "auipc ra, 0" at the return point).
I think the stack overflow analysis is clear and correct, except that it tries to separate ra from other args to the function, whereas I think it's just another arg. I think another source of confusion is that a register marked "caller-save" does not actually have to be saved by the caller before making a function call if it doesn't need the value.
When I learned the CS61C (MIPS), the ra is the register which preserved across function call, while in lastest CS61C (RISC-V) it is the register that not preserved across function call, I just wonder why RISC-V made such a change, like jim says "It doesn't change how the register is used", so I really really appreciate for you all to answering my unnecessary question. Actually, in my mind, I support Krste's mind, which ra belongs to callee, ra is a part of args, but I just can't find a example in asm compiled by gcc to support my mind. I don't think I should be bothered by these gcc details anymore ... Thank you all
The ra
register can be viewed as a special argument register. So it behaves exactly the same as any other general argument registers.
Hi, I found that $ra is a caller saved register, and the most asm code I find compiled by gcc is callee style, just like MIPS. Since a "caller saved register" is "Not preserved across function call", but how could be possible for $ra, if the value has changed, how could be back ? Thanks ...