wasmtime/trampoline/
func.rs

1//! Support for a calling of an imported function.
2
3use crate::{Engine, FuncType, ValRaw};
4use anyhow::Result;
5use std::panic::{self, AssertUnwindSafe};
6use std::ptr::NonNull;
7use wasmtime_jit::{CodeMemory, ProfilingAgent};
8use wasmtime_runtime::{
9    VMContext, VMHostFuncContext, VMOpaqueContext, VMSharedSignatureIndex, VMTrampoline,
10};
11
12struct TrampolineState<F> {
13    func: F,
14    #[allow(dead_code)]
15    code_memory: CodeMemory,
16}
17
18unsafe extern "C" fn stub_fn<F>(
19    vmctx: *mut VMOpaqueContext,
20    caller_vmctx: *mut VMContext,
21    values_vec: *mut ValRaw,
22    values_vec_len: usize,
23) where
24    F: Fn(*mut VMContext, &mut [ValRaw]) -> Result<()> + 'static,
25{
26    // Here we are careful to use `catch_unwind` to ensure Rust panics don't
27    // unwind past us. The primary reason for this is that Rust considers it UB
28    // to unwind past an `extern "C"` function. Here we are in an `extern "C"`
29    // function and the cross into wasm was through an `extern "C"` function at
30    // the base of the stack as well. We'll need to wait for assorted RFCs and
31    // language features to enable this to be done in a sound and stable fashion
32    // before avoiding catching the panic here.
33    //
34    // Also note that there are intentionally no local variables on this stack
35    // frame. The reason for that is that some of the "raise" functions we have
36    // below will trigger a longjmp, which won't run local destructors if we
37    // have any. To prevent leaks we avoid having any local destructors by
38    // avoiding local variables.
39    let result = panic::catch_unwind(AssertUnwindSafe(|| {
40        let vmctx = VMHostFuncContext::from_opaque(vmctx);
41        // Double-check ourselves in debug mode, but we control
42        // the `Any` here so an unsafe downcast should also
43        // work.
44        let state = (*vmctx).host_state();
45        debug_assert!(state.is::<TrampolineState<F>>());
46        let state = &*(state as *const _ as *const TrampolineState<F>);
47        let values_vec = std::slice::from_raw_parts_mut(values_vec, values_vec_len);
48        (state.func)(caller_vmctx, values_vec)
49    }));
50
51    match result {
52        Ok(Ok(())) => {}
53
54        // If a trap was raised (an error returned from the imported function)
55        // then we smuggle the trap through `Box<dyn Error>` through to the
56        // call-site, which gets unwrapped in `Trap::from_runtime` later on as we
57        // convert from the internal `Trap` type to our own `Trap` type in this
58        // crate.
59        Ok(Err(trap)) => crate::trap::raise(trap.into()),
60
61        // And finally if the imported function panicked, then we trigger the
62        // form of unwinding that's safe to jump over wasm code on all
63        // platforms.
64        Err(panic) => wasmtime_runtime::resume_panic(panic),
65    }
66}
67
68#[cfg(compiler)]
69fn register_trampolines(profiler: &dyn ProfilingAgent, code: &CodeMemory) {
70    use object::{File, Object as _, ObjectSection, ObjectSymbol, SectionKind, SymbolKind};
71    let pid = std::process::id();
72    let tid = pid;
73
74    let image = match File::parse(&code.mmap()[..]) {
75        Ok(image) => image,
76        Err(_) => return,
77    };
78
79    let text_base = match image.sections().find(|s| s.kind() == SectionKind::Text) {
80        Some(section) => match section.data() {
81            Ok(data) => data.as_ptr() as usize,
82            Err(_) => return,
83        },
84        None => return,
85    };
86
87    for sym in image.symbols() {
88        if !sym.is_definition() {
89            continue;
90        }
91        if sym.kind() != SymbolKind::Text {
92            continue;
93        }
94        let address = sym.address();
95        let size = sym.size();
96        if address == 0 || size == 0 {
97            continue;
98        }
99        if let Ok(name) = sym.name() {
100            let addr = text_base + address as usize;
101            profiler.load_single_trampoline(name, addr as *const u8, size as usize, pid, tid);
102        }
103    }
104}
105
106#[cfg(compiler)]
107pub fn create_function<F>(
108    ft: &FuncType,
109    func: F,
110    engine: &Engine,
111) -> Result<(Box<VMHostFuncContext>, VMSharedSignatureIndex, VMTrampoline)>
112where
113    F: Fn(*mut VMContext, &mut [ValRaw]) -> Result<()> + Send + Sync + 'static,
114{
115    let mut obj = engine
116        .compiler()
117        .object(wasmtime_environ::ObjectKind::Module)?;
118    let (t1, t2) = engine.compiler().emit_trampoline_obj(
119        ft.as_wasm_func_type(),
120        stub_fn::<F> as usize,
121        &mut obj,
122    )?;
123    engine.append_bti(&mut obj);
124    let obj = wasmtime_jit::ObjectBuilder::new(obj, &engine.config().tunables).finish()?;
125
126    // Copy the results of JIT compilation into executable memory, and this will
127    // also take care of unwind table registration.
128    let mut code_memory = CodeMemory::new(obj)?;
129    code_memory.publish()?;
130
131    register_trampolines(engine.profiler(), &code_memory);
132
133    // Extract the host/wasm trampolines from the results of compilation since
134    // we know their start/length.
135
136    let text = code_memory.text();
137    let host_trampoline = text[t1.start as usize..][..t1.length as usize].as_ptr();
138    let wasm_trampoline = text[t2.start as usize..].as_ptr() as *mut _;
139    let wasm_trampoline = NonNull::new(wasm_trampoline).unwrap();
140
141    let sig = engine.signatures().register(ft.as_wasm_func_type());
142
143    unsafe {
144        let ctx = VMHostFuncContext::new(
145            wasm_trampoline,
146            sig,
147            Box::new(TrampolineState { func, code_memory }),
148        );
149        let host_trampoline = std::mem::transmute::<*const u8, VMTrampoline>(host_trampoline);
150        Ok((ctx, sig, host_trampoline))
151    }
152}