Calls to functions with `returns_twice` do not clobber callee-saved registers
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
When a function with `returns_twice` is called, callee-saved registers are not invalidated in clang, leading to surprising behavior.
[This snippet in Compiler Explorer](https://godbolt.org/#z:OYLghAFBqd5QCxAYwPYBMCmBRdBLAF1QCcAaPECAMzwBtMA7AQwFtMQByARg9KtQYEAysib0QXACx8BBAKoBnTAAUAHpwAMvAFYTStJg1AB9U8lJL6yAngGVG6AMKpaAVxYMQAJgDspBwAyeAyYAHLuAEaYxCAAzKQADqgKhLYMzm4e3n5JKTYCQSHhLFEx8ZaY1mlCBEzEBBnunr4WmFb5DDV1BIVhkdFxFrX1jVktCsM9wX0lA7EAlBaorsTI7ByYqgTRDADUwQS7wLSoEWLGE65UVACksQBCNxoAgk/PpkwEBMR4Ea7bpigxEwBBWDAUxgIAHc8Gt5vM3gA3VB4dC7fioCDI1EIh5vN4HXYsJjBCAIl43HyPF67Wn7QS7JjGE6iWi7O4AESOJzOtAuoOud2pzzp6NQmNxwtFwNBxD2TJZYnZXnu3NO50ugrxFJ8HI4i1onAArLxPBwtKRUJxHLsFMtVphlbEeKQCJp9YsANZxWIAOliAcDQaDADZ9JxJKb3ZbOLwFCANK73Ys4LAkGgWAk6NFyJQM1n6DFkMAuEavHw6NtiPGIBFoxFgnUAJ6cF0N5jEJsAeQi2kqbu4vAzbEEXYYtBb5t4WD%2BwEcYlo8cHpCwxKM4inK7wwKqiMwS4tm0q/3WLoObWjtF%2BxGbziw0e%2BeBYrd4e%2BIEWSmA5mDXx2CoCnRYqAMYAFAANTwTAoS7BJGBfGRBBEMR2CkBD5CUNRo10Lh9EMEwzH0X540gRZUASDolwAWi7WJeFQN8fiwYiyVado0nsBgnBcJoJHLQJpmKUoQAATkSZJUgEUZPC4ctcgkhhekEgZRIqKoBC6EZuKyGTWP7apJkU/oYhUyYpN4oZukM2ZjMWO0VjWCQDWNKNNytDhdlUAAOENKJDSQjmQZBdlLX0vF2CBcEIEgnS4eZeAHLR4VIBBMCYLAYhY70A39YNcoDMNDQ4SNSGfLgNETM0LTcuMEyTQDSFTRAUFQTNszICgIHzNqUGQAwjC8LwyorWgqxrOtN3bZt4Mmzsez7ax4OHRgCDHCdoxnVw5wXJcXVXPCNwtfAdxsPcD14I9kBPeDz0Ki0rwiG9OzvdYLUfZ9lzfD8lG/X8ryMZM%2BBA8DIOg2CzRdfhENZFDpEh9CVHUTddHLPr8OMcx7uY0jyLSKiaLohjUX3eBbLaPS7AgBwzJw/iiiMvQ5I6amxLyNIrKEnDVI6DSGi06TdLUzoDIE%2BnOdMvm9AmSyResxylnslCnI4E1SEqujOA87zfP83q8N2AbfS4X0NHCyKiGIGK4rqxKvR9HK8uDcMipcqrYwsWqEo9J2vBd9WOHi5NFjfFI7EkIA) compares the compilation output of GCC and clang. You can see that in clang output some registers are still live after a call to a function with `returns_twice` attribute:
```asm
main:
push rbp
push rbx
push rax
mov rbx, qword ptr [rip + global_stuff@GOTPCREL]
mov ebp, dword ptr [rbx]
call foo@PLT ;; <- this function returns twice
mov eax, ebp ;; ebp is still live!
add eax, dword ptr [rbx] ;; rbx is still live!
add rsp, 8
pop rbx
pop rbp
ret
```
In GCC output the result of the first read is correctly stashed on the stack:
```asm
main:
sub rsp, 24
mov eax, DWORD PTR global_stuff[rip] ;; first read
mov DWORD PTR [rsp+12], eax ;; stash
call foo
mov eax, DWORD PTR [rsp+12] ;; get the stashed value
add eax, DWORD PTR global_stuff[rip]
add rsp, 24
ret
```
Commenting out the attribute removes the stashing in GCC and does not affect clang output (modulo some register shuffling).
This issue was encountered when we tried to roll our own `setjmp` to work around the bug https://github.com/llvm/llvm-project/issues/72908.
Contributor guide
Assessment
This issue has not been assessed yet.