diff --git a/src/libcore/intrinsics.rs b/src/libcore/intrinsics.rs index fca2c3d31d946..20fb4149b9359 100644 --- a/src/libcore/intrinsics.rs +++ b/src/libcore/intrinsics.rs @@ -1390,14 +1390,16 @@ extern "rust-intrinsic" { /// cast to a `u64`; if `T` has no discriminant, returns 0. pub fn discriminant_value(v: &T) -> u64; - /// Rust's "try catch" construct which invokes the function pointer `f` with - /// the data pointer `data`. + /// Rust's "try catch" construct which invokes the function pointer `try_fn` + /// with the data pointer `data`. /// - /// The third pointer is a target-specific data pointer which is filled in - /// with the specifics of the exception that occurred. For examples on Unix - /// platforms this is a `*mut *mut T` which is filled in by the compiler and - /// on MSVC it's `*mut [usize; 2]`. For more information see the compiler's + /// The third argument is a function called if a panic occurs. This function + /// takes the data pointer and a pointer to the target-specific exception + /// object that was caught. For more information see the compiler's /// source as well as std's catch implementation. + #[cfg(not(bootstrap))] + pub fn r#try(try_fn: fn(*mut u8), data: *mut u8, catch_fn: fn(*mut u8, *mut u8)) -> i32; + #[cfg(bootstrap)] pub fn r#try(f: fn(*mut u8), data: *mut u8, local_ptr: *mut u8) -> i32; /// Emits a `!nontemporal` store according to LLVM (see their docs). diff --git a/src/libpanic_abort/lib.rs b/src/libpanic_abort/lib.rs index 1af93ff4ada57..f44a875c9d0d5 100644 --- a/src/libpanic_abort/lib.rs +++ b/src/libpanic_abort/lib.rs @@ -20,11 +20,8 @@ use core::any::Any; -// We need the definition of TryPayload for __rust_panic_cleanup. -include!("../libpanic_unwind/payload.rs"); - #[rustc_std_internal_symbol] -pub unsafe extern "C" fn __rust_panic_cleanup(_: TryPayload) -> *mut (dyn Any + Send + 'static) { +pub unsafe extern "C" fn __rust_panic_cleanup(_: *mut u8) -> *mut (dyn Any + Send + 'static) { unreachable!() } diff --git a/src/libpanic_unwind/emcc.rs b/src/libpanic_unwind/emcc.rs index 117246aa6c95e..c7144fe16cdda 100644 --- a/src/libpanic_unwind/emcc.rs +++ b/src/libpanic_unwind/emcc.rs @@ -52,7 +52,7 @@ struct Exception { // This needs to be an Option because the object's lifetime follows C++ // semantics: when catch_unwind moves the Box out of the exception it must // still leave the exception object in a valid state because its destructor - // is still going to be called by __cxa_end_catch.. + // is still going to be called by __cxa_end_catch. data: Option>, } diff --git a/src/libpanic_unwind/lib.rs b/src/libpanic_unwind/lib.rs index 20331e8808cad..d6c3366693818 100644 --- a/src/libpanic_unwind/lib.rs +++ b/src/libpanic_unwind/lib.rs @@ -35,8 +35,6 @@ use alloc::boxed::Box; use core::any::Any; use core::panic::BoxMeUp; -// If adding to this list, you should also look at the list of TryPayload types -// defined in payload.rs and likely add to there as well. cfg_if::cfg_if! { if #[cfg(target_os = "emscripten")] { #[path = "emcc.rs"] @@ -62,8 +60,6 @@ cfg_if::cfg_if! { } } -include!("payload.rs"); - extern "C" { /// Handler in libstd called when a panic object is dropped outside of /// `catch_unwind`. @@ -73,9 +69,7 @@ extern "C" { mod dwarf; #[rustc_std_internal_symbol] -pub unsafe extern "C" fn __rust_panic_cleanup( - payload: TryPayload, -) -> *mut (dyn Any + Send + 'static) { +pub unsafe extern "C" fn __rust_panic_cleanup(payload: *mut u8) -> *mut (dyn Any + Send + 'static) { Box::into_raw(imp::cleanup(payload)) } diff --git a/src/libpanic_unwind/payload.rs b/src/libpanic_unwind/payload.rs deleted file mode 100644 index 1234db7da0f08..0000000000000 --- a/src/libpanic_unwind/payload.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Type definition for the payload argument of the try intrinsic. -// -// This must be kept in sync with the implementations of the try intrinsic. -// -// This file is included by both panic runtimes and libstd. It is part of the -// panic runtime ABI. -cfg_if::cfg_if! { - if #[cfg(target_os = "emscripten")] { - type TryPayload = *mut u8; - } else if #[cfg(target_arch = "wasm32")] { - type TryPayload = *mut u8; - } else if #[cfg(target_os = "hermit")] { - type TryPayload = *mut u8; - } else if #[cfg(all(target_env = "msvc", target_arch = "aarch64"))] { - type TryPayload = *mut u8; - } else if #[cfg(target_env = "msvc")] { - type TryPayload = [u64; 2]; - } else { - type TryPayload = *mut u8; - } -} diff --git a/src/libpanic_unwind/seh.rs b/src/libpanic_unwind/seh.rs index f599f9815a62c..c1656023b60e6 100644 --- a/src/libpanic_unwind/seh.rs +++ b/src/libpanic_unwind/seh.rs @@ -49,10 +49,17 @@ use alloc::boxed::Box; use core::any::Any; -use core::mem; -use core::raw; +use core::mem::{self, ManuallyDrop}; use libc::{c_int, c_uint, c_void}; +struct Exception { + // This needs to be an Option because we catch the exception by reference + // and its destructor is executed by the C++ runtime. When we take the Box + // out of the exception, we need to leave the exception in a valid state + // for its destructor to run without double-dropping the Box. + data: Option>, +} + // First up, a whole bunch of type definitions. There's a few platform-specific // oddities here, and a lot that's just blatantly copied from LLVM. The purpose // of all this is to implement the `panic` function below through a call to @@ -186,7 +193,7 @@ static mut CATCHABLE_TYPE: _CatchableType = _CatchableType { properties: 0, pType: ptr!(0), thisDisplacement: _PMD { mdisp: 0, pdisp: -1, vdisp: 0 }, - sizeOrOffset: mem::size_of::<[u64; 2]>() as c_int, + sizeOrOffset: mem::size_of::() as c_int, copyFunction: ptr!(0), }; @@ -229,16 +236,16 @@ static mut TYPE_DESCRIPTOR: _TypeDescriptor = _TypeDescriptor { // because Box isn't clonable. macro_rules! define_cleanup { ($abi:tt) => { - unsafe extern $abi fn exception_cleanup(e: *mut [u64; 2]) { - if (*e)[0] != 0 { - cleanup(*e); + unsafe extern $abi fn exception_cleanup(e: *mut Exception) { + if let Some(b) = e.read().data { + drop(b); super::__rust_drop_panic(); } } #[unwind(allowed)] - unsafe extern $abi fn exception_copy(_dest: *mut [u64; 2], - _src: *mut [u64; 2]) - -> *mut [u64; 2] { + unsafe extern $abi fn exception_copy(_dest: *mut Exception, + _src: *mut Exception) + -> *mut Exception { panic!("Rust panics cannot be copied"); } } @@ -258,12 +265,11 @@ pub unsafe fn panic(data: Box) -> u32 { // need to otherwise transfer `data` to the heap. We just pass a stack // pointer to this function. // - // The first argument is the payload being thrown (our two pointers), and - // the second argument is the type information object describing the - // exception (constructed above). - let ptrs = mem::transmute::<_, raw::TraitObject>(data); - let mut ptrs = [ptrs.data as u64, ptrs.vtable as u64]; - let throw_ptr = ptrs.as_mut_ptr() as *mut _; + // The ManuallyDrop is needed here since we don't want Exception to be + // dropped when unwinding. Instead it will be dropped by exception_cleanup + // which is invoked by the C++ runtime. + let mut exception = ManuallyDrop::new(Exception { data: Some(data) }); + let throw_ptr = &mut exception as *mut _ as *mut _; // This... may seems surprising, and justifiably so. On 32-bit MSVC the // pointers between these structure are just that, pointers. On 64-bit MSVC, @@ -311,8 +317,9 @@ pub unsafe fn panic(data: Box) -> u32 { _CxxThrowException(throw_ptr, &mut THROW_INFO as *mut _ as *mut _); } -pub unsafe fn cleanup(payload: [u64; 2]) -> Box { - mem::transmute(raw::TraitObject { data: payload[0] as *mut _, vtable: payload[1] as *mut _ }) +pub unsafe fn cleanup(payload: *mut u8) -> Box { + let exception = &mut *(payload as *mut Exception); + exception.data.take().unwrap() } // This is required by the compiler to exist (e.g., it's a lang item), but diff --git a/src/librustc_codegen_llvm/intrinsic.rs b/src/librustc_codegen_llvm/intrinsic.rs index baaa2a5cf454f..d34540638f183 100644 --- a/src/librustc_codegen_llvm/intrinsic.rs +++ b/src/librustc_codegen_llvm/intrinsic.rs @@ -851,21 +851,21 @@ fn memset_intrinsic( fn try_intrinsic( bx: &mut Builder<'a, 'll, 'tcx>, - func: &'ll Value, + try_func: &'ll Value, data: &'ll Value, - local_ptr: &'ll Value, + catch_func: &'ll Value, dest: &'ll Value, ) { if bx.sess().no_landing_pads() { - bx.call(func, &[data], None); + bx.call(try_func, &[data], None); // Return 0 unconditionally from the intrinsic call; // we can never unwind. let ret_align = bx.tcx().data_layout.i32_align.abi; bx.store(bx.const_i32(0), dest, ret_align); } else if wants_msvc_seh(bx.sess()) { - codegen_msvc_try(bx, func, data, local_ptr, dest); + codegen_msvc_try(bx, try_func, data, catch_func, dest); } else { - codegen_gnu_try(bx, func, data, local_ptr, dest); + codegen_gnu_try(bx, try_func, data, catch_func, dest); } } @@ -878,9 +878,9 @@ fn try_intrinsic( // as the old ones are still more optimized. fn codegen_msvc_try( bx: &mut Builder<'a, 'll, 'tcx>, - func: &'ll Value, + try_func: &'ll Value, data: &'ll Value, - local_ptr: &'ll Value, + catch_func: &'ll Value, dest: &'ll Value, ) { let llfn = get_rust_try_fn(bx, &mut |mut bx| { @@ -892,15 +892,15 @@ fn codegen_msvc_try( let mut catchpad = bx.build_sibling_block("catchpad"); let mut caught = bx.build_sibling_block("caught"); - let func = llvm::get_param(bx.llfn(), 0); + let try_func = llvm::get_param(bx.llfn(), 0); let data = llvm::get_param(bx.llfn(), 1); - let local_ptr = llvm::get_param(bx.llfn(), 2); + let catch_func = llvm::get_param(bx.llfn(), 2); // We're generating an IR snippet that looks like: // - // declare i32 @rust_try(%func, %data, %ptr) { - // %slot = alloca [2 x i64] - // invoke %func(%data) to label %normal unwind label %catchswitch + // declare i32 @rust_try(%try_func, %data, %catch_func) { + // %slot = alloca u8* + // invoke %try_func(%data) to label %normal unwind label %catchswitch // // normal: // ret i32 0 @@ -910,8 +910,8 @@ fn codegen_msvc_try( // // catchpad: // %tok = catchpad within %cs [%type_descriptor, 0, %slot] - // %ptr[0] = %slot[0] - // %ptr[1] = %slot[1] + // %ptr = load %slot + // call %catch_func(%data, %ptr) // catchret from %tok to label %caught // // caught: @@ -928,26 +928,26 @@ fn codegen_msvc_try( // ~rust_panic(); // // uint64_t x[2]; - // } + // }; // - // int bar(void (*foo)(void), uint64_t *ret) { + // int __rust_try( + // void (*try_func)(void*), + // void *data, + // void (*catch_func)(void*, void*) noexcept + // ) { // try { - // foo(); + // try_func(data); // return 0; // } catch(rust_panic& a) { - // ret[0] = a.x[0]; - // ret[1] = a.x[1]; - // a.x[0] = 0; + // catch_func(data, &a); // return 1; // } // } // // More information can be found in libstd's seh.rs implementation. - let i64_2 = bx.type_array(bx.type_i64(), 2); - let i64_2_ptr = bx.type_ptr_to(i64_2); let ptr_align = bx.tcx().data_layout.pointer_align.abi; - let slot = bx.alloca(i64_2_ptr, ptr_align); - bx.invoke(func, &[data], normal.llbb(), catchswitch.llbb(), None); + let slot = bx.alloca(bx.type_i8p(), ptr_align); + bx.invoke(try_func, &[data], normal.llbb(), catchswitch.llbb(), None); normal.ret(bx.const_i32(0)); @@ -987,17 +987,8 @@ fn codegen_msvc_try( // Source: MicrosoftCXXABI::getAddrOfCXXCatchHandlerType in clang let flags = bx.const_i32(8); let funclet = catchpad.catch_pad(cs, &[tydesc, flags, slot]); - let i64_align = bx.tcx().data_layout.i64_align.abi; - let payload_ptr = catchpad.load(slot, ptr_align); - let payload = catchpad.load(payload_ptr, i64_align); - let local_ptr = catchpad.bitcast(local_ptr, bx.type_ptr_to(i64_2)); - catchpad.store(payload, local_ptr, i64_align); - - // Clear the first word of the exception so avoid double-dropping it. - // This will be read by the destructor which is implicitly called at the - // end of the catch block by the runtime. - let payload_0_ptr = catchpad.inbounds_gep(payload_ptr, &[bx.const_i32(0), bx.const_i32(0)]); - catchpad.store(bx.const_u64(0), payload_0_ptr, i64_align); + let ptr = catchpad.load(slot, ptr_align); + catchpad.call(catch_func, &[data, ptr], Some(&funclet)); catchpad.catch_ret(&funclet, caught.llbb()); @@ -1006,7 +997,7 @@ fn codegen_msvc_try( // 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(llfn, &[func, data, local_ptr], None); + let ret = bx.call(llfn, &[try_func, data, catch_func], None); let i32_align = bx.tcx().data_layout.i32_align.abi; bx.store(ret, dest, i32_align); } @@ -1024,38 +1015,34 @@ fn codegen_msvc_try( // the right personality function. fn codegen_gnu_try( bx: &mut Builder<'a, 'll, 'tcx>, - func: &'ll Value, + try_func: &'ll Value, data: &'ll Value, - local_ptr: &'ll Value, + catch_func: &'ll Value, dest: &'ll Value, ) { let llfn = get_rust_try_fn(bx, &mut |mut bx| { // Codegens the shims described above: // // bx: - // invoke %func(%args...) normal %normal unwind %catch + // invoke %func(%data) normal %normal unwind %catch // // normal: // ret 0 // // catch: - // (ptr, _) = landingpad - // store ptr, %local_ptr + // (%ptr, _) = landingpad + // call %catch_func(%data, %ptr) // ret 1 - // - // Note that the `local_ptr` data passed into the `try` intrinsic is - // expected to be `*mut *mut u8` for this to actually work, but that's - // managed by the standard library. bx.sideeffect(); let mut then = bx.build_sibling_block("then"); let mut catch = bx.build_sibling_block("catch"); - let func = llvm::get_param(bx.llfn(), 0); + let try_func = llvm::get_param(bx.llfn(), 0); let data = llvm::get_param(bx.llfn(), 1); - let local_ptr = llvm::get_param(bx.llfn(), 2); - bx.invoke(func, &[data], then.llbb(), catch.llbb(), None); + let catch_func = llvm::get_param(bx.llfn(), 2); + bx.invoke(try_func, &[data], then.llbb(), catch.llbb(), None); then.ret(bx.const_i32(0)); // Type indicator for the exception being thrown. @@ -1075,15 +1062,13 @@ fn codegen_gnu_try( }; catch.add_clause(vals, tydesc); let ptr = catch.extract_value(vals, 0); - let ptr_align = bx.tcx().data_layout.pointer_align.abi; - let bitcast = catch.bitcast(local_ptr, bx.type_ptr_to(bx.type_i8p())); - catch.store(ptr, bitcast, ptr_align); + catch.call(catch_func, &[data, ptr], None); catch.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(llfn, &[func, data, local_ptr], None); + let ret = bx.call(llfn, &[try_func, data, catch_func], None); let i32_align = bx.tcx().data_layout.i32_align.abi; bx.store(ret, dest, i32_align); } @@ -1130,15 +1115,22 @@ fn get_rust_try_fn<'ll, 'tcx>( // Define the type up front for the signature of the rust_try function. let tcx = cx.tcx; let i8p = tcx.mk_mut_ptr(tcx.types.i8); - let fn_ty = tcx.mk_fn_ptr(ty::Binder::bind(tcx.mk_fn_sig( + let try_fn_ty = tcx.mk_fn_ptr(ty::Binder::bind(tcx.mk_fn_sig( iter::once(i8p), tcx.mk_unit(), false, hir::Unsafety::Unsafe, Abi::Rust, ))); + let catch_fn_ty = tcx.mk_fn_ptr(ty::Binder::bind(tcx.mk_fn_sig( + [i8p, i8p].iter().cloned(), + tcx.mk_unit(), + false, + hir::Unsafety::Unsafe, + Abi::Rust, + ))); let output = tcx.types.i32; - let rust_try = gen_fn(cx, "__rust_try", vec![fn_ty, i8p, i8p], output, codegen); + let rust_try = gen_fn(cx, "__rust_try", vec![try_fn_ty, i8p, catch_fn_ty], output, codegen); cx.rust_try_fn.set(Some(rust_try)); rust_try } diff --git a/src/librustc_typeck/check/intrinsic.rs b/src/librustc_typeck/check/intrinsic.rs index 3572eda5c1399..fc2d315b60924 100644 --- a/src/librustc_typeck/check/intrinsic.rs +++ b/src/librustc_typeck/check/intrinsic.rs @@ -297,14 +297,25 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) { "try" => { let mut_u8 = tcx.mk_mut_ptr(tcx.types.u8); - let fn_ty = ty::Binder::bind(tcx.mk_fn_sig( + let try_fn_ty = ty::Binder::bind(tcx.mk_fn_sig( iter::once(mut_u8), tcx.mk_unit(), false, hir::Unsafety::Normal, Abi::Rust, )); - (0, vec![tcx.mk_fn_ptr(fn_ty), mut_u8, mut_u8], tcx.types.i32) + let catch_fn_ty = ty::Binder::bind(tcx.mk_fn_sig( + [mut_u8, mut_u8].iter().cloned(), + tcx.mk_unit(), + false, + hir::Unsafety::Normal, + Abi::Rust, + )); + ( + 0, + vec![tcx.mk_fn_ptr(try_fn_ty), mut_u8, tcx.mk_fn_ptr(catch_fn_ty)], + tcx.types.i32, + ) } "va_start" | "va_end" => match mk_va_list_ty(hir::Mutability::Mut) { diff --git a/src/libstd/panicking.rs b/src/libstd/panicking.rs index 38cb4418dd036..0be71b52d9edd 100644 --- a/src/libstd/panicking.rs +++ b/src/libstd/panicking.rs @@ -12,7 +12,7 @@ use core::panic::{BoxMeUp, Location, PanicInfo}; use crate::any::Any; use crate::fmt; use crate::intrinsics; -use crate::mem::{self, ManuallyDrop, MaybeUninit}; +use crate::mem::{self, ManuallyDrop}; use crate::process; use crate::sync::atomic::{AtomicBool, Ordering}; use crate::sys::stdio::panic_output; @@ -28,9 +28,6 @@ use crate::io::set_panic; #[cfg(test)] use realstd::io::set_panic; -// Include the definition of UnwindPayload from libpanic_unwind. -include!("../libpanic_unwind/payload.rs"); - // Binary interface to the panic runtime that the standard library depends on. // // The standard library is tagged with `#![needs_panic_runtime]` (introduced in @@ -43,9 +40,7 @@ include!("../libpanic_unwind/payload.rs"); // hook up these functions, but it is not this day! #[allow(improper_ctypes)] extern "C" { - /// The payload ptr here is actually the same as the payload ptr for the try - /// intrinsic (i.e., is really `*mut [u64; 2]` or `*mut *mut u8`). - fn __rust_panic_cleanup(payload: TryPayload) -> *mut (dyn Any + Send + 'static); + fn __rust_panic_cleanup(payload: *mut u8) -> *mut (dyn Any + Send + 'static); /// `payload` is actually a `*mut &mut dyn BoxMeUp` but that would cause FFI warnings. /// It cannot be `Box` because the other end of this call does not depend @@ -246,6 +241,7 @@ pub unsafe fn r#try R>(f: F) -> Result> union Data { f: ManuallyDrop, r: ManuallyDrop, + p: ManuallyDrop>, } // We do some sketchy operations with ownership here for the sake of @@ -275,27 +271,57 @@ pub unsafe fn r#try R>(f: F) -> Result> // method of calling a catch panic whilst juggling ownership. let mut data = Data { f: ManuallyDrop::new(f) }; - let mut payload: MaybeUninit = MaybeUninit::uninit(); - let data_ptr = &mut data as *mut _ as *mut u8; - let payload_ptr = payload.as_mut_ptr() as *mut _; - return if intrinsics::r#try(do_call::, data_ptr, payload_ptr) == 0 { + return if do_try(do_call::, data_ptr, do_catch::) == 0 { Ok(ManuallyDrop::into_inner(data.r)) } else { - Err(cleanup(payload.assume_init())) + Err(ManuallyDrop::into_inner(data.p)) }; + // Compatibility wrapper around the try intrinsic for bootstrap + #[inline] + unsafe fn do_try(try_fn: fn(*mut u8), data: *mut u8, catch_fn: fn(*mut u8, *mut u8)) -> i32 { + #[cfg(not(bootstrap))] + { + intrinsics::r#try(try_fn, data, catch_fn) + } + #[cfg(bootstrap)] + { + use crate::mem::MaybeUninit; + #[cfg(target_env = "msvc")] + type TryPayload = [u64; 2]; + #[cfg(not(target_env = "msvc"))] + type TryPayload = *mut u8; + + let mut payload: MaybeUninit = MaybeUninit::uninit(); + let payload_ptr = payload.as_mut_ptr() as *mut u8; + let r = intrinsics::r#try(try_fn, data, payload_ptr); + if r != 0 { + #[cfg(target_env = "msvc")] + { + catch_fn(data, payload_ptr) + } + #[cfg(not(target_env = "msvc"))] + { + catch_fn(data, payload.assume_init()) + } + } + r + } + } + // We consider unwinding to be rare, so mark this function as cold. However, // do not mark it no-inline -- that decision is best to leave to the // optimizer (in most cases this function is not inlined even as a normal, // non-cold function, though, as of the writing of this comment). #[cold] - unsafe fn cleanup(payload: TryPayload) -> Box { + unsafe fn cleanup(payload: *mut u8) -> Box { let obj = Box::from_raw(__rust_panic_cleanup(payload)); update_panic_count(-1); obj } + #[inline] fn do_call R, R>(data: *mut u8) { unsafe { let data = data as *mut Data; @@ -304,6 +330,19 @@ pub unsafe fn r#try R>(f: F) -> Result> data.r = ManuallyDrop::new(f()); } } + + // We *do* want this part of the catch to be inlined: this allows the + // compiler to properly track accesses to the Data union and optimize it + // away most of the time. + #[inline] + fn do_catch R, R>(data: *mut u8, payload: *mut u8) { + unsafe { + let data = data as *mut Data; + let data = &mut (*data); + let obj = cleanup(payload); + data.p = ManuallyDrop::new(obj); + } + } } /// Determines whether the current thread is unwinding because of panic. diff --git a/src/test/codegen/try-panic-abort.rs b/src/test/codegen/try-panic-abort.rs index 9bc89a321576c..166d2bb99426d 100644 --- a/src/test/codegen/try-panic-abort.rs +++ b/src/test/codegen/try-panic-abort.rs @@ -7,11 +7,14 @@ extern "C" { #[unwind(allow)] fn bar(data: *mut u8); } +extern "Rust" { + fn catch(data: *mut u8, exception: *mut u8); +} // CHECK-LABEL: @foo #[no_mangle] pub unsafe fn foo() -> i32 { // CHECK: call void @bar // CHECK: ret i32 0 - std::intrinsics::r#try(|x| bar(x), 0 as *mut u8, 0 as *mut u8) + std::intrinsics::r#try(|x| bar(x), 0 as *mut u8, |x, y| catch(x, y)) }