diff --git a/compiler/rustc_mir_transform/messages.ftl b/compiler/rustc_mir_transform/messages.ftl index cfc9b8edf7a28..7924c015200f1 100644 --- a/compiler/rustc_mir_transform/messages.ftl +++ b/compiler/rustc_mir_transform/messages.ftl @@ -1,4 +1,7 @@ mir_transform_arithmetic_overflow = this arithmetic operation will overflow + +mir_transform_asm_unwind_call = call to inline assembly that may unwind + mir_transform_const_defined_here = `const` item defined here mir_transform_const_modify = attempting to modify a `const` item diff --git a/compiler/rustc_mir_transform/src/errors.rs b/compiler/rustc_mir_transform/src/errors.rs index 517e4dd692625..ee21d4fbceadc 100644 --- a/compiler/rustc_mir_transform/src/errors.rs +++ b/compiler/rustc_mir_transform/src/errors.rs @@ -143,6 +143,13 @@ impl AssertLintKind { } } +#[derive(LintDiagnostic)] +#[diag(mir_transform_asm_unwind_call)] +pub(crate) struct AsmUnwindCall { + #[label(mir_transform_asm_unwind_call)] + pub span: Span, +} + #[derive(LintDiagnostic)] #[diag(mir_transform_ffi_unwind_call)] pub(crate) struct FfiUnwindCall { diff --git a/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs b/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs index 7c66783548ea4..06e7bf974b515 100644 --- a/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs +++ b/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs @@ -1,4 +1,5 @@ use rustc_abi::ExternAbi; +use rustc_ast::InlineAsmOptions; use rustc_hir::def_id::{LOCAL_CRATE, LocalDefId}; use rustc_middle::mir::*; use rustc_middle::query::{LocalCrate, Providers}; @@ -46,6 +47,34 @@ fn has_ffi_unwind_calls(tcx: TyCtxt<'_>, local_def_id: LocalDefId) -> bool { continue; } let Some(terminator) = &block.terminator else { continue }; + + if let TerminatorKind::InlineAsm { options, .. } = &terminator.kind { + if options.contains(InlineAsmOptions::MAY_UNWIND) { + // We have detected an inline asm block that can possibly leak foreign unwind. + // + // Because the function body itself can unwind, we are not aborting this function call + // upon unwind, so this call can possibly leak foreign unwind into Rust code if the + // panic runtime linked is panic-abort. + + let lint_root = body.source_scopes[terminator.source_info.scope] + .local_data + .as_ref() + .unwrap_crate_local() + .lint_root; + let span = terminator.source_info.span; + + tcx.emit_node_span_lint( + FFI_UNWIND_CALLS, + lint_root, + span, + errors::AsmUnwindCall { span }, + ); + + tainted = true; + } + continue; + } + let TerminatorKind::Call { func, .. } = &terminator.kind else { continue }; let ty = func.ty(body, tcx); diff --git a/library/unwind/src/lib.rs b/library/unwind/src/lib.rs index e3a0a77f53f08..0510e0e79c31f 100644 --- a/library/unwind/src/lib.rs +++ b/library/unwind/src/lib.rs @@ -7,7 +7,7 @@ #![cfg_attr(not(target_env = "msvc"), feature(libc))] #![cfg_attr( all(target_family = "wasm", any(not(target_os = "emscripten"), emscripten_wasm_eh)), - feature(link_llvm_intrinsics, simd_wasm64) + feature(asm_experimental_arch, asm_unwind, simd_wasm64) )] #![allow(internal_features)] #![deny(unsafe_op_in_unsafe_fn)] diff --git a/library/unwind/src/wasm.rs b/library/unwind/src/wasm.rs index 2bff306af293f..af4a91e334b63 100644 --- a/library/unwind/src/wasm.rs +++ b/library/unwind/src/wasm.rs @@ -47,33 +47,19 @@ pub unsafe fn _Unwind_RaiseException(exception: *mut _Unwind_Exception) -> _Unwi // enabled exceptions via `-Z build-std` with `-C panic=unwind`. cfg_select! { panic = "unwind" => { - // It's important that this intrinsic is defined here rather than in `core`. Since it - // unwinds, invoking it from Rust code compiled with `-C panic=unwind` immediately - // forces `panic_unwind` as the required panic runtime. - // - // We ship unwinding `core` on Emscripten, so making this intrinsic part of `core` would - // prevent linking precompiled `core` into `-C panic=abort` binaries. Unlike `core`, - // this particular module is never precompiled with `-C panic=unwind` because it's only - // used for bare-metal targets, so an error can only arise if the user both manually - // recompiles `std` with `-C panic=unwind` and manually compiles the binary crate with - // `-C panic=abort`, which we don't care to support. - // - // See https://github.com/rust-lang/rust/issues/148246. - unsafe extern "C-unwind" { - /// LLVM lowers this intrinsic to the `throw` instruction. - #[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. - // corresponds with llvm::WebAssembly::Tag::CPP_EXCEPTION - // in llvm-project/llvm/include/llvm/CodeGen/WasmEHFuncInfo.h - const CPP_EXCEPTION_TAG: i32 = 0; - wasm_throw(CPP_EXCEPTION_TAG, exception.cast()) + // LLVM currently only runs cleanup code for exception using the C++ exception tag and + // not those for any other exception tag like the longjmp exception tag. 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. + // This is using inline asm rather than the llvm.wasm.throw llvm intrinsic as supporting + // unwinding for llvm intrinsics complicates things in the backend. + core::arch::asm!(" + .tagtype __cpp_exception i32 + .globl __cpp_exception + .weak __cpp_exception + local.get {exc} + throw __cpp_exception + ", exc = in(local) exception, options(may_unwind, noreturn, nostack)); } _ => { let _ = exception; diff --git a/tests/codegen-llvm/wasm_exceptions.rs b/tests/codegen-llvm/wasm_exceptions.rs index 7f38b669a6f8e..4f807e8cacd67 100644 --- a/tests/codegen-llvm/wasm_exceptions.rs +++ b/tests/codegen-llvm/wasm_exceptions.rs @@ -3,7 +3,7 @@ //@ [WASMEXN] compile-flags: -C panic=unwind -Z emscripten-wasm-eh #![crate_type = "lib"] -#![feature(core_intrinsics, link_llvm_intrinsics)] +#![feature(asm_experimental_arch, asm_unwind, core_intrinsics)] extern "C-unwind" { fn may_panic(); @@ -82,15 +82,15 @@ pub fn test_rtry() { pub fn test_intrinsic() { let _log_on_drop = LogOnDrop; - unsafe extern "C-unwind" { - #[link_name = "llvm.wasm.throw"] - fn wasm_throw(tag: i32, ptr: *mut u8) -> !; - } unsafe { - wasm_throw(0, core::ptr::null_mut()); + std::arch::asm!(" + .tagtype __cpp_exception i32 + local.get {exc} + throw __cpp_exception + ", exc = in(local) core::ptr::null_mut::<()>(), options(may_unwind, noreturn, nostack)); } // WASMEXN-NOT: call - // WASMEXN: invoke void @llvm.wasm.throw(i32 noundef 0, ptr noundef null) + // WASMEXN: invoke void asm sideeffect unwind // WASMEXN: %cleanuppad = cleanuppad within none [] }