-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RISC-V] Do not force frame pointers #69890
Conversation
(rust_highfive has picked a reviewer for you, use r? to override) |
Counterpoint: The embedded ARM targets have done the opposite in order to fix backtraces #69248 |
Have you tried changing the uwtable option instead? According to #69231 (comment) that might be a cheaper option than keeping full frame pointers, while still keeping backtraces intact. |
Oh, god, I see part of the problem. You can't use a Looking at why Looking at how this all interacts:
I will investigate adding |
Longer-term, Cargo should gain the ability to recompile the standard library (currently unstable as Do we have a |
GDB also isn't the only use case we need backtraces for. Executables also want to be able to generate backtraces for themselves, so they can print from inside a panic/abort handler, for instance. It should ideally be possible to do this even when in a With frame pointers this logic only takes a handful of lines of code. With |
What happens today if we use
Not sure, I can look to add it in a separate PR. This might be a better solution rather than adding |
What I would really like to prevent here is breaking obtaining a backtrace using GDB after an embedded app has panicked (this traverses If it doesn't, then I don't think we should land this patch. Backtraces should work by default, even on embedded targets (or rather, especially there). For the cases where code size is an issue, passing |
Sorry, I've been conflating two things: ELF Binaries have up to two sources of (slightly different) call frame information:
GDB will parse either of these to construct frame information, it is not fussy (the requirement here is you use the ELF file generated by the compiler/linker, for debugging, not the stripped binary that is loaded onto the device. You will also need to ensure that your linker script preserves at least one of these sections for the elf file). I doubt we need to add the |
Panic backtraces should be correct though. |
Interesting. As with frame pointers, user's choice of debuginfo does not currently affect the parts of the sysroot that are shipped as pre-compiled machine code (which crucially includes the panic machinery that's at the start of every backtrace). Leaving aside the still-unstable possibility of
I expect that debug info, even just line tables, has more space overhead than While writing this up, I realized we could also specifically tweak how the standard library is built for distribution, instead of changing the target defaults. This would effectively mean moving the burden for users from "if you want better code size, use these options" to "if you want backtrace to work in release, use these options". I don't have a strong opinion on which is the better default but it's an option. |
So I don't really have an opinion here. I'd like to delegate this final decision to the wg-embedded group perhaps =) not sure who's the right owner for the RISC-V backend, basically. Any suggestions for who I ought to reassign to? |
LLVM's actual logic for determining whether callee-saved registers (which includes the return address register) should be saved is actually target-independent. The condition for not preserving CSRs is basically that a function must be I personally think that this should be added as a third option to |
On targets which do not support unwinding (e.g. thumb* & riscv*), the standard library would then be built with |
What about |
Just to note: I checked debugging panics and diverging functions on a HiFive1 board (rv32imac) and it works great both with and without |
@Disasm Could you specify a bit more about what you did? Were you running with |
If we are going to be adding a new panic mode, that would be a different set of decision makers. I'm a bit wary of that outcome, although I do see @Amanieu's point (but -- if I'm not mistaken -- isn't that already available to people by setting the "force frame pointer" option manually?). |
@fintelia Yes, it was a |
6438cc7
to
654f414
Compare
Hopefully this change now addresses @hanna-kruppe's suggestion. |
The job Click to expand the log.
I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact |
LGTM. r=me except that I'd like someone more familiar with rustbuild to take a second look at the rustbuild part, so r? @Mark-Simulacrum |
We have been seeing some very inefficient code that went away when using `-Cforce-frame-pointers=no`. For instance `core::ptr::drop_in_place` at `-Oz` was compiled into a function which consisted entirely of saving registers to the stack, then using the frame pointer to restore the same registers (without any instructions between the prolog and epilog). The RISC-V LLVM backend supports frame pointer elimination, so it makes sense to allow this to happen when using Rust. It's not clear to me that frame pointers have ever been required in the general case. In rust-lang#61675 it was pointed out that this made reassembling stack traces easier, which is true, but there is a code generation option for forcing frame pointers, and I feel the default should not be to require frame pointers, given it demonstrably makes code size worse (around 10% in some embedded applications). The kinds of targets mentioned in rust-lang#61675 are popular, but should not dictate that code generation should be worse for all RISC-V targets, especially as there is a way to use CFI information to reconstruct the stack when the frame pointer is eliminated. It is also a misconception that `fp` is always used for the frame pointer. `fp` is an ABI name for `x8` (aka `s0`), and if no frame pointer is required, `x8` may be used for other callee-saved values. This commit does ensure that the standard library is built with unwind tables, so that users do not need to rebuild the standard library in order to get a backtrace that includes standard library calls (which is the original reason for forcing frame pointers).
654f414
to
3da3d15
Compare
I have just confirmed in a build on my own machine (of Update: This has also removed the code generation issues seen in |
ping from triage: |
Yes, this is ready from my point of view. |
@bors r=hanna-kruppe,Mark-Simulacrum |
📌 Commit 3da3d15 has been approved by |
Thanks! |
… r=hanna-kruppe,Mark-Simulacrum [RISC-V] Do not force frame pointers We have been seeing some very inefficient code that went away when using `-Cforce-frame-pointers=no`. For instance `core::ptr::drop_in_place` at `-Oz` was compiled into a function which consisted entirely of saving registers to the stack, then using the frame pointer to restore the same registers (without any instructions between the prolog and epilog). The RISC-V LLVM backend supports frame pointer elimination, so it makes sense to allow this to happen when using Rust. It's not clear to me that frame pointers have ever been required in the general case. In rust-lang#61675 it was pointed out that this made reassembling stack traces easier, which is true, but there is a code generation option for forcing frame pointers, and I feel the default should not be to require frame pointers, given it demonstrably makes code size worse (around 10% in some embedded applications). The kinds of targets mentioned in rust-lang#61675 are popular, but should not dictate that code generation should be worse for all RISC-V targets, especially as there is a way to use CFI information to reconstruct the stack when the frame pointer is eliminated. It is also a misconception that `fp` is always used for the frame pointer. `fp` is an ABI name for `x8` (aka `s0`), and if no frame pointer is required, `x8` may be used for other callee-saved values. --- I am partly posting this to get feedback from @fintelia who introduced the change to require frame pointers, and @hanna-kruppe who had issues with the original PR. I would understand if we wanted to remove this setting on only a subset of RISC-V targets, but my preference would be to remove this setting everywhere. There are more details on the code size savings seen in Tock here: tock/tock#1660
… r=hanna-kruppe,Mark-Simulacrum [RISC-V] Do not force frame pointers We have been seeing some very inefficient code that went away when using `-Cforce-frame-pointers=no`. For instance `core::ptr::drop_in_place` at `-Oz` was compiled into a function which consisted entirely of saving registers to the stack, then using the frame pointer to restore the same registers (without any instructions between the prolog and epilog). The RISC-V LLVM backend supports frame pointer elimination, so it makes sense to allow this to happen when using Rust. It's not clear to me that frame pointers have ever been required in the general case. In rust-lang#61675 it was pointed out that this made reassembling stack traces easier, which is true, but there is a code generation option for forcing frame pointers, and I feel the default should not be to require frame pointers, given it demonstrably makes code size worse (around 10% in some embedded applications). The kinds of targets mentioned in rust-lang#61675 are popular, but should not dictate that code generation should be worse for all RISC-V targets, especially as there is a way to use CFI information to reconstruct the stack when the frame pointer is eliminated. It is also a misconception that `fp` is always used for the frame pointer. `fp` is an ABI name for `x8` (aka `s0`), and if no frame pointer is required, `x8` may be used for other callee-saved values. --- I am partly posting this to get feedback from @fintelia who introduced the change to require frame pointers, and @hanna-kruppe who had issues with the original PR. I would understand if we wanted to remove this setting on only a subset of RISC-V targets, but my preference would be to remove this setting everywhere. There are more details on the code size savings seen in Tock here: tock/tock#1660
☀️ Test successful - checks-azure |
We have been seeing some very inefficient code that went away when using
-Cforce-frame-pointers=no
. For instancecore::ptr::drop_in_place
at-Oz
was compiled into a function which consisted entirely of savingregisters to the stack, then using the frame pointer to restore the same
registers (without any instructions between the prolog and epilog).
The RISC-V LLVM backend supports frame pointer elimination, so it makes
sense to allow this to happen when using Rust. It's not clear to me that
frame pointers have ever been required in the general case.
In #61675 it was pointed out that this made reassembling
stack traces easier, which is true, but there is a code generation
option for forcing frame pointers, and I feel the default should not be
to require frame pointers, given it demonstrably makes code size worse
(around 10% in some embedded applications).
The kinds of targets mentioned in #61675 are popular, but
should not dictate that code generation should be worse for all RISC-V
targets, especially as there is a way to use CFI information to
reconstruct the stack when the frame pointer is eliminated. It is also
a misconception that
fp
is always used for the frame pointer.fp
isan ABI name for
x8
(akas0
), and if no frame pointer is required,x8
may be used for other callee-saved values.I am partly posting this to get feedback from @fintelia who introduced the change to require frame pointers, and @hanna-kruppe who had issues with the original PR. I would understand if we wanted to remove this setting on only a subset of RISC-V targets, but my preference would be to remove this setting everywhere.
There are more details on the code size savings seen in Tock here: tock/tock#1660