From 9fa142966f6ca47338616e886c411d4fce07926e Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Tue, 29 Oct 2019 18:18:04 -0400 Subject: [PATCH 1/7] Add panic implementation docs --- src/SUMMARY.md | 1 + src/panic-implementation.md | 105 ++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 src/panic-implementation.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index fe6584b55..7852a3d88 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -41,6 +41,7 @@ - [Salsa](./salsa.md) - [Lexing and Parsing](./the-parser.md) - [`#[test]` Implementation](./test-implementation.md) + - [Panic Implementation](./panic-implementation.md) - [Macro expansion](./macro-expansion.md) - [Name resolution](./name-resolution.md) - [The HIR (High-level IR)](./hir.md) diff --git a/src/panic-implementation.md b/src/panic-implementation.md new file mode 100644 index 000000000..d8bb4c315 --- /dev/null +++ b/src/panic-implementation.md @@ -0,0 +1,105 @@ +### Panicking in rust ### + +#### Step 1: Invocation of the `panic!` macro. + +There are actually two panic macros - one defined in `libcore`, and one defined in `libstd`. +This is due to the fact that code in `libcore` can panic. `libcore` is built before `libsd`, +but we want panics to use the same machinery at runtime, whether they originate in `libcore` or `libstd`. + +##### libcore definition of panic! + +The `libcore` `panic!` macro eventually makes the following call (in `src/libcore/panicking.rs`): + +```rust +// NOTE This function never crosses the FFI boundary; it's a Rust-to-Rust call +extern "Rust" { + #[lang = "panic_impl"] + fn panic_impl(pi: &PanicInfo<'_>) -> !; +} + +let pi = PanicInfo::internal_constructor(Some(&fmt), location); +unsafe { panic_impl(&pi) } +``` + +Actually resolving this goes through several layers of indirection: + +1. In `src/librustc/middle/weak_lang_items.rs`, `panic_impl` is declared as 'weak lang item', + with the symbol `rust_begin_unwind`. This is used in `librustc_typeck/collect.rs` + to set the actual symbol name to `rust_begin_unwind`. + + Note that `panic_impl` is declared in an `extern "Rust"` block, + which means that libcore will attempt to call a foreign symbol called `rust_begin_unwind` + (to be resolved at link time) + +2. In `src/libstd/panicking.rs`, we have this definition: + +```rust +/// Entry point of panic from the libcore crate. +[cfg(not(test))] +[panic_handler] +[unwind(allowed)] +pub fn rust_begin_panic(info: &PanicInfo<'_>) -> ! { + continue_panic_fmt(&info) +} +``` + +The special `panic_handler` attribute is resolved via `src/librustc/middle/lang_items`. +The `extract` functions convers the `panic_handler` attribute to a `panic_impl` lang item. + +Now, we have a matching `panic_impl` lang item in the `libstd`. This function goes +through the same process as the `extern { fn panic_impl }` definition in `libcore`, ending +up with a symbol name of `rust_begin_unwind`. At link time, the symbol refernce in `libcore` +will be resolved to the definition of `libstd` (the function called `rust_begin_panic` in the +Rust source). + +Thus, control flow will pass from libcore to std at runtime. This allows panics from `libcore` +to go through the same infratructure that other panics use (panic hooks, unwinding, etc) + +##### libstd implementation of panic! + +This is where the actual panic-related logic begins. In `src/libstd/pancking.rs`, +control passes to `rust_panic_with_hook`. This method is responsible +for checking for invoking the global panic hook, and checking for double panics. Finally, +we call ```__rust_start_panic```, which is provided by the panic runtime. + +The call to ```__rust_start_panic``` is very weird - it is passed a ```*mut &mut dyn BoxMeUp```, +converted to an `usize`. Let's break this type down: + +1. `BoxMeUp` is an internal trait. It is implemented for `PanicPayload` (a wrapper around the user-supplied +payload type), and has a method ```fn box_me_up(&mut self) -> *mut (dyn Any + Send)```. +This method takes the user-provided payload (`T: Any + Send`), boxes it, and convertes the box to a raw pointer. + +2. When we call ```__rust_start_panic```, we have an `&mut dyn BoxMeUp`. However, this is a fat pointer +(twice the size of a `usize`). To pass this to the panic runtime across an FFI boundary, we take a mutable +reference *to this mutable reference* (`&mut &mut dyn BoxMeUp`), and convert it to a raw pointer +(`*mut &mut dyn BoxMeUp`). The outer raw pointer is a thin pointer, since it points to a `Sized` +type (a mutable reference). Therefore, we can convert this thin pointer into a `usize`, which +is suitable for passing across an FFI boundary. + +Finally, we call ```__rust_start_panic``` with this `usize`. We have now entered the panic runtime. + +#### Step 2: The panic runtime + +Rust provides two panic runtimes: `libpanic_abort` and `libpanic_unwind`. The user chooses +between them at build time via their `Cargo.toml` + +`libpanic_abort` is extremely simple: its implementation of ```__rust_start_panic``` just aborts, +as you would expect. + +`libpanic_unwind` is the more interesting case. + +In its implementation of ```__rust_start_panic```, we take the `usize`, convert +it back to a `*mut &mut dyn BoxMeUp`, dereference it, and call `box_me_up` +on the `&mut dyn BoxMeUp`. At this point, we have a raw pointer to the payload +itself (a `*mut (dyn Send + Any)`): that is, a raw pointer to the actual value +provided by the user who called `panic!`. + +At this point, the platform-independent code ends. We now call into +platform-specific unwinding logic (e.g `libunwind`). This code is +responsible for unwinding the stack, running any 'landing pads' associated +with each frame (currently, running destructors), and transferring control +to the `catch_unwind` frame. + +Note that all panics either abort the process or get caught by some call to `catch_unwind`: +in `src/libstd/rt.rs`, the call to the user-provided `main` function is wrapped in `catch_unwind + From dd87ad8d5ff082ca8b2439a4789cc27f6d1d72e6 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Mon, 2 Dec 2019 13:06:44 -0500 Subject: [PATCH 2/7] Apply some fixes Co-Authored-By: Who? Me?! --- src/panic-implementation.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/panic-implementation.md b/src/panic-implementation.md index d8bb4c315..41338ce0d 100644 --- a/src/panic-implementation.md +++ b/src/panic-implementation.md @@ -44,7 +44,7 @@ pub fn rust_begin_panic(info: &PanicInfo<'_>) -> ! { ``` The special `panic_handler` attribute is resolved via `src/librustc/middle/lang_items`. -The `extract` functions convers the `panic_handler` attribute to a `panic_impl` lang item. +The `extract` function converts the `panic_handler` attribute to a `panic_impl` lang item. Now, we have a matching `panic_impl` lang item in the `libstd`. This function goes through the same process as the `extern { fn panic_impl }` definition in `libcore`, ending @@ -59,7 +59,7 @@ to go through the same infratructure that other panics use (panic hooks, unwindi This is where the actual panic-related logic begins. In `src/libstd/pancking.rs`, control passes to `rust_panic_with_hook`. This method is responsible -for checking for invoking the global panic hook, and checking for double panics. Finally, +for invoking the global panic hook, and checking for double panics. Finally, we call ```__rust_start_panic```, which is provided by the panic runtime. The call to ```__rust_start_panic``` is very weird - it is passed a ```*mut &mut dyn BoxMeUp```, @@ -101,5 +101,4 @@ with each frame (currently, running destructors), and transferring control to the `catch_unwind` frame. Note that all panics either abort the process or get caught by some call to `catch_unwind`: -in `src/libstd/rt.rs`, the call to the user-provided `main` function is wrapped in `catch_unwind - +in `src/libstd/rt.rs`, the call to the user-provided `main` function is wrapped in `catch_unwind`. From 880b7b10e6949ab11d9ff3db8658285d970f2e30 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Mon, 2 Dec 2019 13:03:27 -0500 Subject: [PATCH 3/7] Fix typo --- src/panic-implementation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panic-implementation.md b/src/panic-implementation.md index 41338ce0d..7de4bc0a2 100644 --- a/src/panic-implementation.md +++ b/src/panic-implementation.md @@ -3,7 +3,7 @@ #### Step 1: Invocation of the `panic!` macro. There are actually two panic macros - one defined in `libcore`, and one defined in `libstd`. -This is due to the fact that code in `libcore` can panic. `libcore` is built before `libsd`, +This is due to the fact that code in `libcore` can panic. `libcore` is built before `libstd`, but we want panics to use the same machinery at runtime, whether they originate in `libcore` or `libstd`. ##### libcore definition of panic! From e87f54b59be65f3a5dc79bb03f7fdad004221c61 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Mon, 2 Dec 2019 13:10:02 -0500 Subject: [PATCH 4/7] Fix name of lang item --- src/panic-implementation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panic-implementation.md b/src/panic-implementation.md index 7de4bc0a2..4dbc5e613 100644 --- a/src/panic-implementation.md +++ b/src/panic-implementation.md @@ -46,7 +46,7 @@ pub fn rust_begin_panic(info: &PanicInfo<'_>) -> ! { The special `panic_handler` attribute is resolved via `src/librustc/middle/lang_items`. The `extract` function converts the `panic_handler` attribute to a `panic_impl` lang item. -Now, we have a matching `panic_impl` lang item in the `libstd`. This function goes +Now, we have a matching `panic_handler` lang item in the `libstd`. This function goes through the same process as the `extern { fn panic_impl }` definition in `libcore`, ending up with a symbol name of `rust_begin_unwind`. At link time, the symbol refernce in `libcore` will be resolved to the definition of `libstd` (the function called `rust_begin_panic` in the From a6c577b316e9cf3947b211f6c1adcbfd6b7ef4ad Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Mon, 2 Dec 2019 13:12:06 -0500 Subject: [PATCH 5/7] Update for method renames in libstd --- src/panic-implementation.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/panic-implementation.md b/src/panic-implementation.md index 4dbc5e613..4f4fb1a98 100644 --- a/src/panic-implementation.md +++ b/src/panic-implementation.md @@ -38,8 +38,8 @@ Actually resolving this goes through several layers of indirection: [cfg(not(test))] [panic_handler] [unwind(allowed)] -pub fn rust_begin_panic(info: &PanicInfo<'_>) -> ! { - continue_panic_fmt(&info) +pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! { + ... } ``` @@ -49,7 +49,7 @@ The `extract` function converts the `panic_handler` attribute to a `panic_impl` Now, we have a matching `panic_handler` lang item in the `libstd`. This function goes through the same process as the `extern { fn panic_impl }` definition in `libcore`, ending up with a symbol name of `rust_begin_unwind`. At link time, the symbol refernce in `libcore` -will be resolved to the definition of `libstd` (the function called `rust_begin_panic` in the +will be resolved to the definition of `libstd` (the function called `begin_panic_handler` in the Rust source). Thus, control flow will pass from libcore to std at runtime. This allows panics from `libcore` From 9f25a0209d51a84e883ba0e0d11d47b4d6b9ec96 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Mon, 2 Dec 2019 13:19:19 -0500 Subject: [PATCH 6/7] Fix line lengths --- src/panic-implementation.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/panic-implementation.md b/src/panic-implementation.md index 4f4fb1a98..b62e26436 100644 --- a/src/panic-implementation.md +++ b/src/panic-implementation.md @@ -4,7 +4,8 @@ There are actually two panic macros - one defined in `libcore`, and one defined in `libstd`. This is due to the fact that code in `libcore` can panic. `libcore` is built before `libstd`, -but we want panics to use the same machinery at runtime, whether they originate in `libcore` or `libstd`. +but we want panics to use the same machinery at runtime, whether they originate in `libcore` +or `libstd`. ##### libcore definition of panic! @@ -65,12 +66,14 @@ we call ```__rust_start_panic```, which is provided by the panic runtime. The call to ```__rust_start_panic``` is very weird - it is passed a ```*mut &mut dyn BoxMeUp```, converted to an `usize`. Let's break this type down: -1. `BoxMeUp` is an internal trait. It is implemented for `PanicPayload` (a wrapper around the user-supplied -payload type), and has a method ```fn box_me_up(&mut self) -> *mut (dyn Any + Send)```. +1. `BoxMeUp` is an internal trait. It is implemented for `PanicPayload` +(a wrapper around the user-supplied payload type), and has a method +```fn box_me_up(&mut self) -> *mut (dyn Any + Send)```. This method takes the user-provided payload (`T: Any + Send`), boxes it, and convertes the box to a raw pointer. -2. When we call ```__rust_start_panic```, we have an `&mut dyn BoxMeUp`. However, this is a fat pointer -(twice the size of a `usize`). To pass this to the panic runtime across an FFI boundary, we take a mutable +2. When we call ```__rust_start_panic```, we have an `&mut dyn BoxMeUp`. +However, this is a fat pointer (twice the size of a `usize`). +To pass this to the panic runtime across an FFI boundary, we take a mutable reference *to this mutable reference* (`&mut &mut dyn BoxMeUp`), and convert it to a raw pointer (`*mut &mut dyn BoxMeUp`). The outer raw pointer is a thin pointer, since it points to a `Sized` type (a mutable reference). Therefore, we can convert this thin pointer into a `usize`, which From 05b70f7d4cc45cc09fa2eb32a1992638ea5df084 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Thu, 2 Jan 2020 22:32:19 -0500 Subject: [PATCH 7/7] Apply fixes Co-Authored-By: Yuki Okushi --- src/panic-implementation.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/panic-implementation.md b/src/panic-implementation.md index b62e26436..4cb4ebb6f 100644 --- a/src/panic-implementation.md +++ b/src/panic-implementation.md @@ -49,29 +49,30 @@ The `extract` function converts the `panic_handler` attribute to a `panic_impl` Now, we have a matching `panic_handler` lang item in the `libstd`. This function goes through the same process as the `extern { fn panic_impl }` definition in `libcore`, ending -up with a symbol name of `rust_begin_unwind`. At link time, the symbol refernce in `libcore` +up with a symbol name of `rust_begin_unwind`. At link time, the symbol reference in `libcore` will be resolved to the definition of `libstd` (the function called `begin_panic_handler` in the Rust source). Thus, control flow will pass from libcore to std at runtime. This allows panics from `libcore` -to go through the same infratructure that other panics use (panic hooks, unwinding, etc) +to go through the same infrastructure that other panics use (panic hooks, unwinding, etc) ##### libstd implementation of panic! -This is where the actual panic-related logic begins. In `src/libstd/pancking.rs`, +This is where the actual panic-related logic begins. In `src/libstd/panicking.rs`, control passes to `rust_panic_with_hook`. This method is responsible for invoking the global panic hook, and checking for double panics. Finally, -we call ```__rust_start_panic```, which is provided by the panic runtime. +we call `__rust_start_panic`, which is provided by the panic runtime. -The call to ```__rust_start_panic``` is very weird - it is passed a ```*mut &mut dyn BoxMeUp```, +The call to `__rust_start_panic` is very weird - it is passed a `*mut &mut dyn BoxMeUp`, converted to an `usize`. Let's break this type down: 1. `BoxMeUp` is an internal trait. It is implemented for `PanicPayload` (a wrapper around the user-supplied payload type), and has a method -```fn box_me_up(&mut self) -> *mut (dyn Any + Send)```. -This method takes the user-provided payload (`T: Any + Send`), boxes it, and convertes the box to a raw pointer. +`fn box_me_up(&mut self) -> *mut (dyn Any + Send)`. +This method takes the user-provided payload (`T: Any + Send`), +boxes it, and converts the box to a raw pointer. -2. When we call ```__rust_start_panic```, we have an `&mut dyn BoxMeUp`. +2. When we call `__rust_start_panic`, we have an `&mut dyn BoxMeUp`. However, this is a fat pointer (twice the size of a `usize`). To pass this to the panic runtime across an FFI boundary, we take a mutable reference *to this mutable reference* (`&mut &mut dyn BoxMeUp`), and convert it to a raw pointer @@ -79,19 +80,19 @@ reference *to this mutable reference* (`&mut &mut dyn BoxMeUp`), and convert it type (a mutable reference). Therefore, we can convert this thin pointer into a `usize`, which is suitable for passing across an FFI boundary. -Finally, we call ```__rust_start_panic``` with this `usize`. We have now entered the panic runtime. +Finally, we call `__rust_start_panic` with this `usize`. We have now entered the panic runtime. #### Step 2: The panic runtime Rust provides two panic runtimes: `libpanic_abort` and `libpanic_unwind`. The user chooses between them at build time via their `Cargo.toml` -`libpanic_abort` is extremely simple: its implementation of ```__rust_start_panic``` just aborts, +`libpanic_abort` is extremely simple: its implementation of `__rust_start_panic` just aborts, as you would expect. `libpanic_unwind` is the more interesting case. -In its implementation of ```__rust_start_panic```, we take the `usize`, convert +In its implementation of `__rust_start_panic`, we take the `usize`, convert it back to a `*mut &mut dyn BoxMeUp`, dereference it, and call `box_me_up` on the `&mut dyn BoxMeUp`. At this point, we have a raw pointer to the payload itself (a `*mut (dyn Send + Any)`): that is, a raw pointer to the actual value