简体   繁体   中英

Using "saved" registers in the main function at RISC-V Assembly

Suppose the simple following main function written in RISC-V Assembly:

.globl main
main:
     addi s3,zero,10 #Should this register (s3) be saved before using?

Since s3 is a "saved register", the procedure calling conventions should be followed and thus, this register should be pushed to the stack before using it. However, by looking at the source file, no other procedure has used this register and saving the register to the stack seems redundant.

My question is, should these types of registers be saved every time before every usage even if it means writing more (redundant) code just to obey the calling conventions? Can these conventions sometimes be ignored to improve performance?

In the example above, should the register be saved because it is unknown if the main's caller has been using the s3 register?

Yes, main is a function that has a real caller you return to, and that caller might be using s3 for something.

Unless your main never returns , either being an infinite loop or only exiting by calling exit or a system call. If you never return, you don't need to be able to restore the caller's state, or even find your way back (via a return address).

So if it's just as convenient to call exit instead of ever returning from main, doing that allows you to avoid saving anything.

This also applies in cases where there's nothing for main to return to, of course, so returning wasn't even an option. eg if it's the entry point in a kernel or other freestanding code.


Also, I hope you understand that saved every time before every usage means once per function that uses them, not separately around each separate block. And not saving call-clobbered registers around each function call; just let them die .

Can these conventions sometimes be ignored to improve performance?

Yes, if you keep the details invisible to any code you don't control.

If you treat small private helper functions as actually part of one big function, then they can use a "private" custom calling convention. (Even if you do actually call / return instead of just jumping to them, if you want to avoid inlining them at multiple callsites)

Sometimes this is just taking advantage of extra guarantees when you know about the function you're calling. eg that it doesn't actually clobber some of its input arg registers. This can be useful in recursion when you're calling yourself: foo(int *p, int a) self calls might take advantage of p still being in the same register unmodified, instead of having to keep p somewhere else for use after the call returns like it would if calling an "unknown" function where you can't assume anything the calling convention doesn't guarantee.

Or if you have a publicly-visible wrapper in front of your actual private recursive function, you can set up some some constants, or even have the recursive function treat one register as a static variable, instead of passing around pointers to some shared state in memory. (That's no longer pure recursion, just a loop that uses the asm stack to keep track of some history that happens to include a jump address.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM