wasmtime/runtime/vm/
throw.rs

1//! Exception-throw logic for Wasm exceptions.
2
3use super::{VMContext, VMStore};
4use crate::{store::AutoAssertNoGc, vm::Instance};
5use core::ptr::NonNull;
6use wasmtime_environ::TagIndex;
7use 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.
15pub 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 = nogc.modules().lookup_module_by_pc(frame.pc())?;
58        let base = module.code_object().code_memory().text().as_ptr() as usize;
59        let rel_pc = u32::try_from(frame.pc().wrapping_sub(base)).expect("Module larger than 4GiB");
60        let et = module.exception_table();
61        let (frame_offset, handlers) = et.lookup_pc(rel_pc);
62        let fp_to_sp = frame_offset.map(|frame_offset| -isize::try_from(frame_offset).unwrap());
63        for handler in handlers {
64            log::trace!("-> checking handler: {handler:?}");
65            let is_match = match handler.tag {
66                // Catch-all/default handler. Always come last in sequence.
67                None => true,
68                Some(module_local_tag_index) => {
69                    let fp_to_sp =
70                        fp_to_sp.expect("frame offset is necessary for exception unwind");
71                    let fp_offset = fp_to_sp
72                        + isize::try_from(
73                            handler
74                                .context_sp_offset
75                                .expect("dynamic context not present for handler record"),
76                        )
77                        .unwrap();
78                    let frame_vmctx = unsafe { frame.read_slot_from_fp(fp_offset) };
79                    log::trace!("-> read vmctx from frame: {frame_vmctx:x}");
80                    let frame_vmctx =
81                        NonNull::new(frame_vmctx as *mut VMContext).expect("null vmctx in frame");
82
83                    // SAFETY: we use `Instance::from_vmctx` to get a
84                    // `NonNull<Instance>` from a raw vmctx we read off the
85                    // stack frame. That method's safety requirements are that
86                    // the `vmctx` is a valid vmctx allocation which should be
87                    // true of all frames on the stack.
88                    //
89                    // Next the `.as_ref()` call enables reading this pointer,
90                    // and the validity of this relies on the fact that all wasm
91                    // frames for this activation belong to the same store and
92                    // there's no other active instance borrows at this time.
93                    // This function takes `&mut dyn VMStore` representing no
94                    // other active borrows, and internally the borrow is scoped
95                    // to this one block.
96                    let (handler_tag_instance, handler_tag_index) = unsafe {
97                        let store_id = nogc.id();
98                        let instance = Instance::from_vmctx(frame_vmctx);
99                        let tag = instance
100                            .as_ref()
101                            .get_exported_tag(store_id, TagIndex::from_u32(module_local_tag_index));
102                        tag.to_raw_indices()
103                    };
104                    log::trace!(
105                        "-> handler's tag {module_local_tag_index:?} resolves to instance {handler_tag_instance:?} defined-tag {handler_tag_index:?}"
106                    );
107
108                    handler_tag_instance == throwing_tag_instance_id
109                        && handler_tag_index == throwing_tag_defined_tag_index
110                }
111            };
112            if is_match {
113                let fp_to_sp = fp_to_sp.expect("frame offset must be known if we found a handler");
114                return Some((
115                    base.wrapping_add(
116                        usize::try_from(handler.handler_offset).expect("Module larger than usize"),
117                    ),
118                    frame.fp().wrapping_add_signed(fp_to_sp),
119                ));
120            }
121        }
122        None
123    };
124    let unwinder = nogc.unwinder();
125    let action = unsafe { Handler::find(unwinder, handler_lookup, exit_pc, exit_fp, entry_fp) };
126    log::trace!("throw action: {action:?}");
127    action
128}