Skip to content

Commit c139641

Browse files
Rollup merge of rust-lang#111322 - mirkootter:master, r=davidtwco
Support for native WASM exceptions ### Motivation Currently, rustc does not support native WASM exceptions. It does support JavaScript based exceptions for the wasm32-emscripten-target, but this requires back&forth with javascript for many calls, which is very slow. Native wasm support for exceptions is quite common: Clang+LLVM implemented them years ago, and all major browsers support them by now. They enable zero-cost exceptions, at least with regard to runtime-performance-cost. They may increase startup-time and code size, though. ### Important: This PR does not change default behaviour Exceptions usually add a lot of code in form of unwinding blocks, increasing the binary size. Most users probably do not want that, especially which regard to web development. Therefore, wasm exceptions play a similar role as WASM-threads: rustc should support them, like clang does, but users who want to use it have to use some command-line magic like rustflags to opt in. ### What does this PR do? As stated above, the default behaviour is not changed. It is already possible to opt-in into wasm exceptions using the command line. Unfortunately, the LLVM IR is invalid and the LLVM backend crashes. ``` rustc <sourcefile> --target wasm32-unknown-unknown -C panic=unwind -C llvm-args=-wasm-enable-eh -C target-feature=+exception-handling ``` As it turns out, LLVM is quite picky when it comes to IR for exception handling. If the IR does not look exactly like it should, some LLVM-assertions fail and the code generation crashes. This PR adds the necessary modifications to the code generator to make it work. It also adds `exception-handling` as a wasm target feature. ### What this PR does not / what is missing This PR is not a full fledges solution. It is the first step. A few parts are still missing; however, it is already useable (see next section). Currently missing: * The std library has to be adapted. Currently, only [no_std] crates work * Usually, nested exceptions abort the program (i.e. a panic during the cleanup of another panic). This is currently not done yet. - Currently, code inside cleanup handlers does not unwind - To fix this requires a little more work: The code generator currently maintains a single terminate block per function for this. Unfortunately, WASM requires funclet based exception handling. Therefore, we need to create a terminate block per funclet. This is probably not a big problem, but I want to keep this PR simple. ### How to use the compiler given this PR? This PR does not add any command line flags or features. It uses those which are already there. To compile with exceptions enabled, you need * to set the panic strategy to unwind, i.e. `-C panic=unwind` * to enable the exception-handling target feature, i.e. `-C target-feature=+exception-handling` * to tell LLVM about the exception handling, i.e. `-C llvm-args=-wasm-enable-eh` Since the standard library has not been adapted, you can only use it in [no_std] crates as of now. The intrinsic `core::intrinsics::r#try` works. To throw exceptions, you need the ````@llvm.wasm.throw```` intrinsic. I created a sample application which works for me: https://github.com/mirkootter/rust-wasm-demos This example can be run at https://webassembly.sh
2 parents 3307274 + 744ec64 commit c139641

File tree

8 files changed

+140
-14
lines changed

8 files changed

+140
-14
lines changed

compiler/rustc_codegen_llvm/src/context.rs

+20-7
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::llvm_util;
88
use crate::type_::Type;
99
use crate::value::Value;
1010

11-
use rustc_codegen_ssa::base::wants_msvc_seh;
11+
use rustc_codegen_ssa::base::{wants_msvc_seh, wants_wasm_eh};
1212
use rustc_codegen_ssa::traits::*;
1313
use rustc_data_structures::base_n;
1414
use rustc_data_structures::fx::FxHashMap;
@@ -532,19 +532,28 @@ impl<'ll, 'tcx> MiscMethods<'tcx> for CodegenCx<'ll, 'tcx> {
532532
if let Some(llpersonality) = self.eh_personality.get() {
533533
return llpersonality;
534534
}
535+
536+
let name = if wants_msvc_seh(self.sess()) {
537+
Some("__CxxFrameHandler3")
538+
} else if wants_wasm_eh(self.sess()) {
539+
// LLVM specifically tests for the name of the personality function
540+
// There is no need for this function to exist anywhere, it will
541+
// not be called. However, its name has to be "__gxx_wasm_personality_v0"
542+
// for native wasm exceptions.
543+
Some("__gxx_wasm_personality_v0")
544+
} else {
545+
None
546+
};
547+
535548
let tcx = self.tcx;
536549
let llfn = match tcx.lang_items().eh_personality() {
537-
Some(def_id) if !wants_msvc_seh(self.sess()) => self.get_fn_addr(
550+
Some(def_id) if name.is_none() => self.get_fn_addr(
538551
ty::Instance::resolve(tcx, ty::ParamEnv::reveal_all(), def_id, ty::List::empty())
539552
.unwrap()
540553
.unwrap(),
541554
),
542555
_ => {
543-
let name = if wants_msvc_seh(self.sess()) {
544-
"__CxxFrameHandler3"
545-
} else {
546-
"rust_eh_personality"
547-
};
556+
let name = name.unwrap_or("rust_eh_personality");
548557
if let Some(llfn) = self.get_declared_value(name) {
549558
llfn
550559
} else {
@@ -662,6 +671,10 @@ impl<'ll> CodegenCx<'ll, '_> {
662671
let t_f32 = self.type_f32();
663672
let t_f64 = self.type_f64();
664673
let t_metadata = self.type_metadata();
674+
let t_token = self.type_token();
675+
676+
ifn!("llvm.wasm.get.exception", fn(t_token) -> i8p);
677+
ifn!("llvm.wasm.get.ehselector", fn(t_token) -> t_i32);
665678

666679
ifn!("llvm.wasm.trunc.unsigned.i32.f32", fn(t_f32) -> t_i32);
667680
ifn!("llvm.wasm.trunc.unsigned.i32.f64", fn(t_f64) -> t_i32);

compiler/rustc_codegen_llvm/src/intrinsic.rs

+77-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::type_of::LayoutLlvmExt;
77
use crate::va_arg::emit_va_arg;
88
use crate::value::Value;
99

10-
use rustc_codegen_ssa::base::{compare_simd_types, wants_msvc_seh};
10+
use rustc_codegen_ssa::base::{compare_simd_types, wants_msvc_seh, wants_wasm_eh};
1111
use rustc_codegen_ssa::common::{IntPredicate, TypeKind};
1212
use rustc_codegen_ssa::errors::{ExpectedPointerMutability, InvalidMonomorphization};
1313
use rustc_codegen_ssa::mir::operand::OperandRef;
@@ -452,6 +452,8 @@ fn try_intrinsic<'ll>(
452452
bx.store(bx.const_i32(0), dest, ret_align);
453453
} else if wants_msvc_seh(bx.sess()) {
454454
codegen_msvc_try(bx, try_func, data, catch_func, dest);
455+
} else if wants_wasm_eh(bx.sess()) {
456+
codegen_wasm_try(bx, try_func, data, catch_func, dest);
455457
} else if bx.sess().target.os == "emscripten" {
456458
codegen_emcc_try(bx, try_func, data, catch_func, dest);
457459
} else {
@@ -610,6 +612,80 @@ fn codegen_msvc_try<'ll>(
610612
bx.store(ret, dest, i32_align);
611613
}
612614

615+
// WASM's definition of the `rust_try` function.
616+
fn codegen_wasm_try<'ll>(
617+
bx: &mut Builder<'_, 'll, '_>,
618+
try_func: &'ll Value,
619+
data: &'ll Value,
620+
catch_func: &'ll Value,
621+
dest: &'ll Value,
622+
) {
623+
let (llty, llfn) = get_rust_try_fn(bx, &mut |mut bx| {
624+
bx.set_personality_fn(bx.eh_personality());
625+
626+
let normal = bx.append_sibling_block("normal");
627+
let catchswitch = bx.append_sibling_block("catchswitch");
628+
let catchpad = bx.append_sibling_block("catchpad");
629+
let caught = bx.append_sibling_block("caught");
630+
631+
let try_func = llvm::get_param(bx.llfn(), 0);
632+
let data = llvm::get_param(bx.llfn(), 1);
633+
let catch_func = llvm::get_param(bx.llfn(), 2);
634+
635+
// We're generating an IR snippet that looks like:
636+
//
637+
// declare i32 @rust_try(%try_func, %data, %catch_func) {
638+
// %slot = alloca i8*
639+
// invoke %try_func(%data) to label %normal unwind label %catchswitch
640+
//
641+
// normal:
642+
// ret i32 0
643+
//
644+
// catchswitch:
645+
// %cs = catchswitch within none [%catchpad] unwind to caller
646+
//
647+
// catchpad:
648+
// %tok = catchpad within %cs [null]
649+
// %ptr = call @llvm.wasm.get.exception(token %tok)
650+
// %sel = call @llvm.wasm.get.ehselector(token %tok)
651+
// call %catch_func(%data, %ptr)
652+
// catchret from %tok to label %caught
653+
//
654+
// caught:
655+
// ret i32 1
656+
// }
657+
//
658+
let try_func_ty = bx.type_func(&[bx.type_i8p()], bx.type_void());
659+
bx.invoke(try_func_ty, None, None, try_func, &[data], normal, catchswitch, None);
660+
661+
bx.switch_to_block(normal);
662+
bx.ret(bx.const_i32(0));
663+
664+
bx.switch_to_block(catchswitch);
665+
let cs = bx.catch_switch(None, None, &[catchpad]);
666+
667+
bx.switch_to_block(catchpad);
668+
let null = bx.const_null(bx.type_i8p());
669+
let funclet = bx.catch_pad(cs, &[null]);
670+
671+
let ptr = bx.call_intrinsic("llvm.wasm.get.exception", &[funclet.cleanuppad()]);
672+
let _sel = bx.call_intrinsic("llvm.wasm.get.ehselector", &[funclet.cleanuppad()]);
673+
674+
let catch_ty = bx.type_func(&[bx.type_i8p(), bx.type_i8p()], bx.type_void());
675+
bx.call(catch_ty, None, None, catch_func, &[data, ptr], Some(&funclet));
676+
bx.catch_ret(&funclet, caught);
677+
678+
bx.switch_to_block(caught);
679+
bx.ret(bx.const_i32(1));
680+
});
681+
682+
// Note that no invoke is used here because by definition this function
683+
// can't panic (that's what it's catching).
684+
let ret = bx.call(llty, None, None, llfn, &[try_func, data, catch_func], None);
685+
let i32_align = bx.tcx().data_layout.i32_align.abi;
686+
bx.store(ret, dest, i32_align);
687+
}
688+
613689
// Definition of the standard `try` function for Rust using the GNU-like model
614690
// of exceptions (e.g., the normal semantics of LLVM's `landingpad` and `invoke`
615691
// instructions).

compiler/rustc_codegen_llvm/src/llvm/ffi.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1071,6 +1071,7 @@ extern "C" {
10711071

10721072
// Operations on other types
10731073
pub fn LLVMVoidTypeInContext(C: &Context) -> &Type;
1074+
pub fn LLVMTokenTypeInContext(C: &Context) -> &Type;
10741075
pub fn LLVMMetadataTypeInContext(C: &Context) -> &Type;
10751076

10761077
// Operations on all values

compiler/rustc_codegen_llvm/src/type_.rs

+4
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ impl<'ll> CodegenCx<'ll, '_> {
5252
unsafe { llvm::LLVMVoidTypeInContext(self.llcx) }
5353
}
5454

55+
pub(crate) fn type_token(&self) -> &'ll Type {
56+
unsafe { llvm::LLVMTokenTypeInContext(self.llcx) }
57+
}
58+
5559
pub(crate) fn type_metadata(&self) -> &'ll Type {
5660
unsafe { llvm::LLVMMetadataTypeInContext(self.llcx) }
5761
}

compiler/rustc_codegen_ssa/src/base.rs

+14
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,13 @@ pub fn cast_shift_expr_rhs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
357357
}
358358
}
359359

360+
// Returns `true` if this session's target will use native wasm
361+
// exceptions. This means that the VM does the unwinding for
362+
// us
363+
pub fn wants_wasm_eh(sess: &Session) -> bool {
364+
sess.target.is_like_wasm && sess.target.os != "emscripten"
365+
}
366+
360367
/// Returns `true` if this session's target will use SEH-based unwinding.
361368
///
362369
/// 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 {
366373
sess.target.is_like_msvc
367374
}
368375

376+
/// Returns `true` if this session's target requires the new exception
377+
/// handling LLVM IR instructions (catchpad / cleanuppad / ... instead
378+
/// of landingpad)
379+
pub fn wants_new_eh_instructions(sess: &Session) -> bool {
380+
wants_wasm_eh(sess) || wants_msvc_seh(sess)
381+
}
382+
369383
pub fn memcpy_ty<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
370384
bx: &mut Bx,
371385
dst: Bx::Value,

compiler/rustc_codegen_ssa/src/mir/block.rs

+21-5
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
7979
lltarget = fx.landing_pad_for(target);
8080
}
8181
if is_cleanupret {
82-
// MSVC cross-funclet jump - need a trampoline
83-
debug_assert!(base::wants_msvc_seh(fx.cx.tcx().sess));
82+
// Cross-funclet jump - need a trampoline
83+
debug_assert!(base::wants_new_eh_instructions(fx.cx.tcx().sess));
8484
debug!("llbb_with_cleanup: creating cleanup trampoline for {:?}", target);
8585
let name = &format!("{:?}_cleanup_trampoline_{:?}", self.bb, target);
8686
let trampoline_llbb = Bx::append_block(fx.cx, fx.llfn, name);
@@ -177,9 +177,16 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
177177
mir::UnwindAction::Continue => None,
178178
mir::UnwindAction::Unreachable => None,
179179
mir::UnwindAction::Terminate => {
180-
if fx.mir[self.bb].is_cleanup && base::wants_msvc_seh(fx.cx.tcx().sess) {
181-
// SEH will abort automatically if an exception tries to
180+
if fx.mir[self.bb].is_cleanup && base::wants_new_eh_instructions(fx.cx.tcx().sess) {
181+
// MSVC SEH will abort automatically if an exception tries to
182182
// propagate out from cleanup.
183+
184+
// FIXME(@mirkootter): For wasm, we currently do not support terminate during
185+
// cleanup, because this requires a few more changes: The current code
186+
// caches the `terminate_block` for each function; funclet based code - however -
187+
// requires a different terminate_block for each funclet
188+
// Until this is implemented, we just do not unwind inside cleanup blocks
189+
183190
None
184191
} else {
185192
Some(fx.terminate_block())
@@ -1528,7 +1535,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
15281535
// FIXME(eddyb) rename this to `eh_pad_for_uncached`.
15291536
fn landing_pad_for_uncached(&mut self, bb: mir::BasicBlock) -> Bx::BasicBlock {
15301537
let llbb = self.llbb(bb);
1531-
if base::wants_msvc_seh(self.cx.sess()) {
1538+
if base::wants_new_eh_instructions(self.cx.sess()) {
15321539
let cleanup_bb = Bx::append_block(self.cx, self.llfn, &format!("funclet_{:?}", bb));
15331540
let mut cleanup_bx = Bx::build(self.cx, cleanup_bb);
15341541
let funclet = cleanup_bx.cleanup_pad(None, &[]);
@@ -1587,6 +1594,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
15871594
// } catch (...) {
15881595
// bar();
15891596
// }
1597+
//
1598+
// which creates an IR snippet like
1599+
//
1600+
// cs_terminate:
1601+
// %cs = catchswitch within none [%cp_terminate] unwind to caller
1602+
// cp_terminate:
1603+
// %cp = catchpad within %cs [null, i32 64, null]
1604+
// ...
1605+
15901606
llbb = Bx::append_block(self.cx, self.llfn, "cs_terminate");
15911607
let cp_llbb = Bx::append_block(self.cx, self.llfn, "cp_terminate");
15921608

compiler/rustc_codegen_ssa/src/mir/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
179179
start_bx.set_personality_fn(cx.eh_personality());
180180
}
181181

182-
let cleanup_kinds = base::wants_msvc_seh(cx.tcx().sess).then(|| analyze::cleanup_kinds(&mir));
182+
let cleanup_kinds =
183+
base::wants_new_eh_instructions(cx.tcx().sess).then(|| analyze::cleanup_kinds(&mir));
183184

184185
let cached_llbbs: IndexVec<mir::BasicBlock, CachedLlbb<Bx::BasicBlock>> =
185186
mir.basic_blocks

compiler/rustc_codegen_ssa/src/target_features.rs

+1
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ const WASM_ALLOWED_FEATURES: &[(&str, Option<Symbol>)] = &[
284284
// tidy-alphabetical-start
285285
("atomics", Some(sym::wasm_target_feature)),
286286
("bulk-memory", Some(sym::wasm_target_feature)),
287+
("exception-handling", Some(sym::wasm_target_feature)),
287288
("multivalue", Some(sym::wasm_target_feature)),
288289
("mutable-globals", Some(sym::wasm_target_feature)),
289290
("nontrapping-fptoint", Some(sym::wasm_target_feature)),

0 commit comments

Comments
 (0)