News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

Assembly RISC-V calling convention

Started by n1k0s, March 18, 2024, 12:47:37 PM

Previous topic - Next topic

n1k0s

Hey people, I am curerntly working on a linked list project but i must follow all calling conventions regarding tmp and saved regs. Now, ive read previous posts about them in this sub and in others yet i still do not understand some things.

What i do know (correct me if im wrong):

* S regs are callee-saved , so we store smth in them when we want that to be preserved across function calls or when that register/variable has a long lifetime.
* T regs are used for short-term operations, and its the caller's responsibility to save to the stack **if** whatever is in there is important.

Now here are my questions:

1. When calling a child function that uses an S register, do i have to **always** save/restore at the start and end of that child function, or only when the caller, or the caller's caller etc need that value? Lets say for example , that i do know ( as a programmer of my program) that the value of S is not needed by the caller function after child function returns ( i  know a tmp reg would be better here, but lets say im forced to have an S register). After that, our S register is "dead" (as the caller does not need its value anymore), so when some other child is changing its contents, do i still have to save/restore the contents to adhere to the calling convention ?
2. In a loop that calls a ***leaf*** function where would the counter be saved, if the register of the counter is:
   1. used by callee
   2. not used by callee
3. If i have some arguments in a0,a1 in a child procedure but these are needed after the call , why save/restore to stack and not just assign them to some temp registers that **I KNOW** are not going to be destroyed by the child (i know as my program's programmer)?\[Assuming child is a leaf procedure here\]
4. In general, **when** is it needed to save/restore to the **stack** whatever is in S and T registers, when its possible to bypass this for certain, small programs that you know that some registers go unused by all procedures?
5. When first putting a value in an S register, do i have to worry about some other program being linked to mine will be using that so i need to save/restore S even in the beginning? When I know my program will not be linked with smth else, do i still save/restore? What happens to S if Indeed its being linked to some other program that i do not know anything about?

Thank you for your time and sorry for the lengthy post. I just had a lot on my mind and wanted to share it with you as my professor and all his TA's keep telling me the same thing over and over (the two bullets above), with no real world examples and a lot of abstraction. Apologies for any grammar/spelling mistakes, english is not my native language.

NR.

NoCforMe

Wellll ... I think most of us understand pretty well the concept of what you're calling "S registers" and "T registers". Thing is, that terminology is pretty much Greek to us here, since we (most of us, anyhow) use assembly language on Intel/AMD processors (X86/X64), and not the RISC processor that you're apparently using. Other than that, yes, there are what we refer to as "volatile" (can be changed arbitrarily) and "non-volatile" (must be preserved) registers.

In the MASM32 world, at least on the Win32 platform, these registers are considered non-volatile and must be preserved across function calls: EBX, ESI, EDI, EBP. The other ones can all be trashed at will.

And of course you understand that this convention is enforced by the operating system, not the processor, right? The processor doesn't care particularly what you do with registers, but it's on account of the ABI (application binary iterface) of the OS. In other words, the OS makes the rules of the game here.
Assembly language programming should be fun. That's why I do it.

jj2007

Quote from: n1k0s on March 18, 2024, 12:47:37 PMwith no real world examples and a lot of abstraction

mov esi, 123  ; a non-volatile register (S)
mov ecx, 456  ; a temporary, volatile register (T)
mov eax, 789  ; another T register
push eax      ; save a register that will get trashed by Windows
invoke CreateWindowEx, ... a call to Windows
pop eax
print str$(eax), " is safe, hooray", 13, 10
print str$(ecx), " has been trashed by the evil OS", 13, 10
print str$(esi), " is still intact", 13, 10

This is all you need to know about your code: the OS will trash eax, ecx, edx. So if the OS is not involved, do whatever you need.

Let's turn to the OS' point of view. From time to time, the OS calls your code ("callback function"), and expects that you don't change its precious non-volatile registers.

The main callback function you have to care about is WndProc:
WndProc proc uses esi edi ebx hWnd, uMsg, wParam:WPARAM, lParam:LPARAM
  SWITCH uMsg
  CASE WM_CREATE
...
  CASE WM_DESTROY    ; quit after WM_CLOSE
    invoke PostQuitMessage, NULL
  ENDSW
  invoke DefWindowProc, hWnd, uMsg, wParam, lParam
  ret
WndProc endp

The uses esi edi ebx creates a series of push reg ... pop reg pairs to ensure that Windows finds its own precious registers unchanged.

Same code but explicit push & pop:
WndProc proc hWnd, uMsg, wParam:WPARAM, lParam:LPARAM
  push esi
  push edi
  push ebx
  SWITCH uMsg
  CASE WM_CREATE
...
  CASE WM_DESTROY    ; quit after WM_CLOSE
    invoke PostQuitMessage, NULL
  ENDSW
  invoke DefWindowProc, hWnd, uMsg, wParam, lParam
  pop esi
  pop edi
  pop ebx
  ret
WndProc endp

See also The "register gets trashed" trap, and note that after the "hooray" line above, eax is, of course, trashed - for a simple print, you need to call Windows under the hood.

Everything NoCForMe writes is correct, of course.

P.S., our tests show that recent versions of Windows couldn't care less if you respect the ABI or not:
  mov esi, 123
  mov edi, 456
  mov ebx, 789
  ret
WndProc endp

Finally, Microsh*t has realised that there are too many incompetent programmers around, so they decided to let the OS save esi edi ebx.
However, if you propose that "solution" to your profs, they will eat you alive: it's not documented and therefore tabooooooo!

NoCforMe

Quote from: jj2007 on March 18, 2024, 09:18:16 PMP.S., our tests show that recent versions of Windows couldn't care less if you respect the ABI or not:
  mov esi, 123
  mov edi, 456
  mov ebx, 789
  ret
WndProc endp

Finally, Microsh*t has realised that there are too many incompetent programmers around, so they decided to let the OS save esi edi ebx.
However, if you propose that "solution" to your profs, they will eat you alive: it's not documented and therefore tabooooooo!

Just to be a little more clear here: Yes, the consensus 'round here seems to be that you do not need to worry about any non-volatile registers in your code that is called by Windows (i.e., your "WinMain" function, no matter what it's called).

However, though it may be obvious, it bears remembering that Windows itself absolutely does respect the ABI when you call any Windows function, meaning that the non-volatile registers (EBX, ESI, EDI) will be preserved across the function call. And it's good programming practice to follow this in your own code: unless it's known that saving these registers in a function isn't required, you should save and restore them in your functions as well.

Example: I often use EBX as a pointer to a list of structures to be processed sequentially. So I rely on both Windows and my own private functions to not disturb this register:
MOV EBX, OFFSET SomeList
doloop: MOV EAX, [EBX].SOMELIST.somePtr
CALL DoSomePtr
MOV EAX, [EBX].SOMELIST.someHandle
INVOKE BringWindowToTop, EAX ;Win32 function

. . .

ADD EBX, SIZEOF SOMELIST ;Next array element
CMP someSignalValue, 0
JNE doloop
Assembly language programming should be fun. That's why I do it.

sinsi

Basically, if you use a non-volatile register (your S register) your subroutine should save it and restore it on exit.
Any non-volatile register (T) is fair game and can/will be clobbered.

Looking further into it, this (warning: pdf file) document might help
Tá fuinneoga a haon déag níos fearr :biggrin:

lucho

I fully agree with the previous reply to the question of the original poster, but will still try to answer myself too below:

Quote from: n1k0s on March 18, 2024, 12:47:37 PM1. When calling a child function that uses an S register, do i have to **always** save/restore at the start and end of that child function, or only when the caller, or the caller's caller etc need that value? Lets say for example , that i do know ( as a programmer of my program) that the value of S is not needed by the caller function after child function returns ( i  know a tmp reg would be better here, but lets say im forced to have an S register). After that, our S register is "dead" (as the caller does not need its value anymore), so when some other child is changing its contents, do i still have to save/restore the contents to adhere to the calling convention ?
The best practice is to follow the ABI strictly, i.e. ALWAYS save the callee-saved registers, even if you know that the function you call is a leaf function. Because one day it may CEASE to be a leaf function.

Quote2. In a loop that calls a ***leaf*** function where would the counter be saved, if the register of the counter is:
  1. used by callee
  2. not used by callee
Again, even if (2) is valid NOW, how can you be sure that it will always be? So, save the counter in the caller function.

Quote3. If i have some arguments in a0,a1 in a child procedure but these are needed after the call , why save/restore to stack and not just assign them to some temp registers that **I KNOW** are not going to be destroyed by the child (i know as my program's programmer)?\[Assuming child is a leaf procedure here\]
The argument registers are by definition ( http://five-embeddev.com/quickref/regs_abi.html ) caller-saved, so if you need to use their old values after you call the function (leaf or not), you have to save them too.

Quote4. In general, **when** is it needed to save/restore to the **stack** whatever is in S and T registers, when its possible to bypass this for certain, small programs that you know that some registers go unused by all procedures?
Always save in the stack the calle-saved registers when you're called, and the caller-saved ones when you call.

Quote5. When first putting a value in an S register, do i have to worry about some other program being linked to mine will be using that so i need to save/restore S even in the beginning? When I know my program will not be linked with smth else, do i still save/restore? What happens to S if Indeed its being linked to some other program that i do not know anything about?
If by "linked" you mean "called", how do you know that it won't be called? Don't you write it exactly in order for it to be called by other functions? In general, follow strictly the ABI and don't worry. It's like the traffic code. If everybody follows it strictly and there are not technical problems with any vehicle and no health or mental problems with any people, there will be no traffic incidents.

tenkey

Quote from: n1k0s on March 18, 2024, 12:47:37 PMWhat i do know (correct me if im wrong):

* S regs are callee-saved , so we store smth in them when we want that to be preserved across function calls or when that register/variable has a long lifetime.
* T regs are used for short-term operations, and its the caller's responsibility to save to the stack **if** whatever is in there is important.

If you're calling function F, F will not change a callee-saved register. This can be faked by F, saving the register at entry and restoring it at exit. The preservation property is a guarantee. It is also known as call-preserved (or nonvolatile).

The other kind of register is caller-saved. No guarantee that F will change or not change the register. The preservation property is also known as call-clobbered (or volatile) because you must assume it can be changed by F.