|
| 1 | +- Feature Name: `panic_runtime` |
| 2 | +- Start Date: 2016-02-25 |
| 3 | +- RFC PR: (leave this empty) |
| 4 | +- Rust Issue: (leave this empty) |
| 5 | + |
| 6 | +# Summary |
| 7 | +[summary]: #summary |
| 8 | + |
| 9 | +Stabilize implementing panics as aborts. |
| 10 | + |
| 11 | +* Stabilize the `-Z no-landing-pads` flag under the name `-C panic=strategy` |
| 12 | +* Implement a number of unstable features akin to custom allocators to swap out |
| 13 | + implementations of panic just before a final product is generated. |
| 14 | +* Add a `[profile.dev]` option to Cargo to configure how panics are implemented. |
| 15 | + |
| 16 | +# Motivation |
| 17 | +[motivation]: #motivation |
| 18 | + |
| 19 | +Panics in Rust have long since been implemented with the intention of being |
| 20 | +caught at particular boundaries (for example the thread boundary). This is quite |
| 21 | +useful for isolating failures in Rust code, for example: |
| 22 | + |
| 23 | +* Servers can avoid taking down the entire process but can instead just take |
| 24 | + down one request. |
| 25 | +* Embedded Rust libraries can avoid taking down the entire process and can |
| 26 | + instead gracefully inform the caller that an internal logic error occurred. |
| 27 | +* Rust applications can isolate failure from various components. The classical |
| 28 | + example of this is Servo can display a "red X" for an image which fails to |
| 29 | + decode instead of aborting the entire browser or killing an entire page. |
| 30 | + |
| 31 | +While these are examples where a recoverable panic is useful, there are many |
| 32 | +applications where recovering panics is undesirable or doesn't lead to anything |
| 33 | +productive: |
| 34 | + |
| 35 | +* Rust applications which use `Result` for error handling typically use `panic!` |
| 36 | + to indicate a fatal error, in which case the process *should* be taken down. |
| 37 | +* Many applications simply can't recover from an internal assertion failure, so |
| 38 | + there's no need trying to recover it. |
| 39 | +* To implement a recoverable panic, the compiler and standard library use a |
| 40 | + method called stack unwinding. The compiler must generate code to support this |
| 41 | + unwinding, however, and this takes time in codegen and optimizers. |
| 42 | +* Low-level applications typically don't use unwinding at all as there's no |
| 43 | + stack unwinder (e.g. kernels). |
| 44 | + |
| 45 | +> **Note**: as an idea of the compile-time and object-size savings from |
| 46 | +> disabling the extra codegen, compiling Cargo as a library is 11% faster (16s |
| 47 | +> from 18s) and 13% smaller (15MB to 13MB). Sizable gains! |
| 48 | +
|
| 49 | +Overall, the ability to recover panics is something that needs to be decided at |
| 50 | +the application level rather than at the language level. Currently the compiler |
| 51 | +does not support the ability to translate panics to process aborts in a stable |
| 52 | +fashion, and the purpose of this RFC is to add such a venue. |
| 53 | + |
| 54 | +With such an important codegen option, however, as whether or not exceptions can |
| 55 | +be caught, it's easy to get into a situation where libraries of mixed |
| 56 | +compilation modes are linked together, causing odd or unknown errors. This RFC |
| 57 | +proposes a situation similar to the design of custom allocators to alleviate |
| 58 | +this situation. |
| 59 | + |
| 60 | +# Detailed design |
| 61 | +[design]: #detailed-design |
| 62 | + |
| 63 | +The major goal of this RFC is to develop a work flow around managing crates |
| 64 | +which wish to disable unwinding. This intends to set forth a complete vision for |
| 65 | +how these crates interact with the ecosystem at large. Much of this design will |
| 66 | +be similar to the [custom allocator RFC][custom-allocators]. |
| 67 | + |
| 68 | +[custom-allocators]: https://github.com/rust-lang/rfcs/blob/master/text/1183-swap-out-jemalloc.md |
| 69 | + |
| 70 | +### High level design |
| 71 | + |
| 72 | +This section serves as a high-level tour through the design proposed in this |
| 73 | +RFC. The linked sections provide more complete explanation as to what each step |
| 74 | +entails. |
| 75 | + |
| 76 | +* The compiler will have a [new stable flag](#new-compiler-flags), `-C panic` |
| 77 | + which will configure how unwinding-related code is generated. |
| 78 | +* [Two new unstable attributes](#panic-attributes) will be added to the |
| 79 | + compiler, `#![needs_panic_runtime]` and `#![panic_runtime]`. The standard |
| 80 | + library will need a runtime and will be lazily linked to a crate which has |
| 81 | + `#![panic_runtime]`. |
| 82 | +* [Two unstable crates](#panic-crates) tagged with `#![panic_runtime]` will be |
| 83 | + distributed as the runtime implementation of panicking, `panic_abort` and |
| 84 | + `panic_unwind` crates. The former will translate all panics to process |
| 85 | + aborts, whereas the latter will be implemented as unwinding is today, via the |
| 86 | + system stack unwinder. |
| 87 | +* [Cargo will gain](#cargo-changes) a new `panic` option in the `[profile.foo]` |
| 88 | + sections to indicate how that profile should compile panic support. |
| 89 | + |
| 90 | +### New Compiler Flags |
| 91 | + |
| 92 | +The first component to this design is to have a **stable** flag to the compiler |
| 93 | +which configures how panic-related code is generated. This will be |
| 94 | +stabilized in the form: |
| 95 | + |
| 96 | +``` |
| 97 | +$ rustc -C help |
| 98 | +
|
| 99 | +Available codegen options: |
| 100 | +
|
| 101 | + ... |
| 102 | + -C panic=val -- strategy to compile in for panic related code |
| 103 | + ... |
| 104 | +``` |
| 105 | + |
| 106 | +There will currently be two supported strategies: |
| 107 | + |
| 108 | +* `unwind` - this is what the compiler implements by default today via the |
| 109 | + `invoke` LLVM instruction. |
| 110 | +* `abort` - this will implement that `-Z no-landing-pads` does today, which is |
| 111 | + to disable the `invoke` instruction and use `call` instead everywhere. |
| 112 | + |
| 113 | +This codegen option will default to `unwind` if not specified (what happens |
| 114 | +today), and the value will be encoded into the crate metadata. This option is |
| 115 | +planned with extensibility in mind to future panic strategies if we ever |
| 116 | +implement some (return-based unwinding is at least one other possible option). |
| 117 | + |
| 118 | +### Panic Attributes |
| 119 | + |
| 120 | +Very similarly to [custom allocators][allocator-attributes], two new |
| 121 | +**unstable** crate attributes will be added to the compiler: |
| 122 | + |
| 123 | +[allocator-attributes]: https://github.com/rust-lang/rfcs/blob/master/text/1183-swap-out-jemalloc.md#new-attributes |
| 124 | + |
| 125 | +* `#![needs_panic_runtime]` - indicates that this crate requires a "panic |
| 126 | + runtime" to link correctly. This will be attached to the standard library and |
| 127 | + is not intended to be attached to any other crate. |
| 128 | +* `#![panic_runtime]` - indicates that this crate is a runtime implementation of |
| 129 | + panics. |
| 130 | + |
| 131 | +As with allocators, there are a number of limitations imposed by these |
| 132 | +attributes by the compiler: |
| 133 | + |
| 134 | +* Any crate DAG can only contain at most one instance of `#![panic_runtime]`. |
| 135 | +* Implicit dependency edges are drawn from crates tagged with |
| 136 | + `#![needs_panic_runtime]` to those tagged with `#![panic_runtime]`. Loops as |
| 137 | + usual are forbidden (e.g. a panic runtime can't depend on libstd). |
| 138 | +* Complete artifacts which include a crate tagged with `#![needs_panic_runtime]` |
| 139 | + must include a panic runtime. This includes executables, dylibs, and |
| 140 | + staticlibs. If no panic runtime is explicitly linked, then the compiler will |
| 141 | + select an appropriate runtime to inject. |
| 142 | +* Finally, the compiler will ensure that panic runtimes and compilation modes |
| 143 | + are not mismatched. For a resolved DAG, the panic runtime will have been |
| 144 | + compiled with a particular `-C panic` option, let's call it PS (panic |
| 145 | + strategy). If PS is "abort", then no validation is performed (doesn't matter |
| 146 | + how the rest of the DAG is compiled). Otherwise, all other crates must also |
| 147 | + be compiled with the same PS. |
| 148 | + |
| 149 | +The purpose of these limitations is to solve a number of problems that arise |
| 150 | +when switching panic strategies. For example with aborting panic crates won't |
| 151 | +have to link to runtime support of unwinding, or rustc will disallow mixing |
| 152 | +panic strategies by accident. |
| 153 | + |
| 154 | +The actual API of panic runtimes will not be detailed in this RFC. These new |
| 155 | +attributes will be unstable, and consequently the API itself will also be |
| 156 | +unstable. It suffices to say, however, that like custom allocators a panic |
| 157 | +runtime will implement some public `extern` symbols known to the crates that |
| 158 | +need a panic runtime, and that's how they'll communicate/link up. |
| 159 | + |
| 160 | +### Panic Crates |
| 161 | + |
| 162 | +Two new **unstable** crates will be added to the distribution for each target: |
| 163 | + |
| 164 | +* `panic_unwind` - this is an extraction of the current implementation of |
| 165 | + panicking from the standard library. It will use the same mechanism of stack |
| 166 | + unwinding as is implemented on all current platforms. |
| 167 | +* `panic_abort` - this is a new implementation of panicking which will simply |
| 168 | + translate unwinding to process aborts. There will be no runtime support |
| 169 | + required by this crate. |
| 170 | + |
| 171 | +The compiler will assume that these crates are distributed for each platform |
| 172 | +where the standard library is also distributed (e.g. a crate that has |
| 173 | +`#![needs_panic_runtime]`). |
| 174 | + |
| 175 | +### Compiler defaults |
| 176 | + |
| 177 | +The compiler will ship with a few defaults which affect how panic runtimes are |
| 178 | +selected in Rust programs. Specifically: |
| 179 | + |
| 180 | +* The `-C panic` option will default to **unwind** as it does today. |
| 181 | +* The libtest crate will explicitly link to `panic_unwind`. The test runner that |
| 182 | + libtest implements relies on equating panics with failure and cannot work if |
| 183 | + panics are translated to aborts. |
| 184 | +* If no panic runtime is explicitly selected, the compiler will employ the |
| 185 | + following logic to decide what panic runtime to inject: |
| 186 | + |
| 187 | + 1. If any crate in the DAG is compiled with `-C panic=abort`, then `panic_abort` |
| 188 | + will be injected. |
| 189 | + 2. If all crates in the DAG are compiled with `-C panic=unwind`, then |
| 190 | + `panic_unwind` is injected. |
| 191 | + |
| 192 | +### Cargo changes |
| 193 | + |
| 194 | +In order to export this new feature to Cargo projects, a new option will be |
| 195 | +added to the `[profile]` section of manifests: |
| 196 | + |
| 197 | +```toml |
| 198 | +[profile.dev] |
| 199 | +panic = 'unwind' |
| 200 | +``` |
| 201 | + |
| 202 | +This will cause Cargo to pass `-C panic=unwind` to all `rustc` invocations for |
| 203 | +a crate graph. Cargo will have special knowledge, however, that for `cargo |
| 204 | +test` it cannot pass `-C panic=abort`. |
| 205 | + |
| 206 | +# Drawbacks |
| 207 | +[drawbacks]: #drawbacks |
| 208 | + |
| 209 | +* The implementation of custom allocators was no small feat in the compiler, and |
| 210 | + much of this RFC is essentially the same thing. Similar infrastructure can |
| 211 | + likely be leveraged to alleviate the implementation complexity, but this is |
| 212 | + undeniably a large change to the compiler for albeit a relatively minor |
| 213 | + option. The counter point to this, however, is that disabling unwinding in a |
| 214 | + principled fashion provides far higher quality error messages, prevents |
| 215 | + erroneous situations, and provides an immediate benefit for many Rust users |
| 216 | + today. |
| 217 | + |
| 218 | +* The binary distribution of the standard library will not change from what it |
| 219 | + is today. In other words, the standard library (and dependency crates like |
| 220 | + libcore) will be compiled with `-C panic=unwind`. This introduces the |
| 221 | + opportunity for extra code bloat or missed optimizations in applications that |
| 222 | + end up disabling unwinding in the long run. Distribution, however, is *far* |
| 223 | + easier because there's only one copy of the standard library and we don't have |
| 224 | + to rely on any other form of infrastructure. |
| 225 | + |
| 226 | +* This represents a proliferation of the `#![needs_foo]` and `#![foo]` style |
| 227 | + system that allocators have begun. This may be indicative of a deeper |
| 228 | + underlying requirement here of the standard library or perhaps showing how the |
| 229 | + strategy in the standard library needs to change. If the standard library were |
| 230 | + a crates.io crate it would arguably support these options via Cargo features, |
| 231 | + but without that option is this the best way to be implementing these switches |
| 232 | + for the standard library? |
| 233 | + |
| 234 | +* Applications may silently revert to the wrong panic runtime given the |
| 235 | + heuristics here. For example if an application relies on unwinding panics, if |
| 236 | + a dependency is pulled in with an explicit `extern crate panic_abort`, then |
| 237 | + the entire application will switch to aborting panics silently. This can be |
| 238 | + corrected, however, with an explicit `extern crate panic_unwind` on behalf of |
| 239 | + the application. |
| 240 | + |
| 241 | +# Alternatives |
| 242 | +[alternatives]: #alternatives |
| 243 | + |
| 244 | +* Currently this RFC allows mixing multiple panic runtimes in a crate graph so |
| 245 | + long as the actual runtime is compiled with `-C panic=abort`. This is |
| 246 | + primarily done to immediately reap benefit from `-C panic=abort` even though |
| 247 | + the standard library we distribute will still have unwinding support compiled |
| 248 | + in (compiled with `-C panic=unwind`). In the not-too-distant future however, |
| 249 | + we will likely be poised to distribute multiple binary copies of the standard |
| 250 | + library compiled with different profiles. We may be able to tighten this |
| 251 | + restriction on behalf of the compiler, requiring that all crates in a DAG have |
| 252 | + the same `-C panic` compilation mode, but there would unfortunately be no |
| 253 | + immediate benefit to implementing the RFC from users of our precompiled |
| 254 | + nightlies. |
| 255 | + |
| 256 | + This alternative, additionally, can also be viewed as a drawback. It's unclear |
| 257 | + what a future libstd distribution mechanism would look like and how this RFC |
| 258 | + might interact with it. Stabilizing disabling unwinding via a compiler switch |
| 259 | + or a Cargo profile option may not end up meshing well with the strategy we |
| 260 | + pursue with shipping multiple standard libraries. |
| 261 | + |
| 262 | +* Instead of the panic runtime support in this RFC, we could instead just ship |
| 263 | + two different copies of the standard library where one simply translates |
| 264 | + panics to abort instead of unwinding. This is unfortunately very difficult |
| 265 | + for Cargo or the compiler to track, however, to ensure that the codegen |
| 266 | + option of how panics are translated is propagated throughout the rest of |
| 267 | + the crate graph. Additionally it may be easy to mix up crates of different |
| 268 | + panic strategies. |
| 269 | + |
| 270 | +# Unresolved questions |
| 271 | +[unresolved]: #unresolved-questions |
| 272 | + |
| 273 | +* One possible implementation of unwinding is via return-based flags. Much of |
| 274 | + this RFC is designed with the intention of supporting arbitrary unwinding |
| 275 | + implementations, but it's unclear whether it's too heavily biased towards |
| 276 | + panic is either unwinding or aborting. |
| 277 | + |
| 278 | +* The current implementation of Cargo would mean that a native implementation of |
| 279 | + the profile option would cause recompiles between `cargo build` and `cargo |
| 280 | + test` for projects that specify `panic = 'unwind'`. Is this acceptable? Should |
| 281 | + Cargo cache both copies of the crate? |
0 commit comments