1 //! Exception-throw logic for Wasm exceptions. 2 3 use core::ptr::NonNull; 4 5 use wasmtime_environ::TagIndex; 6 use wasmtime_unwinder::{Frame, ThrowAction}; 7 8 use super::{VMContext, VMStore}; 9 use crate::{store::AutoAssertNoGc, vm::Instance}; 10 11 /// Compute the target of the pending exception on the store. 12 /// 13 /// # Safety 14 /// 15 /// The stored last-exit state in `store` either must be valid, or 16 /// must have a zeroed exit FP if no Wasm is on the stack. 17 pub unsafe fn compute_throw_action(store: &mut dyn VMStore) -> ThrowAction { 18 let mut nogc = AutoAssertNoGc::new(store.store_opaque_mut()); 19 20 // Get the tag identity relative to the store. 21 22 // Temporarily take, to avoid borrowing issues. 23 let exnref = nogc 24 .take_pending_exception() 25 .expect("Only invoked when an exception is pending"); 26 let (throwing_tag_instance_id, throwing_tag_defined_tag_index) = 27 exnref.tag(&mut nogc).expect("cannot read tag"); 28 nogc.set_pending_exception(exnref); 29 log::trace!( 30 "throwing: tag defined in instance {throwing_tag_instance_id:?} defined-tag {throwing_tag_defined_tag_index:?}" 31 ); 32 33 // Get the state needed for a stack walk. 34 let (exit_pc, exit_fp, entry_fp) = unsafe { 35 ( 36 *nogc.vm_store_context().last_wasm_exit_pc.get(), 37 nogc.vm_store_context().last_wasm_exit_fp(), 38 *nogc.vm_store_context().last_wasm_entry_fp.get(), 39 ) 40 }; 41 42 // Early out: if there is no exit FP -- which can happen if a host 43 // func, wrapped up as a `Func`, is called directly via 44 // `Func::call` -- then the only possible action we can take is 45 // `None` (i.e., no handler, unwind to entry from host). 46 if exit_fp == 0 { 47 return ThrowAction::None; 48 } 49 50 // Walk the stack, looking up the module with each PC, and using 51 // that module to resolve local tag indices into (instance, tag) 52 // tuples. 53 let handler_lookup = |frame: &Frame| -> Option<(usize, usize)> { 54 log::trace!( 55 "exception-throw stack walk: frame at FP={:x} PC={:x}", 56 frame.fp(), 57 frame.pc() 58 ); 59 let module = nogc.modules().lookup_module_by_pc(frame.pc())?; 60 let base = module.code_object().code_memory().text().as_ptr() as usize; 61 let rel_pc = u32::try_from(frame.pc().wrapping_sub(base)).expect("Module larger than 4GiB"); 62 let et = module.exception_table(); 63 let (frame_offset, handlers) = et.lookup_pc(rel_pc); 64 let fp_to_sp = frame_offset.map(|frame_offset| -isize::try_from(frame_offset).unwrap()); 65 for handler in handlers { 66 log::trace!("-> checking handler: {handler:?}"); 67 let is_match = match handler.tag { 68 // Catch-all/default handler. Always come last in sequence. 69 None => true, 70 Some(module_local_tag_index) => { 71 let fp_to_sp = 72 fp_to_sp.expect("frame offset is necessary for exception unwind"); 73 let fp_offset = fp_to_sp 74 + isize::try_from( 75 handler 76 .context_sp_offset 77 .expect("dynamic context not present for handler record"), 78 ) 79 .unwrap(); 80 let frame_vmctx = unsafe { frame.read_slot_from_fp(fp_offset) }; 81 log::trace!("-> read vmctx from frame: {frame_vmctx:x}"); 82 let frame_vmctx = 83 NonNull::new(frame_vmctx as *mut VMContext).expect("null vmctx in frame"); 84 85 // SAFETY: we use `Instance::from_vmctx` to get an 86 // `&Instance` from a raw vmctx we read off the 87 // stack frame. That method's safety requirements 88 // are that the `vmctx` is a valid vmctx belonging 89 // to an instance in the store (`nogc`). This is 90 // satisfied because every Wasm frame in this 91 // activation must belong to an instance in this 92 // store: we do not permit cross-store calls 93 // without exiting through host code. 94 let (handler_tag_instance, handler_tag_index) = unsafe { 95 let store_id = nogc.id(); 96 let instance = Instance::from_vmctx(&nogc, frame_vmctx); 97 let tag = instance 98 .get_exported_tag(store_id, TagIndex::from_u32(module_local_tag_index)); 99 tag.to_raw_indices() 100 }; 101 log::trace!( 102 "-> handler's tag {module_local_tag_index:?} resolves to instance {handler_tag_instance:?} defined-tag {handler_tag_index:?}" 103 ); 104 105 handler_tag_instance == throwing_tag_instance_id 106 && handler_tag_index == throwing_tag_defined_tag_index 107 } 108 }; 109 if is_match { 110 let fp_to_sp = fp_to_sp.expect("frame offset must be known if we found a handler"); 111 return Some(( 112 base.wrapping_add( 113 usize::try_from(handler.handler_offset).expect("Module larger than usize"), 114 ), 115 frame.fp().wrapping_add_signed(fp_to_sp), 116 )); 117 } 118 } 119 None 120 }; 121 let unwinder = nogc.unwinder(); 122 let action = unsafe { 123 wasmtime_unwinder::compute_throw_action( 124 unwinder, 125 handler_lookup, 126 exit_pc, 127 exit_fp, 128 entry_fp, 129 ) 130 }; 131 132 log::trace!("throw action: {action:?}"); 133 action 134 } 135