about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJubilee <46493976+workingjubilee@users.noreply.github.com>2024-03-11 09:29:34 -0700
committerGitHub <noreply@github.com>2024-03-11 09:29:34 -0700
commit12798300680b31a61a47c066b6e0d3ba46676fd6 (patch)
tree0da2687dbc8ffa1844571ce695f2b8a9d44ed2fe
parenta4503390ba89559fc60c0374116b4652736e27e4 (diff)
parentc7fcf437f1cde4e7f45dcdaf57e80f178d47bd65 (diff)
downloadrust-12798300680b31a61a47c066b6e0d3ba46676fd6.tar.gz
rust-12798300680b31a61a47c066b6e0d3ba46676fd6.zip
Rollup merge of #121438 - coolreader18:wasm32-panic-unwind, r=cuviper
std support for wasm32 panic=unwind

Tracking issue: #118168

This adds std support for `-Cpanic=unwind` on wasm, and with it slightly more fleshed out rustc support. Now, the stable default is still panic=abort without exception-handling, but if you `-Zbuild-std` with `RUSTFLAGS=-Cpanic=unwind`, you get wasm exception-handling try/catch blocks in the binary:

```rust
#[no_mangle]
pub fn foo_bar(x: bool) -> *mut u8 {
    let s = Box::<str>::from("hello");
    maybe_panic(x);
    Box::into_raw(s).cast()
}

#[inline(never)]
#[no_mangle]
fn maybe_panic(x: bool) {
    if x {
        panic!("AAAAA");
    }
}
```
```wat
;; snip...
(try $label$5
 (do
  (call $maybe_panic
   (local.get $0)
  )
  (br $label$1)
 )
 (catch_all
  (global.set $__stack_pointer
   (local.get $1)
  )
  (call $__rust_dealloc
   (local.get $2)
   (i32.const 5)
   (i32.const 1)
  )
  (rethrow $label$5)
 )
)
;; snip...
```
-rw-r--r--compiler/rustc_codegen_llvm/src/llvm_util.rs9
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/block.rs2
-rw-r--r--library/panic_unwind/src/gcc.rs2
-rw-r--r--library/panic_unwind/src/lib.rs2
-rw-r--r--library/std/src/sys/personality/mod.rs8
-rw-r--r--library/unwind/src/lib.rs8
-rw-r--r--library/unwind/src/libunwind.rs2
-rw-r--r--library/unwind/src/unwinding.rs2
-rw-r--r--library/unwind/src/wasm.rs65
9 files changed, 90 insertions, 10 deletions
diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs
index 78d47f36f91..e32c38644aa 100644
--- a/compiler/rustc_codegen_llvm/src/llvm_util.rs
+++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs
@@ -5,6 +5,7 @@ use crate::errors::{
 };
 use crate::llvm;
 use libc::c_int;
+use rustc_codegen_ssa::base::wants_wasm_eh;
 use rustc_codegen_ssa::traits::PrintBackendInfo;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_data_structures::small_c_str::SmallCStr;
@@ -98,6 +99,10 @@ unsafe fn configure_llvm(sess: &Session) {
             }
         }
 
+        if wants_wasm_eh(sess) {
+            add("-wasm-enable-eh", false);
+        }
+
         if sess.target.os == "emscripten" && sess.panic_strategy() == PanicStrategy::Unwind {
             add("-enable-emscripten-cxx-exceptions", false);
         }
@@ -523,6 +528,10 @@ pub(crate) fn global_llvm_features(sess: &Session, diagnostics: bool) -> Vec<Str
             .map(String::from),
     );
 
+    if wants_wasm_eh(sess) && sess.panic_strategy() == PanicStrategy::Unwind {
+        features.push("+exception-handling".into());
+    }
+
     // -Ctarget-features
     let supported_features = sess.target.supported_target_features();
     let mut featsmap = FxHashMap::default();
diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs
index 89a44d2897a..d3f5de25d9a 100644
--- a/compiler/rustc_codegen_ssa/src/mir/block.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/block.rs
@@ -1597,7 +1597,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
         let funclet;
         let llbb;
         let mut bx;
-        if base::wants_msvc_seh(self.cx.sess()) {
+        if base::wants_new_eh_instructions(self.cx.sess()) {
             // This is a basic block that we're aborting the program for,
             // notably in an `extern` function. These basic blocks are inserted
             // so that we assert that `extern` functions do indeed not panic,
diff --git a/library/panic_unwind/src/gcc.rs b/library/panic_unwind/src/gcc.rs
index 54eb6627c01..589d3c1b4d2 100644
--- a/library/panic_unwind/src/gcc.rs
+++ b/library/panic_unwind/src/gcc.rs
@@ -62,7 +62,7 @@ pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
     let exception = Box::new(Exception {
         _uwe: uw::_Unwind_Exception {
             exception_class: rust_exception_class(),
-            exception_cleanup,
+            exception_cleanup: Some(exception_cleanup),
             private: [core::ptr::null(); uw::unwinder_private_data_size],
         },
         canary: &CANARY,
diff --git a/library/panic_unwind/src/lib.rs b/library/panic_unwind/src/lib.rs
index 7a0bae34642..dde1c64c6f1 100644
--- a/library/panic_unwind/src/lib.rs
+++ b/library/panic_unwind/src/lib.rs
@@ -53,12 +53,12 @@ cfg_if::cfg_if! {
         target_os = "solid_asp3",
         all(target_family = "unix", not(target_os = "espidf")),
         all(target_vendor = "fortanix", target_env = "sgx"),
+        target_family = "wasm",
     ))] {
         #[path = "gcc.rs"]
         mod real_imp;
     } else {
         // Targets that don't support unwinding.
-        // - family=wasm
         // - os=none ("bare metal" targets)
         // - os=uefi
         // - os=espidf
diff --git a/library/std/src/sys/personality/mod.rs b/library/std/src/sys/personality/mod.rs
index d37b8ce6346..1a6ea1dafcb 100644
--- a/library/std/src/sys/personality/mod.rs
+++ b/library/std/src/sys/personality/mod.rs
@@ -16,11 +16,12 @@ mod dwarf;
 cfg_if::cfg_if! {
     if #[cfg(target_os = "emscripten")] {
         mod emcc;
-    } else if #[cfg(target_env = "msvc")] {
+    } else if #[cfg(any(target_env = "msvc", target_family = "wasm"))] {
         // This is required by the compiler to exist (e.g., it's a lang item),
         // but it's never actually called by the compiler because
-        // _CxxFrameHandler3 is the personality function that is always used.
-        // Hence this is just an aborting stub.
+        // __CxxFrameHandler3 (msvc) / __gxx_wasm_personality_v0 (wasm) is the
+        // personality function that is always used.  Hence this is just an
+        // aborting stub.
         #[lang = "eh_personality"]
         fn rust_eh_personality() {
             core::intrinsics::abort()
@@ -36,7 +37,6 @@ cfg_if::cfg_if! {
         mod gcc;
     } else {
         // Targets that don't support unwinding.
-        // - family=wasm
         // - os=none ("bare metal" targets)
         // - os=uefi
         // - os=espidf
diff --git a/library/unwind/src/lib.rs b/library/unwind/src/lib.rs
index a64f2904633..45aa0ea4fbb 100644
--- a/library/unwind/src/lib.rs
+++ b/library/unwind/src/lib.rs
@@ -6,6 +6,10 @@
 #![cfg_attr(bootstrap, feature(cfg_target_abi))]
 #![feature(strict_provenance)]
 #![cfg_attr(not(target_env = "msvc"), feature(libc))]
+#![cfg_attr(
+    all(target_family = "wasm", not(target_os = "emscripten")),
+    feature(link_llvm_intrinsics)
+)]
 #![allow(internal_features)]
 
 cfg_if::cfg_if! {
@@ -29,9 +33,11 @@ cfg_if::cfg_if! {
     } else if #[cfg(target_os = "xous")] {
         mod unwinding;
         pub use unwinding::*;
+    } else if #[cfg(target_family = "wasm")] {
+        mod wasm;
+        pub use wasm::*;
     } else {
         // no unwinder on the system!
-        // - wasm32 (not emscripten, which is "unix" family)
         // - os=none ("bare metal" targets)
         // - os=hermit
         // - os=uefi
diff --git a/library/unwind/src/libunwind.rs b/library/unwind/src/libunwind.rs
index 527c408c89e..0a0dc9438b3 100644
--- a/library/unwind/src/libunwind.rs
+++ b/library/unwind/src/libunwind.rs
@@ -91,7 +91,7 @@ pub struct _Unwind_Exception {
 pub enum _Unwind_Context {}
 
 pub type _Unwind_Exception_Cleanup_Fn =
-    extern "C" fn(unwind_code: _Unwind_Reason_Code, exception: *mut _Unwind_Exception);
+    Option<extern "C" fn(unwind_code: _Unwind_Reason_Code, exception: *mut _Unwind_Exception)>;
 
 // FIXME: The `#[link]` attributes on `extern "C"` block marks those symbols declared in
 // the block are reexported in dylib build of std. This is needed when build rustc with
diff --git a/library/unwind/src/unwinding.rs b/library/unwind/src/unwinding.rs
index 1a4187b2220..95e2eb000cc 100644
--- a/library/unwind/src/unwinding.rs
+++ b/library/unwind/src/unwinding.rs
@@ -46,7 +46,7 @@ pub const unwinder_private_data_size: usize = core::mem::size_of::<UnwindExcepti
     - core::mem::size_of::<_Unwind_Exception_Cleanup_Fn>();
 
 pub type _Unwind_Exception_Cleanup_Fn =
-    extern "C" fn(unwind_code: _Unwind_Reason_Code, exception: *mut _Unwind_Exception);
+    Option<extern "C" fn(unwind_code: _Unwind_Reason_Code, exception: *mut _Unwind_Exception)>;
 
 #[repr(C)]
 pub struct _Unwind_Exception {
diff --git a/library/unwind/src/wasm.rs b/library/unwind/src/wasm.rs
new file mode 100644
index 00000000000..b06671bcb83
--- /dev/null
+++ b/library/unwind/src/wasm.rs
@@ -0,0 +1,65 @@
+//! A shim for libunwind implemented in terms of the native wasm `throw` instruction.
+
+#![allow(nonstandard_style)]
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub enum _Unwind_Reason_Code {
+    _URC_NO_REASON = 0,
+    _URC_FOREIGN_EXCEPTION_CAUGHT = 1,
+    _URC_FATAL_PHASE2_ERROR = 2,
+    _URC_FATAL_PHASE1_ERROR = 3,
+    _URC_NORMAL_STOP = 4,
+    _URC_END_OF_STACK = 5,
+    _URC_HANDLER_FOUND = 6,
+    _URC_INSTALL_CONTEXT = 7,
+    _URC_CONTINUE_UNWIND = 8,
+    _URC_FAILURE = 9, // used only by ARM EHABI
+}
+pub use _Unwind_Reason_Code::*;
+
+pub type _Unwind_Exception_Class = u64;
+pub type _Unwind_Word = *const u8;
+
+pub const unwinder_private_data_size: usize = 2;
+
+#[repr(C)]
+pub struct _Unwind_Exception {
+    pub exception_class: _Unwind_Exception_Class,
+    pub exception_cleanup: _Unwind_Exception_Cleanup_Fn,
+    pub private: [_Unwind_Word; unwinder_private_data_size],
+}
+
+pub type _Unwind_Exception_Cleanup_Fn =
+    Option<extern "C" fn(unwind_code: _Unwind_Reason_Code, exception: *mut _Unwind_Exception)>;
+
+pub unsafe fn _Unwind_DeleteException(exception: *mut _Unwind_Exception) {
+    if let Some(exception_cleanup) = unsafe { (*exception).exception_cleanup } {
+        exception_cleanup(_URC_FOREIGN_EXCEPTION_CAUGHT, exception);
+    }
+}
+
+pub unsafe fn _Unwind_RaiseException(exception: *mut _Unwind_Exception) -> _Unwind_Reason_Code {
+    #[cfg(panic = "unwind")]
+    extern "C" {
+        /// LLVM lowers this intrinsic to the `throw` instruction.
+        // FIXME(coolreader18): move to stdarch
+        #[link_name = "llvm.wasm.throw"]
+        fn wasm_throw(tag: i32, ptr: *mut u8) -> !;
+    }
+
+    // The wasm `throw` instruction takes a "tag", which differentiates certain
+    // types of exceptions from others. LLVM currently just identifies these
+    // via integers, with 0 corresponding to C++ exceptions and 1 to C setjmp()/longjmp().
+    // Ideally, we'd be able to choose something unique for Rust, but for now,
+    // we pretend to be C++ and implement the Itanium exception-handling ABI.
+    cfg_if::cfg_if! {
+        // for now, unless we're -Zbuild-std with panic=unwind, never codegen a throw.
+        if #[cfg(panic = "unwind")] {
+            wasm_throw(0, exception.cast())
+        } else {
+            let _ = exception;
+            core::arch::wasm32::unreachable()
+        }
+    }
+}