unwinding icon indicating copy to clipboard operation
unwinding copied to clipboard

What registers should be restored/saved by save_context and restore_context?

Open Tazdevil971 opened this issue 1 year ago • 3 comments

According to the itanium cxx ABI (section 1.6.3):

All registers specified as callee-saved by the base ABI are restored, as well as scratch registers GR15, GR16, GR17 and GR18 This means that not all registers need to be restored correctly, at least for itanium.

Currently for x86/x86-64 it only saves callee saved registers, and restores callee saved registers + some GP registers.

While for most of the other archs callee saved registers get saved, and almost all other registers (including FP ones) get restored.

Looking at how other unwind implementations do it:

  • gcc: (restore and save) is quite puzzling, as it seems that it does not save or restore any registers apart from the SP and IP registers.
  • llvm libunwind: (restore and save) seems to save (not only callee saved registers!) and restore every arch register.
  • nongnu libunwind: highly depends on the arch, but in general tries to make usage of getcontext/setcontext.

Meanwhile, if we look at how LLVM uses these registers, here is the relevant documentation on the topic and it says:

TargetLowering must implement both functions. The personality function passes the exception structure (a pointer) and selector value (an integer) to the landing pad through the registers specified by getExceptionPointerRegister and getExceptionSelectorRegister respectively. On most platforms, they will be GPRs and will be the same as the ones specified in the calling convention.

So if I'm not missing anything, for making LLVM EH work one could only restore callee saved registers (as per itanium ABI) and the two registers mentioned above. More in general those two registers are usually the first two argument passing registers defined by the ABI.

What should be the standard for this crate?

Should the target be LLVM? Should we maximize compatibility such as LLVM libunwind? Should a middle ground be chosen, restoring for example callee saved registers and argument passing registers?

I'm asking this because I want to add support for MIPS CPUs, but I couldn't really find a complete answer to the question "what registers should I save/restore?".

Tazdevil971 avatar Sep 17 '24 17:09 Tazdevil971

For the save routine, saving caller-saved registers are moot because these register can be considered already-clobbered when _Unwind_RaiseException is called.

For restore, I play a bit conservative and restore all registers. I think restoring callee-saved registers + 2 special registers are probably sufficient, but I think what registers to be used ultimately is decided by language, and some hypothetical codegen could use any GPR with a custom personality routine that sets them.

For MIPS there's an open PR, which I think is almostly there, you might want to take a look first.

nbdd0121 avatar Sep 17 '24 20:09 nbdd0121

This might not be the best place to discuss this, but one of the reason for this issue is the MIPS PR.

It currently doesn't support FP registers very well because rust doesn't have a good way to detect hardware floating point support. This is especially problematic because the PR is for MIPS32 only, and one really popular CPU in that category is the R3000 (used in the PlayStation 1) which doesn't have hardware FP support.

Also the PR in question doesn't support restoring/saving HI/LO registers.

So this issue is here for a couple of reasons:

  • Should HI/LO registers be saved/restored? IMO it doesn't need to, but LLVM libunwind does it.
  • Should FP registers be saved/restored? If they don't need to, we can avoid the whole "detect hardware FP support".

Personally, at least for MIPS, saving callee saved registers and restoring all GP registers (but not FP registers) is good enough, but this is just my opinion, and I wanted to see if there were already some guidelines on the topic.

Tazdevil971 avatar Sep 17 '24 22:09 Tazdevil971

I have no much experience with MIPS, but in RISC-V, callee-saved FP registers need to restored. For example, in https://godbolt.org/z/6aKvv5EcE you can see the landing pad uses fmv.s fa0, fs0 which implies that fs0 is restored. I don't know about MIPS calling convention, but if there're callee-saved FP registers then it would definitely need to be restored.

What's HI/LO register?

nbdd0121 avatar Sep 17 '24 23:09 nbdd0121

I double checked and MIPS does have callee saved FP registers (here), so yes, restoring FP registers is needed.

HI/LO registers are special registers used to store multiplication/division output, they are technically part of the register file, but they are caller saved and never get used for argument passing.

Maybe we can expose a soft-float feature to disable saving/restoring floating point registers? This avoids having to detect hardware FP support. Could other archs benefit from this?

Tazdevil971 avatar Sep 18 '24 19:09 Tazdevil971

For RISC-V we use cfg(target_feature = "d") to detect FP support. Is there no equivalent for MIPS?

nbdd0121 avatar Sep 18 '24 19:09 nbdd0121

Sadly not. The current MIPS PR uses single-float but I don't think the compiler supports that. The best that we have is fp64 which indicates the presence of 64bit floats

Tazdevil971 avatar Sep 18 '24 21:09 Tazdevil971

I guess "soft-float" should be used then.

nbdd0121 avatar Sep 18 '24 21:09 nbdd0121