diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index e8a7afcc632..287a22bc9a6 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -8,7 +8,7 @@ use crate::type_::Type; use crate::value::Value; -use rustc_codegen_ssa::base::wants_msvc_seh; +use rustc_codegen_ssa::base::{wants_msvc_seh, wants_wasm_eh}; use rustc_codegen_ssa::traits::*; use rustc_data_structures::base_n; use rustc_data_structures::fx::FxHashMap; @@ -532,19 +532,28 @@ fn eh_personality(&self) -> &'ll Value { if let Some(llpersonality) = self.eh_personality.get() { return llpersonality; } + + let name = if wants_msvc_seh(self.sess()) { + Some("__CxxFrameHandler3") + } else if wants_wasm_eh(self.sess()) { + // LLVM specifically tests for the name of the personality function + // There is no need for this function to exist anywhere, it will + // not be called. However, its name has to be "__gxx_wasm_personality_v0" + // for native wasm exceptions. + Some("__gxx_wasm_personality_v0") + } else { + None + }; + let tcx = self.tcx; let llfn = match tcx.lang_items().eh_personality() { - Some(def_id) if !wants_msvc_seh(self.sess()) => self.get_fn_addr( + Some(def_id) if name.is_none() => self.get_fn_addr( ty::Instance::resolve(tcx, ty::ParamEnv::reveal_all(), def_id, ty::List::empty()) .unwrap() .unwrap(), ), _ => { - let name = if wants_msvc_seh(self.sess()) { - "__CxxFrameHandler3" - } else { - "rust_eh_personality" - }; + let name = name.unwrap_or("rust_eh_personality"); if let Some(llfn) = self.get_declared_value(name) { llfn } else { @@ -662,6 +671,10 @@ macro_rules! mk_struct { let t_f32 = self.type_f32(); let t_f64 = self.type_f64(); let t_metadata = self.type_metadata(); + let t_token = self.type_token(); + + ifn!("llvm.wasm.get.exception", fn(t_token) -> i8p); + ifn!("llvm.wasm.get.ehselector", fn(t_token) -> t_i32); ifn!("llvm.wasm.trunc.unsigned.i32.f32", fn(t_f32) -> t_i32); ifn!("llvm.wasm.trunc.unsigned.i32.f64", fn(t_f64) -> t_i32); diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 4e28034a850..31bafa87814 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -7,7 +7,7 @@ use crate::va_arg::emit_va_arg; use crate::value::Value; -use rustc_codegen_ssa::base::{compare_simd_types, wants_msvc_seh}; +use rustc_codegen_ssa::base::{compare_simd_types, wants_msvc_seh, wants_wasm_eh}; use rustc_codegen_ssa::common::{IntPredicate, TypeKind}; use rustc_codegen_ssa::errors::{ExpectedPointerMutability, InvalidMonomorphization}; use rustc_codegen_ssa::mir::operand::OperandRef; @@ -452,6 +452,8 @@ fn try_intrinsic<'ll>( bx.store(bx.const_i32(0), dest, ret_align); } else if wants_msvc_seh(bx.sess()) { codegen_msvc_try(bx, try_func, data, catch_func, dest); + } else if wants_wasm_eh(bx.sess()) { + codegen_wasm_try(bx, try_func, data, catch_func, dest); } else if bx.sess().target.os == "emscripten" { codegen_emcc_try(bx, try_func, data, catch_func, dest); } else { @@ -610,6 +612,80 @@ fn codegen_msvc_try<'ll>( bx.store(ret, dest, i32_align); } +// WASM's definition of the `rust_try` function. +fn codegen_wasm_try<'ll>( + bx: &mut Builder<'_, 'll, '_>, + try_func: &'ll Value, + data: &'ll Value, + catch_func: &'ll Value, + dest: &'ll Value, +) { + let (llty, llfn) = get_rust_try_fn(bx, &mut |mut bx| { + bx.set_personality_fn(bx.eh_personality()); + + let normal = bx.append_sibling_block("normal"); + let catchswitch = bx.append_sibling_block("catchswitch"); + let catchpad = bx.append_sibling_block("catchpad"); + let caught = bx.append_sibling_block("caught"); + + let try_func = llvm::get_param(bx.llfn(), 0); + let data = llvm::get_param(bx.llfn(), 1); + let catch_func = llvm::get_param(bx.llfn(), 2); + + // We're generating an IR snippet that looks like: + // + // declare i32 @rust_try(%try_func, %data, %catch_func) { + // %slot = alloca i8* + // invoke %try_func(%data) to label %normal unwind label %catchswitch + // + // normal: + // ret i32 0 + // + // catchswitch: + // %cs = catchswitch within none [%catchpad] unwind to caller + // + // catchpad: + // %tok = catchpad within %cs [null] + // %ptr = call @llvm.wasm.get.exception(token %tok) + // %sel = call @llvm.wasm.get.ehselector(token %tok) + // call %catch_func(%data, %ptr) + // catchret from %tok to label %caught + // + // caught: + // ret i32 1 + // } + // + let try_func_ty = bx.type_func(&[bx.type_i8p()], bx.type_void()); + bx.invoke(try_func_ty, None, None, try_func, &[data], normal, catchswitch, None); + + bx.switch_to_block(normal); + bx.ret(bx.const_i32(0)); + + bx.switch_to_block(catchswitch); + let cs = bx.catch_switch(None, None, &[catchpad]); + + bx.switch_to_block(catchpad); + let null = bx.const_null(bx.type_i8p()); + let funclet = bx.catch_pad(cs, &[null]); + + let ptr = bx.call_intrinsic("llvm.wasm.get.exception", &[funclet.cleanuppad()]); + let _sel = bx.call_intrinsic("llvm.wasm.get.ehselector", &[funclet.cleanuppad()]); + + let catch_ty = bx.type_func(&[bx.type_i8p(), bx.type_i8p()], bx.type_void()); + bx.call(catch_ty, None, None, catch_func, &[data, ptr], Some(&funclet)); + bx.catch_ret(&funclet, caught); + + bx.switch_to_block(caught); + bx.ret(bx.const_i32(1)); + }); + + // Note that no invoke is used here because by definition this function + // can't panic (that's what it's catching). + let ret = bx.call(llty, None, None, llfn, &[try_func, data, catch_func], None); + let i32_align = bx.tcx().data_layout.i32_align.abi; + bx.store(ret, dest, i32_align); +} + // Definition of the standard `try` function for Rust using the GNU-like model // of exceptions (e.g., the normal semantics of LLVM's `landingpad` and `invoke` // instructions). diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 6ef3418cc5f..5c1657268b7 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -1071,6 +1071,7 @@ pub fn LLVMStructTypeInContext<'a>( // Operations on other types pub fn LLVMVoidTypeInContext(C: &Context) -> &Type; + pub fn LLVMTokenTypeInContext(C: &Context) -> &Type; pub fn LLVMMetadataTypeInContext(C: &Context) -> &Type; // Operations on all values diff --git a/compiler/rustc_codegen_llvm/src/type_.rs b/compiler/rustc_codegen_llvm/src/type_.rs index 4ffa2b9c6a3..7e672a8dc33 100644 --- a/compiler/rustc_codegen_llvm/src/type_.rs +++ b/compiler/rustc_codegen_llvm/src/type_.rs @@ -52,6 +52,10 @@ pub(crate) fn type_void(&self) -> &'ll Type { unsafe { llvm::LLVMVoidTypeInContext(self.llcx) } } + pub(crate) fn type_token(&self) -> &'ll Type { + unsafe { llvm::LLVMTokenTypeInContext(self.llcx) } + } + pub(crate) fn type_metadata(&self) -> &'ll Type { unsafe { llvm::LLVMMetadataTypeInContext(self.llcx) } } diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 28f3c23364c..f8ced6949d5 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -357,6 +357,13 @@ pub fn cast_shift_expr_rhs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( } } +// Returns `true` if this session's target will use native wasm +// exceptions. This means that the VM does the unwinding for +// us +pub fn wants_wasm_eh(sess: &Session) -> bool { + sess.target.is_like_wasm && sess.target.os != "emscripten" +} + /// Returns `true` if this session's target will use SEH-based unwinding. /// /// This is only true for MSVC targets, and even then the 64-bit MSVC target @@ -366,6 +373,13 @@ pub fn wants_msvc_seh(sess: &Session) -> bool { sess.target.is_like_msvc } +/// Returns `true` if this session's target requires the new exception +/// handling LLVM IR instructions (catchpad / cleanuppad / ... instead +/// of landingpad) +pub fn wants_new_eh_instructions(sess: &Session) -> bool { + wants_wasm_eh(sess) || wants_msvc_seh(sess) +} + pub fn memcpy_ty<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( bx: &mut Bx, dst: Bx::Value, diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index 5e40b672866..ac566c49ed4 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -79,8 +79,8 @@ fn llbb_with_cleanup>( lltarget = fx.landing_pad_for(target); } if is_cleanupret { - // MSVC cross-funclet jump - need a trampoline - debug_assert!(base::wants_msvc_seh(fx.cx.tcx().sess)); + // Cross-funclet jump - need a trampoline + debug_assert!(base::wants_new_eh_instructions(fx.cx.tcx().sess)); debug!("llbb_with_cleanup: creating cleanup trampoline for {:?}", target); let name = &format!("{:?}_cleanup_trampoline_{:?}", self.bb, target); let trampoline_llbb = Bx::append_block(fx.cx, fx.llfn, name); @@ -177,9 +177,16 @@ fn do_call>( mir::UnwindAction::Continue => None, mir::UnwindAction::Unreachable => None, mir::UnwindAction::Terminate => { - if fx.mir[self.bb].is_cleanup && base::wants_msvc_seh(fx.cx.tcx().sess) { - // SEH will abort automatically if an exception tries to + if fx.mir[self.bb].is_cleanup && base::wants_new_eh_instructions(fx.cx.tcx().sess) { + // MSVC SEH will abort automatically if an exception tries to // propagate out from cleanup. + + // FIXME(@mirkootter): For wasm, we currently do not support terminate during + // cleanup, because this requires a few more changes: The current code + // caches the `terminate_block` for each function; funclet based code - however - + // requires a different terminate_block for each funclet + // Until this is implemented, we just do not unwind inside cleanup blocks + None } else { Some(fx.terminate_block()) @@ -1528,7 +1535,7 @@ fn landing_pad_for(&mut self, bb: mir::BasicBlock) -> Bx::BasicBlock { // FIXME(eddyb) rename this to `eh_pad_for_uncached`. fn landing_pad_for_uncached(&mut self, bb: mir::BasicBlock) -> Bx::BasicBlock { let llbb = self.llbb(bb); - if base::wants_msvc_seh(self.cx.sess()) { + if base::wants_new_eh_instructions(self.cx.sess()) { let cleanup_bb = Bx::append_block(self.cx, self.llfn, &format!("funclet_{:?}", bb)); let mut cleanup_bx = Bx::build(self.cx, cleanup_bb); let funclet = cleanup_bx.cleanup_pad(None, &[]); @@ -1587,6 +1594,15 @@ fn terminate_block(&mut self) -> Bx::BasicBlock { // } catch (...) { // bar(); // } + // + // which creates an IR snippet like + // + // cs_terminate: + // %cs = catchswitch within none [%cp_terminate] unwind to caller + // cp_terminate: + // %cp = catchpad within %cs [null, i32 64, null] + // ... + llbb = Bx::append_block(self.cx, self.llfn, "cs_terminate"); let cp_llbb = Bx::append_block(self.cx, self.llfn, "cp_terminate"); diff --git a/compiler/rustc_codegen_ssa/src/mir/mod.rs b/compiler/rustc_codegen_ssa/src/mir/mod.rs index 15b0e34b8e4..9ff6a249748 100644 --- a/compiler/rustc_codegen_ssa/src/mir/mod.rs +++ b/compiler/rustc_codegen_ssa/src/mir/mod.rs @@ -179,7 +179,8 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( start_bx.set_personality_fn(cx.eh_personality()); } - let cleanup_kinds = base::wants_msvc_seh(cx.tcx().sess).then(|| analyze::cleanup_kinds(&mir)); + let cleanup_kinds = + base::wants_new_eh_instructions(cx.tcx().sess).then(|| analyze::cleanup_kinds(&mir)); let cached_llbbs: IndexVec> = mir.basic_blocks diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs index 6a3a31a0d60..9e06fec550d 100644 --- a/compiler/rustc_codegen_ssa/src/target_features.rs +++ b/compiler/rustc_codegen_ssa/src/target_features.rs @@ -284,6 +284,7 @@ // tidy-alphabetical-start ("atomics", Some(sym::wasm_target_feature)), ("bulk-memory", Some(sym::wasm_target_feature)), + ("exception-handling", Some(sym::wasm_target_feature)), ("multivalue", Some(sym::wasm_target_feature)), ("mutable-globals", Some(sym::wasm_target_feature)), ("nontrapping-fptoint", Some(sym::wasm_target_feature)),