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.
compute_handler(store: &mut dyn VMStore) -> Option<Handler>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