Skip to content

Commit a370ca6

Browse files
committed
RFC: Stabilize implementing panics as aborts
* Stabilize the `-Z no-landing-pads` flag under the name `-C unwind=val` * Implement a number of unstable features akin to custom allocators to swap out implementations of panic just before a final product is generated. * Add a `[profile.dev]` option to Cargo to disable unwinding.
1 parent 4428b97 commit a370ca6

File tree

1 file changed

+281
-0
lines changed

1 file changed

+281
-0
lines changed

text/0000-less-unwinding.md

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
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

Comments
 (0)