From 3908a935ef821c2828a7d825eac859f8ff54702a Mon Sep 17 00:00:00 2001 From: Noa Date: Wed, 21 Feb 2024 17:32:58 -0600 Subject: [PATCH 1/5] std support for wasm32 panic=unwind --- compiler/rustc_codegen_llvm/src/llvm_util.rs | 13 ++++++-- library/panic_unwind/src/lib.rs | 13 +++++++- library/panic_unwind/src/wasm.rs | 32 ++++++++++++++++++++ library/std/src/sys/personality/mod.rs | 8 ++--- 4 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 library/panic_unwind/src/wasm.rs diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index 54e8ed85e32..e383673e1d4 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::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,8 +99,12 @@ fn llvm_arg_to_arg_name(full_arg: &str) -> &str { } } - if sess.target.os == "emscripten" && sess.panic_strategy() == PanicStrategy::Unwind { - add("-enable-emscripten-cxx-exceptions", false); + if sess.panic_strategy() == PanicStrategy::Unwind { + if sess.target.os == "emscripten" { + add("-enable-emscripten-cxx-exceptions", false); + } else if wants_wasm_eh(sess) { + add("-wasm-enable-eh", false); + } } // HACK(eddyb) LLVM inserts `llvm.assume` calls to preserve align attributes @@ -520,6 +525,10 @@ pub(crate) fn global_llvm_features(sess: &Session, diagnostics: bool) -> Vec>; + +extern "C" { + /// LLVM lowers this intrinsic to the `throw` instruction. + #[link_name = "llvm.wasm.throw"] + fn wasm_throw(tag: i32, ptr: *mut u8) -> !; +} + +pub unsafe fn panic(payload: Box) -> u32 { + // The payload we pass to `wasm_throw` will be exactly the argument we get + // in `cleanup` below. So we just box it up once, to get something pointer-sized. + let payload_box: Payload = Box::new(payload); + // 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, such that we + // don't try to treat a C++ exception payload as a `Box>`, but + // otherwise, pretending to be C++ works for now. + wasm_throw(0, Box::into_raw(payload_box) as *mut u8) +} + +pub unsafe fn cleanup(payload_box: *mut u8) -> Box { + // Recover the underlying `Box`. + let payload_box: Payload = Box::from_raw(payload_box as *mut _); + *payload_box +} 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 @@ 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 @@ fn rust_eh_personality() { mod gcc; } else { // Targets that don't support unwinding. - // - family=wasm // - os=none ("bare metal" targets) // - os=uefi // - os=espidf From 861c7e74c8bbc49082dcd38ef7169d99c69cd104 Mon Sep 17 00:00:00 2001 From: Noa Date: Thu, 22 Feb 2024 00:34:54 -0600 Subject: [PATCH 2/5] Fix llvm hang --- compiler/rustc_codegen_ssa/src/mir/block.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index 75d413dedad..4a4ac60d59e 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -1539,7 +1539,7 @@ fn terminate_block(&mut self, reason: UnwindTerminateReason) -> Bx::BasicBlock { 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, From 658a0a20eac4341e31c57cf08e019b53f09b93b4 Mon Sep 17 00:00:00 2001 From: Noa Date: Thu, 22 Feb 2024 16:52:48 -0600 Subject: [PATCH 3/5] Unconditionally pass -wasm-enable-eh --- compiler/rustc_codegen_llvm/src/llvm_util.rs | 12 ++++++------ library/panic_unwind/src/lib.rs | 7 +------ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index e383673e1d4..51e1f408a69 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -99,12 +99,12 @@ fn llvm_arg_to_arg_name(full_arg: &str) -> &str { } } - if sess.panic_strategy() == PanicStrategy::Unwind { - if sess.target.os == "emscripten" { - add("-enable-emscripten-cxx-exceptions", false); - } else if wants_wasm_eh(sess) { - add("-wasm-enable-eh", false); - } + 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); } // HACK(eddyb) LLVM inserts `llvm.assume` calls to preserve align attributes diff --git a/library/panic_unwind/src/lib.rs b/library/panic_unwind/src/lib.rs index bc3c9363d8a..37e1d2cf6d1 100644 --- a/library/panic_unwind/src/lib.rs +++ b/library/panic_unwind/src/lib.rs @@ -60,12 +60,7 @@ ))] { #[path = "gcc.rs"] mod real_imp; - } else if #[cfg(all(target_family = "wasm", panic = "unwind"))] { - // for now, PanicStrategy::Unwind is not the default for wasm targets, - // so we need the panic = "unwind" in the cfg above. to use llvm.wasm.throw, - // we need to pass -wasm-enable-eh to LLVM, but that only happens if rustc - // is compiling with -C panic=unwind. So, this lets us -Zbuild-std with - // panic=unwind, while keeping the default panic=abort working. + } else if #[cfg(target_family = "wasm")] { #[path = "wasm.rs"] mod real_imp; } else { From 125b26acf6f3127ecdd344c372691cefe0e9243e Mon Sep 17 00:00:00 2001 From: Noa Date: Thu, 22 Feb 2024 17:32:06 -0600 Subject: [PATCH 4/5] Use Itanium ABI for thrown exceptions --- library/panic_unwind/src/gcc.rs | 2 +- library/panic_unwind/src/lib.rs | 8 +---- library/panic_unwind/src/wasm.rs | 32 ------------------ library/unwind/src/lib.rs | 8 ++++- library/unwind/src/libunwind.rs | 2 +- library/unwind/src/unwinding.rs | 2 +- library/unwind/src/wasm.rs | 56 ++++++++++++++++++++++++++++++++ 7 files changed, 67 insertions(+), 43 deletions(-) delete mode 100644 library/panic_unwind/src/wasm.rs create mode 100644 library/unwind/src/wasm.rs 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) -> 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 37e1d2cf6d1..dde1c64c6f1 100644 --- a/library/panic_unwind/src/lib.rs +++ b/library/panic_unwind/src/lib.rs @@ -16,10 +16,6 @@ #![doc(issue_tracker_base_url = "https://github.com/rust-lang/rust/issues/")] #![feature(core_intrinsics)] #![feature(lang_items)] -#![cfg_attr( - all(target_family = "wasm", not(target_os = "emscripten")), - feature(link_llvm_intrinsics) -)] #![feature(panic_unwind)] #![feature(staged_api)] #![feature(std_internals)] @@ -57,12 +53,10 @@ 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 if #[cfg(target_family = "wasm")] { - #[path = "wasm.rs"] - mod real_imp; } else { // Targets that don't support unwinding. // - os=none ("bare metal" targets) diff --git a/library/panic_unwind/src/wasm.rs b/library/panic_unwind/src/wasm.rs deleted file mode 100644 index b11fb912b63..00000000000 --- a/library/panic_unwind/src/wasm.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Unwinding panics for wasm32. -use alloc::boxed::Box; -use core::any::Any; - -// The type of the exception payload that the wasm engine propagates -// through unwinding for us. LLVM requires that it be a thin pointer. -type Payload = Box>; - -extern "C" { - /// LLVM lowers this intrinsic to the `throw` instruction. - #[link_name = "llvm.wasm.throw"] - fn wasm_throw(tag: i32, ptr: *mut u8) -> !; -} - -pub unsafe fn panic(payload: Box) -> u32 { - // The payload we pass to `wasm_throw` will be exactly the argument we get - // in `cleanup` below. So we just box it up once, to get something pointer-sized. - let payload_box: Payload = Box::new(payload); - // 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, such that we - // don't try to treat a C++ exception payload as a `Box>`, but - // otherwise, pretending to be C++ works for now. - wasm_throw(0, Box::into_raw(payload_box) as *mut u8) -} - -pub unsafe fn cleanup(payload_box: *mut u8) -> Box { - // Recover the underlying `Box`. - let payload_box: Payload = Box::from_raw(payload_box as *mut _); - *payload_box -} diff --git a/library/unwind/src/lib.rs b/library/unwind/src/lib.rs index f5988a4df13..c25c949b942 100644 --- a/library/unwind/src/lib.rs +++ b/library/unwind/src/lib.rs @@ -6,6 +6,10 @@ #![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 @@ } 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 1b5f6f9dde3..617a2ad7ebc 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; // 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 enum _Unwind_Context {} - 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; #[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..1de12905f89 --- /dev/null +++ b/library/unwind/src/wasm.rs @@ -0,0 +1,56 @@ +//! 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; + +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 { + 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. + wasm_throw(0, exception.cast()) +} From c7fcf437f1cde4e7f45dcdaf57e80f178d47bd65 Mon Sep 17 00:00:00 2001 From: Noa Date: Mon, 26 Feb 2024 11:56:48 -0600 Subject: [PATCH 5/5] Don't codegen wasm.throw unless with -Zbuild-std --- library/unwind/src/wasm.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/library/unwind/src/wasm.rs b/library/unwind/src/wasm.rs index 1de12905f89..b06671bcb83 100644 --- a/library/unwind/src/wasm.rs +++ b/library/unwind/src/wasm.rs @@ -40,6 +40,7 @@ pub unsafe fn _Unwind_DeleteException(exception: *mut _Unwind_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 @@ -52,5 +53,13 @@ pub unsafe fn _Unwind_RaiseException(exception: *mut _Unwind_Exception) -> _Unwi // 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. - wasm_throw(0, exception.cast()) + 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() + } + } }