Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement mrb_raise with Rust panic! #1904

Open
wants to merge 22 commits into
base: trunk
Choose a base branch
from

Conversation

lopopolo
Copy link
Member

Fixes #35.

WIP, blocked on stabilization of the c_unwind feature.

Compatibility Notes

This PR changes Artichoke to compile mruby as C++ instead of C. A compiler that supports c++17 is required.

By compiling mruby as C++, artichoke-backend's build.rs is able to set -DMRB_USE_CXX_EXCEPTION which uses C++ try/catch instead of setjmp/longjmp for unwinding in the parser.

C++ exceptions are possible now with the C-unwind ABI; whereas before they would be UB if passing through Rust stack frames.

Hopefully this improves the state of the playground's Emscripten bundle when raising Ruby Exceptions in native code:

@lopopolo lopopolo added S-blocked Status: Marked as blocked ❌ on something else such as other implementation work. CAPI-mruby C API: mruby MRB_API-compatible C API. A-C-API Area: C APIs for compatibility with existing interpreters. A-vm Area: Interpreter VM implementations. O-wasm-emscripten Target: Support for building the `wasm32-unknown-emscripten` target. S-wip Status: This pull request is a work in progress. B-mruby Backend: Implementation of artichoke-core using mruby. A-packaging Area: Packaging releases for distribution. labels Jun 12, 2022
@lopopolo lopopolo force-pushed the lopopolo/c-unwind-mrb_raise-panic branch 3 times, most recently from f0019ad to ee80042 Compare June 28, 2022 02:20
@lopopolo
Copy link
Member Author

Blocked on c_unwind feature stabilization which is blocked on this ticket which is the source of the compilation errors on this branch:

@lopopolo
Copy link
Member Author

Errors with the latest nightly:

error[E0277]: `unsafe extern "C-unwind" fn(*mut sys::ffi::mrb_state, sys::ffi::mrb_value) -> sys::ffi::mrb_value` doesn't implement `std::fmt::Debug`
  --> artichoke-backend/src/method.rs:22:5
   |
17 | #[derive(Debug, Clone)]
   |          ----- in this derive macro expansion
...
22 |     method: Method,
   |     ^^^^^^^^^^^^^^ `unsafe extern "C-unwind" fn(*mut sys::ffi::mrb_state, sys::ffi::mrb_value) -> sys::ffi::mrb_value` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`
   |
   = help: the trait `std::fmt::Debug` is not implemented for `unsafe extern "C-unwind" fn(*mut sys::ffi::mrb_state, sys::ffi::mrb_value) -> sys::ffi::mrb_value`
   = help: the following other types implement trait `std::fmt::Debug`:
             extern "C" fn() -> Ret
             extern "C" fn(A) -> Ret
             extern "C" fn(A, ...) -> Ret
             extern "C" fn(A, B) -> Ret
             extern "C" fn(A, B, ...) -> Ret
             extern "C" fn(A, B, C) -> Ret
             extern "C" fn(A, B, C, ...) -> Ret
             extern "C" fn(A, B, C, D) -> Ret
           and 68 others
   = note: this error originates in the derive macro `Debug` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: `unsafe extern "C-unwind" fn(*mut sys::ffi::mrb_state, *mut c_void, usize, *mut c_void) -> *mut c_void` doesn't implement `std::fmt::Debug`
    --> /Users/lopopolo/dev/artichoke/artichoke/target/debug/build/artichoke-backend-6cd3d86938d9a17a/out/ffi.rs:1612:5
     |
1609 | #[derive(Debug, Copy, Clone)]
     |          ----- in this derive macro expansion
...
1612 |     pub allocf: mrb_allocf,
     |     ^^^^^^^^^^^^^^^^^^^^^^ `unsafe extern "C-unwind" fn(*mut sys::ffi::mrb_state, *mut c_void, usize, *mut c_void) -> *mut c_void` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`
     |
     = help: the trait `std::fmt::Debug` is not implemented for `unsafe extern "C-unwind" fn(*mut sys::ffi::mrb_state, *mut c_void, usize, *mut c_void) -> *mut c_void`
     = help: the following other types implement trait `std::fmt::Debug`:
               extern "C" fn() -> Ret
               extern "C" fn(A) -> Ret
               extern "C" fn(A, ...) -> Ret
               extern "C" fn(A, B) -> Ret
               extern "C" fn(A, B, ...) -> Ret
               extern "C" fn(A, B, C) -> Ret
               extern "C" fn(A, B, C, ...) -> Ret
               extern "C" fn(A, B, C, D) -> Ret
             and 68 others
     = note: this error originates in the derive macro `Debug` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: `unsafe extern "C-unwind" fn(*mut mrb_parser_state) -> i32` doesn't implement `std::fmt::Debug`
    --> /Users/lopopolo/dev/artichoke/artichoke/target/debug/build/artichoke-backend-6cd3d86938d9a17a/out/ffi.rs:3546:5
     |
3540 |   #[derive(Debug, Copy, Clone)]
     |            ----- in this derive macro expansion
...
3546 | /     pub partial_hook:
3547 | |         ::std::option::Option<unsafe extern "C-unwind" fn(arg1: *mut mrb_parser_state) -> ::std::os::raw::c_int>,
     | |________________________________________________________________________________________________________________^ `unsafe extern "C-unwind" fn(*mut mrb_parser_state) -> i32` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`
     |
     = help: the trait `std::fmt::Debug` is not implemented for `unsafe extern "C-unwind" fn(*mut mrb_parser_state) -> i32`
     = help: the following other types implement trait `std::fmt::Debug`:
               extern "C" fn() -> Ret
               extern "C" fn(A) -> Ret
               extern "C" fn(A, ...) -> Ret
               extern "C" fn(A, B) -> Ret
               extern "C" fn(A, B, ...) -> Ret
               extern "C" fn(A, B, C) -> Ret
               extern "C" fn(A, B, C, ...) -> Ret
               extern "C" fn(A, B, C, D) -> Ret
             and 68 others
     = note: this error originates in the derive macro `Debug` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: `unsafe extern "C-unwind" fn(*mut sys::ffi::mrb_state, *mut c_void)` doesn't implement `std::fmt::Debug`
    --> /Users/lopopolo/dev/artichoke/artichoke/target/debug/build/artichoke-backend-6cd3d86938d9a17a/out/ffi.rs:5007:5
     |
5004 | #[derive(Debug, Copy, Clone)]
     |          ----- in this derive macro expansion
...
5007 |     pub dfree: ::std::option::Option<unsafe extern "C-unwind" fn(mrb: *mut mrb_state, arg1: *mut ::std::os::raw::c_void)>,
     |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `unsafe extern "C-unwind" fn(*mut sys::ffi::mrb_state, *mut c_void)` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`
     |
     = help: the trait `std::fmt::Debug` is not implemented for `unsafe extern "C-unwind" fn(*mut sys::ffi::mrb_state, *mut c_void)`
     = help: the following other types implement trait `std::fmt::Debug`:
               extern "C" fn() -> Ret
               extern "C" fn(A) -> Ret
               extern "C" fn(A, ...) -> Ret
               extern "C" fn(A, B) -> Ret
               extern "C" fn(A, B, ...) -> Ret
               extern "C" fn(A, B, C) -> Ret
               extern "C" fn(A, B, C, ...) -> Ret
               extern "C" fn(A, B, C, D) -> Ret
             and 68 others
     = note: this error originates in the derive macro `Debug` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0277`.
error: could not compile `artichoke-backend` due to 4 previous errors

Nightly version

$ rustc -v --version
rustc 1.64.0-nightly (f8588549c 2022-07-18)
binary: rustc
commit-hash: f8588549c3c3d45c32b404210cada01e2a45def3
commit-date: 2022-07-18
host: x86_64-apple-darwin
release: 1.64.0-nightly
LLVM version: 14.0.6

@lopopolo lopopolo force-pushed the lopopolo/c-unwind-mrb_raise-panic branch from f6a053b to 20157e8 Compare July 24, 2022 01:50
@lopopolo
Copy link
Member Author

@lopopolo
Copy link
Member Author

Unblocked on nightly I believe now that fmt::Debug impls are merged:

@lopopolo
Copy link
Member Author

The nightly changes made it, but now blocked on linker failures in mruby:

@lopopolo lopopolo force-pushed the lopopolo/c-unwind-mrb_raise-panic branch from 20157e8 to 84506ca Compare October 24, 2022 06:11
@lopopolo lopopolo force-pushed the lopopolo/c-unwind-mrb_raise-panic branch from 84506ca to 59e1fb4 Compare November 8, 2022 04:19
The Rust team has put out a call for testing on the `c_unwind` feature
and associated `C-unwind` ABI:

- rust-lang/rfcs#2945 (comment)

I intend to implement mruby's exception unwinding using Rust's panic
infrastructure:

- #35

This commit changes the Rust toolchain to nightly for this test. Merging
this experiment will be blocked on the `C-unwind` ABI making it to
stable.
`mruby-error` implements `mrb_protect` and `mrb_ensure`. These two
functions are used to stop the propagation of exception unwinding in
mruby, which natively uses either `setjmp`/`longjmp` or C++'s `catch`
and `throw`.

I intend to reimplement these functions in Rust using Rust panics:
`std::panic::catch_unwind` and `std::panic::panic_any`.

See:

- #35
When `-DARTICHOKE` is passed when compiling mruby, `#ifndef` out the
definition and implementation of the `exc_throw` static function and
replace it with a forward declaration.

This function normally uses the `MRB_THROW` macro to initiate an unwind.

This prepares us to implement this function in Rust with a
`#[no_mangle] unsafe extern "C-unwind" fn` item that unwinds with a Rust
panic.

The previous commit disables the implementations of `mrb_protect` and
`mrb_ensure` which pair usage of `MRB_TRY` and `MRB_CATCH` with the
`MRB_THROW` in `exc_throw`.
- Add a custom panic payload which wraps a `sys::mrb_value` and
  implements the necessary traits to be used with `panic!`.
- Implement `exc_throw` with `std::panic::panic_any` and the custom
  panic payload.
- Implement `mrb_protect` with `std::panic::catch_unwind` and
  downcasting to check for the custom panic payload.

When using `std::panic::panic_any`, the built-in Rust panic hook is
triggered which prints the panic to stderr and enables the
`RUST_BACKTRACE` handler. This commit includes some code that
experiments with disabling these built-in behaviors.

Per feedback from upstream, `panic_any` should be replaced with
`std::panic::resume_unwind` which initiates the panic with a custom
payload and avoids running the registered panic hook.
Per feedback from upstream, to avoid printing to stderr when panicking,
I can use `std::panic::resume_unwind` instead of `std::panic::panic_any`.
bindgen does not support customizing the ABI of function and function
pointer bindings. This commit hacks support for `C-unwind` ABI into the
generated bindings by doing a find and replace.
- Update `Free` function type alias.
- Update `Method` function type alias.
- Update `box_unbox_free` to use `C-unwind` ABI.
mruby supports compilation as a C++ library with a C++ compiler by
defining the `MRB_USE_CXX_EXCEPTION` macro.

Per upstream, it is always UB to unwind past stack frames that assume
that no unwinding will occur. On most compilers, C code is compiled by
default with this assumption. In Rust, C code compiled this way
corresponds to the `unsafe extern "C" fn` ABI.

This branch changes Rust functions and the mruby bindings to use the
`C-unwind` ABI, which corresponds to C compiled with support for
exceptions and unwinding. The unwinding mechanism used by Rust and its
`C-unwind` ABI is the same mechanism used by C++ for its `try`/`catch`
exception handling.

These changes are similar to if mruby was compiled by a C compiler with
`-fexceptions`. However, several parts of mruby, like the codegen
module, use the `MRB_TRY` and `MRB_CATCH` macros still. Compiling mruby
as C++ means `setjmp`/`longjmp` will no longer be used for these
internal unwinding cases.

`-x c++` is passed to the `cc` crate and to `clang` via `bindgen` to
avoid this deprecation warning:

    warning: clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated [-Wdeprecated]
Neither Artichoke nor its vendored mruby sources call `mrb_ensure`, but
implementing it ensures we have maintain an API/ABI compatible mruby
implementation.
- Ensure Rust panics are propagated and ignored by `mrb_protect` and
  `mrb_ensure`.
- Add safety comments to unsafe trait impls.
- Add documentation to panic payload.
- Add inline comments.
- Properly forward Artichoke `Error`s.
Instead of disabling the static function `exc_throw` in C code when
compiling for Artichoke and implementing the function with global
linkage in Rust as a `#[no_mangle] extern "C" fn`, implement an
Artichoke-specific `extern "C" fn` and make `exc_throw` an alias to it
with a macro.
This commit includes changes to parse.y. The parser is regenerated with:

```
/usr/local/opt/bison/bin/bison -o artichoke-backend/vendor/mruby/mrbgems/mruby-compiler/core/y.tab.c artichoke-backend/vendor/mruby/mrbgems/mruby-compiler/core/parse.y
sed -i '' -e 's#artichoke-backend/vendor/mruby/##g' artichoke-backend/vendor/mruby/mrbgems/mruby-compiler/core/y.tab.c
```
- Improve safety comment on `ExceptionPayload` unsafe impl
- Document why `panic::resume_unwind` is used instead of `panic_any!`
@lopopolo lopopolo force-pushed the lopopolo/c-unwind-mrb_raise-panic branch from 59e1fb4 to 958bece Compare November 8, 2022 04:37
@lopopolo
Copy link
Member Author

lopopolo commented Nov 9, 2022

I think this PR is stuck. The VM relies on being able to catch unwinds so it can enter the rescue flow:

MRB_CATCH(&c_jmp) {
mrb_callinfo *ci = mrb->c->ci;
while (ci > mrb->c->cibase && ci->cci == CINFO_DIRECT) {
ci = cipop(mrb);
}
exc_catched = TRUE;
pc = ci->pc;
goto RETRY_TRY_BLOCK;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-C-API Area: C APIs for compatibility with existing interpreters. A-packaging Area: Packaging releases for distribution. A-vm Area: Interpreter VM implementations. B-mruby Backend: Implementation of artichoke-core using mruby. CAPI-mruby C API: mruby MRB_API-compatible C API. O-wasm-emscripten Target: Support for building the `wasm32-unknown-emscripten` target. S-blocked Status: Marked as blocked ❌ on something else such as other implementation work. S-wip Status: This pull request is a work in progress.
Development

Successfully merging this pull request may close these issues.

Implement mrb_raise with panic
1 participant