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