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

Support for native WASM exceptions #111322

Merged
merged 6 commits into from
Jun 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions compiler/rustc_codegen_llvm/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::llvm_util;
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;
Expand Down Expand Up @@ -532,19 +532,28 @@ impl<'ll, 'tcx> MiscMethods<'tcx> for CodegenCx<'ll, 'tcx> {
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")
bjorn3 marked this conversation as resolved.
Show resolved Hide resolved
} 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 {
Expand Down Expand Up @@ -662,6 +671,10 @@ impl<'ll> CodegenCx<'ll, '_> {
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);
Expand Down
78 changes: 77 additions & 1 deletion compiler/rustc_codegen_llvm/src/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::type_of::LayoutLlvmExt;
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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()]);
mirkootter marked this conversation as resolved.
Show resolved Hide resolved

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).
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_codegen_llvm/src/llvm/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,7 @@ extern "C" {

// 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
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_codegen_llvm/src/type_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ impl<'ll> CodegenCx<'ll, '_> {
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) }
}
Expand Down
14 changes: 14 additions & 0 deletions compiler/rustc_codegen_ssa/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down
26 changes: 21 additions & 5 deletions compiler/rustc_codegen_ssa/src/mir/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
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);
Expand Down Expand Up @@ -177,9 +177,16 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
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())
Expand Down Expand Up @@ -1528,7 +1535,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
// 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, &[]);
Expand Down Expand Up @@ -1587,6 +1594,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
// } 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");

Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_codegen_ssa/src/mir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,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::BasicBlock, CachedLlbb<Bx::BasicBlock>> =
mir.basic_blocks
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_codegen_ssa/src/target_features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ const WASM_ALLOWED_FEATURES: &[(&str, Option<Symbol>)] = &[
// 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)),
Expand Down