Skip to content

Commit 3409840

Browse files
committed
Add panic implementation docs
1 parent 6f66e05 commit 3409840

File tree

2 files changed

+106
-0
lines changed

2 files changed

+106
-0
lines changed

src/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
- [Debugging and Testing](./incrcomp-debugging.md)
3737
- [The parser](./the-parser.md)
3838
- [`#[test]` Implementation](./test-implementation.md)
39+
- [Panic Implementation](./panic-implementation.md)
3940
- [Macro expansion](./macro-expansion.md)
4041
- [Name resolution](./name-resolution.md)
4142
- [The HIR (High-level IR)](./hir.md)

src/panic-implementation.md

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
### Panicking in rust ###
2+
3+
#### Step 1: Invocation of the `panic!` macro.
4+
5+
There are actually two panic macros - one defined in `libcore`, and one defined in `libstd`.
6+
This is due to the fact that code in `libcore` can panic. `libcore` is built before `libsd`,
7+
but we want panics to use the same machinery at runtime, whether they originate in `libcore` or `libstd`.
8+
9+
##### libcore definition of panic!
10+
11+
The `libcore` `panic!` macro eventually makes the following call (in `src/libcore/panicking.rs`):
12+
13+
```rust
14+
// NOTE This function never crosses the FFI boundary; it's a Rust-to-Rust call
15+
extern "Rust" {
16+
#[lang = "panic_impl"]
17+
fn panic_impl(pi: &PanicInfo<'_>) -> !;
18+
}
19+
20+
let pi = PanicInfo::internal_constructor(Some(&fmt), location);
21+
unsafe { panic_impl(&pi) }
22+
```
23+
24+
Actually resolving this goes through several layers of indirection:
25+
26+
1. In `src/librustc/middle/weak_lang_items.rs`, `panic_impl` is declared as 'weak lang item',
27+
with the symbol `rust_begin_unwind`. This is used in `librustc_typeck/collect.rs`
28+
to set the actual symbol name to `rust_begin_unwind`.
29+
30+
Note that `panic_impl` is declared in an `extern "Rust"` block,
31+
which means that libcore will attempt to call a foreign symbol called `rust_begin_unwind`
32+
(to be resolved at link time)
33+
34+
2. In `src/libstd/panicking.rs`, we have this definition:
35+
36+
```rust
37+
/// Entry point of panic from the libcore crate.
38+
[cfg(not(test))]
39+
[panic_handler]
40+
[unwind(allowed)]
41+
pub fn rust_begin_panic(info: &PanicInfo<'_>) -> ! {
42+
continue_panic_fmt(&info)
43+
}
44+
```
45+
46+
The special `panic_handler` attribute is resolved via `src/librustc/middle/lang_items`.
47+
The `extract` functions convers the `panic_handler` attribute to a `panic_impl` lang item.
48+
49+
Now, we have a matching `panic_impl` lang item in the `libstd`. This function goes
50+
through the same process as the `extern { fn panic_impl }` definition in `libcore`, ending
51+
up with a symbol name of `rust_begin_unwind`. At link time, the symbol refernce in `libcore`
52+
will be resolved to the definition of `libstd` (the function called `rust_begin_panic` in the
53+
Rust source).
54+
55+
Thus, control flow will pass from libcore to std at runtime. This allows panics from `libcore`
56+
to go through the same infratructure that other panics use (panic hooks, unwinding, etc)
57+
58+
##### libstd implementation of panic!
59+
60+
This is where the actual panic-related logic begins. In `src/libstd/pancking.rs`,
61+
control passes to `rust_panic_with_hook`. This method is responsible
62+
for checking for invoking the global panic hook, and checking for double panics. Finally,
63+
we call ```__rust_start_panic```, which is provided by the panic runtime.
64+
65+
The call to ```__rust_start_panic``` is very weird - it is passed a ```*mut &mut dyn BoxMeUp```,
66+
converted to an `usize`. Let's break this type down:
67+
68+
1. `BoxMeUp` is an internal trait. It is implemented for `PanicPayload` (a wrapper around the user-supplied
69+
payload type), and has a method ```fn box_me_up(&mut self) -> *mut (dyn Any + Send)```.
70+
This method takes the user-provided payload (`T: Any + Send`), boxes it, and convertes the box to a raw pointer.
71+
72+
2. When we call ```__rust_start_panic```, we have an `&mut dyn BoxMeUp`. However, this is a fat pointer
73+
(twice the size of a `usize`). To pass this to the panic runtime across an FFI boundary, we take a mutable
74+
reference *to this mutable reference* (`&mut &mut dyn BoxMeUp`), and convert it to a raw pointer
75+
(`*mut &mut dyn BoxMeUp`). The outer raw pointer is a thin pointer, since it points to a `Sized`
76+
type (a mutable reference). Therefore, we can convert this thin pointer into a `usize`, which
77+
is suitable for passing across an FFI boundary.
78+
79+
Finally, we call ```__rust_start_panic``` with this `usize`. We have now entered the panic runtime.
80+
81+
#### Step 2: The panic runtime
82+
83+
Rust provides two panic runtimes: `libpanic_abort` and `libpanic_unwind`. The user chooses
84+
between them at build time via their `Cargo.toml`
85+
86+
`libpanic_abort` is extremely simple: its implementation of ```__rust_start_panic``` just aborts,
87+
as you would expect.
88+
89+
`libpanic_unwind` is the more interesting case.
90+
91+
In its implementation of ```__rust_start_panic```, we take the `usize`, convert
92+
it back to a `*mut &mut dyn BoxMeUp`, dereference it, and call `box_me_up`
93+
on the `&mut dyn BoxMeUp`. At this point, we have a raw pointer to the payload
94+
itself (a `*mut (dyn Send + Any)`): that is, a raw pointer to the actual value
95+
provided by the user who called `panic!`.
96+
97+
At this point, the platform-independent code ends. We now call into
98+
platform-specific unwinding logic (e.g `libunwind`). This code is
99+
responsible for unwinding the stack, running any 'landing pads' associated
100+
with each frame (currently, running destructors), and transferring control
101+
to the `catch_unwind` frame.
102+
103+
Note that all panics either abort the process or get caught by some call to `catch_unwind`:
104+
in `src/libstd/rt.rs`, the call to the user-provided `main` function is wrapped in `catch_unwind
105+

0 commit comments

Comments
 (0)