I’d like to discuss about a compiler flag disabling panic! handling. More precisely, panic! would expand to a std::intrinsics::abort, meaning that the whole process exits. Therefore, stack unwinding info would be useless and should not be generated, resulting in a smaller output.
I don’t advocate for its systematic use and this is out of discussion.
It is actually the same feature as g++'s -fno-exception.
Most functions of the std library are switching from panic!ing to returning a Result<>, so they will be usable with this flag. The only field I’m aware of that may stay problematic is memory allocation, I I have no arguments about what to do in this case, I guess aborting would be okay.
For the record, rustc -Z no-landing-pads does some of that - it causes LLVM calls to be generated instead of invoke (i.e. code isn’t generated to run destructors early), and marks all functions nounwind. Panic description strings are still generated though.
Thanks, I didn’t know that, and I think it’s the biggest step toward my intention.
Then a flag like -Z abort-on-panic could do the remaining part, i.e. expanding panic! into abort, thus making description string disappear. Logically, this flag would implies -Z no-landing-pads.
This flag would be useful for low-level, freestanding work like kernel development since there’s no meaningful way to panic in a kernel until after a large amount of initialization code has been run, and the panic code simply bloats the binary.
I recompiled core with panic() and panic_bounds_check() redefined to be loop { }, and this reduced my final binary size from ~40K to ~15K, so there is some use for this function.
For reference, here is the patch I’ve made to libcore to eliminate panicking. I obtain a ~6K reduction in code size in my current kernel by compiling the standard libraries with feature=trivial_panic, mostly because more fmt-related functions can be garbage-collected by the linker. (In both cases, the libraries are compiled -O3. Failure to use -O3 resulted in the 45K image I mentioned earlier)
My understanding of the proposal is that -Z abort-on-panic would do something like this patch (or maybe just replace call panic with abort()), except as a compiler flag, not a configuration variable in libcore
Unless I’m missing something, you should definitely get rid of #[cold] #[inline(never)] (#[inline(always)] would be better). If it’s inlined, the binary doesn’t have to contain the expr/file strings, and the instruction sequence to call panic with those arguments can be replaced with a single instruction abort.
(I can’t believe I missed that) That does improve things, but marginally. Interestingly, the compiler generates a distinct ud2 for each panic!, since a panic is now a jump to a undefined instruction. Checking strings with and without inline shows minor differences (the panic message from Vec::resize appears once, not twice).
I also ran into trouble with the panic overhead writing an x86 bootloader. In my case, I actually wanted to keep the ability to print file/line panic messages, but I didn’t want the expensive formatting code.
Stripping file/line/messages might also be interesting for hosted Unix/Windows programs, perhaps to reduce the size of distributable binaries, or perhaps to avoid leaking internal filenames or source. In either case, I don’t think we want to replace the panic calls with aborts.
For what it’s worth, there’s a lot of avoidable overhead from the panicking/formatting code in a bare metal 32-bit x86 binary. To list a few examples relevant to krzysz00’s kernel:
In Option::expect, replace the panic!("{}", msg) call with panic!(msg). (You have to get panic!(msg) working first.) Formatting a str pulls in padding code.
Fix encode_utf8_raw to stop calling panic_bounds_check. Formatting the usize values in the bounds check error pulls in padding code and integer conversion code. (I opened a related compiler issue.)
Fix the Display::fmt function for usize to stop panicking. It currently panics with a complex error message for the case where the (x % 10) digit value is outside the 0..9 range. There might be an optimizer problem here.
@krzysz00 interesting patch. Indeed I suggest having a compiler flag instead because I believe this is a compilation-wide behavior whereas features are defined per module. I don’t understand your last message, I find logical that panic! in this case are compiled to ud2 and then panic messages should not be generated anymore since they are not used. Can you investigate why the panic message from Vec::resize is still there?
@ryanprichard these may be relevant optimizations, but I’m really interested in removing the overhead of panics in low-level executables <100kB. I agree that this behavior is not suited to end-user applications >1MB.
Ordinarily, ERR_MSG is included in the binary twice. I’m not sure why, but it’s probably because it goes in once as a constant and once as a panic string.
It turns out that I can get larger size savings (and optimize all the panic strings out of the binary) by fully replacing core::panicking::panic_fmt by a call to core::intrinsics::abort().